forgedock 1.29.0 → 1.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/engine/outcome-codes.mjs +4 -2
- package/bin/engine/phases.mjs +20 -3
- package/bin/engine-cli.mjs +102 -38
- package/bin/engine.mjs +3 -1
- package/bin/lean/batch.test.mjs +62 -1
- package/bin/lean/conformance.mjs +129 -8
- package/bin/lean/conformance.test.mjs +77 -3
- package/bin/lean/context.mjs +32 -4
- package/bin/lean/context.test.mjs +71 -12
- package/bin/lean/effects.mjs +20 -6
- package/bin/lean/enumerating.mjs +189 -0
- package/bin/lean/enumerating.test.mjs +97 -0
- package/bin/lean/lean.mjs +207 -56
- package/bin/lean/lean.test.mjs +336 -9
- package/bin/lean/orchestrate.mjs +132 -83
- package/bin/lean/precheck.mjs +55 -0
- package/bin/lean/precheck.test.mjs +93 -0
- package/bin/lean/prefilter.mjs +96 -14
- package/bin/lean/prefilter.test.mjs +111 -2
- package/bin/lean/preflight.mjs +133 -0
- package/bin/lean/preflight.test.mjs +61 -0
- package/bin/lean/prompts.mjs +37 -3
- package/bin/lean/recovery.test.mjs +271 -7
- package/bin/lean/render.mjs +13 -0
- package/bin/lean/sequence.mjs +102 -0
- package/bin/lean/sequence.test.mjs +112 -0
- package/bin/lean/testmap.mjs +57 -3
- package/bin/lean/testmap.test.mjs +53 -1
- package/bin/lean/verify.mjs +7 -2
- package/bin/lean/verify.test.mjs +8 -2
- package/commands/deploy-pr.md +5 -4
- package/commands/orchestrate.md +7 -4
- package/commands/review-pr-staging.md +6 -1
- package/docs/CONFIG.md +8 -1
- package/docs/OUTCOME-CODES.md +6 -4
- package/docs/RELEASE-NOTES.md +10 -0
- package/package.json +1 -1
- package/scripts/acceptance-red-green.sh +13 -4
- package/scripts/acceptance-red-green.test.sh +14 -0
- package/scripts/code-index.sh +33 -33
- package/scripts/code-index.test.sh +21 -2
- package/scripts/fixtures/test-map/go/.github/workflows/ci.yml +2 -0
- package/scripts/repo-facts.test.sh +5 -5
- package/scripts/review-prepare.sh +28 -6
- package/scripts/review-prepare.test.sh +22 -2
- package/scripts/test-map.sh +186 -34
- package/scripts/test-map.test.sh +250 -7
- package/scripts/verify-phase-trail.test.sh +44 -44
- package/scripts/wait-ci-green.sh +1 -1
- package/scripts/wait-ci-green.test.sh +4 -0
package/scripts/test-map.sh
CHANGED
|
@@ -17,11 +17,22 @@
|
|
|
17
17
|
# `ruff check .` (through `uv run` / `poetry run` when that lockfile exists), `**/*.py`
|
|
18
18
|
# go.mod (dir D) → `go test ./...`, `go vet ./...` in D, `D/**/*.go`, `D/go.mod`, `D/go.sum`
|
|
19
19
|
# Makefile / GNUmakefile targets test/lint/check/typecheck/vet → `make <target>`, `**`
|
|
20
|
-
# .github/workflows/*.yml|*.yaml `run:` steps (one command per line of a `|` block, a `>` block is one command
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
20
|
+
# .github/workflows/*.yml|*.yaml `run:` steps (one command per line of a `|` block, a `>` block is one command).
|
|
21
|
+
# The engine runs these commands before a PR, so a step is mapped only when its whole
|
|
22
|
+
# command is an allowlisted check (forge#4300): `<pm> test`, `<pm> run <package.json check script>` (also `test:*`
|
|
23
|
+
# style names, `pnpm -r`, `npm --prefix DIR`), `make <check target>`, `go test|vet|build`, `cargo test|clippy|check`,
|
|
24
|
+
# gradle/maven with check tasks/goals only, `[bundle exec] rspec`, `python manage.py test`, `python -m unittest`,
|
|
25
|
+
# `tox`/`nox` (not a publish/release/deploy/upload env), `deno|bun test`, `bash|sh <path>.test.sh` or a known
|
|
26
|
+
# test/lint/typecheck tool (forge#4315); everything else is dropped, and job and step names are never consulted. One leading `cd DIR &&` / `pushd DIR &&` becomes the entry cwd; any other chaining,
|
|
27
|
+
# substitution, `${{ }}`, `$GITHUB_*`, redirect or `<(...)`/`>(...)`, a non-check make target, or a fixer form
|
|
28
|
+
# (`--fix`, `--write`, `-w`, `black`/`ruff format`/`gofmt`/`prettier` without their check flag) drops the step (forge#4316). Globs from the tool (python/go/shell/js, under the cwd), else the
|
|
29
|
+
# workflow's `paths:` filter, else `DIR/**` for a cwd; a workflow entry never gets a bare `**`.
|
|
30
|
+
# forge#4318: also dropped: any quote, fixer/snapshot flag families (`--fix-only`, `--add-noqa`, `--write=*`, `--w`,
|
|
31
|
+
# `--update*`, `--updateSnapshot`, `--snapshot-update`, `-u` on jest/vitest/package managers), write targets (`go -o`,
|
|
32
|
+
# `-exec`, `-toolexec`, `tsc --outDir/--outFile`, `pytest --junitxml/--basetemp/--html`, any absolute or `..` path) and
|
|
33
|
+
# tox/nox `dist`/`pypi`/`twine` envs in any spelling; `-w` is a workspace on npm/pnpm. Also mapped: `uv run python -m X`,
|
|
34
|
+
# `python3.N -m X`, `npx --yes X`, `pnpm --filter X`, `npm -w PKG`, `make -C DIR` (DIR becomes the cwd), `cargo +TC <sub>`,
|
|
35
|
+
# `cargo fmt --check`, gradle `--tests X` / maven `-pl X`, `./x.test.sh`, `bash -x x.test.sh`. `clean` goals stay unmapped.
|
|
25
36
|
# Exit: 0 ok | 2 usage or bad repo path. bash 3.2 portable.
|
|
26
37
|
set -uo pipefail
|
|
27
38
|
|
|
@@ -53,6 +64,38 @@ fi
|
|
|
53
64
|
US="$(printf '\037')"
|
|
54
65
|
row() { printf '%s\t%s\t%s\t%s\n' "$1" "$2" "$3" "$4" >> "$T/rows"; }
|
|
55
66
|
JS_EXT="js jsx ts tsx mjs cjs"
|
|
67
|
+
# check recognisers shared by package.json scripts / Makefile targets and the workflow allowlist (forge#4300)
|
|
68
|
+
PKG_SCRIPTS="test|lint|typecheck|type-check|check|tsc"
|
|
69
|
+
MAKE_TARGETS="test|lint|check|typecheck|vet"
|
|
70
|
+
# forge#4315: `test:unit`, `lint:css` ... are the same check under a sub-name
|
|
71
|
+
# (#4314: only a closed set of suffixes; fixers `lint:fix`, watchers, e2e/integration (need services), deploy-ish and
|
|
72
|
+
# snapshot-updating scripts never run pre-PR.) $2 (optional) is the script's command: `format` is a check only when
|
|
73
|
+
# that command is a check form.
|
|
74
|
+
PKG_DENY='fix|watch|write|update|dev|serve|start|e2e|integration|deploy|release|publish|snapshot|-u'
|
|
75
|
+
PKG_SUFFIXES="unit ci node types type ts js static check"
|
|
76
|
+
pkg_check_script() {
|
|
77
|
+
[ -n "${1:-}" ] || return 1
|
|
78
|
+
case "$1" in *[!A-Za-z0-9:_.-]*) return 1 ;; esac
|
|
79
|
+
b=${1%%:*}
|
|
80
|
+
case "|$PKG_SCRIPTS|" in *"|$b|"*) ;; *) return 1 ;; esac
|
|
81
|
+
[ "$b" = "$1" ] && return 0
|
|
82
|
+
printf '%s' "$1" | grep -qiE "$PKG_DENY" && return 1
|
|
83
|
+
rest=${1#*:}; [ -n "$rest" ] || return 1
|
|
84
|
+
oldifs=$IFS; IFS=:; set -f
|
|
85
|
+
for seg in $rest; do
|
|
86
|
+
ok=0
|
|
87
|
+
case " $PKG_SUFFIXES " in *" $seg "*) [ -n "$seg" ] && ok=1 ;; esac
|
|
88
|
+
if [ "$ok" = 0 ] && [ "$seg" = format ]; then
|
|
89
|
+
case " ${2:-} " in *" --check "*|*" -c "*|*" --list-different "*|*" -l "*) ok=1 ;; esac
|
|
90
|
+
fi
|
|
91
|
+
[ "$ok" = 1 ] || { IFS=$oldifs; set +f; return 1; }
|
|
92
|
+
done
|
|
93
|
+
IFS=$oldifs; set +f
|
|
94
|
+
return 0
|
|
95
|
+
}
|
|
96
|
+
make_check_target() { case "|$MAKE_TARGETS|" in *"|$1|"*) [ -n "$1" ] ;; *) return 1 ;; esac; }
|
|
97
|
+
has_word() { k=$1; shift; for w in "$@"; do [ "$w" = "$k" ] && return 0; done; return 1; }
|
|
98
|
+
safe_dir() { case "$1" in ""|/*|*[!A-Za-z0-9._/-]*|..|../*|*/..|*/../*) return 1 ;; esac; return 0; }
|
|
56
99
|
# globs for the extensions in $2 under dir $1 ("." = repo root)
|
|
57
100
|
ext_globs() {
|
|
58
101
|
g=""; pre=""; [ "$1" = "." ] || pre="$1/"
|
|
@@ -70,10 +113,10 @@ grep -E '(^|/)package\.json$' "$T/files" | grep -v -E '(^|/)node_modules/' | sor
|
|
|
70
113
|
globs="$(ext_globs "$d" "$JS_EXT")$US$pj"
|
|
71
114
|
tag=""
|
|
72
115
|
if [ -n "$(ls "$d"/vitest.config.* "$d"/jest.config.* 2>/dev/null)" ] || jq -e 'has("jest")' "$pj" >/dev/null 2>&1; then tag=" (vitest/jest config)"; fi
|
|
73
|
-
jq -r '(.scripts // {}) | to_entries[] | select(.value | type == "string") | .key' "$pj" 2>/dev/null | while IFS= read -r s; do
|
|
116
|
+
jq -r '(.scripts // {}) | to_entries[] | select(.value | type == "string") | .key + "\t" + .value' "$pj" 2>/dev/null | while IFS="$(printf '\t')" read -r s sv; do
|
|
74
117
|
case "$s" in
|
|
75
118
|
test) row "$pj#scripts.$s$tag" "$d" "$PM run $s" "$globs" ;;
|
|
76
|
-
|
|
119
|
+
*) pkg_check_script "$s" "$sv" && row "$pj#scripts.$s" "$d" "$PM run $s" "$globs" ;;
|
|
77
120
|
esac
|
|
78
121
|
done
|
|
79
122
|
done
|
|
@@ -105,20 +148,20 @@ done
|
|
|
105
148
|
# ---- Makefile ----
|
|
106
149
|
for mf in Makefile GNUmakefile; do
|
|
107
150
|
[ -f "$mf" ] || continue
|
|
108
|
-
grep -oE
|
|
151
|
+
grep -oE "^($MAKE_TARGETS)[[:space:]]*:" "$mf" | sed -E 's/[[:space:]]*:$//' | sort -u | while IFS= read -r t; do
|
|
109
152
|
row "$mf#$t" "." "make $t" "**"
|
|
110
153
|
done
|
|
111
154
|
done
|
|
112
155
|
|
|
113
156
|
# ---- CI workflow run: steps ----
|
|
114
|
-
# Emits: job US
|
|
157
|
+
# Emits: job US command (US = \037), one per command line; and P TAB path for each `paths:` filter item
|
|
115
158
|
# before `jobs:`. A block ends at the first non-blank line not indented past the `run` key (sibling keys such as env:).
|
|
116
159
|
wf_extract() {
|
|
117
160
|
awk '
|
|
118
161
|
function indent(s) { match(s, /^ */); return RLENGTH }
|
|
119
162
|
function unq(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); if (s ~ /^".*"$/ || s ~ /^\x27.*\x27$/) s = substr(s, 2, length(s) - 2); return s }
|
|
120
|
-
function emit(c) { c = unq(c); if (c != "") print job "\037"
|
|
121
|
-
BEGIN { injobs = 0; jind = -1; block = 0; inpaths = 0; job = ""
|
|
163
|
+
function emit(c) { c = unq(c); if (c != "") print job "\037" c }
|
|
164
|
+
BEGIN { injobs = 0; jind = -1; block = 0; inpaths = 0; job = "" }
|
|
122
165
|
{
|
|
123
166
|
line = $0; sub(/\r$/, "", line)
|
|
124
167
|
if (block) {
|
|
@@ -143,13 +186,7 @@ wf_extract() {
|
|
|
143
186
|
next
|
|
144
187
|
}
|
|
145
188
|
if (line ~ /^[^ \t#]/) { injobs = 0; next }
|
|
146
|
-
if (line ~ /^ +[A-Za-z0-9_-]+:[ \t]*$/ && (jind < 0 || indent(line) == jind)) { jind = indent(line); job = line; sub(/^ +/, "", job); sub(/:.*$/, "", job);
|
|
147
|
-
if (line ~ /^[ \t]*-[ \t]/) sname = ""
|
|
148
|
-
if (line ~ /^[ \t]*(-[ \t]+)?name:/) {
|
|
149
|
-
nm = line; sub(/^[ \t]*(-[ \t]+)?name:[ \t]*/, "", nm); sub(/[ \t]+#.*$/, "", nm)
|
|
150
|
-
if (line !~ /^[ \t]*-[ \t]/ && indent(line) == jind + 2) jname = unq(nm); else sname = unq(nm)
|
|
151
|
-
next
|
|
152
|
-
}
|
|
189
|
+
if (line ~ /^ +[A-Za-z0-9_-]+:[ \t]*$/ && (jind < 0 || indent(line) == jind)) { jind = indent(line); job = line; sub(/^ +/, "", job); sub(/:.*$/, "", job); next }
|
|
153
190
|
if (line ~ /^[ \t]*(-[ \t]+)?run:/) {
|
|
154
191
|
rest = line; sub(/^[ \t]*(-[ \t]+)?run:[ \t]*/, "", rest); sub(/[ \t]+#.*$/, "", rest)
|
|
155
192
|
# bind = the column of the `run` key: step keys after it (env:, shell:) sit there and end the block
|
|
@@ -160,23 +197,136 @@ wf_extract() {
|
|
|
160
197
|
END { if (block && pend != "") emit(pend) }
|
|
161
198
|
' "$1"
|
|
162
199
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
200
|
+
# forge#4300: allowlist. 0 when the whole command $1 is a recognised check; sets CK_CWD (one leading `cd|pushd DIR &&`,
|
|
201
|
+
# else ".") and CK_CMD (the command without that prefix). Names never matter: a mutating command is dropped whatever its
|
|
202
|
+
# step is called, and a check is mapped whatever it is called.
|
|
203
|
+
check_cmd() {
|
|
204
|
+
c=$1; CK_CWD=.; CK_CMD=
|
|
205
|
+
# shellcheck disable=SC2016 # literal ${{ and $( patterns
|
|
206
|
+
# forge#4316: runner-file writes, redirects and process substitution are CI plumbing, never a local check
|
|
207
|
+
case "$c" in *'${{'*|*'`'*|*'$('*|*'$GITHUB_'*|*'<'*|*'>'*) return 1 ;; esac
|
|
208
|
+
case "$c" in
|
|
209
|
+
"cd "*" && "*|"pushd "*" && "*)
|
|
210
|
+
d=${c#* }; d=${d%%" && "*}; c=${c#*" && "}
|
|
211
|
+
case "$d" in ""|/*|*[!A-Za-z0-9._/-]*|..|../*|*/..|*/../*) return 1 ;; esac
|
|
212
|
+
d=${d%/}; [ -n "$d" ] && [ "$d" != . ] && CK_CWD=$d ;;
|
|
213
|
+
esac
|
|
214
|
+
c=${c%" && popd"}
|
|
215
|
+
case "$c" in *';'*|*'|'*|*'&'*) return 1 ;; esac
|
|
216
|
+
# forge#4318: a quoted word hides a flag from the word checks below ("--fix"); a real check never needs quotes
|
|
217
|
+
case "$c" in *\"*|*\'*) return 1 ;; esac
|
|
218
|
+
CK_CMD=$c
|
|
219
|
+
set -f
|
|
220
|
+
# shellcheck disable=SC2086 # split into words on purpose; set -f stops glob expansion
|
|
221
|
+
set -- $c
|
|
222
|
+
set +f
|
|
223
|
+
while [ $# -gt 0 ]; do case "$1" in [A-Za-z_]*=*) shift ;; *) break ;; esac; done
|
|
224
|
+
case "${1:-} ${2:-}" in
|
|
225
|
+
"uv run"|"poetry run") shift 2; case "${1:-} ${2:-}" in "python -m"|"python3 -m") shift 2 ;; esac ;; # forge#4318: uv run python -m X
|
|
226
|
+
"pnpm exec"|"yarn exec"|"python -m"|"python3 -m"|"bundle exec") shift 2 ;;
|
|
227
|
+
"python3."[0-9]" -m"|"python3."[0-9][0-9]" -m") shift 2 ;; # forge#4318: a versioned interpreter
|
|
228
|
+
"npx "*|"bunx "*) shift; case "${1:-}" in --yes|-y) shift ;; esac ;;
|
|
229
|
+
esac
|
|
230
|
+
tool=${1:-}
|
|
231
|
+
# forge#4316/#4318: a fixer, snapshot updater or write-target flag rewrites the tree or writes outside it; matched as a
|
|
232
|
+
# flag family (`--fix-only`, `--write=true`, `--w`), and `-w` only off a package manager (there it names a workspace)
|
|
233
|
+
for w in "$@"; do
|
|
234
|
+
case "$w" in
|
|
235
|
+
--fix|--fix[=-]*|--add-noqa*|--unsafe-fixes*|--write|--write[=-]*|--update|--update[=-]*|--updateSnapshot*|--snapshot-update*) return 1 ;;
|
|
236
|
+
--outDir*|--outdir*|--out-dir*|--outFile*|--declarationDir*|--tsBuildInfoFile*|--junitxml*|--junit-xml*|--basetemp*|--html*|-exec|-exec=*|-toolexec*) return 1 ;;
|
|
237
|
+
/*|*=/*|..|../*|*/..|*/../*|*=..|*=../*) return 1 ;; # an absolute or parent path is outside the checkout
|
|
238
|
+
-w|-w=*|--w|--w=*) case "$tool" in npm|pnpm|yarn|bun) ;; *) return 1 ;; esac ;;
|
|
239
|
+
-u) case "$tool" in jest|vitest|npm|pnpm|yarn|bun) return 1 ;; esac ;;
|
|
240
|
+
-o|-o=*) [ "$tool" = go ] && return 1 ;;
|
|
241
|
+
esac
|
|
242
|
+
done
|
|
243
|
+
case "${1:-} ${2:-}" in
|
|
244
|
+
"black "*|"ruff format") has_word --check "$@" || return 1 ;;
|
|
245
|
+
"gofmt "*) has_word -l "$@" || has_word -d "$@" || return 1 ;;
|
|
246
|
+
"prettier "*) has_word --check "$@" || has_word -c "$@" || has_word --list-different "$@" || has_word -l "$@" || return 1 ;;
|
|
247
|
+
esac
|
|
248
|
+
case "${1:-}" in
|
|
249
|
+
npm|pnpm|yarn|bun)
|
|
250
|
+
pm=$1; shift
|
|
251
|
+
case "$pm ${1:-}" in # forge#4315: pnpm -r test, npm --prefix DIR test; forge#4318: npm -w PKG, pnpm -w, pnpm --filter X
|
|
252
|
+
"pnpm -r"|"pnpm --recursive"|"pnpm -w"|"pnpm --workspace-root") shift ;;
|
|
253
|
+
"npm --prefix"|"npm -w"|"npm --workspace") [ $# -ge 2 ] && safe_dir "$2" || return 1; shift 2 ;;
|
|
254
|
+
"pnpm --filter"|"pnpm -F") [ $# -ge 2 ] || return 1; case "$2" in *[!A-Za-z0-9@._/-]*) return 1 ;; esac; shift 2 ;;
|
|
255
|
+
esac
|
|
256
|
+
[ "${1:-}" = test ] && return 0
|
|
257
|
+
[ "${1:-}" = run ] && pkg_check_script "${2:-}" && return 0
|
|
258
|
+
case "$pm" in yarn|pnpm) pkg_check_script "${1:-}" && return 0 ;; esac
|
|
259
|
+
return 1 ;;
|
|
260
|
+
cargo)
|
|
261
|
+
a=${2:-}; [ "${a#+}" != "$a" ] && a=${3:-} # forge#4318: cargo +TOOLCHAIN <sub>
|
|
262
|
+
case "$a" in test|clippy|check) return 0 ;; fmt) has_word --check "$@"; return ;; esac; return 1 ;;
|
|
263
|
+
./gradlew|gradlew|gradle|./mvnw|mvnw|mvn)
|
|
264
|
+
# every non-flag word is a check task/goal (`:mod:test` qualified for gradle); publish/deploy/install/clean never pass;
|
|
265
|
+
# forge#4318: the value of --tests / -pl / --projects / -P / -D is not a goal
|
|
266
|
+
case "$1" in *mvn*) goals="test|verify|validate" ;; *) goals="test|check|lint" ;; esac
|
|
267
|
+
shift; nt=0; skipv=""
|
|
268
|
+
for w in "$@"; do
|
|
269
|
+
if [ -n "$skipv" ]; then skipv=""; continue; fi
|
|
270
|
+
case "$w" in --tests|-pl|--projects|-P|-D) skipv=1; continue ;; -*) continue ;; esac
|
|
271
|
+
t=${w##*:}
|
|
272
|
+
case "|$goals|" in *"|$t|"*) [ -n "$t" ] && nt=$((nt+1)) ;; *) return 1 ;; esac
|
|
273
|
+
done
|
|
274
|
+
[ "$nt" -gt 0 ]; return ;;
|
|
275
|
+
rspec|unittest) return 0 ;;
|
|
276
|
+
python|python3) [ "${2:-}" = manage.py ] && [ "${3:-}" = test ]; return ;;
|
|
277
|
+
tox|nox)
|
|
278
|
+
# forge#4318: `--env=X`, `-eX` and comma lists are split, case-folded, and packaging envs (dist, pypi) are publish too
|
|
279
|
+
shift; prev=""
|
|
280
|
+
for w in "$@"; do
|
|
281
|
+
v=""
|
|
282
|
+
case "$prev" in -e|-s|--env|--session|--sessions) v=$w ;; esac
|
|
283
|
+
case "$w" in --env=*|--session=*|--sessions=*) v=${w#*=} ;; --*) ;; -e?*|-s?*) v=${w#-?} ;; esac
|
|
284
|
+
if [ -n "$v" ]; then
|
|
285
|
+
v=$(printf '%s' "$v" | tr '[:upper:]' '[:lower:]')
|
|
286
|
+
case ",$v," in *publish*|*release*|*deploy*|*upload*|*pypi*|*twine*|*,dist,*) return 1 ;; esac
|
|
287
|
+
fi
|
|
288
|
+
prev=$w
|
|
289
|
+
done
|
|
290
|
+
return 0 ;;
|
|
291
|
+
deno) [ "${2:-}" = test ]; return ;;
|
|
292
|
+
bash|sh)
|
|
293
|
+
# forge#4318: shell option words (-x, -eu) before the script are allowed
|
|
294
|
+
shift; while [ $# -gt 0 ]; do case "$1" in -[a-z]*) shift ;; *) break ;; esac; done
|
|
295
|
+
case "${1:-}" in *.test.sh) safe_dir "$1"; return ;; esac; return 1 ;;
|
|
296
|
+
./*.test.sh) safe_dir "${1#./}"; return ;;
|
|
297
|
+
make)
|
|
298
|
+
# forge#4316: every target must be a check (`make test deploy` is dropped); VAR=value and flags are not targets
|
|
299
|
+
# forge#4318: `make -C DIR` runs in DIR: it becomes the entry cwd, not a target
|
|
300
|
+
shift; nt=0; prev=""; mdir=""
|
|
301
|
+
for w in "$@"; do
|
|
302
|
+
if [ "$prev" = -C ]; then mdir=$w; prev=$w; continue; fi
|
|
303
|
+
prev=$w
|
|
304
|
+
case "$w" in -*|*=*) continue ;; esac; make_check_target "$w" || return 1; nt=$((nt+1))
|
|
305
|
+
done
|
|
306
|
+
if [ -n "$mdir" ]; then
|
|
307
|
+
safe_dir "$mdir" || return 1
|
|
308
|
+
CK_CMD=${CK_CMD/ -C $mdir/}; mdir=${mdir%/}
|
|
309
|
+
if [ "$mdir" != . ]; then if [ "$CK_CWD" = . ]; then CK_CWD=$mdir; else CK_CWD="$CK_CWD/$mdir"; fi; fi
|
|
310
|
+
fi
|
|
311
|
+
[ "$nt" -gt 0 ]; return ;;
|
|
312
|
+
go) case "${2:-}" in test|vet|build) return 0 ;; esac; return 1 ;;
|
|
313
|
+
node) [ "${2:-}" = --test ]; return ;;
|
|
314
|
+
eslint|tsc|prettier|vitest|jest|pytest|ruff|flake8|pylint|mypy|black|golangci-lint|gofmt|staticcheck|shellcheck) return 0 ;;
|
|
167
315
|
esac
|
|
168
316
|
return 1
|
|
169
317
|
}
|
|
170
|
-
# forge#4297: deploy, publish, release, push and secret-using steps are never check commands
|
|
171
|
-
deploy_step() {
|
|
172
|
-
grep -qiE 'deploy|publish|release|push|secret' <<<"$*"
|
|
173
|
-
}
|
|
174
318
|
tool_globs() {
|
|
175
319
|
c=" $1 "
|
|
176
320
|
case "$c" in
|
|
321
|
+
*\ cargo\ *) printf '%s' "**/*.rs$US**/Cargo.toml${US}Cargo.lock"; return ;;
|
|
322
|
+
*gradlew\ *|*\ gradle\ *|*mvnw\ *|*\ mvn\ *) printf '%s' "$(ext_globs . "java kt kts gradle groovy scala")$US**/pom.xml"; return ;;
|
|
323
|
+
*[\ /]rspec\ *) printf '**/*.rb'; return ;;
|
|
324
|
+
*\ manage.py\ test\ *|*\ unittest\ *|*\ tox\ *|*\ nox\ *) printf '**/*.py'; return ;;
|
|
325
|
+
*\ npm\ --prefix\ *) d=${1#*--prefix }; d=${d%% *}; ext_globs "$d" "$JS_EXT"; return ;;
|
|
326
|
+
*\ deno\ *|*\ bun\ *|*\ bunx\ *) ext_globs . "$JS_EXT"; return ;;
|
|
177
327
|
*[\ /]ruff\ *|*[\ /]flake8\ *|*[\ /]pylint\ *|*[\ /]mypy\ *|*[\ /]black\ *|*[\ /]pytest\ *) printf '**/*.py'; return ;;
|
|
178
328
|
*[\ /]golangci-lint\ *|*[\ /]gofmt\ *|*[\ /]staticcheck\ *|*\ go\ *) printf '**/*.go'; return ;;
|
|
179
|
-
*[\ /]shellcheck\ *|*\ bash\ *.sh\ *|*\ sh\ *.sh\ *) printf '**/*.sh'; return ;;
|
|
329
|
+
*[\ /]shellcheck\ *|*\ bash\ *.sh\ *|*\ sh\ *.sh\ *|*.test.sh\ *) printf '**/*.sh'; return ;;
|
|
180
330
|
*[\ /]eslint\ *|*[\ /]tsc\ *|*[\ /]prettier\ *|*[\ /]vitest\ *|*[\ /]jest\ *|*\ node\ --test*|*\ npm\ *|*\ pnpm\ *|*\ yarn\ *|*\ npx\ *) ext_globs . "$JS_EXT"; return ;;
|
|
181
331
|
esac
|
|
182
332
|
printf ''
|
|
@@ -185,13 +335,14 @@ grep -E '^\.github/workflows/[^/]+\.(yml|yaml)$' "$T/files" | sort | while IFS=
|
|
|
185
335
|
[ -f "$wf" ] || continue
|
|
186
336
|
wf_extract "$wf" > "$T/wf"
|
|
187
337
|
paths=$(awk -F'\t' '$1 == "P" { printf "%s\037", $2 }' "$T/wf"); paths=${paths%"$US"}
|
|
188
|
-
awk -F'\t' '$1 != "P"' "$T/wf" | while IFS="$
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
g
|
|
338
|
+
awk -F'\t' '$1 != "P"' "$T/wf" | while IFS="$US" read -r job cmd; do
|
|
339
|
+
check_cmd "$cmd" || continue
|
|
340
|
+
g=$(tool_globs "$CK_CMD")
|
|
341
|
+
[ -n "$g" ] && [ "$CK_CWD" != . ] && g="$CK_CWD/${g//$US/$US$CK_CWD/}"
|
|
192
342
|
[ -n "$g" ] || g="$paths"
|
|
193
|
-
[ -n "$g" ] || g="
|
|
194
|
-
|
|
343
|
+
[ -n "$g" ] || { [ "$CK_CWD" != . ] && g="$CK_CWD/**"; }
|
|
344
|
+
[ -n "$g" ] || continue
|
|
345
|
+
row "$wf#jobs.$job" "$CK_CWD" "$CK_CMD" "$g"
|
|
195
346
|
done
|
|
196
347
|
done
|
|
197
348
|
|
|
@@ -207,5 +358,6 @@ MAP=$(jq -R -s -c '
|
|
|
207
358
|
if [ "$STDOUT" = 1 ]; then printf '%s\n' "$MAP"; exit 0; fi
|
|
208
359
|
[ -n "$OUT" ] || OUT=".forge/test-map.json"
|
|
209
360
|
mkdir -p "$(dirname "$OUT")" || die "cannot create $(dirname "$OUT")"
|
|
210
|
-
printf '%s\n' "$MAP" > "$OUT.tmp.$$"
|
|
361
|
+
printf '%s\n' "$MAP" > "$OUT.tmp.$$" || die "cannot write $OUT"
|
|
362
|
+
mv "$OUT.tmp.$$" "$OUT" || die "cannot write $OUT"
|
|
211
363
|
echo "$OUT"
|
package/scripts/test-map.test.sh
CHANGED
|
@@ -15,11 +15,13 @@ eq() { if [ "$2" = "$3" ]; then ok; else bad "$1 (got: $2 want: $3)"; fi }
|
|
|
15
15
|
|
|
16
16
|
# copy a fixture into a fresh git repo and build its map
|
|
17
17
|
setup() {
|
|
18
|
-
rm -rf "$TMP/$1"; cp -R "$FIX/$1" "$TMP/$1"; cd "$TMP/$1" || exit 1
|
|
18
|
+
rm -rf "${TMP:?}/$1"; cp -R "$FIX/$1" "$TMP/$1"; cd "$TMP/$1" || exit 1
|
|
19
19
|
git init -q -b main; git config user.email t@example.com; git config user.name t; git config commit.gpgsign false
|
|
20
20
|
git add -A; git commit -qm init
|
|
21
21
|
OUTP="$(bash "$S")"; RC=$?
|
|
22
22
|
}
|
|
23
|
+
# forge#4300: workflow-derived entries of the map JSON in $2 carry no bare `**` glob
|
|
24
|
+
nostar() { eq "$1 no bare ** on workflow entries" "$(printf '%s' "$2" | jq '[.entries[] | select(.source | startswith(".github/workflows/")) | .globs[] | select(. == "**")] | length')" 0; }
|
|
23
25
|
# selected commands for changed files, sorted, joined by |
|
|
24
26
|
sel() { node "$SEL" --map .forge/test-map.json --select "$@" | LC_ALL=C sort | paste -sd'|' -; }
|
|
25
27
|
|
|
@@ -44,13 +46,15 @@ eq "python non-py" "$(sel uv.lock)" ""
|
|
|
44
46
|
# ---- 3. go: go.mod, Makefile and CI workflow steps; CI-only lint step; install lines skipped ----
|
|
45
47
|
setup go
|
|
46
48
|
eq "go rc" "$RC" 0
|
|
47
|
-
eq "go file" "$(sel main.go)" "
|
|
48
|
-
eq "nested go file" "$(sel internal/util/util.go)" "
|
|
49
|
+
eq "go file" "$(sel main.go)" "go test ./...|go vet ./...|golangci-lint run ./...|make test|make vet"
|
|
50
|
+
eq "nested go file" "$(sel internal/util/util.go)" "go test ./...|go vet ./...|golangci-lint run ./...|make test|make vet"
|
|
51
|
+
eq "unrecognised ci script dropped" "$(jq '[.entries[] | select(.command | test("check-docs"))] | length' .forge/test-map.json)" 0
|
|
49
52
|
eq "ci-only lint source" "$(jq -r '.entries[] | select(.command == "golangci-lint run ./...") | .source' .forge/test-map.json)" ".github/workflows/ci.yml#jobs.lint"
|
|
50
53
|
eq "install line skipped" "$(jq '[.entries[] | select(.command | test("mod download"))] | length' .forge/test-map.json)" 0
|
|
51
|
-
eq "paths filter fallback" "$(jq -c '.entries[] | select(.command == "
|
|
54
|
+
eq "paths filter fallback" "$(jq -c '.entries[] | select(.command == "make vet") | .globs' .forge/test-map.json)" '["**.go","go.mod"]'
|
|
52
55
|
eq "docs file" "$(sel README.md)" "make test"
|
|
53
56
|
eq "go.sum" "$(sel go.sum)" "go test ./...|go vet ./...|make test"
|
|
57
|
+
nostar go "$(cat .forge/test-map.json)"
|
|
54
58
|
|
|
55
59
|
# ---- 4. regenerable: a rerun is byte-identical; --stdout writes nothing; --output ----
|
|
56
60
|
cp .forge/test-map.json "$TMP/first.json"
|
|
@@ -84,8 +88,11 @@ jobs:
|
|
|
84
88
|
EOF
|
|
85
89
|
git add -A
|
|
86
90
|
OUTS="$(bash "$S" --stdout)"
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
# forge#4300: ./custom-check and `bash scripts/verify.sh` are not allowlisted checks, so they are dropped
|
|
92
|
+
# forge#4316: `ruff check --fix` rewrites the tree, so it is dropped too
|
|
93
|
+
eq "wf commands" "$(printf '%s' "$OUTS" | jq -r '[.entries[].command] | sort | join("|")')" "shellcheck scripts/*.sh"
|
|
94
|
+
eq "wf globs" "$(printf '%s' "$OUTS" | jq -c '[.entries[] | {(.command): .globs}] | add')" '{"shellcheck scripts/*.sh":["**/*.sh"]}'
|
|
95
|
+
nostar wf "$OUTS"
|
|
89
96
|
|
|
90
97
|
# ---- 5b. forge#4297: `- run: |` then env:/shell: ends the block; deploy/publish/release/push/secret steps dropped ----
|
|
91
98
|
mkdir -p "$TMP/wf2/.github/workflows"; cd "$TMP/wf2" || exit 1
|
|
@@ -130,6 +137,56 @@ EOF
|
|
|
130
137
|
git add -A
|
|
131
138
|
OUTS="$(bash "$S" --stdout)"
|
|
132
139
|
eq "wf2 commands" "$(printf '%s' "$OUTS" | jq -r '[.entries[].command] | sort | join("|")')" "npm run lint|npm test|tsc --noEmit"
|
|
140
|
+
nostar wf2 "$OUTS"
|
|
141
|
+
|
|
142
|
+
# ---- 5d. forge#4300: allowlist, not a name/substring denylist; names never decide; cd/pushd prefix becomes the cwd ----
|
|
143
|
+
mkdir -p "$TMP/wf3/.github/workflows"; cd "$TMP/wf3" || exit 1
|
|
144
|
+
git init -q -b main
|
|
145
|
+
cat > .github/workflows/ci.yml <<'EOF'
|
|
146
|
+
on: pull_request
|
|
147
|
+
jobs:
|
|
148
|
+
release-tests:
|
|
149
|
+
runs-on: ubuntu-latest
|
|
150
|
+
steps:
|
|
151
|
+
- run: npm test
|
|
152
|
+
- run: pytest tests/test_release.py
|
|
153
|
+
- run: pushd sub && make test
|
|
154
|
+
- run: npm run lint
|
|
155
|
+
name: Deploy
|
|
156
|
+
- name: Run tests
|
|
157
|
+
uses: some/action@v1
|
|
158
|
+
with:
|
|
159
|
+
name: release
|
|
160
|
+
- name: Typecheck
|
|
161
|
+
with:
|
|
162
|
+
name: release
|
|
163
|
+
run: npm run typecheck
|
|
164
|
+
ship:
|
|
165
|
+
runs-on: ubuntu-latest
|
|
166
|
+
steps:
|
|
167
|
+
- run: twine upload dist/*
|
|
168
|
+
- run: terraform apply -auto-approve
|
|
169
|
+
- run: kubectl apply -f k8s/
|
|
170
|
+
- run: ./deploy.sh
|
|
171
|
+
name: test
|
|
172
|
+
- name: Tests
|
|
173
|
+
with:
|
|
174
|
+
name: test
|
|
175
|
+
run: ./publish.sh
|
|
176
|
+
- run: pytest && twine upload dist/*
|
|
177
|
+
- run: cd ../other && make test
|
|
178
|
+
- run: npm run build
|
|
179
|
+
- run: npm test -- --token=${{ secrets.NPM_TOKEN }}
|
|
180
|
+
EOF
|
|
181
|
+
git add -A
|
|
182
|
+
OUTS="$(bash "$S" --stdout)"
|
|
183
|
+
eq "wf3 commands" "$(printf '%s' "$OUTS" | jq -r '[.entries[].command] | sort | join("|")')" "make test|npm run lint|npm run typecheck|npm test|pytest tests/test_release.py"
|
|
184
|
+
eq "wf3 release-tests job kept" "$(printf '%s' "$OUTS" | jq -r '[.entries[].source] | unique | join("|")')" ".github/workflows/ci.yml#jobs.release-tests"
|
|
185
|
+
eq "wf3 pushd cwd + globs" "$(printf '%s' "$OUTS" | jq -c '[.entries[] | select(.command == "make test") | {cwd, globs}]')" '[{"cwd":"sub","globs":["sub/**"]}]'
|
|
186
|
+
eq "wf3 no mutating command" "$(printf '%s' "$OUTS" | jq '[.entries[] | select(.command | test("twine|terraform|kubectl|deploy|publish|build|secrets"))] | length')" 0
|
|
187
|
+
nostar wf3 "$OUTS"
|
|
188
|
+
printf '%s' "$OUTS" > "$TMP/wf3-map.json"
|
|
189
|
+
eq "wf3 pushd entry runs in its dir" "$(node "$SEL" --map "$TMP/wf3-map.json" --select sub/Makefile)" "cd sub && make test"
|
|
133
190
|
|
|
134
191
|
# ---- 5c. forge#4298: a package dir with a space and a non-ASCII name keeps whole globs; workflow paths with spaces ----
|
|
135
192
|
mkdir -p "$TMP/sp/web app" "$TMP/sp/pkg/ü/src" "$TMP/sp/.github/workflows"; cd "$TMP/sp" || exit 1
|
|
@@ -148,15 +205,201 @@ jobs:
|
|
|
148
205
|
runs-on: ubuntu-latest
|
|
149
206
|
steps:
|
|
150
207
|
- run: ./build-docs.sh
|
|
208
|
+
- run: make check
|
|
151
209
|
EOF
|
|
152
210
|
git add -A
|
|
153
211
|
OUTS="$(bash "$S" --stdout)"
|
|
154
212
|
eq "spaced package globs" "$(printf '%s' "$OUTS" | jq -c '[.entries[] | select(.cwd == "web app") | .globs[]] | map(select(endswith("package.json") or endswith(".js")))')" '["web app/**/*.js","web app/package.json"]'
|
|
155
213
|
eq "non-ASCII package dir" "$(printf '%s' "$OUTS" | jq -r '[.entries[] | select(.command == "npm run lint") | .cwd] | join("|")')" "pkg/ü"
|
|
156
|
-
eq "spaced workflow path filter" "$(printf '%s' "$OUTS" | jq -c '[.entries[] | select(.command == "
|
|
214
|
+
eq "spaced workflow path filter" "$(printf '%s' "$OUTS" | jq -c '[.entries[] | select(.command == "make check") | .globs]')" '[["docs site/**"]]'
|
|
215
|
+
nostar sp "$OUTS"
|
|
216
|
+
eq "unrecognised docs script dropped" "$(printf '%s' "$OUTS" | jq '[.entries[] | select(.command == "./build-docs.sh")] | length')" 0
|
|
157
217
|
printf '%s' "$OUTS" > "$TMP/sp-map.json"
|
|
158
218
|
eq "spaced changed file selects its package test" "$(node "$SEL" --map "$TMP/sp-map.json" --select "web app/src/x.js")" "cd 'web app' && npm run test"
|
|
159
219
|
|
|
220
|
+
# ---- 5e. forge#4315: real runners per ecosystem are mapped with their globs; mutating siblings stay dropped ----
|
|
221
|
+
# wf_case NAME: the step commands on stdin become .github/workflows/ci.yml of a fresh repo; OUTS = its map
|
|
222
|
+
wf_case() {
|
|
223
|
+
mkdir -p "$TMP/$1/.github/workflows"; cd "$TMP/$1" || exit 1
|
|
224
|
+
git init -q -b main
|
|
225
|
+
{ printf 'on: pull_request\njobs:\n ci:\n runs-on: ubuntu-latest\n steps:\n'; sed 's/^/ - run: /'; } > .github/workflows/ci.yml
|
|
226
|
+
git add -A
|
|
227
|
+
OUTS="$(bash "$S" --stdout)"
|
|
228
|
+
nostar "$1" "$OUTS"
|
|
229
|
+
}
|
|
230
|
+
cmds() { printf '%s' "$OUTS" | jq -r '[.entries[].command] | sort | join("|")'; }
|
|
231
|
+
globs_of() { printf '%s' "$OUTS" | jq -r --arg c "$1" '[.entries[] | select(.command == $c) | .globs[]] | join(",")'; }
|
|
232
|
+
|
|
233
|
+
wf_case rust <<'WF'
|
|
234
|
+
cargo test --workspace
|
|
235
|
+
cargo clippy -- -D warnings
|
|
236
|
+
cargo publish
|
|
237
|
+
cargo build --release
|
|
238
|
+
WF
|
|
239
|
+
eq "rust commands" "$(cmds)" "cargo clippy -- -D warnings|cargo test --workspace"
|
|
240
|
+
eq "rust globs" "$(globs_of "cargo test --workspace")" "**/*.rs,**/Cargo.toml,Cargo.lock"
|
|
241
|
+
|
|
242
|
+
wf_case jvm <<'WF'
|
|
243
|
+
./gradlew test
|
|
244
|
+
./gradlew :app:check --no-daemon
|
|
245
|
+
mvn -B test
|
|
246
|
+
./gradlew publish
|
|
247
|
+
./gradlew test publish
|
|
248
|
+
mvn deploy
|
|
249
|
+
mvn install
|
|
250
|
+
WF
|
|
251
|
+
eq "jvm commands" "$(cmds)" "./gradlew :app:check --no-daemon|./gradlew test|mvn -B test"
|
|
252
|
+
eq "jvm globs" "$(globs_of "mvn -B test")" "**/*.java,**/*.kt,**/*.kts,**/*.gradle,**/*.groovy,**/*.scala,**/pom.xml"
|
|
253
|
+
|
|
254
|
+
wf_case ruby <<'WF'
|
|
255
|
+
bundle exec rspec
|
|
256
|
+
rspec spec/models
|
|
257
|
+
bundle exec rake release
|
|
258
|
+
WF
|
|
259
|
+
eq "ruby commands" "$(cmds)" "bundle exec rspec|rspec spec/models"
|
|
260
|
+
eq "ruby globs" "$(globs_of "bundle exec rspec")" "**/*.rb"
|
|
261
|
+
|
|
262
|
+
wf_case pyrun <<'WF'
|
|
263
|
+
python manage.py test
|
|
264
|
+
python -m unittest discover
|
|
265
|
+
tox
|
|
266
|
+
nox -s lint
|
|
267
|
+
tox -e publish
|
|
268
|
+
nox -s release
|
|
269
|
+
python manage.py migrate
|
|
270
|
+
WF
|
|
271
|
+
eq "python runner commands" "$(cmds)" "nox -s lint|python -m unittest discover|python manage.py test|tox"
|
|
272
|
+
eq "python runner globs" "$(globs_of "tox")" "**/*.py"
|
|
273
|
+
|
|
274
|
+
wf_case js <<'WF'
|
|
275
|
+
deno test
|
|
276
|
+
bun test
|
|
277
|
+
pnpm -r test
|
|
278
|
+
npm --prefix web test
|
|
279
|
+
npm run test:unit
|
|
280
|
+
deno deploy
|
|
281
|
+
npm run deploy:prod
|
|
282
|
+
npm --prefix ../x test
|
|
283
|
+
WF
|
|
284
|
+
eq "js runner commands" "$(cmds)" "bun test|deno test|npm --prefix web test|npm run test:unit|pnpm -r test"
|
|
285
|
+
eq "bun globs" "$(globs_of "bun test")" "**/*.js,**/*.jsx,**/*.ts,**/*.tsx,**/*.mjs,**/*.cjs"
|
|
286
|
+
eq "npm --prefix globs" "$(globs_of "npm --prefix web test")" "web/**/*.js,web/**/*.jsx,web/**/*.ts,web/**/*.tsx,web/**/*.mjs,web/**/*.cjs"
|
|
287
|
+
|
|
288
|
+
# ---- 5f. forge#4314: package.json script suffix allowlist (fixers/watchers/e2e/deploy never run pre-PR) ----
|
|
289
|
+
mkdir -p "$TMP/sfx"; cd "$TMP/sfx" || exit 1
|
|
290
|
+
git init -q -b main
|
|
291
|
+
cat > package.json <<'JSON'
|
|
292
|
+
{"name":"s","scripts":{"test":"x","test:unit":"x","test:ci":"x","test:node":"x","test:types":"x","lint:ts":"x","lint:js":"x","check:static":"x","check:types":"x","tsc":"tsc --noEmit","typecheck":"tsc","lint:check":"x","test:unit:ci":"x","lint:format":"prettier --check .","check:format":"prettier --write .",
|
|
293
|
+
"lint:fix":"eslint --fix .","check:fix":"x","test:watch":"x","test:e2e":"x","test:integration":"x","test:deploy":"x","test:update":"x","test:snapshot":"x","lint:css":"x","test:unit:watch":"x","test:-u":"x","lint:write":"x","test:dev":"x","check:release":"x"}}
|
|
294
|
+
JSON
|
|
295
|
+
git add -A
|
|
296
|
+
SFX="$(bash "$S" --stdout | jq -r '[.entries[].source | sub("^package.json#scripts\\.";"")] | sort | join("|")')"
|
|
297
|
+
eq "script suffix allowlist" "$SFX" "check:static|check:types|lint:check|lint:format|lint:js|lint:ts|test|test:ci|test:node|test:types|test:unit|test:unit:ci|tsc|typecheck"
|
|
298
|
+
|
|
299
|
+
wf_case sfxwf <<'WF'
|
|
300
|
+
npm run lint:fix
|
|
301
|
+
npm run check:fix
|
|
302
|
+
pnpm test:watch
|
|
303
|
+
yarn test:e2e
|
|
304
|
+
bun run test:integration
|
|
305
|
+
npm run test:deploy
|
|
306
|
+
pnpm run lint:fix
|
|
307
|
+
npm run test:unit
|
|
308
|
+
pnpm lint:ts
|
|
309
|
+
yarn check:types
|
|
310
|
+
bun run test:ci
|
|
311
|
+
WF
|
|
312
|
+
eq "workflow suffix commands" "$(cmds)" "bun run test:ci|npm run test:unit|pnpm lint:ts|yarn check:types"
|
|
313
|
+
|
|
314
|
+
wf_case shell <<'WF'
|
|
315
|
+
bash scripts/a.test.sh
|
|
316
|
+
sh tests/b.test.sh
|
|
317
|
+
bash scripts/deploy.sh
|
|
318
|
+
bash scripts/verify.sh
|
|
319
|
+
WF
|
|
320
|
+
eq "shell commands" "$(cmds)" "bash scripts/a.test.sh|sh tests/b.test.sh"
|
|
321
|
+
eq "shell globs" "$(globs_of "bash scripts/a.test.sh")" "**/*.sh"
|
|
322
|
+
|
|
323
|
+
# ---- 5f. forge#4316: redirects, process substitution, $GITHUB_*, extra make targets and fixer forms are dropped ----
|
|
324
|
+
# (make needs a cwd for its globs: a workflow entry never gets a bare **)
|
|
325
|
+
wf_case unsafe <<'WF'
|
|
326
|
+
pytest > out.txt
|
|
327
|
+
pytest 2>&1
|
|
328
|
+
diff <(a) <(b)
|
|
329
|
+
tee >(x)
|
|
330
|
+
cd sub && make test deploy
|
|
331
|
+
eslint --fix .
|
|
332
|
+
prettier --write .
|
|
333
|
+
gofmt -w .
|
|
334
|
+
black .
|
|
335
|
+
ruff format .
|
|
336
|
+
cargo clippy --fix
|
|
337
|
+
pytest --junitxml=$GITHUB_WORKSPACE/r.xml
|
|
338
|
+
cd sub && make test lint
|
|
339
|
+
cd sub && make test V=1
|
|
340
|
+
black --check .
|
|
341
|
+
ruff format --check .
|
|
342
|
+
prettier --check .
|
|
343
|
+
gofmt -l .
|
|
344
|
+
eslint .
|
|
345
|
+
WF
|
|
346
|
+
eq "unsafe forms dropped, check forms kept" "$(cmds)" "black --check .|eslint .|gofmt -l .|make test V=1|make test lint|prettier --check .|ruff format --check ."
|
|
347
|
+
|
|
348
|
+
# ---- 5g. forge#4318: flag-family fixers, snapshot updaters, write targets, quotes and tox publish envs are dropped ----
|
|
349
|
+
wf_case bypass <<'WF'
|
|
350
|
+
cargo clippy --fix
|
|
351
|
+
tox --env=publish
|
|
352
|
+
tox --session=release
|
|
353
|
+
tox -ePUBLISH
|
|
354
|
+
tox -e dist,pypi
|
|
355
|
+
nox -s Release
|
|
356
|
+
eslint "--fix" .
|
|
357
|
+
gofmt -l --w .
|
|
358
|
+
gofmt -w=true .
|
|
359
|
+
prettier --write=true .
|
|
360
|
+
prettier -cw .
|
|
361
|
+
ruff --fix-only .
|
|
362
|
+
ruff check --add-noqa .
|
|
363
|
+
jest -u
|
|
364
|
+
jest --updateSnapshot
|
|
365
|
+
vitest -u
|
|
366
|
+
vitest --update
|
|
367
|
+
pytest --snapshot-update
|
|
368
|
+
npm test -- -u
|
|
369
|
+
go build -o /tmp/x ./...
|
|
370
|
+
go test -o out.test ./...
|
|
371
|
+
go test -exec foo ./...
|
|
372
|
+
tsc --outDir /tmp/out
|
|
373
|
+
tsc --outFile out.js
|
|
374
|
+
pytest --junitxml=/abs/x.xml
|
|
375
|
+
pytest --basetemp=../t
|
|
376
|
+
mvn clean test
|
|
377
|
+
./gradlew clean test
|
|
378
|
+
tox -e py311,lint
|
|
379
|
+
pytest -q
|
|
380
|
+
WF
|
|
381
|
+
eq "bypass forms dropped" "$(cmds)" "pytest -q|tox -e py311,lint"
|
|
382
|
+
|
|
383
|
+
# ---- 5h. forge#4318: workspace flags, versioned/wrapped runners, -C, toolchains and test scripts are mapped ----
|
|
384
|
+
wf_case forms <<'WF'
|
|
385
|
+
npm -w pkg test
|
|
386
|
+
pnpm -w test
|
|
387
|
+
uv run python -m pytest
|
|
388
|
+
python3.12 -m pytest
|
|
389
|
+
npx --yes vitest
|
|
390
|
+
pnpm --filter web test
|
|
391
|
+
make -C sub test
|
|
392
|
+
mvn -pl mod test
|
|
393
|
+
./gradlew test --tests Foo
|
|
394
|
+
cargo +nightly test
|
|
395
|
+
cargo fmt --check
|
|
396
|
+
./x.test.sh
|
|
397
|
+
bash -x x.test.sh
|
|
398
|
+
WF
|
|
399
|
+
eq "real forms mapped" "$(cmds)" "./gradlew test --tests Foo|./x.test.sh|bash -x x.test.sh|cargo +nightly test|cargo fmt --check|make test|mvn -pl mod test|npm -w pkg test|npx --yes vitest|pnpm --filter web test|pnpm -w test|python3.12 -m pytest|uv run python -m pytest"
|
|
400
|
+
eq "make -C dir is the cwd" "$(printf '%s' "$OUTS" | jq -r '.entries[] | select(.command == "make test") | "\(.cwd) \(.globs | join(","))"')" "sub sub/**"
|
|
401
|
+
eq "test script globs" "$(globs_of "./x.test.sh")" "**/*.sh"
|
|
402
|
+
|
|
160
403
|
# ---- 6. usage ----
|
|
161
404
|
bash "$S" --repo-path "$TMP/missing" >/dev/null 2>&1; eq "bad repo path" "$?" 2
|
|
162
405
|
bash "$S" --nope >/dev/null 2>&1; eq "unknown option" "$?" 2
|