add brain
This commit is contained in:
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Detect project stack/tooling signals for CI/CD pipeline generation.
|
||||
|
||||
Input sources:
|
||||
- repository scan via --repo
|
||||
- JSON via --input file
|
||||
- JSON via stdin
|
||||
|
||||
Output:
|
||||
- text summary or JSON payload
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from dataclasses import dataclass, asdict
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
|
||||
class CLIError(Exception):
|
||||
"""Raised for expected CLI failures."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class StackReport:
|
||||
repo: str
|
||||
languages: List[str]
|
||||
package_managers: List[str]
|
||||
ci_targets: List[str]
|
||||
test_commands: List[str]
|
||||
build_commands: List[str]
|
||||
lint_commands: List[str]
|
||||
signals: Dict[str, bool]
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Detect stack/tooling from a repository.")
|
||||
parser.add_argument("--input", help="JSON input file (precomputed signal payload).")
|
||||
parser.add_argument("--repo", default=".", help="Repository path to scan.")
|
||||
parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format.")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def load_payload(input_path: Optional[str]) -> Optional[dict]:
|
||||
if input_path:
|
||||
try:
|
||||
return json.loads(Path(input_path).read_text(encoding="utf-8"))
|
||||
except Exception as exc:
|
||||
raise CLIError(f"Failed reading --input file: {exc}") from exc
|
||||
|
||||
if not sys.stdin.isatty():
|
||||
raw = sys.stdin.read().strip()
|
||||
if raw:
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise CLIError(f"Invalid JSON from stdin: {exc}") from exc
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def read_package_scripts(repo: Path) -> Dict[str, str]:
|
||||
pkg = repo / "package.json"
|
||||
if not pkg.exists():
|
||||
return {}
|
||||
try:
|
||||
data = json.loads(pkg.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return {}
|
||||
scripts = data.get("scripts", {})
|
||||
return scripts if isinstance(scripts, dict) else {}
|
||||
|
||||
|
||||
def detect(repo: Path) -> StackReport:
|
||||
signals = {
|
||||
"package_json": (repo / "package.json").exists(),
|
||||
"pnpm_lock": (repo / "pnpm-lock.yaml").exists(),
|
||||
"yarn_lock": (repo / "yarn.lock").exists(),
|
||||
"npm_lock": (repo / "package-lock.json").exists(),
|
||||
"pyproject": (repo / "pyproject.toml").exists(),
|
||||
"requirements": (repo / "requirements.txt").exists(),
|
||||
"go_mod": (repo / "go.mod").exists(),
|
||||
"dockerfile": (repo / "Dockerfile").exists(),
|
||||
"vercel": (repo / "vercel.json").exists(),
|
||||
"helm": (repo / "helm").exists() or (repo / "charts").exists(),
|
||||
"k8s": (repo / "k8s").exists() or (repo / "kubernetes").exists(),
|
||||
}
|
||||
|
||||
languages: List[str] = []
|
||||
package_managers: List[str] = []
|
||||
ci_targets: List[str] = ["github", "gitlab"]
|
||||
|
||||
if signals["package_json"]:
|
||||
languages.append("node")
|
||||
if signals["pnpm_lock"]:
|
||||
package_managers.append("pnpm")
|
||||
elif signals["yarn_lock"]:
|
||||
package_managers.append("yarn")
|
||||
else:
|
||||
package_managers.append("npm")
|
||||
|
||||
if signals["pyproject"] or signals["requirements"]:
|
||||
languages.append("python")
|
||||
package_managers.append("pip")
|
||||
|
||||
if signals["go_mod"]:
|
||||
languages.append("go")
|
||||
|
||||
scripts = read_package_scripts(repo)
|
||||
lint_commands: List[str] = []
|
||||
test_commands: List[str] = []
|
||||
build_commands: List[str] = []
|
||||
|
||||
if "lint" in scripts:
|
||||
lint_commands.append("npm run lint")
|
||||
if "test" in scripts:
|
||||
test_commands.append("npm test")
|
||||
if "build" in scripts:
|
||||
build_commands.append("npm run build")
|
||||
|
||||
if "python" in languages:
|
||||
lint_commands.append("python3 -m ruff check .")
|
||||
test_commands.append("python3 -m pytest")
|
||||
|
||||
if "go" in languages:
|
||||
lint_commands.append("go vet ./...")
|
||||
test_commands.append("go test ./...")
|
||||
build_commands.append("go build ./...")
|
||||
|
||||
return StackReport(
|
||||
repo=str(repo.resolve()),
|
||||
languages=sorted(set(languages)),
|
||||
package_managers=sorted(set(package_managers)),
|
||||
ci_targets=ci_targets,
|
||||
test_commands=sorted(set(test_commands)),
|
||||
build_commands=sorted(set(build_commands)),
|
||||
lint_commands=sorted(set(lint_commands)),
|
||||
signals=signals,
|
||||
)
|
||||
|
||||
|
||||
def format_text(report: StackReport) -> str:
|
||||
lines = [
|
||||
"Detected stack",
|
||||
f"- repo: {report.repo}",
|
||||
f"- languages: {', '.join(report.languages) if report.languages else 'none'}",
|
||||
f"- package managers: {', '.join(report.package_managers) if report.package_managers else 'none'}",
|
||||
f"- lint commands: {', '.join(report.lint_commands) if report.lint_commands else 'none'}",
|
||||
f"- test commands: {', '.join(report.test_commands) if report.test_commands else 'none'}",
|
||||
f"- build commands: {', '.join(report.build_commands) if report.build_commands else 'none'}",
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
payload = load_payload(args.input)
|
||||
|
||||
if payload:
|
||||
try:
|
||||
report = StackReport(**payload)
|
||||
except TypeError as exc:
|
||||
raise CLIError(f"Invalid input payload for StackReport: {exc}") from exc
|
||||
else:
|
||||
repo = Path(args.repo).resolve()
|
||||
if not repo.exists() or not repo.is_dir():
|
||||
raise CLIError(f"Invalid repo path: {repo}")
|
||||
report = detect(repo)
|
||||
|
||||
if args.format == "json":
|
||||
print(json.dumps(asdict(report), indent=2))
|
||||
else:
|
||||
print(format_text(report))
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except CLIError as exc:
|
||||
print(f"ERROR: {exc}", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
Reference in New Issue
Block a user