mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
The pre-push hook runs lint/typecheck/test/build checks on every push, including `git push origin --delete <branch>`. Branch deletion does not push any code, so verification checks are unnecessary and block the delete operation. Detect deletion pushes by reading stdin (local sha is all zeros for deletes) and exit early. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
125 lines
3.2 KiB
Bash
125 lines
3.2 KiB
Bash
#!/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
|
|
exit 0
|
|
fi
|
|
|
|
if [[ -f ".ecc-hooks-disable" || -f ".git/ecc-hooks-disable" ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
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_node_script() {
|
|
local pm="$1"
|
|
local script_name="$2"
|
|
case "$pm" in
|
|
pnpm) 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
|
|
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
|
|
|
|
if [[ "${ECC_PREPUSH_AUDIT:-0}" == "1" ]]; then
|
|
ran_any_check=1
|
|
log "Running dependency audit (ECC_PREPUSH_AUDIT=1)"
|
|
case "$pm" in
|
|
pnpm) 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
|
|
|
|
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
|
|
|
|
if [[ "$ran_any_check" -eq 0 ]]; then
|
|
log "No supported checks found in this repository. Skipping."
|
|
else
|
|
log "Verification checks passed."
|
|
fi
|
|
|
|
exit 0
|