@systemfsoftware/stryker-js-cli 4.0.2 → 5.0.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/CHANGELOG.md +32 -0
- package/README.md +2 -2
- package/dist/main.mjs +349 -396
- package/dist/node-D-ynY_kk.mjs +105 -0
- package/dist/worker-runtime-CT5LMMgY.mjs +20 -0
- package/dist/workers/checker-worker.mjs +53 -0
- package/dist/workers/child-process-test-runner-worker.mjs +67 -0
- package/package.json +13 -12
package/dist/main.mjs
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { n as nodePlatformLayer } from "./node-D-ynY_kk.mjs";
|
|
3
3
|
import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem";
|
|
4
4
|
import * as NodePath from "@effect/platform-node-shared/NodePath";
|
|
5
5
|
import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
|
|
6
6
|
import * as NodeStdio from "@effect/platform-node/NodeStdio";
|
|
7
|
-
import { ConfigFileUnreadableError, buildVerdictEnvelope, defaultOptions, generateRunId, makeRunLayer, readConfig, runMutationTest, strykerEngines, strykerVersion, toRelativeNormalizedFileName } from "@systemfsoftware/stryker-js-platform-node";
|
|
8
7
|
import * as Effect from "effect/Effect";
|
|
9
8
|
import * as Exit from "effect/Exit";
|
|
10
9
|
import * as Layer from "effect/Layer";
|
|
11
10
|
import * as Logger from "effect/Logger";
|
|
12
11
|
import * as NodeChildProcessSpawner from "@effect/platform-node-shared/NodeChildProcessSpawner";
|
|
12
|
+
import { ConfigFileUnreadableError, buildVerdictEnvelope, defaultOptions, generateRunId, makeRunLayer, readConfig, runMutationTest, strykerVersion, toRelativeNormalizedFileName } from "@systemfsoftware/stryker-js-engine";
|
|
13
|
+
import { Mutant, causeText } from "@systemfsoftware/stryker-js/Mutant";
|
|
13
14
|
import { Heartbeat, HelpRendered, ManifestRendered, RunEvents, RunFailed, RunStarted, VerdictReached } from "@systemfsoftware/stryker-js/Run";
|
|
14
15
|
import { RENDERED_OPTION_DEFAULTS } from "@systemfsoftware/stryker-js/Schema";
|
|
15
16
|
import * as Cause from "effect/Cause";
|
|
@@ -30,94 +31,21 @@ import * as CliError from "effect/unstable/cli/CliError";
|
|
|
30
31
|
import * as Command from "effect/unstable/cli/Command";
|
|
31
32
|
import * as Flag from "effect/unstable/cli/Flag";
|
|
32
33
|
import * as GlobalFlag from "effect/unstable/cli/GlobalFlag";
|
|
33
|
-
import {
|
|
34
|
-
import { resolve } from "node:path";
|
|
34
|
+
import { Cell, Workflow } from "@systemfsoftware/effect-cell-types";
|
|
35
35
|
import { ExitClass, highestExitClass } from "@systemfsoftware/stryker-js/ExitClass";
|
|
36
|
-
import { Mutant, causeText } from "@systemfsoftware/stryker-js/Mutant";
|
|
37
36
|
import * as Clock from "effect/Clock";
|
|
38
37
|
import * as Formatter from "effect/Formatter";
|
|
39
38
|
import * as Predicate from "effect/Predicate";
|
|
40
|
-
import { Cell, Workflow } from "@systemfsoftware/effect-cell-types";
|
|
41
39
|
import * as Context from "effect/Context";
|
|
42
|
-
import { pipe } from "effect/Function";
|
|
43
40
|
import * as Stdio from "effect/Stdio";
|
|
44
41
|
import * as Stream from "effect/Stream";
|
|
42
|
+
import "@systemfsoftware/stryker-js/Module";
|
|
45
43
|
import { sha256 } from "@noble/hashes/sha256";
|
|
46
44
|
import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils";
|
|
47
|
-
//#region
|
|
48
|
-
var
|
|
49
|
-
succeeded: S.Boolean,
|
|
50
|
-
signal: S.optional(S.Finite),
|
|
51
|
-
interrupted: S.Boolean,
|
|
52
|
-
helpErrorCount: S.optional(S.Finite),
|
|
53
|
-
cliError: S.Boolean,
|
|
54
|
-
unrecognized: S.optional(S.String),
|
|
55
|
-
survivorsReason: S.optional(S.Literals(["no-report", "mismatch"])),
|
|
56
|
-
survivorsDiagnostic: S.optional(S.String),
|
|
57
|
-
schemaError: S.Boolean,
|
|
58
|
-
successExitClass: S.optional(ExitClass),
|
|
59
|
-
highestExitClass: S.optional(ExitClass),
|
|
60
|
-
configDetail: S.optional(S.String),
|
|
61
|
-
diagnostic: S.optional(S.String)
|
|
62
|
-
}) {};
|
|
63
|
-
const CONFIG_CODE$1 = 2;
|
|
64
|
-
const classCode = (exitClass) => Match.value(exitClass).pipe(Match.when("VerdictFail", () => 1), Match.when("ConfigError", () => CONFIG_CODE$1), Match.when("RuntimeError", () => 3), Match.when("InternalError", () => 4), Match.exhaustive);
|
|
65
|
-
var RunOk = class extends S.TaggedClass()("RunOk", { help: S.Boolean }) {};
|
|
66
|
-
var RunInterrupted = class extends S.TaggedError()("RunInterrupted", { code: S.Finite }) {};
|
|
67
|
-
var RunParseFailed = class extends S.TaggedError()("RunParseFailed", { unrecognized: S.optional(S.String) }) {};
|
|
68
|
-
var RunSurvivorsRejected = class extends S.TaggedError()("RunSurvivorsRejected", {
|
|
69
|
-
reason: S.Literals(["no-report", "mismatch"]),
|
|
70
|
-
diagnostic: S.optional(S.String)
|
|
71
|
-
}) {};
|
|
72
|
-
var RunConfigFailed = class extends S.TaggedError()("RunConfigFailed", { detail: S.optional(S.String) }) {};
|
|
73
|
-
var RunFailed$1 = class extends S.TaggedError()("RunFailed", {
|
|
74
|
-
code: S.Finite,
|
|
75
|
-
diagnostic: S.optional(S.String)
|
|
76
|
-
}) {};
|
|
77
|
-
function classify(command) {
|
|
78
|
-
if (command.signal !== void 0) return RunInterrupted.make({ code: 128 + command.signal });
|
|
79
|
-
if (command.succeeded) {
|
|
80
|
-
if (command.successExitClass !== void 0) return RunFailed$1.make({
|
|
81
|
-
code: classCode(command.successExitClass),
|
|
82
|
-
diagnostic: command.diagnostic
|
|
83
|
-
});
|
|
84
|
-
return RunOk.make({ help: false });
|
|
85
|
-
}
|
|
86
|
-
if (command.interrupted) return RunInterrupted.make({ code: 1 });
|
|
87
|
-
if (command.helpErrorCount !== void 0) {
|
|
88
|
-
if (command.helpErrorCount > 0) return RunParseFailed.make({ unrecognized: command.unrecognized });
|
|
89
|
-
return RunOk.make({ help: true });
|
|
90
|
-
}
|
|
91
|
-
if (command.cliError) return RunParseFailed.make({ unrecognized: command.unrecognized });
|
|
92
|
-
if (command.survivorsReason !== void 0) return RunSurvivorsRejected.make({
|
|
93
|
-
reason: command.survivorsReason,
|
|
94
|
-
diagnostic: command.survivorsDiagnostic
|
|
95
|
-
});
|
|
96
|
-
if (command.schemaError) return RunConfigFailed.make({ detail: command.configDetail });
|
|
97
|
-
if (command.highestExitClass !== void 0) {
|
|
98
|
-
if (command.highestExitClass === "ConfigError") return RunConfigFailed.make({ detail: command.configDetail });
|
|
99
|
-
return RunFailed$1.make({
|
|
100
|
-
code: classCode(command.highestExitClass),
|
|
101
|
-
diagnostic: command.diagnostic
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
return RunFailed$1.make({
|
|
105
|
-
code: 1,
|
|
106
|
-
diagnostic: command.diagnostic
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
function succeedRun(ok) {
|
|
110
|
-
return Result.succeed(ok);
|
|
111
|
-
}
|
|
112
|
-
function failRun(error) {
|
|
113
|
-
return Result.fail(error);
|
|
114
|
-
}
|
|
115
|
-
function runOutcomeDecision(command) {
|
|
116
|
-
return Match.value(classify(command)).pipe(Match.tag("RunOk", succeedRun), Match.tag("RunInterrupted", failRun), Match.tag("RunParseFailed", failRun), Match.tag("RunSurvivorsRejected", failRun), Match.tag("RunConfigFailed", failRun), Match.tag("RunFailed", failRun), Match.exhaustive);
|
|
117
|
-
}
|
|
118
|
-
const runOutcomeWorkflow = Workflow.make(RunOutcomeCommand, runOutcomeDecision);
|
|
45
|
+
//#region package.json
|
|
46
|
+
var engines = { "node": ">=20.0.0" };
|
|
119
47
|
//#endregion
|
|
120
|
-
//#region src/
|
|
48
|
+
//#region src/admit-survivors-run.workflow.ts
|
|
121
49
|
/**
|
|
122
50
|
* The mutant shape the admission carries, named once because both the decision's
|
|
123
51
|
* `Admitted` payload and the command's precomputed survivor list are the same shape.
|
|
@@ -317,20 +245,91 @@ function admissionDecision(command) {
|
|
|
317
245
|
}
|
|
318
246
|
const admitSurvivorsRun = Workflow.make(AdmitSurvivorsRunCommand, admissionDecision);
|
|
319
247
|
//#endregion
|
|
248
|
+
//#region src/classify-run-outcome.workflow.ts
|
|
249
|
+
var RunOutcomeCommand = class extends S.TaggedClass()("RunOutcomeCommand", {
|
|
250
|
+
succeeded: S.Boolean,
|
|
251
|
+
signal: S.optional(S.Finite),
|
|
252
|
+
interrupted: S.Boolean,
|
|
253
|
+
helpErrorCount: S.optional(S.Finite),
|
|
254
|
+
cliError: S.Boolean,
|
|
255
|
+
unrecognized: S.optional(S.String),
|
|
256
|
+
survivorsReason: S.optional(S.Literals(["no-report", "mismatch"])),
|
|
257
|
+
survivorsDiagnostic: S.optional(S.String),
|
|
258
|
+
schemaError: S.Boolean,
|
|
259
|
+
successExitClass: S.optional(ExitClass),
|
|
260
|
+
highestExitClass: S.optional(ExitClass),
|
|
261
|
+
configDetail: S.optional(S.String),
|
|
262
|
+
diagnostic: S.optional(S.String)
|
|
263
|
+
}) {};
|
|
264
|
+
const CONFIG_CODE$1 = 2;
|
|
265
|
+
const classCode = (exitClass) => Match.value(exitClass).pipe(Match.when("VerdictFail", () => 1), Match.when("ConfigError", () => CONFIG_CODE$1), Match.when("RuntimeError", () => 3), Match.when("InternalError", () => 4), Match.exhaustive);
|
|
266
|
+
const RunOutcomeTypeId = Symbol.for("@systemfsoftware/stryker-js-cli/RunOutcome");
|
|
267
|
+
var RunOk = class extends S.TaggedClass()("RunOk", { help: S.Boolean }) {
|
|
268
|
+
[RunOutcomeTypeId] = RunOutcomeTypeId;
|
|
269
|
+
};
|
|
270
|
+
var RunInterrupted = class extends S.TaggedError()("RunInterrupted", { code: S.Finite }) {
|
|
271
|
+
[RunOutcomeTypeId] = RunOutcomeTypeId;
|
|
272
|
+
};
|
|
273
|
+
var RunParseFailed = class extends S.TaggedClass()("RunParseFailed", { unrecognized: S.optional(S.String) }) {
|
|
274
|
+
[RunOutcomeTypeId] = RunOutcomeTypeId;
|
|
275
|
+
};
|
|
276
|
+
var RunSurvivorsRejected = class extends S.TaggedClass()("RunSurvivorsRejected", {
|
|
277
|
+
reason: S.Literals(["no-report", "mismatch"]),
|
|
278
|
+
diagnostic: S.optional(S.String)
|
|
279
|
+
}) {
|
|
280
|
+
[RunOutcomeTypeId] = RunOutcomeTypeId;
|
|
281
|
+
};
|
|
282
|
+
var RunConfigFailed = class extends S.TaggedClass()("RunConfigFailed", { detail: S.optional(S.String) }) {
|
|
283
|
+
[RunOutcomeTypeId] = RunOutcomeTypeId;
|
|
284
|
+
};
|
|
285
|
+
var RunFailed$1 = class extends S.TaggedClass()("RunFailed", {
|
|
286
|
+
code: S.Finite,
|
|
287
|
+
diagnostic: S.optional(S.String)
|
|
288
|
+
}) {
|
|
289
|
+
[RunOutcomeTypeId] = RunOutcomeTypeId;
|
|
290
|
+
};
|
|
291
|
+
function classify(command) {
|
|
292
|
+
if (command.signal !== void 0) return RunInterrupted.make({ code: 128 + command.signal });
|
|
293
|
+
if (command.succeeded) {
|
|
294
|
+
if (command.successExitClass !== void 0) return RunFailed$1.make({
|
|
295
|
+
code: classCode(command.successExitClass),
|
|
296
|
+
diagnostic: command.diagnostic
|
|
297
|
+
});
|
|
298
|
+
return RunOk.make({ help: false });
|
|
299
|
+
}
|
|
300
|
+
if (command.interrupted) return RunInterrupted.make({ code: 1 });
|
|
301
|
+
if (command.helpErrorCount !== void 0) {
|
|
302
|
+
if (command.helpErrorCount > 0) return RunParseFailed.make({ unrecognized: command.unrecognized });
|
|
303
|
+
return RunOk.make({ help: true });
|
|
304
|
+
}
|
|
305
|
+
if (command.cliError) return RunParseFailed.make({ unrecognized: command.unrecognized });
|
|
306
|
+
if (command.survivorsReason !== void 0) return RunSurvivorsRejected.make({
|
|
307
|
+
reason: command.survivorsReason,
|
|
308
|
+
diagnostic: command.survivorsDiagnostic
|
|
309
|
+
});
|
|
310
|
+
if (command.schemaError) return RunConfigFailed.make({ detail: command.configDetail });
|
|
311
|
+
if (command.highestExitClass !== void 0) {
|
|
312
|
+
if (command.highestExitClass === "ConfigError") return RunConfigFailed.make({ detail: command.configDetail });
|
|
313
|
+
return RunFailed$1.make({
|
|
314
|
+
code: classCode(command.highestExitClass),
|
|
315
|
+
diagnostic: command.diagnostic
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return RunFailed$1.make({
|
|
319
|
+
code: 1,
|
|
320
|
+
diagnostic: command.diagnostic
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
const classifyRunOutcome$1 = Workflow.make(RunOutcomeCommand, (command) => Match.value(classify(command)).pipe(Match.tag("RunInterrupted", (error) => Result.fail(error)), Match.when((outcome) => !(outcome instanceof RunInterrupted), (decision) => Result.succeed(decision)), Match.exhaustive));
|
|
324
|
+
//#endregion
|
|
320
325
|
//#region src/Envelope.ts
|
|
321
|
-
/**
|
|
322
|
-
* Envelope — the failure envelope and console capture leaf.
|
|
323
|
-
*
|
|
324
|
-
* Extracted from Cli.ts to break the import cycle Cli <-> Output.
|
|
325
|
-
* Both Cli and Output import from this leaf, so neither depends on the other
|
|
326
|
-
* for these values. This file imports only from external packages and from
|
|
327
|
-
* StreamVersion (leaf) and Survivors.workflow (leaf), never from Cli or Output
|
|
328
|
-
* themselves.
|
|
329
|
-
*/
|
|
330
326
|
const CONFIG_CODE = 2;
|
|
331
327
|
function runOutcomeCode(result) {
|
|
332
|
-
|
|
333
|
-
|
|
328
|
+
const outcome = Result.match(result, {
|
|
329
|
+
onSuccess: (success) => success,
|
|
330
|
+
onFailure: (failure) => failure
|
|
331
|
+
});
|
|
332
|
+
return Match.value(outcome).pipe(Match.tag("RunOk", () => 0), Match.tag("RunInterrupted", (error) => error.code), Match.tag("RunParseFailed", () => CONFIG_CODE), Match.tag("RunSurvivorsRejected", () => CONFIG_CODE), Match.tag("RunConfigFailed", () => CONFIG_CODE), Match.tag("RunFailed", (failed) => failed.code), Match.exhaustive);
|
|
334
333
|
}
|
|
335
334
|
function isExitClass(value) {
|
|
336
335
|
return S.is(ExitClass)(value);
|
|
@@ -577,7 +576,7 @@ function shapeEnvelope(error, captured) {
|
|
|
577
576
|
};
|
|
578
577
|
}
|
|
579
578
|
function classifyRunOutcome(exit, signal, argv) {
|
|
580
|
-
return
|
|
579
|
+
return classifyRunOutcome$1(gatherRunOutcome(exit, signal, argv));
|
|
581
580
|
}
|
|
582
581
|
const capturedConsoleChunks = [];
|
|
583
582
|
const countByLabel = /* @__PURE__ */ new Map();
|
|
@@ -684,12 +683,8 @@ function resetCapturedConsole() {
|
|
|
684
683
|
timeByLabel.clear();
|
|
685
684
|
}
|
|
686
685
|
//#endregion
|
|
687
|
-
//#region src/
|
|
686
|
+
//#region src/resolve-output-mode.workflow.ts
|
|
688
687
|
const TOOL_VARIABLES$1 = ["CLAUDECODE", "CODEX_SANDBOX"];
|
|
689
|
-
/**
|
|
690
|
-
* The command of the output-mode workflow: a schema class, because `Workflow.make`
|
|
691
|
-
* derives the command type from it and pins the error channel at the construction site.
|
|
692
|
-
*/
|
|
693
688
|
var ResolveModeCommand = class extends S.TaggedClass()("ResolveModeCommand", {
|
|
694
689
|
stdoutIsTTY: S.Boolean,
|
|
695
690
|
text: S.optional(S.Boolean),
|
|
@@ -698,17 +693,33 @@ var ResolveModeCommand = class extends S.TaggedClass()("ResolveModeCommand", {
|
|
|
698
693
|
agent: S.optional(S.String),
|
|
699
694
|
toolVars: S.optional(S.Record(S.String, S.String))
|
|
700
695
|
}) {};
|
|
701
|
-
|
|
702
|
-
* The conflict error for mutually exclusive format flags. Defined locally so the
|
|
703
|
-
* workflow decision remains pure — the sealed effect surface does not include
|
|
704
|
-
* `effect/unstable/cli/CliError`, so the decision returns this local error and
|
|
705
|
-
* the shell maps it to `CliError.InvalidValue` at the boundary.
|
|
706
|
-
*/
|
|
696
|
+
const ResolveModeTypeId = Symbol.for("@systemfsoftware/stryker-js-cli/ResolveMode");
|
|
707
697
|
var ModeConflictError = class extends S.TaggedError()("ModeConflictError", {
|
|
708
698
|
option: S.String,
|
|
709
699
|
value: S.String,
|
|
710
700
|
expected: S.String
|
|
711
|
-
}) {
|
|
701
|
+
}) {
|
|
702
|
+
[ResolveModeTypeId] = ResolveModeTypeId;
|
|
703
|
+
};
|
|
704
|
+
const ModeSignal = S.Literals([
|
|
705
|
+
"flag",
|
|
706
|
+
"env",
|
|
707
|
+
"tty",
|
|
708
|
+
"agent",
|
|
709
|
+
"tool"
|
|
710
|
+
]);
|
|
711
|
+
var HumanOutput = class extends S.TaggedClass()("HumanOutput", {
|
|
712
|
+
signal: ModeSignal,
|
|
713
|
+
stdoutIsTTY: S.Boolean
|
|
714
|
+
}) {
|
|
715
|
+
[ResolveModeTypeId] = ResolveModeTypeId;
|
|
716
|
+
};
|
|
717
|
+
var MachineOutput = class extends S.TaggedClass()("MachineOutput", {
|
|
718
|
+
signal: ModeSignal,
|
|
719
|
+
stdoutIsTTY: S.Boolean
|
|
720
|
+
}) {
|
|
721
|
+
[ResolveModeTypeId] = ResolveModeTypeId;
|
|
722
|
+
};
|
|
712
723
|
const CONFLICT_EXPECTED = "the \"--format text\" and \"--json\" flags are mutually exclusive — use one or the other";
|
|
713
724
|
function r4(command) {
|
|
714
725
|
if (command.text === true && command.json === true) return Result.fail(ModeConflictError.make({
|
|
@@ -716,71 +727,51 @@ function r4(command) {
|
|
|
716
727
|
value: "text",
|
|
717
728
|
expected: CONFLICT_EXPECTED
|
|
718
729
|
}));
|
|
719
|
-
if (command.text === true) return Result.succeed({
|
|
720
|
-
mode: "human",
|
|
730
|
+
if (command.text === true) return Result.succeed(HumanOutput.make({
|
|
721
731
|
signal: "flag",
|
|
722
732
|
stdoutIsTTY: command.stdoutIsTTY
|
|
723
|
-
});
|
|
724
|
-
if (command.json === true) return Result.succeed({
|
|
725
|
-
mode: "machine",
|
|
733
|
+
}));
|
|
734
|
+
if (command.json === true) return Result.succeed(MachineOutput.make({
|
|
726
735
|
signal: "flag",
|
|
727
736
|
stdoutIsTTY: command.stdoutIsTTY
|
|
728
|
-
});
|
|
737
|
+
}));
|
|
729
738
|
if (command.envMode !== void 0 && command.envMode.length > 0) {
|
|
730
|
-
if (command.envMode === "machine") return Result.succeed({
|
|
731
|
-
mode: "machine",
|
|
739
|
+
if (command.envMode === "machine") return Result.succeed(MachineOutput.make({
|
|
732
740
|
signal: "env",
|
|
733
741
|
stdoutIsTTY: command.stdoutIsTTY
|
|
734
|
-
});
|
|
735
|
-
return Result.succeed({
|
|
736
|
-
mode: "human",
|
|
742
|
+
}));
|
|
743
|
+
return Result.succeed(HumanOutput.make({
|
|
737
744
|
signal: "env",
|
|
738
745
|
stdoutIsTTY: command.stdoutIsTTY
|
|
739
|
-
});
|
|
746
|
+
}));
|
|
740
747
|
}
|
|
741
|
-
if (!command.stdoutIsTTY) return Result.succeed({
|
|
742
|
-
mode: "machine",
|
|
748
|
+
if (!command.stdoutIsTTY) return Result.succeed(MachineOutput.make({
|
|
743
749
|
signal: "tty",
|
|
744
750
|
stdoutIsTTY: false
|
|
745
|
-
});
|
|
746
|
-
if (command.agent !== void 0 && command.agent.length > 0) return Result.succeed({
|
|
747
|
-
mode: "machine",
|
|
751
|
+
}));
|
|
752
|
+
if (command.agent !== void 0 && command.agent.length > 0) return Result.succeed(MachineOutput.make({
|
|
748
753
|
signal: "agent",
|
|
749
754
|
stdoutIsTTY: true
|
|
750
|
-
});
|
|
755
|
+
}));
|
|
751
756
|
const toolVars = command.toolVars ?? {};
|
|
752
757
|
for (const variable of TOOL_VARIABLES$1) {
|
|
753
758
|
const value = toolVars[variable];
|
|
754
|
-
if (typeof value === "string" && value.length > 0) return Result.succeed({
|
|
755
|
-
mode: "machine",
|
|
759
|
+
if (typeof value === "string" && value.length > 0) return Result.succeed(MachineOutput.make({
|
|
756
760
|
signal: "tool",
|
|
757
761
|
stdoutIsTTY: true
|
|
758
|
-
});
|
|
762
|
+
}));
|
|
759
763
|
}
|
|
760
|
-
return Result.succeed({
|
|
761
|
-
mode: "human",
|
|
764
|
+
return Result.succeed(HumanOutput.make({
|
|
762
765
|
signal: "tty",
|
|
763
766
|
stdoutIsTTY: true
|
|
764
|
-
});
|
|
767
|
+
}));
|
|
765
768
|
}
|
|
766
769
|
function modeDecision(command) {
|
|
767
770
|
return r4(command);
|
|
768
771
|
}
|
|
769
|
-
const
|
|
772
|
+
const resolveOutputMode = Workflow.make(ResolveModeCommand, modeDecision);
|
|
770
773
|
//#endregion
|
|
771
774
|
//#region src/Output.ts
|
|
772
|
-
/**
|
|
773
|
-
* Output — the machine/human output capability.
|
|
774
|
-
*
|
|
775
|
-
* The NDJSON run-event stream, wire protocol constants, mode resolution probes,
|
|
776
|
-
* and machine-mode terminal output. Pure mode resolution lives in
|
|
777
|
-
* Output.workflow.ts.
|
|
778
|
-
*/
|
|
779
|
-
/**
|
|
780
|
-
* The heartbeat interval (R19), matching Terraform's `apply_progress`
|
|
781
|
-
* cadence: long enough that a slow phase is not noisy, short enough that a
|
|
782
|
-
* consumer can tell "slow" from "hung" without waiting for a mutant event.
|
|
783
|
-
*/
|
|
784
775
|
const TICK_INTERVAL_MS = 1e4;
|
|
785
776
|
var RunEventStreamPortTag = class extends Context.Service()("@systemfsoftware/stryker-js-cli/Output/RunEventStreamPortTag") {};
|
|
786
777
|
const RunEventStreamPort = RunEventStreamPortTag;
|
|
@@ -892,12 +883,15 @@ const makeRunEventStream = (stdio, resolved, drainFramed = drainOf.bind(null, st
|
|
|
892
883
|
});
|
|
893
884
|
Layer.effect(RunEventStreamPort, Effect.map(Stdio.Stdio, (stdio) => RunEventStreamPort.of({ createRunEventStream: (resolved) => makeRunEventStream(stdio, resolved) })));
|
|
894
885
|
const TOOL_VARIABLES = ["CLAUDECODE", "CODEX_SANDBOX"];
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
886
|
+
const decisionToResolvedMode = (decision) => Match.value(decision).pipe(Match.tag("HumanOutput", (human) => ({
|
|
887
|
+
mode: "human",
|
|
888
|
+
signal: human.signal,
|
|
889
|
+
stdoutIsTTY: human.stdoutIsTTY
|
|
890
|
+
})), Match.tag("MachineOutput", (machine) => ({
|
|
891
|
+
mode: "machine",
|
|
892
|
+
signal: machine.signal,
|
|
893
|
+
stdoutIsTTY: machine.stdoutIsTTY
|
|
894
|
+
})), Match.exhaustive);
|
|
901
895
|
function isColorEnabled(resolved, noColor) {
|
|
902
896
|
if (resolved.mode !== "human") return false;
|
|
903
897
|
if (noColor === void 0) return true;
|
|
@@ -906,83 +900,80 @@ function isColorEnabled(resolved, noColor) {
|
|
|
906
900
|
}
|
|
907
901
|
var OutputModeProbeTag = class extends Context.Service()("@systemfsoftware/stryker-js-cli/Output/OutputModeProbeTag") {};
|
|
908
902
|
const OutputModeProbe = OutputModeProbeTag;
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
903
|
+
const outputModeProbeCell = Cell.layer({
|
|
904
|
+
read: (command) => Effect.succeed((() => {
|
|
905
|
+
const toolVarsRecord = {};
|
|
906
|
+
for (const variable of TOOL_VARIABLES) {
|
|
907
|
+
const value = process.env[variable];
|
|
908
|
+
if (value !== void 0) toolVarsRecord[variable] = value;
|
|
909
|
+
}
|
|
910
|
+
const envMode = process.env["STRYKER_MODE"];
|
|
911
|
+
const agent = process.env["AGENT"];
|
|
912
|
+
let result = { stdoutIsTTY: process.stdout.isTTY === true };
|
|
913
|
+
if (Object.keys(toolVarsRecord).length > 0) result = {
|
|
914
|
+
...result,
|
|
915
|
+
toolVars: toolVarsRecord
|
|
916
|
+
};
|
|
917
|
+
if (envMode !== void 0) result = {
|
|
918
|
+
...result,
|
|
919
|
+
envMode
|
|
920
|
+
};
|
|
921
|
+
if (agent !== void 0) result = {
|
|
922
|
+
...result,
|
|
923
|
+
agent
|
|
924
|
+
};
|
|
925
|
+
if (command.text !== void 0) result = {
|
|
926
|
+
...result,
|
|
927
|
+
text: command.text
|
|
928
|
+
};
|
|
929
|
+
if (command.json !== void 0) result = {
|
|
930
|
+
...result,
|
|
931
|
+
json: command.json
|
|
932
|
+
};
|
|
933
|
+
return result;
|
|
934
|
+
})()),
|
|
935
|
+
decode: (raw) => Result.succeed((() => {
|
|
936
|
+
const filteredToolVars = {};
|
|
937
|
+
if (raw.toolVars !== void 0) {
|
|
938
|
+
for (const [key, value] of Object.entries(raw.toolVars)) if (value !== void 0) filteredToolVars[key] = value;
|
|
939
|
+
}
|
|
940
|
+
let commandInput = { stdoutIsTTY: raw.stdoutIsTTY };
|
|
941
|
+
if (raw.text !== void 0) commandInput = {
|
|
942
|
+
...commandInput,
|
|
943
|
+
text: raw.text
|
|
944
|
+
};
|
|
945
|
+
if (raw.json !== void 0) commandInput = {
|
|
946
|
+
...commandInput,
|
|
947
|
+
json: raw.json
|
|
948
|
+
};
|
|
949
|
+
if (raw.envMode !== void 0) commandInput = {
|
|
950
|
+
...commandInput,
|
|
951
|
+
envMode: raw.envMode
|
|
952
|
+
};
|
|
953
|
+
if (raw.agent !== void 0) commandInput = {
|
|
954
|
+
...commandInput,
|
|
955
|
+
agent: raw.agent
|
|
956
|
+
};
|
|
957
|
+
if (Object.keys(filteredToolVars).length > 0) commandInput = {
|
|
958
|
+
...commandInput,
|
|
959
|
+
toolVars: filteredToolVars
|
|
960
|
+
};
|
|
961
|
+
return ResolveModeCommand.make(commandInput);
|
|
962
|
+
})()),
|
|
963
|
+
decide: resolveOutputMode,
|
|
964
|
+
encode: (outcome) => Result.map(outcome, decisionToResolvedMode),
|
|
965
|
+
write: (outcome) => Result.match(outcome, {
|
|
966
|
+
onFailure: (error) => Effect.fail(error),
|
|
967
|
+
onSuccess: (mode) => Effect.succeed(mode)
|
|
968
|
+
})
|
|
969
|
+
});
|
|
970
|
+
const detectModeWithProbe = (flags = {}) => Cell.run(outputModeProbeCell, flags).pipe(Effect.mapError((error) => CliError.InvalidValue.make({
|
|
971
971
|
option: error.option,
|
|
972
972
|
value: error.value,
|
|
973
973
|
expected: error.expected,
|
|
974
974
|
kind: "flag"
|
|
975
975
|
})));
|
|
976
976
|
const OutputModeProbeLive = Layer.succeed(OutputModeProbe, OutputModeProbe.of({ detectMode: detectModeWithProbe({}) }));
|
|
977
|
-
/**
|
|
978
|
-
* Machine mode emits the U4 verdict envelope for a run that produced no
|
|
979
|
-
* mutants and no report file: a `--survivors` run with zero survivors (AE3)
|
|
980
|
-
* or a successful `--dryRunOnly` run that ended before the mutation
|
|
981
|
-
* pipeline. The envelope carries a null score and an empty mutant list and is
|
|
982
|
-
* written as the terminal `verdict` line of the stdout stream (U6), carrying
|
|
983
|
-
* the run id the stream header already opened with (KTD11 — never a fresh
|
|
984
|
-
* id). Human mode prints nothing (the sink drops in human mode).
|
|
985
|
-
*/
|
|
986
977
|
function emitNullScoreVerdict(stream, mode, thresholds, config, basePath, pathService) {
|
|
987
978
|
const envelope = buildVerdictEnvelope({
|
|
988
979
|
schemaVersion: "1.0",
|
|
@@ -1007,49 +998,41 @@ function emitNullScoreVerdict(stream, mode, thresholds, config, basePath, pathSe
|
|
|
1007
998
|
mutants: envelope.mutants
|
|
1008
999
|
}));
|
|
1009
1000
|
}
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
* stream is still open, which means the run never reached a verdict (the
|
|
1020
|
-
* `--dryRunOnly` early return): then a null-score `verdict` closes the
|
|
1021
|
-
* stream so the last stdout line is always a terminal event (R5).
|
|
1022
|
-
*/
|
|
1001
|
+
function offerFailureEnvelope(stream, failed, captured) {
|
|
1002
|
+
const envelope = shapeEnvelope(failed, captured);
|
|
1003
|
+
return Queue.offer(stream.queue, RunFailed.make({
|
|
1004
|
+
schemaVersion: envelope.schemaVersion,
|
|
1005
|
+
code: envelope.code,
|
|
1006
|
+
error: envelope.error,
|
|
1007
|
+
remediation: envelope.remediation
|
|
1008
|
+
}));
|
|
1009
|
+
}
|
|
1023
1010
|
function emitMachineModeOutput(stream, mode, outcome, basePath, pathService) {
|
|
1024
1011
|
return Effect.gen(function* () {
|
|
1025
1012
|
const captured = readCapturedConsole();
|
|
1026
1013
|
if (Result.isSuccess(outcome)) {
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1014
|
+
const decision = outcome.success;
|
|
1015
|
+
return yield* Match.value(decision).pipe(Match.tag("RunOk", (ok) => Effect.gen(function* () {
|
|
1016
|
+
if (ok.help) {
|
|
1017
|
+
yield* Queue.offer(stream.queue, HelpRendered.make({
|
|
1018
|
+
schemaVersion: "1.0",
|
|
1019
|
+
code: 0,
|
|
1020
|
+
help: captured
|
|
1021
|
+
}));
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
if (captured.length > 0) {
|
|
1025
|
+
yield* Queue.offer(stream.queue, HelpRendered.make({
|
|
1026
|
+
schemaVersion: "1.0",
|
|
1027
|
+
code: 0,
|
|
1028
|
+
help: captured
|
|
1029
|
+
}));
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
if (stream.isOpen()) yield* emitNullScoreVerdict(stream, mode, (yield* defaultOptions).thresholds, {}, basePath, pathService);
|
|
1033
|
+
})), Match.tag("RunParseFailed", (failed) => offerFailureEnvelope(stream, failed, captured)), Match.tag("RunSurvivorsRejected", (failed) => offerFailureEnvelope(stream, failed, captured)), Match.tag("RunConfigFailed", (failed) => offerFailureEnvelope(stream, failed, captured)), Match.tag("RunFailed", (failed) => offerFailureEnvelope(stream, failed, captured)), Match.exhaustive);
|
|
1045
1034
|
}
|
|
1046
|
-
|
|
1047
|
-
yield* Queue.offer(stream.queue, RunFailed.make({
|
|
1048
|
-
schemaVersion: envelope.schemaVersion,
|
|
1049
|
-
code: envelope.code,
|
|
1050
|
-
error: envelope.error,
|
|
1051
|
-
remediation: envelope.remediation
|
|
1052
|
-
}));
|
|
1035
|
+
return yield* offerFailureEnvelope(stream, outcome.failure, captured);
|
|
1053
1036
|
});
|
|
1054
1037
|
}
|
|
1055
1038
|
//#endregion
|
|
@@ -1088,7 +1071,7 @@ const RunEventStreamFileLive = Layer.effect(RunEventStreamPort, Effect.gen(funct
|
|
|
1088
1071
|
*
|
|
1089
1072
|
* The prior-report decoding, source hashing, mutant conversion, and admission
|
|
1090
1073
|
* pipeline for --survivors runs. Pure admission decision lives in
|
|
1091
|
-
*
|
|
1074
|
+
* admit-survivors-run.workflow.ts.
|
|
1092
1075
|
*/
|
|
1093
1076
|
const DEFAULT_SURVIVORS_PRIOR_REPORT = "reports/mutation-report.json";
|
|
1094
1077
|
/**
|
|
@@ -1184,87 +1167,57 @@ function survivorMutateSpans(survivors, basePath) {
|
|
|
1184
1167
|
return spans;
|
|
1185
1168
|
}
|
|
1186
1169
|
const hashContent = (content) => bytesToHex(sha256(utf8ToBytes(content)));
|
|
1187
|
-
const
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
}))
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1170
|
+
const survivorsAdmissionCell = (basePath) => Cell.layer({
|
|
1171
|
+
read: (cliOptions) => Effect.gen(function* () {
|
|
1172
|
+
const pathService = yield* Path.Path;
|
|
1173
|
+
const resolvedOptions = yield* resolveSurvivorsRunOptions(cliOptions, basePath);
|
|
1174
|
+
const priorReportPath = priorReportPathOf(resolvedOptions);
|
|
1175
|
+
const resolveAbsolutePath = (file) => pathService.resolve(file);
|
|
1176
|
+
const read = yield* readPriorReport(priorReportPath);
|
|
1177
|
+
const sourceContentHashes = yield* currentSourceHashesFor(priorReportFileKeys(read.raw));
|
|
1178
|
+
return {
|
|
1179
|
+
resolvedOptions,
|
|
1180
|
+
priorReportRaw: read.raw,
|
|
1181
|
+
priorReportFound: read.found,
|
|
1182
|
+
priorReportPath,
|
|
1183
|
+
sourceContentHashes,
|
|
1184
|
+
resolveAbsolutePath
|
|
1185
|
+
};
|
|
1186
|
+
}),
|
|
1187
|
+
decode: ({ resolvedOptions, priorReportRaw, priorReportFound, sourceContentHashes, resolveAbsolutePath }) => {
|
|
1188
|
+
if (!priorReportFound) return Result.succeed(AdmitSurvivorsRunCommand.make({
|
|
1189
|
+
priorReport: void 0,
|
|
1190
|
+
currentConfig: resolvedOptions,
|
|
1191
|
+
frameworkVersion: strykerVersion,
|
|
1192
|
+
sourceContentHashes,
|
|
1193
|
+
priorSourceHashes: {},
|
|
1194
|
+
priorSurvivors: []
|
|
1195
|
+
}));
|
|
1196
|
+
return Result.map(decodePriorReport(priorReportRaw), (document) => AdmitSurvivorsRunCommand.make({
|
|
1197
|
+
priorReport: PriorReportFacts.make({
|
|
1198
|
+
config: document.config ?? {},
|
|
1199
|
+
frameworkVersion: document.framework?.version
|
|
1200
|
+
}),
|
|
1201
|
+
currentConfig: resolvedOptions,
|
|
1202
|
+
frameworkVersion: strykerVersion,
|
|
1203
|
+
sourceContentHashes,
|
|
1204
|
+
priorSourceHashes: priorSourceHashes(document, hashContent),
|
|
1205
|
+
priorSurvivors: extractSurvivors(document, resolveAbsolutePath)
|
|
1206
|
+
}));
|
|
1207
|
+
},
|
|
1208
|
+
decide: admitSurvivorsRun,
|
|
1209
|
+
encode: (outcome) => outcome,
|
|
1210
|
+
write: (outcome, raw) => Result.match(outcome, {
|
|
1211
|
+
onSuccess: (admission) => Effect.succeed({
|
|
1212
|
+
admission,
|
|
1213
|
+
resolvedOptions: raw.resolvedOptions,
|
|
1214
|
+
priorReportPath: raw.priorReportPath
|
|
1225
1215
|
}),
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
}));
|
|
1232
|
-
}), Cell.decide(admitSurvivorsRun), Cell.encode((outcome) => outcome), Cell.write((outcome) => Effect.flatMap(Ref.get(runContext), (context) => {
|
|
1233
|
-
if (context === void 0) return Effect.die("the survivors admission read must run before its write");
|
|
1234
|
-
const { resolvedOptions, priorReportPath, pathService } = context;
|
|
1235
|
-
return Result.match(outcome, {
|
|
1236
|
-
onSuccess: (decision) => Match.value(decision).pipe(Match.tag("NoSurvivors", () => emitNullScoreVerdict(stream, mode, resolvedOptions.thresholds, resolvedOptions, basePath, pathService)), Match.tag("Admitted", (admitted) => {
|
|
1237
|
-
const admittedMutants = admitted.survivors.map((s) => Mutant.make(s));
|
|
1238
|
-
return runMutationTest({
|
|
1239
|
-
...resolvedOptions,
|
|
1240
|
-
survivors: admittedMutants,
|
|
1241
|
-
mutate: survivorMutateSpans(admittedMutants, basePath),
|
|
1242
|
-
survivorsPriorReport: priorReportPath,
|
|
1243
|
-
incremental: false
|
|
1244
|
-
}).pipe(Effect.orDie);
|
|
1245
|
-
}), Match.orElse(() => Effect.die("unreachable admission decision variant"))),
|
|
1246
|
-
onFailure: (rejection) => Effect.fail(rejection)
|
|
1247
|
-
});
|
|
1248
|
-
})));
|
|
1249
|
-
/**
|
|
1250
|
-
* The `--survivors` request: re-test exactly the prior report's survivor set.
|
|
1251
|
-
* The survivors flag was parsed as a boolean; the admission decides between
|
|
1252
|
-
* running the survivors and the plain pipeline. The chain's order is carried by
|
|
1253
|
-
* the description's phase types; the run's resolved context cell is created
|
|
1254
|
-
* here, beside the description it feeds.
|
|
1255
|
-
*
|
|
1256
|
-
* Two failures reach the caller, and they are not the same thing. A rejection is the
|
|
1257
|
-
* decision's own outcome — the run was inspected and refused. A `SchemaError` is a prior
|
|
1258
|
-
* report that was present and did not decode, which stops the chain before any decision
|
|
1259
|
-
* is made; it is in this signature because the phase types put it there, not because the
|
|
1260
|
-
* admission chose it.
|
|
1261
|
-
*/
|
|
1262
|
-
function runSurvivorsAdmission(runMutationTest, stream, mode, cliOptions, basePath) {
|
|
1263
|
-
return Effect.gen(function* () {
|
|
1264
|
-
const services = yield* Effect.context();
|
|
1265
|
-
const admissionContext = yield* Ref.make(void 0);
|
|
1266
|
-
return yield* Cell.apply(survivorsAdmissionDescription(runMutationTest, stream, mode, admissionContext, basePath, services), cliOptions);
|
|
1267
|
-
});
|
|
1216
|
+
onFailure: Effect.fail
|
|
1217
|
+
})
|
|
1218
|
+
});
|
|
1219
|
+
function runSurvivorsAdmission(cliOptions, basePath) {
|
|
1220
|
+
return Cell.run(survivorsAdmissionCell(basePath), cliOptions);
|
|
1268
1221
|
}
|
|
1269
1222
|
function resolveSurvivorsRunOptions(cliOptions, basePath) {
|
|
1270
1223
|
return readConfig(cliOptions, basePath);
|
|
@@ -1513,37 +1466,47 @@ function describeCommandNode(node) {
|
|
|
1513
1466
|
};
|
|
1514
1467
|
}
|
|
1515
1468
|
function readCoreEntries() {
|
|
1516
|
-
|
|
1517
|
-
const
|
|
1518
|
-
const
|
|
1519
|
-
const
|
|
1469
|
+
return Effect.gen(function* () {
|
|
1470
|
+
const path = yield* Path.Path;
|
|
1471
|
+
const fs = yield* FileSystem.FileSystem;
|
|
1472
|
+
const resolved = import.meta.resolve("@systemfsoftware/stryker-js-engine/package.json");
|
|
1473
|
+
const manifestPath = yield* path.fromFileUrl(new URL(resolved));
|
|
1474
|
+
const raw = yield* fs.readFileString(manifestPath);
|
|
1475
|
+
let parsed;
|
|
1476
|
+
try {
|
|
1477
|
+
parsed = JSON.parse(raw);
|
|
1478
|
+
} catch {
|
|
1479
|
+
return [];
|
|
1480
|
+
}
|
|
1520
1481
|
const ExportsSchema = S.Struct({ exports: S.Record(S.String, S.Unknown) });
|
|
1521
1482
|
const decoded = S.decodeUnknownResult(ExportsSchema)(parsed);
|
|
1522
1483
|
if (Result.isSuccess(decoded)) return Object.keys(decoded.success.exports).filter((key) => key !== "./package.json");
|
|
1523
1484
|
return [];
|
|
1524
|
-
}
|
|
1525
|
-
return [];
|
|
1526
|
-
}
|
|
1485
|
+
}).pipe(Effect.catchCause(() => {
|
|
1486
|
+
return Effect.succeed([]);
|
|
1487
|
+
}));
|
|
1527
1488
|
}
|
|
1528
1489
|
function buildLLMSManifest(command, version) {
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1490
|
+
return Effect.gen(function* () {
|
|
1491
|
+
const root = describeCommandNode(command) ?? {
|
|
1492
|
+
name: "",
|
|
1493
|
+
description: "",
|
|
1494
|
+
options: [],
|
|
1495
|
+
args: [],
|
|
1496
|
+
subcommands: []
|
|
1497
|
+
};
|
|
1498
|
+
const entries = yield* readCoreEntries();
|
|
1499
|
+
return {
|
|
1500
|
+
schemaVersion: "1.0",
|
|
1501
|
+
tool: root.name,
|
|
1502
|
+
version,
|
|
1503
|
+
commands: [root],
|
|
1504
|
+
entries
|
|
1505
|
+
};
|
|
1506
|
+
});
|
|
1543
1507
|
}
|
|
1544
|
-
/** The manifest as one JSON document, ready for stdout — the U4 convention. */
|
|
1545
1508
|
function emitLLMSManifest(command, version) {
|
|
1546
|
-
return
|
|
1509
|
+
return Effect.map(buildLLMSManifest(command, version), (manifest) => JSON.stringify(manifest));
|
|
1547
1510
|
}
|
|
1548
1511
|
function createSplitter(separator) {
|
|
1549
1512
|
return (value) => value.split(separator).filter(Boolean);
|
|
@@ -1716,39 +1679,26 @@ function makeStrykerCommand(requestRef) {
|
|
|
1716
1679
|
if (Option.isSome(config["configFile"])) options["configFile"] = config["configFile"].value;
|
|
1717
1680
|
return options;
|
|
1718
1681
|
}
|
|
1719
|
-
const strykerCommand = Command.make("stryker", rootConfig, (config) => {
|
|
1682
|
+
const strykerCommand = Command.make("stryker", rootConfig, (config) => Effect.gen(function* () {
|
|
1720
1683
|
if (config.llms === true) {
|
|
1721
1684
|
const document = {
|
|
1722
1685
|
_tag: "manifest",
|
|
1723
1686
|
schemaVersion: "1.0",
|
|
1724
1687
|
code: 0,
|
|
1725
|
-
manifest: emitLLMSManifest(strykerCommand, strykerVersion)
|
|
1688
|
+
manifest: yield* emitLLMSManifest(strykerCommand, strykerVersion)
|
|
1726
1689
|
};
|
|
1727
|
-
return Ref.set(requestRef, Option.some({
|
|
1690
|
+
return yield* Ref.set(requestRef, Option.some({
|
|
1728
1691
|
_tag: "llms",
|
|
1729
1692
|
document
|
|
1730
1693
|
}));
|
|
1731
1694
|
}
|
|
1732
|
-
return Effect.failSync(() => CliError.ShowHelp.make({
|
|
1695
|
+
return yield* Effect.failSync(() => CliError.ShowHelp.make({
|
|
1733
1696
|
commandPath: ["stryker"],
|
|
1734
1697
|
errors: []
|
|
1735
1698
|
}));
|
|
1736
|
-
}).pipe(Command.withSubcommands([runCommand]));
|
|
1699
|
+
})).pipe(Command.withSubcommands([runCommand]));
|
|
1737
1700
|
return strykerCommand;
|
|
1738
1701
|
}
|
|
1739
|
-
/**
|
|
1740
|
-
* The CLI parses only text/number/choice options, so the framework's platform
|
|
1741
|
-
* services are never read at runtime; the v4 runner still demands them in its
|
|
1742
|
-
* environment. The bootstrap provides `Path.layer` (the universal
|
|
1743
|
-
* implementation in `effect/Path`), an *empty* file system (`layerNoop`:
|
|
1744
|
-
* every operation reports not-found), and a process-stdio `Terminal` whose
|
|
1745
|
-
* interactive input primitives fail loudly — the run-only surface (R14) has
|
|
1746
|
-
* no prompts. `@effect/platform-node` is a declared dependency of this
|
|
1747
|
-
* package (it provides the `NodeRuntime` the bin runs through), but no
|
|
1748
|
-
* platform-node service is wired into the command environment: the parser
|
|
1749
|
-
* never reads a real file system at run time, so the noop layers are
|
|
1750
|
-
* sufficient.
|
|
1751
|
-
*/
|
|
1752
1702
|
const terminalLayer = Layer.succeed(Terminal.Terminal, Terminal.make({
|
|
1753
1703
|
columns: Effect.sync(() => process.stdout.columns),
|
|
1754
1704
|
rows: Effect.sync(() => process.stdout.rows),
|
|
@@ -1767,7 +1717,7 @@ const cliLayer = Layer.mergeAll(CliConfig.layer({ builtIns: [
|
|
|
1767
1717
|
GlobalFlag.Wizard,
|
|
1768
1718
|
GlobalFlag.Completions,
|
|
1769
1719
|
GlobalFlag.LogLevel
|
|
1770
|
-
] }), Path.layer,
|
|
1720
|
+
] }), Path.layer, NodeFileSystem.layer, terminalLayer, NodeStdio.layer, NodeChildProcessSpawner.layer.pipe(Layer.provideMerge(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer))));
|
|
1771
1721
|
function strykerCliEffect(argv, runMutationTest, detectMode, createRunEventStream, lastSignal) {
|
|
1772
1722
|
return Effect.gen(function* () {
|
|
1773
1723
|
const mode = yield* detectMode;
|
|
@@ -1790,14 +1740,15 @@ function strykerCliEffect(argv, runMutationTest, detectMode, createRunEventStrea
|
|
|
1790
1740
|
return result.success;
|
|
1791
1741
|
}).pipe(Effect.orElseSucceed(() => 2));
|
|
1792
1742
|
}
|
|
1793
|
-
const
|
|
1743
|
+
const hostRunLayer = (hostOptions, queue) => makeRunLayer(hostOptions, queue).pipe(Layer.provideMerge(nodePlatformLayer));
|
|
1744
|
+
const defaultRunMutationTest = (hostOptions, queue) => (...args) => Effect.scoped(runMutationTest(...args)).pipe(Effect.provide(hostRunLayer(hostOptions, queue)), Effect.provideService(RunEvents, queue));
|
|
1794
1745
|
function hostOptionsOf(mode, stream) {
|
|
1795
1746
|
return {
|
|
1796
1747
|
runId: stream.runId,
|
|
1797
1748
|
resolvedMode: mode,
|
|
1798
1749
|
runStartedAt: stream.startedAt,
|
|
1799
|
-
basePath:
|
|
1800
|
-
reporterPluginModules: [import.meta.resolve("@systemfsoftware/stryker-js-html-reporter"), import.meta.resolve("@systemfsoftware/stryker-js-
|
|
1750
|
+
basePath: process.cwd(),
|
|
1751
|
+
reporterPluginModules: [import.meta.resolve("@systemfsoftware/stryker-js-html-reporter"), import.meta.resolve("@systemfsoftware/stryker-js-engine/builtin-reporters")],
|
|
1801
1752
|
allowConsoleColors: isColorEnabled(mode, process.env["NO_COLOR"])
|
|
1802
1753
|
};
|
|
1803
1754
|
}
|
|
@@ -1814,7 +1765,20 @@ const runStrykerCli = (input, createRunEventStream) => Effect.gen(function* () {
|
|
|
1814
1765
|
if (currentFiber !== null) currentFiber.interruptUnsafe(currentFiber.id);
|
|
1815
1766
|
};
|
|
1816
1767
|
const dispatch = (request) => Match.value(request).pipe(Match.tag("run", (runRequest) => (() => {
|
|
1817
|
-
if (runRequest.survivors) return
|
|
1768
|
+
if (runRequest.survivors) return Effect.gen(function* () {
|
|
1769
|
+
const { admission, resolvedOptions, priorReportPath } = yield* runSurvivorsAdmission(runRequest.options, basePath).pipe(Effect.provide(hostRunLayer(hostOptions)));
|
|
1770
|
+
return yield* Match.value(admission).pipe(Match.tag("NoSurvivors", () => emitNullScoreVerdict(stream, input.mode, resolvedOptions.thresholds, resolvedOptions, basePath, pathService)), Match.tag("Admitted", (admitted) => {
|
|
1771
|
+
const admittedMutants = admitted.survivors.map((s) => Mutant.make(s));
|
|
1772
|
+
const restricted = {
|
|
1773
|
+
...resolvedOptions,
|
|
1774
|
+
survivors: admittedMutants,
|
|
1775
|
+
mutate: survivorMutateSpans(admittedMutants, basePath),
|
|
1776
|
+
survivorsPriorReport: priorReportPath,
|
|
1777
|
+
incremental: false
|
|
1778
|
+
};
|
|
1779
|
+
return runMutationTestImpl(restricted).pipe(Effect.orDie);
|
|
1780
|
+
}), Match.exhaustive);
|
|
1781
|
+
});
|
|
1818
1782
|
return runMutationTestImpl(runRequest.options).pipe(Effect.orDie);
|
|
1819
1783
|
})()), Match.tag("llms", (llmsRequest) => Effect.gen(function* () {
|
|
1820
1784
|
stream.ensureOpen({
|
|
@@ -1868,24 +1832,13 @@ const EXIT_CODE_RUN_NEVER_REACHED_ITS_FINALIZER = 1;
|
|
|
1868
1832
|
process.title = "stryker";
|
|
1869
1833
|
const lastSignal = observeTerminatingSignal();
|
|
1870
1834
|
function isSupportedNodeVersion(version) {
|
|
1871
|
-
|
|
1872
|
-
if (version.startsWith("v")) withoutV = version.slice(1);
|
|
1873
|
-
const dashBaseRaw = withoutV.split("-")[0];
|
|
1874
|
-
let dashBase = withoutV;
|
|
1875
|
-
if (dashBaseRaw !== void 0) dashBase = dashBaseRaw;
|
|
1876
|
-
const baseRaw = dashBase.split("+")[0];
|
|
1877
|
-
let base = dashBase;
|
|
1878
|
-
if (baseRaw !== void 0) base = baseRaw;
|
|
1879
|
-
const parts = base.split(".").map((p) => Number.parseInt(p, 10));
|
|
1880
|
-
const major = parts[0] ?? 0;
|
|
1881
|
-
const minor = parts[1] ?? 0;
|
|
1882
|
-
const patch = parts[2] ?? 0;
|
|
1835
|
+
const [major = 0, minor = 0, patch = 0] = (version.replace(/^v/, "").split(/[-+]/)[0] ?? "").split(".").map((p) => Number.parseInt(p, 10));
|
|
1883
1836
|
if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch)) return false;
|
|
1884
1837
|
if (major !== 20) return major > 20;
|
|
1885
1838
|
if (minor !== 0) return minor > 0;
|
|
1886
1839
|
return patch >= 0;
|
|
1887
1840
|
}
|
|
1888
|
-
if (!isSupportedNodeVersion(process.version)) throw new Error(`Node.js version ${process.version} detected. StrykerJS requires version to match ${
|
|
1841
|
+
if (!isSupportedNodeVersion(process.version)) throw new Error(`Node.js version ${process.version} detected. StrykerJS requires version to match ${engines.node}. Please update your Node.js version or visit https://nodejs.org/ for additional instructions`);
|
|
1889
1842
|
const program = Effect.gen(function* () {
|
|
1890
1843
|
const outputMode = yield* OutputModeProbe;
|
|
1891
1844
|
const runEvents = yield* RunEventStreamPort;
|