@systemfsoftware/stryker-js-vitest-runner 4.0.4 → 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/dist/index.mjs CHANGED
@@ -1,1030 +1,48 @@
1
- import { INSTRUMENTER_CONSTANTS, Module, RunConfiguration, SandboxDirectory, TestRunner, TestRunnerFailed, declarePlugin, errorToString, normalizeFileName, testFilesProvided } from "@systemfsoftware/stryker-js";
2
- import * as Effect$1 from "effect/Effect";
3
- import * as Layer from "effect/Layer";
4
1
  import * as S from "effect/Schema";
5
- import { createVitest } from "vitest/node";
6
- import { Cell, Workflow } from "@systemfsoftware/effect-cell-types";
7
- import * as Context from "effect/Context";
8
- import * as FileSystem from "effect/FileSystem";
9
- import * as Match from "effect/Match";
10
- import * as Option from "effect/Option";
11
- import * as Path from "effect/Path";
12
- import * as Predicate from "effect/Predicate";
13
- import * as Ref from "effect/Ref";
14
- import * as Result from "effect/Result";
15
2
  import { Effect } from "effect";
16
- //#region src/interpret-vitest-run.workflow.ts
17
- var VitestMutantRunCommand = class extends S.TaggedClass()("VitestMutantRunCommand", {
18
- rawTests: S.Array(S.Unknown),
19
- projectRoot: S.String,
20
- hasExternalError: S.Boolean,
21
- externalErrorText: S.String,
22
- hitCount: S.optional(S.Finite),
23
- hitLimit: S.optional(S.Finite),
24
- reportAllKillers: S.Boolean
25
- }) {};
26
- const VitestMutantRunTypeId = Symbol.for("@systemfsoftware/stryker-js-vitest-runner/VitestMutantRun");
27
- var MutantKilled = class extends S.TaggedClass()("Killed", {
28
- testsJson: S.String,
29
- killerIds: S.optional(S.Array(S.String)),
30
- failureMessage: S.optional(S.String)
31
- }) {
32
- [VitestMutantRunTypeId] = VitestMutantRunTypeId;
33
- };
34
- var MutantSurvived = class extends S.TaggedClass()("Survived", { testsJson: S.String }) {
35
- [VitestMutantRunTypeId] = VitestMutantRunTypeId;
36
- };
37
- var MutantTimeout = class extends S.TaggedClass()("Timeout", {
38
- testsJson: S.String,
39
- reason: S.optional(S.String)
40
- }) {
41
- [VitestMutantRunTypeId] = VitestMutantRunTypeId;
42
- };
43
- var MutantDryError = class extends S.TaggedClass()("Error", {
44
- testsJson: S.String,
45
- errorMessage: S.optional(S.String)
46
- }) {
47
- [VitestMutantRunTypeId] = VitestMutantRunTypeId;
48
- };
49
- var VitestMutantRunError = class extends S.TaggedError()("VitestMutantRunError", { message: S.String }) {
50
- [VitestMutantRunTypeId] = VitestMutantRunTypeId;
51
- };
52
- var VitestDryRunCommand$1 = class extends S.TaggedClass()("VitestDryRunCommand", {
53
- rawTests: S.Array(S.Unknown),
54
- projectRoot: S.String,
55
- hasExternalError: S.Boolean,
56
- externalErrorText: S.String
57
- }) {};
58
- var VitestDryRunOutput = class extends S.TaggedClass()("VitestDryRunOutput", {
59
- status: S.Literals(["Complete", "Error"]),
60
- testsJson: S.String,
61
- errorMessage: S.optional(S.String)
62
- }) {};
63
- const recordOption$1 = (value) => S.decodeUnknownOption(S.Record(S.String, S.Unknown))(value);
64
- const getStringField$1 = (record, key) => Option.fromNullishOr(record[key]).pipe(Option.filter((v) => typeof v === "string"));
65
- const getNumberField$1 = (record, key) => Option.fromNullishOr(record[key]).pipe(Option.filter((v) => typeof v === "number"));
66
- const getSuite$1 = (value) => recordOption$1(value).pipe(Option.flatMap((rec) => Option.fromNullishOr(rec["suite"])));
67
- const getFile$1 = (value) => recordOption$1(value).pipe(Option.flatMap((rec) => Option.fromNullishOr(rec["file"])));
68
- const getResult$1 = (value) => recordOption$1(value).pipe(Option.flatMap((rec) => Option.fromNullishOr(rec["result"])));
69
- const getErrors$1 = (value) => recordOption$1(value).pipe(Option.flatMap((rec) => Option.fromNullishOr(rec["errors"])), Option.filter((v) => Array.isArray(v)));
70
- const getMessage$1 = (value) => recordOption$1(value).pipe(Option.flatMap((rec) => Option.fromNullishOr(rec["message"])), Option.filter((v) => typeof v === "string"));
71
- const getName$1 = (value) => Option.match(recordOption$1(value), {
72
- onNone: () => "",
73
- onSome: (rec) => Option.getOrElse(getStringField$1(rec, "name"), () => "")
74
- });
75
- const getMode$1 = (value) => Option.match(recordOption$1(value), {
76
- onNone: () => "run",
77
- onSome: (rec) => Option.getOrElse(getStringField$1(rec, "mode"), () => "run")
78
- });
79
- const getState$1 = (value) => Match.value(value).pipe(Match.when("pass", () => "pass"), Match.when("fail", () => "fail"), Match.when("skip", () => "skip"), Match.when("todo", () => "todo"), Match.when("run", () => "run"), Match.when("queued", () => "queued"), Match.when("only", () => "only"), Match.when(void 0, () => void 0), Match.orElse(() => void 0));
80
- const getDuration$1 = (value) => Option.match(recordOption$1(value), {
81
- onNone: () => 0,
82
- onSome: (rec) => Option.getOrElse(getNumberField$1(rec, "duration"), () => 0)
83
- });
84
- const getFilepath$1 = (value) => Option.match(recordOption$1(value), {
85
- onNone: () => void 0,
86
- onSome: (rec) => Option.getOrUndefined(Option.fromNullishOr(rec["filepath"]).pipe(Option.filter((v) => typeof v === "string")))
87
- });
88
- const collectSuiteNames$1 = (suite) => Option.match(Option.fromNullishOr(suite), {
89
- onNone: () => [],
90
- onSome: (current) => Option.match(recordOption$1(current), {
91
- onNone: () => [],
92
- onSome: (rec) => {
93
- const name = Option.getOrElse(getStringField$1(rec, "name"), () => "");
94
- const hasName = name.length > 0;
95
- const parentNames = collectSuiteNames$1(rec["suite"]);
96
- return Match.value(hasName).pipe(Match.when(true, () => [...parentNames, name]), Match.when(false, () => parentNames), Match.exhaustive);
97
- }
98
- })
99
- });
100
- const collectTestNameRaw$1 = (test) => {
101
- const name = getName$1(test);
102
- const suite = Option.getOrUndefined(getSuite$1(test));
103
- return [...collectSuiteNames$1(suite), name].join(" ").trim();
104
- };
105
- const toRawTestIdRaw$1 = (test) => {
106
- return `${Option.match(getFile$1(test), {
107
- onNone: () => "unknown.js",
108
- onSome: (file) => Option.getOrElse(Option.fromNullishOr(getFilepath$1(file)), () => "unknown.js")
109
- })}#${collectTestNameRaw$1(test)}`;
110
- };
111
- const stripProjectRoot = (file, projectRoot) => Match.value(file.startsWith(projectRoot)).pipe(Match.when(true, () => file.slice(projectRoot.length)), Match.when(false, () => file), Match.exhaustive);
112
- const toProjectRelativePath = (file) => file.replace(/^[/\\]+/, "").replaceAll("\\", "/");
113
- const normalizeTestIdRaw$1 = (id, projectRoot) => {
114
- const hash = id.indexOf("#");
115
- return Match.value(hash === -1).pipe(Match.when(true, () => id), Match.when(false, () => {
116
- return `${toProjectRelativePath(stripProjectRoot(id.slice(0, hash), projectRoot))}#${id.slice(hash + 1)}`;
117
- }), Match.exhaustive);
118
- };
119
- const toTestStatus$1 = (taskState, mode) => Match.value(mode === "skip").pipe(Match.when(true, () => "skipped"), Match.when(false, () => Match.value(taskState).pipe(Match.when("pass", () => "success"), Match.when("skip", () => "skipped"), Match.when("todo", () => "skipped"), Match.orElse(() => "failed"))), Match.exhaustive);
120
- const findSuiteErrorRaw$1 = (suite) => Option.match(Option.fromNullishOr(suite), {
121
- onNone: () => void 0,
122
- onSome: (current) => Option.match(recordOption$1(current), {
123
- onNone: () => void 0,
124
- onSome: (rec) => {
125
- const maybeError = Option.flatMap(getResult$1(rec), (result) => Option.flatMap(getErrors$1(result), (errs) => Match.value(errs.length > 0).pipe(Match.when(true, () => Option.flatMap(Option.fromNullishOr(errs[0]), (first) => getMessage$1(first))), Match.when(false, () => Option.none()), Match.exhaustive)));
126
- return Option.match(maybeError, {
127
- onNone: () => findSuiteErrorRaw$1(rec["suite"]),
128
- onSome: (msg) => msg
129
- });
130
- }
131
- })
132
- });
133
- const extractStatus$1 = (test) => {
134
- const result = Option.getOrUndefined(getResult$1(test));
135
- const mode = getMode$1(test);
136
- const state = Option.match(Option.fromNullishOr(result), {
137
- onNone: () => void 0,
138
- onSome: (r) => Option.match(recordOption$1(r), {
139
- onNone: () => void 0,
140
- onSome: (rec) => getState$1(rec["state"])
141
- })
142
- });
143
- return toTestStatus$1(state, mode);
144
- };
145
- const extractDuration$1 = (test) => Option.match(getResult$1(test), {
146
- onNone: () => 0,
147
- onSome: (result) => Option.match(recordOption$1(result), {
148
- onNone: () => 0,
149
- onSome: (rec) => getDuration$1(rec)
150
- })
151
- });
152
- const extractFileName$1 = (test) => Option.match(getFile$1(test), {
153
- onNone: () => void 0,
154
- onSome: (file) => getFilepath$1(file)
155
- });
156
- const extractRawId$1 = (test, projectRoot) => normalizeTestIdRaw$1(toRawTestIdRaw$1(test), projectRoot);
157
- const extractName$1 = (test) => collectTestNameRaw$1(test);
158
- const extractFailureMessage$1 = (test) => Option.match(getResult$1(test), {
159
- onNone: () => "StrykerJS: Unknown test failure",
160
- onSome: (result) => Option.match(getErrors$1(result), {
161
- onNone: () => "StrykerJS: Unknown test failure",
162
- onSome: (errs) => Match.value(errs.length > 0).pipe(Match.when(true, () => Option.match(Option.fromNullishOr(errs[0]), {
163
- onNone: () => "StrykerJS: Unknown test failure",
164
- onSome: (first) => Option.getOrElse(getMessage$1(first), () => "StrykerJS: Unknown test failure")
165
- })), Match.when(false, () => "StrykerJS: Unknown test failure"), Match.exhaustive)
166
- })
167
- });
168
- const convertTestRaw$1 = (test, projectRoot) => {
169
- const status = extractStatus$1(test);
170
- const base = {
171
- id: extractRawId$1(test, projectRoot),
172
- name: extractName$1(test),
173
- timeSpentMs: extractDuration$1(test),
174
- fileName: extractFileName$1(test),
175
- status
176
- };
177
- return Match.value(status).pipe(Match.when("failed", () => ({
178
- ...base,
179
- status,
180
- failureMessage: extractFailureMessage$1(test)
181
- })), Match.when("skipped", () => Match.value(findSuiteErrorRaw$1(Option.getOrUndefined(getSuite$1(test)))).pipe(Match.when(Match.defined, (suiteError) => ({
182
- ...base,
183
- status: "failed",
184
- failureMessage: suiteError
185
- })), Match.orElse(() => ({
186
- ...base,
187
- status
188
- })))), Match.orElse(() => ({
189
- ...base,
190
- status
191
- })));
192
- };
193
- /**
194
- * A run whose tests all passed yet reported an error outside the test files: the shape a dry
195
- * run reports as `Error` rather than `Complete`.
196
- */
197
- const isSilentExternalError = (tests, command) => Match.value(tests.some((test) => test.status === "failed")).pipe(Match.when(true, () => false), Match.when(false, () => command.hasExternalError), Match.exhaustive);
198
- const decideVitestDryRun$1 = (command) => Match.value(command.rawTests.map((t) => convertTestRaw$1(t, command.projectRoot))).pipe(Match.when((tests) => isSilentExternalError(tests, command), (tests) => Result.succeed(VitestDryRunOutput.make({
199
- status: "Error",
200
- testsJson: JSON.stringify(tests),
201
- errorMessage: `An error occurred outside of a test run: ${command.externalErrorText}`
202
- }))), Match.orElse((tests) => Result.succeed(VitestDryRunOutput.make({
203
- status: "Complete",
204
- testsJson: JSON.stringify(tests),
205
- errorMessage: void 0
206
- }))));
207
- const hitLimitReason = (hitCount, hitLimit) => Option.flatMap(Option.fromNullishOr(hitCount), (count) => Option.flatMap(Option.fromNullishOr(hitLimit), (limit) => Match.value(count > limit).pipe(Match.when(true, () => Option.some(`Hit limit reached (${count}/${limit})`)), Match.when(false, () => Option.none()), Match.exhaustive)));
208
- const decideVitestMutantRun = (command) => Match.value(hitLimitReason(command.hitCount, command.hitLimit)).pipe(Match.when(Option.isSome, (hit) => Result.succeed(MutantTimeout.make({
209
- testsJson: "[]",
210
- reason: hit.value
211
- }))), Match.when(Option.isNone, () => {
212
- const dryOut = decideVitestDryRun$1(VitestDryRunCommand$1.make({
213
- rawTests: command.rawTests,
214
- projectRoot: command.projectRoot,
215
- hasExternalError: command.hasExternalError,
216
- externalErrorText: command.externalErrorText
217
- }));
218
- return Match.value(Result.isFailure(dryOut)).pipe(Match.when(true, () => Result.fail(new VitestMutantRunError({ message: "dry run mapping failed" }))), Match.when(false, () => {
219
- const dry = Result.getOrElse(dryOut, () => VitestDryRunOutput.make({
220
- status: "Complete",
221
- testsJson: "[]",
222
- errorMessage: void 0
223
- }));
224
- return Match.value(dry.status === "Error").pipe(Match.when(true, () => Result.succeed(MutantDryError.make({
225
- testsJson: "[]",
226
- errorMessage: dry.errorMessage
227
- }))), Match.when(false, () => {
228
- const testsOption = Option.liftThrowable((input) => JSON.parse(input))(dry.testsJson).pipe(Option.filter((v) => Array.isArray(v)));
229
- const killed = Option.getOrElse(testsOption, () => []).filter((t) => t.status === "failed");
230
- return Match.value(killed.length > 0).pipe(Match.when(true, () => Match.value(command.reportAllKillers).pipe(Match.when(true, () => {
231
- const firstKiller = Option.fromNullishOr(killed[0]);
232
- const failureMessage = Option.match(firstKiller, {
233
- onNone: () => void 0,
234
- onSome: (k) => k.failureMessage
235
- });
236
- return Result.succeed(MutantKilled.make({
237
- testsJson: dry.testsJson,
238
- killerIds: killed.map((t) => t.id),
239
- failureMessage
240
- }));
241
- }), Match.when(false, () => {
242
- const first = Option.fromNullishOr(killed[0]);
243
- const failureMessage = Option.match(first, {
244
- onNone: () => void 0,
245
- onSome: (k) => k.failureMessage
246
- });
247
- return Match.value(Option.isSome(first)).pipe(Match.when(true, () => Result.succeed(MutantKilled.make({
248
- testsJson: dry.testsJson,
249
- killerIds: Option.match(first, {
250
- onNone: () => void 0,
251
- onSome: (k) => [k.id]
252
- }),
253
- failureMessage
254
- }))), Match.when(false, () => Result.succeed(MutantKilled.make({
255
- testsJson: dry.testsJson,
256
- killerIds: Option.getOrUndefined(Option.match(first, {
257
- onNone: () => Option.none(),
258
- onSome: (k) => Option.some([k.id])
259
- })),
260
- failureMessage
261
- }))), Match.exhaustive);
262
- }), Match.exhaustive)), Match.when(false, () => Result.succeed(MutantSurvived.make({ testsJson: dry.testsJson }))), Match.exhaustive);
263
- }), Match.exhaustive);
264
- }), Match.exhaustive);
265
- }), Match.exhaustive);
266
- const interpretVitestRun = Workflow.make(VitestMutantRunCommand, decideVitestMutantRun);
267
- //#endregion
3
+ import * as Predicate from "effect/Predicate";
268
4
  //#region src/Runner.schema.ts
