supercov 2.0.0 → 3.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.
Files changed (37) hide show
  1. package/README.md +52 -22
  2. package/docs/agent-loop.md +8 -13
  3. package/docs/assertions.md +161 -75
  4. package/docs/cli.md +111 -59
  5. package/docs/coverage-model.md +5 -6
  6. package/docs/evidence.md +0 -11
  7. package/docs/getting-started.md +1 -1
  8. package/docs/performance.md +6 -14
  9. package/docs/quality.md +1 -1
  10. package/docs/security.md +43 -12
  11. package/docs/supported-suites.md +44 -48
  12. package/docs/troubleshooting.md +12 -5
  13. package/docs/workspace-isolation.md +23 -2
  14. package/package.json +36 -21
  15. package/runtime/javascript/atomic.mjs +46 -2
  16. package/runtime/javascript/ava.mjs +82 -0
  17. package/runtime/javascript/bootstrap.cjs +43 -0
  18. package/runtime/javascript/jest.cjs +52 -15
  19. package/runtime/javascript/jest.config.mjs +9 -1
  20. package/runtime/javascript/jestReporter.mjs +5 -6
  21. package/runtime/javascript/launchSupervisor.mjs +127 -15
  22. package/runtime/javascript/mocha.mjs +180 -0
  23. package/runtime/javascript/nodeTest.mjs +57 -3
  24. package/runtime/javascript/playwright.mjs +241 -32
  25. package/runtime/javascript/provenance.mjs +1 -1
  26. package/runtime/javascript/register.mjs +290 -2
  27. package/runtime/javascript/resolve-loader.mjs +40 -1
  28. package/runtime/javascript/runnerEvidence.mjs +3 -5
  29. package/runtime/javascript/runtime.mjs +551 -61
  30. package/runtime/javascript/tap.mjs +130 -0
  31. package/runtime/javascript/transport.mjs +7 -2
  32. package/runtime/javascript/vitest.mjs +5 -8
  33. package/runtime/javascript/vitestReporter.mjs +3 -10
  34. package/docs/assertion-agent.md +0 -150
  35. package/docs/assertion-evidence.md +0 -103
  36. package/docs/assertion-maps.md +0 -316
  37. package/schemas/assertions.schema.json +0 -276
@@ -7,8 +7,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
7
7
  return path;
8
8
  };
9
9
  import Module, { register, syncBuiltinESMExports } from "node:module";
10
- import { closeSync, openSync, unlinkSync } from "node:fs";
11
- import { resolve } from "node:path";
10
+ import fs, { closeSync, openSync, readFileSync, realpathSync, unlinkSync } from "node:fs";
11
+ import { isAbsolute, relative, resolve, sep } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { installLaunchSupervisor, wrapImportedCapability } from "./launchSupervisor.mjs";
14
14
  import { __supercovBindCapabilityWrapper } from "./capability.mjs";
@@ -121,6 +121,42 @@ syncBuiltinESMExports();
121
121
  const generatedVitestConfig = process.env.SUPERCOV_GENERATED_VITEST_CONFIG;
122
122
  const generatedPlaywrightConfig = process.env.SUPERCOV_GENERATED_PLAYWRIGHT_CONFIG;
123
123
  const entrypoint = process.argv[1]?.replaceAll("\\", "/") ?? "";
