@blzsky/variant 0.2.1 → 0.3.0-canary.1

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.
Files changed (58) hide show
  1. package/README.md +222 -85
  2. package/dist/{build-3DSBMMI3.js → build-GCUFPPIB.js} +11 -10
  3. package/dist/check-AKYABBIV.js +1459 -0
  4. package/dist/{chunk-43VLELTL.js → chunk-2UVLKBLV.js} +1 -1
  5. package/dist/chunk-34VGKPEM.js +59 -0
  6. package/dist/chunk-BNACT47L.js +590 -0
  7. package/dist/chunk-C3NZIFW6.js +174 -0
  8. package/dist/{chunk-ZH2PQUNL.js → chunk-EFCMBBBT.js} +72 -110
  9. package/dist/chunk-H55GYSQ6.js +102 -0
  10. package/dist/chunk-HL65IPEQ.js +167 -0
  11. package/dist/chunk-IHXJ72XA.js +31 -0
  12. package/dist/{chunk-FZEXZBXB.js → chunk-JEJRXPCU.js} +41 -9
  13. package/dist/chunk-LUTP2TVU.js +94 -0
  14. package/dist/{chunk-7OPTWU6E.js → chunk-MKUYZIXF.js} +1 -1
  15. package/dist/chunk-MNYKLWRC.js +377 -0
  16. package/dist/{chunk-Q7RLUZSN.js → chunk-NJSTAWDM.js} +10 -3
  17. package/dist/{chunk-26DHEXGS.js → chunk-NOC53Z5Q.js} +1 -1
  18. package/dist/chunk-O6FCFZDF.js +127 -0
  19. package/dist/{chunk-6BXSZPHC.js → chunk-OON6O4S5.js} +99 -6
  20. package/dist/{chunk-FA64KWJ2.js → chunk-TPMHY4L6.js} +16 -31
  21. package/dist/chunk-TRUK3TJF.js +575 -0
  22. package/dist/chunk-WTXWEEI2.js +21 -0
  23. package/dist/{chunk-RC23JQKI.js → chunk-WULBOSG5.js} +15 -2
  24. package/dist/chunk-X3VVGTEV.js +268 -0
  25. package/dist/{chunk-TCISS2AS.js → chunk-XXMD4NC5.js} +120 -25
  26. package/dist/cli.js +89 -30
  27. package/dist/{diff-HSJBZFMC.js → diff-ST5KHT7P.js} +5 -5
  28. package/dist/{doctor-B7MHFIYJ.js → doctor-OEIY4Y5X.js} +41 -8
  29. package/dist/{env-I7JZRWDN.js → env-EQAOZCFR.js} +8 -9
  30. package/dist/git-VGHUF2SY.js +30 -0
  31. package/dist/impact-AXLZTLVT.js +266 -0
  32. package/dist/index.js +2 -2
  33. package/dist/init-QCOZWLOO.js +793 -0
  34. package/dist/{insight-JQLTGFYB.js → insight-KRWT6Z47.js} +9 -10
  35. package/dist/pr-7JXUC5BQ.js +710 -0
  36. package/dist/{run-C6VAQOVM.js → run-UOPS5FRU.js} +11 -10
  37. package/dist/stryker-host.d.ts +2 -0
  38. package/dist/stryker-host.js +135 -0
  39. package/dist/stryker-plugins.d.ts +33 -0
  40. package/dist/stryker-plugins.js +142 -0
  41. package/dist/{symbol-graph-E5GXKDFZ.js → symbol-graph-GYOI4GU4.js} +3 -3
  42. package/dist/{tsconfig-paths-E3UEDGXZ.js → tsconfig-paths-SV3PULCW.js} +3 -1
  43. package/dist/vitest.cjs +629 -0
  44. package/dist/vitest.d.cts +181 -0
  45. package/dist/vitest.d.ts +181 -0
  46. package/dist/vitest.js +358 -0
  47. package/dist/workspace-IQFLR3RY.js +65 -0
  48. package/package.json +17 -3
  49. package/dist/check-SIQJU6Z2.js +0 -44
  50. package/dist/chunk-DRMASVYN.js +0 -25
  51. package/dist/chunk-KSHWC2XN.js +0 -78
  52. package/dist/chunk-QRPI6TJE.js +0 -68
  53. package/dist/chunk-VYWPQKMS.js +0 -88
  54. package/dist/chunk-XTYVVUWW.js +0 -279
  55. package/dist/impact-PRZ6HEP5.js +0 -735
  56. package/dist/init-O2BY3FOY.js +0 -158
  57. package/dist/pr-Y724QAHE.js +0 -165
  58. package/dist/workspace-HRTFGWKN.js +0 -186
