diff --git a/scripts/ci/select-gate-families.selftest.sh b/scripts/ci/select-gate-families.selftest.sh index cb812fc28e..ae75c1548c 100644 --- a/scripts/ci/select-gate-families.selftest.sh +++ b/scripts/ci/select-gate-families.selftest.sh @@ -67,6 +67,7 @@ printf '{"name":"a"}\n' > "$UP/packages/a/package.json" printf 'export const a = 1;\n' > "$UP/packages/a/src/index.ts" printf 'export const t = 1;\n' > "$UP/packages/a/src/index.test.ts" printf '{"rows":[]}\n' > "$UP/packages/a/src/data.json" +printf '#!/usr/bin/env bash\necho foo\n' > "$UP/packages/a/foo.sh" printf 'dist/\n' > "$UP/packages/a/.gitignore" printf 'console.log(1);\n' > "$UP/packages/a/scripts/build.mjs" printf 'export const site = 1;\n' > "$UP/apps/site/src/page.tsx" @@ -428,6 +429,13 @@ expect_rc 0 expect_warnings '' '' expect_verdicts +S=$(scenario M:packages/a/foo.sh) +run_case 'merge_group: a workspace shell script outside scripts/ is still gate source, not a skipped non-source workspace file (#16769)' "$REPO" merge_group '' "$C0" +expect_rc 0 +expect_warnings '' '' +expect_verdicts pm_dispatch_gates +expect_reason pm_dispatch_gates packages/a/foo.sh + S=$(scenario M:packages/a/scripts/build.mjs) run_case 'merge_group: a package-local script is a gate source (PM) and a masked source (corpus)' "$REPO" merge_group '' "$C0" expect_rc 0 diff --git a/scripts/ci/select-gate-families.sh b/scripts/ci/select-gate-families.sh index 95aad78301..b260018ab6 100644 --- a/scripts/ci/select-gate-families.sh +++ b/scripts/ci/select-gate-families.sh @@ -61,17 +61,17 @@ # `AGENTS.md`, `CLAUDE.md`, `tsconfig.json`, # .gitignore, and sweeps `git ls-files` for hint # reachability and test-file residue. It also reads -# the CONTENT of every JS/TS file in the tree: the -# compound-anchor census of `function ...SelfTest...(` -# declarations asserts none is unlisted, and the -# exposed-scratch-dir sweep reads every -# mkdtempSync/mkdirSync caller and consults nested -# .gitignore files. So any masked source file and any -# .gitignore runs it, and because the name sweep reads -# the tracked NAME set an ADDED file anywhere runs it -# too; only modifications of docs, changesets and -# non-source workspace files that are neither a -# manifest nor a script skip it. +# the CONTENT of every JS/TS and shell (`.sh`) file in +# the tree: the compound-anchor census of +# `function ...SelfTest...(` declarations asserts none +# is unlisted, and the exposed-scratch-dir sweep reads +# every mkdtempSync/mkdirSync caller and consults +# nested .gitignore files. So any masked source file, +# any `.sh` file, and any .gitignore runs it, and +# because the name sweep reads the tracked NAME set an +# ADDED file anywhere runs it too; only modifications +# of docs, changesets and non-source workspace files +# that are neither a manifest nor a script skip it. # query_options_erasure `pnpm check:query-options-erasure`. Lints # packages/**/*.{ts,tsx,mts,cts} under # `eslint.config.mjs`, reads its baseline @@ -314,9 +314,12 @@ family_reads() { # The self-test reads the CONTENT of every JS/TS file in the tree (the # compound-anchor census of `function ...SelfTest...(` declarations, the # exposed-scratch-dir sweep of every mkdtempSync/mkdirSync caller) and - # consults nested .gitignore files, so any masked source and any - # .gitignore runs it whatever class it sits in. + # every tracked `.sh` file (the same watch-hint extraction, run through + # the comment mask), and consults nested .gitignore files, so any masked + # source, any `.sh` file, and any .gitignore runs it whatever class it + # sits in. is_masked_source "$path" && return 0 + case "$path" in *.sh) return 0 ;; esac case "$path" in */.gitignore) return 0 ;; esac case "$class" in docs|changeset) return 1 ;;