|
2 | 2 | # integ-local-gate.sh |
3 | 3 | # |
4 | 4 | # PreToolUse hook. Blocks `gh pr merge` (including --auto) and |
5 | | -# `git merge` unless the `integ-local` markgate marker is fresh for |
6 | | -# the current content state. The gate's scope (see .markgate.yml) |
7 | | -# covers every code path that participates in the `cdkd local *` |
8 | | -# family (Lambda RIE containers, ECS task emulation, HTTP server, |
9 | | -# container pool, etc.); editing any of them invalidates the marker |
10 | | -# and forces a successful Docker-based `/run-integ local-*` run |
11 | | -# before the PR can be merged. |
| 5 | +# `git merge` when the merged PR actually touches local-execution code |
| 6 | +# AND the `integ-local` markgate marker is not fresh. The gate's scope |
| 7 | +# (see .markgate.yml) covers every code path that participates in the |
| 8 | +# `cdkd local *` family (Lambda RIE containers, ECS task emulation, |
| 9 | +# HTTP server, container pool, etc.); editing any of them invalidates |
| 10 | +# the marker and forces a successful Docker-based `/run-integ local-*` |
| 11 | +# run before the PR can be merged. |
| 12 | +# |
| 13 | +# IMPORTANT — PR-diff scope guard (see below): for `gh pr merge <N>` |
| 14 | +# the hook first checks whether the PR's file list actually touches |
| 15 | +# local-execution scope. A PR that touches NO local code passes |
| 16 | +# through even when the marker is stale, mirroring integ-destroy-gate |
| 17 | +# / integ-broad-gate (which already scope-check). Without this guard a |
| 18 | +# stale marker (14d TTL expiry, or an unrelated src/local change |
| 19 | +# already on main) would block EVERY merge, including pure |
| 20 | +# src/provisioning PRs — the over-fire this guard fixes. |
12 | 21 | # |
13 | 22 | # This is the structural counterpart for local-execution changes, |
14 | 23 | # mirroring `integ-destroy-gate.sh` for deletion logic. |
|
108 | 117 |
|
109 | 118 | cd "$target_dir" 2>/dev/null || exit 0 |
110 | 119 |
|
| 120 | +# --- PR-diff scope check (mirrors integ-destroy-gate.sh / integ-broad-gate.sh) --- |
| 121 | +# The markgate scope (.markgate.yml) is file-level, but markgate `verify` |
| 122 | +# cannot tell whether THIS PR's diff actually touches local-execution code. |
| 123 | +# Without this guard a stale `integ-local` marker (14d TTL expiry, or an |
| 124 | +# unrelated src/local change already on main) blocks EVERY merge — including |
| 125 | +# PRs that touch no local code at all (e.g. a pure src/provisioning fix). |
| 126 | +# The sibling gates integ-destroy / integ-broad already scope-check their |
| 127 | +# diff and pass non-matching PRs through; integ-local must do the same. |
| 128 | +# |
| 129 | +# Only applies to `gh pr merge <N>` where we can fetch the PR's file list. |
| 130 | +# `git merge` and number-less `gh pr merge` fall through to the |
| 131 | +# unconditional verify below (conservative — those are rarer and we |
| 132 | +# cannot cheaply enumerate the incoming diff). |
| 133 | +LOCAL_SCOPE_REGEX='^src/local/|^src/cli/commands/local-[A-Za-z0-9_-]*\.ts$|^tests/integration/local-' |
| 134 | + |
| 135 | +if printf '%s' "$cmd" | grep -qE 'gh([[:space:]]+-C[[:space:]]+[^[:space:]]+)?[[:space:]]+pr[[:space:]]+merge'; then |
| 136 | + pr_number="" |
| 137 | + args="${cmd#*merge}" |
| 138 | + # shellcheck disable=SC2086 |
| 139 | + set -- $args |
| 140 | + while [ $# -gt 0 ]; do |
| 141 | + case "$1" in |
| 142 | + --*=*) shift; continue ;; |
| 143 | + --auto|--admin|--delete-branch|--squash|--merge|--rebase) shift; continue ;; |
| 144 | + -*) shift; [ $# -gt 0 ] && shift; continue ;; |
| 145 | + *) |
| 146 | + if printf '%s' "$1" | grep -qE '^[0-9]+$'; then |
| 147 | + pr_number="$1" |
| 148 | + break |
| 149 | + fi |
| 150 | + shift |
| 151 | + ;; |
| 152 | + esac |
| 153 | + done |
| 154 | + |
| 155 | + if [ -n "$pr_number" ]; then |
| 156 | + # Pass-through on any gh error so an unrelated infra outage does not |
| 157 | + # block merges (mirrors integ-broad-gate.sh / pr-review-gate.sh). |
| 158 | + pr_json=$(gh pr view "$pr_number" --json files 2>/dev/null) || { |
| 159 | + printf 'integ-local-gate: gh pr view %s failed; allowing merge (infra fail-open)\n' "$pr_number" >&2 |
| 160 | + exit 0 |
| 161 | + } |
| 162 | + paths=$(printf '%s' "$pr_json" | jq -r '.files[].path' 2>/dev/null || echo "") |
| 163 | + touches_local=0 |
| 164 | + while IFS= read -r f; do |
| 165 | + [ -z "$f" ] && continue |
| 166 | + if printf '%s' "$f" | grep -qE "$LOCAL_SCOPE_REGEX"; then |
| 167 | + touches_local=1 |
| 168 | + break |
| 169 | + fi |
| 170 | + done <<EOF_FILES |
| 171 | +$paths |
| 172 | +EOF_FILES |
| 173 | + # No local-execution file in the PR diff -> this gate does not apply. |
| 174 | + if [ "$touches_local" -eq 0 ]; then |
| 175 | + exit 0 |
| 176 | + fi |
| 177 | + fi |
| 178 | +fi |
| 179 | + |
111 | 180 | # Prefer the `.mise.toml`-pinned version via `mise exec --` so the repo's |
112 | 181 | # canonical markgate wins over an older PATH binary; see check-gate.sh for |
113 | 182 | # the schema-bump rationale (0.3.0 markers are silently invisible to 0.3.1). |
|
0 commit comments