@@ -2,23 +2,24 @@ import {
2
2
  executeTarget,
3
3
  renderDryRunPlan,
4
4
  reportRunInsights
5
- } from "./chunk-FA64KWJ2.js";
6
- import "./chunk-43VLELTL.js";
7
- import "./chunk-QRPI6TJE.js";
5
+ } from "./chunk-TPMHY4L6.js";
6
+ import "./chunk-2UVLKBLV.js";
8
7
  import {
9
8
  createContext,
10
9
  toRunOptions
11
- } from "./chunk-ZH2PQUNL.js";
12
- import "./chunk-V5CTGXMM.js";
13
- import "./chunk-26DHEXGS.js";
14
- import "./chunk-YYGCSKIQ.js";
10
+ } from "./chunk-EFCMBBBT.js";
11
+ import "./chunk-H55GYSQ6.js";
15
12
  import "./chunk-GDSZFIHM.js";
16
- import "./chunk-7OPTWU6E.js";
13
+ import "./chunk-MKUYZIXF.js";
14
+ import "./chunk-IHXJ72XA.js";
15
+ import "./chunk-V5CTGXMM.js";
17
16
  import "./chunk-3E5LBU3O.js";
18
- import "./chunk-6BXSZPHC.js";
19
17
  import "./chunk-6J4XDG44.js";
18
+ import "./chunk-NOC53Z5Q.js";
19
+ import "./chunk-YYGCSKIQ.js";
20
20
  import "./chunk-EISTWXDW.js";
21
- import "./chunk-VYWPQKMS.js";
21
+ import "./chunk-OON6O4S5.js";
22
+ import "./chunk-C3NZIFW6.js";
22
23
 
23
24
  // src/cli/commands/run.ts