269
5
  const VitestRunnerOptionsSchema = S.Struct({
270
6
  dir: S.optional(S.String),
271
7
  related: S.optional(S.Boolean).pipe(S.withDecodingDefault(Effect.succeed(true))),
272
8
  configFile: S.optional(S.String)
273
9
  });
274
- const VitestSectionSchema = S.optional(VitestRunnerOptionsSchema).pipe(S.withDecodingDefault(Effect.succeed({ related: true })));
275
- const HitCountMetaSchema = S.Struct({ hitCount: S.optional(S.Finite) });
276
- const MutantCoverageMetaSchema = S.Struct({ mutantCoverage: S.optional(S.Struct({
10
+ S.optional(VitestRunnerOptionsSchema).pipe(S.withDecodingDefault(Effect.succeed({ related: true })));
11
+ S.Struct({ hitCount: S.optional(S.Finite) });
12
+ S.Struct({ mutantCoverage: S.optional(S.Struct({
277
13
  static: S.Record(S.String, S.Finite),
278
14
  perTest: S.Record(S.String, S.Record(S.String, S.Finite))
279
15
  })) });
280
- const MutantCoverageShapeSchema = S.Struct({
16
+ S.Struct({
281
17
  static: S.Record(S.String, S.Finite),
282
18
  perTest: S.Record(S.String, S.Record(S.String, S.Finite))
283
19
  });
284
- var CoverageDecodeFailed = class extends S.TaggedError()("CoverageDecodeFailed", { cause: S.Unknown }) {};
20
+ S.TaggedError()("CoverageDecodeFailed", { cause: S.Unknown });
285
21
  const ExportEntry = S.Union([S.String, S.Record(S.String, S.Unknown)]);
286
- const PackageManifest = S.StructWithRest(S.Struct({
22
+ S.StructWithRest(S.Struct({
287
23
  name: S.optional(S.String),
288
24
  exports: S.optional(S.Record(S.String, ExportEntry))
289
25
  }), [S.Record(S.String, S.Unknown)]);
290
- const VitestNodeModuleSchema = S.declare((input) => Predicate.isObject(input), { description: "The project-local vitest/node module" });
291
- const VitestPackageSchema = S.Struct({ version: S.String });
292
- var VitestDryRunCommand = class extends S.TaggedClass()("VitestDryRunCommand", {
26
+ S.declare((input) => Predicate.isObject(input), { description: "The project-local vitest/node module" });
27
+ S.Struct({ version: S.String });
28
+ S.TaggedClass()("VitestDryRunCommand", {
293
29
  rawTests: S.Array(S.Unknown),
294
30
  projectRoot: S.String,
295
31
  hasExternalError: S.Boolean,
296
32
  externalErrorText: S.String
297
- }) {};
298
- var DryRunComplete = class extends S.TaggedClass()("Complete", { testsJson: S.String }) {};
299
- var DryRunExternalError = class extends S.TaggedClass()("Error", {
33
+ });
34
+ S.TaggedClass()("Complete", { testsJson: S.String });
35
+ S.TaggedClass()("Error", {
300
36
  testsJson: S.String,
301
37
  errorMessage: S.String
302
- }) {};
303
- //#endregion
304
- //#region src/Runner.ts
305
- var VitestHarness = class extends Context.Service()("VitestHarness") {};
306
- function fromTestId(id) {
307
- const [file, ...name] = id.split("#");
308
- return {
309
- file,
310
- test: name.join("#")
311
- };
312
- }
313
- function normalizeTestId(id, projectRoot, pathService) {
314
- const { file, test } = fromTestId(id);
315
- return `${normalizeFileName(pathService.relative(projectRoot, file))}#${test}`;
316
- }
317
- function normalizeCoverage(rawCoverage, projectRoot, pathService) {
318
- return {
319
- perTest: Object.fromEntries(Object.entries(rawCoverage.perTest).map(([rawTestId, coverageData]) => [normalizeTestId(rawTestId, projectRoot, pathService), coverageData])),
320
- static: rawCoverage.static
321
- };
322
- }
323
- function collectTestsFromSuite(suite) {
324
- return suite.tasks.flatMap((task) => {
325
- if (task.type === "suite") return collectTestsFromSuite(task);
326
- return task;
327
- });
328
- }
329
- function isErrorCodeError(error) {
330
- return error instanceof Error && typeof Reflect.get(error, "code") === "string";
331
- }
332
- const VITEST_ERROR_CODES = Object.freeze({ FILES_NOT_FOUND: "VITEST_FILES_NOT_FOUND" });
333
- const recordOption = (value) => S.decodeUnknownOption(S.Record(S.String, S.Unknown))(value);
334
- const getStringField = (record, key) => Option.fromNullishOr(record[key]).pipe(Option.filter((v) => typeof v === "string"));
335
- const getNumberField = (record, key) => Option.fromNullishOr(record[key]).pipe(Option.filter((v) => typeof v === "number"));
336
- const getSuite = (value) => recordOption(value).pipe(Option.flatMap((rec) => Option.fromNullishOr(rec["suite"])));
337
- const getFile = (value) => recordOption(value).pipe(Option.flatMap((rec) => Option.fromNullishOr(rec["file"])));
338
- const getResult = (value) => recordOption(value).pipe(Option.flatMap((rec) => Option.fromNullishOr(rec["result"])));
339
- const getErrors = (value) => recordOption(value).pipe(Option.flatMap((rec) => Option.fromNullishOr(rec["errors"])), Option.filter((v) => Array.isArray(v)));
340
- const getMessage = (value) => recordOption(value).pipe(Option.flatMap((rec) => Option.fromNullishOr(rec["message"])), Option.filter((v) => typeof v === "string"));
341
- const getName = (value) => Option.match(recordOption(value), {
342
- onNone: () => "",
343
- onSome: (rec) => Option.getOrElse(getStringField(rec, "name"), () => "")
344
- });
345
- const getMode = (value) => Option.match(recordOption(value), {
346
- onNone: () => "run",
347
- onSome: (rec) => Option.getOrElse(getStringField(rec, "mode"), () => "run")
348
- });
349
- const getState = (value) => Match.value(value).pipe(Match.when("pass", () => "pass"), Match.when("fail", () => "fail"), Match.when("skip", () => "skip"), Match.when("todo", () => "todo"), Match.when("run", () => "run"), Match.when("queued", () => "queued"), Match.when("only", () => "only"), Match.when(void 0, () => void 0), Match.orElse(() => void 0));
350
- const getDuration = (value) => Option.match(recordOption(value), {
351
- onNone: () => 0,
352
- onSome: (rec) => Option.getOrElse(getNumberField(rec, "duration"), () => 0)
353
- });
354
- const getFilepath = (value) => Option.match(recordOption(value), {
355
- onNone: () => void 0,
356
- onSome: (rec) => Option.getOrUndefined(Option.fromNullishOr(rec["filepath"]).pipe(Option.filter((v) => typeof v === "string")))
357
- });
358
- const collectSuiteNames = (suite) => Option.match(Option.fromNullishOr(suite), {
359
- onNone: () => [],
360
- onSome: (current) => Option.match(recordOption(current), {
361
- onNone: () => [],
362
- onSome: (rec) => {
363
- const name = Option.getOrElse(getStringField(rec, "name"), () => "");
364
- const hasName = name.length > 0;
365
- const parentNames = collectSuiteNames(rec["suite"]);
366
- return Match.value(hasName).pipe(Match.when(true, () => [...parentNames, name]), Match.when(false, () => parentNames), Match.exhaustive);
367
- }
368
- })
369
- });
370
- const collectTestNameRaw = (test) => {
371
- const name = getName(test);
372
- const suite = Option.getOrUndefined(getSuite(test));
373
- return [...collectSuiteNames(suite), name].join(" ").trim();
374
- };
375
- const toRawTestIdRaw = (test) => {
376
- return `${Option.match(getFile(test), {
377
- onNone: () => "unknown.js",
378
- onSome: (file) => Option.getOrElse(Option.fromNullishOr(getFilepath(file)), () => "unknown.js")
379
- })}#${collectTestNameRaw(test)}`;
380
- };
381
- const normalizeTestIdRaw = (id, projectRoot) => {
382
- const hash = id.indexOf("#");
383
- if (hash === -1) return id;
384
- const file = id.slice(0, hash);
385
- const rest = id.slice(hash + 1);
386
- return `${(() => {
387
- if (file.startsWith(projectRoot)) return file.slice(projectRoot.length);
388
- return file;
389
- })().replace(/^[/\\]+/, "").replaceAll("\\", "/")}#${rest}`;
390
- };
391
- const toTestStatus = (taskState, mode) => Match.value(mode === "skip").pipe(Match.when(true, () => "skipped"), Match.when(false, () => Match.value(taskState).pipe(Match.when("pass", () => "success"), Match.when("skip", () => "skipped"), Match.when("todo", () => "skipped"), Match.orElse(() => "failed"))), Match.exhaustive);
392
- const findSuiteErrorRaw = (suite) => Option.match(Option.fromNullishOr(suite), {
393
- onNone: () => void 0,
394
- onSome: (current) => Option.match(recordOption(current), {
395
- onNone: () => void 0,
396
- onSome: (rec) => {
397
- const maybeError = Option.flatMap(getResult(rec), (result) => Option.flatMap(getErrors(result), (errs) => Match.value(errs.length > 0).pipe(Match.when(true, () => Option.flatMap(Option.fromNullishOr(errs[0]), (first) => getMessage(first))), Match.when(false, () => Option.none()), Match.exhaustive)));
398
- return Option.match(maybeError, {
399
- onNone: () => findSuiteErrorRaw(rec["suite"]),
400
- onSome: (msg) => msg
401
- });
402
- }
403
- })
404
- });
405
- const extractStatus = (test) => {
406
- const result = Option.getOrUndefined(getResult(test));
407
- const mode = getMode(test);
408
- const state = Option.match(Option.fromNullishOr(result), {
409
- onNone: () => void 0,
410
- onSome: (r) => Option.match(recordOption(r), {
411
- onNone: () => void 0,
412
- onSome: (rec) => getState(rec["state"])
413
- })
414
- });
415
- return toTestStatus(state, mode);
416
- };
417
- const extractDuration = (test) => Option.match(getResult(test), {
418
- onNone: () => 0,
419
- onSome: (result) => Option.match(recordOption(result), {
420
- onNone: () => 0,
421
- onSome: (rec) => getDuration(rec)
422
- })
423
- });
424
- const extractFileName = (test) => Option.match(getFile(test), {
425
- onNone: () => void 0,
426
- onSome: (file) => getFilepath(file)
427
38
  });
428
- const extractRawId = (test, projectRoot) => normalizeTestIdRaw(toRawTestIdRaw(test), projectRoot);
429
- const extractName = (test) => collectTestNameRaw(test);
430
- const extractFailureMessage = (test) => Option.match(getResult(test), {
431
- onNone: () => "StrykerJS: Unknown test failure",
432
- onSome: (result) => Option.match(getErrors(result), {
433
- onNone: () => "StrykerJS: Unknown test failure",
434
- onSome: (errs) => Match.value(errs.length > 0).pipe(Match.when(true, () => Option.match(Option.fromNullishOr(errs[0]), {
435
- onNone: () => "StrykerJS: Unknown test failure",
436
- onSome: (first) => Option.getOrElse(getMessage(first), () => "StrykerJS: Unknown test failure")
437
- })), Match.when(false, () => "StrykerJS: Unknown test failure"), Match.exhaustive)
438
- })
439
- });
440
- const convertTestRaw = (test, projectRoot) => {
441
- const status = extractStatus(test);
442
- const base = {
443
- id: extractRawId(test, projectRoot),
444
- name: extractName(test),
445
- timeSpentMs: extractDuration(test),
446
- fileName: extractFileName(test),
447
- status
448
- };
449
- return Match.value(status).pipe(Match.when("failed", () => ({
450
- ...base,
451
- status,
452
- failureMessage: extractFailureMessage(test)
453
- })), Match.when("skipped", () => Match.value(findSuiteErrorRaw(Option.getOrUndefined(getSuite(test)))).pipe(Match.when(Match.defined, (suiteError) => ({
454
- ...base,
455
- status: "failed",
456
- failureMessage: suiteError
457
- })), Match.orElse(() => ({
458
- ...base,
459
- status
460
- })))), Match.orElse(() => ({
461
- ...base,
462
- status
463
- })));
464
- };
465
- const decideVitestDryRun = (command) => {
466
- const tests = command.rawTests.map((t) => convertTestRaw(t, command.projectRoot));
467
- const testsJson = JSON.stringify(tests);
468
- const hasFailure = tests.some((t) => t.status === "failed");
469
- return Match.value(hasFailure).pipe(Match.when(true, () => DryRunComplete.make({ testsJson })), Match.orElse(() => Match.value(command.hasExternalError).pipe(Match.when(true, () => DryRunExternalError.make({
470
- testsJson,
471
- errorMessage: `An error occurred outside of a test run: ${command.externalErrorText}`
472
- })), Match.orElse(() => DryRunComplete.make({ testsJson })))));
473
- };
474
- const TYPESCRIPT_SOURCE_EXTENSIONS = [
475
- ".ts",
476
- ".tsx",
477
- ".mts"
478
- ];
479
- const isTypescriptSourcePath = (filePath) => TYPESCRIPT_SOURCE_EXTENSIONS.some((extension) => filePath.endsWith(extension));
480
- const typescriptSourcePath = (filePath) => Option.getOrUndefined(Option.filter(Option.some(filePath), isTypescriptSourcePath));
481
- const sourceTargetOf = (entry) => Match.value(entry).pipe(Match.when(Match.string, (filePath) => typescriptSourcePath(filePath)), Match.orElse(() => void 0));
482
- const subpathSpecifier = (packageName, exportKey) => Match.value(exportKey.startsWith("./")).pipe(Match.when(true, () => `${packageName}/${exportKey.slice(2)}`), Match.orElse(() => void 0));
483
- const specifierForExport = (packageName, exportKey) => Match.value(exportKey).pipe(Match.when(".", () => packageName), Match.when("./package.json", () => void 0), Match.orElse((key) => subpathSpecifier(packageName, key)));
484
- const namedExports = (manifest) => Option.flatMap(Option.filter(Option.fromNullishOr(manifest.name), (name) => name.length > 0), (name) => Option.map(Option.fromNullishOr(manifest.exports), (exportMap) => ({
485
- name,
486
- exports: exportMap
487
- })));
488
- const exportAlias = (packageName, projectRoot, pathService, [exportKey, entry]) => Option.flatMap(Option.fromNullishOr(specifierForExport(packageName, exportKey)), (spec) => Option.map(Option.fromNullishOr(sourceTargetOf(entry)), (target) => ({
489
- find: new RegExp(`^${RegExp.escape(spec)}$`),
490
- replacement: pathService.resolve(projectRoot, target)
491
- })));
492
- const sandboxSelfAliases = (manifest, projectRoot, pathService) => Option.match(namedExports(manifest), {
493
- onNone: () => [],
494
- onSome: ({ name, exports: exportMap }) => Object.entries(exportMap).flatMap((entry) => Option.toArray(exportAlias(name, projectRoot, pathService, entry)))
495
- });
496
- const parseJson = (text) => {
497
- try {
498
- return JSON.parse(text);
499
- } catch {
500
- return;
501
- }
502
- };
503
- const readSandboxSelfAliases = (projectRoot) => Effect$1.gen(function* () {
504
- const fs = yield* FileSystem.FileSystem;
505
- const pathService = yield* Path.Path;
506
- const raw = yield* fs.readFileString(pathService.join(projectRoot, "package.json")).pipe(Effect$1.orElseSucceed(() => null));
507
- return Option.match(Option.flatMap(Option.fromNullishOr(raw), (content) => S.decodeUnknownOption(PackageManifest)(parseJson(content))), {
508
- onNone: () => [],
509
- onSome: (manifest) => sandboxSelfAliases(manifest, projectRoot, pathService)
510
- });
511
- });
512
- const sandboxSelfPlugin = (aliases) => ({
513
- name: "stryker-sandbox-self-exports",
514
- enforce: "pre",
515
- resolveId(source) {
516
- return Option.getOrUndefined(Option.map(Option.fromNullishOr(aliases.find((alias) => alias.find.test(source))), (alias) => alias.replacement));
517
- }
518
- });
519
- const isRunnerTestSuite = (value) => Predicate.isObject(value) && Array.isArray(value["tasks"]);
520
- const STRYKER_SETUP_URL = new URL("./stryker-setup.mjs", import.meta.url);
521
- const resolveVitest = (_dir) => Effect$1.gen(function* () {
522
- const fallback = Effect$1.gen(function* () {
523
- const pathService = yield* Path.Path;
524
- const fs = yield* FileSystem.FileSystem;
525
- const urlString = import.meta.resolve("vitest/package.json");
526
- const packageJsonPath = yield* pathService.fromFileUrl(new URL(urlString));
527
- const content = yield* fs.readFileString(packageJsonPath);
528
- const parsed = JSON.parse(content);
529
- const decoded = yield* S.decodeUnknownEffect(VitestPackageSchema)(parsed);
530
- return {
531
- createVitest,
532
- version: decoded.version
533
- };
534
- }).pipe(Effect$1.orDie);
535
- return yield* Effect$1.gen(function* () {
536
- const module = yield* Module;
537
- const pathService = yield* Path.Path;
538
- const fs = yield* FileSystem.FileSystem;
539
- const requireFromProject = module.createRequire(pathService.join(_dir, "package.json"));
540
- const imported = requireFromProject("vitest/node");
541
- const decodedNode = yield* S.decodeUnknownEffect(VitestNodeModuleSchema)(imported);
542
- const packageJsonPath = requireFromProject.resolve("vitest/package.json");
543
- const content = yield* fs.readFileString(packageJsonPath);
544
- const parsed = JSON.parse(content);
545
- const decodedPackage = yield* S.decodeUnknownEffect(VitestPackageSchema)(parsed);
546
- return {
547
- createVitest: decodedNode.createVitest,
548
- version: decodedPackage.version
549
- };
550
- }).pipe(Effect$1.catchCause(() => fallback), Effect$1.catchDefect(() => fallback));
551
- }).pipe(Effect$1.orDie);
552
- const versionPart = (parts, index) => Option.getOrElse(Option.map(Option.fromNullishOr(parts[index]), (part) => Number(part)), () => 0);
553
- const minimumMinorForMajor = (major) => Match.value(major).pipe(Match.when((value) => value > 4, () => Option.some(0)), Match.when(4, () => Option.some(1)), Match.orElse(() => Option.none()));
554
- const shouldUseSuiteMetaSecondArg = (version) => {
555
- const parts = version.split(".");
556
- const major = versionPart(parts, 0);
557
- const minor = versionPart(parts, 1);
558
- return Match.value(Number.isNaN(major) || Number.isNaN(minor)).pipe(Match.when(true, () => false), Match.orElse(() => Option.exists(minimumMinorForMajor(major), (minimumMinor) => minor >= minimumMinor)));
559
- };
560
- const relatedFilesOf = (relatedValue, relatedFiles) => Match.value(relatedValue !== false).pipe(Match.when(true, () => Option.getOrUndefined(Option.map(Option.fromNullishOr(relatedFiles), (files) => files.map(normalizeFileName)))), Match.orElse(() => void 0));
561
- const testIdPlan = (testIds, projectRoot, pathService) => Option.map(Option.filter(Option.fromNullishOr(testIds), (ids) => ids.length > 0), (ids) => ({
562
- testNamePattern: new RegExp(ids.map((id) => RegExp.escape(fromTestId(id).test)).join("|")),
563
- testFiles: ids.map((id) => pathService.resolve(projectRoot, fromTestId(id).file))
564
- }));
565
- const runFilterPlan = (filter, projectRoot, pathService) => {
566
- const plan = testIdPlan(filter.testIds, projectRoot, pathService);
567
- return {
568
- testNamePattern: Option.getOrUndefined(Option.map(plan, (value) => value.testNamePattern)),
569
- testFiles: Option.match(plan, {
570
- onNone: () => Option.getOrUndefined(Option.map(Option.fromNullishOr(filter.testFiles), (files) => [...files])),
571
- onSome: (value) => value.testFiles
572
- })
573
- };
574
- };
575
- const isMissingTestFilesCause = (cause) => Match.value(isErrorCodeError(cause)).pipe(Match.when(true, () => typeof cause === "string" && cause.includes(VITEST_ERROR_CODES.FILES_NOT_FOUND)), Match.orElse(() => false));
576
- const experimentalStateGetFiles = (vitest) => vitest.state.getFiles();
577
- const propertyOf = (value, key) => Option.flatMap(Option.filter(Option.fromNullishOr(value), Predicate.isObject), (record) => Option.fromNullishOr(record[key]));
578
- const vitestStateOf = (vitest) => propertyOf(vitest, "state");
579
- const errorsSetOf = (vitest) => Option.flatMap(vitestStateOf(vitest), (state) => propertyOf(state, "errorsSet"));
580
- const invokeMethod = (holder, name) => Option.match(Option.filter(propertyOf(holder, name), Predicate.isFunction), {
581
- onNone: () => void 0,
582
- onSome: (method) => {
583
- Reflect.apply(method, holder, []);
584
- }
585
- });
586
- const clearFilesMap = (filesMap) => Match.value(filesMap).pipe(Match.when(Match.instanceOf(Map), (map) => {
587
- map.clear();
588
- }), Match.orElse((value) => invokeMethod(value, "clear")));
589
- const experimentalStateClearFiles = (vitest) => Option.match(Option.flatMap(vitestStateOf(vitest), (state) => propertyOf(state, "filesMap")), {
590
- onNone: () => void 0,
591
- onSome: (filesMap) => clearFilesMap(filesMap)
592
- });
593
- const entryCountOf = (collection) => Match.value(collection).pipe(Match.when(Match.instanceOf(Set), (set) => Option.some(set.size)), Match.orElse((value) => Option.filter(propertyOf(value, "size"), Predicate.isNumber)));
594
- const experimentalStateHasExternalErrors = (vitest) => Option.exists(Option.flatMap(errorsSetOf(vitest), entryCountOf), (count) => count > 0);
595
- const experimentalStateGetExternalErrorText = (vitest) => Option.match(errorsSetOf(vitest), {
596
- onNone: () => "",
597
- onSome: (errorsSet) => Match.value(errorsSet).pipe(Match.when(Predicate.isIterable, (errors) => [...errors].map(errorToString).join("\n")), Match.orElse(() => ""))
598
- });
599
- const applyHarnessValue = (ctx, key, value) => Match.value(key).pipe(Match.when("hitLimit", () => {
600
- ctx.provide("hitLimit", Option.getOrUndefined(Option.filter(Option.fromNullishOr(value), Predicate.isNumber)));
601
- }), Match.when("mutantActivation", () => {
602
- Match.value(value).pipe(Match.when(Match.is("runtime", "static"), (activation) => {
603
- ctx.provide("mutantActivation", activation);
604
- }), Match.orElse(() => void 0));
605
- }), Match.orElse(() => {
606
- Match.value(value).pipe(Match.when(Predicate.isString, (activeMutant) => {
607
- ctx.provide("activeMutant", activeMutant);
608
- }), Match.orElse(() => void 0));
609
- }));
610
- const applyRunFilterToConfig = (vitest, options) => {
611
- Reflect.set(vitest.config, "related", options.related);
612
- for (const project of vitest.projects) Reflect.set(project.config, "testNamePattern", options.testNamePattern);
613
- };
614
- const disableScreenshotFailures = (value) => Option.match(Option.filter(Option.fromNullishOr(value), Predicate.isObject), {
615
- onNone: () => void 0,
616
- onSome: (browser) => {
617
- Reflect.set(browser, "screenshotFailures", false);
618
- }
619
- });
620
- const setupFilePathsOf = (value) => Match.value(value).pipe(Match.when(Array.isArray, (setupFiles) => setupFiles.filter(Predicate.isString)), Match.orElse(() => []));
621
- const applySetupFilesToProjects = (vitest, localSetupFile) => {
622
- disableScreenshotFailures(Reflect.get(vitest.config, "browser"));
623
- for (const project of vitest.projects) {
624
- const setupFiles = setupFilePathsOf(Reflect.get(project.config, "setupFiles"));
625
- Reflect.set(project.config, "setupFiles", [localSetupFile, ...setupFiles]);
626
- disableScreenshotFailures(Reflect.get(project.config, "browser"));
627
- }
628
- };
629
- const makeVitestRunnerLayer = (input) => Layer.effect(TestRunner, Effect$1.gen(function* () {
630
- const stateRef = yield* Ref.make({
631
- ctx: void 0,
632
- localSetupFile: void 0
633
- });
634
- const fsService = yield* FileSystem.FileSystem;
635
- const pathService = yield* Path.Path;
636
- const moduleService = yield* Module;
637
- const getState = Ref.get(stateRef);
638
- const requireCtx = Effect$1.gen(function* () {
639
- const state = yield* getState;
640
- if (state.ctx === void 0) return yield* new TestRunnerFailed({
641
- runnerName: "vitest",
642
- phase: "dryRun",
643
- cause: errorToString(/* @__PURE__ */ new Error("Vitest runner is not initialized; call init() before running tests"))
644
- });
645
- return state.ctx;
646
- });
647
- const decodedOptionsEffect = (raw) => S.decodeUnknownEffect(VitestSectionSchema)(raw).pipe(Effect$1.map((decoded) => (() => {
648
- if (decoded === void 0) return { related: true };
649
- return decoded;
650
- })()), Effect$1.mapError((cause) => new TestRunnerFailed({
651
- runnerName: "vitest",
652
- phase: "init",
653
- cause: errorToString(cause)
654
- })));
655
- const optionsEffect = decodedOptionsEffect(Reflect.get(input.options, "vitest")).pipe(Effect$1.map((vitestOptions) => ({
656
- ...input.options,
657
- vitest: vitestOptions
658
- })));
659
- const capabilities = Effect$1.succeed({ reloadEnvironment: true });
660
- const init = Effect$1.gen(function* () {
661
- const options = yield* optionsEffect;
662
- yield* Effect$1.sync(() => {
663
- process.env.NODE_ENV = "test";
664
- process.env.VITEST = "1";
665
- });
666
- const projectRoot = input.sandboxDirectory;
667
- const localSetupFile = pathService.resolve(projectRoot, `stryker-setup-${process.pid}.js`);
668
- yield* Ref.update(stateRef, (s) => ({
669
- ...s,
670
- localSetupFile
671
- }));
672
- const defaultSetupPath = yield* pathService.fromFileUrl(STRYKER_SETUP_URL).pipe(Effect$1.mapError((cause) => new TestRunnerFailed({
673
- runnerName: "vitest",
674
- phase: "init",
675
- cause: errorToString(cause)
676
- })));
677
- const setupFilePath = Option.getOrElse(Option.fromNullishOr(input.setupFilePath), () => defaultSetupPath);
678
- yield* fsService.copyFile(setupFilePath, localSetupFile).pipe(Effect$1.mapError((cause) => new TestRunnerFailed({
679
- runnerName: "vitest",
680
- phase: "init",
681
- cause: errorToString(cause)
682
- })));
683
- const { createVitest, version } = yield* Option.getOrElse(Option.fromNullishOr(input.resolveVitestFor), () => resolveVitest)(projectRoot).pipe(Effect$1.provideService(Module, moduleService), Effect$1.provideService(FileSystem.FileSystem, fsService), Effect$1.provideService(Path.Path, pathService), Effect$1.catchDefect((cause) => Effect$1.fail(new TestRunnerFailed({
684
- runnerName: "vitest",
685
- phase: "init",
686
- cause: errorToString(cause)
687
- }))));
688
- const namespace = Option.getOrElse(Option.fromNullishOr(input.globalNamespace), () => INSTRUMENTER_CONSTANTS.NAMESPACE);
689
- const scanDir = (() => {
690
- if (typeof options.vitest.dir === "string") return pathService.resolve(projectRoot, options.vitest.dir);
691
- })();
692
- const aliases = yield* readSandboxSelfAliases(projectRoot).pipe(Effect$1.provideService(FileSystem.FileSystem, fsService), Effect$1.provideService(Path.Path, pathService));
693
- const plugin = sandboxSelfPlugin(aliases);
694
- const ctx = yield* Effect$1.tryPromise({
695
- try: () => createVitest("test", {
696
- config: options.vitest.configFile,
697
- coverage: { enabled: false },
698
- maxWorkers: 1,
699
- maxConcurrency: 1,
700
- watch: false,
701
- root: projectRoot,
702
- ...(() => {
703
- if (scanDir === void 0) return {};
704
- return { dir: scanDir };
705
- })(),
706
- bail: (() => {
707
- if (options.disableBail) return 0;
708
- return 1;
709
- })(),
710
- onConsoleLog: () => false,
711
- silent: true,
712
- reporters: [{ onInit(_vitest) {} }]
713
- }, {
714
- resolve: {
715
- alias: [...aliases],
716
- conditions: ["import"]
717
- },
718
- plugins: [plugin]
719
- }),
720
- catch: (cause) => new TestRunnerFailed({
721
- runnerName: "vitest",
722
- phase: "init",
723
- cause: errorToString(cause)
724
- })
725
- });
726
- ctx.provide("globalNamespace", namespace);
727
- ctx.provide("isGreaterThanVitest4Point1", shouldUseSuiteMetaSecondArg(version));
728
- applySetupFilesToProjects(ctx, localSetupFile);
729
- yield* Ref.update(stateRef, (s) => ({
730
- ...s,
731
- ctx
732
- }));
733
- }).pipe(Effect$1.mapError((cause) => (() => {
734
- if (cause instanceof TestRunnerFailed) return cause;
735
- return new TestRunnerFailed({
736
- runnerName: "vitest",
737
- phase: "init",
738
- cause: errorToString(cause)
739
- });
740
- })()));
741
- const resetContext = Effect$1.gen(function* () {
742
- const ctx = yield* requireCtx;
743
- experimentalStateClearFiles(ctx);
744
- });
745
- const getFileMeta = (file) => Option.getOrUndefined(propertyOf(file, "meta"));
746
- const readHitCount = Effect$1.gen(function* () {
747
- const ctx = yield* requireCtx.pipe(Effect$1.mapError((cause) => new CoverageDecodeFailed({ cause })));
748
- return (yield* Effect$1.forEach(experimentalStateGetFiles(ctx), (file) => Effect$1.map(S.decodeUnknownEffect(HitCountMetaSchema)(getFileMeta(file)).pipe(Effect$1.mapError((cause) => new CoverageDecodeFailed({ cause })), Effect$1.orElseSucceed(() => ({ hitCount: void 0 }))), (decoded) => Option.getOrElse(Option.fromNullishOr(decoded.hitCount), () => 0)))).reduce((total, count) => total + count, 0);
749
- });
750
- const stringProperty = (value, key) => Option.getOrElse(Option.filter(propertyOf(value, key), Predicate.isString), () => "");
751
- const dedupeFilesByName = (files) => Object.fromEntries(files.map((file) => [`${stringProperty(file, "projectName")}-${stringProperty(file, "name")}`, file]));
752
- const validateCoverage = (mutantCoverage) => {
753
- const normalized = normalizeCoverage(mutantCoverage, input.sandboxDirectory, pathService);
754
- return S.decodeEffect(MutantCoverageShapeSchema)(normalized).pipe(Effect$1.mapError((cause) => new CoverageDecodeFailed({ cause })), Effect$1.map(() => normalized));
755
- };
756
- const coverageOfFile = (file) => Effect$1.gen(function* () {
757
- const decoded = yield* S.decodeUnknownEffect(MutantCoverageMetaSchema)(getFileMeta(file)).pipe(Effect$1.mapError((cause) => new CoverageDecodeFailed({ cause })), Effect$1.orElseSucceed(() => ({ mutantCoverage: void 0 })));
758
- return yield* Option.match(Option.fromNullishOr(decoded.mutantCoverage), {
759
- onNone: () => Effect$1.succeed(void 0),
760
- onSome: (mutantCoverage) => validateCoverage(mutantCoverage)
761
- });
762
- });
763
- const mergeTestCoverage = (perTest, testId, coverage) => Option.match(Option.fromNullishOr(perTest[testId]), {
764
- onNone: () => {
765
- perTest[testId] = coverage;
766
- },
767
- onSome: (existing) => {
768
- mergeCoverage(existing, coverage);
769
- }
770
- });
771
- const mergeProjectCoverage = (acc, projectCoverage) => {
772
- for (const [testId, testCoverage] of Object.entries(projectCoverage.perTest)) mergeTestCoverage(acc.perTest, testId, testCoverage);
773
- mergeCoverage(acc.static, projectCoverage.static);
774
- return acc;
775
- };
776
- const readMutantCoverage = Effect$1.gen(function* () {
777
- const ctx = yield* requireCtx.pipe(Effect$1.mapError((cause) => new CoverageDecodeFailed({ cause })));
778
- const files = Object.values(dedupeFilesByName(experimentalStateGetFiles(ctx)));
779
- const coverages = (yield* Effect$1.forEach(files, coverageOfFile)).filter(Predicate.isNotNullish);
780
- return Option.getOrUndefined(Option.map(Option.fromNullishOr(coverages[0]), (first) => coverages.slice(1).reduce(mergeProjectCoverage, first)));
781
- });
782
- const collectRaw = (filter) => Effect$1.gen(function* () {
783
- const ctx = yield* requireCtx;
784
- const options = yield* optionsEffect;
785
- yield* resetContext.pipe(Effect$1.mapError((cause) => new TestRunnerFailed({
786
- runnerName: "vitest",
787
- phase: "dryRun",
788
- cause: errorToString(cause)
789
- })));
790
- const vitestInRun = Reflect.get(options, "vitest");
791
- const related = relatedFilesOf(Reflect.get(vitestInRun, "related"), filter.relatedFiles);
792
- const plan = runFilterPlan(filter, input.sandboxDirectory, pathService);
793
- applyRunFilterToConfig(ctx, {
794
- related,
795
- testNamePattern: plan.testNamePattern
796
- });
797
- yield* Effect$1.tryPromise({
798
- try: () => ctx.start(plan.testFiles),
799
- catch: (cause) => new TestRunnerFailed({
800
- runnerName: "vitest",
801
- phase: "dryRun",
802
- cause: errorToString(cause)
803
- })
804
- }).pipe(Effect$1.catchIf((error) => isMissingTestFilesCause(error.cause), () => Effect$1.void));
805
- const rawTests = experimentalStateGetFiles(ctx).flatMap((file) => (() => {
806
- if (isRunnerTestSuite(file)) return collectTestsFromSuite(file);
807
- return [];
808
- })()).filter((test) => test.result !== void 0);
809
- const hasExternalError = experimentalStateHasExternalErrors(ctx);
810
- return {
811
- rawTests,
812
- hasExternalError,
813
- externalErrorText: Match.value(hasExternalError).pipe(Match.when(true, () => experimentalStateGetExternalErrorText(ctx)), Match.orElse(() => ""))
814
- };
815
- });
816
- const harnessImpl = {
817
- setMode: (mode) => Effect$1.gen(function* () {
818
- (yield* requireCtx).provide("mode", mode);
819
- }),
820
- provide: (key, value) => Effect$1.gen(function* () {
821
- const ctx = yield* requireCtx;
822
- applyHarnessValue(ctx, key, value);
823
- })
824
- };
825
- const mutantRunCell = Cell.layer({
826
- read: (command) => Effect$1.gen(function* () {
827
- const harness = yield* VitestHarness;
828
- yield* harness.setMode("mutant");
829
- yield* harness.provide("hitLimit", command.hitLimit);
830
- yield* harness.provide("mutantActivation", command.mutantActivation);
831
- yield* harness.provide("activeMutant", command.activeMutant.id);
832
- const { rawTests, hasExternalError, externalErrorText } = yield* collectRaw({
833
- testIds: (() => {
834
- if (command.testFilter !== void 0) return [...command.testFilter];
835
- })(),
836
- relatedFiles: [command.sandboxFileName]
837
- });
838
- const hitCount = yield* readHitCount.pipe(Effect$1.mapError((cause) => new TestRunnerFailed({
839
- runnerName: "vitest",
840
- phase: "mutantRun",
841
- cause: errorToString(cause)
842
- })), Effect$1.option, Effect$1.map(Option.getOrUndefined));
843
- const reportAllKillers = (() => {
844
- if (typeof input.options.disableBail === "boolean") return input.options.disableBail;
845
- return false;
846
- })();
847
- if (hitCount === void 0) return {
848
- rawTests,
849
- projectRoot: input.sandboxDirectory,
850
- hasExternalError,
851
- externalErrorText,
852
- hitLimit: command.hitLimit,
853
- reportAllKillers
854
- };
855
- return {
856
- rawTests,
857
- projectRoot: input.sandboxDirectory,
858
- hasExternalError,
859
- externalErrorText,
860
- hitCount,
861
- hitLimit: command.hitLimit,
862
- reportAllKillers
863
- };
864
- }),
865
- decode: (raw) => Result.succeed(new VitestMutantRunCommand({
866
- rawTests: raw.rawTests,
867
- projectRoot: raw.projectRoot,
868
- hasExternalError: raw.hasExternalError,
869
- externalErrorText: raw.externalErrorText,
870
- hitCount: raw.hitCount,
871
- hitLimit: raw.hitLimit,
872
- reportAllKillers: raw.reportAllKillers
873
- })),
874
- decide: interpretVitestRun,
875
- encode: (outcome) => Result.match(outcome, {
876
- onFailure: (e) => ({
877
- status: "error",
878
- errorMessage: e.message
879
- }),
880
- onSuccess: (out) => {
881
- const nrOfTests = () => countIdRecords(parseJson(out.testsJson));
882
- return Match.value(out).pipe(Match.tag("Error", (error) => ({
883
- status: "error",
884
- errorMessage: error.errorMessage ?? "unknown"
885
- })), Match.tag("Timeout", (timeout) => (() => {
886
- if (timeout.reason === void 0) return { status: "timeout" };
887
- return {
888
- status: "timeout",
889
- reason: timeout.reason
890
- };
891
- })()), Match.tag("Killed", (killed) => ({
892
- status: "killed",
893
- failureMessage: killed.failureMessage ?? "",
894
- killedBy: (() => {
895
- if (killed.killerIds !== void 0) return [...killed.killerIds];
896
- return [];
897
- })(),
898
- nrOfTests: nrOfTests()
899
- })), Match.tag("Survived", () => ({
900
- status: "survived",
901
- nrOfTests: nrOfTests()
902
- })), Match.exhaustive);
903
- }
904
- }),
905
- write: (output, _raw) => Effect$1.succeed(output)
906
- });
907
- const dryRunFilter = (options) => {
908
- const relatedFiles = Option.getOrUndefined(Option.map(Option.fromNullishOr(options.files), (files) => [...files]));
909
- return Match.value(testFilesProvided(options)).pipe(Match.when(true, () => ({
910
- testFiles: Option.getOrElse(Option.map(Option.fromNullishOr(options.testFiles), (files) => [...files]), () => []),
911
- relatedFiles
912
- })), Match.orElse(() => ({ relatedFiles })));
913
- };
914
- const completeDryRun = (testsJson) => Effect$1.gen(function* () {
915
- const tests = Match.value(parseJson(testsJson)).pipe(Match.when(Array.isArray, (entries) => entries.filter(isTestResultLike)), Match.orElse(() => []));
916
- const mutantCoverage = yield* readMutantCoverage.pipe(Effect$1.mapError((cause) => new TestRunnerFailed({
917
- runnerName: "vitest",
918
- phase: "dryRun",
919
- cause: errorToString(cause)
920
- })));
921
- return Match.value(mutantCoverage).pipe(Match.when(Match.defined, (coverage) => ({
922
- status: "complete",
923
- tests,
924
- mutantCoverage: coverage
925
- })), Match.orElse(() => ({
926
- status: "complete",
927
- tests
928
- })));
929
- });
930
- const dryRun = (options) => Effect$1.gen(function* () {
931
- yield* (yield* VitestHarness).setMode("dry-run");
932
- const filter = dryRunFilter(options);
933
- const { rawTests, hasExternalError, externalErrorText } = yield* collectRaw(filter);
934
- const decision = decideVitestDryRun(new VitestDryRunCommand({
935
- rawTests,
936
- projectRoot: input.sandboxDirectory,
937
- hasExternalError,
938
- externalErrorText
939
- }));
940
- return yield* Match.value(decision).pipe(Match.tag("Error", (error) => Effect$1.succeed({
941
- status: "error",
942
- errorMessage: error.errorMessage
943
- })), Match.tag("Complete", (complete) => completeDryRun(complete.testsJson)), Match.exhaustive);
944
- }).pipe(Effect$1.provideService(VitestHarness, harnessImpl), Effect$1.mapError((cause) => (() => {
945
- if (cause instanceof TestRunnerFailed) return cause;
946
- return new TestRunnerFailed({
947
- runnerName: "vitest",
948
- phase: "dryRun",
949
- cause: errorToString(cause)
950
- });
951
- })()));
952
- const mutantRun = (options) => mutantRunCell.run(options).pipe(Effect$1.provideService(VitestHarness, harnessImpl), Effect$1.mapError((cause) => (() => {
953
- if (cause instanceof TestRunnerFailed) return cause;
954
- return new TestRunnerFailed({
955
- runnerName: "vitest",
956
- phase: "mutantRun",
957
- cause: errorToString(cause)
958
- });
959
- })()));
960
- const disposeContext = (ctx, localSetupFile) => Effect$1.gen(function* () {
961
- Option.match(Option.fromNullishOr(localSetupFile), {
962
- onNone: () => void 0,
963
- onSome: (file) => {
964
- ctx.onClose(() => Effect$1.runPromise(fsService.remove(file, {
965
- recursive: true,
966
- force: true
967
- }).pipe(Effect$1.orElseSucceed(() => void 0))));
968
- }
969
- });
970
- yield* Effect$1.tryPromise({
971
- try: () => ctx.close(),
972
- catch: (cause) => new TestRunnerFailed({
973
- runnerName: "vitest",
974
- phase: "dispose",
975
- cause: errorToString(cause)
976
- })
977
- });
978
- });
979
- const dispose = Effect$1.gen(function* () {
980
- const state = yield* getState;
981
- return yield* Option.match(Option.fromNullishOr(state.ctx), {
982
- onNone: () => Effect$1.void,
983
- onSome: (ctx) => disposeContext(ctx, state.localSetupFile)
984
- });
985
- });
986
- return TestRunner.of({
987
- capabilities,
988
- init,
989
- dryRun,
990
- mutantRun,
991
- dispose
992
- });
993
- }));
994
- function isTestResultLike(value) {
995
- return Predicate.isObject(value) && typeof value["id"] === "string";
996
- }
997
- function countIdRecords(raw) {
998
- return Match.value(raw).pipe(Match.when(Array.isArray, (entries) => entries.filter(isTestResultLike).length), Match.orElse(() => 0));
999
- }
1000
- const mergeHitCount = (to, mutantId, hitCount) => Option.match(Option.fromNullishOr(to[mutantId]), {
1001
- onNone: () => {
1002
- to[mutantId] = hitCount;
1003
- },
1004
- onSome: (existing) => {
1005
- to[mutantId] = existing + hitCount;
1006
- }
1007
- });
1008
- function mergeCoverage(to, from) {
1009
- for (const [mutantId, hitCount] of Object.entries(from)) mergeHitCount(to, mutantId, hitCount);
1010
- }
1011
39
  //#endregion
1012
40
  //#region src/index.ts
1013
- /**
1014
- * The `vitest` test runner, as the plugin the engine loads.
1015
- *
1016
- * The declared layer asks for the run's resolved options and the sandbox it runs
1017
- * in, so the requirement is visible in the type and an engine that does not
1018
- * provide it fails to compile.
1019
- */
1020
- const strykerPlugins = [declarePlugin("TestRunner", "vitest", Layer.unwrap(Effect$1.gen(function* () {
1021
- const options = yield* RunConfiguration;
1022
- const sandboxDirectory = yield* SandboxDirectory;
1023
- return makeVitestRunnerLayer({
1024
- options,
1025
- sandboxDirectory
1026
- });
1027
- })))];
41
+ const strykerPlugins = [{
42
+ kind: "TestRunner",
43
+ name: "vitest",
44
+ workerEntry: new URL("./main.mjs", import.meta.url).href
45
+ }];
1028
46
  /**
1029
47
  * The `vitest` option section as a JSON Schema document, for Stryker's option
1030
48
  * validation — derived from the declaration, never read from a file. It is built