124
+ // Node's own test coverage filters files by `--test-coverage-include` and
125
+ // `--test-coverage-exclude` globs relative to the working directory, and the
126
+ // instrumented copy's source map names the project's file, outside the
127
+ // isolated workspace the tests run in: `src/**` matched nothing, the report
128
+ // was empty and its thresholds passed with nothing checked. When those globs
129
+ // are given, the map this process caches names the copy's own path instead;
130
+ // its embedded original text is what Node reports. Nothing is written: other
131
+ // coverage tools read the file as it is on disk.
132
+ const NODE_COVERAGE_FILTERS = process.execArgv.some((argument) => /^--test-coverage-(?:include|exclude)(?:=|$)/.test(argument)) &&
133
+ (Boolean(process.env.NODE_V8_COVERAGE) || process.execArgv.includes("--experimental-test-coverage"));
134
+ const INLINE_MAP = "//# sourceMappingURL=data:application/json;base64,";
135
+ function mapToCopy(source, filename) {
136
+ if (typeof source !== "string" || !source.includes("__supercov"))
137
+ return source;
138
+ const at = source.lastIndexOf(INLINE_MAP);
139
+ if (at < 0)
140
+ return source;
141
+ const end = source.indexOf("\n", at);
142
+ const encoded = source.slice(at + INLINE_MAP.length, end < 0 ? undefined : end).trim();
143
+ try {
144
+ const map = JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
145
+ if (!Array.isArray(map.sources) || map.sources.length !== 1)
146
+ return source;
147
+ map.sources = [filename.replace(/^.*[\\/]/, "")];
148
+ return `${source.slice(0, at)}${INLINE_MAP}${Buffer.from(JSON.stringify(map)).toString("base64")}${end < 0 ? "" : source.slice(end)}`;
149
+ }
150
+ catch {
151
+ return source;
152
+ }
153
+ }
154
+ if (NODE_COVERAGE_FILTERS) {
155
+ const compile = Module.prototype._compile;
156
+ Module.prototype._compile = function _compile(content, filename, ...rest) {
157
+ return Reflect.apply(compile, this, [mapToCopy(content, filename), filename, ...rest]);
158
+ };
159
+ }
124
160
  const playwrightTarget = process.env.SUPERCOV_PLAYWRIGHT_MODULE;
125
161
  const projectRoot = process.env.SUPERCOV_PROJECT_ROOT?.replaceAll("\\", "/").replace(/\/$/, "");
126
162
  const nodeTestWrapper = new URL("./nodeTest.mjs", import.meta.url).href;
