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