mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-20 16:47:59 +02:00
- pre-push: send skip diagnostic to stderr (not stdout) so consumers relying on stderr for warnings receive the message - pre-push: move ECC_PREPUSH_AUDIT outside RUN_CHECKS gate so audit-only configurations still check dependencies - mcp-health-check: classify home config paths as trusted before applying workspace opt-in gate; when cwd == home, ~/.claude.json was incorrectly blocked as untrusted workspace config - install-global-git-hooks: check conflicting global core.hooksPath in dry-run mode too, so dry-run accurately reflects what apply would do
153 lines
4.5 KiB
Bash
Executable File
153 lines
4.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ECC Codex Git Hook: pre-push
|
|
# Runs a lightweight verification flow before pushes.
|
|
|
|
if [[ "${ECC_SKIP_GIT_HOOKS:-0}" == "1" || "${ECC_SKIP_PREPUSH:-0}" == "1" ]]; then
|
|
printf '[ECC pre-push] WARNING: hook bypassed via env (ECC_SKIP_*=1)\n' >&2
|
|
exit 0
|
|
fi
|
|
|
|
# NOTE: file-based disables (.ecc-hooks-disable) were removed — a malicious
|
|
# repo could ship that file and silently disable verification.
|
|
|
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
exit 0
|
|
fi
|
|
|
|
# Skip checks for branch deletion pushes (e.g., git push origin --delete <branch>).
|
|
# The pre-push hook receives lines on stdin: <local ref> <local sha> <remote ref> <remote sha>.
|
|
# For deletions, the local sha is the zero OID.
|
|
is_delete_only=true
|
|
while read -r _local_ref local_sha _remote_ref _remote_sha; do
|
|
if [[ "$local_sha" != "0000000000000000000000000000000000000000" ]]; then
|
|
is_delete_only=false
|
|
break
|
|
fi
|
|
done
|
|
if [[ "$is_delete_only" == "true" ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
ran_any_check=0
|
|
|
|
log() {
|
|
printf '[ECC pre-push] %s\n' "$*"
|
|
}
|
|
|
|
fail() {
|
|
printf '[ECC pre-push] FAILED: %s\n' "$*" >&2
|
|
exit 1
|
|
}
|
|
|
|
detect_pm() {
|
|
if [[ -f "pnpm-lock.yaml" ]]; then
|
|
echo "pnpm"
|
|
elif [[ -f "bun.lockb" ]]; then
|
|
echo "bun"
|
|
elif [[ -f "yarn.lock" ]]; then
|
|
echo "yarn"
|
|
elif [[ -f "package-lock.json" ]]; then
|
|
echo "npm"
|
|
else
|
|
echo "npm"
|
|
fi
|
|
}
|
|
|
|
has_node_script() {
|
|
local script_name="$1"
|
|
node -e 'const fs=require("fs"); const p=JSON.parse(fs.readFileSync("package.json","utf8")); process.exit(p.scripts && p.scripts[process.argv[1]] ? 0 : 1)' "$script_name" >/dev/null 2>&1
|
|
}
|
|
|
|
run_pnpm() {
|
|
if command -v corepack >/dev/null 2>&1; then
|
|
# Corepack may download the pinned pnpm version on a cache miss. Set
|
|
# COREPACK_ENABLE_NETWORK=0 to make an offline cache miss fail immediately.
|
|
corepack pnpm "$@"
|
|
elif command -v pnpm >/dev/null 2>&1; then
|
|
pnpm "$@"
|
|
else
|
|
fail "pnpm could not be resolved from PATH or Corepack"
|
|
fi
|
|
}
|
|
|
|
run_node_script() {
|
|
local pm="$1"
|
|
local script_name="$2"
|
|
case "$pm" in
|
|
pnpm) run_pnpm run "$script_name" ;;
|
|
bun) bun run "$script_name" ;;
|
|
yarn) yarn "$script_name" ;;
|
|
npm) npm run "$script_name" ;;
|
|
*) npm run "$script_name" ;;
|
|
esac
|
|
}
|
|
|
|
if [[ -f "package.json" ]]; then
|
|
# SECURITY: executing a cloned repo's lint/test/build scripts on push is
|
|
# arbitrary code execution (package.json scripts run as you). Opt-in only:
|
|
# set ECC_PREPUSH_RUN_CHECKS=1 for repos you trust.
|
|
if [[ "${ECC_PREPUSH_RUN_CHECKS:-0}" != "1" ]]; then
|
|
printf '[ECC pre-push] Node project detected but ECC_PREPUSH_RUN_CHECKS!=1; skipping repo script execution (set =1 to opt in).\n' >&2
|
|
else
|
|
pm="$(detect_pm)"
|
|
log "Node project detected (package manager: $pm)"
|
|
|
|
for script_name in lint typecheck test build; do
|
|
if has_node_script "$script_name"; then
|
|
ran_any_check=1
|
|
log "Running: $script_name"
|
|
run_node_script "$pm" "$script_name" || fail "$script_name failed"
|
|
else
|
|
log "Skipping missing script: $script_name"
|
|
fi
|
|
done
|
|
|
|
fi
|
|
if [[ "${ECC_PREPUSH_AUDIT:-0}" == "1" ]]; then
|
|
pm="${pm:-$(detect_pm)}"
|
|
ran_any_check=1
|
|
log "Running dependency audit (ECC_PREPUSH_AUDIT=1)"
|
|
case "$pm" in
|
|
pnpm) run_pnpm audit --prod || fail "pnpm audit failed" ;;
|
|
bun) bun audit || fail "bun audit failed" ;;
|
|
yarn) yarn npm audit --recursive || fail "yarn audit failed" ;;
|
|
npm) npm audit --omit=dev || fail "npm audit failed" ;;
|
|
*) npm audit --omit=dev || fail "npm audit failed" ;;
|
|
esac
|
|
fi
|
|
fi
|
|
|
|
# SECURITY: go test / pytest execute repo-controlled code (TestMain,
|
|
# conftest.py). Same opt-in gate as Node scripts above.
|
|
if [[ "${ECC_PREPUSH_RUN_CHECKS:-0}" == "1" ]]; then
|
|
if [[ -f "go.mod" ]] && command -v go >/dev/null 2>&1; then
|
|
ran_any_check=1
|
|
log "Go project detected. Running: go test ./..."
|
|
go test ./... || fail "go test failed"
|
|
fi
|
|
|
|
if [[ -f "pyproject.toml" || -f "requirements.txt" ]]; then
|
|
if command -v pytest >/dev/null 2>&1; then
|
|
ran_any_check=1
|
|
log "Python project detected. Running: pytest -q"
|
|
pytest -q || fail "pytest failed"
|
|
else
|
|
log "Python project detected but pytest is not installed. Skipping."
|
|
fi
|
|
fi
|
|
else
|
|
if [[ -f "go.mod" || -f "pyproject.toml" || -f "requirements.txt" ]]; then
|
|
log "Go/Python project detected but ECC_PREPUSH_RUN_CHECKS!=1; skipping test execution."
|
|
fi
|
|
fi
|
|
|
|
if [[ "$ran_any_check" -eq 0 ]]; then
|
|
log "No supported checks found in this repository. Skipping."
|
|
else
|
|
log "Verification checks passed."
|
|
fi
|
|
|
|
exit 0
|