@@ -130,6 +166,212 @@ const isPlaywrightEntrypoint = /\/(?:node_modules\/\.bin\/playwright|node_module
130
166
  const isJestEntrypoint = /\/node_modules\/(?:\.bin\/jest|(?:jest|jest-cli)\/bin\/jest\.js)$/.test(entrypoint);
131
167
  if (generatedPlaywrightConfig && isPlaywrightEntrypoint)
132
168
  process.env.SUPERCOV_INSIDE_PLAYWRIGHT = "1";
169
+ // Linters, formatters and type checks read the project to judge it, not to
170
+ // run it. The workspace holds rewritten copies, and ESLint failed semver,
171
+ // js-yaml and picomatch on code nobody wrote ("Strings must use singlequote",
172
+ // "'globalThis' is not defined"), Supercov's own .supercov/*.mjs included.
173
+ // Such a process reads every rewritten file as its author wrote it, and no
174
+ // directory listing in the workspace shows .supercov.
175
+ const analysisTools = "eslint|eslint_d|prettier|standard|semistandard|ts-standard|xo|jshint|tslint";
176
+ const isAnalysisEntrypoint = new RegExp(`/node_modules/(?:\\.bin/(?:${analysisTools})$|(?:${analysisTools})/)`).test(entrypoint) ||
177
+ (/\/node_modules\/(?:\.bin\/(?:tsc|vue-tsc)$|(?:typescript|vue-tsc)\/bin\/)/.test(entrypoint) &&
178
+ process.argv.includes("--noEmit"));
179
+ if (isAnalysisEntrypoint)
180
+ installAuthoredSourceView();
181
+ function installAuthoredSourceView() {
182
+ let authored;
183
+ try {
184
+ authored = new Set(JSON.parse(readFileSync(new URL("./authored-sources.json", import.meta.url), "utf8")));
185
+ }
186
+ catch {
187
+ authored = new Set();
188
+ }
189
+ const authoredRoot = fileURLToPath(new URL("./.authored/", import.meta.url));
190
+ const workspace = fileURLToPath(new URL("../../", import.meta.url));
191
+ const roots = [...new Set([workspace, (() => {
192
+ try {
193
+ return realpathSync(workspace);
194
+ }
195
+ catch {
196
+ return workspace;
197
+ }
198
+ })()])];
199
+ const inside = (path) => {
200
+ if (typeof path !== "string" && !(path instanceof URL))
201
+ return undefined;
202
+ let absolute;
203
+ try {
204
+ absolute = resolve(path instanceof URL ? fileURLToPath(path) : path);
205
+ }
206
+ catch {
207
+ return undefined;
208
+ }
209
+ for (const root of roots) {
210
+ const local = relative(root, absolute);
211
+ if (local !== ".." && !local.startsWith(`..${sep}`) && !isAbsolute(local))
212
+ return local.split(sep).join("/");
213
+ }
214
+ return undefined;
215
+ };
216
+ const authoredPath = (path) => {
217
+ const local = inside(path);
218
+ return local !== undefined && authored.has(local) ? resolve(authoredRoot, local) : path;
219
+ };
220
+ // A recursive listing names nested entries by relative path, or as
221
+ // entries whose parent is inside .supercov.
222
+ const supercovPath = (path) => typeof path === "string" && path.split(/[\\/]/).includes(".supercov");
223
+ const hidden = (entry) => {
224
+ if (typeof entry === "string")
225
+ return supercovPath(entry);
226
+ if (entry instanceof Uint8Array)
227
+ return supercovPath(new TextDecoder().decode(entry));
228
+ const parent = inside(entry?.parentPath ?? entry?.path ?? "");
229
+ return entry?.name === ".supercov" || supercovPath(parent);
230
+ };
231
+ const listed = (path, entries) => inside(path) === undefined || !Array.isArray(entries)
232
+ ? entries
233
+ : entries.filter((entry) => !hidden(entry));
234
+ const { readFileSync: readSync, readFile: readCallback, readdirSync: listSync, readdir: listCallback } = fs;
235
+ const { readFile: readPromise, readdir: listPromise } = fs.promises;
236
+ fs.readFileSync = function readFileSync(path, ...rest) {
237
+ return Reflect.apply(readSync, this, [authoredPath(path), ...rest]);
238
+ };
239
+ fs.readFile = function readFile(path, ...rest) {
240
+ return Reflect.apply(readCallback, this, [authoredPath(path), ...rest]);
241
+ };
242
+ fs.promises.readFile = function readFile(path, ...rest) {
243
+ return Reflect.apply(readPromise, this, [authoredPath(path), ...rest]);
244
+ };
245
+ fs.readdirSync = function readdirSync(path, ...rest) {
246
+ return listed(path, Reflect.apply(listSync, this, [path, ...rest]));
247
+ };
248
+ fs.readdir = function readdir(path, ...rest) {
249
+ const callback = rest.at(-1);
250
+ if (typeof callback !== "function")
251
+ return Reflect.apply(listCallback, this, [path, ...rest]);
252
+ return Reflect.apply(listCallback, this, [path, ...rest.slice(0, -1), (error, entries) => callback(error, error ? entries : listed(path, entries))]);
253
+ };
254
+ fs.promises.readdir = async function readdir(path, ...rest) {
255
+ return listed(path, await Reflect.apply(listPromise, this, [path, ...rest]));
256
+ };
257
+ syncBuiltinESMExports();
258
+ }
259
+ // A project's own coverage tool measures what runs, and under Supercov that is
260
+ // the instrumented copy, whose probes are branches no test was written to
261
+ // cover: c8, Jest and Vitest each failed a 100% gate on a suite that covers
262
+ // every branch (80%, 83.33%, 87.5%). The tool still runs and reports; its
263
+ // thresholds are not checked, and the run says so once. Jest's and Vitest's
264
+ // thresholds are dropped from the configurations Supercov hands them, which
265
+ // call this too.
266
+ process.__SUPERCOV_SKIPPED_COVERAGE_THRESHOLDS__ ??= (tool) => {
267
+ if (process.__SUPERCOV_SKIPPED_COVERAGE_THRESHOLDS_NOTED__)
268
+ return;
269
+ process.__SUPERCOV_SKIPPED_COVERAGE_THRESHOLDS_NOTED__ = true;
270
+ console.error(`[supercov] ${tool}'s coverage thresholds were not checked: under Supercov, ${tool} measures the instrumented copy, probes included. Supercov's own report has this run's coverage.`);
271
+ };
272
+ const coverageTool = /\/node_modules\/(?:\.bin\/(c8|nyc)|(c8|nyc)\/bin\/(?:c8|nyc)\.js)$/.exec(entrypoint);
273
+ if (coverageTool)
274
+ skipCoverageThresholds(coverageTool[1] ?? coverageTool[2]);
275
+ if (/\/node_modules\/(?:\.bin\/karma|karma\/bin\/karma)$/.test(entrypoint))
276
+ skipKarmaCoverageCheck();
277
+ // karma-coverage checks `coverageReporter.check` in the reporter karma builds
278
+ // from its plugin, which the karma process requires by name from the project.
279
+ // Loaded first, the plugin's constructor is replaced by one that leaves the
280
+ // check out of the configuration it is given.
281
+ function skipKarmaCoverageCheck() {
282
+ try {
283
+ const plugin = Module.createRequire(resolve(process.cwd(), "package.json"))("karma-coverage");
284
+ const entry = plugin?.["reporter:coverage"];
285
+ if (!Array.isArray(entry) || typeof entry[1] !== "function")
286
+ return;
287
+ const Reporter = entry[1];
288
+ function CoverageReporter(rootConfig, ...rest) {
289
+ const options = rootConfig?.coverageReporter;
290
+ if (options && Object.prototype.hasOwnProperty.call(options, "check")) {
291
+ delete options.check;
292
+ process.__SUPERCOV_SKIPPED_COVERAGE_THRESHOLDS__("karma-coverage");
293
+ }
294
+ return Reflect.construct(Reporter, [rootConfig, ...rest], new.target);
295
+ }
296
+ CoverageReporter.$inject = Reporter.$inject;
297
+ CoverageReporter.prototype = Reporter.prototype;
298
+ plugin["reporter:coverage"] = [entry[0], CoverageReporter];
299
+ }
300
+ catch (error) {
301
+ if (process.env.SUPERCOV_DEBUG === "1")
302
+ console.error("[supercov] karma-coverage's check stays as configured", error);
303
+ }
304
+ }
305
+ // nyc (through 18) remaps with istanbul-lib-source-maps 4, which drops an
306
+ // `if`'s implicit else from any file with a source map: it has no location to
307
+ // map. The instrumented copy has one, so nyc read one branch fewer for every
308
+ // `if` without an `else` than it reads without Supercov. Version 5 keeps the
309
+ // location in the file of the branch's other one; so does this, loaded before
310
+ // nyc's remapping captures the function.
311
+ function keepImplicitElse(toolRequire) {
312
+ try {
313
+ const path = toolRequire.resolve("istanbul-lib-source-maps/lib/get-mapping.js");
314
+ const getMapping = toolRequire(path);
315
+ const cached = Module._cache[path];
316
+ if (typeof getMapping !== "function" || !cached)
317
+ return;
318
+ let lastSource;
319
+ cached.exports = function getMappingKeepingImplicitElse(sourceMap, location, originalFile) {
320
+ const mapping = getMapping(sourceMap, location, originalFile);
321
+ if (mapping) {
322
+ lastSource = mapping.source;
323
+ return mapping;
324
+ }
325
+ const implicit = location?.start?.line === undefined && location?.end?.line === undefined;
326
+ const source = lastSource;
327
+ lastSource = undefined;
328
+ return implicit && source !== undefined ? { source, loc: location } : mapping;
329
+ };
330
+ }
331
+ catch (error) {
332
+ if (process.env.SUPERCOV_DEBUG === "1")
333
+ console.error("[supercov] nyc keeps its own remapping", error);
334
+ }
335
+ }
336
+ function skipCoverageThresholds(tool) {
337
+ const skipped = async function skippedCoverageThresholds() {
338
+ process.__SUPERCOV_SKIPPED_COVERAGE_THRESHOLDS__(tool);
339
+ };
340
+ try {
341
+ // Resolved from the tool's own bin, so the module patched is the one
342
+ // its CLI loads next (npm links .bin/c8 to c8/bin/c8.js).
343
+ const toolRequire = Module.createRequire(realpathSync(process.argv[1]));
344
+ if (tool === "c8") {
345
+ // Every c8 threshold -- --check-coverage, --100, .c8rc, .nycrc,
346
+ // package.json, `c8 check-coverage` -- is checked here, and
347
+ // report.js takes this export when it loads.
348
+ toolRequire("../lib/commands/check-coverage.js").checkCoverages = skipped;
349
+ }
350
+ else {
351
+ keepImplicitElse(toolRequire);
352
+ // A gated nyc run, `nyc report --check-coverage` and
353
+ // `nyc check-coverage` all check through this method.
354
+ const NYC = toolRequire("../index.js");
355
+ NYC.prototype.checkCoverage = skipped;
356
+ // The instrumented copy's source map leads to the project's own
357
+ // file, outside the isolated workspace nyc runs in, and
358
+ // excluding after remapping (nyc's default) dropped it: nyc
359
+ // reported "All files 0". The project's excludes still apply,
360
+ // to the copies, before remapping.
361
+ const collect = NYC.prototype.getCoverageMapFromAllCoverageFiles;
362
+ if (typeof collect === "function") {
363
+ NYC.prototype.getCoverageMapFromAllCoverageFiles = function getCoverageMapFromAllCoverageFiles(...args) {
364
+ this.config.excludeAfterRemap = false;
365
+ return Reflect.apply(collect, this, args);
366
+ };
367
+ }
368
+ }
369
+ }
370
+ catch (error) {
371
+ if (process.env.SUPERCOV_DEBUG === "1")
372
+ console.error(`[supercov] ${tool}'s coverage thresholds stay as configured`, error);
373
+ }
374
+ }
133
375
  register(new URL("./resolve-loader.mjs", import.meta.url));
134
376
  if (process.env.SUPERCOV_DEBUG === "1") {
135
377
  console.error("[supercov] preload", { entrypoint });
@@ -162,6 +404,18 @@ if (generatedVitestConfig && /\/vitest(?:\.m?js)?$/.test(entrypoint)) {
162
404
  index -= 1;
163
405
  }
164
406
  }
407
+ // Thresholds on the command line override the configuration's, which the
408
+ // generated config leaves out (see the coverage tools above). Vitest 0.x
409
+ // took them directly under --coverage.
410
+ for (let index = 2; index < process.argv.length; index += 1) {
411
+ const argument = process.argv[index];
412
+ if (!/^--coverage\.(?:thresholds(?:\.[^=]+)?|lines|functions|branches|statements|perFile|100|thresholdAutoUpdate)(?:=|$)/.test(argument))
413
+ continue;
414
+ const valued = !argument.includes("=") && /^(?:\d+(?:\.\d+)?|true|false)$/.test(process.argv[index + 1] ?? "");
415
+ process.argv.splice(index, valued ? 2 : 1);
416
+ index -= 1;
417
+ process.__SUPERCOV_SKIPPED_COVERAGE_THRESHOLDS__("Vitest");
418
+ }
165
419
  if (originalConfig) {
166
420
  process.env.SUPERCOV_ORIGINAL_VITEST_CONFIG = originalConfig;
167
421
  }
@@ -186,6 +440,32 @@ if ((isJestEntrypoint || process.env.JEST_WORKER_ID) && process.env.SUPERCOV_EVI
186
440
  globalThis.__SUPERCOV_DIRECT_RUNTIME__ ??= await import("./runtime.mjs");
187
441
  process.__SUPERCOV_DIRECT_RUNTIME__ ??= globalThis.__SUPERCOV_DIRECT_RUNTIME__;
188
442
  }
443
+ // The Mocha CLI, the child it forks for Node options, and each --parallel
444
+ // worker run tests through Mocha's Runner, which the adapter patches before
445
+ // Mocha loads it.
446
+ const isMochaEntrypoint = /\/node_modules\/(?:\.bin\/_?mocha|mocha\/(?:bin\/_?mocha(?:\.js)?|lib\/cli\/cli\.js|lib\/nodejs\/worker\.c?js))$/.test(entrypoint);
447
+ if (isMochaEntrypoint && process.env.SUPERCOV_EVIDENCE_DIR) {
448
+ globalThis.__SUPERCOV_DIRECT_RUNTIME__ ??= await import("./runtime.mjs");
449
+ process.__SUPERCOV_DIRECT_RUNTIME__ ??= globalThis.__SUPERCOV_DIRECT_RUNTIME__;
450
+ const { adaptMocha } = await import(new URL("./mocha.mjs", import.meta.url).href);
451
+ await adaptMocha(entrypoint);
452
+ }
453
+ // An AVA worker, a thread or a child process, runs one test file through
454
+ // AVA's Runner, which the adapter patches before the worker loads it.
455
+ if (/\/node_modules\/ava\/lib\/worker\/base\.js$/.test(entrypoint) && process.env.SUPERCOV_EVIDENCE_DIR) {
456
+ globalThis.__SUPERCOV_DIRECT_RUNTIME__ ??= await import("./runtime.mjs");
457
+ process.__SUPERCOV_DIRECT_RUNTIME__ ??= globalThis.__SUPERCOV_DIRECT_RUNTIME__;
458
+ const { adaptAva } = await import(new URL("./ava.mjs", import.meta.url).href);
459
+ await adaptAva(entrypoint);
460
+ }
461
+ // The tap CLI runs each test file as a process of its own, marked with
462
+ // TAP_CHILD_ID; the adapter patches tap's TestBase before the file loads it.
463
+ if (process.env.TAP_CHILD_ID && process.env.SUPERCOV_EVIDENCE_DIR && entrypoint) {
464
+ globalThis.__SUPERCOV_DIRECT_RUNTIME__ ??= await import("./runtime.mjs");
465
+ process.__SUPERCOV_DIRECT_RUNTIME__ ??= globalThis.__SUPERCOV_DIRECT_RUNTIME__;
466
+ const { adaptTap } = await import(new URL("./tap.mjs", import.meta.url).href);
467
+ await adaptTap(entrypoint);
468
+ }
189
469
  if (isJestEntrypoint && process.env.SUPERCOV_EVIDENCE_DIR) {
190
470
  // Jest reads one configuration. Ours (jest.config.mjs) reads the user's
191
471
  // the way Jest would and adds the adapter and reporter; an explicit
@@ -210,6 +490,14 @@ if (isJestEntrypoint && process.env.SUPERCOV_EVIDENCE_DIR) {
210
490
  process.argv.splice(index, 1);
211
491
  index -= 1;
212
492
  }
493
+ else if (/^--coverage-?[Tt]hreshold(?:=|$)/.test(argument ?? "")) {
494
+ // Overrides the configuration's coverageThreshold, which
495
+ // jest.config.mjs leaves out (see the coverage tools above).
496
+ const valued = !argument.includes("=") && process.argv[index + 1]?.startsWith("{");
497
+ process.argv.splice(index, valued ? 2 : 1);
498
+ index -= 1;
499
+ process.__SUPERCOV_SKIPPED_COVERAGE_THRESHOLDS__("Jest");
500
+ }
213
501
  }
214
502
  process.argv.push("--config", generatedJestConfig);
215
503
  }
