@systemfsoftware/stryker-js-cli 4.0.3 → 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 +22 -0
- package/README.md +2 -2
- package/dist/main.mjs +315 -360
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @systemfsoftware/stryker-js-cli
|
|
2
2
|
|
|
3
|
+
## 5.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- The Node host package `@systemfsoftware/stryker-js-platform-node` is gone: the engine (`@systemfsoftware/stryker-js-engine`) is the host-neutral mutation run, and the `stryker` CLI owns the Node composition roots and worker entries. Depend on the engine for the run and the CLI for process entries; install the new names.
|
|
8
|
+
|
|
9
|
+
- RunOutcome now places the genuine verdicts (RunParseFailed, RunSurvivorsRejected, RunConfigFailed, RunFailed) on the decision channel as a branded tagged union; RunInterrupted remains a failure. Output.workflow resolves a branded HumanOutput|MachineOutput union instead of a plain record
|
|
10
|
+
|
|
11
|
+
- Disposing a worker now terminates the whole process group and escalates. The
|
|
12
|
+
runner signals the group so a worker's own child processes go with it, waits for
|
|
13
|
+
the exit, and sends `SIGKILL` if the worker is still alive two seconds later.
|
|
14
|
+
Previously a single `SIGTERM` went to one process id and the runner moved on
|
|
15
|
+
after a fixed wait, so a worker that installed a `SIGTERM` handler — or that had
|
|
16
|
+
spawned children of its own — outlived the run.
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- Updated dependencies:
|
|
21
|
+
- @systemfsoftware/effect-cell-types@6.0.0
|
|
22
|
+
- @systemfsoftware/stryker-js@1.0.0
|
|
23
|
+
- @systemfsoftware/stryker-js-html-reporter@1.0.0
|
|
24
|
+
|
|
3
25
|
## 4.0.3
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -84,7 +84,7 @@ The highest pending class wins; a terminating signal outranks all of them.
|
|
|
84
84
|
|
|
85
85
|
## Related
|
|
86
86
|
|
|
87
|
-
The mutation engine is [`@systemfsoftware/stryker-js-
|
|
87
|
+
The mutation engine is [`@systemfsoftware/stryker-js-engine`][engine].
|
|
88
88
|
This package is the terminal-facing half and reaches it through an injected
|
|
89
89
|
run-event sink;
|
|
90
90
|
it ships a command and exposes no importable API.
|
|
@@ -100,5 +100,5 @@ Licensed under [Apache 2.0][license-url].
|
|
|
100
100
|
[license-badge]: https://img.shields.io/badge/license-Apache_2.0-blue?style=flat-square
|
|
101
101
|
[license-url]: https://github.com/systemfsoftware/systemfsoftware/blob/main/LICENSE
|
|
102
102
|
[repo]: https://github.com/systemfsoftware/systemfsoftware
|
|
103
|
-
[engine]: https://github.com/systemfsoftware/systemfsoftware/tree/main/packages/testing/mutation/stryker-js/
|
|
103
|
+
[engine]: https://github.com/systemfsoftware/systemfsoftware/tree/main/packages/testing/mutation/stryker-js/engine
|
|
104
104
|
[docs]: https://stryker-mutator.io/docs/stryker-js/configuration/
|
package/dist/main.mjs
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import * as
|
|
2
|
+
import { n as nodePlatformLayer } from "./node-D-ynY_kk.mjs";
|
|
3
|
+
import * as NodeFileSystem from "@effect/platform-node-shared/NodeFileSystem";
|
|
4
|
+
import * as NodePath from "@effect/platform-node-shared/NodePath";
|
|
4
5
|
import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
|
|
5
6
|
import * as NodeStdio from "@effect/platform-node/NodeStdio";
|
|
6
|
-
import { ConfigFileUnreadableError, buildVerdictEnvelope, defaultOptions, generateRunId, makeRunLayer, nodeModuleLayer, readConfig, runMutationTest, strykerEngines, strykerVersion, toRelativeNormalizedFileName } from "@systemfsoftware/stryker-js-platform-node";
|
|
7
7
|
import * as Effect from "effect/Effect";
|
|
8
8
|
import * as Exit from "effect/Exit";
|
|
9
9
|
import * as Layer from "effect/Layer";
|
|
10
10
|
import * as Logger from "effect/Logger";
|
|
11
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";
|
|
12
14
|
import { Heartbeat, HelpRendered, ManifestRendered, RunEvents, RunFailed, RunStarted, VerdictReached } from "@systemfsoftware/stryker-js/Run";
|
|
13
15
|
import { RENDERED_OPTION_DEFAULTS } from "@systemfsoftware/stryker-js/Schema";
|
|
14
16
|
import * as Cause from "effect/Cause";
|
|
@@ -29,94 +31,21 @@ import * as CliError from "effect/unstable/cli/CliError";
|
|
|
29
31
|
import * as Command from "effect/unstable/cli/Command";
|
|
30
32
|
import * as Flag from "effect/unstable/cli/Flag";
|
|
31
33
|
import * as GlobalFlag from "effect/unstable/cli/GlobalFlag";
|
|
34
|
+
import { Cell, Workflow } from "@systemfsoftware/effect-cell-types";
|
|
32
35
|
import { ExitClass, highestExitClass } from "@systemfsoftware/stryker-js/ExitClass";
|
|
33
|
-
import { Mutant, causeText } from "@systemfsoftware/stryker-js/Mutant";
|
|
34
36
|
import * as Clock from "effect/Clock";
|
|
35
37
|
import * as Formatter from "effect/Formatter";
|
|
36
38
|
import * as Predicate from "effect/Predicate";
|
|
37
|
-
import { Cell, Workflow } from "@systemfsoftware/effect-cell-types";
|
|
38
39
|
import * as Context from "effect/Context";
|
|
39
|
-
import { pipe } from "effect/Function";
|
|
40
40
|
import * as Stdio from "effect/Stdio";
|
|
41
41
|
import * as Stream from "effect/Stream";
|
|
42
|
-
import
|
|
42
|
+
import "@systemfsoftware/stryker-js/Module";
|
|
43
43
|
import { sha256 } from "@noble/hashes/sha256";
|
|
44
44
|
import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils";
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
var RunOutcomeCommand = class extends S.TaggedClass()("RunOutcomeCommand", {
|
|
48
|
-
succeeded: S.Boolean,
|
|
49
|
-
signal: S.optional(S.Finite),
|
|
50
|
-
interrupted: S.Boolean,
|
|
51
|
-
helpErrorCount: S.optional(S.Finite),
|
|
52
|
-
cliError: S.Boolean,
|
|
53
|
-
unrecognized: S.optional(S.String),
|
|
54
|
-
survivorsReason: S.optional(S.Literals(["no-report", "mismatch"])),
|
|
55
|
-
survivorsDiagnostic: S.optional(S.String),
|
|
56
|
-
schemaError: S.Boolean,
|
|
57
|
-
successExitClass: S.optional(ExitClass),
|
|
58
|
-
highestExitClass: S.optional(ExitClass),
|
|
59
|
-
configDetail: S.optional(S.String),
|
|
60
|
-
diagnostic: S.optional(S.String)
|
|
61
|
-
}) {};
|
|
62
|
-
const CONFIG_CODE$1 = 2;
|
|
63
|
-
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);
|
|
64
|
-
var RunOk = class extends S.TaggedClass()("RunOk", { help: S.Boolean }) {};
|
|
65
|
-
var RunInterrupted = class extends S.TaggedError()("RunInterrupted", { code: S.Finite }) {};
|
|
66
|
-
var RunParseFailed = class extends S.TaggedError()("RunParseFailed", { unrecognized: S.optional(S.String) }) {};
|
|
67
|
-
var RunSurvivorsRejected = class extends S.TaggedError()("RunSurvivorsRejected", {
|
|
68
|
-
reason: S.Literals(["no-report", "mismatch"]),
|
|
69
|
-
diagnostic: S.optional(S.String)
|
|
70
|
-
}) {};
|
|
71
|
-
var RunConfigFailed = class extends S.TaggedError()("RunConfigFailed", { detail: S.optional(S.String) }) {};
|
|
72
|
-
var RunFailed$1 = class extends S.TaggedError()("RunFailed", {
|
|
73
|
-
code: S.Finite,
|
|
74
|
-
diagnostic: S.optional(S.String)
|
|
75
|
-
}) {};
|
|
76
|
-
function classify(command) {
|
|
77
|
-
if (command.signal !== void 0) return RunInterrupted.make({ code: 128 + command.signal });
|
|
78
|
-
if (command.succeeded) {
|
|
79
|
-
if (command.successExitClass !== void 0) return RunFailed$1.make({
|
|
80
|
-
code: classCode(command.successExitClass),
|
|
81
|
-
diagnostic: command.diagnostic
|
|
82
|
-
});
|
|
83
|
-
return RunOk.make({ help: false });
|
|
84
|
-
}
|
|
85
|
-
if (command.interrupted) return RunInterrupted.make({ code: 1 });
|
|
86
|
-
if (command.helpErrorCount !== void 0) {
|
|
87
|
-
if (command.helpErrorCount > 0) return RunParseFailed.make({ unrecognized: command.unrecognized });
|
|
88
|
-
return RunOk.make({ help: true });
|
|
89
|
-
}
|
|
90
|
-
if (command.cliError) return RunParseFailed.make({ unrecognized: command.unrecognized });
|
|
91
|
-
if (command.survivorsReason !== void 0) return RunSurvivorsRejected.make({
|
|
92
|
-
reason: command.survivorsReason,
|
|
93
|
-
diagnostic: command.survivorsDiagnostic
|
|
94
|
-
});
|
|
95
|
-
if (command.schemaError) return RunConfigFailed.make({ detail: command.configDetail });
|
|
96
|
-
if (command.highestExitClass !== void 0) {
|
|
97
|
-
if (command.highestExitClass === "ConfigError") return RunConfigFailed.make({ detail: command.configDetail });
|
|
98
|
-
return RunFailed$1.make({
|
|
99
|
-
code: classCode(command.highestExitClass),
|
|
100
|
-
diagnostic: command.diagnostic
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
return RunFailed$1.make({
|
|
104
|
-
code: 1,
|
|
105
|
-
diagnostic: command.diagnostic
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
function succeedRun(ok) {
|
|
109
|
-
return Result.succeed(ok);
|
|
110
|
-
}
|
|
111
|
-
function failRun(error) {
|
|
112
|
-
return Result.fail(error);
|
|
113
|
-
}
|
|
114
|
-
function runOutcomeDecision(command) {
|
|
115
|
-
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);
|
|
116
|
-
}
|
|
117
|
-
const runOutcomeWorkflow = Workflow.make(RunOutcomeCommand, runOutcomeDecision);
|
|
45
|
+
//#region package.json
|
|
46
|
+
var engines = { "node": ">=20.0.0" };
|
|
118
47
|
//#endregion
|
|
119
|
-
//#region src/
|
|
48
|
+
//#region src/admit-survivors-run.workflow.ts
|
|
120
49
|
/**
|
|
121
50
|
* The mutant shape the admission carries, named once because both the decision's
|
|
122
51
|
* `Admitted` payload and the command's precomputed survivor list are the same shape.
|
|
@@ -316,20 +245,91 @@ function admissionDecision(command) {
|
|
|
316
245
|
}
|
|
317
246
|
const admitSurvivorsRun = Workflow.make(AdmitSurvivorsRunCommand, admissionDecision);
|
|
318
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
|
|
319
325
|
//#region src/Envelope.ts
|
|
320
|
-
/**
|
|
321
|
-
* Envelope — the failure envelope and console capture leaf.
|
|
322
|
-
*
|
|
323
|
-
* Extracted from Cli.ts to break the import cycle Cli <-> Output.
|
|
324
|
-
* Both Cli and Output import from this leaf, so neither depends on the other
|
|
325
|
-
* for these values. This file imports only from external packages and from
|
|
326
|
-
* StreamVersion (leaf) and Survivors.workflow (leaf), never from Cli or Output
|
|
327
|
-
* themselves.
|
|
328
|
-
*/
|
|
329
326
|
const CONFIG_CODE = 2;
|
|
330
327
|
function runOutcomeCode(result) {
|
|
331
|
-
|
|
332
|
-
|
|
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);
|
|
333
333
|
}
|
|
334
334
|
function isExitClass(value) {
|
|
335
335
|
return S.is(ExitClass)(value);
|
|
@@ -576,7 +576,7 @@ function shapeEnvelope(error, captured) {
|
|
|
576
576
|
};
|
|
577
577
|
}
|
|
578
578
|
function classifyRunOutcome(exit, signal, argv) {
|
|
579
|
-
return
|
|
579
|
+
return classifyRunOutcome$1(gatherRunOutcome(exit, signal, argv));
|
|
580
580
|
}
|
|
581
581
|
const capturedConsoleChunks = [];
|
|
582
582
|
const countByLabel = /* @__PURE__ */ new Map();
|
|
@@ -683,12 +683,8 @@ function resetCapturedConsole() {
|
|
|
683
683
|
timeByLabel.clear();
|
|
684
684
|
}
|
|
685
685
|
//#endregion
|
|
686
|
-
//#region src/
|
|
686
|
+
//#region src/resolve-output-mode.workflow.ts
|
|
687
687
|
const TOOL_VARIABLES$1 = ["CLAUDECODE", "CODEX_SANDBOX"];
|
|
688
|
-
/**
|
|
689
|
-
* The command of the output-mode workflow: a schema class, because `Workflow.make`
|
|
690
|
-
* derives the command type from it and pins the error channel at the construction site.
|
|
691
|
-
*/
|
|
692
688
|
var ResolveModeCommand = class extends S.TaggedClass()("ResolveModeCommand", {
|
|
693
689
|
stdoutIsTTY: S.Boolean,
|
|
694
690
|
text: S.optional(S.Boolean),
|
|
@@ -697,17 +693,33 @@ var ResolveModeCommand = class extends S.TaggedClass()("ResolveModeCommand", {
|
|
|
697
693
|
agent: S.optional(S.String),
|
|
698
694
|
toolVars: S.optional(S.Record(S.String, S.String))
|
|
699
695
|
}) {};
|
|
700
|
-
|
|
701
|
-
* The conflict error for mutually exclusive format flags. Defined locally so the
|
|
702
|
-
* workflow decision remains pure — the sealed effect surface does not include
|
|
703
|
-
* `effect/unstable/cli/CliError`, so the decision returns this local error and
|
|
704
|
-
* the shell maps it to `CliError.InvalidValue` at the boundary.
|
|
705
|
-
*/
|
|
696
|
+
const ResolveModeTypeId = Symbol.for("@systemfsoftware/stryker-js-cli/ResolveMode");
|
|
706
697
|
var ModeConflictError = class extends S.TaggedError()("ModeConflictError", {
|
|
707
698
|
option: S.String,
|
|
708
699
|
value: S.String,
|
|
709
700
|
expected: S.String
|
|
710
|
-
}) {
|
|
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
|
+
};
|
|
711
723
|
const CONFLICT_EXPECTED = "the \"--format text\" and \"--json\" flags are mutually exclusive — use one or the other";
|
|
712
724
|
function r4(command) {
|
|
713
725
|
if (command.text === true && command.json === true) return Result.fail(ModeConflictError.make({
|
|
@@ -715,71 +727,51 @@ function r4(command) {
|
|
|
715
727
|
value: "text",
|
|
716
728
|
expected: CONFLICT_EXPECTED
|
|
717
729
|
}));
|
|
718
|
-
if (command.text === true) return Result.succeed({
|
|
719
|
-
mode: "human",
|
|
730
|
+
if (command.text === true) return Result.succeed(HumanOutput.make({
|
|
720
731
|
signal: "flag",
|
|
721
732
|
stdoutIsTTY: command.stdoutIsTTY
|
|
722
|
-
});
|
|
723
|
-
if (command.json === true) return Result.succeed({
|
|
724
|
-
mode: "machine",
|
|
733
|
+
}));
|
|
734
|
+
if (command.json === true) return Result.succeed(MachineOutput.make({
|
|
725
735
|
signal: "flag",
|
|
726
736
|
stdoutIsTTY: command.stdoutIsTTY
|
|
727
|
-
});
|
|
737
|
+
}));
|
|
728
738
|
if (command.envMode !== void 0 && command.envMode.length > 0) {
|
|
729
|
-
if (command.envMode === "machine") return Result.succeed({
|
|
730
|
-
mode: "machine",
|
|
739
|
+
if (command.envMode === "machine") return Result.succeed(MachineOutput.make({
|
|
731
740
|
signal: "env",
|
|
732
741
|
stdoutIsTTY: command.stdoutIsTTY
|
|
733
|
-
});
|
|
734
|
-
return Result.succeed({
|
|
735
|
-
mode: "human",
|
|
742
|
+
}));
|
|
743
|
+
return Result.succeed(HumanOutput.make({
|
|
736
744
|
signal: "env",
|
|
737
745
|
stdoutIsTTY: command.stdoutIsTTY
|
|
738
|
-
});
|
|
746
|
+
}));
|
|
739
747
|
}
|
|
740
|
-
if (!command.stdoutIsTTY) return Result.succeed({
|
|
741
|
-
mode: "machine",
|
|
748
|
+
if (!command.stdoutIsTTY) return Result.succeed(MachineOutput.make({
|
|
742
749
|
signal: "tty",
|
|
743
750
|
stdoutIsTTY: false
|
|
744
|
-
});
|
|
745
|
-
if (command.agent !== void 0 && command.agent.length > 0) return Result.succeed({
|
|
746
|
-
mode: "machine",
|
|
751
|
+
}));
|
|
752
|
+
if (command.agent !== void 0 && command.agent.length > 0) return Result.succeed(MachineOutput.make({
|
|
747
753
|
signal: "agent",
|
|
748
754
|
stdoutIsTTY: true
|
|
749
|
-
});
|
|
755
|
+
}));
|
|
750
756
|
const toolVars = command.toolVars ?? {};
|
|
751
757
|
for (const variable of TOOL_VARIABLES$1) {
|
|
752
758
|
const value = toolVars[variable];
|
|
753
|
-
if (typeof value === "string" && value.length > 0) return Result.succeed({
|
|
754
|
-
mode: "machine",
|
|
759
|
+
if (typeof value === "string" && value.length > 0) return Result.succeed(MachineOutput.make({
|
|
755
760
|
signal: "tool",
|
|
756
761
|
stdoutIsTTY: true
|
|
757
|
-
});
|
|
762
|
+
}));
|
|
758
763
|
}
|
|
759
|
-
return Result.succeed({
|
|
760
|
-
mode: "human",
|
|
764
|
+
return Result.succeed(HumanOutput.make({
|
|
761
765
|
signal: "tty",
|
|
762
766
|
stdoutIsTTY: true
|
|
763
|
-
});
|
|
767
|
+
}));
|
|
764
768
|
}
|
|
765
769
|
function modeDecision(command) {
|
|
766
770
|
return r4(command);
|
|
767
771
|
}
|
|
768
|
-
const
|
|
772
|
+
const resolveOutputMode = Workflow.make(ResolveModeCommand, modeDecision);
|
|
769
773
|
//#endregion
|
|
770
774
|
//#region src/Output.ts
|
|
771
|
-
/**
|
|
772
|
-
* Output — the machine/human output capability.
|
|
773
|
-
*
|
|
774
|
-
* The NDJSON run-event stream, wire protocol constants, mode resolution probes,
|
|
775
|
-
* and machine-mode terminal output. Pure mode resolution lives in
|
|
776
|
-
* Output.workflow.ts.
|
|
777
|
-
*/
|
|
778
|
-
/**
|
|
779
|
-
* The heartbeat interval (R19), matching Terraform's `apply_progress`
|
|
780
|
-
* cadence: long enough that a slow phase is not noisy, short enough that a
|
|
781
|
-
* consumer can tell "slow" from "hung" without waiting for a mutant event.
|
|
782
|
-
*/
|
|
783
775
|
const TICK_INTERVAL_MS = 1e4;
|
|
784
776
|
var RunEventStreamPortTag = class extends Context.Service()("@systemfsoftware/stryker-js-cli/Output/RunEventStreamPortTag") {};
|
|
785
777
|
const RunEventStreamPort = RunEventStreamPortTag;
|
|
@@ -891,12 +883,15 @@ const makeRunEventStream = (stdio, resolved, drainFramed = drainOf.bind(null, st
|
|
|
891
883
|
});
|
|
892
884
|
Layer.effect(RunEventStreamPort, Effect.map(Stdio.Stdio, (stdio) => RunEventStreamPort.of({ createRunEventStream: (resolved) => makeRunEventStream(stdio, resolved) })));
|
|
893
885
|
const TOOL_VARIABLES = ["CLAUDECODE", "CODEX_SANDBOX"];
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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);
|
|
900
895
|
function isColorEnabled(resolved, noColor) {
|
|
901
896
|
if (resolved.mode !== "human") return false;
|
|
902
897
|
if (noColor === void 0) return true;
|
|
@@ -905,83 +900,80 @@ function isColorEnabled(resolved, noColor) {
|
|
|
905
900
|
}
|
|
906
901
|
var OutputModeProbeTag = class extends Context.Service()("@systemfsoftware/stryker-js-cli/Output/OutputModeProbeTag") {};
|
|
907
902
|
const OutputModeProbe = OutputModeProbeTag;
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
const
|
|
912
|
-
|
|
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
|
-
|
|
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({
|
|
970
971
|
option: error.option,
|
|
971
972
|
value: error.value,
|
|
972
973
|
expected: error.expected,
|
|
973
974
|
kind: "flag"
|
|
974
975
|
})));
|
|
975
976
|
const OutputModeProbeLive = Layer.succeed(OutputModeProbe, OutputModeProbe.of({ detectMode: detectModeWithProbe({}) }));
|
|
976
|
-
/**
|
|
977
|
-
* Machine mode emits the U4 verdict envelope for a run that produced no
|
|
978
|
-
* mutants and no report file: a `--survivors` run with zero survivors (AE3)
|
|
979
|
-
* or a successful `--dryRunOnly` run that ended before the mutation
|
|
980
|
-
* pipeline. The envelope carries a null score and an empty mutant list and is
|
|
981
|
-
* written as the terminal `verdict` line of the stdout stream (U6), carrying
|
|
982
|
-
* the run id the stream header already opened with (KTD11 — never a fresh
|
|
983
|
-
* id). Human mode prints nothing (the sink drops in human mode).
|
|
984
|
-
*/
|
|
985
977
|
function emitNullScoreVerdict(stream, mode, thresholds, config, basePath, pathService) {
|
|
986
978
|
const envelope = buildVerdictEnvelope({
|
|
987
979
|
schemaVersion: "1.0",
|
|
@@ -1006,49 +998,41 @@ function emitNullScoreVerdict(stream, mode, thresholds, config, basePath, pathSe
|
|
|
1006
998
|
mutants: envelope.mutants
|
|
1007
999
|
}));
|
|
1008
1000
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
* stream is still open, which means the run never reached a verdict (the
|
|
1019
|
-
* `--dryRunOnly` early return): then a null-score `verdict` closes the
|
|
1020
|
-
* stream so the last stdout line is always a terminal event (R5).
|
|
1021
|
-
*/
|
|
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
|
+
}
|
|
1022
1010
|
function emitMachineModeOutput(stream, mode, outcome, basePath, pathService) {
|
|
1023
1011
|
return Effect.gen(function* () {
|
|
1024
1012
|
const captured = readCapturedConsole();
|
|
1025
1013
|
if (Result.isSuccess(outcome)) {
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
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);
|
|
1044
1034
|
}
|
|
1045
|
-
|
|
1046
|
-
yield* Queue.offer(stream.queue, RunFailed.make({
|
|
1047
|
-
schemaVersion: envelope.schemaVersion,
|
|
1048
|
-
code: envelope.code,
|
|
1049
|
-
error: envelope.error,
|
|
1050
|
-
remediation: envelope.remediation
|
|
1051
|
-
}));
|
|
1035
|
+
return yield* offerFailureEnvelope(stream, outcome.failure, captured);
|
|
1052
1036
|
});
|
|
1053
1037
|
}
|
|
1054
1038
|
//#endregion
|
|
@@ -1087,7 +1071,7 @@ const RunEventStreamFileLive = Layer.effect(RunEventStreamPort, Effect.gen(funct
|
|
|
1087
1071
|
*
|
|
1088
1072
|
* The prior-report decoding, source hashing, mutant conversion, and admission
|
|
1089
1073
|
* pipeline for --survivors runs. Pure admission decision lives in
|
|
1090
|
-
*
|
|
1074
|
+
* admit-survivors-run.workflow.ts.
|
|
1091
1075
|
*/
|
|
1092
1076
|
const DEFAULT_SURVIVORS_PRIOR_REPORT = "reports/mutation-report.json";
|
|
1093
1077
|
/**
|
|
@@ -1183,87 +1167,57 @@ function survivorMutateSpans(survivors, basePath) {
|
|
|
1183
1167
|
return spans;
|
|
1184
1168
|
}
|
|
1185
1169
|
const hashContent = (content) => bytesToHex(sha256(utf8ToBytes(content)));
|
|
1186
|
-
|
|
1187
|
-
|
|
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 admissionContext = yield* Ref.make(void 0);
|
|
1265
|
-
return yield* Cell.apply(survivorsAdmissionDescription(runMutationTest, stream, mode, admissionContext, basePath), cliOptions);
|
|
1266
|
-
});
|
|
1216
|
+
onFailure: Effect.fail
|
|
1217
|
+
})
|
|
1218
|
+
});
|
|
1219
|
+
function runSurvivorsAdmission(cliOptions, basePath) {
|
|
1220
|
+
return Cell.run(survivorsAdmissionCell(basePath), cliOptions);
|
|
1267
1221
|
}
|
|
1268
1222
|
function resolveSurvivorsRunOptions(cliOptions, basePath) {
|
|
1269
1223
|
return readConfig(cliOptions, basePath);
|
|
@@ -1515,7 +1469,7 @@ function readCoreEntries() {
|
|
|
1515
1469
|
return Effect.gen(function* () {
|
|
1516
1470
|
const path = yield* Path.Path;
|
|
1517
1471
|
const fs = yield* FileSystem.FileSystem;
|
|
1518
|
-
const resolved = import.meta.resolve("@systemfsoftware/stryker-js-
|
|
1472
|
+
const resolved = import.meta.resolve("@systemfsoftware/stryker-js-engine/package.json");
|
|
1519
1473
|
const manifestPath = yield* path.fromFileUrl(new URL(resolved));
|
|
1520
1474
|
const raw = yield* fs.readFileString(manifestPath);
|
|
1521
1475
|
let parsed;
|
|
@@ -1530,8 +1484,6 @@ function readCoreEntries() {
|
|
|
1530
1484
|
return [];
|
|
1531
1485
|
}).pipe(Effect.catchCause(() => {
|
|
1532
1486
|
return Effect.succeed([]);
|
|
1533
|
-
}), Effect.catchDefect(() => {
|
|
1534
|
-
return Effect.succeed([]);
|
|
1535
1487
|
}));
|
|
1536
1488
|
}
|
|
1537
1489
|
function buildLLMSManifest(command, version) {
|
|
@@ -1765,7 +1717,7 @@ const cliLayer = Layer.mergeAll(CliConfig.layer({ builtIns: [
|
|
|
1765
1717
|
GlobalFlag.Wizard,
|
|
1766
1718
|
GlobalFlag.Completions,
|
|
1767
1719
|
GlobalFlag.LogLevel
|
|
1768
|
-
] }), Path.layer, NodeFileSystem
|
|
1720
|
+
] }), Path.layer, NodeFileSystem.layer, terminalLayer, NodeStdio.layer, NodeChildProcessSpawner.layer.pipe(Layer.provideMerge(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer))));
|
|
1769
1721
|
function strykerCliEffect(argv, runMutationTest, detectMode, createRunEventStream, lastSignal) {
|
|
1770
1722
|
return Effect.gen(function* () {
|
|
1771
1723
|
const mode = yield* detectMode;
|
|
@@ -1788,14 +1740,15 @@ function strykerCliEffect(argv, runMutationTest, detectMode, createRunEventStrea
|
|
|
1788
1740
|
return result.success;
|
|
1789
1741
|
}).pipe(Effect.orElseSucceed(() => 2));
|
|
1790
1742
|
}
|
|
1791
|
-
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));
|
|
1792
1745
|
function hostOptionsOf(mode, stream) {
|
|
1793
1746
|
return {
|
|
1794
1747
|
runId: stream.runId,
|
|
1795
1748
|
resolvedMode: mode,
|
|
1796
1749
|
runStartedAt: stream.startedAt,
|
|
1797
1750
|
basePath: process.cwd(),
|
|
1798
|
-
reporterPluginModules: [import.meta.resolve("@systemfsoftware/stryker-js-html-reporter"), import.meta.resolve("@systemfsoftware/stryker-js-
|
|
1751
|
+
reporterPluginModules: [import.meta.resolve("@systemfsoftware/stryker-js-html-reporter"), import.meta.resolve("@systemfsoftware/stryker-js-engine/builtin-reporters")],
|
|
1799
1752
|
allowConsoleColors: isColorEnabled(mode, process.env["NO_COLOR"])
|
|
1800
1753
|
};
|
|
1801
1754
|
}
|
|
@@ -1804,7 +1757,7 @@ const runStrykerCli = (input, createRunEventStream) => Effect.gen(function* () {
|
|
|
1804
1757
|
const hostOptions = hostOptionsOf(input.mode, stream);
|
|
1805
1758
|
const runMutationTestImpl = input.runMutationTest ?? defaultRunMutationTest(hostOptions, stream.queue);
|
|
1806
1759
|
const basePath = hostOptions.basePath;
|
|
1807
|
-
const pathService = yield* Path.Path.pipe(Effect.provide(NodePath
|
|
1760
|
+
const pathService = yield* Path.Path.pipe(Effect.provide(NodePath.layer));
|
|
1808
1761
|
let currentFiber = null;
|
|
1809
1762
|
const onSignal = () => {
|
|
1810
1763
|
process.removeListener("SIGINT", onSignal);
|
|
@@ -1812,7 +1765,20 @@ const runStrykerCli = (input, createRunEventStream) => Effect.gen(function* () {
|
|
|
1812
1765
|
if (currentFiber !== null) currentFiber.interruptUnsafe(currentFiber.id);
|
|
1813
1766
|
};
|
|
1814
1767
|
const dispatch = (request) => Match.value(request).pipe(Match.tag("run", (runRequest) => (() => {
|
|
1815
|
-
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
|
+
});
|
|
1816
1782
|
return runMutationTestImpl(runRequest.options).pipe(Effect.orDie);
|
|
1817
1783
|
})()), Match.tag("llms", (llmsRequest) => Effect.gen(function* () {
|
|
1818
1784
|
stream.ensureOpen({
|
|
@@ -1866,29 +1832,18 @@ const EXIT_CODE_RUN_NEVER_REACHED_ITS_FINALIZER = 1;
|
|
|
1866
1832
|
process.title = "stryker";
|
|
1867
1833
|
const lastSignal = observeTerminatingSignal();
|
|
1868
1834
|
function isSupportedNodeVersion(version) {
|
|
1869
|
-
|
|
1870
|
-
if (version.startsWith("v")) withoutV = version.slice(1);
|
|
1871
|
-
const dashBaseRaw = withoutV.split("-")[0];
|
|
1872
|
-
let dashBase = withoutV;
|
|
1873
|
-
if (dashBaseRaw !== void 0) dashBase = dashBaseRaw;
|
|
1874
|
-
const baseRaw = dashBase.split("+")[0];
|
|
1875
|
-
let base = dashBase;
|
|
1876
|
-
if (baseRaw !== void 0) base = baseRaw;
|
|
1877
|
-
const parts = base.split(".").map((p) => Number.parseInt(p, 10));
|
|
1878
|
-
const major = parts[0] ?? 0;
|
|
1879
|
-
const minor = parts[1] ?? 0;
|
|
1880
|
-
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));
|
|
1881
1836
|
if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch)) return false;
|
|
1882
1837
|
if (major !== 20) return major > 20;
|
|
1883
1838
|
if (minor !== 0) return minor > 0;
|
|
1884
1839
|
return patch >= 0;
|
|
1885
1840
|
}
|
|
1886
|
-
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`);
|
|
1887
1842
|
const program = Effect.gen(function* () {
|
|
1888
1843
|
const outputMode = yield* OutputModeProbe;
|
|
1889
1844
|
const runEvents = yield* RunEventStreamPort;
|
|
1890
1845
|
return yield* strykerCliEffect(process.argv.slice(2), void 0, outputMode.detectMode, runEvents.createRunEventStream, lastSignal);
|
|
1891
|
-
}).pipe(Effect.provideService(Logger.LogToStderr, true), Effect.provide(Layer.merge(OutputModeProbeLive, RunEventStreamFileLive).pipe(Layer.provide(Layer.mergeAll(NodeStdio.layer, NodeFileSystem
|
|
1846
|
+
}).pipe(Effect.provideService(Logger.LogToStderr, true), Effect.provide(Layer.merge(OutputModeProbeLive, RunEventStreamFileLive).pipe(Layer.provide(Layer.mergeAll(NodeStdio.layer, NodeFileSystem.layer, NodePath.layer)))));
|
|
1892
1847
|
NodeRuntime.runMain(program, {
|
|
1893
1848
|
disableErrorReporting: true,
|
|
1894
1849
|
teardown: (exit, onExit) => {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
import * as NodeChildProcessSpawner from "@effect/platform-node-shared/NodeChildProcessSpawner";
|
|
4
|
+
import { ChildProcessCrashedError, WorkerEntries, WorkerLauncher } from "@systemfsoftware/stryker-js-engine";
|
|
5
|
+
import * as FileSystem from "effect/FileSystem";
|
|
6
|
+
import * as Match from "effect/Match";
|
|
7
|
+
import * as Path from "effect/Path";
|
|
8
|
+
import { NodeFileSystem, NodePath, NodeSocket } from "@effect/platform-node";
|
|
9
|
+
import { Module } from "@systemfsoftware/stryker-js/Module";
|
|
10
|
+
import * as ChildProcess from "effect/unstable/process/ChildProcess";
|
|
11
|
+
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
|
|
12
|
+
import * as RpcClient from "effect/unstable/rpc/RpcClient";
|
|
13
|
+
import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
|
|
14
|
+
//#region src/platform/node.ts
|
|
15
|
+
const makeModuleRequire = (nodeModule, filename) => {
|
|
16
|
+
const requireFrom = nodeModule.createRequire(filename);
|
|
17
|
+
const requireFn = (request) => requireFrom(request);
|
|
18
|
+
requireFn.resolve = (request, options) => {
|
|
19
|
+
if (options === void 0) return requireFrom.resolve(request);
|
|
20
|
+
return requireFrom.resolve(request, { paths: [...options.paths ?? []] });
|
|
21
|
+
};
|
|
22
|
+
return requireFn;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* The Node implementation of the {@link Module} port: every call routes
|
|
26
|
+
* through the runtime's own `node:module` via `process.getBuiltinModule`, so
|
|
27
|
+
* this package imports no host builtins and the import ban holds here too.
|
|
28
|
+
*/
|
|
29
|
+
const nodeModuleLayer = Layer.effect(Module, Effect.sync(() => {
|
|
30
|
+
const nodeModule = process.getBuiltinModule("node:module");
|
|
31
|
+
return {
|
|
32
|
+
createRequire: (filename) => makeModuleRequire(nodeModule, filename),
|
|
33
|
+
isBuiltin: (moduleName) => nodeModule.isBuiltin(moduleName)
|
|
34
|
+
};
|
|
35
|
+
}));
|
|
36
|
+
/**
|
|
37
|
+
* The worker entries this package's own build emits. The engine spawns by
|
|
38
|
+
* address; this process package knows its dist layout and hands the
|
|
39
|
+
* addresses in.
|
|
40
|
+
*/
|
|
41
|
+
const workerEntriesLayer = Layer.succeed(WorkerEntries, {
|
|
42
|
+
checkerWorkerUrl: new URL("./workers/checker-worker.mjs", import.meta.url),
|
|
43
|
+
testRunnerWorkerUrl: new URL("./workers/child-process-test-runner-worker.mjs", import.meta.url)
|
|
44
|
+
});
|
|
45
|
+
/**
|
|
46
|
+
* The Node worker launcher: spawn a worker child with this runtime's
|
|
47
|
+
* executable, host the RPC server's address as a `net` `path` endpoint (a
|
|
48
|
+
* socket file in the worker directory on POSIX, a same-user named pipe on
|
|
49
|
+
* Windows), and connect the NDJSON protocol client over `NodeSocket`.
|
|
50
|
+
*/
|
|
51
|
+
const nodeWorkerLauncherLayer = Layer.effect(WorkerLauncher, Effect.gen(function* () {
|
|
52
|
+
const fs = yield* FileSystem.FileSystem;
|
|
53
|
+
const path = yield* Path.Path;
|
|
54
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
55
|
+
return { spawn: (params) => Effect.gen(function* () {
|
|
56
|
+
const workerDir = yield* fs.makeTempDirectoryScoped({ prefix: params.tempDirPrefix });
|
|
57
|
+
const socketPath = Match.value(process.platform).pipe(Match.when("win32", () => `\\\\.\\pipe\\stryker-worker-${globalThis.crypto.randomUUID()}`), Match.orElse(() => path.join(workerDir, "worker.sock")));
|
|
58
|
+
yield* fs.writeFileString(path.join(workerDir, "options.json"), params.optionsJson);
|
|
59
|
+
const entryPath = yield* path.fromFileUrl(params.entryUrl);
|
|
60
|
+
const handle = yield* ChildProcess.make(process.execPath, [...params.execArgv, entryPath], {
|
|
61
|
+
cwd: params.workingDirectory,
|
|
62
|
+
extendEnv: true,
|
|
63
|
+
env: {
|
|
64
|
+
STRYKER_WORKER_DIR: workerDir,
|
|
65
|
+
STRYKER_SOCKET: socketPath
|
|
66
|
+
},
|
|
67
|
+
stderr: "inherit"
|
|
68
|
+
}).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner));
|
|
69
|
+
const clientLayer = RpcClient.layerProtocolSocket({ retryTransientErrors: true }).pipe(Layer.provide(NodeSocket.layerNet({ path: socketPath })), Layer.provide(RpcSerialization.layerNdjson));
|
|
70
|
+
const exited = handle.exitCode.pipe(Effect.orDie, Effect.flatMap((exitCode) => Effect.fail(new ChildProcessCrashedError({
|
|
71
|
+
pid: Number(handle.pid),
|
|
72
|
+
exit: {
|
|
73
|
+
_tag: "Code",
|
|
74
|
+
code: exitCode
|
|
75
|
+
},
|
|
76
|
+
cause: "worker exited before it accepted the RPC connection"
|
|
77
|
+
}))));
|
|
78
|
+
return {
|
|
79
|
+
pid: Number(handle.pid),
|
|
80
|
+
clientLayer,
|
|
81
|
+
exited
|
|
82
|
+
};
|
|
83
|
+
}).pipe(Effect.catch((error) => {
|
|
84
|
+
if (error instanceof ChildProcessCrashedError) return Effect.fail(error);
|
|
85
|
+
return Effect.fail(new ChildProcessCrashedError({
|
|
86
|
+
pid: 0,
|
|
87
|
+
exit: {
|
|
88
|
+
_tag: "Code",
|
|
89
|
+
code: 1
|
|
90
|
+
},
|
|
91
|
+
cause: "worker spawn failed"
|
|
92
|
+
}));
|
|
93
|
+
})) };
|
|
94
|
+
}));
|
|
95
|
+
const nodeFsPathLayer = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer);
|
|
96
|
+
const nodeSpawnerLayer = NodeChildProcessSpawner.layer.pipe(Layer.provide(nodeFsPathLayer));
|
|
97
|
+
const nodeBase = Layer.merge(nodeFsPathLayer, nodeSpawnerLayer);
|
|
98
|
+
/**
|
|
99
|
+
* Every port the engine requires, provided from this runtime: the file
|
|
100
|
+
* system, the path service, the module loader, the child-process spawner,
|
|
101
|
+
* the worker launcher, and this build's worker entry addresses.
|
|
102
|
+
*/
|
|
103
|
+
const nodePlatformLayer = Layer.mergeAll(nodeModuleLayer, workerEntriesLayer, nodeWorkerLauncherLayer.pipe(Layer.provide(nodeBase)), nodeBase);
|
|
104
|
+
//#endregion
|
|
105
|
+
export { nodePlatformLayer as n, nodeModuleLayer as t };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Layer from "effect/Layer";
|
|
3
|
+
import * as Cause from "effect/Cause";
|
|
4
|
+
//#region src/workers/worker-runtime.ts
|
|
5
|
+
const workerSocketPath = (label) => {
|
|
6
|
+
const socketPath = process.env["STRYKER_SOCKET"];
|
|
7
|
+
if (socketPath === void 0) {
|
|
8
|
+
process.stderr.write(`${label}: STRYKER_SOCKET is not set\n`);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
return socketPath;
|
|
12
|
+
};
|
|
13
|
+
const launchWorker = (main, label) => {
|
|
14
|
+
Effect.runFork(Layer.launch(main).pipe(Effect.tapCause((cause) => Effect.sync(() => {
|
|
15
|
+
process.stderr.write(`${label}: ${Cause.pretty(cause)}\n`);
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
}))));
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
export { workerSocketPath as n, launchWorker as t };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { t as nodeModuleLayer } from "../node-D-ynY_kk.mjs";
|
|
2
|
+
import { n as workerSocketPath, t as launchWorker } from "../worker-runtime-CT5LMMgY.mjs";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
5
|
+
import * as FileSystem from "effect/FileSystem";
|
|
6
|
+
import * as Option from "effect/Option";
|
|
7
|
+
import * as Path from "effect/Path";
|
|
8
|
+
import { NodeFileSystem, NodePath, NodeSocketServer } from "@effect/platform-node";
|
|
9
|
+
import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
|
|
10
|
+
import { Checker, CheckerFailed } from "@systemfsoftware/stryker-js/Checker";
|
|
11
|
+
import { RunConfiguration, SandboxDirectory } from "@systemfsoftware/stryker-js/Plugin";
|
|
12
|
+
import * as HashMap from "effect/HashMap";
|
|
13
|
+
import * as RpcServer from "effect/unstable/rpc/RpcServer";
|
|
14
|
+
import { CheckerRpcs, create, decodeWorkerOptions, loadPlugins } from "@systemfsoftware/stryker-js-engine/worker";
|
|
15
|
+
//#region src/workers/Checker.worker.ts
|
|
16
|
+
const buildChecker = (contribution, options) => Checker.pipe(Effect.provide(contribution.layer.pipe(Layer.provide(Layer.mergeAll(Layer.succeed(RunConfiguration, options), Layer.succeed(SandboxDirectory, process.cwd()), NodeFileSystem.layer, NodePath.layer, nodeModuleLayer)))));
|
|
17
|
+
const mutantIdsOf = (mutants) => mutants.map((mutant) => mutant.id);
|
|
18
|
+
const readWorkerOptions = Effect.gen(function* () {
|
|
19
|
+
const workerDir = process.env["STRYKER_WORKER_DIR"] ?? (yield* Effect.die(/* @__PURE__ */ new Error("STRYKER_WORKER_DIR is not set")));
|
|
20
|
+
const fs = yield* FileSystem.FileSystem;
|
|
21
|
+
const path = yield* Path.Path;
|
|
22
|
+
const raw = yield* fs.readFileString(path.join(workerDir, "options.json"));
|
|
23
|
+
return yield* decodeWorkerOptions(raw);
|
|
24
|
+
});
|
|
25
|
+
const CheckerHandlers = CheckerRpcs.toLayer(Effect.gen(function* () {
|
|
26
|
+
const options = yield* readWorkerOptions;
|
|
27
|
+
const loaded = yield* loadPlugins(options.plugins, process.cwd());
|
|
28
|
+
const checkers = HashMap.fromIterable(yield* Effect.forEach(options.checkers, (name) => Effect.gen(function* () {
|
|
29
|
+
const contribution = yield* create(loaded.pluginsByKind, "Checker", name);
|
|
30
|
+
const checker = yield* buildChecker(contribution, options);
|
|
31
|
+
yield* checker.init;
|
|
32
|
+
return [name, checker];
|
|
33
|
+
}), {
|
|
34
|
+
concurrency: "unbounded",
|
|
35
|
+
discard: false
|
|
36
|
+
}));
|
|
37
|
+
const resolve = (checkerName, mutants) => Option.match(HashMap.get(checkers, checkerName), {
|
|
38
|
+
onNone: () => Effect.fail(new CheckerFailed({
|
|
39
|
+
cause: `Checker ${checkerName} does not exist`,
|
|
40
|
+
checkerName,
|
|
41
|
+
mutantIds: mutantIdsOf(mutants)
|
|
42
|
+
})),
|
|
43
|
+
onSome: (checker) => Effect.succeed(checker)
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
check: ({ checkerName, mutants }) => resolve(checkerName, mutants).pipe(Effect.flatMap((checker) => checker.check([...mutants])), Effect.map((resultMap) => Object.fromEntries(resultMap))),
|
|
47
|
+
group: ({ checkerName, mutants }) => resolve(checkerName, mutants).pipe(Effect.flatMap((checker) => checker.group([...mutants])))
|
|
48
|
+
};
|
|
49
|
+
})).pipe(Layer.provide(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)));
|
|
50
|
+
const MainLayer = RpcServer.layer(CheckerRpcs).pipe(Layer.provide(CheckerHandlers), Layer.provide(RpcServer.layerProtocolSocketServer), Layer.provide(RpcSerialization.layerNdjson), Layer.provide(NodeSocketServer.layer({ path: workerSocketPath("checker worker") })), Layer.provide(nodeModuleLayer));
|
|
51
|
+
launchWorker(MainLayer, "checker worker");
|
|
52
|
+
//#endregion
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { t as nodeModuleLayer } from "../node-D-ynY_kk.mjs";
|
|
2
|
+
import { n as workerSocketPath, t as launchWorker } from "../worker-runtime-CT5LMMgY.mjs";
|
|
3
|
+
import * as Effect from "effect/Effect";
|
|
4
|
+
import * as Layer from "effect/Layer";
|
|
5
|
+
import { errorToString } from "@systemfsoftware/stryker-js/Mutant";
|
|
6
|
+
import * as Cause from "effect/Cause";
|
|
7
|
+
import * as FileSystem from "effect/FileSystem";
|
|
8
|
+
import * as Path from "effect/Path";
|
|
9
|
+
import { NodeFileSystem, NodePath, NodeSocketServer } from "@effect/platform-node";
|
|
10
|
+
import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
|
|
11
|
+
import { RunConfiguration, SandboxDirectory } from "@systemfsoftware/stryker-js/Plugin";
|
|
12
|
+
import * as RpcServer from "effect/unstable/rpc/RpcServer";
|
|
13
|
+
import { MutantCoverageSchema, TestRunnerRpcs, create, decodeWorkerOptions, loadPlugins } from "@systemfsoftware/stryker-js-engine/worker";
|
|
14
|
+
import { TestRunner, TestRunnerFailed } from "@systemfsoftware/stryker-js/TestRunner";
|
|
15
|
+
import { Schema } from "effect";
|
|
16
|
+
//#region src/workers/child-process-test-runner-worker.ts
|
|
17
|
+
const withCoverage = (result, options) => Effect.gen(function* () {
|
|
18
|
+
if (result.status === "error") return {
|
|
19
|
+
...result,
|
|
20
|
+
errorMessage: errorToString(result.errorMessage)
|
|
21
|
+
};
|
|
22
|
+
if (result.status !== "complete") return result;
|
|
23
|
+
if (result.mutantCoverage !== void 0) return result;
|
|
24
|
+
if (options.coverageAnalysis === "off") return result;
|
|
25
|
+
const decoded = yield* Schema.decodeUnknownEffect(Schema.optional(MutantCoverageSchema))(globalThis.__mutantCoverage__).pipe(Effect.orElseSucceed(() => void 0));
|
|
26
|
+
if (decoded === void 0) return result;
|
|
27
|
+
return {
|
|
28
|
+
...result,
|
|
29
|
+
mutantCoverage: decoded
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
const normalizeMutantRun = (result) => {
|
|
33
|
+
if (result.status === "error") return {
|
|
34
|
+
...result,
|
|
35
|
+
errorMessage: errorToString(result.errorMessage)
|
|
36
|
+
};
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
const readWorkerOptions = Effect.gen(function* () {
|
|
40
|
+
const workerDir = process.env["STRYKER_WORKER_DIR"] ?? (yield* Effect.die(/* @__PURE__ */ new Error("STRYKER_WORKER_DIR is not set")));
|
|
41
|
+
const fs = yield* FileSystem.FileSystem;
|
|
42
|
+
const path = yield* Path.Path;
|
|
43
|
+
const raw = yield* fs.readFileString(path.join(workerDir, "options.json"));
|
|
44
|
+
return yield* decodeWorkerOptions(raw);
|
|
45
|
+
});
|
|
46
|
+
const TestRunnerHandlers = TestRunnerRpcs.toLayer(Effect.gen(function* () {
|
|
47
|
+
const options = yield* readWorkerOptions;
|
|
48
|
+
const runnerName = options.testRunner;
|
|
49
|
+
const failed = (phase) => (cause) => Effect.fail(new TestRunnerFailed({
|
|
50
|
+
cause: Cause.pretty(cause),
|
|
51
|
+
phase,
|
|
52
|
+
runnerName
|
|
53
|
+
}));
|
|
54
|
+
const loaded = yield* loadPlugins(options.plugins, process.cwd()).pipe(Effect.catchCause(failed("init")));
|
|
55
|
+
const underlying = yield* create(loaded.pluginsByKind, "TestRunner", runnerName).pipe(Effect.flatMap((contribution) => TestRunner.pipe(Effect.provide(contribution.layer))), Effect.provide(Layer.merge(Layer.succeed(RunConfiguration, options), Layer.succeed(SandboxDirectory, process.cwd()))), Effect.catchCause(failed("init")));
|
|
56
|
+
yield* underlying.init.pipe(Effect.catchCause(failed("init")));
|
|
57
|
+
yield* Effect.addFinalizer(() => underlying.dispose.pipe(Effect.ignore));
|
|
58
|
+
return {
|
|
59
|
+
capabilities: () => underlying.capabilities.pipe(Effect.catchCause(failed("capabilities"))),
|
|
60
|
+
dryRun: ({ options: runOptions }) => underlying.dryRun(runOptions).pipe(Effect.flatMap((result) => withCoverage(result, runOptions)), Effect.catchCause(failed("dryRun"))),
|
|
61
|
+
mutantRun: ({ options: runOptions }) => underlying.mutantRun(runOptions).pipe(Effect.map(normalizeMutantRun), Effect.catchCause(failed("mutantRun")))
|
|
62
|
+
};
|
|
63
|
+
})).pipe(Layer.provide(Layer.mergeAll(NodeFileSystem.layer, NodePath.layer)));
|
|
64
|
+
const MainLayer = RpcServer.layer(TestRunnerRpcs).pipe(Layer.provide(TestRunnerHandlers), Layer.provide(RpcServer.layerProtocolSocketServer), Layer.provide(RpcSerialization.layerNdjson), Layer.provide(NodeSocketServer.layer({ path: workerSocketPath("test runner worker") })), Layer.provide(nodeModuleLayer));
|
|
65
|
+
launchWorker(MainLayer, "test runner worker");
|
|
66
|
+
//#endregion
|
|
67
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@systemfsoftware/stryker-js-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/systemfsoftware/systemfsoftware.git",
|
|
@@ -18,32 +18,33 @@
|
|
|
18
18
|
"@effect/platform-node-shared": "^4.0.0-rc.112",
|
|
19
19
|
"@noble/hashes": "1.8.0",
|
|
20
20
|
"effect": "^4.0.0-rc.112",
|
|
21
|
-
"@systemfsoftware/effect-cell-types": "^
|
|
22
|
-
"@systemfsoftware/stryker-js-
|
|
23
|
-
"@systemfsoftware/stryker-js
|
|
24
|
-
"@systemfsoftware/stryker-js": "^0.
|
|
21
|
+
"@systemfsoftware/effect-cell-types": "^6.0.0",
|
|
22
|
+
"@systemfsoftware/stryker-js-engine": "^0.2.0",
|
|
23
|
+
"@systemfsoftware/stryker-js": "^1.0.0",
|
|
24
|
+
"@systemfsoftware/stryker-js-html-reporter": "^1.0.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@systemfsoftware/arethetypeswrong-cli": "^1.1.1",
|
|
28
28
|
"@types/node": "^24",
|
|
29
29
|
"@vitest/coverage-v8": "^4",
|
|
30
|
+
"mutation-testing-report-schema": "3.7.3",
|
|
30
31
|
"oxlint": "^1.77.0",
|
|
31
32
|
"rimraf": "^6.1.3",
|
|
32
33
|
"testcontainers": "^12.1.0",
|
|
33
34
|
"tsdown": "^0.22.14",
|
|
34
35
|
"vite-tsconfig-paths": "^6.1.1",
|
|
35
36
|
"vitest": "^4",
|
|
37
|
+
"@systemfsoftware/all": "^1.1.2",
|
|
36
38
|
"@systemfsoftware/effect-gherkin-spec": "^4.0.0",
|
|
37
39
|
"@systemfsoftware/effect-schema-law": "^2.0.1",
|
|
38
40
|
"@systemfsoftware/effect-schema-vite": "^2.0.2",
|
|
39
|
-
"@systemfsoftware/
|
|
40
|
-
"@systemfsoftware/
|
|
41
|
-
"@systemfsoftware/stryker-
|
|
42
|
-
"@systemfsoftware/stryker-
|
|
43
|
-
"@systemfsoftware/stryker-plugins": "^2.0.1",
|
|
44
|
-
"@systemfsoftware/tsconfig": "^1.3.3",
|
|
41
|
+
"@systemfsoftware/stryker-js-typescript-checker": "^4.0.0",
|
|
42
|
+
"@systemfsoftware/stryker-js-vitest-runner": "^3.0.0",
|
|
43
|
+
"@systemfsoftware/stryker-plugins": "^2.0.2",
|
|
44
|
+
"@systemfsoftware/stryker-test-contribution": "^1.1.3",
|
|
45
45
|
"@systemfsoftware/vitest-config": "^0.1.0",
|
|
46
|
-
"@systemfsoftware/
|
|
46
|
+
"@systemfsoftware/tsconfig": "^1.3.3",
|
|
47
|
+
"@systemfsoftware/oxlint-config": "^0.1.0"
|
|
47
48
|
},
|
|
48
49
|
"engines": {
|
|
49
50
|
"node": ">=20.0.0"
|