|
@@ -0,0 +1,107 @@
|
|
|
|
|
+#!/usr/bin/env bash
|
|
|
|
|
+set -euo pipefail
|
|
|
|
|
+
|
|
|
|
|
+# Pre-commit hook: keeps dist/ in sync with src/.
|
|
|
|
|
+#
|
|
|
|
|
+# Why: vendor/qmd/dist/ is tracked in git. Consumer machines fetch the
|
|
|
|
|
+# repo and use dist/ AS-IS without rebuilding (cli/build.sh and the fleet
|
|
|
|
|
+# pull_and_build_and_deploy.sh both consume the pre-built JS). A commit
|
|
|
|
|
+# that updates src/*.ts without committing the matching dist/ will ship a
|
|
|
|
|
+# CLI that crashes with MODULE_NOT_FOUND on any new import path.
|
|
|
|
|
+# Reference: i-9l2ueyyz (post-mortem of i-qkarfffa Stage-3 dist/embedding/
|
|
|
|
|
+# catch-up bug).
|
|
|
|
|
+#
|
|
|
|
|
+# Behaviour:
|
|
|
|
|
+# 1. If no staged file matches ^src/.*\.ts$ -> exit 0 (fast path for
|
|
|
|
|
+# docs/config-only commits).
|
|
|
|
|
+# 2. Otherwise run `npm run build` (which runs tsc + chmod the CLI shim).
|
|
|
|
|
+# 3. Then `git diff --quiet -- dist/` -- if dist/ changed as a result of
|
|
|
|
|
+# the rebuild AND those changes are not staged, FAIL LOUDLY with a
|
|
|
|
|
+# message naming the drifted files and the recovery command.
|
|
|
|
|
+#
|
|
|
|
|
+# Bypass: `git commit --no-verify` (standard git escape hatch).
|
|
|
|
|
+
|
|
|
|
|
+# --- collect staged src/*.ts paths -----------------------------------------
|
|
|
|
|
+mapfile -t STAGED_SRC < <(git diff --cached --name-only --diff-filter=ACMR | grep -E '^src/.*\.ts$' || true)
|
|
|
|
|
+
|
|
|
|
|
+if [[ "${#STAGED_SRC[@]}" -eq 0 ]]; then
|
|
|
|
|
+ # Nothing under src/ in this commit -- no rebuild required.
|
|
|
|
|
+ exit 0
|
|
|
|
|
+fi
|
|
|
|
|
+
|
|
|
|
|
+echo "pre-commit: ${#STAGED_SRC[@]} staged src/*.ts file(s) detected -- rebuilding dist/" >&2
|
|
|
|
|
+
|
|
|
|
|
+# --- snapshot dist/ before rebuild so we can detect rebuild-induced drift --
|
|
|
|
|
+# Use SHA-256 of git ls-tree-style content (working tree) for every dist/ file
|
|
|
|
|
+# we care about. Cheap enough for ~75 files.
|
|
|
|
|
+DIST_SHA_BEFORE=$(find dist -type f \( -name '*.js' -o -name '*.d.ts' -o -name '*.js.map' -o -name '*.d.ts.map' \) -print0 \
|
|
|
|
|
+ | sort -z \
|
|
|
|
|
+ | xargs -0 sha256sum \
|
|
|
|
|
+ | sha256sum \
|
|
|
|
|
+ | awk '{print $1}')
|
|
|
|
|
+
|
|
|
|
|
+# --- run build -- this also gates on tsc errors ----------------------------
|
|
|
|
|
+if ! npm run --silent build >/tmp/qmd-pre-commit-build.log 2>&1; then
|
|
|
|
|
+ echo "" >&2
|
|
|
|
|
+ echo "pre-commit: npm run build FAILED" >&2
|
|
|
|
|
+ echo "" >&2
|
|
|
|
|
+ echo "Build output (last 40 lines):" >&2
|
|
|
|
|
+ tail -n 40 /tmp/qmd-pre-commit-build.log >&2
|
|
|
|
|
+ echo "" >&2
|
|
|
|
|
+ echo "Full log: /tmp/qmd-pre-commit-build.log" >&2
|
|
|
|
|
+ exit 1
|
|
|
|
|
+fi
|
|
|
|
|
+
|
|
|
|
|
+DIST_SHA_AFTER=$(find dist -type f \( -name '*.js' -o -name '*.d.ts' -o -name '*.js.map' -o -name '*.d.ts.map' \) -print0 \
|
|
|
|
|
+ | sort -z \
|
|
|
|
|
+ | xargs -0 sha256sum \
|
|
|
|
|
+ | sha256sum \
|
|
|
|
|
+ | awk '{print $1}')
|
|
|
|
|
+
|
|
|
|
|
+# --- determine drift relative to git index ---------------------------------
|
|
|
|
|
+# `git diff --quiet -- dist/` exits 0 if working tree dist/ matches index.
|
|
|
|
|
+# Non-zero means dist/ has changes that are NOT staged.
|
|
|
|
|
+if git diff --quiet -- dist/; then
|
|
|
|
|
+ # dist/ matches index -- everything is in sync, commit may proceed.
|
|
|
|
|
+ exit 0
|
|
|
|
|
+fi
|
|
|
|
|
+
|
|
|
|
|
+# Drift detected. Print the offending files + recovery command.
|
|
|
|
|
+echo "" >&2
|
|
|
|
|
+echo "==========================================================================" >&2
|
|
|
|
|
+echo "ABORT: dist/ out of sync with src/" >&2
|
|
|
|
|
+echo "==========================================================================" >&2
|
|
|
|
|
+echo "" >&2
|
|
|
|
|
+echo "Your commit modifies src/*.ts but dist/ is not in sync after rebuild." >&2
|
|
|
|
|
+echo "Shipping this commit would push a broken @oivo/qmd to consumer machines" >&2
|
|
|
|
|
+echo "(see i-9l2ueyyz / i-qkarfffa Stage-3 retrospective)." >&2
|
|
|
|
|
+echo "" >&2
|
|
|
|
|
+echo "Drifted files:" >&2
|
|
|
|
|
+git diff --name-only -- dist/ | sed 's/^/ /' >&2
|
|
|
|
|
+echo "" >&2
|
|
|
|
|
+
|
|
|
|
|
+if [[ "$DIST_SHA_BEFORE" == "$DIST_SHA_AFTER" ]]; then
|
|
|
|
|
+ # The rebuild produced the SAME dist/ as before, but git still reports
|
|
|
|
|
+ # drift -- this means dist/ was already drifted before this commit
|
|
|
|
|
+ # (someone hand-edited or forgot to stage a previous build). Tell them.
|
|
|
|
|
+ echo "Note: rebuild produced no changes -- dist/ was already drifted before this" >&2
|
|
|
|
|
+ echo "commit. Stage the drift OR revert dist/ to HEAD before retrying:" >&2
|
|
|
|
|
+ echo "" >&2
|
|
|
|
|
+ echo " # Option A: include the drift in this commit" >&2
|
|
|
|
|
+ echo " git add dist/ && git commit # retry" >&2
|
|
|
|
|
+ echo "" >&2
|
|
|
|
|
+ echo " # Option B: discard the drift (use if dist/ was hand-edited)" >&2
|
|
|
|
|
+ echo " git checkout HEAD -- dist/ && git commit # retry" >&2
|
|
|
|
|
+else
|
|
|
|
|
+ # The rebuild itself produced new dist/ content. User just needs to stage
|
|
|
|
|
+ # the freshly-built dist/.
|
|
|
|
|
+ echo "Recovery (run, then retry the commit):" >&2
|
|
|
|
|
+ echo "" >&2
|
|
|
|
|
+ echo " git add dist/" >&2
|
|
|
|
|
+ echo " git commit # retry" >&2
|
|
|
|
|
+fi
|
|
|
|
|
+echo "" >&2
|
|
|
|
|
+echo "Bypass (NOT recommended -- ships broken @oivo/qmd to fleet):" >&2
|
|
|
|
|
+echo " git commit --no-verify" >&2
|
|
|
|
|
+echo "" >&2
|
|
|
|
|
+exit 1
|