|
#!/usr/bin/env bash |
|
|
|
# Bash settings (fail fast, but with helpful diagnostics) |
|
set -o errexit |
|
set -o nounset |
|
set -o pipefail |
|
|
|
# --- Debug helpers ----------------------------------------------------------- |
|
|
|
DEBUG="${DEBUG:-0}" |
|
|
|
log() { printf '%s\n' "$*"; } |
|
debug() { if [[ "$DEBUG" == "1" ]]; then printf '[debug] %s\n' "$*"; fi } |
|
|
|
die() { |
|
printf '[pre-push] ERROR: %s\n' "$*" >&2 |
|
exit 1 |
|
} |
|
|
|
on_error() { |
|
local exit_code="$?" |
|
local line_no="${BASH_LINENO[0]:-?}" |
|
local cmd="${BASH_COMMAND:-?}" |
|
printf '[pre-push] FAILED (exit=%s) at line %s: %s\n' "$exit_code" "$line_no" "$cmd" >&2 |
|
exit "$exit_code" |
|
} |
|
trap on_error ERR |
|
|
|
# Print some context when debugging is enabled |
|
if [[ "$DEBUG" == "1" ]]; then |
|
set -x |
|
log ">>> Debug enabled (DEBUG=1)" |
|
log ">>> Shell: ${BASH:-unknown}" |
|
log ">>> PWD: $(pwd)" |
|
log ">>> Git: $(command -v git || true)" |
|
git --version || true |
|
log ">>> Hook: $0" |
|
fi |
|
|
|
# --- Assumption checks ------------------------------------------------------- |
|
|
|
# Ensure we're in a git repo (hooks normally are, but good to validate). |
|
git_root="$(git rev-parse --show-toplevel 2>/dev/null || true)" |
|
[[ -n "$git_root" ]] || die "Not inside a Git repository (git rev-parse failed)." |
|
|
|
debug "git_root=$git_root" |
|
cd "$git_root" |
|
|
|
push_input="$(mktemp)" |
|
trap 'rm -f "$push_input"' EXIT |
|
cat > "$push_input" |
|
|
|
# Choose npm runner (plain npm or ddev npm) using an array (handles spaces safely) |
|
NPM=(npm) |
|
|
|
if [[ -d ".ddev" ]]; then |
|
log ">>> Detected .ddev/ (will try to use ddev npm)" |
|
if command -v ddev >/dev/null 2>&1; then |
|
NPM=(ddev npm) |
|
else |
|
die "Found .ddev/ but 'ddev' is not on PATH. Install ddev or remove/disable .ddev usage." |
|
fi |
|
fi |
|
|
|
# --- Lint before push -------------------------------------------------------- |
|
|
|
if [[ -f "package.json" ]]; then |
|
log ">>> package.json found" |
|
|
|
# Verify the chosen command exists |
|
if [[ "${NPM[0]}" == "npm" ]]; then |
|
command -v npm >/dev/null 2>&1 || die "'npm' not found on PATH." |
|
else |
|
# NPM=(ddev npm) => check ddev exists (already done) and that ddev can run npm |
|
debug "Checking 'ddev npm' availability..." |
|
ddev version >/dev/null 2>&1 || die "'ddev' is present but not working (ddev version failed)." |
|
fi |
|
|
|
|
|
# Some npm versions return non-zero for "run --list" depending on project; don’t fail the whole push on that. |
|
# We still want output for debugging. |
|
npm_list_output="$("${NPM[@]}" run --list 2>&1 || true)" |
|
debug "npm run --list output:" |
|
debug "$npm_list_output" |
|
|
|
if grep -Eq '(^|[[:space:]])lint([[:space:]]|$)' <<<"$npm_list_output"; then |
|
log ">>> Lint script detected, running lint..." |
|
"${NPM[@]}" run lint |
|
else |
|
log ">>> No lint script detected; skipping." |
|
fi |
|
else |
|
log ">>> No package.json; skipping lint." |
|
fi |
|
|
|
# --- Composer lint ----------------------------------------------------------- |
|
|
|
if [[ -f "composer.json" ]]; then |
|
log ">>> composer.json found" |
|
|
|
if command -v composer >/dev/null 2>&1; then |
|
COMPOSER=(composer) |
|
elif command -v devbox >/dev/null 2>&1; then |
|
COMPOSER=(devbox run -- composer) |
|
else |
|
die "Found composer.json but neither 'composer' nor 'devbox' is on PATH." |
|
fi |
|
|
|
composer_list_output="$("${COMPOSER[@]}" run-script --list --no-ansi 2>&1 || true)" |
|
debug "composer run-script --list output:" |
|
debug "$composer_list_output" |
|
|
|
if grep -Eq '(^|[[:space:]])lint:pre-push([[:space:]]|$)' <<<"$composer_list_output"; then |
|
log ">>> Composer pre-push lint script detected, running lint:pre-push..." |
|
PRE_PUSH_INPUT="$push_input" "${COMPOSER[@]}" lint:pre-push |
|
elif grep -Eq '(^|[[:space:]])lint([[:space:]]|$)' <<<"$composer_list_output"; then |
|
log ">>> Composer lint script detected, running lint..." |
|
"${COMPOSER[@]}" lint < "$push_input" |
|
else |
|
log ">>> No Composer lint script detected; skipping." |
|
fi |
|
else |
|
log ">>> No composer.json; skipping Composer lint." |
|
fi |
|
|
|
# --- Repo hooks -------------------------------------------------------------- |
|
|
|
log ">>> Calling repo hooks..." |
|
script="$(basename "$0")" |
|
|
|
repo_hooks="$git_root/.repo-hooks.sh" |
|
if [[ ! -e "$repo_hooks" ]]; then |
|
repo_hooks="$git_root/repo-hooks.sh" |
|
fi |
|
|
|
if [[ -x "$repo_hooks" ]]; then |
|
"$repo_hooks" "$script" < "$push_input" |
|
elif [[ -f "$repo_hooks" ]]; then |
|
die "Found $(basename "$repo_hooks") but it is not executable. Run: chmod +x "$repo_hooks"" |
|
else |
|
log ">>> No repository hook found; skipping." |
|
fi |
|
|
|
log ">>> pre-push OK" |
|
exit 0 |