testing-conventions 0.0.125 → 0.0.127

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.
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * The TypeScript mutation adapter: run Stryker over `argv`'s scope and emit the normalized results
3
- * as JSON, to `--out <path>` when given and to stdout otherwise.
3
+ * as JSON, to `--out <path>` when given and to stdout otherwise. Refuses outright on a vitest the
4
+ * runner silently mis-scores, so the gate never reports a survivor it did not actually judge.
4
5
  */
5
6
  export declare function mutationCLI(argv: string[]): Promise<void>;
@@ -1,12 +1,19 @@
1
1
  import { writeFile } from 'node:fs/promises';
2
2
  import { parseArgs } from './parse-args.js';
3
+ import { readVitestVersion } from './read-vitest-version.js';
3
4
  import { runStryker } from './run-stryker.js';
5
+ import { unsupportedVitestReason } from './unsupported-vitest-reason.js';
4
6
  /**
5
7
  * The TypeScript mutation adapter: run Stryker over `argv`'s scope and emit the normalized results
6
- * as JSON, to `--out <path>` when given and to stdout otherwise.
8
+ * as JSON, to `--out <path>` when given and to stdout otherwise. Refuses outright on a vitest the
9
+ * runner silently mis-scores, so the gate never reports a survivor it did not actually judge.
7
10
  */
