From 0f3959516ca7cba6755cc26530adf23a4dd30f32 Mon Sep 17 00:00:00 2001 From: Adnan Date: Sat, 5 Sep 2026 07:21:02 +0100 Subject: [PATCH] fix(#4106): drop the orphaned test:mutation:since script (#4179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(#4106): drop the orphaned test:mutation:since script `"test:mutation:since": "stryker run --incremental --since origin/next"` could not run. Stryker has no `--since` flag — verified against the pinned `@stryker-mutator/core` 9.6.1, whose options schema has no `since` property and whose CLI exits `error: unknown option '--since'`. It went unnoticed because nothing invokes it. `.github/workflows/mutation.yml` uses the per-module matrix instead: `scripts/mutation-matrix.cjs` computes the changed covered modules and each shard runs its own `npx stryker run --incremental --mutate "${MUTATE_GLOB}"`. That path works, so no gate ever exercised the script. It is still worth removing rather than leaving: its name makes it the obvious thing to reach for when a reviewer asks for a mutation score on changed scope, and the Commander parse error it produces says nothing about the matrix being the real entry point — the next person has to reverse-engineer mutation.yml to find that out. Delete rather than repoint. The issue offered both, and the maintainer had not picked; delete is the option that stays scoped to the reported bug. The suggested replacement (`node scripts/mutation-matrix.cjs --base origin/next`) emits a CI matrix rather than running mutants, so shipping it under a `test:mutation:*` name would be a second, differently-shaped claim, and documenting the per-module invocation in CONTRIBUTING.md is enhancement-shaped work that belongs in its own issue. The discovery command is named in the changeset instead. Flipping this PR to the pointer script is a one-line change if that is preferred. The regression test asserts that every `stryker` script in package.json passes only flags Stryker's own options schema defines. It carries its own control — the historical `--incremental --since origin/next` string is checked to still be rejected by the same predicate — so the sweep cannot go quietly vacuous if the remaining invocations lose their flags. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017zgWzB96uz3LVKdJePYTLR * chore(#4106): backfill the changeset PR number Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017zgWzB96uz3LVKdJePYTLR * docs(#4106): mark the changeset docs-exempt `type: Removed` makes docs-lint require a `docs/` change. Nothing under `docs/` or in CONTRIBUTING.md ever referenced `test:mutation:since` — the script was orphaned and could not run — so there is no documented behaviour to update. Using the gate's own per-fragment escape hatch rather than weakening the changeset type to dodge the requirement. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017zgWzB96uz3LVKdJePYTLR --------- Co-authored-by: Claude Opus 5 Co-authored-by: Tom Boucher --- .changeset/curious-foxes-fly.md | 7 ++++ package.json | 1 - tests/repo-invariants.test.cjs | 73 +++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 .changeset/curious-foxes-fly.md diff --git a/.changeset/curious-foxes-fly.md b/.changeset/curious-foxes-fly.md new file mode 100644 index 000000000..e55a1098d --- /dev/null +++ b/.changeset/curious-foxes-fly.md @@ -0,0 +1,7 @@ +--- +type: Removed +pr: 4179 +--- +**Dropped the `test:mutation:since` npm script** — it passed `--since`, which Stryker 9.x does not accept (`error: unknown option '--since'`), so it could not run at all. Nothing invoked it: the mutation gate runs the per-module matrix from `scripts/mutation-matrix.cjs` instead, so no workflow regresses. To see which modules a change puts in scope, run `node scripts/mutation-matrix.cjs --base origin/next --print`. (#4106) + + diff --git a/package.json b/package.json index f29bb1242..6d0f5db0c 100644 --- a/package.json +++ b/package.json @@ -155,7 +155,6 @@ "test:coverage:report": "c8 report --reporter text --reporter json-summary --merge-async --include 'gsd-core/bin/lib/**/*.cjs' --exclude 'tests/**' --all && node scripts/check-coverage-gate.cjs", "test:coverage:all": "npm run test:coverage", "test:mutation": "stryker run", - "test:mutation:since": "stryker run --incremental --since origin/next", "gen:features": "node scripts/gen-features.cjs", "gen:state-md-docs": "node scripts/gen-state-md-docs.cjs" }, diff --git a/tests/repo-invariants.test.cjs b/tests/repo-invariants.test.cjs index 71ecd30e4..822b94163 100644 --- a/tests/repo-invariants.test.cjs +++ b/tests/repo-invariants.test.cjs @@ -431,3 +431,76 @@ test('--cwd pointing at a non-existent path fails uniformly across families', () }); }); } + + +// ──────────────────────────────────────────────────────────────────────── +// #4106 — every Stryker flag in package.json is one Stryker actually accepts +// ──────────────────────────────────────────────────────────────────────── +{ + const { describe: __d, test: __t } = require('node:test'); + const __assert = require('node:assert/strict'); + const __fs = require('node:fs'); + const __path = require('node:path'); + + const __ROOT = __path.resolve(__dirname, '..'); + const __PKG = require(__path.join(__ROOT, 'package.json')); + const __SCHEMA = __path.join( + __ROOT, 'node_modules', '@stryker-mutator', 'core', 'schema', 'stryker-schema.json', + ); + + // Stryker's CLI options are generated from its own options schema, so the schema's + // property names ARE the accepted long flags. Three commander-level flags have no + // schema entry because they are not run options. + const __CLI_ONLY = new Set(['files', 'help', 'version']); + + /** Long flags a `stryker …` npm script passes, normalised (`--mutate=x` → `mutate`). */ + function __strykerFlags(script) { + if (!/(^|\s)(npx\s+)?stryker(\s|$)/.test(script)) return null; + return script + .split(/\s+/) + .filter((tok) => tok.startsWith('--')) + .map((tok) => tok.slice(2).split('=')[0]); + } + + __d('package.json Stryker scripts pass only flags Stryker accepts (#4106)', () => { + const accepted = new Set([ + ...Object.keys(JSON.parse(__fs.readFileSync(__SCHEMA, 'utf-8')).properties || {}), + ...__CLI_ONLY, + ]); + + // Guard: a moved/renamed schema would otherwise reject every flag for the wrong reason. + __assert.ok( + accepted.size > 20, + `no options schema at ${__SCHEMA} — @stryker-mutator/core may have moved it`, + ); + + __t('the checker rejects the flag this bug shipped', () => { + // `--since` is what `test:mutation:since` carried; Stryker 9.x exits with + // "error: unknown option '--since'". Pinning it here keeps this suite honest + // if every real invocation later drops its flags. + const flags = __strykerFlags('stryker run --incremental --since origin/next'); + __assert.deepEqual(flags, ['incremental', 'since']); + __assert.equal(accepted.has('incremental'), true); + __assert.equal(accepted.has('since'), false, '--since is not a Stryker option'); + }); + + __t('every stryker script in package.json is runnable', () => { + const offenders = []; + let scanned = 0; + for (const [name, script] of Object.entries(__PKG.scripts || {})) { + const flags = __strykerFlags(script); + if (flags === null) continue; + scanned += 1; + for (const flag of flags) { + if (!accepted.has(flag)) offenders.push(`${name}: --${flag}`); + } + } + __assert.ok(scanned > 0, 'no stryker script found — the sweep matched nothing'); + __assert.deepEqual( + offenders, + [], + `package.json scripts pass flags Stryker does not accept: ${offenders.join(', ')}`, + ); + }); + }); +}