24
25
  async function registerRunAction(taskName, opts = {}) {
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,135 @@
1
+ import {
2
+ EXCLUDED_MUTATORS
3
+ } from "./chunk-WTXWEEI2.js";
4
+
5
+ // src/adapters/mutation/stryker-host.ts
6
+ import { readFile } from "fs/promises";
7
+ import { createRequire as createRequire2 } from "module";
8
+ import { Stryker } from "@stryker-mutator/core";
9
+
10
+ // src/adapters/mutation/tsconfig.ts
11
+ import { createRequire } from "module";
12
+ import path from "path";
13
+ import { pathToFileURL } from "url";
14
+ async function canRewriteTsconfig(load) {
15
+ try {
16
+ const module = await load();
17
+ const api = module?.default ?? module;
18
+ return typeof api?.["parseConfigFileTextToJson"] === "function";
19
+ } catch {
20
+ return false;
21
+ }
22
+ }
23
+ function loadTypescriptAsStryker() {
24
+ const core = createRequire(import.meta.url).resolve("@stryker-mutator/core");
25
+ const typescript = createRequire(core).resolve("typescript");
26
+ return import(pathToFileURL(typescript).href);
27
+ }
28
+ async function tsconfigLeavesPackage(root, read) {
29
+ const { ts } = await import("ts-morph");
30
+ const visited = /* @__PURE__ */ new Set();
31
+ const inside = (file) => {
32
+ const relative = path.relative(root, file);
33
+ return !relative.startsWith("..") && !path.isAbsolute(relative);
34
+ };
35
+ const visit = async (file) => {
36
+ if (visited.has(file)) return false;
37
+ visited.add(file);
38
+ const text = await read(file);
39
+ if (text === null) return false;
40
+ const config = ts.parseConfigFileTextToJson(file, text).config;
41
+ const extended = Array.isArray(config?.extends) ? config.extends : [config?.extends];
42
+ for (const target of extended) {
43
+ if (typeof target !== "string" || !target.startsWith(".")) continue;
44
+ const resolved = path.resolve(path.dirname(file), target);
45
+ if (!inside(resolved)) return true;
46
+ if (await visit(resolved.endsWith(".json") ? resolved : `${resolved}.json`)) {
47
+ return true;
48
+ }
49
+ }
50
+ const references = Array.isArray(config?.references) ? config.references : [];
51
+ return references.some(
52
+ (reference) => typeof reference?.path === "string" && !inside(
53
+ path.resolve(
54
+ path.dirname(file),
55
+ reference.path
56
+ )
57
+ )
58
+ );
59
+ };
60
+ return visit(path.join(root, "tsconfig.json"));
61
+ }
62
+
63
+ // src/adapters/mutation/stryker-host.ts
64
+ function logLevel(value) {
65
+ return value;
66
+ }
67
+ function send(message) {
68
+ process.send?.(message);
69
+ }
70
+ process.on("disconnect", () => {
71
+ if (process.platform === "win32") process.exit(1);
72
+ try {
73
+ process.kill(-process.pid, "SIGKILL");
74
+ } catch {
75
+ process.exit(1);
76
+ }
77
+ });
78
+ process.once("message", (request) => {
79
+ void run(request);
80
+ });
81
+ async function run(request) {
82
+ const vitestRunner = createRequire2(import.meta.url).resolve(
83
+ "@stryker-mutator/vitest-runner"
84
+ );
85
+ const rewriteTsconfig = await canRewriteTsconfig(loadTypescriptAsStryker);
86
+ if (!rewriteTsconfig && await tsconfigLeavesPackage(
87
+ process.cwd(),
88
+ (file) => readFile(file, "utf8").catch(() => null)
89
+ ).catch(() => false)) {
90
+ send({
91
+ kind: "note",
92
+ text: "the tsconfig here points outside this package (a relative `extends` or `references`). This TypeScript has no JavaScript API (TypeScript 7 does not), so that path was not adjusted for the copy the tests ran in."
93
+ });
94
+ }
95
+ try {
96
+ await new Stryker({
97
+ ...!rewriteTsconfig && {
98
+ tsconfigFile: `${request.tempDirName}.no-tsconfig.json`
99
+ },
100
+ configFile: request.configFile,
101
+ // Stryker's Vitest runner, made to count a test file that fails to
102
+ // load: see stryker-plugins.ts.
103
+ testRunner: "variant-vitest",
104
+ vitest: {
105
+ // The whole configured suite in the baseline, so each mutant's
106
+ // tests come from measured coverage, not from Vitest's guess at
107
+ // which tests relate to the file.
108
+ related: false,
109
+ ...request.vitestConfig !== void 0 && {
110
+ configFile: request.vitestConfig
111
+ }
112
+ },
113
+ mutate: request.mutate,
114
+ mutator: { excludedMutations: EXCLUDED_MUTATORS },
115
+ plugins: [vitestRunner],
116
+ appendPlugins: [request.plugins],
117
+ reporters: ["variant"],
118
+ tempDirName: request.tempDirName,
119
+ cleanTempDir: "always",
120
+ ignorePatterns: ["/.variant"],
121
+ fileLogLevel: logLevel("off"),
122
+ // Stryker names the failing baseline tests only in its log.
123
+ logLevel: logLevel("error"),
124
+ incremental: false,
125
+ thresholds: { high: 80, low: 60, break: null },
126
+ dryRunTimeoutMinutes: request.baselineTimeoutMinutes
127
+ }).runMutationTest();
128
+ send({ kind: "finished" });
129
+ } catch (error) {
130
+ const message = error instanceof Error ? error.message : String(error);
131
+ send(
132
+ /failed tests in the initial test run/i.test(message) ? { kind: "baseline-failed" } : { kind: "error", message }
133
+ );
134
+ }
135
+ }
@@ -0,0 +1,33 @@
1
+ import * as _stryker_mutator_api_plugin from '@stryker-mutator/api/plugin';
2
+ import { PluginKind } from '@stryker-mutator/api/plugin';
3
+ import { TestRunner, FailedTestResult, SkippedTestResult, DryRunResult } from '@stryker-mutator/api/test-runner';
4
+
5
+ /**
6
+ * Stryker's setup records each file's coverage on `meta.mutantCoverage`, as
7
+ * whatever the instrumented code collected: nothing at all when no mutated
8
+ * code ran. With every file empty its runner returns no coverage, which
9
+ * Stryker reads as "coverage analysis off": every mutant then ran against
10
+ * every test, and code no test imports read as undetected (found on pyra).
11
+ *
12
+ * The key is set even when empty, so a file that has it shows the setup ran
13
+ * and found nothing: that is coverage, of nothing. A file without it says
14
+ * nothing either way.
15
+ */
16
+ declare function measuredNothing(runner: TestRunner): boolean;
17
+ /**
18
+ * Tests Vitest skipped (`skip`, `skipIf`, `todo`). They have no result, and
19
+ * Stryker's runner keeps only tests with one, so without these the report
20
+ * could not say that any were skipped.
21
+ */
22
+ declare function skippedTests(runner: TestRunner): SkippedTestResult[];
23
+ /**
24
+ * Files Vitest could not run at all: an import that fails, a `beforeAll` at
25
+ * the top of the file that throws. Their errors sit on the file, not on a
26
+ * test, which is where Stryker's runner looks.
27
+ */
28
+ declare function filesThatDidNotLoad(runner: TestRunner): FailedTestResult[];
29
+ /** The baseline as Vitest saw it: files that did not load fail it, and skipped tests are counted. */
30
+ declare function withLoadFailures(result: DryRunResult, runner: TestRunner): DryRunResult;
31
+ declare const strykerPlugins: (_stryker_mutator_api_plugin.FactoryPlugin<PluginKind.TestRunner, ["$injector"]> | _stryker_mutator_api_plugin.ClassPlugin<PluginKind.Reporter, []>)[];
32
+
33
+ export { filesThatDidNotLoad, measuredNothing, skippedTests, strykerPlugins, withLoadFailures };
@@ -0,0 +1,142 @@
1
+ import {
2
+ LOAD_FAILURE_SUFFIX
3
+ } from "./chunk-WTXWEEI2.js";
4
+
5
+ // src/adapters/mutation/stryker-plugins.ts
6
+ import {
7
+ commonTokens,
8
+ declareClassPlugin,
9
+ declareFactoryPlugin,
10
+ PluginKind,
11
+ tokens
12
+ } from "@stryker-mutator/api/plugin";
13
+ import { DryRunStatus, TestStatus } from "@stryker-mutator/api/test-runner";
14
+ import { strykerPlugins as vitestPlugins } from "@stryker-mutator/vitest-runner";
15
+ function send(message) {
16
+ process.send?.(message);
17
+ }
18
+ var VariantReporter = class {
19
+ onDryRunCompleted(event) {
20
+ const result = event.result;
21
+ send({
22
+ kind: "baseline",
23
+ tests: (result.status === "complete" ? result.tests : []).map((test) => ({
24
+ id: test.id,
25
+ name: test.name,
26
+ ...test.fileName !== void 0 && { fileName: test.fileName },
27
+ ...test.status === TestStatus.Skipped && { skipped: true }
28
+ }))
29
+ });
30
+ }
31
+ onMutationTestingPlanReady(event) {
32
+ send({
33
+ kind: "plan",
34
+ mutants: event.mutantPlans.map(({ mutant }) => ({
35
+ id: mutant.id,
36
+ fileName: mutant.fileName,
37
+ mutatorName: mutant.mutatorName,
38
+ replacement: mutant.replacement,
39
+ location: mutant.location,
40
+ ...mutant.coveredBy !== void 0 && { coveredBy: mutant.coveredBy },
41
+ ...mutant.static !== void 0 && { static: mutant.static }
42
+ }))
43
+ });
44
+ }
45
+ onMutantTested(result) {
46
+ send({
47
+ kind: "tested",
48
+ mutant: {
49
+ id: result.id,
50
+ status: result.status,
51
+ ...result.statusReason !== void 0 && {
52
+ statusReason: result.statusReason
53
+ },
54
+ ...result.killedBy !== void 0 && {
55
+ killedBy: [...result.killedBy]
56
+ },
57
+ ...result.testsCompleted !== void 0 && {
58
+ testsCompleted: result.testsCompleted
59
+ }
60
+ }
61
+ });
62
+ }
63
+ };
64
+ function measuredNothing(runner) {
65
+ const files = runner.ctx?.state?.getFiles?.() ?? [];
66
+ return files.some(
67
+ (file) => file.meta !== void 0 && Object.hasOwn(file.meta, "mutantCoverage")
68
+ );
69
+ }
70
+ function skippedTests(runner) {
71
+ const files = runner.ctx?.state?.getFiles?.() ?? [];
72
+ const out = [];
73
+ const walk = (task, file) => {
74
+ if (task.type === "test") {
75
+ if (task.result === void 0 && (task.mode === "skip" || task.mode === "todo")) {
76
+ out.push({
77
+ id: task.id ?? `${file.name ?? ""}#${task.name ?? ""}`,
78
+ name: task.name ?? "a skipped test",
79
+ status: TestStatus.Skipped,
80
+ timeSpentMs: 0,
81
+ ...file.filepath !== void 0 && { fileName: file.filepath }
82
+ });
83
+ }
84
+ return;
85
+ }
86
+ for (const child of task.tasks ?? []) walk(child, file);
87
+ };
88
+ for (const file of files) walk(file, file);
89
+ return out;
90
+ }
91
+ function filesThatDidNotLoad(runner) {
92
+ const files = runner.ctx?.state?.getFiles?.() ?? [];
93
+ return files.filter((file) => (file.result?.errors?.length ?? 0) > 0).map((file) => {
94
+ const name = file.name ?? file.filepath ?? "a test file";
95
+ return {
96
+ id: `${name}#variant-load`,
97
+ name: `${name}${LOAD_FAILURE_SUFFIX}`,
98
+ status: TestStatus.Failed,
99
+ failureMessage: file.result?.errors?.[0]?.message?.split("\n")[0] ?? "the file failed to load",
100
+ timeSpentMs: 0,
101
+ ...file.filepath !== void 0 && { fileName: file.filepath }
102
+ };
103
+ });
104
+ }
105
+ function withLoadFailures(result, runner) {
106
+ if (result.status !== DryRunStatus.Complete) return result;
107
+ const added = [...filesThatDidNotLoad(runner), ...skippedTests(runner)];
108
+ const coverage = result.mutantCoverage === void 0 && measuredNothing(runner) ? { mutantCoverage: { static: {}, perTest: {} } } : {};
109
+ return added.length === 0 && Object.keys(coverage).length === 0 ? result : { ...result, ...coverage, tests: [...result.tests, ...added] };
110
+ }
111
+ var vitest = vitestPlugins.find(
112
+ (plugin) => plugin.kind === PluginKind.TestRunner && plugin.name === "vitest"
113
+ );
114
+ function createVitestRunner(injector) {
115
+ if (vitest === void 0 || !("factory" in vitest)) {
116
+ throw new Error("@stryker-mutator/vitest-runner has no Vitest runner");
117
+ }
118
+ const runner = injector.injectFunction(vitest.factory);
119
+ return {
120
+ capabilities: () => runner.capabilities(),
121
+ init: async () => runner.init?.(),
122
+ dryRun: async (options) => withLoadFailures(await runner.dryRun(options), runner),
123
+ mutantRun: (options) => runner.mutantRun(options),
124
+ dispose: async () => runner.dispose?.()
125
+ };
126
+ }
127
+ createVitestRunner.inject = tokens(commonTokens.injector);
128
+ var strykerPlugins = [
129
+ declareClassPlugin(PluginKind.Reporter, "variant", VariantReporter),
130
+ declareFactoryPlugin(
131
+ PluginKind.TestRunner,
132
+ "variant-vitest",
133
+ createVitestRunner
134
+ )
135
+ ];
136
+ export {
137
+ filesThatDidNotLoad,
138
+ measuredNothing,
139
+ skippedTests,
140
+ strykerPlugins,
141
+ withLoadFailures
142
+ };
@@ -7,9 +7,9 @@ import {
7
7
  loadSymbolGraph,
8
8
  saveSymbolGraph,
9
9
  updateSymbolGraph
10
- } from "./chunk-TCISS2AS.js";
11
- import "./chunk-RC23JQKI.js";
12
- import "./chunk-VYWPQKMS.js";
10
+ } from "./chunk-XXMD4NC5.js";
11
+ import "./chunk-WULBOSG5.js";
12
+ import "./chunk-C3NZIFW6.js";
13
13
  export {
14
14
  SYMBOL_GRAPH_VERSION,
15
15
  buildSymbolGraph,
@@ -1,9 +1,11 @@
1
1
  import {
2
+ aliasesInScope,
2
3
  matchPathAlias,
3
4
  readPathAliases,
4
5
  toPathAliases
5
- } from "./chunk-KSHWC2XN.js";
6
+ } from "./chunk-X3VVGTEV.js";
6
7
  export {
8
+ aliasesInScope,
7
9
  matchPathAlias,
8
10
  readPathAliases,
9
11
  toPathAliases