@systemfsoftware/stryker-js 17.0.2 → 22.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +178 -0
- package/README.md +44 -29
- package/dist/{Run.schema-7DBK1X32.d.mts → Run.schema-CYduD68z.d.mts} +7 -3
- package/dist/RunEnvironment.service-D4so1HnE.d.mts +4912 -0
- package/dist/config.d.mts +2 -2
- package/dist/config.mjs +1 -1
- package/dist/events.d.mts +1 -1
- package/dist/{index-y-Pg3XgT.d.mts → index-ClWSuUog.d.mts} +1292 -946
- package/dist/index.d.mts +229 -54
- package/dist/index.mjs +625 -17
- package/dist/main.mjs +44203 -28065
- package/dist/{merge-config-ruui2TPS.mjs → merge-config-BWQeU3-p.mjs} +413 -56
- package/dist/{merge-config-52qu-jD7.d.mts → merge-config-D0F1cI43.d.mts} +1 -1
- package/dist/promises.d.mts +3 -3
- package/dist/promises.mjs +2 -2
- package/dist/{run-stages-DnDU_wrY.mjs → run-stages-BnFibYAg.mjs} +10644 -7081
- package/package.json +29 -29
- package/dist/RunEnvironment.service-Cluh-Bxt.d.mts +0 -2999
package/dist/index.mjs
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
2
|
-
import { i as Mutant_exports, l as Report_exports, n as mergeConfigs } from "./merge-config-
|
|
3
|
-
import { $ as
|
|
2
|
+
import { i as Mutant_exports, l as Report_exports, n as mergeConfigs } from "./merge-config-BWQeU3-p.mjs";
|
|
3
|
+
import { $ as IncrementalReportSchema, $t as checkCell, A as optionalField, At as ExtendsStepDone, B as nodePlatformLayer, Bt as createDefaultOptions, C as offerSkipsIfAny, Ct as WorkerLauncher, D as dryRunChoiceOf, Dt as importModule, E as reusedTestCoverage, Et as decideExtendsStep, F as diffScopeDecisionOf, Ft as ConfigError, G as git_diff_service_exports, Gt as ImportedModuleSchema, H as SourceParser, Ht as ConfigDocumentSchema, I as DryRunCoverageReused, It as ConfigFileInvalidError, J as git_diff_schema_exports, Jt as ReadConfigCommand, K as ChangedSince, Kt as MergeCommand, L as scoped$1, Lt as ConfigFileNotFoundError, M as relativeNormalizedFileName, Mt as ExtendsStepRefused, N as requireDryRun, Nt as ExtendsStepResolve, O as requireDryRunCommandOf, Ot as initialExtendsStepState, P as readProjectCell, Pt as FileMatcher, Q as classifyWorkerExit, Qt as groupCell, R as makeCheckerPoolHandle, Rt as ConfigFileUnreadableError, S as instrumentFiles, St as makeWorkerClient, T as readIncrementalReuse, Tt as ReproducerSchema, U as source_parser_service_exports, Ut as ExtendsStepDocumentSchema, V as layerWorkerProtocol, Vt as defaultOptions, W as GitDiff, Wt as ExtendsStepStateSchema, X as ClassifyWorkerExitCommand, Xt as forkOptionsSchema, Y as EmptyDirectory, Yt as extendsPropertySchema, Z as ClassifyWorkerExitDecision, Zt as survivorsPriorReport, _ as testRunnerConfigOf, _t as metricsResultFromFiles, a as HtmlReporter_exports, an as ShardPlan, at as RunEnvironment, b as planPrepare, bt as spawnReporterWorker, c as instrumentCell, cn as SpanTaxonomy_exports, ct as SurfacingFields, d as isCommandRunner, dn as CheckerSkippedRequested, dt as RunEvents, en as ChildProcessCrashedError, et as validateOptions, f as mutantDetailEventsOf, ft as RunIdentity, g as isVmRunner, gt as staticVerdictOf, h as readPrepare, ht as VerdictEnvelope, i as MachineConsole, in as RunEvent_exports$1, it as readConfig, j as import_closure_cell_exports, jt as ExtendsStepRead, k as incrementalReportTextsOf, kt as StrykerError, l as buildTestRunner, ln as CheckerAnsweredUnrequested, lt as SurvivorRef, m as admitPreparedProject, mt as generateRunId, n as RunEventDrain, nn as WorkerBootTimeoutError, nt as describeErrors, o as mutationTestCell, on as ShardPlanVersion, ot as IdGenerator, p as concurrencyCell, pt as buildVerdictEnvelope, q as WholeProject, qt as MergeResult, r as makeRunEventStream, rn as OutputMode_exports, rt as forkCoreSchema, s as prepareCell, sn as ShardProject, st as surfacedSurvivorsOf, t as strykerCell, tn as OutOfMemoryError, tt as loadConfigCell, u as makeChildProcessTestRunner, un as CheckerIgnoredWithoutRule, ut as StrykerPackage, v as vmRunnerPluginUrl, vt as MetricsResultFromReport, w as planInstrumentation, wt as make, x as enteringInstrumentPhase, xt as withPhaseSpan, y as vmTestRunnerConfig, yt as reporterWorkerFactory, z as programDigestOf, zt as ConfigFileUnsupportedError } from "./run-stages-BnFibYAg.mjs";
|
|
4
4
|
import { n as StageError } from "./Run.schema-DuaneDGd.mjs";
|
|
5
|
-
import { Cell, Sandwich, Workflow } from "@systemfsoftware/effect-cell-types";
|
|
5
|
+
import { Blueprint, Cell, Handle, Sandwich, Workflow } from "@systemfsoftware/effect-cell-types";
|
|
6
6
|
import * as S from "effect/Schema";
|
|
7
|
+
import * as Arr from "effect/Array";
|
|
8
|
+
import * as Effect from "effect/Effect";
|
|
7
9
|
import * as Option from "effect/Option";
|
|
10
|
+
import { dual } from "effect/Function";
|
|
8
11
|
import * as Context from "effect/Context";
|
|
9
|
-
import * as Effect from "effect/Effect";
|
|
10
12
|
import * as Boolean from "effect/Boolean";
|
|
11
|
-
import * as Arr from "effect/Array";
|
|
12
13
|
import * as Match from "effect/Match";
|
|
13
14
|
import * as Result from "effect/Result";
|
|
14
|
-
import { dual } from "effect/Function";
|
|
15
15
|
import "effect/Cause";
|
|
16
|
+
import * as Record from "effect/Record";
|
|
16
17
|
import * as FileSystem from "effect/FileSystem";
|
|
17
18
|
import * as Path from "effect/Path";
|
|
18
19
|
import * as Stream from "effect/Stream";
|
|
@@ -20,8 +21,10 @@ import * as Clock from "effect/Clock";
|
|
|
20
21
|
import * as Layer from "effect/Layer";
|
|
21
22
|
import * as Queue from "effect/Queue";
|
|
22
23
|
import "effect/Scope";
|
|
24
|
+
import * as MutableHashMap from "effect/MutableHashMap";
|
|
23
25
|
import * as SocketServer from "effect/socket/SocketServer";
|
|
24
26
|
import * as Fiber from "effect/Fiber";
|
|
27
|
+
import * as HashSet from "effect/HashSet";
|
|
25
28
|
import * as DateTime from "effect/DateTime";
|
|
26
29
|
import * as Ref from "effect/Ref";
|
|
27
30
|
import * as Stdio from "effect/Stdio";
|
|
@@ -29,12 +32,20 @@ import { NodeSocketServer } from "@effect/platform-node";
|
|
|
29
32
|
import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner";
|
|
30
33
|
import * as FiberSet from "effect/FiberSet";
|
|
31
34
|
import * as Socket from "effect/socket/Socket";
|
|
35
|
+
import "effect/Console";
|
|
32
36
|
import { McpProtocol, McpServer, Tool, Toolkit } from "effect/ai";
|
|
37
|
+
//#region src/Checker/Checker.cell.ts
|
|
38
|
+
const checkGroupedCell = groupCell.pipe(Cell.flatMap((groups) => Cell.mapInput(Cell.collect(checkCell, (perGroup) => perGroup.flat()), (input) => groups.map((group) => ({
|
|
39
|
+
...input,
|
|
40
|
+
plans: group
|
|
41
|
+
})))));
|
|
42
|
+
//#endregion
|
|
33
43
|
//#region src/Checker/mod.ts
|
|
34
44
|
var Checker_exports = /* @__PURE__ */ __exportAll({
|
|
35
45
|
CheckerAnsweredUnrequested: () => CheckerAnsweredUnrequested,
|
|
46
|
+
CheckerIgnoredWithoutRule: () => CheckerIgnoredWithoutRule,
|
|
36
47
|
CheckerSkippedRequested: () => CheckerSkippedRequested,
|
|
37
|
-
|
|
48
|
+
checkGroupedCell: () => checkGroupedCell
|
|
38
49
|
});
|
|
39
50
|
//#endregion
|
|
40
51
|
//#region src/Configuration/mod.ts
|
|
@@ -72,15 +83,587 @@ var Configuration_exports = /* @__PURE__ */ __exportAll({
|
|
|
72
83
|
validateOptions: () => validateOptions
|
|
73
84
|
});
|
|
74
85
|
//#endregion
|
|
86
|
+
//#region src/plan-request.schema.ts
|
|
87
|
+
const CostsFieldSchema = S.StructWithRest(S.Struct({ costs: S.Record(S.String, S.Struct({
|
|
88
|
+
predictedMs: S.Finite,
|
|
89
|
+
actualMs: S.NullOr(S.Finite)
|
|
90
|
+
})).pipe(S.optional) }), [S.Record(S.String, S.Json)]);
|
|
91
|
+
const CompileErrorProbeSchema = S.Struct({ files: S.Record(S.String, S.Struct({ mutants: S.Array(S.Struct({ status: S.String })) })) });
|
|
92
|
+
var PlanShardsRefused = class extends S.TaggedError()("PlanShardsRefused", { detail: S.String }) {
|
|
93
|
+
exitClass = "ConfigError";
|
|
94
|
+
next = "Pass --target-seconds above 0 and --max-shards of 1 or more, and name each project directory once in --projects.";
|
|
95
|
+
get reason() {
|
|
96
|
+
return `stryker plan: the planner refused its input: ${this.detail}. ${this.next}`;
|
|
97
|
+
}
|
|
98
|
+
get message() {
|
|
99
|
+
return this.reason;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/plan-shards.workflow.ts
|
|
104
|
+
const { ceil, max, min, sign } = Math;
|
|
105
|
+
const MAX_COST_MS = Number.MAX_SAFE_INTEGER;
|
|
106
|
+
const CostMs = S.Finite.pipe(S.check(S.isBetween({
|
|
107
|
+
minimum: 0,
|
|
108
|
+
maximum: MAX_COST_MS
|
|
109
|
+
})));
|
|
110
|
+
const PositiveSeconds = S.Finite.pipe(S.check(S.isGreaterThan(0)));
|
|
111
|
+
const PositiveInt = S.Int.pipe(S.check(S.isGreaterThanOrEqualTo(1)));
|
|
112
|
+
const PlannedMutant = S.Struct({
|
|
113
|
+
project: S.String,
|
|
114
|
+
id: Mutant_exports.MutantId,
|
|
115
|
+
costMs: CostMs,
|
|
116
|
+
dependsOnDryRun: S.Boolean,
|
|
117
|
+
placementKey: S.optional(Mutant_exports.MutantId)
|
|
118
|
+
});
|
|
119
|
+
const mutantKeyOf = (mutant) => `\u0000${mutant.project}\u0000${mutant.id}`;
|
|
120
|
+
const plannedMutantsAreDistinct = S.makeFilter((mutants) => Boolean.match(new Set(mutants.map(mutantKeyOf)).size === mutants.length, {
|
|
121
|
+
onTrue: () => void 0,
|
|
122
|
+
onFalse: () => "two planned mutants share a project and a mutant id"
|
|
123
|
+
}), { arbitraryConstraint: { uniqueBy: mutantKeyOf } });
|
|
124
|
+
const PlannedMutants = S.Array(PlannedMutant).check(plannedMutantsAreDistinct);
|
|
125
|
+
var PlanShardsCommand = class extends S.TaggedClass()("PlanShardsCommand", {
|
|
126
|
+
targetSeconds: PositiveSeconds,
|
|
127
|
+
maxShards: S.optional(PositiveInt),
|
|
128
|
+
mutants: PlannedMutants,
|
|
129
|
+
dryRunCosts: S.Record(S.String, CostMs)
|
|
130
|
+
}) {
|
|
131
|
+
static [Workflow.InstrumentationBrand] = {};
|
|
132
|
+
};
|
|
133
|
+
const ShardPlanTypeId = Symbol.for("@systemfsoftware/stryker-js/PlannedShards");
|
|
134
|
+
var PlannedShard = class extends S.TaggedClass()("PlannedShard", {
|
|
135
|
+
index: S.Int,
|
|
136
|
+
count: S.Int,
|
|
137
|
+
predictedSeconds: Report_exports.NonNegativeFinite,
|
|
138
|
+
projects: S.Array(ShardProject)
|
|
139
|
+
}) {
|
|
140
|
+
[ShardPlanTypeId] = ShardPlanTypeId;
|
|
141
|
+
};
|
|
142
|
+
const compareText = (left, right) => sign(Number(left > right) - Number(left < right));
|
|
143
|
+
const compareProjectThenId = (left, right) => Boolean.match(compareText(left.project, right.project) === 0, {
|
|
144
|
+
onTrue: () => compareText(left.id, right.id),
|
|
145
|
+
onFalse: () => compareText(left.project, right.project)
|
|
146
|
+
});
|
|
147
|
+
const totalCostMsOf = (mutants) => mutants.reduce((total, mutant) => total + mutant.costMs, 0);
|
|
148
|
+
const dryRunCostOf = (command, project) => Option.getOrElse(Record.get(command.dryRunCosts, project), () => 0);
|
|
149
|
+
const dependentProjectsOf = (command) => {
|
|
150
|
+
const byProject = command.mutants.reduce((accumulated, mutant) => Boolean.match(mutant.dependsOnDryRun, {
|
|
151
|
+
onTrue: () => Record.set(accumulated, mutant.project, [...Option.getOrElse(Record.get(accumulated, mutant.project), () => []), mutant]),
|
|
152
|
+
onFalse: () => accumulated
|
|
153
|
+
}), {});
|
|
154
|
+
return [...Record.toEntries(byProject)].sort(([left], [right]) => compareText(left, right)).map(([project, mutants]) => ({
|
|
155
|
+
project,
|
|
156
|
+
dependentCostMs: totalCostMsOf(mutants),
|
|
157
|
+
dryRunCostMs: dryRunCostOf(command, project),
|
|
158
|
+
mutants
|
|
159
|
+
}));
|
|
160
|
+
};
|
|
161
|
+
const dryRunBinsNeededOf = (project, targetMs) => {
|
|
162
|
+
const remainingMs = targetMs - project.dryRunCostMs;
|
|
163
|
+
return Boolean.match(remainingMs > 0, {
|
|
164
|
+
onTrue: () => max(1, ceil(project.dependentCostMs / remainingMs)),
|
|
165
|
+
onFalse: () => Number.POSITIVE_INFINITY
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
const plannedLoadMsOf = (command, projects, targetMs) => totalCostMsOf(command.mutants) + projects.reduce((total, project) => {
|
|
169
|
+
const bins = dryRunBinsNeededOf(project, targetMs);
|
|
170
|
+
return Boolean.match(Number.isFinite(bins), {
|
|
171
|
+
onTrue: () => total + bins * project.dryRunCostMs,
|
|
172
|
+
onFalse: () => total
|
|
173
|
+
});
|
|
174
|
+
}, 0);
|
|
175
|
+
const neededShardsOf = (command, loadMs) => max(1, ceil(loadMs / 1e3 / command.targetSeconds));
|
|
176
|
+
const cappedShardsOf = (command, loadMs) => Option.match(Option.fromUndefinedOr(command.maxShards), {
|
|
177
|
+
onNone: () => neededShardsOf(command, loadMs),
|
|
178
|
+
onSome: (maxShards) => max(1, min(maxShards, neededShardsOf(command, loadMs)))
|
|
179
|
+
});
|
|
180
|
+
const shardCountOf = (command, loadMs) => min(cappedShardsOf(command, loadMs), max(command.mutants.length, 1));
|
|
181
|
+
const EMPTY_BIN = {
|
|
182
|
+
mutants: [],
|
|
183
|
+
load: 0
|
|
184
|
+
};
|
|
185
|
+
const emptyBins = (count) => Array.from({ length: count }, () => ({
|
|
186
|
+
mutants: [],
|
|
187
|
+
load: 0
|
|
188
|
+
}));
|
|
189
|
+
const indicesOf = (count) => Array.from({ length: count }, (_unused, index) => index);
|
|
190
|
+
const loadAt = (bins, index) => Option.getOrElse(Option.fromUndefinedOr(bins[index]), () => EMPTY_BIN).load;
|
|
191
|
+
const leastLoadedOf = (bins, indices) => indices.reduce((best, index) => Boolean.match(loadAt(bins, index) < loadAt(bins, best), {
|
|
192
|
+
onTrue: () => index,
|
|
193
|
+
onFalse: () => best
|
|
194
|
+
}), Option.getOrElse(Option.fromUndefinedOr(indices[0]), () => 0));
|
|
195
|
+
const compareLoadedBins = (left, right) => sign(sign(left.load - right.load) * 2 + sign(left.index - right.index));
|
|
196
|
+
const leastLoadedIndicesOf = (bins, count) => indicesOf(bins.length).map((index) => ({
|
|
197
|
+
index,
|
|
198
|
+
load: loadAt(bins, index)
|
|
199
|
+
})).sort(compareLoadedBins).slice(0, count).map((entry) => entry.index);
|
|
200
|
+
const placementKeyOf$1 = (mutant) => Option.getOrElse(Option.fromUndefinedOr(mutant.placementKey), () => mutantKeyOf(mutant));
|
|
201
|
+
const projectOfGroup = (members) => Option.match(Arr.head(members), {
|
|
202
|
+
onNone: () => "",
|
|
203
|
+
onSome: (head) => head.project
|
|
204
|
+
});
|
|
205
|
+
const placementGroupsOf = (mutants) => Record.toEntries(Arr.groupBy(mutants, placementKeyOf$1)).map(([key, members]) => {
|
|
206
|
+
const sorted = [...members].sort(compareProjectThenId);
|
|
207
|
+
return {
|
|
208
|
+
key,
|
|
209
|
+
project: projectOfGroup(sorted),
|
|
210
|
+
mutants: sorted,
|
|
211
|
+
costMs: totalCostMsOf(sorted),
|
|
212
|
+
dependsOnDryRun: sorted.some((mutant) => mutant.dependsOnDryRun)
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
const compareGroupsCostliestFirst = (left, right) => Boolean.match(left.costMs === right.costMs, {
|
|
216
|
+
onTrue: () => Boolean.match(compareText(left.project, right.project) === 0, {
|
|
217
|
+
onTrue: () => compareText(left.key, right.key),
|
|
218
|
+
onFalse: () => compareText(left.project, right.project)
|
|
219
|
+
}),
|
|
220
|
+
onFalse: () => sign(right.costMs - left.costMs)
|
|
221
|
+
});
|
|
222
|
+
const withGroupIn = (bins, index, group) => bins.map((bin, at) => Boolean.match(at === index, {
|
|
223
|
+
onTrue: () => ({
|
|
224
|
+
mutants: [...bin.mutants, ...group.mutants],
|
|
225
|
+
load: bin.load + group.costMs
|
|
226
|
+
}),
|
|
227
|
+
onFalse: () => bin
|
|
228
|
+
}));
|
|
229
|
+
const reservedBin = (bins, index, costMs) => bins.map((bin, at) => Boolean.match(at === index, {
|
|
230
|
+
onTrue: () => ({
|
|
231
|
+
mutants: bin.mutants,
|
|
232
|
+
load: bin.load + costMs
|
|
233
|
+
}),
|
|
234
|
+
onFalse: () => bin
|
|
235
|
+
}));
|
|
236
|
+
const projectsOf$1 = (mutants) => {
|
|
237
|
+
const byProject = mutants.reduce((accumulated, mutant) => Record.set(accumulated, mutant.project, [...Option.getOrElse(Record.get(accumulated, mutant.project), () => []), mutant.id]), {});
|
|
238
|
+
return [...Record.toEntries(byProject)].sort(([left], [right]) => compareText(left, right)).map(([project, ids]) => ({
|
|
239
|
+
project,
|
|
240
|
+
mutants: [...ids].sort(compareText)
|
|
241
|
+
}));
|
|
242
|
+
};
|
|
243
|
+
const shardsOf = (command) => {
|
|
244
|
+
const targetMs = command.targetSeconds * 1e3;
|
|
245
|
+
const dependentProjects = dependentProjectsOf(command);
|
|
246
|
+
const count = shardCountOf(command, plannedLoadMsOf(command, dependentProjects, targetMs));
|
|
247
|
+
const reserved = dependentProjects.reduce((accumulated, project) => {
|
|
248
|
+
const indices = leastLoadedIndicesOf(accumulated.bins, min(dryRunBinsNeededOf(project, targetMs), count));
|
|
249
|
+
return {
|
|
250
|
+
bins: indices.reduce((bins, index) => reservedBin(bins, index, project.dryRunCostMs), accumulated.bins),
|
|
251
|
+
indicesByProject: {
|
|
252
|
+
...accumulated.indicesByProject,
|
|
253
|
+
[project.project]: indices
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
}, {
|
|
257
|
+
bins: emptyBins(count),
|
|
258
|
+
indicesByProject: {}
|
|
259
|
+
});
|
|
260
|
+
const groups = placementGroupsOf(command.mutants);
|
|
261
|
+
const withDependents = groups.filter((group) => group.dependsOnDryRun).sort(compareGroupsCostliestFirst).reduce((bins, group) => withGroupIn(bins, leastLoadedOf(bins, Option.getOrElse(Record.get(reserved.indicesByProject, group.project), () => indicesOf(count))), group), reserved.bins);
|
|
262
|
+
return groups.filter((group) => Boolean.not(group.dependsOnDryRun)).sort(compareGroupsCostliestFirst).reduce((bins, group) => withGroupIn(bins, leastLoadedOf(bins, indicesOf(bins.length)), group), withDependents).map((bin, index) => PlannedShard.make({
|
|
263
|
+
index: index + 1,
|
|
264
|
+
count,
|
|
265
|
+
predictedSeconds: bin.load / 1e3,
|
|
266
|
+
projects: projectsOf$1(bin.mutants)
|
|
267
|
+
}));
|
|
268
|
+
};
|
|
269
|
+
const planShards = Workflow.make({
|
|
270
|
+
command: PlanShardsCommand,
|
|
271
|
+
decision: S.Array(PlannedShard),
|
|
272
|
+
error: S.Never,
|
|
273
|
+
decide: (command) => Result.succeed(shardsOf(command))
|
|
274
|
+
});
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/project-directory.ts
|
|
277
|
+
const projectsOf = (projects) => Option.getOrElse(Option.filter(Option.fromUndefinedOr(projects), Arr.isReadonlyArrayNonEmpty), () => ["."]);
|
|
278
|
+
const inProjectDirectory = (directory, body) => Effect.suspend(() => {
|
|
279
|
+
const previous = globalThis.process.cwd();
|
|
280
|
+
return Effect.acquireUseRelease(Effect.sync(() => globalThis.process.chdir(directory)), () => body, () => Effect.sync(() => globalThis.process.chdir(previous)));
|
|
281
|
+
});
|
|
282
|
+
/**
|
|
283
|
+
* Runs `body` once per `--projects` entry, one at a time, with the process
|
|
284
|
+
* working directory switched to that entry, because config discovery reads
|
|
285
|
+
* the working directory. No entries means the working directory alone.
|
|
286
|
+
*/
|
|
287
|
+
const forEachProjectDirectory = dual(2, (projects, body) => Effect.forEach(projectsOf(projects), (directory) => inProjectDirectory(directory, body(directory)), { concurrency: 1 }));
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/run/plan-instrument.cell.ts
|
|
290
|
+
const gatherPlanInstrument = Effect.fn(SpanTaxonomy_exports.Spans.instrumentGather.name)(function* (command) {
|
|
291
|
+
const { filesToMutate, instrumentResult, instrumentedProject } = yield* instrumentFiles(command);
|
|
292
|
+
return {
|
|
293
|
+
_tag: "InstrumentCommand",
|
|
294
|
+
fileCount: filesToMutate.length,
|
|
295
|
+
inPlace: command.options.inPlace,
|
|
296
|
+
pluginCount: command.loadedPlugins.pluginModulePaths.length,
|
|
297
|
+
prev: command,
|
|
298
|
+
instrumentedProject,
|
|
299
|
+
instrumentResult,
|
|
300
|
+
mutants: instrumentResult.mutants
|
|
301
|
+
};
|
|
302
|
+
});
|
|
303
|
+
const readPlanInstrument = (command) => enteringInstrumentPhase(command.project.filesToMutate.size, gatherPlanInstrument(command));
|
|
304
|
+
const writePlanInstrument = (raw) => Effect.as(offerSkipsIfAny({
|
|
305
|
+
skipped: raw.instrumentResult.skipped,
|
|
306
|
+
claimants: raw.prev.frameworkClaimants
|
|
307
|
+
}), {
|
|
308
|
+
project: raw.instrumentedProject,
|
|
309
|
+
mutants: raw.mutants,
|
|
310
|
+
options: raw.prev.options,
|
|
311
|
+
incrementalReportDiscard: raw.prev.incrementalReportDiscard
|
|
312
|
+
});
|
|
313
|
+
const planInstrumentCell = Sandwich.named(SpanTaxonomy_exports.Spans.instrument.name)(readPlanInstrument).decide(planInstrumentation).write({
|
|
314
|
+
InPlaceInstrument: (_decision, raw) => writePlanInstrument(raw),
|
|
315
|
+
EphemeralInstrument: (_decision, raw) => writePlanInstrument(raw),
|
|
316
|
+
CommandRejected: ({ issue }) => Effect.fail(StageError.make({
|
|
317
|
+
stage: "instrument",
|
|
318
|
+
reason: issue
|
|
319
|
+
}))
|
|
320
|
+
});
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/run/plan-prepare.cell.ts
|
|
323
|
+
const writePrepareForInstrument = (raw) => withPhaseSpan(SpanTaxonomy_exports.Spans.preparePhase, {}, () => Effect.gen(function* () {
|
|
324
|
+
yield* admitPreparedProject(raw);
|
|
325
|
+
yield* Queue.offer(raw.queue, RunEvent_exports$1.PhaseEntered.make({
|
|
326
|
+
phase: "prepare",
|
|
327
|
+
elapsedMs: raw.now - raw.env.runStartedAt
|
|
328
|
+
}));
|
|
329
|
+
return {
|
|
330
|
+
project: raw.project,
|
|
331
|
+
loadedPlugins: raw.loaded,
|
|
332
|
+
ignorers: raw.ignorers,
|
|
333
|
+
mutatorCatalogs: raw.mutatorCatalogs,
|
|
334
|
+
mutatorSelection: raw.mutatorSelection,
|
|
335
|
+
formatRegistry: raw.formatRegistry,
|
|
336
|
+
options: raw.options,
|
|
337
|
+
frameworkClaimants: raw.frameworkClaimants,
|
|
338
|
+
incrementalReportDiscard: raw.incrementalReportDiscard
|
|
339
|
+
};
|
|
340
|
+
}));
|
|
341
|
+
const prepareForInstrumentCell = Sandwich.named(SpanTaxonomy_exports.Spans.prepare.name)(readPrepare).decide(planPrepare).write({
|
|
342
|
+
HumanReporters: (_decision, raw) => writePrepareForInstrument(raw),
|
|
343
|
+
MachineReporters: (_decision, raw) => writePrepareForInstrument(raw),
|
|
344
|
+
CommandRejected: ({ issue }) => Effect.fail(StageError.make({
|
|
345
|
+
stage: "prepare",
|
|
346
|
+
reason: issue
|
|
347
|
+
}))
|
|
348
|
+
});
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region src/plan-request.cell.ts
|
|
351
|
+
const DEFAULT_MUTANT_COST_MS = 1e3;
|
|
352
|
+
const prepareStageCell = Cell.andThen(Cell.andThen(loadConfigCell, readProjectCell), prepareForInstrumentCell);
|
|
353
|
+
const planStageCell = Cell.andThen(prepareStageCell, (prepared) => Cell.map(planInstrumentCell, (done) => ({
|
|
354
|
+
prepared,
|
|
355
|
+
done
|
|
356
|
+
})));
|
|
357
|
+
const namedCostsOf = (costs) => Object.fromEntries(Object.entries(costs).flatMap(([mutantId, entry]) => Option.toArray(Option.map(Option.orElse(Option.fromNullishOr(entry.actualMs), () => Option.fromNullishOr(entry.predictedMs)), (costMs) => [mutantId, costMs]))));
|
|
358
|
+
const reportCostsOf = (texts) => texts.reduce((accumulated, text) => Option.match(S.decodeOption(S.fromJsonString(CostsFieldSchema))(text), {
|
|
359
|
+
onNone: () => accumulated,
|
|
360
|
+
onSome: (decoded) => Option.match(Option.fromUndefinedOr(decoded.costs), {
|
|
361
|
+
onNone: () => accumulated,
|
|
362
|
+
onSome: (costs) => ({
|
|
363
|
+
...namedCostsOf(costs),
|
|
364
|
+
...accumulated
|
|
365
|
+
})
|
|
366
|
+
})
|
|
367
|
+
}), {});
|
|
368
|
+
const testsTimeOf = (tests) => tests.reduce((total, test) => total + test.timeSpentMs, 0);
|
|
369
|
+
const runsWholeSuite = (testCoverage, mutantId) => Option.match(Option.fromNullishOr(testCoverage.staticCoverage), {
|
|
370
|
+
onNone: () => true,
|
|
371
|
+
onSome: (staticCoverage) => Option.getOrElse(Option.fromUndefinedOr(staticCoverage[mutantId]), () => 0) > 0
|
|
372
|
+
});
|
|
373
|
+
const coveringCostOf = (coverage, testCoverage, mutantId) => Option.orElse(Option.map(Option.filter(Option.map(MutableHashMap.get(testCoverage.testsByMutantId, mutantId), (tests) => testsTimeOf([...tests])), (millis) => millis > 0), (millis) => millis + coverage.timeOverheadMs), () => Option.filter(Option.some(testsTimeOf(coverage.tests) + coverage.timeOverheadMs), () => runsWholeSuite(testCoverage, mutantId)));
|
|
374
|
+
const costOf = (mutantId, reportCosts, coverage, testCoverage) => Option.getOrElse(Option.orElse(Record.get(reportCosts, mutantId), () => Option.flatMap(coverage, (present) => coveringCostOf(present, testCoverage, mutantId))), () => DEFAULT_MUTANT_COST_MS);
|
|
375
|
+
const emptyTestCoverage = () => ({
|
|
376
|
+
testsByMutantId: MutableHashMap.empty(),
|
|
377
|
+
testsById: MutableHashMap.empty(),
|
|
378
|
+
staticCoverage: void 0,
|
|
379
|
+
hitsByMutantId: MutableHashMap.empty(),
|
|
380
|
+
dryRunCoverage: void 0
|
|
381
|
+
});
|
|
382
|
+
const dependentMutantIdsOf = (decision) => Match.value(decision).pipe(Match.tag("DryRunNeeded", (needed) => needed.dependentMutantIds), Match.tag("DryRunSkippable", () => []), Match.exhaustive);
|
|
383
|
+
const labelOf = (path, basePath, project) => {
|
|
384
|
+
const relative = path.relative(basePath, project).replace(/\\/g, "/");
|
|
385
|
+
return relative.length === 0 ? "." : relative;
|
|
386
|
+
};
|
|
387
|
+
const reportHoldsCompileErrorRecord = (text) => Option.exists(S.decodeOption(S.fromJsonString(CompileErrorProbeSchema))(text), (report) => Object.values(report.files).some((file) => file.mutants.some((mutant) => mutant.status === "CompileError")));
|
|
388
|
+
const programDigestAtPlanTime = ({ texts, context, options, loadedPlugins, project }) => Boolean.match(Boolean.and(Boolean.not(options.inPlace), texts.some(reportHoldsCompileErrorRecord)), {
|
|
389
|
+
onTrue: () => Effect.gen(function* () {
|
|
390
|
+
const pool = yield* scoped$1({
|
|
391
|
+
options,
|
|
392
|
+
loadedPlugins,
|
|
393
|
+
size: 1,
|
|
394
|
+
workingDirectory: project
|
|
395
|
+
});
|
|
396
|
+
return yield* Option.match(Option.fromNullishOr(pool), {
|
|
397
|
+
onNone: () => Effect.as(Effect.void, void 0),
|
|
398
|
+
onSome: (present) => programDigestOf(makeCheckerPoolHandle(present), project)
|
|
399
|
+
});
|
|
400
|
+
}).pipe(Effect.provide(context), Effect.scoped),
|
|
401
|
+
onFalse: () => Effect.as(Effect.void, void 0)
|
|
402
|
+
});
|
|
403
|
+
const projectDiffOf = (path, planRoot, project, diff) => {
|
|
404
|
+
const fromProject = (file) => path.relative(project, path.resolve(planRoot, file)).replace(/\\/g, "/");
|
|
405
|
+
return {
|
|
406
|
+
...diff,
|
|
407
|
+
hunks: diff.hunks.map((hunk) => ({
|
|
408
|
+
...hunk,
|
|
409
|
+
file: fromProject(hunk.file)
|
|
410
|
+
})),
|
|
411
|
+
untrackedFiles: diff.untrackedFiles.map(fromProject)
|
|
412
|
+
};
|
|
413
|
+
};
|
|
414
|
+
const stageCliOptionsOf = (since, full) => Option.match(Option.fromUndefinedOr(since), {
|
|
415
|
+
onNone: () => ({ force: full }),
|
|
416
|
+
onSome: (present) => ({
|
|
417
|
+
force: full,
|
|
418
|
+
since: present
|
|
419
|
+
})
|
|
420
|
+
});
|
|
421
|
+
const plannedDiffOf = (path, planDiff, project) => Option.match(planDiff.diff, {
|
|
422
|
+
onNone: () => WholeProject.make({}),
|
|
423
|
+
onSome: (diff) => ChangedSince.make({ diff: projectDiffOf(path, planDiff.root, project, diff) })
|
|
424
|
+
});
|
|
425
|
+
const resolvedSinceOf = (planDiff) => Option.getOrUndefined(Option.map(planDiff.diff, (diff) => diff.ref));
|
|
426
|
+
const subsumedOf = (mutant) => Option.filter(Option.fromUndefinedOr(mutant.subsumption), S.is(Mutant_exports.Subsumed));
|
|
427
|
+
const dominatorIdsOf = (mutants) => new Set(mutants.flatMap((mutant) => Option.match(subsumedOf(mutant), {
|
|
428
|
+
onNone: () => [],
|
|
429
|
+
onSome: (subsumed) => subsumed.dominators
|
|
430
|
+
})));
|
|
431
|
+
const placementKeyOf = (dominatorIds, mutant) => Option.getOrUndefined(Option.orElse(Option.map(subsumedOf(mutant), (subsumed) => subsumed.dominators[0]), () => Option.liftPredicate(mutant.id, (id) => dominatorIds.has(id))));
|
|
432
|
+
const planProject = (request, channel, labelBase, planDiff, directory) => Effect.gen(function* () {
|
|
433
|
+
const fs = yield* FileSystem.FileSystem;
|
|
434
|
+
const path = yield* Path.Path;
|
|
435
|
+
const basePath = channel.environment.basePath;
|
|
436
|
+
const project = yield* fs.realPath(path.resolve(basePath, directory));
|
|
437
|
+
const env = {
|
|
438
|
+
...channel.environment.host.env,
|
|
439
|
+
basePath: project
|
|
440
|
+
};
|
|
441
|
+
const context = yield* Layer.build(RunEnvironment.layerStage(env, channel.environment.host.events));
|
|
442
|
+
const stageInput = {
|
|
443
|
+
cliOptions: stageCliOptionsOf(resolvedSinceOf(planDiff), request.full),
|
|
444
|
+
targetMutatePatterns: void 0,
|
|
445
|
+
plannedDiff: plannedDiffOf(path, planDiff, project)
|
|
446
|
+
};
|
|
447
|
+
const { prepared, done } = yield* Cell.provideContext(planStageCell, context).run(stageInput);
|
|
448
|
+
const texts = yield* incrementalReportTextsOf({
|
|
449
|
+
basePath: project,
|
|
450
|
+
options: done.options
|
|
451
|
+
});
|
|
452
|
+
const dryRunChoice = yield* dryRunChoiceOf(done, project, texts);
|
|
453
|
+
const coverage = dryRunChoice.prior;
|
|
454
|
+
const testCoverage = Option.match(coverage, {
|
|
455
|
+
onNone: emptyTestCoverage,
|
|
456
|
+
onSome: reusedTestCoverage
|
|
457
|
+
});
|
|
458
|
+
const reportCosts = reportCostsOf(texts);
|
|
459
|
+
const label = labelOf(path, labelBase, project);
|
|
460
|
+
const programDigest = yield* programDigestAtPlanTime({
|
|
461
|
+
texts,
|
|
462
|
+
context,
|
|
463
|
+
options: done.options,
|
|
464
|
+
loadedPlugins: prepared.loadedPlugins,
|
|
465
|
+
project
|
|
466
|
+
});
|
|
467
|
+
const reuse = yield* readIncrementalReuse({
|
|
468
|
+
project: done.project,
|
|
469
|
+
currentMutants: [...done.mutants],
|
|
470
|
+
testCoverage,
|
|
471
|
+
basePath: project,
|
|
472
|
+
force: request.full,
|
|
473
|
+
options: done.options,
|
|
474
|
+
globalTestInputs: Option.map(coverage, (present) => [...present.globalTestInputs]).pipe(Option.getOrElse(() => [])),
|
|
475
|
+
observedModules: Option.getOrUndefined(Option.map(coverage, (present) => present.testFileModules)),
|
|
476
|
+
originalFileOf: (file) => path.resolve(file),
|
|
477
|
+
programDigestOf: Effect.succeed(programDigest)
|
|
478
|
+
});
|
|
479
|
+
const dryRunDecision = Result.getOrElse(requireDryRun(requireDryRunCommandOf({
|
|
480
|
+
options: done.options,
|
|
481
|
+
mutants: [...reuse.mutants, ...reuse.rememberedResults],
|
|
482
|
+
texts,
|
|
483
|
+
kept: dryRunChoice.kept
|
|
484
|
+
})), (neverError) => neverError);
|
|
485
|
+
const dependentIds = HashSet.fromIterable(dependentMutantIdsOf(dryRunDecision));
|
|
486
|
+
const dryRunCostMs = Boolean.match(S.is(DryRunCoverageReused)(dryRunChoice.decision), {
|
|
487
|
+
onTrue: () => 0,
|
|
488
|
+
onFalse: () => Option.getOrElse(Option.map(coverage, (present) => testsTimeOf(present.tests) + present.timeOverheadMs), () => DEFAULT_MUTANT_COST_MS)
|
|
489
|
+
});
|
|
490
|
+
const dominatorIds = dominatorIdsOf(reuse.mutants);
|
|
491
|
+
return {
|
|
492
|
+
label,
|
|
493
|
+
mutants: [...reuse.mutants.map((mutant) => ({
|
|
494
|
+
id: mutant.id,
|
|
495
|
+
costMs: costOf(mutant.id, reportCosts, coverage, testCoverage),
|
|
496
|
+
dependsOnDryRun: HashSet.has(dependentIds, mutant.id),
|
|
497
|
+
...optionalField("placementKey", placementKeyOf(dominatorIds, mutant))
|
|
498
|
+
})), ...reuse.rememberedResults.map((mutant) => ({
|
|
499
|
+
id: mutant.id,
|
|
500
|
+
costMs: 0,
|
|
501
|
+
dependsOnDryRun: HashSet.has(dependentIds, mutant.id),
|
|
502
|
+
...optionalField("placementKey", placementKeyOf(dominatorIds, mutant))
|
|
503
|
+
}))],
|
|
504
|
+
files: [...HashSet.fromIterable([...reuse.mutants, ...reuse.rememberedResults].map((mutant) => path.resolve(project, mutant.fileName)))].sort(),
|
|
505
|
+
dryRunCostMs,
|
|
506
|
+
reuse: {
|
|
507
|
+
reused: reuse.rememberedResults.length,
|
|
508
|
+
ran: reuse.mutants.length,
|
|
509
|
+
refused: reuse.refusalCounts,
|
|
510
|
+
...done.incrementalReportDiscard === void 0 ? {} : { discard: done.incrementalReportDiscard }
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
}).pipe(Effect.orDie);
|
|
514
|
+
const planScopeOf = (diff, planned, basePath) => Option.match(diff, {
|
|
515
|
+
onNone: () => Effect.succeed({ _tag: "Unscoped" }),
|
|
516
|
+
onSome: (present) => Effect.gen(function* () {
|
|
517
|
+
const path = yield* Path.Path;
|
|
518
|
+
const git = yield* GitDiff;
|
|
519
|
+
const root = yield* git.toplevel(basePath);
|
|
520
|
+
const files = [...HashSet.fromIterable(planned.flatMap((entry) => entry.files.map((file) => path.relative(root, file).replace(/\\/g, "/"))))].sort();
|
|
521
|
+
const sources = yield* git.sourceDigest({
|
|
522
|
+
cwd: basePath,
|
|
523
|
+
paths: files
|
|
524
|
+
});
|
|
525
|
+
return Match.value(diffScopeDecisionOf(present)).pipe(Match.tag("DiffScoped", () => ({
|
|
526
|
+
_tag: "DiffScoped",
|
|
527
|
+
base: present.base,
|
|
528
|
+
head: present.head,
|
|
529
|
+
sources,
|
|
530
|
+
files
|
|
531
|
+
})), Match.tag("FullScope", (full) => ({
|
|
532
|
+
_tag: "FullScope",
|
|
533
|
+
base: present.base,
|
|
534
|
+
head: present.head,
|
|
535
|
+
sources,
|
|
536
|
+
files,
|
|
537
|
+
reason: full.reason
|
|
538
|
+
})), Match.exhaustive);
|
|
539
|
+
})
|
|
540
|
+
});
|
|
541
|
+
const assemblePlan = (request, scope, planned, dryRunCosts) => Effect.map(PlanShardsCommand.makeEffect({
|
|
542
|
+
targetSeconds: request.targetSeconds,
|
|
543
|
+
maxShards: request.maxShards,
|
|
544
|
+
mutants: planned.map((mutant) => ({ ...mutant })),
|
|
545
|
+
dryRunCosts
|
|
546
|
+
}).pipe(Effect.mapError((issue) => PlanShardsRefused.make({ detail: new S.SchemaError(issue).message }))), (command) => shardPlanOf(request, scope, Result.getOrElse(planShards(command), (neverError) => neverError)));
|
|
547
|
+
const shardPlanOf = (request, scope, shards) => ({
|
|
548
|
+
version: ShardPlanVersion.literal,
|
|
549
|
+
scope,
|
|
550
|
+
targetSeconds: request.targetSeconds,
|
|
551
|
+
shards: shards.map((shard) => ({
|
|
552
|
+
index: shard.index,
|
|
553
|
+
count: shard.count,
|
|
554
|
+
predictedSeconds: shard.predictedSeconds,
|
|
555
|
+
projects: shard.projects.map((entry) => ({
|
|
556
|
+
project: entry.project,
|
|
557
|
+
mutants: [...entry.mutants]
|
|
558
|
+
}))
|
|
559
|
+
})),
|
|
560
|
+
matrix: { include: shards.map((shard) => ({
|
|
561
|
+
shard: `${shard.index}/${shard.count}`,
|
|
562
|
+
predictedSeconds: shard.predictedSeconds
|
|
563
|
+
})) }
|
|
564
|
+
});
|
|
565
|
+
const writePlan = (request, channel, plan) => Effect.gen(function* () {
|
|
566
|
+
const fs = yield* FileSystem.FileSystem;
|
|
567
|
+
const path = yield* Path.Path;
|
|
568
|
+
const text = yield* Effect.orDie(S.encodeEffect(S.fromJsonString(ShardPlan, { space: 2 }))(plan));
|
|
569
|
+
return yield* Option.match(Option.fromUndefinedOr(request.out), {
|
|
570
|
+
onNone: () => Effect.sync(() => channel.environment.console.log(text)),
|
|
571
|
+
onSome: (out) => Effect.gen(function* () {
|
|
572
|
+
const target = path.resolve(channel.environment.basePath, out);
|
|
573
|
+
yield* fs.makeDirectory(path.dirname(target), { recursive: true });
|
|
574
|
+
yield* fs.writeFileString(target, text);
|
|
575
|
+
})
|
|
576
|
+
});
|
|
577
|
+
}).pipe(Effect.orDie);
|
|
578
|
+
const labelBaseOf = (path, basePath, out) => Option.getOrElse(Option.map(Option.fromUndefinedOr(out), (present) => path.dirname(path.resolve(basePath, present))), () => basePath);
|
|
579
|
+
const planReuseRowOf = (entry) => RunEvent_exports$1.PlanProjectReuse.make({
|
|
580
|
+
project: entry.label,
|
|
581
|
+
reused: entry.reuse.reused,
|
|
582
|
+
ran: entry.reuse.ran,
|
|
583
|
+
refused: entry.reuse.refused,
|
|
584
|
+
...Option.match(Option.fromUndefinedOr(entry.reuse.discard), {
|
|
585
|
+
onNone: () => ({}),
|
|
586
|
+
onSome: (discard) => ({ discard: RunEvent_exports$1.PlanReportDiscard.make({
|
|
587
|
+
reason: discard.reason,
|
|
588
|
+
expected: discard.expected,
|
|
589
|
+
...Option.match(Option.fromUndefinedOr(discard.actual), {
|
|
590
|
+
onNone: () => ({}),
|
|
591
|
+
onSome: (actual) => ({ actual })
|
|
592
|
+
})
|
|
593
|
+
}) })
|
|
594
|
+
})
|
|
595
|
+
});
|
|
596
|
+
const planDiffOf = (since, basePath) => Effect.gen(function* () {
|
|
597
|
+
const fs = yield* FileSystem.FileSystem;
|
|
598
|
+
const root = yield* Effect.orDie(fs.realPath(basePath));
|
|
599
|
+
return {
|
|
600
|
+
root,
|
|
601
|
+
diff: yield* Option.match(Option.fromUndefinedOr(since), {
|
|
602
|
+
onNone: () => Effect.succeedNone,
|
|
603
|
+
onSome: (ref) => Effect.asSome(Effect.flatMap(GitDiff, (git) => git.changedSince({
|
|
604
|
+
cwd: root,
|
|
605
|
+
ref
|
|
606
|
+
})))
|
|
607
|
+
})
|
|
608
|
+
};
|
|
609
|
+
});
|
|
610
|
+
const rootOptionsOf = (request, channel) => Effect.gen(function* () {
|
|
611
|
+
const env = {
|
|
612
|
+
...channel.environment.host.env,
|
|
613
|
+
basePath: channel.environment.basePath
|
|
614
|
+
};
|
|
615
|
+
const context = yield* Layer.build(RunEnvironment.layerStage(env, channel.environment.host.events));
|
|
616
|
+
return (yield* Cell.provideContext(loadConfigCell, context).run({
|
|
617
|
+
cliOptions: stageCliOptionsOf(request.since, request.full),
|
|
618
|
+
targetMutatePatterns: void 0
|
|
619
|
+
})).options;
|
|
620
|
+
}).pipe(Effect.orDie);
|
|
621
|
+
const realProjectOf = (fs, path, basePath, directory) => {
|
|
622
|
+
const resolved = path.resolve(basePath, directory);
|
|
623
|
+
return fs.realPath(resolved).pipe(Effect.orElseSucceed(() => resolved));
|
|
624
|
+
};
|
|
625
|
+
const distinctProjectsOf = (basePath, projects) => Effect.gen(function* () {
|
|
626
|
+
const fs = yield* FileSystem.FileSystem;
|
|
627
|
+
const path = yield* Path.Path;
|
|
628
|
+
const keyed = yield* Effect.forEach(Option.getOrElse(Option.fromUndefinedOr(projects), () => []), (directory) => Effect.map(realProjectOf(fs, path, basePath, directory), (real) => ({
|
|
629
|
+
directory,
|
|
630
|
+
real
|
|
631
|
+
})));
|
|
632
|
+
return Arr.dedupeWith(keyed, (left, right) => left.real === right.real).map((entry) => entry.directory);
|
|
633
|
+
});
|
|
634
|
+
const planRequest = ({ request, channel }) => Effect.gen(function* () {
|
|
635
|
+
const path = yield* Path.Path;
|
|
636
|
+
const labelBase = labelBaseOf(path, channel.environment.basePath, request.out);
|
|
637
|
+
const rootOptions = yield* rootOptionsOf(request, channel);
|
|
638
|
+
const resolvedSince = Option.getOrUndefined(Option.orElse(Option.fromUndefinedOr(request.since), () => Option.fromUndefinedOr(rootOptions.since)));
|
|
639
|
+
const planDiff = yield* planDiffOf(resolvedSince, channel.environment.basePath);
|
|
640
|
+
const planned = yield* forEachProjectDirectory(yield* distinctProjectsOf(channel.environment.basePath, request.projects), (directory) => planProject(request, channel, labelBase, planDiff, directory));
|
|
641
|
+
const scheduled = planned.flatMap((entry) => entry.mutants.map((mutant) => ({
|
|
642
|
+
project: entry.label,
|
|
643
|
+
...mutant
|
|
644
|
+
})));
|
|
645
|
+
const dryRunCosts = Object.fromEntries(planned.map((entry) => [entry.label, entry.dryRunCostMs]));
|
|
646
|
+
const scope = yield* planScopeOf(planDiff.diff, planned, channel.environment.basePath);
|
|
647
|
+
const plan = yield* assemblePlan(request, scope, scheduled, dryRunCosts);
|
|
648
|
+
yield* Queue.offer(channel.environment.host.events, RunEvent_exports$1.PlanKnown.make({
|
|
649
|
+
total: scheduled.length,
|
|
650
|
+
shardPlan: plan,
|
|
651
|
+
projects: planned.map(planReuseRowOf)
|
|
652
|
+
}));
|
|
653
|
+
yield* writePlan(request, channel, plan).pipe(Effect.orDie);
|
|
654
|
+
}).pipe(Effect.scoped);
|
|
655
|
+
//#endregion
|
|
75
656
|
//#region src/Engine/mod.ts
|
|
76
657
|
var Engine_exports = /* @__PURE__ */ __exportAll({
|
|
77
658
|
IdGenerator: () => IdGenerator,
|
|
78
659
|
IncrementalReportSchema: () => IncrementalReportSchema,
|
|
660
|
+
PlanShardsRefused: () => PlanShardsRefused,
|
|
79
661
|
RunEnvironment: () => RunEnvironment,
|
|
80
662
|
StageError: () => StageError,
|
|
81
663
|
StrykerError: () => StrykerError,
|
|
82
664
|
mutationTestCell: () => mutationTestCell,
|
|
83
665
|
nodePlatformLayer: () => nodePlatformLayer,
|
|
666
|
+
planRequest: () => planRequest,
|
|
84
667
|
strykerCell: () => strykerCell
|
|
85
668
|
});
|
|
86
669
|
//#endregion
|
|
@@ -278,7 +861,8 @@ const priorMutantsOf = (text, basePath) => Option.match(S.decodeOption(S.fromJso
|
|
|
278
861
|
mutatorName: Mutant_exports.MutatorName.make(mutant.mutatorName),
|
|
279
862
|
replacement: mutant.replacement ?? mutant.mutatorName,
|
|
280
863
|
location: mutant.location,
|
|
281
|
-
relativeFileName
|
|
864
|
+
relativeFileName,
|
|
865
|
+
probeEligibility: Mutant_exports.Unprobed.make({ reason: "not-decided" })
|
|
282
866
|
}));
|
|
283
867
|
})
|
|
284
868
|
});
|
|
@@ -343,7 +927,9 @@ const engineDependencies = [
|
|
|
343
927
|
Path.Path,
|
|
344
928
|
Stdio.Stdio,
|
|
345
929
|
ChildProcessSpawner.ChildProcessSpawner,
|
|
930
|
+
EmptyDirectory,
|
|
346
931
|
GitDiff,
|
|
932
|
+
SourceParser,
|
|
347
933
|
WorkerLauncher
|
|
348
934
|
];
|
|
349
935
|
const ListSurvivors = Tool.make("list_survivors", {
|
|
@@ -379,6 +965,25 @@ const ReportUsefulness = Tool.make("report_usefulness", {
|
|
|
379
965
|
});
|
|
380
966
|
const mcpToolkit = Toolkit.make(ListSurvivors, ShowMutant, RerunMutant, ReportUsefulness);
|
|
381
967
|
//#endregion
|
|
968
|
+
//#region src/Mcp/rerun-stage.handle.ts
|
|
969
|
+
const TypeId$1 = Symbol.for("~systemfsoftware/stryker-js/Mcp/RerunStage");
|
|
970
|
+
const RerunStageHandle = Handle.make()(TypeId$1);
|
|
971
|
+
RerunStageHandle.is;
|
|
972
|
+
const makeRerunStageHandle = (stage) => RerunStageHandle.make({}, stage);
|
|
973
|
+
const run = dual(3, (handle, cell, input) => Cell.provideContext(cell, RerunStageHandle.slot(handle)).run(input));
|
|
974
|
+
//#endregion
|
|
975
|
+
//#region src/Mcp/rerun-stage.blueprint.ts
|
|
976
|
+
const TypeId = Symbol.for("~systemfsoftware/stryker-js/Mcp/RerunStageBlueprint");
|
|
977
|
+
const acquire = Effect.fnUntraced(function* (spec) {
|
|
978
|
+
const stage = yield* Layer.build(Layer.merge(RunEnvironment.layerStage(spec.environment, spec.events), MachineConsole.layerCapture.pipe(Layer.provide(MachineConsole.layer))));
|
|
979
|
+
return makeRerunStageHandle(stage);
|
|
980
|
+
});
|
|
981
|
+
const RerunStages = Blueprint.make()(TypeId).steps({
|
|
982
|
+
steps: {},
|
|
983
|
+
targets: { scoped: acquire }
|
|
984
|
+
});
|
|
985
|
+
const scoped = (spec) => RerunStages.of(spec).scoped;
|
|
986
|
+
//#endregion
|
|
382
987
|
//#region src/Mcp/mcp-server.cell.ts
|
|
383
988
|
const MCP_RUN_ID = RunEvent_exports$1.RunId.make("01ARZ3NDEKTSV4RRFFQ69G5FAM");
|
|
384
989
|
const REPRODUCERS_FILE = "reports/mutation/reproducers.json";
|
|
@@ -431,11 +1036,13 @@ const runRestricted = (basePath, options) => Effect.gen(function* () {
|
|
|
431
1036
|
builtinReporters: {},
|
|
432
1037
|
allowConsoleColors: false
|
|
433
1038
|
};
|
|
434
|
-
|
|
435
|
-
|
|
1039
|
+
return yield* Effect.scoped(Effect.flatMap(scoped({
|
|
1040
|
+
environment,
|
|
1041
|
+
events: queue
|
|
1042
|
+
}), run(mutationTestCell, {
|
|
436
1043
|
cliOptions: options,
|
|
437
1044
|
targetMutatePatterns: void 0
|
|
438
|
-
}).pipe(Effect.
|
|
1045
|
+
}))).pipe(Effect.orDie);
|
|
439
1046
|
});
|
|
440
1047
|
const NO_RESULTS = {
|
|
441
1048
|
results: [],
|
|
@@ -485,12 +1092,14 @@ const handlers = (basePath) => mcpToolkit.toLayer({
|
|
|
485
1092
|
reason: reason ?? null
|
|
486
1093
|
})
|
|
487
1094
|
});
|
|
488
|
-
const
|
|
1095
|
+
const provideEngineToRequests = McpServer.middleware()(Effect.map(Effect.context(), (context) => (effect) => Effect.provideContext(effect, context)));
|
|
1096
|
+
const mcpServerLayer = ({ basePath }) => McpServer.layerStdio({
|
|
489
1097
|
name: "stryker",
|
|
490
1098
|
version: StrykerPackage.version,
|
|
491
1099
|
instructions: MCP_SERVER_INSTRUCTIONS,
|
|
492
|
-
protocols: [McpProtocol.v2025_06_18]
|
|
493
|
-
|
|
1100
|
+
protocols: [McpProtocol.v2025_06_18],
|
|
1101
|
+
registrations: McpServer.toolkit(mcpToolkit).pipe(Layer.provide(provideEngineToRequests.layer), Layer.provide(handlers(basePath)))
|
|
1102
|
+
}).pipe(Layer.provide(MachineConsole.layer));
|
|
494
1103
|
//#endregion
|
|
495
1104
|
//#region src/Mcp/mod.ts
|
|
496
1105
|
var Mcp_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -520,7 +1129,6 @@ var Plugin_exports = /* @__PURE__ */ __exportAll({
|
|
|
520
1129
|
var RunEvent_exports = /* @__PURE__ */ __exportAll({
|
|
521
1130
|
MetricsResultFromReport: () => MetricsResultFromReport,
|
|
522
1131
|
RunEventDrain: () => RunEventDrain,
|
|
523
|
-
RunEventDrainLive: () => RunEventDrainLive,
|
|
524
1132
|
RunEvents: () => RunEvents,
|
|
525
1133
|
RunIdentity: () => RunIdentity,
|
|
526
1134
|
VerdictEnvelope: () => VerdictEnvelope,
|
|
@@ -964,7 +1572,7 @@ const withEngine = (consume, use) => Effect.gen(function* () {
|
|
|
964
1572
|
const events = yield* Queue.bounded(RunEvent_exports$1.RunEvent.QUEUE_BOUND);
|
|
965
1573
|
const consumer = yield* Stream.fromQueue(events).pipe(Stream.runForEach(consume), Effect.forkChild);
|
|
966
1574
|
return yield* Effect.scoped(Effect.gen(function* () {
|
|
967
|
-
const context = yield* Layer.build(RunEnvironment.
|
|
1575
|
+
const context = yield* Layer.build(RunEnvironment.layerStage(env, events));
|
|
968
1576
|
return yield* use({
|
|
969
1577
|
env,
|
|
970
1578
|
context
|
|
@@ -1196,4 +1804,4 @@ var Worker_exports = /* @__PURE__ */ __exportAll({
|
|
|
1196
1804
|
makeWorkerClient: () => makeWorkerClient
|
|
1197
1805
|
});
|
|
1198
1806
|
//#endregion
|
|
1199
|
-
export { Checker_exports as Checker, Configuration_exports as Configuration, Engine_exports as Engine, git_diff_service_exports as GitDiff, git_diff_schema_exports as GitDiffSchema, import_closure_cell_exports as ImportClosure, Mcp_exports as Mcp, Plugin_exports as Plugin, RunEvent_exports as RunEvent, Serve_exports as Serve, Worker_exports as Worker };
|
|
1807
|
+
export { Checker_exports as Checker, Configuration_exports as Configuration, Engine_exports as Engine, git_diff_service_exports as GitDiff, git_diff_schema_exports as GitDiffSchema, import_closure_cell_exports as ImportClosure, Mcp_exports as Mcp, Plugin_exports as Plugin, RunEvent_exports as RunEvent, Serve_exports as Serve, source_parser_service_exports as SourceParser, Worker_exports as Worker };
|