igneum/tools/ci/no-foreign-tree-writes.sh

49 lines
3.8 KiB
Bash
Executable file

#!/usr/bin/env bash
# A script writes only under its own repository (git rev-parse --show-toplevel of its own path), the downloads folder
# and the scratch dirs. It never builds a target path from another worktree's name, from a list of worktrees or from a
# walk over $HOME/Projects. Ruled 6 October 2026: five worktrees held 0.3.14's site rows as uncommitted edits to tracked
# files after the pre-push hook's site build fetched the live downloads index and rewrote its snapshot in whatever tree
# the push ran from (site/build.mjs now writes the snapshot only on SITE_DOWNLOADS_REFRESH=1 or in CI). Runs in CI and
# locally; --self-test shows it firing.
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
# a path built from a worktree list or a Projects walk: `git worktree list` piped into a loop with a write, or
# $HOME/Projects, ~/Projects, /Users/*/Projects used in a path (comments and docs excluded; strings in tests excluded)
PAT='(\$HOME|~|/Users/[a-z]+)/Projects/igneum-wt-|(\$HOME|~|/Users/[a-z]+)/Projects/(\*|igneum\*|igneum-wt-\*)|git worktree list[^|]*\|[^#]*(cp|mv|tee|>|writeFileSync|install )'
# the shared checkout as an absolute default (/Users/<user>/Projects/igneum/...) is the lesser class: a read of a binary
# or a script there, overridable by an environment variable. Listed as a warning; the row of 6 October 2026 moves each
# to an env-only default (no fallback path) and this pattern then joins PAT.
WARN='/Users/[a-z]+/Projects/igneum/'
check_file() {
local f="$1" bad=0
while IFS= read -r line; do
local code="${line%%#*}"
[[ "$code" =~ ^[[:space:]]*(//|\*|/\*) ]] && continue
[[ "$code" =~ $PAT ]] || continue
echo "foreign-tree: $f builds a path into another worktree or a Projects walk: ${line:0:140}"; bad=1
done < "$f"
return $bad
}
if [ "${1:-}" = "--self-test" ]; then
t="$(mktemp -d)"; trap 'rm -rf "$t"' EXIT
printf 'cp out.json "$HOME/Projects/igneum-wt-other/site/downloads.json"\nfor d in ~/Projects/igneum*/; do echo "$d"; done\n' > "$t/bad.sh"
printf 'for w in $(git worktree list | cut -d" " -f1); do cp x "$w/site/x"; done\n' > "$t/bad2.sh"
printf 'ROOT="$(git rev-parse --show-toplevel)"; cp out.json "$ROOT/site/downloads.json"\n# ~/Projects/igneum is fine in a comment\n' > "$t/good.sh"
check_file "$t/bad.sh" && { echo "self-test failed: bad.sh passed"; exit 1; }
check_file "$t/bad2.sh" && { echo "self-test failed: bad2.sh passed"; exit 1; }
check_file "$t/good.sh" || { echo "self-test failed: good.sh flagged"; exit 1; }
echo "self-test passed: a Projects path fails, a worktree-list loop with a write fails, an own-toplevel write passes"; exit 0
fi
fail=0
# a git hook never writes a tracked file: the hook body in tools/ci/install-hooks.sh (between <<'HOOK' and HOOK) may run the
# site build only inside a mktemp copy (6 October 2026: the in-place build rewrote generated pages in other worktrees)
if [ -f tools/ci/install-hooks.sh ]; then
body="$(sed -n "/<<'HOOK'/,/^HOOK$/p" tools/ci/install-hooks.sh)"
if printf '%s' "$body" | grep -qE "build\.mjs" && ! printf '%s' "$body" | grep -qE "mktemp"; then
echo "foreign-tree: the pre-push hook runs the site build in the worktree (no mktemp copy): a hook never writes a tracked file"; fail=1
fi
fi
while IFS= read -r f; do grep -nE "$WARN" "$f" | grep -vE '^[0-9]+:\s*(#|//)' | sed "s|^|foreign-tree (warning, env-only default owed): $f:|" | cut -c1-200; done < <(git ls-files 'tools/**' 'packaging/**' 'infra/**' 'relay/**' | grep -E '\.(sh|mjs|js|py|ps1)$' | grep -v '^tools/ci/no-foreign-tree-writes.sh$')
while IFS= read -r f; do check_file "$f" || fail=1; done < <(git ls-files 'tools/**' 'packaging/**' 'site/*.mjs' 'infra/**' 'relay/**' | grep -E '\.(sh|mjs|js|py|ps1)$' | grep -v '^tools/ci/no-foreign-tree-writes.sh$')
[ "$fail" = 0 ] && echo "foreign-tree: every script writes under its own toplevel"
exit $fail