@@ -1,5 +1,5 @@
1
1
  import { resolve as resolvePath } from "node:path";
2
- import { pathToFileURL } from "node:url";
2
+ import { fileURLToPath, pathToFileURL } from "node:url";
3
3
  const GENERATED_TARGET = "__SUPERCOV_PLAYWRIGHT_MODULE__";
4
4
  const TARGET = process.env.SUPERCOV_PLAYWRIGHT_MODULE ??
5
5
  (GENERATED_TARGET.startsWith("__") ? "@playwright/test" : GENERATED_TARGET);
@@ -75,3 +75,42 @@ export async function resolve(specifier, context, nextResolve) {
75
75
  }
76
76
  return nextResolve(specifier, context);
77
77
  }
78
+
79
+ // Node's own test coverage filters files by `--test-coverage-include` and
80
+ // `--test-coverage-exclude` globs relative to the working directory, and the
81
+ // instrumented copy's source map names the project's file, outside the
82
+ // isolated workspace the tests run in: `src/**` matched nothing, the report
83
+ // was empty and its thresholds passed with nothing checked. When those globs
84
+ // are given, the map this process caches names the copy's own path instead;
85
+ // its embedded original text is what Node reports. Nothing is written: other
86
+ // coverage tools read the file as it is on disk.
87
+ const NODE_COVERAGE_FILTERS = process.execArgv.some((argument) => /^--test-coverage-(?:include|exclude)(?:=|$)/.test(argument)) &&
88
+ (Boolean(process.env.NODE_V8_COVERAGE) || process.execArgv.includes("--experimental-test-coverage"));
89
+ const INLINE_MAP = "//# sourceMappingURL=data:application/json;base64,";
90
+ function mapToCopy(source, filename) {
91
+ if (typeof source !== "string" || !source.includes("__supercov"))
92
+ return source;
93
+ const at = source.lastIndexOf(INLINE_MAP);
94
+ if (at < 0)
95
+ return source;
96
+ const end = source.indexOf("\n", at);
97
+ const encoded = source.slice(at + INLINE_MAP.length, end < 0 ? undefined : end).trim();
98
+ try {
99
+ const map = JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
100
+ if (!Array.isArray(map.sources) || map.sources.length !== 1)
101
+ return source;
102
+ map.sources = [filename.replace(/^.*[\\/]/, "")];
103
+ return `${source.slice(0, at)}${INLINE_MAP}${Buffer.from(JSON.stringify(map)).toString("base64")}${end < 0 ? "" : source.slice(end)}`;
104
+ }
105
+ catch {
106
+ return source;
107
+ }
108
+ }
109
+ export async function load(url, context, nextLoad) {
110
+ const result = await nextLoad(url, context);
111
+ if (!NODE_COVERAGE_FILTERS || result.source == null || !url.startsWith("file:"))
112
+ return result;
113
+ const text = typeof result.source === "string" ? result.source : Buffer.from(result.source).toString("utf8");
114
+ const mapped = mapToCopy(text, fileURLToPath(url));
115
+ return mapped === text ? result : { ...result, source: mapped };
116
+ }
@@ -1,8 +1,8 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
- import { mkdirSync, readFileSync } from "node:fs";
2
+ import { readFileSync } from "node:fs";
3
3
  import { relative, resolve, sep } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { atomicWriteFileSync } from "./atomic.mjs";
5
+ import { appendEvidenceRecord } from "./atomic.mjs";
6
6
  import { inferTestProvenance } from "./provenance.mjs";
7
7
  import { serverEvidencePath } from "./transport.mjs";
8
8
 
@@ -131,7 +131,5 @@ export function writeRunnerEvidence(identity, status, scope, evidenceDirectoryOv
131
131
  ? readScopedServerEvidence(scope, serverEvidenceSource)
132
132
  : readScopedServerEvidence(scope),
133
133
  };
134
- const directory = resolve(process.cwd(), evidenceDirectory, `${identity.runner.replace(/[^A-Za-z0-9_-]/g, "_")}-${scope.attemptId}`);
135
- mkdirSync(directory, { recursive: true });
136
- atomicWriteFileSync(resolve(directory, "mcdc.json"), `${JSON.stringify(payload)}\n`);
134
+ appendEvidenceRecord(resolve(process.cwd(), evidenceDirectory), identity.runner.replace(/[^A-Za-z0-9_-]/g, "_"), payload);
137
135
  }