import json import re import shutil import subprocess import sys from pathlib import Path CHECKS = ("lint", "build", "test") def _repo_root(cwd: Path) -> Path: try: root = subprocess.check_output( ["git", "rev-parse", "--show-toplevel"], cwd=cwd, text=True, stderr=subprocess.DEVNULL, ).strip() except (subprocess.CalledProcessError, FileNotFoundError): return cwd return Path(root) def _load_scripts(root: Path) -> dict[str, str]: package_json = root / "package.json" if not package_json.exists(): return {} try: package = json.loads(package_json.read_text(encoding="utf-8")) except json.JSONDecodeError as exc: _deny(f"Invalid package.json: {exc}") raise SystemExit(0) from exc scripts = package.get("scripts", {}) if not isinstance(scripts, dict): return {} return {str(name): str(command) for name, command in scripts.items()} def _is_git_commit(command: str) -> bool: return re.search(r"\bgit(?:\s+(?:-[A-Za-z]\s+\S+|--[A-Za-z0-9-]+(?:=\S+)?))*\s+commit\b", command) is not None def _deny(reason: str) -> None: print( json.dumps( { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": reason, } } ) ) def _tail(text: str, limit: int = 1200) -> str: text = text.strip() if len(text) <= limit: return text return text[-limit:] def _run_checks(root: Path, scripts: dict[str, str]) -> str | None: npm = shutil.which("npm") or shutil.which("npm.cmd") if npm is None: return "npm was not found, so pre-commit checks could not run." for check in CHECKS: if check not in scripts: continue result = subprocess.run( [npm, "run", check], cwd=root, capture_output=True, text=True, ) if result.returncode != 0: details = _tail(result.stdout + "\n" + result.stderr) if details: return f"npm run {check} failed:\n{details}" return f"npm run {check} failed with exit code {result.returncode}." return None def main() -> int: try: payload = json.load(sys.stdin) except json.JSONDecodeError: return 0 command = payload.get("tool_input", {}).get("command", "") if not isinstance(command, str) or not _is_git_commit(command): return 0 cwd = Path(payload.get("cwd") or Path.cwd()) root = _repo_root(cwd) failure = _run_checks(root, _load_scripts(root)) if failure: _deny(f"PRE-COMMIT CHECKS: {failure}") return 0 if __name__ == "__main__": raise SystemExit(main())