@systemfsoftware/stryker-js 6.0.0 → 7.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.
@@ -1,1585 +0,0 @@
1
- import { A as formatTraceparent, C as TestRunnerFailed, D as WorkerEntryUrl, E as TraceContextReference, M as parseTraceparent, N as toMutantRunResult, O as WorkerPluginKind, P as INSTRUMENTER_CONSTANTS, T as TraceContextMiddleware, b as TRACEPARENT_HEADER, f as PropagatedTrace, j as isCustomTestRunner, w as TestRunnerRpcs, x as TRACESTATE_HEADER, y as StrykerOptionsSchema } from "./dist-CWbipfGB.mjs";
2
- import { t as mergeConfig } from "./merge-config-BrrNQWje.mjs";
3
- import * as Effect$1 from "effect/Effect";
4
- import * as FileSystem from "effect/FileSystem";
5
- import * as Path from "effect/Path";
6
- import * as Predicate from "effect/Predicate";
7
- import * as S from "effect/Schema";
8
- import * as Match from "effect/Match";
9
- import * as Option from "effect/Option";
10
- import * as Context from "effect/Context";
11
- import * as RpcMiddleware from "effect/unstable/rpc/RpcMiddleware";
12
- import { Schema } from "effect";
13
- import * as Clock from "effect/Clock";
14
- import * as Config from "effect/Config";
15
- import * as Layer from "effect/Layer";
16
- import * as Result from "effect/Result";
17
- import * as Stream from "effect/Stream";
18
- import * as Array$1 from "effect/Array";
19
- import * as Cause from "effect/Cause";
20
- import "effect/Exit";
21
- import * as Duration from "effect/Duration";
22
- import * as HashMap from "effect/HashMap";
23
- import * as HashSet from "effect/HashSet";
24
- import * as api from "@opentelemetry/api";
25
- import * as Tracer from "effect/Tracer";
26
- import * as Headers from "effect/unstable/http/Headers";
27
- import { NodeFileSystem, NodePath, NodeSocket, NodeStdio } from "@effect/platform-node";
28
- import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
29
- import "effect/unstable/rpc/RpcServer";
30
- import * as NodeSdk from "@effect/opentelemetry/NodeSdk";
31
- import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
32
- import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
33
- import * as Ref from "effect/Ref";
34
- import * as ChildProcess from "effect/unstable/process/ChildProcess";
35
- import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";
36
- import * as NodeChildProcessSpawner from "@effect/platform-node-shared/NodeChildProcessSpawner";
37
- import * as NodeCrypto from "@effect/platform-node-shared/NodeCrypto";
38
- import * as Crypto from "effect/Crypto";
39
- import * as RpcClient from "effect/unstable/rpc/RpcClient";
40
- import * as Schedule from "effect/Schedule";
41
- //#region src/Run.schema.ts
42
- const TypeId = "~stryker/mutation-run/StageError";
43
- var StageError = class extends S.TaggedError(TypeId)("StageError", {
44
- stage: S.Literals([
45
- "prepare",
46
- "instrument",
47
- "dryRun",
48
- "dryRunNoTests",
49
- "mutationTest"
50
- ]),
51
- reason: S.String,
52
- cause: S.optional(S.Defect()),
53
- command: S.optional(S.String)
54
- }) {
55
- [TypeId] = TypeId;
56
- get exitClass() {
57
- return STAGE_PRESENTATION[this.stage].exitClass;
58
- }
59
- get message() {
60
- const failure = `${STAGE_PRESENTATION[this.stage].label} failed: ${this.reason}`;
61
- return Match.value(this.command).pipe(Match.when(Match.nonEmptyString, (present) => `${failure} (command: ${present})`), Match.orElse(() => failure));
62
- }
63
- };
64
- const STAGE_PRESENTATION = {
65
- prepare: {
66
- label: "Prepare",
67
- exitClass: "ConfigError"
68
- },
69
- instrument: {
70
- label: "Instrument",
71
- exitClass: "RuntimeError"
72
- },
73
- dryRun: {
74
- label: "Dry run",
75
- exitClass: "RuntimeError"
76
- },
77
- dryRunNoTests: {
78
- label: "Dry run",
79
- exitClass: "ConfigError"
80
- },
81
- mutationTest: {
82
- label: "Mutation testing",
83
- exitClass: "RuntimeError"
84
- }
85
- };
86
- var PrepareError = class extends S.TaggedError()("PrepareError", {
87
- stage: S.Literal("prepare"),
88
- reason: S.String
89
- }) {};
90
- //#endregion
91
- //#region src/glob-match.ts
92
- const GLOB_META = /[*?[{]/;
93
- const isGlob = (value) => GLOB_META.test(value);
94
- const escapeRegex = (value) => value.replace(/[\\^$.|()+]/g, "\\$&");
95
- const globSegmentToRegex = (segment) => Match.value(segment.length === 0).pipe(Match.when(true, () => ""), Match.orElse(() => {
96
- const char = segment[0] ?? "";
97
- const rest = segment.slice(1);
98
- return Match.value(char).pipe(Match.when("*", () => Match.value(rest.startsWith("*")).pipe(Match.when(true, () => {
99
- const afterStars = rest.slice(1);
100
- return Match.value(afterStars.startsWith("/")).pipe(Match.when(true, () => `(?:(?:[^/]+/)*)?${globSegmentToRegex(afterStars.slice(1))}`), Match.orElse(() => `.*${globSegmentToRegex(afterStars)}`));
101
- }), Match.orElse(() => `[^/]*${globSegmentToRegex(rest)}`))), Match.when("?", () => `[^/]${globSegmentToRegex(rest)}`), Match.when("{", () => {
102
- const close = segment.indexOf("}");
103
- return Match.value(close < 0).pipe(Match.when(true, () => `${escapeRegex(char)}${globSegmentToRegex(rest)}`), Match.orElse(() => {
104
- const inner = segment.slice(1, close);
105
- const after = segment.slice(close + 1);
106
- return `(?:${inner.split(",").map(globSegmentToRegex).join("|")})${globSegmentToRegex(after)}`;
107
- }));
108
- }), Match.when("[", () => {
109
- const close = segment.indexOf("]", 1);
110
- return Match.value(close < 0).pipe(Match.when(true, () => `${escapeRegex(char)}${globSegmentToRegex(rest)}`), Match.orElse(() => `${segment.slice(0, close + 1)}${globSegmentToRegex(segment.slice(close + 1))}`));
111
- }), Match.orElse(() => `${escapeRegex(char)}${globSegmentToRegex(rest)}`));
112
- }));
113
- const globToRegExp = (pattern, caseInsensitive) => new RegExp(`^${globSegmentToRegex(pattern)}$`, Match.value(caseInsensitive).pipe(Match.when(true, () => "i"), Match.orElse(() => "")));
114
- const matchesGlob = (path, pattern, caseInsensitive = false) => globToRegExp(pattern, caseInsensitive).test(path);
115
- const compileIgnoreRule = (pattern) => {
116
- const negate = pattern.startsWith("!");
117
- const glob = Match.value(negate).pipe(Match.when(true, () => pattern.slice(1)), Match.orElse(() => pattern));
118
- const re = globToRegExp(glob, true);
119
- const prefix = new RegExp(re.source.replace(/\$$/, ""), re.flags);
120
- return {
121
- negate,
122
- matches: (candidate) => re.test(candidate),
123
- matchesPrefix: (candidate) => prefix.test(candidate)
124
- };
125
- };
126
- //#endregion
127
- //#region src/Config.schema.ts
128
- const ConfigDocumentSchema = S.Record(S.String, S.Unknown);
129
- const ImportedModuleSchema = S.Struct({ default: S.optional(S.Unknown) });
130
- var ConfigFileNotFoundError = class extends S.TaggedError()("ConfigFileNotFoundError", { file: S.String }) {
131
- exitClass = "ConfigError";
132
- };
133
- var ConfigFileUnsupportedError = class extends S.TaggedError()("ConfigFileUnsupportedError", {
134
- file: S.String,
135
- hint: S.String
136
- }) {
137
- exitClass = "ConfigError";
138
- get message() {
139
- return this.hint;
140
- }
141
- };
142
- var ConfigFileUnreadableError = class extends S.TaggedError()("ConfigFileUnreadableError", {
143
- file: S.String,
144
- cause: S.Unknown
145
- }) {
146
- exitClass = "ConfigError";
147
- };
148
- var ConfigFileInvalidError = class extends S.TaggedError()("ConfigFileInvalidError", {
149
- file: S.String,
150
- cause: S.Unknown
151
- }) {
152
- exitClass = "ConfigError";
153
- };
154
- var ConfigError = class extends S.TaggedError()("ConfigError", { message: S.String }) {
155
- exitClass = "ConfigError";
156
- };
157
- var ReadConfigCommand = class extends S.TaggedClass()("ReadConfigCommand", {
158
- cliOptions: S.Record(S.String, S.Unknown),
159
- basePath: S.String
160
- }) {};
161
- var MergeCommand = class extends S.TaggedClass()("MergeCommand", {
162
- base: S.Record(S.String, S.Unknown),
163
- overrides: S.Record(S.String, S.Unknown)
164
- }) {};
165
- var MergeResult = class extends S.TaggedClass()("MergeResult", { merged: S.Record(S.String, S.Unknown) }) {};
166
- const ExtendsStepDocumentSchema = S.Struct({
167
- path: S.String,
168
- options: ConfigDocumentSchema
169
- });
170
- const ExtendsStepStateSchema = S.Struct({
171
- visited: S.Array(S.String),
172
- documents: S.Array(ExtendsStepDocumentSchema)
173
- });
174
- var ExtendsStepDone = class extends S.TaggedClass()("done", { options: ConfigDocumentSchema }) {};
175
- var ExtendsStepRead = class extends S.TaggedClass()("read", {
176
- path: S.String,
177
- state: ExtendsStepStateSchema
178
- }) {};
179
- var ExtendsStepResolve = class extends S.TaggedClass()("resolve", {
180
- specifier: S.String,
181
- state: ExtendsStepStateSchema
182
- }) {};
183
- var ExtendsStepRefused = class extends S.TaggedClass()("refused", {
184
- reason: S.Literals(["cycle", "non-string-extends"]),
185
- file: S.String
186
- }) {};
187
- const survivorsPriorReport = S.optionalKey(S.String.pipe(S.annotate({ description: "The path of the prior mutation report a --survivors run admits against and re-tests the survivors of. Defaults to \"reports/mutation-report.json\" when unset. Deliberately has no default: a default would be injected into every resolved options object and written into every report, poisoning the KTD7 marker that identifies a report produced by a survivors run." })));
188
- const extendsPropertySchema = S.optionalKey(S.String.pipe(S.annotate({ description: "Path to another stryker config file whose options merge underneath this one. Resolved relative to this file. A child scalar or array replaces the inherited value; a child object merges one level deep; a child key set to null deletes the inherited key. Inheritance chains are not rewritten, so an inherited relative path value still resolves against the working directory of the run that reads it." })));
189
- const forkOptionsSchema = S.StructWithRest(S.Struct({
190
- ...StrykerOptionsSchema.schema.fields,
191
- survivorsPriorReport,
192
- extends: extendsPropertySchema
193
- }), [S.Record(S.String, S.Unknown)]);
194
- //#endregion
195
- //#region src/Project.ignore.ts
196
- const ALWAYS_IGNORE = Object.freeze([
197
- "node_modules",
198
- ".git",
199
- "*.tsbuildinfo",
200
- "/stryker.log",
201
- ".next",
202
- ".nuxt",
203
- ".svelte-kit"
204
- ]);
205
- const MUTATION_RANGE_REGEX = /(.*?):((\d+)(?::(\d+))?-(\d+)(?::(\d+))?)$/;
206
- //#endregion
207
- //#region src/stryker-error.schema.ts
208
- var StrykerError = class extends Schema.TaggedError()("StrykerError", {
209
- message: Schema.String,
210
- cause: Schema.optional(Schema.Unknown)
211
- }) {};
212
- //#endregion
213
- //#region ../stryker-js-plugin-runtime/dist/index.mjs
214
- const CURRENT_VERSION = "00";
215
- const serializedTraceState = (traceState) => Option.filter(Option.map(Option.fromUndefinedOr(traceState), (state) => state.serialize()), (serialized) => serialized.length > 0);
216
- const traceStateField = (traceState) => Option.match(serializedTraceState(traceState), {
217
- onNone: () => ({}),
218
- onSome: (serialized) => ({ traceState: serialized })
219
- });
220
- const tracePartsOf = (context) => Option.map(Option.liftPredicate(context, (candidate) => api.trace.isSpanContextValid(candidate)), (valid) => ({
221
- version: CURRENT_VERSION,
222
- traceId: valid.traceId,
223
- spanId: valid.spanId,
224
- traceFlags: valid.traceFlags,
225
- ...traceStateField(valid.traceState)
226
- }));
227
- const partsOfSpan = (span) => Option.flatMap(Option.map(Option.fromUndefinedOr(span), (present) => present.spanContext()), tracePartsOf);
228
- const remotePartsFromHeaders = (headers) => Option.map(Option.flatMap(Headers.get(headers, TRACEPARENT_HEADER), parseTraceparent), (parts) => ({
229
- ...parts,
230
- traceState: Option.getOrUndefined(Headers.get(headers, TRACESTATE_HEADER))
231
- }));
232
- const traceHeaders = (parts) => {
233
- const traceparent = Headers.set(Headers.empty, TRACEPARENT_HEADER, formatTraceparent(parts));
234
- if (parts.traceState === void 0) return traceparent;
235
- return Headers.set(traceparent, TRACESTATE_HEADER, parts.traceState);
236
- };
237
- const inject = (request, parts) => ({
238
- ...request,
239
- headers: Headers.merge(request.headers, traceHeaders(parts))
240
- });
241
- const hostParts = Effect$1.map(Effect$1.context(), (context) => Option.orElse(Context.get(context, TraceContextReference), () => partsOfSpan(api.trace.getSpan(api.context.active()))));
242
- const clientMiddleware = (options) => hostParts.pipe(Effect$1.flatMap((parts) => Option.match(parts, {
243
- onNone: () => options.next(options.request),
244
- onSome: (present) => options.next(inject(options.request, present))
245
- })));
246
- const layerTraceContextClient = RpcMiddleware.layerClient(TraceContextMiddleware, clientMiddleware);
247
- const SAMPLED_FLAG = 1;
248
- const traceStateOption = (traceState) => Option.match(Option.fromUndefinedOr(traceState), {
249
- onNone: () => ({}),
250
- onSome: (state) => ({ traceState: state })
251
- });
252
- const sampledFlagOf = (sampled) => {
253
- if (sampled) return SAMPLED_FLAG;
254
- return 0;
255
- };
256
- const externalSpanOf = (parts) => Tracer.externalSpan({
257
- traceId: parts.traceId,
258
- spanId: parts.spanId,
259
- sampled: (parts.traceFlags & SAMPLED_FLAG) === SAMPLED_FLAG,
260
- ...traceStateOption(parts.traceState)
261
- });
262
- const partsOfEffectSpan = (span) => ({
263
- version: CURRENT_VERSION,
264
- traceId: span.traceId,
265
- spanId: span.spanId,
266
- traceFlags: sampledFlagOf(span.sampled)
267
- });
268
- const serverMiddleware = (effect, options) => {
269
- const remote = remotePartsFromHeaders(options.headers);
270
- const attributes = { "rpc.method": options.rpc._tag };
271
- return Option.match(Option.map(remote, externalSpanOf), {
272
- onNone: () => Effect$1.useSpan(`rpc.${options.rpc._tag}`, { attributes }, () => effect),
273
- onSome: (parent) => Effect$1.useSpan(`rpc.${options.rpc._tag}`, {
274
- attributes,
275
- parent
276
- }, () => effect)
277
- }).pipe(Effect$1.provideService(PropagatedTrace, remote));
278
- };
279
- Layer.succeed(TraceContextMiddleware, serverMiddleware);
280
- let wire;
281
- const workerOptionsWire = () => wire ??= S.fromJsonString(S.toCodecJson(StrykerOptionsSchema));
282
- const encodeWorkerOptions = (options) => S.encodeEffect(workerOptionsWire())(options).pipe(Effect$1.orDie);
283
- const decodeWorkerOptions = (raw) => S.decodeUnknownEffect(workerOptionsWire())(raw);
284
- Effect$1.gen(function* () {
285
- const workerDir = yield* Config.string("STRYKER_WORKER_DIR");
286
- const fs = yield* FileSystem.FileSystem;
287
- const path = yield* Path.Path;
288
- const raw = yield* fs.readFileString(path.join(workerDir, "options.json"));
289
- return yield* decodeWorkerOptions(raw);
290
- });
291
- const WORKER_SERVICE_NAME = "stryker-js-plugin-worker";
292
- const DEFAULT_ENDPOINT = "http://127.0.0.1:4318";
293
- const TRACES_SUFFIX = "/v1/traces";
294
- const tracesUrl = (endpoint) => {
295
- const trimmed = endpoint.replace(/\/+$/u, "");
296
- if (trimmed.endsWith(TRACES_SUFFIX)) return trimmed;
297
- return `${trimmed}${TRACES_SUFFIX}`;
298
- };
299
- const otlpLayer = (serviceName, endpoint) => NodeSdk.layer(() => ({
300
- resource: { serviceName },
301
- spanProcessor: new SimpleSpanProcessor(new OTLPTraceExporter({ url: tracesUrl(endpoint) }))
302
- }));
303
- Layer.unwrap(Effect$1.gen(function* () {
304
- if (!(yield* Config.boolean("OTEL_ENABLED").pipe(Config.withDefault(false)))) return Layer.empty;
305
- const serviceName = yield* Config.string("OTEL_SERVICE_NAME").pipe(Config.withDefault(WORKER_SERVICE_NAME));
306
- const endpoint = yield* Config.string("OTEL_EXPORTER_OTLP_ENDPOINT").pipe(Config.withDefault(DEFAULT_ENDPOINT));
307
- return otlpLayer(serviceName, endpoint);
308
- }));
309
- //#endregion
310
- //#region src/VmRunner.ts
311
- var VmRunner = class extends Context.Service()("@systemfsoftware/stryker-js/VmRunner") {};
312
- const vmRunnerName = "vm";
313
- const ALL_TESTS_NAME = "All tests";
314
- const isVmRunner = (name) => typeof name === "string" && name.toLowerCase() === "vm";
315
- const vmRunnerCapabilities = { reloadEnvironment: true };
316
- const FALLBACK_FILE_NAME = "stryker-vm-tests.js";
317
- const errorText = (error) => Match.value(error).pipe(Match.when(Match.instanceOf(Error), (failure) => failure.message), Match.orElse((value) => String(value)));
318
- const compileFailure = (file, cause) => TestRunnerFailed.make({
319
- runnerName: "vm",
320
- phase: "init",
321
- cause: `Could not compile "${file}" for the in-memory runner: ${errorText(cause)}`
322
- });
323
- const compileTests = (platform, fs, testFiles) => Effect$1.gen(function* () {
324
- const sources = yield* Effect$1.forEach(testFiles, (file) => fs.readFileString(file).pipe(Effect$1.mapError((cause) => compileFailure(file, cause)), Effect$1.flatMap((source) => Effect$1.try({
325
- try: () => platform.module.stripTypeScriptTypes(source, { mode: "transform" }),
326
- catch: (cause) => compileFailure(file, cause)
327
- }))));
328
- const fileName = Option.getOrElse(Option.fromUndefinedOr(testFiles.at(0)), () => FALLBACK_FILE_NAME);
329
- return {
330
- fileName,
331
- script: yield* Effect$1.try({
332
- try: () => new platform.vm.Script(sources.join("\n;\n"), { filename: fileName }),
333
- catch: (cause) => compileFailure(fileName, cause)
334
- })
335
- };
336
- });
337
- const sandboxFor = (platform, fileName, activeMutantId) => {
338
- const namespace = Match.value(activeMutantId).pipe(Match.when(Match.string, (active) => ({ [INSTRUMENTER_CONSTANTS.ACTIVE_MUTANT]: active })), Match.orElse(() => ({})));
339
- const moduleExports = {};
340
- const moduleObj = { exports: moduleExports };
341
- const sandbox = {
342
- ...globalThis,
343
- [INSTRUMENTER_CONSTANTS.NAMESPACE]: namespace,
344
- require: platform.module.createRequire(fileName),
345
- module: moduleObj,
346
- exports: moduleExports,
347
- __filename: fileName
348
- };
349
- sandbox["global"] = sandbox;
350
- sandbox["globalThis"] = sandbox;
351
- return sandbox;
352
- };
353
- const resultFromRun = (failureMessage, timeSpentMs) => Match.value(failureMessage).pipe(Match.when(Match.string, (failure) => ({
354
- status: "complete",
355
- tests: [{
356
- id: "all",
357
- name: ALL_TESTS_NAME,
358
- status: "failed",
359
- failureMessage: failure,
360
- timeSpentMs
361
- }]
362
- })), Match.orElse(() => ({
363
- status: "complete",
364
- tests: [{
365
- id: "all",
366
- name: ALL_TESTS_NAME,
367
- status: "success",
368
- timeSpentMs
369
- }]
370
- })));
371
- const runInFreshContext = (platform, compiled, activeMutantId) => Effect$1.sync(() => {
372
- const context = platform.vm.createContext(sandboxFor(platform, compiled.fileName, activeMutantId));
373
- try {
374
- compiled.script.runInContext(context);
375
- return;
376
- } catch (cause) {
377
- return errorText(cause);
378
- }
379
- });
380
- const runOnce = (platform, compiled, activeMutantId) => Effect$1.gen(function* () {
381
- const startedAt = yield* Clock.currentTimeMillis;
382
- const failure = yield* runInFreshContext(platform, compiled, activeMutantId);
383
- const elapsedMs = (yield* Clock.currentTimeMillis) - startedAt;
384
- return resultFromRun(failure, elapsedMs);
385
- });
386
- const vmTestRunner = (config) => Effect$1.gen(function* () {
387
- const platform = yield* VmRunner;
388
- const fs = yield* FileSystem.FileSystem;
389
- const compiled = yield* Ref.make(Option.none());
390
- const compiledTests = (testFiles) => Effect$1.gen(function* () {
391
- const cached = yield* Ref.get(compiled);
392
- if (Option.isSome(cached)) return cached.value;
393
- const tests = yield* compileTests(platform, fs, testFiles);
394
- yield* Ref.set(compiled, Option.some(tests));
395
- return tests;
396
- });
397
- const run = (testFiles, activeMutantId) => compiledTests(testFiles).pipe(Effect$1.flatMap((tests) => runOnce(platform, tests, activeMutantId)));
398
- const testFilesOf = (override) => Match.value(config.testFiles).pipe(Match.when((files) => files.length > 0, (files) => files), Match.orElse(() => override ?? []));
399
- return {
400
- capabilities: Effect$1.succeed(vmRunnerCapabilities),
401
- init: Effect$1.void,
402
- dryRun: (options) => run(testFilesOf(options.testFiles), void 0),
403
- mutantRun: (options) => run(config.testFiles, options.activeMutant.id).pipe(Effect$1.map((result) => toMutantRunResult(result, true)), Effect$1.catchTag("TestRunnerFailed", (failure) => Effect$1.succeed(toMutantRunResult({
404
- status: "error",
405
- errorMessage: failure.cause
406
- }, true))))
407
- };
408
- });
409
- Schema.TaggedError()("WorkerMethodError", {
410
- message: Schema.String,
411
- name: Schema.optional(Schema.String),
412
- stack: Schema.optional(Schema.String)
413
- });
414
- /**
415
- * A process identifier. `S.Int` rather than `S.Number` because the plain number
416
- * domain admits `NaN` and the infinities, and a pid is none of those.
417
- */
418
- const ProcessId = Schema.Int;
419
- /**
420
- * How a child process ended.
421
- */
422
- const ChildExit = Schema.Union([Schema.Struct({
423
- _tag: Schema.Literals(["Code"]),
424
- code: Schema.Int
425
- }), Schema.Struct({
426
- _tag: Schema.Literals(["Signal"]),
427
- signal: Schema.String
428
- })]);
429
- /**
430
- * The child process hosting a worker ended when it was not supposed to.
431
- */
432
- var ChildProcessCrashedError = class extends Schema.TaggedError()("ChildProcessCrashedError", {
433
- pid: ProcessId,
434
- exit: ChildExit,
435
- cause: Schema.optional(Schema.String)
436
- }) {
437
- exitClass = "InternalError";
438
- };
439
- var OutOfMemoryError = class extends Schema.TaggedError()("OutOfMemoryError", {
440
- pid: ProcessId,
441
- exitCode: Schema.Int
442
- }) {
443
- exitClass = "RuntimeError";
444
- };
445
- var WorkerBootTimeoutError = class extends Schema.TaggedError()("WorkerBootTimeoutError", { pid: ProcessId }) {
446
- exitClass = "InternalError";
447
- };
448
- //#endregion
449
- //#region src/Worker.ts
450
- var IdGenerator = class extends Context.Service()("@systemfsoftware/stryker-js/Worker/IdGenerator") {};
451
- const makeIdGenerator = Effect$1.gen(function* () {
452
- const ref = yield* Ref.make(0);
453
- return { next: Ref.getAndUpdate(ref, (n) => n + 1) };
454
- });
455
- const layer = Layer.effect(IdGenerator)(makeIdGenerator);
456
- const ASSUMED_PARALLELISM = 4;
457
- const reportedParallelism = () => {
458
- if (typeof globalThis.navigator === "undefined") return void 0;
459
- return globalThis.navigator.hardwareConcurrency;
460
- };
461
- const getAvailableParallelism = () => {
462
- const reported = reportedParallelism();
463
- if (typeof reported !== "number") return ASSUMED_PARALLELISM;
464
- return reported;
465
- };
466
- const PERCENTAGE_OPTION = /^(100|[1-9]?[0-9])%$/;
467
- const percentageOf = (text) => Option.fromNullishOr(PERCENTAGE_OPTION.exec(text)?.[1]).pipe(Option.map((digits) => Number.parseInt(digits, 10)), Option.getOrUndefined);
468
- const defaultedTotal = (availableParallelism) => Match.value(availableParallelism > 4).pipe(Match.when(true, () => availableParallelism - 1), Match.orElse(() => availableParallelism));
469
- const unsetDetails = (availableParallelism) => ({
470
- total: defaultedTotal(availableParallelism),
471
- isPercentage: false
472
- });
473
- const percentageDetails = (text, availableParallelism) => Match.value(percentageOf(text)).pipe(Match.when(Predicate.isNumber, (percentage) => ({
474
- total: Math.max(1, Math.round(availableParallelism * percentage / 100)),
475
- isPercentage: true
476
- })), Match.orElse(() => unsetDetails(availableParallelism)));
477
- const computeTotalConcurrencyDetails = (concurrencyOption, availableParallelism) => Match.value(concurrencyOption).pipe(Match.when(Predicate.isString, (text) => percentageDetails(text, availableParallelism)), Match.when(Predicate.isNumber, (total) => ({
478
- total,
479
- isPercentage: false
480
- })), Match.orElse(() => unsetDetails(availableParallelism)));
481
- const splitConcurrency = (total, checkerCount) => {
482
- if (checkerCount > 0) return {
483
- checkers: Math.max(Math.ceil(total / 2), 1),
484
- testRunners: Math.max(Math.floor(total / 2), 1)
485
- };
486
- return {
487
- testRunners: total,
488
- checkers: 0
489
- };
490
- };
491
- const announcePercentage = (resolved) => Match.value(resolved.isPercentage).pipe(Match.when(true, () => Effect$1.logDebug(`Computed concurrency ${resolved.total} from "${resolved.option}" based on ${resolved.availableParallelism} available parallelism.`)), Match.orElse(() => Effect$1.void));
492
- const announceProcesses = (split) => Match.value(split.checkers > 0).pipe(Match.when(true, () => Effect$1.logInfo(`Creating ${split.checkers} checker process(es) and ${split.testRunners} test runner process(es).`)), Match.orElse(() => Effect$1.logInfo(`Creating ${split.testRunners} test runner process(es).`)));
493
- const makeConcurrency = (options) => Effect$1.gen(function* () {
494
- const availableParallelism = yield* Effect$1.sync(getAvailableParallelism);
495
- const { total, isPercentage } = computeTotalConcurrencyDetails(options.concurrency, availableParallelism);
496
- yield* announcePercentage({
497
- option: options.concurrency,
498
- total,
499
- isPercentage,
500
- availableParallelism
501
- });
502
- const result = splitConcurrency(total, options.checkers.length);
503
- yield* announceProcesses(result);
504
- return result;
505
- });
506
- const OUT_OF_MEMORY_EXIT_CODES = [134, 137];
507
- const isOutOfMemoryExit = (exitCode) => OUT_OF_MEMORY_EXIT_CODES.includes(exitCode);
508
- const classifyWorkerExit = (pid, exitCode) => Match.value(exitCode).pipe(Match.when(isOutOfMemoryExit, () => OutOfMemoryError.make({
509
- pid,
510
- exitCode
511
- })), Match.orElse(() => ChildProcessCrashedError.make({
512
- pid,
513
- exit: {
514
- _tag: "Code",
515
- code: exitCode
516
- },
517
- cause: "worker exited before it accepted the RPC connection"
518
- })));
519
- //#endregion
520
- //#region src/WorkerLauncher.ts
521
- const connectRetry = Schedule.max([Schedule.spaced(50), Schedule.recurs(100)]);
522
- var WorkerLauncher = class extends Context.Service()("@systemfsoftware/stryker-js/WorkerLauncher") {};
523
- const makeWorkerClient = (params) => Effect$1.gen(function* () {
524
- const worker = yield* (yield* WorkerLauncher).spawn({
525
- entrypoint: params.entrypoint,
526
- workingDirectory: params.workingDirectory,
527
- execArgv: params.execArgv,
528
- optionsJson: params.optionsJson,
529
- tempDirPrefix: params.tempDirPrefix,
530
- env: params.env
531
- });
532
- const protocol = yield* Layer.build(worker.clientLayer).pipe(Effect$1.retry(connectRetry), Effect$1.raceFirst(worker.exited), Effect$1.catchTag("SocketError", () => Effect$1.fail(WorkerBootTimeoutError.make({ pid: worker.pid }))));
533
- return yield* RpcClient.make(params.rpcs).pipe(Effect$1.provideContext(protocol), Effect$1.provide(layerTraceContextClient));
534
- });
535
- //#endregion
536
- //#region src/platform/node.ts
537
- const restrictToOwnerOrWarn = (fs, file) => fs.chmod(file, 384).pipe(Effect$1.catchTag("PlatformError", (cause) => Effect$1.logWarning(`Could not restrict "${file}" to its owner; the worker directory's own mode still protects it.`).pipe(Effect$1.annotateLogs("cause", cause))));
538
- /**
539
- * The Node worker launcher: spawn a worker child with this runtime's
540
- * executable, host the RPC server's address as a `net` `path` endpoint (a
541
- * socket file in the worker directory on POSIX, a same-user named pipe on
542
- * Windows), and connect the NDJSON protocol client over `NodeSocket`.
543
- */
544
- const nodeWorkerLauncherLayer = Layer.effect(WorkerLauncher, Effect$1.gen(function* () {
545
- const crypto = yield* Crypto.Crypto;
546
- const fs = yield* FileSystem.FileSystem;
547
- const path = yield* Path.Path;
548
- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
549
- return { spawn: (params) => Effect$1.gen(function* () {
550
- const workerDir = yield* fs.makeTempDirectoryScoped({ prefix: params.tempDirPrefix });
551
- const workerId = yield* crypto.randomUUIDv4;
552
- const socketPath = Match.value(process.platform).pipe(Match.when("win32", () => `\\\\.\\pipe\\stryker-worker-${workerId}`), Match.orElse(() => path.join(workerDir, "worker.sock")));
553
- const optionsFile = path.join(workerDir, "options.json");
554
- yield* fs.writeFileString(optionsFile, params.optionsJson);
555
- yield* restrictToOwnerOrWarn(fs, optionsFile);
556
- const entrypointPath = yield* path.fromFileUrl(new URL(params.entrypoint));
557
- const handle = yield* ChildProcess.make(process.execPath, [...params.execArgv, entrypointPath], {
558
- cwd: params.workingDirectory,
559
- extendEnv: true,
560
- env: {
561
- STRYKER_WORKER_DIR: workerDir,
562
- STRYKER_SOCKET: socketPath,
563
- ...params.env
564
- },
565
- stderr: "inherit"
566
- }).pipe(Effect$1.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner));
567
- const clientLayer = RpcClient.layerProtocolSocket({ retryTransientErrors: true }).pipe(Layer.provide(NodeSocket.layerNet({ path: socketPath })), Layer.provide(RpcSerialization.layerNdjson));
568
- const exited = handle.exitCode.pipe(Effect$1.orDie, Effect$1.flatMap((exitCode) => Effect$1.fail(classifyWorkerExit(Number(handle.pid), exitCode))));
569
- return {
570
- pid: Number(handle.pid),
571
- clientLayer,
572
- exited
573
- };
574
- }).pipe(Effect$1.catchIf(S.is(ChildProcessCrashedError), (error) => Effect$1.fail(error), () => Effect$1.fail(ChildProcessCrashedError.make({
575
- pid: 0,
576
- exit: {
577
- _tag: "Code",
578
- code: 1
579
- },
580
- cause: "worker spawn failed"
581
- })))) };
582
- }));
583
- const nodeFsPathLayer = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer);
584
- const nodeSpawnerLayer = NodeChildProcessSpawner.layer.pipe(Layer.provide(nodeFsPathLayer));
585
- const nodeBase = Layer.mergeAll(nodeFsPathLayer, nodeSpawnerLayer, NodeStdio.layer);
586
- const nodePlatformLayer = Layer.mergeAll(nodeWorkerLauncherLayer.pipe(Layer.provide(Layer.merge(nodeBase, NodeCrypto.layer))), nodeBase);
587
- /**
588
- * The in-memory runner's platform: the V8 sandbox module and the module
589
- * builtin that strips TypeScript and resolves a sandbox's `require`.
590
- *
591
- * Reached through `process.getBuiltinModule` rather than an `import` because a
592
- * Node builtin import is forbidden in product code; the call is wrapped in
593
- * `Effect.promise` so a platform without `node:vm` fails as a defect — the
594
- * runner has no fallback to fall back to.
595
- */
596
- const nodeVmPlatformLayer = Layer.effect(VmRunner, Effect$1.sync(() => ({
597
- module: process.getBuiltinModule("node:module"),
598
- vm: process.getBuiltinModule("node:vm")
599
- })));
600
- S.TaggedError()("CommandRunnerUnsupportedOption", { option: S.String });
601
- S.Struct({
602
- static: S.Record(S.String, S.Finite),
603
- perTest: S.Record(S.String, S.Record(S.String, S.Finite))
604
- });
605
- //#endregion
606
- //#region src/TestRunner.ts
607
- /**
608
- * The test runner capability — spawning, timeout, retry, reuse and environment
609
- * decisions for the engine's test execution.
610
- */
611
- const toRunnerBootFailure = (runnerName) => (error) => Match.value(error).pipe(Match.tag("WorkerBootTimeoutError", (timeout) => TestRunnerFailed.make({
612
- runnerName,
613
- phase: "init",
614
- cause: `Worker ${timeout.pid} did not accept the RPC connection before its boot window closed; its stderr above carries the reason`
615
- })), Match.tag("ChildProcessCrashedError", (crashed) => crashed), Match.tag("OutOfMemoryError", (outOfMemory) => outOfMemory), Match.exhaustive);
616
- const toRunnerFailure = (runnerName, phase) => (error) => Match.value(error).pipe(Match.tag("TestRunnerFailed", (e) => e), Match.orElse((e) => TestRunnerFailed.make({
617
- runnerName,
618
- phase,
619
- cause: e.message
620
- })));
621
- /**
622
- * A test runner that runs in a child process.
623
- *
624
- * Spawning happens **once**, here, and the child's teardown is a finalizer on the
625
- * scope this Effect is acquired in — which is the scope `Pool.make` opens for one
626
- * worker. So a worker lives as long as its pool slot, and each `dryRun` or
627
- * `mutantRun` only sends a message to a process that is already up.
628
- *
629
- * The scope boundary is the whole correctness question. Putting it inside each
630
- * method type-checks identically and spawns a Node process per mutant, which
631
- * makes the pool hold nothing and pays worker startup thousands of times.
632
- *
633
- * A failed spawn stays a typed failure rather than a defect, because
634
- * `Pool.invalidate` and the crash-retry combinator can only act on a failure they
635
- * can see.
636
- */
637
- const makeChildProcessTestRunner = (params) => Effect$1.gen(function* () {
638
- const runnerName = Match.value(params.options.testRunner).pipe(Match.when(isCustomTestRunner, (runner) => runner.plugin), Match.orElse((name) => name));
639
- const execArgv = Match.value(params.options.testRunner).pipe(Match.when(isCustomTestRunner, (runner) => Match.value(runner.nodeArgs).pipe(Match.when(Match.undefined, () => params.options.testRunnerNodeArgs), Match.orElse((args) => args))), Match.orElse(() => params.options.testRunnerNodeArgs));
640
- const optionsJson = yield* encodeWorkerOptions(params.options);
641
- const client = yield* makeWorkerClient({
642
- rpcs: TestRunnerRpcs,
643
- entrypoint: params.workerEntrypoint,
644
- workingDirectory: params.sandboxWorkingDirectory,
645
- execArgv: [...execArgv],
646
- optionsJson,
647
- tempDirPrefix: "stryker-test-runner-",
648
- env: {
649
- NODE_ENV: "test",
650
- VITEST: "1",
651
- STRYKER_SANDBOX_DIR: params.sandboxWorkingDirectory
652
- }
653
- }).pipe(Effect$1.mapError(toRunnerBootFailure(runnerName)));
654
- return {
655
- capabilities: client.capabilities().pipe(Effect$1.mapError(toRunnerFailure(runnerName, "capabilities"))),
656
- init: Effect$1.void,
657
- dryRun: (options) => client.dryRun({ options }).pipe(Effect$1.mapError(toRunnerFailure(runnerName, "dryRun"))),
658
- mutantRun: (options) => client.mutantRun({ options }).pipe(Effect$1.mapError(toRunnerFailure(runnerName, "mutantRun")))
659
- };
660
- });
661
- /**
662
- * Report a run that outlives its timeout as a timed-out result.
663
- *
664
- * `Effect.timeoutOrElse` is the race, and retiring the worker belongs to the
665
- * pool, which invalidates it. An interrupted `Effect` is cancelled, where a
666
- * losing `Promise` was not.
667
- */
668
- const withTimeout = (inner) => ({
669
- ...inner,
670
- dryRun: (options) => {
671
- const policy = Effect$1.timeoutOrElse({
672
- duration: Duration.millis(options.timeout),
673
- orElse: () => Effect$1.succeed({ status: "timeout" })
674
- });
675
- return inner.dryRun(options).pipe(policy);
676
- },
677
- mutantRun: (options) => {
678
- const policy = Effect$1.timeoutOrElse({
679
- duration: Duration.millis(options.timeout),
680
- orElse: () => Effect$1.succeed({ status: "timeout" })
681
- });
682
- return inner.mutantRun(options).pipe(policy);
683
- }
684
- });
685
- /**
686
- * Retry a run whose runner crashed, and report the crash as a result once the
687
- * attempts are spent.
688
- *
689
- * Every failure is retried, not only out-of-memory; the out-of-memory check
690
- * only changes the log message. On exhaustion the combinator produces a
691
- * `status: Error` result rather than failing, because one broken runner must
692
- * not end the whole run. The exhausted message renders the `Cause`, which
693
- * keeps the chain that led there.
694
- */
695
- const withRetry = (inner) => {
696
- const attempt = (run, onExhausted) => run.pipe(Effect$1.tapError((error) => Match.value(error).pipe(Match.when(S.is(OutOfMemoryError), (outOfMemory) => Effect$1.logInfo(`Test runner process [${outOfMemory.pid}] ran out of memory. That usually means the tests leak memory. Stryker restarts the process and carries on, but the run is slower for it.`)), Match.orElse(() => Effect$1.void))), Effect$1.retry({ times: 2 }), Effect$1.catchCause((cause) => Effect$1.succeed(onExhausted(`Test runner crashed. Tried 2 times to restart it without any luck. ${Cause.pretty(cause)}`))));
697
- return {
698
- ...inner,
699
- dryRun: (options) => attempt(inner.dryRun(options), (errorMessage) => ({
700
- status: "error",
701
- errorMessage
702
- })),
703
- mutantRun: (options) => attempt(inner.mutantRun(options), (errorMessage) => ({
704
- status: "error",
705
- errorMessage
706
- }))
707
- };
708
- };
709
- /**
710
- * Retire a runner after a configured number of mutant runs.
711
- *
712
- * The count lives in a `Ref` the combinator closes over, so nothing outside can
713
- * read or reset it. `retire` is supplied rather than being a `recover()` the
714
- * runner performs on itself: the pool owns worker lifetime, so retiring one is
715
- * `Pool.invalidate`, and a runner that restarts itself behind the pool's back
716
- * would create two owners of one process.
717
- */
718
- const withMaxReuse = (options, retire) => (inner) => Effect$1.gen(function* () {
719
- const restartAfter = options.maxTestRunnerReuse;
720
- if (restartAfter <= 0) return inner;
721
- const runs = yield* Ref.make(0);
722
- return {
723
- ...inner,
724
- mutantRun: (runOptions) => {
725
- const policy = (self) => Effect$1.gen(function* () {
726
- if ((yield* Ref.updateAndGet(runs, (n) => n + 1)) > restartAfter) {
727
- yield* retire;
728
- yield* Ref.set(runs, 1);
729
- }
730
- return yield* self;
731
- });
732
- return policy(inner.mutantRun(runOptions));
733
- }
734
- };
735
- });
736
- const stateNeedsReload = (requested, current) => Match.value(requested).pipe(Match.when(true, () => current !== "pristine"), Match.orElse(() => current === "loaded-static-mutant"));
737
- const reloadsEnvironment = (request) => request.canReload && stateNeedsReload(request.requested, request.current);
738
- const retiresWorker = (request) => !request.canReload && request.current === "loaded-static-mutant";
739
- const stateAfterRun = (requested) => Match.value(requested).pipe(Match.when(true, () => "loaded-static-mutant"), Match.orElse(() => "loaded"));
740
- const environmentPlan = (request) => ({
741
- reloadEnvironment: reloadsEnvironment(request),
742
- retire: retiresWorker(request),
743
- nextState: stateAfterRun(request.requested)
744
- });
745
- /**
746
- * Decide whether the test environment must be reloaded before a mutant runs.
747
- *
748
- * The decision produces a new options value; the caller's options are not
749
- * mutated, so a caller that reuses its own options does not read a value it
750
- * never wrote.
751
- */
752
- const withEnvironmentReload = (retire) => (inner) => Effect$1.gen(function* () {
753
- const state = yield* Ref.make("pristine");
754
- return {
755
- ...inner,
756
- dryRun: (options) => {
757
- const policy = (self) => Ref.set(state, "loaded").pipe(Effect$1.andThen(self));
758
- return policy(inner.dryRun(options));
759
- },
760
- mutantRun: (options) => Effect$1.gen(function* () {
761
- const current = yield* Ref.get(state);
762
- const canReload = (yield* inner.capabilities).reloadEnvironment;
763
- const plan = environmentPlan({
764
- requested: options.reloadEnvironment,
765
- current,
766
- canReload
767
- });
768
- if (plan.retire) yield* retire;
769
- const policy = (self) => Effect$1.gen(function* () {
770
- const result = yield* self;
771
- yield* Ref.set(state, plan.nextState);
772
- return result;
773
- });
774
- return yield* policy(inner.mutantRun({
775
- ...options,
776
- reloadEnvironment: plan.reloadEnvironment
777
- }));
778
- })
779
- };
780
- });
781
- /** Whether a configured runner name selects this runner. */
782
- const isCommandRunner = (name) => typeof name === "string" && name.toLowerCase() === "command";
783
- /** Each call is a fresh process, so the environment is always reloadable. */
784
- const commandRunnerCapabilities = { reloadEnvironment: true };
785
- /** One test result standing for the whole command, decided by its exit code. */
786
- const resultFromExit = (exitCode, output, timeSpentMs) => {
787
- if (exitCode === 0) return {
788
- status: "complete",
789
- tests: [{
790
- id: "all",
791
- name: ALL_TESTS_NAME,
792
- status: "success",
793
- timeSpentMs
794
- }]
795
- };
796
- return {
797
- status: "complete",
798
- tests: [{
799
- id: "all",
800
- name: ALL_TESTS_NAME,
801
- status: "failed",
802
- failureMessage: output,
803
- timeSpentMs
804
- }]
805
- };
806
- };
807
- const mutantActivation = (activeMutantId) => Match.value(activeMutantId).pipe(Match.when(Predicate.isString, (id) => ({
808
- env: { [INSTRUMENTER_CONSTANTS.ACTIVE_MUTANT_ENV_VARIABLE]: id },
809
- extendEnv: true
810
- })), Match.orElse(() => void 0));
811
- const spawnResult = (outcome, elapsed) => Match.value(outcome).pipe(Match.tag("Failure", (failed) => ({
812
- status: "error",
813
- errorMessage: String(failed.cause)
814
- })), Match.tag("Success", (exited) => resultFromExit(exited.value.exitCode, exited.value.output, elapsed)), Match.exhaustive);
815
- /**
816
- * Run the configured command once.
817
- *
818
- * `activeMutantId` is threaded through the environment rather than the command
819
- * line, which is how the instrumented code finds the mutant to activate.
820
- */
821
- const runCommand = (config, activeMutantId) => Effect$1.gen(function* () {
822
- const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
823
- const startedAt = yield* Clock.currentTimeMillis;
824
- const command = ChildProcess.make(config.options.commandRunner.command, {
825
- shell: true,
826
- cwd: config.workingDir,
827
- ...mutantActivation(activeMutantId)
828
- });
829
- const outcome = yield* Effect$1.scoped(Effect$1.gen(function* () {
830
- const handle = yield* spawner.spawn(command);
831
- return {
832
- output: yield* Stream.mkString(Stream.decodeText(handle.all)),
833
- exitCode: yield* handle.exitCode
834
- };
835
- })).pipe(Effect$1.exit);
836
- const elapsed = (yield* Clock.currentTimeMillis) - startedAt;
837
- return spawnResult(outcome, elapsed);
838
- });
839
- /** Run the tests with no mutant active, to establish the baseline. */
840
- const commandRunnerDryRun = (config) => runCommand(config, void 0);
841
- /** Run the tests with one mutant active. */
842
- const commandRunnerMutantRun = (config, { activeMutant }) => runCommand(config, activeMutant.id).pipe(Effect$1.map((result) => toMutantRunResult(result, true)));
843
- /**
844
- * The command runner, wrapped in the two adjustments that apply to it.
845
- *
846
- * No reuse limit and no environment reload, because every call is a new
847
- * process: there is nothing to reuse and nothing loaded to reload. The
848
- * combinators applied are the list that remains.
849
- *
850
- * The spawner is provided into each operation here rather than appearing in the
851
- * port's `R`. A port that named its own dependencies would make every consumer
852
- * discover and supply them (`REPO-A3`), and the service is built exactly where
853
- * the spawner is already in scope.
854
- */
855
- const commandRunner = (context, spawner) => {
856
- const config = {
857
- workingDir: context.sandboxWorkingDirectory,
858
- options: context.options
859
- };
860
- const provided = Effect$1.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner);
861
- const base = {
862
- capabilities: Effect$1.succeed(commandRunnerCapabilities),
863
- init: Effect$1.void,
864
- dryRun: () => commandRunnerDryRun(config).pipe(provided),
865
- mutantRun: (options) => commandRunnerMutantRun(config, options).pipe(provided)
866
- };
867
- return withRetry(withTimeout(base));
868
- };
869
- const inProcessRunner = (context) => Match.value(context.options.testRunner).pipe(Match.when(isCommandRunner, () => Option.some(ChildProcessSpawner.ChildProcessSpawner.pipe(Effect$1.map((spawner) => commandRunner(context, spawner))))), Match.when(isVmRunner, () => Option.some(vmTestRunner({ testFiles: context.testFiles }).pipe(Effect$1.provide(nodeVmPlatformLayer)))), Match.orElse(() => Option.none()));
870
- const buildTestRunner = (context, childProcessRunner) => Effect$1.gen(function* () {
871
- const inProcess = inProcessRunner(context);
872
- if (Option.isSome(inProcess)) return yield* inProcess.value;
873
- const base = yield* childProcessRunner;
874
- const timed = withTimeout(base);
875
- const limited = yield* withMaxReuse(context.options, context.retire)(timed);
876
- const reloading = yield* withEnvironmentReload(context.retire)(limited);
877
- return withRetry(reloading);
878
- });
879
- //#endregion
880
- //#region src/run/load-config.cell.ts
881
- const isNonNullObject = (value) => typeof value === "object" && value !== null;
882
- const optionsPath = (...path) => path.join(".");
883
- const combine = (prefixes, suffixes, extensions) => prefixes.flatMap((prefix) => suffixes.flatMap((suffix) => extensions.map((extension) => `${prefix}stryker${suffix}.${extension}`)));
884
- const CONFIG_FILE_NAME_PREFIXES = ["", "."];
885
- const CONFIG_FILE_NAME_SUFFIXES = [".conf", ".config"];
886
- const SUPPORTED_CONFIG_FILE_EXTENSIONS = [
887
- "ts",
888
- "mts",
889
- "js",
890
- "mjs"
891
- ];
892
- const LEGACY_CONFIG_FILE_EXTENSIONS = ["json", "cjs"];
893
- const configFileNames = (extensions) => combine([...CONFIG_FILE_NAME_PREFIXES], [...CONFIG_FILE_NAME_SUFFIXES], [...extensions]);
894
- const SUPPORTED_CONFIG_FILE_NAMES = Object.freeze(configFileNames(SUPPORTED_CONFIG_FILE_EXTENSIONS));
895
- const LEGACY_CONFIG_FILE_NAMES = Object.freeze(configFileNames(LEGACY_CONFIG_FILE_EXTENSIONS));
896
- const SUPPORTED_CONFIG_FILE_EXTENSION_GUIDE = `one of these extensions: ${SUPPORTED_CONFIG_FILE_EXTENSIONS.map((extension) => `.${extension}`).join(", ")}`;
897
- const legacyConfigHint = (file) => `Stryker no longer reads JSON or CommonJS config files. Convert "${file}" to a config module with an "export default { ... }" object — Stryker reads config modules with ${SUPPORTED_CONFIG_FILE_EXTENSION_GUIDE} — and delete the legacy file.`;
898
- const unsupportedConfigHint = (file) => `"${file}" is not a supported config file. Stryker reads config modules with ${SUPPORTED_CONFIG_FILE_EXTENSION_GUIDE}, for example "stryker.config.ts" with an "export default { ... }" object.`;
899
- const extendsChildHint = (file) => `The extended config "${file}" is a JSON or CommonJS config file, which Stryker no longer reads. Convert it to a config module — Stryker reads config modules with ${SUPPORTED_CONFIG_FILE_EXTENSION_GUIDE} — and point "extends" at the converted file.`;
900
- const shadowedLegacyWarning = (legacyFile, supportedFile) => `Ignoring the legacy config file "${legacyFile}": "${supportedFile}" is the config Stryker reads. Stryker no longer reads JSON or CommonJS config files; delete the legacy file.`;
901
- const isArrayValue = (value) => Array.isArray(value);
902
- const freezeArrayValue = (value) => Object.freeze(value.map(deepFreeze));
903
- const freezeMapEntry = ([entryKey, entryValue]) => [deepFreeze(entryKey), deepFreeze(entryValue)];
904
- const freezeMapValue = (value) => Object.freeze(new Map([...value.entries()].map(freezeMapEntry)));
905
- const freezeSetValue = (value) => Object.freeze(new Set([...value.values()].map(deepFreeze)));
906
- const freezeRegExpValue = (value) => Object.freeze(value);
907
- const freezeRecordValue = (value) => Object.freeze(Object.entries(value).reduce((frozen, [property, propertyValue]) => {
908
- frozen[property] = deepFreeze(propertyValue);
909
- return frozen;
910
- }, {}));
911
- function deepFreeze(target) {
912
- return Match.value(target).pipe(Match.when(isArrayValue, freezeArrayValue), Match.when((value) => value instanceof Map, freezeMapValue), Match.when((value) => value instanceof RegExp, freezeRegExpValue), Match.when((value) => value instanceof Set, freezeSetValue), Match.when(isNonNullObject, freezeRecordValue), Match.orElse(() => target));
913
- }
914
- const scopedUnserializable = (scope) => (description) => ({
915
- ...description,
916
- path: [scope, ...description.path]
917
- });
918
- const describeUnserializableChild = (child, scope) => Match.value(findUnserializables(child)).pipe(Match.when(void 0, () => []), Match.orElse((descriptions) => descriptions.map(scopedUnserializable(scope))));
919
- const collectUnserializables = (groups) => {
920
- const found = groups.flat();
921
- if (found.length > 0) return found;
922
- };
923
- const describeUnserializableArray = (value) => collectUnserializables(value.map((child, index) => describeUnserializableChild(child, index.toString())));
924
- const describeUnserializableRecord = (value) => collectUnserializables(Object.entries(value).map(([key, child]) => describeUnserializableChild(child, key)));
925
- const isPlainObjectValue = (value) => !Array.isArray(value) && value.constructor === Object;
926
- const classNameOf = (value) => Match.value(value.constructor).pipe(Match.when(Match.defined, (ctor) => ctor.name), Match.orElse(() => "Object"));
927
- const describeUnserializableInstance = (value) => [{
928
- path: [],
929
- reason: `Value is an instance of "${classNameOf(value)}", this detail will get lost in translation during serialization`
930
- }];
931
- const describeUnserializableObject = (value) => Match.value(value).pipe(Match.when(isArrayValue, describeUnserializableArray), Match.when(isPlainObjectValue, describeUnserializableRecord), Match.orElse(describeUnserializableInstance));
932
- const NON_JSON_PRIMITIVE_TYPES = [
933
- "bigint",
934
- "function",
935
- "symbol"
936
- ];
937
- const isNonJsonPrimitive = (value) => NON_JSON_PRIMITIVE_TYPES.includes(typeof value);
938
- const describeNonJsonPrimitive = (value) => [{
939
- path: [],
940
- reason: `Primitive type "${typeof value}" has no JSON representation`
941
- }];
942
- const describeNumber = (value) => {
943
- if (isFinite(value)) return void 0;
944
- return [{
945
- reason: `Number value \`${value}\` has no JSON representation`,
946
- path: []
947
- }];
948
- };
949
- function findUnserializables(thing) {
950
- return Match.value(thing).pipe(Match.when((value) => typeof value === "number", describeNumber), Match.when(isNonJsonPrimitive, describeNonJsonPrimitive), Match.when(isNonNullObject, describeUnserializableObject), Match.orElse(() => void 0));
951
- }
952
- function isWarningEnabled(warningType, warningOptions) {
953
- if (typeof warningOptions === "boolean") return warningOptions;
954
- else return warningOptions[warningType] === true;
955
- }
956
- const PATH_LINE = /^at\s+(\[.*\])$/;
957
- const PATH_SEGMENT = /\["([^"]*)"\]|\[(\d+)\]/g;
958
- const EXPECTED_PATTERN = /^Expected a string matching the RegExp (.+)$/;
959
- const EXPECTED_TYPE = /^Expected (.+)$/;
960
- const appendPathSegment = (path, segment) => Match.value(segment[2]).pipe(Match.when(void 0, () => appendKeySegment(path, segment[1] ?? "")), Match.orElse((index) => `${path}[${index}]`));
961
- const appendKeySegment = (path, key) => Match.value(path.length > 0).pipe(Match.when(true, () => `${path}.${key}`), Match.orElse(() => `${path}${key}`));
962
- const dottedPath = (raw) => Array.from(raw.matchAll(PATH_SEGMENT)).reduce(appendPathSegment, "");
963
- const phraseExpectedType = (expectation) => Match.value(EXPECTED_TYPE.exec(expectation)).pipe(Match.when((match) => match !== null, (match) => `should be ${match[1] ?? ""}`), Match.orElse(() => expectation));
964
- const phrase = (expectation) => Match.value(EXPECTED_PATTERN.exec(expectation)).pipe(Match.when((match) => match !== null, (match) => `must match pattern "${match[1] ?? ""}"`), Match.orElse(() => phraseExpectedType(expectation)));
965
- const EMPTY_ERROR_DESCRIPTION_STATE = {
966
- messages: [],
967
- expectation: void 0
968
- };
969
- const isBlankLine = (line) => line.length === 0;
970
- const startExpectation = (state, line) => Match.value(state.expectation).pipe(Match.when(void 0, () => ({
971
- messages: state.messages,
972
- expectation: line
973
- })), Match.orElse((pending) => ({
974
- messages: [...state.messages, pending],
975
- expectation: line
976
- })));
977
- const completeExpectation = (state, pathText, line) => Match.value(state.expectation).pipe(Match.when(void 0, () => startExpectation(state, line)), Match.orElse((pending) => ({
978
- messages: [...state.messages, `Config option "${dottedPath(pathText)}" ${phrase(pending)}.`],
979
- expectation: void 0
980
- })));
981
- const applyErrorLine = (state, line) => Match.value(PATH_LINE.exec(line)).pipe(Match.when((match) => match !== null, (match) => completeExpectation(state, match[1] ?? "", line)), Match.orElse(() => startExpectation(state, line)));
982
- const advanceErrorDescription = (state, rawLine) => Match.value(rawLine.trim()).pipe(Match.when(isBlankLine, () => state), Match.orElse((line) => applyErrorLine(state, line)));
983
- const completedErrorMessages = (state) => Match.value(state.expectation).pipe(Match.when(void 0, () => state.messages), Match.orElse((pending) => [...state.messages, pending]));
984
- const errorMessageOrFallback = (messages, fallback) => Match.value(messages.length > 0).pipe(Match.when(true, () => [...messages]), Match.orElse(() => [fallback]));
985
- function describeErrors(error) {
986
- const state = error.message.split("\n").reduce(advanceErrorDescription, EMPTY_ERROR_DESCRIPTION_STATE);
987
- return errorMessageOrFallback(completedErrorMessages(state), error.message);
988
- }
989
- function importModule(moduleName) {
990
- return Effect$1.tryPromise({
991
- try: () => import(moduleName),
992
- catch: (cause) => StrykerError.make({
993
- message: `Failed to import module "${moduleName}"`,
994
- cause
995
- })
996
- });
997
- }
998
- const initialExtendsStepState = {
999
- visited: [],
1000
- documents: []
1001
- };
1002
- const asUnknownArray = (value) => {
1003
- if (Array.isArray(value)) return value;
1004
- return [];
1005
- };
1006
- const isFirstDescriptorOccurrence = (descriptors) => (descriptor, index) => typeof descriptor !== "string" || descriptors.slice(0, index).includes(descriptor) === false;
1007
- const mergePluginDescriptors = (parentPlugins, childPlugins) => {
1008
- const merged = [...parentPlugins, ...childPlugins];
1009
- return merged.filter(isFirstDescriptorOccurrence(merged));
1010
- };
1011
- const inheritNested = (parentValue, childValue) => Match.value(parentValue).pipe(Match.when((value) => isNonNullObject(value) && Array.isArray(value) === false, (parentNested) => Match.value(childValue).pipe(Match.when((value) => isNonNullObject(value) && Array.isArray(value) === false, (childNested) => ({
1012
- ...parentNested,
1013
- ...childNested
1014
- })), Match.orElse(() => childValue))), Match.orElse(() => childValue));
1015
- const inheritEntry = (out, key, parentValue, childValue) => Match.value(childValue).pipe(Match.when(null, () => {
1016
- const next = { ...out };
1017
- delete next[key];
1018
- return next;
1019
- }), Match.orElse(() => Match.value(key).pipe(Match.when("plugins", () => ({
1020
- ...out,
1021
- [key]: mergePluginDescriptors(asUnknownArray(parentValue), asUnknownArray(childValue))
1022
- })), Match.orElse(() => ({
1023
- ...out,
1024
- [key]: inheritNested(parentValue, childValue)
1025
- })))));
1026
- function mergeConfigs(parent, child) {
1027
- return Object.entries(child).reduce((out, entry) => inheritEntry(out, entry[0], parent[entry[0]], entry[1]), { ...parent });
1028
- }
1029
- const RELATIVE_SPECIFIER_PREFIXES = [
1030
- "./",
1031
- "../",
1032
- "/",
1033
- "\\"
1034
- ];
1035
- function isModuleSpecifier(value) {
1036
- return RELATIVE_SPECIFIER_PREFIXES.every((prefix) => value.startsWith(prefix) === false);
1037
- }
1038
- const stripExtends = (document) => {
1039
- const { extends: _ignored, ...rest } = document;
1040
- return rest;
1041
- };
1042
- const mergeChainDocuments = (documents) => documents.reduceRight((merged, entry) => mergeConfigs(merged, stripExtends(entry.options)), {});
1043
- const decideExtendsStep = (state, document, file, pathService) => {
1044
- if (state.visited.includes(file)) return ExtendsStepRefused.make({
1045
- reason: "cycle",
1046
- file
1047
- });
1048
- const nextState = {
1049
- visited: [...state.visited, file],
1050
- documents: [...state.documents, {
1051
- path: file,
1052
- options: document
1053
- }]
1054
- };
1055
- return Match.value(document["extends"]).pipe(Match.when(void 0, () => ExtendsStepDone.make({ options: mergeChainDocuments(nextState.documents) })), Match.when(null, () => ExtendsStepDone.make({ options: mergeChainDocuments(nextState.documents) })), Match.when(Match.string, (extendValue) => Match.value(isModuleSpecifier(extendValue)).pipe(Match.when(true, () => ExtendsStepResolve.make({
1056
- specifier: extendValue,
1057
- state: nextState
1058
- })), Match.when(false, () => ExtendsStepRead.make({
1059
- path: pathService.resolve(pathService.dirname(file), extendValue),
1060
- state: nextState
1061
- })), Match.exhaustive)), Match.orElse(() => ExtendsStepRefused.make({
1062
- reason: "non-string-extends",
1063
- file
1064
- })));
1065
- };
1066
- const decodeConfigDocument = (configFile, document) => S.decodeUnknownEffect(ConfigDocumentSchema)(document).pipe(Effect$1.mapError((cause) => ConfigFileInvalidError.make({
1067
- file: configFile,
1068
- cause
1069
- })));
1070
- const requireDefaultExport = (configFile, defaultExport) => Match.value(defaultExport).pipe(Match.when(void 0, () => Effect$1.fail(ConfigFileInvalidError.make({
1071
- file: configFile,
1072
- cause: "Config file must have a default export!"
1073
- }))), Match.when(isNonNullObject, (value) => Effect$1.succeed(value)), Match.orElse(() => Effect$1.fail(ConfigFileInvalidError.make({
1074
- file: configFile,
1075
- cause: "Default export of config file must be an object!"
1076
- }))));
1077
- const isConfigFactory = (value) => typeof value === "function";
1078
- const factoryFailureCause = (cause) => Match.value(cause).pipe(Match.when(Match.instanceOf(Error), (error) => new Error(`Evaluating the config module's exported factory failed: ${error.message}`, { cause: error })), Match.orElse(() => cause));
1079
- /**
1080
- * Settle a config module's default export into the object the document schema decodes.
1081
- *
1082
- * `./config` types `defineConfig` as accepting the config object, a promise of it,
1083
- * or a factory receiving `ConfigEnv`, so the loader accepts all three. The factory
1084
- * is invoked here because this is the only place that knows the invocation the
1085
- * config is being read for; the promise is awaited here because a promise is a
1086
- * non-null object and would otherwise reach the document decoder as one.
1087
- */
1088
- const settleConfigDefault = (configFile, defaultExport, configEnv) => Effect$1.tryPromise({
1089
- try: () => Promise.resolve(defaultExport).then((exported) => Match.value(exported).pipe(Match.when(isConfigFactory, (factory) => factory(configEnv)), Match.orElse((value) => value))),
1090
- catch: (cause) => ConfigFileInvalidError.make({
1091
- file: configFile,
1092
- cause: factoryFailureCause(cause)
1093
- })
1094
- }).pipe(Effect$1.flatMap((settled) => requireDefaultExport(configFile, settled)));
1095
- const ERASABLE_SYNTAX_HELP = "Config modules may use only erasable TypeScript syntax: no enums, no namespaces with runtime code, no parameter properties, and no decorators.";
1096
- const carriesCode = (cause) => cause instanceof Error && "code" in cause;
1097
- const errorCodeOf = (cause) => {
1098
- const code = Match.value(cause).pipe(Match.when(carriesCode, (error) => error.code), Match.orElse(() => void 0));
1099
- if (typeof code === "string") return code;
1100
- };
1101
- const errorMessageOf = (cause) => {
1102
- const message = Match.value(cause).pipe(Match.when(Match.instanceOf(Error), (error) => error.message), Match.orElse(() => void 0));
1103
- if (typeof message === "string") return message;
1104
- };
1105
- const importFailureDetail = (failure) => errorMessageOf(failure.cause) ?? failure.message;
1106
- const configImportCause = (configFile, failure) => Match.value(errorCodeOf(failure.cause)).pipe(Match.when("ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX", () => new Error(`The config file "${configFile}" uses TypeScript syntax Node cannot execute. ${ERASABLE_SYNTAX_HELP}`, { cause: failure.cause })), Match.when("ERR_UNKNOWN_FILE_EXTENSION", () => new Error(`The config file "${configFile}" has an extension Node cannot load. Stryker reads config modules with ${SUPPORTED_CONFIG_FILE_EXTENSION_GUIDE}.`, { cause: failure.cause })), Match.when("ERR_MODULE_NOT_FOUND", () => new Error(`The config file "${configFile}" imports a module Node cannot find. The specifier is most likely not installed — install it as a dependency of the project, or remove the import. Node reported: ${importFailureDetail(failure)}`, { cause: failure.cause })), Match.when("ERR_PACKAGE_PATH_NOT_EXPORTED", () => new Error(`The config file "${configFile}" imports a path a package does not export. The package is installed but its "exports" map leaves this specifier out — import a path the package publishes. Node reported: ${importFailureDetail(failure)}`, { cause: failure.cause })), Match.orElse(() => failure));
1107
- const configModuleUrl = (configFile, pathService) => Match.value(configFile.startsWith("file:")).pipe(Match.when(true, () => Effect$1.try({
1108
- try: () => new URL(configFile),
1109
- catch: (cause) => ConfigFileUnreadableError.make({
1110
- file: configFile,
1111
- cause
1112
- })
1113
- })), Match.orElse(() => pathService.toFileUrl(pathService.resolve(configFile)).pipe(Effect$1.mapError((cause) => ConfigFileUnreadableError.make({
1114
- file: configFile,
1115
- cause
1116
- })))));
1117
- const readConfigModule = (configFile, configEnv) => Effect$1.gen(function* () {
1118
- const pathService = yield* Path.Path;
1119
- const importedModule = yield* importModule((yield* configModuleUrl(configFile, pathService)).href).pipe(Effect$1.mapError((failure) => ConfigFileUnreadableError.make({
1120
- file: configFile,
1121
- cause: configImportCause(configFile, failure)
1122
- })));
1123
- const exported = yield* S.decodeUnknownEffect(ImportedModuleSchema)(importedModule).pipe(Effect$1.mapError((cause) => ConfigFileInvalidError.make({
1124
- file: configFile,
1125
- cause
1126
- })), Effect$1.map((decoded) => decoded.default));
1127
- const document = yield* settleConfigDefault(configFile, exported, configEnv);
1128
- return yield* decodeConfigDocument(configFile, document);
1129
- });
1130
- const configFileExtension = (configFile, pathService) => pathService.extname(configFile).toLowerCase().slice(1);
1131
- const isLegacyConfigFile = (configFile, pathService) => LEGACY_CONFIG_FILE_EXTENSIONS.includes(configFileExtension(configFile, pathService));
1132
- const readExtendsChild = (configFile, configEnv) => Effect$1.gen(function* () {
1133
- const pathService = yield* Path.Path;
1134
- return yield* Match.value(isLegacyConfigFile(configFile, pathService)).pipe(Match.when(true, () => Effect$1.fail(ConfigFileUnsupportedError.make({
1135
- file: configFile,
1136
- hint: extendsChildHint(configFile)
1137
- }))), Match.orElse(() => readConfigModule(configFile, configEnv)));
1138
- });
1139
- const canonicalConfigFile = (file, pathService) => Match.value(file.startsWith("file:")).pipe(Match.when(true, () => Effect$1.try({
1140
- try: () => new URL(file),
1141
- catch: (cause) => ConfigFileUnreadableError.make({
1142
- file,
1143
- cause
1144
- })
1145
- }).pipe(Effect$1.flatMap((url) => pathService.fromFileUrl(url).pipe(Effect$1.mapError((cause) => ConfigFileUnreadableError.make({
1146
- file,
1147
- cause
1148
- })))))), Match.orElse(() => Effect$1.succeed(pathService.resolve(file))));
1149
- function resolveExtendsSpecifier(specifier) {
1150
- return Effect$1.try({
1151
- try: () => import.meta.resolve(specifier),
1152
- catch: (cause) => ConfigFileUnreadableError.make({
1153
- file: specifier,
1154
- cause
1155
- })
1156
- });
1157
- }
1158
- function resolveExtends(configFile, document, configEnv) {
1159
- return Effect$1.gen(function* () {
1160
- const pathService = yield* Path.Path;
1161
- const loop = (state, file, currentDocument) => Effect$1.gen(function* () {
1162
- const canonicalFile = yield* canonicalConfigFile(file, pathService);
1163
- return yield* Match.value(decideExtendsStep(state, currentDocument, canonicalFile, pathService)).pipe(Match.tag("done", (d) => Effect$1.succeed(d.options)), Match.tag("read", (d) => readExtendsChild(d.path, configEnv).pipe(Effect$1.flatMap((nextDocument) => loop(d.state, d.path, nextDocument)))), Match.tag("resolve", (d) => resolveExtendsSpecifier(d.specifier).pipe(Effect$1.flatMap((resolvedUrl) => readExtendsChild(resolvedUrl, configEnv).pipe(Effect$1.flatMap((nextDocument) => loop(d.state, resolvedUrl, nextDocument)))))), Match.tag("refused", (d) => {
1164
- let message = `Invalid config file "${d.file}". "extends" must be a string`;
1165
- if (d.reason === "cycle") message = `Config inheritance cycle detected at "${d.file}"`;
1166
- return Effect$1.fail(ConfigFileInvalidError.make({
1167
- file: d.file,
1168
- cause: message
1169
- }));
1170
- }), Match.exhaustive);
1171
- });
1172
- return yield* loop(initialExtendsStepState, configFile, document);
1173
- });
1174
- }
1175
- const forkCoreSchema = S.toJsonSchemaDocument(forkOptionsSchema).schema;
1176
- const decodeOptions = S.decodeUnknownResult(StrykerOptionsSchema, { errors: "all" });
1177
- function recordOf(value) {
1178
- return { ...value };
1179
- }
1180
- const thresholdErrors = (options) => Match.value(options.thresholds.high < options.thresholds.low).pipe(Match.when(true, () => ["Config option \"thresholds.high\" should be higher than \"thresholds.low\"."]), Match.orElse(() => []));
1181
- const ignoreStaticErrors = (options) => Match.value(options.ignoreStatic && options.coverageAnalysis !== "perTest").pipe(Match.when(true, () => [`Config option "${optionsPath("ignoreStatic")}" is not supported with coverage analysis "${options.coverageAnalysis}". Either turn off "${optionsPath("ignoreStatic")}", or configure "${optionsPath("coverageAnalysis")}" to be "perTest".`]), Match.orElse(() => []));
1182
- const matchLineNumber = (match, group) => Number.parseInt(match[group] ?? "", 10);
1183
- const startLineErrors = (index, mutationRange, start) => Match.value(start < 1).pipe(Match.when(true, () => [`Config option "mutate[${index}]" is invalid. Mutation range "${mutationRange}" is invalid, line ${start} does not exist (lines start at 1).`]), Match.orElse(() => []));
1184
- const lineOrderErrors = (index, mutationRange, start, end) => Match.value(start > end).pipe(Match.when(true, () => [`Config option "mutate[${index}]" is invalid. Mutation range "${mutationRange}" is invalid. The "from" line number (${start}) should be less then the "to" line number (${end}).`]), Match.orElse(() => []));
1185
- const mutationRangeBoundErrors = (index, match) => {
1186
- const mutationRange = match[2];
1187
- const start = matchLineNumber(match, 3);
1188
- const end = matchLineNumber(match, 5);
1189
- return [...startLineErrors(index, mutationRange, start), ...lineOrderErrors(index, mutationRange, start, end)];
1190
- };
1191
- const requireUnmagicalMutationRange = (mutateString, index, match) => Match.value(isGlob(mutateString)).pipe(Match.when(true, () => [`Config option "mutate[${index}]" is invalid. Cannot combine a glob expression with a mutation range in "${mutateString}".`]), Match.orElse(() => mutationRangeBoundErrors(index, match)));
1192
- const mutationRangeErrors = (mutateString, index) => Match.value(MUTATION_RANGE_REGEX.exec(mutateString)).pipe(Match.when((match) => match !== null, (match) => requireUnmagicalMutationRange(mutateString, index, match)), Match.orElse(() => []));
1193
- const warnOnIgnoredNodeArgs = (nodeArgs) => Match.value(nodeArgs.length > 0).pipe(Match.when(true, () => Effect$1.logWarning("Using \"testRunnerNodeArgs\" together with the \"command\" test runner is not supported, these arguments will be ignored. You can add your custom arguments by setting the \"commandRunner.command\" option.")), Match.orElse(() => Effect$1.void));
1194
- const warnOnCommandRunnerNodeArgs = (options) => Match.value(isCommandRunner(options.testRunner)).pipe(Match.when(true, () => warnOnIgnoredNodeArgs(options.testRunnerNodeArgs)), Match.orElse(() => Effect$1.void));
1195
- const customValidationErrors = (options) => [
1196
- ...thresholdErrors(options),
1197
- ...ignoreStaticErrors(options),
1198
- ...options.mutate.flatMap(mutationRangeErrors)
1199
- ];
1200
- function customValidation(options) {
1201
- const additionalErrors = customValidationErrors(options);
1202
- return warnOnCommandRunnerNodeArgs(options).pipe(Effect$1.flatMap(() => logConfigErrors(additionalErrors)), Effect$1.flatMap(() => throwErrorIfNeeded(additionalErrors)));
1203
- }
1204
- function schemaValidate(options) {
1205
- const decoded = decodeOptions(options);
1206
- if (Result.isFailure(decoded)) return failWithConfigErrors(describeErrors(decoded.failure));
1207
- Object.assign(options, decoded.success);
1208
- return Effect$1.succeed(decoded.success);
1209
- }
1210
- const configErrorHeadline = (errors) => Match.value(errors.length === 1).pipe(Match.when(true, () => "Please correct this configuration error and try again."), Match.orElse(() => "Please correct these configuration errors and try again."));
1211
- const configErrorMessage = (errors) => `${configErrorHeadline(errors)} ${errors.join(" ")}`;
1212
- const logConfigErrors = (errors) => Effect$1.gen(function* () {
1213
- for (const error of errors) yield* Effect$1.logError(error);
1214
- });
1215
- const failWithConfigErrors = (errors) => logConfigErrors(errors).pipe(Effect$1.flatMap(() => Effect$1.fail(ConfigError.make({ message: configErrorMessage(errors) }))));
1216
- function throwErrorIfNeeded(errors) {
1217
- if (errors.length === 0) return Effect$1.void;
1218
- return Effect$1.fail(ConfigError.make({ message: configErrorMessage(errors) }));
1219
- }
1220
- const OPTIONS_ADDED_BY_STRYKER = [
1221
- "set",
1222
- "configFile",
1223
- "$schema"
1224
- ];
1225
- const schemaPropertyNames = (schema) => Match.value(schema["properties"]).pipe(Match.when(isNonNullObject, (properties) => Object.keys(recordOf(properties))), Match.orElse(() => []));
1226
- const excessOptionNames = (options, schema) => {
1227
- const schemaKeys = schemaPropertyNames(schema);
1228
- return Object.keys(options).filter((key) => key.endsWith("_comment") === false).filter((key) => OPTIONS_ADDED_BY_STRYKER.includes(key) === false).filter((key) => schemaKeys.includes(key) === false);
1229
- };
1230
- const warnAboutUnknownOptions = (options, excessNames) => Effect$1.gen(function* () {
1231
- for (const excessPropertyName of excessNames) yield* Effect$1.logWarning(`Unknown stryker config option "${excessPropertyName}".`);
1232
- const pluginsJson = yield* S.encodeEffect(S.fromJsonString(S.Array(S.String)))([...options.plugins]).pipe(Effect$1.orDie);
1233
- yield* Effect$1.logWarning(`Possible causes:
1234
- * Is it a typo on your end?
1235
- * Did you only write this property as a comment? If so, please postfix it with "_comment".
1236
- * You might be missing a plugin that is supposed to use it. Stryker loaded plugins from: ${pluginsJson}
1237
- * The plugin that is using it did not contribute explicit validation.
1238
- (disable "${optionsPath("warnings", "unknownOptions")}" to ignore this warning)`);
1239
- });
1240
- const unknownOptionWarning = (options, schema) => {
1241
- const excessNames = excessOptionNames(options, schema);
1242
- return Match.value(excessNames.length > 0).pipe(Match.when(true, () => warnAboutUnknownOptions(options, excessNames)), Match.orElse(() => Effect$1.void));
1243
- };
1244
- function markExcessOptions(options, schema) {
1245
- return Effect$1.gen(function* () {
1246
- if (isWarningEnabled("unknownOptions", options.warnings)) yield* unknownOptionWarning(options, schema);
1247
- });
1248
- }
1249
- const logUnserializableWarnings = (unserializables) => Effect$1.gen(function* () {
1250
- for (const unserializable of unserializables) yield* Effect$1.logWarning(`Config option "${unserializable.path.join(".")}" is not (fully) serializable. ${unserializable.reason}. Any test runner or checker worker processes might not receive this value as intended.`);
1251
- yield* Effect$1.logWarning(`(disable ${optionsPath("warnings", "unserializableOptions")} to ignore this warning)`);
1252
- });
1253
- const warnAboutUnserializableOptions = (options) => Match.value(findUnserializables(options)).pipe(Match.when(void 0, () => Effect$1.void), Match.orElse((unserializables) => logUnserializableWarnings(unserializables)));
1254
- function markUnserializableOptions(options) {
1255
- return Effect$1.gen(function* () {
1256
- if (isWarningEnabled("unserializableOptions", options.warnings)) yield* warnAboutUnserializableOptions(options);
1257
- });
1258
- }
1259
- function markOptions(options, schema) {
1260
- return Effect$1.gen(function* () {
1261
- yield* markExcessOptions(options, schema);
1262
- yield* markUnserializableOptions(options);
1263
- });
1264
- }
1265
- function validateOptions(options, schema) {
1266
- return Effect$1.gen(function* () {
1267
- const typed = yield* schemaValidate(options);
1268
- yield* customValidation(typed);
1269
- yield* markOptions(typed, schema);
1270
- return typed;
1271
- });
1272
- }
1273
- const createDefaultOptions = S.decodeEffect(StrykerOptionsSchema)({}).pipe(Effect$1.orDie);
1274
- Effect$1.map(createDefaultOptions, (opts) => deepFreeze(opts));
1275
- `
1276
- Example of how a config file should look:
1277
- /**
1278
- * @type {import('@systemfsoftware/stryker-js-plugin-interface').StrykerOptions}
1279
- */
1280
- export default {
1281
- // Your options here!
1282
- }
1283
-
1284
- A config file is a TypeScript or ESM JavaScript module: stryker.conf.ts, stryker.config.ts, or the same names with a .mts, .js, or .mjs extension.
1285
-
1286
- See https://stryker-mutator.io/docs/stryker-js/config-file for more information.`.trim();
1287
- function exists(fileName) {
1288
- return Effect$1.gen(function* () {
1289
- return yield* (yield* FileSystem.FileSystem).access(fileName).pipe(Effect$1.as(true), Effect$1.catchTag("PlatformError", (error) => Match.value(error.reason).pipe(Match.tag("NotFound", () => Effect$1.succeed(false)), Match.orElse(() => Effect$1.fail(ConfigFileUnreadableError.make({
1290
- file: fileName,
1291
- cause: error
1292
- }))))), Effect$1.mapError((error) => ConfigFileUnreadableError.make({
1293
- file: fileName,
1294
- cause: error
1295
- })));
1296
- });
1297
- }
1298
- const requireExistingConfigFile = (configFileName) => exists(configFileName).pipe(Effect$1.flatMap((doesExist) => Match.value(doesExist).pipe(Match.when(true, () => Effect$1.succeed(configFileName)), Match.orElse(() => Effect$1.fail(ConfigFileNotFoundError.make({ file: configFileName }))))));
1299
- const firstExistingConfigFile = (fileNames) => Option.match(Option.fromUndefinedOr(fileNames[0]), {
1300
- onNone: () => Effect$1.succeedNone,
1301
- onSome: (head) => exists(head).pipe(Effect$1.flatMap((doesExist) => {
1302
- if (doesExist) return Effect$1.succeedSome(head);
1303
- return firstExistingConfigFile(fileNames.slice(1));
1304
- }))
1305
- });
1306
- const configFileFor = (configFileName) => Effect$1.gen(function* () {
1307
- const pathService = yield* Path.Path;
1308
- const supported = SUPPORTED_CONFIG_FILE_EXTENSIONS.includes(configFileExtension(configFileName, pathService));
1309
- const legacy = isLegacyConfigFile(configFileName, pathService);
1310
- return yield* Match.value(supported).pipe(Match.when(true, () => requireExistingConfigFile(configFileName)), Match.orElse(() => Effect$1.fail(ConfigFileUnsupportedError.make({
1311
- file: configFileName,
1312
- hint: Match.value(legacy).pipe(Match.when(true, () => legacyConfigHint(configFileName)), Match.orElse(() => unsupportedConfigHint(configFileName)))
1313
- }))));
1314
- });
1315
- const firstLegacyConfigFile = () => firstExistingConfigFile([...LEGACY_CONFIG_FILE_NAMES]);
1316
- const legacyConfigError = (file) => ConfigFileUnsupportedError.make({
1317
- file,
1318
- hint: legacyConfigHint(file)
1319
- });
1320
- const legacyConfigWarning = (file, supportedFile) => Effect$1.logWarning(shadowedLegacyWarning(file, supportedFile));
1321
- const refuseLegacyOnlyProject = () => firstLegacyConfigFile().pipe(Effect$1.flatMap((legacyFile) => Option.match(legacyFile, {
1322
- onNone: () => Effect$1.succeedNone,
1323
- onSome: (file) => Effect$1.fail(legacyConfigError(file))
1324
- })));
1325
- const warnShadowedLegacyConfig = (supportedFile) => firstLegacyConfigFile().pipe(Effect$1.flatMap((legacyFile) => Option.match(legacyFile, {
1326
- onNone: () => Effect$1.succeed(supportedFile),
1327
- onSome: (file) => Effect$1.as(legacyConfigWarning(file, supportedFile), supportedFile)
1328
- })));
1329
- const discoverConfigFile = () => firstExistingConfigFile([...SUPPORTED_CONFIG_FILE_NAMES]).pipe(Effect$1.flatMap((found) => Option.match(found, {
1330
- onNone: () => refuseLegacyOnlyProject().pipe(Effect$1.map((legacy) => Option.getOrUndefined(legacy))),
1331
- onSome: (supportedFile) => warnShadowedLegacyConfig(supportedFile)
1332
- })));
1333
- function findConfigFile(configFileName) {
1334
- return Match.value(configFileName).pipe(Match.when(Match.string, (fileName) => configFileFor(fileName)), Match.orElse(() => discoverConfigFile()));
1335
- }
1336
- const resolveChildExtends = (configFile, child, configEnv) => Match.value("extends" in child).pipe(Match.when(true, () => resolveExtends(configFile, child, configEnv)), Match.orElse(() => Effect$1.succeed(child)));
1337
- const isCiEnvironment = Config.string("CI").pipe(Effect$1.map((value) => Match.value(value.trim().toLowerCase()).pipe(Match.when("", () => false), Match.when("0", () => false), Match.when("false", () => false), Match.orElse(() => true))), Effect$1.orElseSucceed(() => false));
1338
- function loadOptionsFromConfigFile(cliOptions, configEnv) {
1339
- return findConfigFile(cliOptions["configFile"]).pipe(Effect$1.flatMap((configFile) => Match.value(configFile).pipe(Match.when(void 0, () => Effect$1.succeed({})), Match.orElse((found) => readConfigModule(found, configEnv).pipe(Effect$1.flatMap((child) => resolveChildExtends(found, child, configEnv)))))));
1340
- }
1341
- function readConfig(cliOptions, invocation) {
1342
- return Effect$1.gen(function* () {
1343
- const configEnv = {
1344
- command: invocation.command,
1345
- mode: invocation.mode,
1346
- isDryRun: cliOptions["dryRunOnly"] === true,
1347
- isCi: yield* isCiEnvironment
1348
- };
1349
- const cliRecord = yield* S.decodeUnknownEffect(ConfigDocumentSchema)(cliOptions).pipe(Effect$1.orDie);
1350
- const fileRecord = yield* loadOptionsFromConfigFile(cliRecord, configEnv);
1351
- const fileOptions = yield* Result.match(S.decodeUnknownResult(ConfigDocumentSchema)(fileRecord), {
1352
- onFailure: (cause) => Effect$1.fail(ConfigFileInvalidError.make({
1353
- file: "config",
1354
- cause
1355
- })),
1356
- onSuccess: (options) => Effect$1.succeed(options)
1357
- });
1358
- const merged = mergeConfig(fileOptions, cliRecord);
1359
- const decoded = S.decodeUnknownResult(StrykerOptionsSchema)(merged);
1360
- if (Result.isFailure(decoded)) throw ConfigError.make({ message: configErrorMessage(describeErrors(decoded.failure)) });
1361
- return decoded.success;
1362
- });
1363
- }
1364
- const loadConfigCell = readConfig;
1365
- //#endregion
1366
- //#region src/Plugins.schema.ts
1367
- /**
1368
- * Plugins capability — declarations for plugin module shapes and load failures.
1369
- */
1370
- const PluginDescriptorSchema = Schema.Union([Schema.Struct({
1371
- kind: WorkerPluginKind,
1372
- name: Schema.String,
1373
- workerEntry: WorkerEntryUrl
1374
- }), Schema.Struct({
1375
- kind: Schema.Literals(["Evaluator"]),
1376
- name: Schema.String
1377
- })]);
1378
- const PluginModuleSchema = Schema.Struct({ strykerPlugins: Schema.Array(PluginDescriptorSchema) });
1379
- const isShouldIgnore = (value) => typeof value === "function";
1380
- const ignoreNothing = () => void 0;
1381
- const IgnorerEntrySchema = Schema.Struct({
1382
- name: Schema.String,
1383
- shouldIgnore: Schema.declare(isShouldIgnore, { toArbitrary: () => (fc) => fc.constant(ignoreNothing) })
1384
- });
1385
- const IgnorerModuleSchema = Schema.Struct({ strykerIgnorers: Schema.Array(IgnorerEntrySchema) });
1386
- const SchemaValidationContributionSchema = Schema.Struct({ strykerValidationSchema: Schema.Record(Schema.String, Schema.Unknown) });
1387
- var PluginNotFoundError = class extends Schema.TaggedError()("PluginNotFoundError", { descriptor: Schema.String }) {
1388
- exitClass = "ConfigError";
1389
- };
1390
- var PluginLoadFailedError = class extends Schema.TaggedError()("PluginLoadFailedError", {
1391
- descriptor: Schema.String,
1392
- cause: Schema.Unknown
1393
- }) {
1394
- exitClass = "InternalError";
1395
- };
1396
- //#endregion
1397
- //#region src/Plugins.ts
1398
- const NO_IGNORERS = [];
1399
- const buildPluginLoadPlan = (entries) => {
1400
- const declarations = entries.flatMap((entry, index) => (entry.plugins ?? []).map((plugin) => ({
1401
- plugin,
1402
- moduleName: entry.moduleName,
1403
- entryIndex: index
1404
- })));
1405
- const shadowingState = declarations.reduce((acc, declaration, position) => {
1406
- const key = `${declaration.plugin.kind}:${declaration.plugin.name}`;
1407
- return {
1408
- seen: HashMap.set(acc.seen, key, {
1409
- position,
1410
- entryIndex: declaration.entryIndex
1411
- }),
1412
- shadowings: Option.match(HashMap.get(acc.seen, key), {
1413
- onNone: () => acc.shadowings,
1414
- onSome: (previous) => [...acc.shadowings, {
1415
- kind: declaration.plugin.kind,
1416
- name: declaration.plugin.name,
1417
- shadowedIndex: previous.entryIndex,
1418
- winnerIndex: declaration.entryIndex
1419
- }]
1420
- })
1421
- };
1422
- }, {
1423
- seen: HashMap.empty(),
1424
- shadowings: []
1425
- });
1426
- const winningDeclarations = declarations.filter((declaration, position) => Option.match(HashMap.get(shadowingState.seen, `${declaration.plugin.kind}:${declaration.plugin.name}`), {
1427
- onNone: () => false,
1428
- onSome: (winner) => winner.position === position
1429
- }));
1430
- const pluginsByKind = winningDeclarations.reduce((map, declaration) => Option.match(HashMap.get(map, declaration.plugin.kind), {
1431
- onNone: () => HashMap.set(map, declaration.plugin.kind, [declaration.plugin]),
1432
- onSome: (existing) => HashMap.set(map, declaration.plugin.kind, [...existing, declaration.plugin])
1433
- }), HashMap.empty());
1434
- const pluginSources = winningDeclarations.map((declaration) => Match.value(declaration.plugin).pipe(Match.when((plugin) => plugin.kind === "Evaluator", (evaluator) => ({
1435
- kind: "Evaluator",
1436
- name: evaluator.name,
1437
- modulePath: declaration.moduleName
1438
- })), Match.orElse((worker) => ({
1439
- kind: worker.kind,
1440
- name: worker.name,
1441
- modulePath: declaration.moduleName,
1442
- workerEntry: worker.workerEntry
1443
- }))));
1444
- const pluginModulePaths = entries.flatMap((entry) => Option.match(Option.fromUndefinedOr(entry.plugins), {
1445
- onNone: () => [],
1446
- onSome: () => [entry.moduleName]
1447
- }));
1448
- return {
1449
- schemaContributions: entries.flatMap((entry) => Option.match(Option.fromUndefinedOr(entry.schemaContribution), {
1450
- onNone: () => [],
1451
- onSome: (value) => [value]
1452
- })),
1453
- pluginsByKind,
1454
- pluginModulePaths,
1455
- pluginSources,
1456
- shadowings: shadowingState.shadowings
1457
- };
1458
- };
1459
- const failPluginLoad = (descriptor, error) => Effect$1.logWarning(`Error during loading "${descriptor}" plugin`).pipe(Effect$1.andThen(() => Effect$1.fail(PluginLoadFailedError.make({
1460
- descriptor,
1461
- cause: error
1462
- }))));
1463
- const modulePluginContributions = (module) => Match.value(Predicate.hasProperty(module, "strykerPlugins")).pipe(Match.when(true, () => Schema.decodeUnknownResult(PluginModuleSchema)(module).pipe(Result.map((pluginModule) => pluginModule.strykerPlugins))), Match.orElse(() => Result.succeed(void 0)));
1464
- const moduleIgnorers = (module) => Match.value(Predicate.hasProperty(module, "strykerIgnorers")).pipe(Match.when(true, () => Schema.decodeUnknownResult(IgnorerModuleSchema)(module).pipe(Result.map((ignorerModule) => ignorerModule.strykerIgnorers))), Match.orElse(() => Result.succeed(void 0)));
1465
- const moduleSchemaContribution = (module) => Match.value(module).pipe(Match.when(hasValidationSchemaContribution, (contribution) => contribution.strykerValidationSchema), Match.orElse(() => void 0));
1466
- const pluginContributionsOf = (module) => Result.flatMap(moduleIgnorers(module), (ignorers) => Result.map(modulePluginContributions(module), (plugins) => ({
1467
- plugins,
1468
- ignorers,
1469
- schemaContribution: moduleSchemaContribution(module)
1470
- })));
1471
- const hasContribution = (contributions) => [
1472
- contributions.plugins,
1473
- contributions.ignorers,
1474
- contributions.schemaContribution
1475
- ].some((contribution) => contribution !== void 0);
1476
- const warnUndescribedPluginModule = (descriptor) => Effect$1.logWarning(`Module "${descriptor}" did not contribute a StrykerJS plugin. It didn't export a "strykerPlugins", "strykerIgnorers", or "strykerValidationSchema".`).pipe(Effect$1.as(void 0));
1477
- const describeLoadedPlugin = (descriptor, module) => Result.match(pluginContributionsOf(module), {
1478
- onFailure: (cause) => failPluginLoad(descriptor, cause),
1479
- onSuccess: (contributions) => Match.value(hasContribution(contributions)).pipe(Match.when(true, () => Effect$1.succeed(contributions)), Match.orElse(() => warnUndescribedPluginModule(descriptor)))
1480
- });
1481
- function loadPlugin(descriptor, entrypoint) {
1482
- return Effect$1.gen(function* () {
1483
- yield* Effect$1.logDebug(`Loading plugin ${descriptor}`);
1484
- const maybeModule = yield* importModule(entrypoint).pipe(Effect$1.catch((error) => failPluginLoad(descriptor, error)));
1485
- const module = Option.getOrUndefined(Option.fromUndefinedOr(maybeModule));
1486
- if (module === void 0) return;
1487
- return yield* describeLoadedPlugin(descriptor, module);
1488
- });
1489
- }
1490
- function loadPlugins(pluginDescriptors) {
1491
- return Effect$1.gen(function* () {
1492
- const path = yield* Path.Path;
1493
- const entrypoints = yield* Effect$1.forEach(Array$1.fromIterable(HashSet.fromIterable(pluginDescriptors)), (specifier) => path.fromFileUrl(new URL(specifier)).pipe(Effect$1.map((entrypoint) => ({
1494
- specifier,
1495
- entrypoint
1496
- })), Effect$1.mapError((cause) => PluginLoadFailedError.make({
1497
- descriptor: specifier,
1498
- cause
1499
- }))), { concurrency: "unbounded" });
1500
- const loaded = yield* Effect$1.forEach(entrypoints, (resolved) => loadPlugin(resolved.specifier, resolved.entrypoint).pipe(Effect$1.map((plugin) => {
1501
- if (plugin === void 0) return;
1502
- return {
1503
- ...plugin,
1504
- moduleName: resolved.specifier
1505
- };
1506
- })), { concurrency: "unbounded" }).pipe(Effect$1.map((arr) => arr.filter(Predicate.isNotNullish)));
1507
- const ignorers = loaded.flatMap((entry) => entry.ignorers ?? NO_IGNORERS);
1508
- const entries = loaded.map((entry) => ({
1509
- moduleName: entry.moduleName,
1510
- plugins: entry.plugins,
1511
- schemaContribution: entry.schemaContribution
1512
- }));
1513
- const plan_ = buildPluginLoadPlan(entries);
1514
- for (const shadowing of plan_.shadowings) yield* Effect$1.logWarning(`Plugin "${shadowing.name}" of kind "${shadowing.kind}" at index ${shadowing.winnerIndex} shadows plugin at index ${shadowing.shadowedIndex}.`);
1515
- return {
1516
- schemaContributions: plan_.schemaContributions,
1517
- pluginsByKind: plan_.pluginsByKind,
1518
- pluginModulePaths: plan_.pluginModulePaths,
1519
- pluginSources: plan_.pluginSources,
1520
- ignorers
1521
- };
1522
- });
1523
- }
1524
- const pluginUrlsFromOptions = (options) => [
1525
- ...options.plugins,
1526
- ...options.appendPlugins,
1527
- ...options.ignorers,
1528
- ...Match.value(options.testRunner).pipe(Match.when(isCustomTestRunner, (runner) => [runner.plugin]), Match.orElse(() => [])),
1529
- ...options.checkers.map((checker) => checker.plugin)
1530
- ];
1531
- function hasValidationSchemaContribution(module) {
1532
- return Schema.is(SchemaValidationContributionSchema)(module);
1533
- }
1534
- const findByKindAndName = (items, kind, name) => Option.fromUndefinedOr(items.find((item) => item.kind === kind && item.name.toLowerCase() === name.toLowerCase()));
1535
- const findContribution = (descriptors, kind, name) => Effect$1.fromOption(Option.filter(findByKindAndName(descriptors, kind, name), (descriptor) => descriptor.kind === kind), () => PluginNotFoundError.make({ descriptor: `${kind}:${name} (available: ${descriptors.map((d) => d.name).join(", ")})` }));
1536
- function findPlugin(pluginsByKind, kind, name) {
1537
- return Option.match(HashMap.get(pluginsByKind, kind), {
1538
- onNone: () => Effect$1.fail(PluginNotFoundError.make({ descriptor: `${kind}:${name} (no ${kind} plugins were loaded)` })),
1539
- onSome: (descriptors) => findContribution(descriptors, kind, name)
1540
- });
1541
- }
1542
- function create(pluginsByKind, kind, name) {
1543
- return findPlugin(pluginsByKind, kind, name);
1544
- }
1545
- function createAll(pluginsByKind, kind) {
1546
- const descriptors = HashMap.get(pluginsByKind, kind);
1547
- if (Option.isNone(descriptors)) return Effect$1.succeed([]);
1548
- return Effect$1.succeed(descriptors.value.filter((d) => d.kind === kind));
1549
- }
1550
- //#endregion
1551
- //#region src/plugin-worker-entry.ts
1552
- const missingWorkerEntry = (stage, kind, name) => (failure) => StageError.make({
1553
- stage,
1554
- reason: `the ${kind} plugin "${name}" is not among the loaded plugins`,
1555
- cause: failure
1556
- });
1557
- const requiredSource = (params) => Effect$1.fromOption(findByKindAndName(params.loaded.pluginSources, params.kind, params.name), () => PluginNotFoundError.make({ descriptor: `${params.kind}:${params.name}` }));
1558
- const resolvePluginWorkerEntry = (params) => requiredSource(params).pipe(Effect$1.map((source) => ({
1559
- kind: params.kind,
1560
- entrypoint: source.workerEntry
1561
- })));
1562
- const resolveConfiguredWorkerSpawn = (params) => {
1563
- const byName = (name) => requiredSource({
1564
- loaded: params.loaded,
1565
- kind: params.kind,
1566
- name
1567
- }).pipe(Effect$1.map((source) => ({
1568
- name,
1569
- spawn: {
1570
- kind: params.kind,
1571
- entrypoint: source.workerEntry
1572
- }
1573
- })));
1574
- if (typeof params.configured === "string") return byName(params.configured);
1575
- const pluginUrl = params.configured.plugin;
1576
- return Effect$1.fromOption(Option.map(Option.fromUndefinedOr(params.loaded.pluginSources.find((source) => source.kind === params.kind && source.modulePath === pluginUrl)), (source) => ({
1577
- name: source.name,
1578
- spawn: {
1579
- kind: params.kind,
1580
- entrypoint: source.workerEntry
1581
- }
1582
- })), () => PluginNotFoundError.make({ descriptor: `${params.kind}:${pluginUrl}` }));
1583
- };
1584
- //#endregion
1585
- export { ExtendsStepRefused as $, makeConcurrency as A, partsOfEffectSpan as B, nodePlatformLayer as C, IdGenerator as D, makeWorkerClient as E, isVmRunner as F, ConfigError as G, ALWAYS_IGNORE as H, vmRunnerCapabilities as I, ConfigFileUnreadableError as J, ConfigFileInvalidError as K, vmRunnerName as L, OutOfMemoryError as M, WorkerBootTimeoutError as N, classifyWorkerExit as O, VmRunner as P, ExtendsStepRead as Q, vmTestRunner as R, makeChildProcessTestRunner as S, connectRetry as T, MUTATION_RANGE_REGEX as U, StrykerError as V, ConfigDocumentSchema as W, ExtendsStepDocumentSchema as X, ConfigFileUnsupportedError as Y, ExtendsStepDone as Z, readConfig as _, createAll as a, ReadConfigCommand as at, buildTestRunner as b, PluginLoadFailedError as c, survivorsPriorReport as ct, describeErrors as d, PrepareError as dt, ExtendsStepResolve as et, forkCoreSchema as f, StageError as ft, mergeConfigs as g, loadConfigCell as h, create as i, MergeResult as it, ChildProcessCrashedError as j, layer as k, PluginNotFoundError as l, compileIgnoreRule as lt, initialExtendsStepState as m, resolveConfiguredWorkerSpawn as n, ImportedModuleSchema as nt, loadPlugins as o, extendsPropertySchema as ot, importModule as p, ConfigFileNotFoundError as q, resolvePluginWorkerEntry as r, MergeCommand as rt, pluginUrlsFromOptions as s, forkOptionsSchema as st, missingWorkerEntry as t, ExtendsStepStateSchema as tt, decideExtendsStep as u, matchesGlob as ut, resolveExtends as v, WorkerLauncher as w, isCommandRunner as x, validateOptions as y, encodeWorkerOptions as z };