8
11
  export async function mutationCLI(argv) {
9
12
  const { mutate, out, testFiles } = parseArgs(argv);
13
+ const unsupported = unsupportedVitestReason(readVitestVersion(process.cwd()));
14
+ if (unsupported !== null) {
15
+ throw new Error(unsupported);
16
+ }
10
17
  const results = await runStryker({
11
18
  ...(mutate === undefined ? {} : { mutate }),
12
19
  ...(testFiles === undefined ? {} : { testFiles }),
@@ -1 +1 @@
1
- {"version":3,"file":"mutation-cli.js","sourceRoot":"","sources":["../../src/mutation/mutation-cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAc;IAC9C,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC;QAC/B,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3C,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;KAClD,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;IAC5C,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;SAAM,CAAC;QACN,MAAM,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"mutation-cli.js","sourceRoot":"","sources":["../../src/mutation/mutation-cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAEzE;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAc;IAC9C,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,WAAW,GAAG,uBAAuB,CAAC,iBAAiB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC9E,IAAI,WAAW,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;IAC/B,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC;QAC/B,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3C,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC;KAClD,CAAC,CAAC;IACH,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;IAC5C,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;SAAM,CAAC;QACN,MAAM,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ /** Loads `vitest/package.json` as resolved from a project root. Throws when the project has none. */
2
+ export type LoadVitestPackage = (projectRoot: string) => unknown;
3
+ /**
4
+ * The `version` of the vitest that the project at `projectRoot` resolves, or `null` when it
5
+ * resolves none or reports no version. Resolution runs from the project, not from this package, so
6
+ * a consumer's own vitest is the one measured — including one hoisted to a workspace root.
7
+ */
8
+ export declare function readVitestVersion(projectRoot: string, load?: LoadVitestPackage): string | null;
@@ -0,0 +1,18 @@
1
+ import { createRequire } from 'node:module';
2
+ import { join } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ /**
5
+ * The `version` of the vitest that the project at `projectRoot` resolves, or `null` when it
6
+ * resolves none or reports no version. Resolution runs from the project, not from this package, so
7
+ * a consumer's own vitest is the one measured — including one hoisted to a workspace root.
8
+ */
9
+ export function readVitestVersion(projectRoot, load = (root) => createRequire(pathToFileURL(join(root, 'package.json')))('vitest/package.json')) {
10
+ try {
11
+ const { version } = load(projectRoot);
12
+ return typeof version === 'string' ? version : null;
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ //# sourceMappingURL=read-vitest-version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-vitest-version.js","sourceRoot":"","sources":["../../src/mutation/read-vitest-version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAKzC;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,WAAmB,EACnB,OAA0B,CAAC,IAAI,EAAE,EAAE,CACjC,aAAa,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC;IAEjF,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,WAAW,CAA0B,CAAC;QAC/D,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Why the mutation gate refuses to run against `version`, or `null` when it will run. An absent or
3
+ * unparseable version yields `null` — the runner reports its own missing-vitest failure better than
4
+ * a guess here would.
5
+ */
6
+ export declare function unsupportedVitestReason(version: string | null): string | null;
@@ -0,0 +1,24 @@
1
+ // Stryker's vitest runner completes zero tests per mutant from this major on: the dry run and
2
+ // per-test coverage both still succeed, so every mutant scores as `survived` rather than erroring.
3
+ // Verified against @stryker-mutator/vitest-runner 9.6.1 and 10.0.0; vitest 4 is unaffected.
4
+ const FIRST_UNSUPPORTED_MAJOR = 5;
5
+ /**
6
+ * Why the mutation gate refuses to run against `version`, or `null` when it will run. An absent or
7
+ * unparseable version yields `null` — the runner reports its own missing-vitest failure better than
8
+ * a guess here would.
9
+ */
10
+ export function unsupportedVitestReason(version) {
11
+ if (version === null) {
12
+ return null;
13
+ }
14
+ const major = Number.parseInt(version, 10);
15
+ if (!Number.isInteger(major) || major < FIRST_UNSUPPORTED_MAJOR) {
16
+ return null;
17
+ }
18
+ return (`the mutation gate cannot judge mutants on vitest ${version}: Stryker's vitest runner runs no ` +
19
+ `tests per mutant on vitest ${FIRST_UNSUPPORTED_MAJOR} and above, so every mutant reports as ` +
20
+ `survived even when the suite kills it. Pin \`vitest\` and \`@vitest/coverage-v8\` to \`^4\` ` +
21
+ `to run this gate, or leave \`mutation\` out of the workflow's \`gates\` to skip it. ` +
22
+ `See https://github.com/thekevinscott/testing-conventions/issues/700.`);
23
+ }
24
+ //# sourceMappingURL=unsupported-vitest-reason.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unsupported-vitest-reason.js","sourceRoot":"","sources":["../../src/mutation/unsupported-vitest-reason.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,mGAAmG;AACnG,4FAA4F;AAC5F,MAAM,uBAAuB,GAAG,CAAC,CAAC;AAElC;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAsB;IAC5D,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC3C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,uBAAuB,EAAE,CAAC;QAChE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,CACL,oDAAoD,OAAO,oCAAoC;QAC/F,8BAA8B,uBAAuB,yCAAyC;QAC9F,8FAA8F;QAC9F,sFAAsF;QACtF,sEAAsE,CACvE,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testing-conventions",
3
- "version": "0.0.125",
3
+ "version": "0.0.127",
4
4
  "description": "Enforce testing conventions in libraries (Python, TypeScript, and Rust).",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/thekevinscott/testing-conventions",
@@ -55,10 +55,10 @@
55
55
  "vitest": "^3.2.4"
56
56
  },
57
57
  "optionalDependencies": {
58
- "@testing-conventions/x86_64-unknown-linux-gnu": "0.0.125",
59
- "@testing-conventions/aarch64-unknown-linux-gnu": "0.0.125",
60
- "@testing-conventions/x86_64-apple-darwin": "0.0.125",
61
- "@testing-conventions/aarch64-apple-darwin": "0.0.125",
62
- "@testing-conventions/x86_64-pc-windows-msvc": "0.0.125"
58
+ "@testing-conventions/x86_64-unknown-linux-gnu": "0.0.127",
59
+ "@testing-conventions/aarch64-unknown-linux-gnu": "0.0.127",
60
+ "@testing-conventions/x86_64-apple-darwin": "0.0.127",
61
+ "@testing-conventions/aarch64-apple-darwin": "0.0.127",
62
+ "@testing-conventions/x86_64-pc-windows-msvc": "0.0.127"
63
63
  }
64
64
  }