supercov 0.0.12 → 0.0.14

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/README.md CHANGED
@@ -129,10 +129,11 @@ npx supercov diff <older-run> <newer-run>
129
129
  npx supercov merge <first-run-id> <second-run-id>
130
130
  ```
131
131
 
132
- `supercov runs` is metadata-only for uncached history and never reconstructs
133
- coverage for twenty runs merely to list them. Runs whose disposable query index
134
- already exists include their metrics; other rows say `coverage not indexed`.
135
- Selecting a run with `runs <run-id> coverage` materializes its index lazily.
132
+ `supercov runs` always includes coverage percentages. Supercov derives a
133
+ missing disposable query view from the run's immutable evidence before printing
134
+ that row; whether the view was already available is never exposed in the CLI.
135
+ `supercov runs <run-id>` prints the same summary as
136
+ `supercov runs <run-id> coverage`.
136
137
 
137
138
  Coverage queries use `--filter all` by default, matching conventional coverage
138
139
  tools: every executed attempt contributes, including attempts that later fail.
@@ -195,7 +196,7 @@ producing a plausible but invalid aggregate.
195
196
  For a JavaScript or TypeScript project, the CLI:
196
197
 
197
198
  1. refreshes a stable isolated source namespace under
198
- `supercov/workspace/<project>/`, links the existing
199
+ `.supercov/cache/workspace/<project>/`, links the existing
199
200
  dependency tree, and creates generated runner configuration and build output
200
201
  only there; file data uses copy-on-write reflinks where the filesystem
201
202
  supports them, and falls back to copying where it does not; the stable path
@@ -228,7 +229,7 @@ For a JavaScript or TypeScript project, the CLI:
228
229
  rebuilt afterward.
229
230
 
230
231
  Only the Supercov-owned `.supercov/` run store and marker-protected
231
- `supercov/workspace/` cache are modified in the user's checkout. A user-created
232
+ `.supercov/cache/workspace/` cache are modified in the user's checkout. A user-created
232
233
  `supercov/` directory without Supercov's ownership marker is never treated as
233
234
  storage. A per-project lock rejects overlapping runs before either can build. Run state is durably written
234
235
  through preparing/building/testing/publishing phases; SIGINT, SIGTERM,
@@ -244,17 +245,17 @@ state writes use sibling-temp files, fsync, and atomic rename; lock acquisition
244
245
  uses exclusive creation and fsync. Published `run.json` is the durable terminal
245
246
  record, so terminal work state is not retained.
246
247
 
247
- Retention is deterministic because UTC run IDs sort chronologically:
248
+ Retention is deterministic because run records store their UTC start time:
248
249
 
249
250
  ```sh
250
- npx supercov prune --keep 20
251
- npx supercov prune --keep 20 --dry-run
252
- npx supercov clean --keep 20 # also removes the shared build cache
251
+ npx supercov clean # removes all stored runs and the shared build cache
252
+ npx supercov clean --dry-run # previews the default full cleanup
253
+ npx supercov clean --keep 20 # explicitly retains the 20 newest runs
253
254
  ```
254
255
 
255
- Neither operation runs automatically. `prune` removes explicit history beyond
256
- the requested retention and orphan/terminal transient data while preserving
257
- the shared cache. `clean` also removes that cache. Both acquire the same lock
256
+ Cleanup never runs automatically. `clean` removes explicit history beyond the
257
+ requested retention, orphan/terminal transient data, and the shared build
258
+ cache. It acquires the same lock
258
259
  as a coverage run, refuse to race an active run, and never touch files outside
259
260
  `.supercov/`.
260
261
 
@@ -43,6 +43,20 @@ reported separately from Supercov's recorded phases.
43
43
 
44
44
  ## Reference measurement, not a guarantee
45
45
 
46
+ On 2026-08-26, the 30-test Essential SEO Playwright suite running across seven
47
+ cycle-restored VMs produced a 43.02 s uncovered control and two valid warm
48
+ Supercov measurements of 46.30 s and 40.20 s. The slower covered sample is
49
+ 1.076x the control and therefore remains below the current 1.1x realistic-suite
50
+ target; the faster result illustrates why multiple alternating pairs are still
51
+ required for a statistically stable benchmark. The 40.20 s run spent 38.63 s
52
+ inside the unchanged test command and 1.04 s total on initialization, workspace
53
+ refresh, adapter restoration and evidence publication. It attributed 6,727
54
+ server records across 30 remote launches with zero corrupt evidence records.
55
+
56
+ VM-image creation is not included in these warm comparisons. A changed engine
57
+ fingerprint intentionally caused a one-time 119 s rebake before the first
58
+ post-change run; comparing that cold run with a warm control would be invalid.
59
+
46
60
  On 2026-08-24, the 29-test Essential SEO offline suite on the development Mac
47
61
  produced this warm pair:
48
62
 
@@ -73,8 +87,10 @@ Before raw-evidence-only storage, the reference run retained 4.5 MB of reports
73
87
  and 1.7 MB across 178 loose evidence files. Its canonical compressed JSON was
74
88
  0.9 MB. The execution evidence alone packed to about 121 KiB; current archives
75
89
  also embed the exact denominator manifest and are the sole coverage artifact.
76
- Every CLI query derives its view from the archive on demand without writing a
77
- cache.
90
+ Every CLI query derives its view from the archive. The first query builds a
91
+ disposable integrity-bound binary index; later queries reuse it while the run
92
+ identity remains valid. The index is an implementation detail and can always be
93
+ deleted and reconstructed.
78
94
  These numbers are application- and filesystem-specific optimization baselines.
79
95
  An exact matching Vite build is also reused across runs, removing the measured
80
96
  4.87-second repeated build; any source/configuration/toolchain-key change falls
@@ -88,8 +104,8 @@ phase and 40.49 seconds total: 0.07 seconds initialization, 0.36 seconds
88
104
  workspace refresh, 0.05 seconds adapter setup, 39.84 seconds in the unchanged
89
105
  test command, and 0.12 seconds evidence validation/archive publication. Fresh
90
106
  process queries for summary, files, and gaps each took 0.16–0.20 seconds on
91
- this run while writing no cache. Test execution is still the dominant and
92
- naturally variable part of the total.
107
+ this run before the reusable query index was introduced. Test execution is
108
+ still the dominant and naturally variable part of the total.
93
109
 
94
110
  ## Isolation strategy trade-offs
95
111
 
@@ -16,7 +16,7 @@ is below the project's `.supercov/` directory:
16
16
  | `work/<run>/state.json` | In-flight lifecycle record; removed after atomic run publication. |
17
17
  | `work/<run>/run-publication/` | Incomplete run staging; atomically renamed or removed on recovery. |
18
18
  | `evidence/<run>/` | Loose in-flight evidence; packed and removed after publication. |
19
- | `runs/<run>/` | Immutable `evidence.raw.gz` (manifest plus raw execution evidence) and `run.json`; retained until explicit prune/clean. Derived query views are never cached. |
19
+ | `runs/<run>/` | Immutable `evidence.raw.gz` (manifest plus raw execution evidence) and `run.json`; retained until explicit `clean`. Derived query views are cached only after their first query. |
20
20
  | `cache/instrumented-workspace/<project>/` | Stable physical fallback and provider snapshot cache. |
21
21
  | `cache/instrumented-workspace/.<project>.staging-*` | Unpublished cache transaction; removed on error or recovery. |
22
22
  | `cache/instrumented-workspace/.<project>.previous-*` | Last complete cache generation during publication; restored or removed on recovery. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supercov",
3
- "version": "0.0.12",
3
+ "version": "0.0.14",
4
4
  "description": "Zero-edit, runner-aware coverage completeness for JavaScript test suites",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -63,12 +63,12 @@
63
63
  "prepublishOnly": "npm run release:check"
64
64
  },
65
65
  "optionalDependencies": {
66
- "@supercov/cli-darwin-arm64": "0.0.12",
67
- "@supercov/cli-darwin-x64": "0.0.12",
68
- "@supercov/cli-linux-arm64-gnu": "0.0.12",
69
- "@supercov/cli-linux-arm64-musl": "0.0.12",
70
- "@supercov/cli-linux-x64-gnu": "0.0.12",
71
- "@supercov/cli-linux-x64-musl": "0.0.12"
66
+ "@supercov/cli-darwin-arm64": "0.0.14",
67
+ "@supercov/cli-darwin-x64": "0.0.14",
68
+ "@supercov/cli-linux-arm64-gnu": "0.0.14",
69
+ "@supercov/cli-linux-arm64-musl": "0.0.14",
70
+ "@supercov/cli-linux-x64-gnu": "0.0.14",
71
+ "@supercov/cli-linux-x64-musl": "0.0.14"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "@playwright/test": ">=1.55.0",
@@ -1,4 +1,4 @@
1
- import { mkdirSync, openSync, closeSync, fsyncSync, renameSync, rmSync, writeFileSync, } from "node:fs";
1
+ import { appendFileSync, existsSync, mkdirSync, openSync, closeSync, fsyncSync, renameSync, rmSync, writeFileSync, } from "node:fs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { dirname } from "node:path";
4
4
  function fsyncDirectory(path) {
@@ -38,6 +38,26 @@ export function atomicWriteFileSync(path, data, options) {
38
38
  rmSync(temporary, { force: true });
39
39
  }
40
40
  }
41
+ /** Append one recoverable JSONL record and make the completed line durable. */
42
+ export function appendJsonLineDurableSync(path, data) {
43
+ mkdirSync(dirname(path), { recursive: true });
44
+ const existed = existsSync(path);
45
+ const descriptor = openSync(path, "a", 0o600);
46
+ try {
47
+ writeFileSync(descriptor, data.endsWith("\n") ? data : `${data}\n`);
48
+ fsyncSync(descriptor);
49
+ }
50
+ finally {
51
+ closeSync(descriptor);
52
+ }
53
+ if (!existed)
54
+ fsyncDirectory(dirname(path));
55
+ }
56
+ /** Append a complete local record; process exit closes it before publication. */
57
+ export function appendJsonLineSync(path, data) {
58
+ mkdirSync(dirname(path), { recursive: true });
59
+ appendFileSync(path, data.endsWith("\n") ? data : `${data}\n`, { mode: 0o600 });
60
+ }
41
61
  /** Atomically publish a fully prepared file or directory and persist its entry. */
42
62
  export function atomicRenameSync(source, destination) {
43
63
  mkdirSync(dirname(destination), { recursive: true });
@@ -16,6 +16,7 @@ const patchedBuilders = new WeakSet();
16
16
  const exportedValues = new WeakSet();
17
17
  const capabilityProxies = new WeakMap();
18
18
  const importedCapabilityProxies = new WeakMap();
19
+ const importedMemberProxies = new WeakMap();
19
20
  let installed = false;
20
21
  let remoteLaunchSequence = 0;
21
22
  function executionLogPath(path) {
@@ -189,6 +190,7 @@ export function guestCoverageEnvironment(mapping, coverageEnvironment = process.
189
190
  ...translated,
190
191
  SUPERCOV_PROJECT_ROOT: mapping.guestRoot,
191
192
  SUPERCOV_CJS_INTERCEPT: "1",
193
+ SUPERCOV_DURABLE_EVIDENCE_EACH_TEST: "1",
192
194
  NODE_OPTIONS: appendNodeImport(existingEnvironment.NODE_OPTIONS, registerUrl),
193
195
  };
194
196
  }
@@ -227,6 +229,43 @@ function wrapResult(value, mapping) {
227
229
  return value.then((result) => wrapCapabilityObject(result, mapping));
228
230
  return wrapCapabilityObject(value, mapping);
229
231
  }
232
+
233
+ // A Proxy `get` trap must return the exact value of a non-configurable,
234
+ // non-writable own data property (and `undefined` for a non-configurable
235
+ // accessor without a getter). Constructors commonly expose `prototype` this
236
+ // way. Returning another capability proxy is a TypeError before user code can
237
+ // run, as seen with PrismaSessionStorage during an Essential Apps VM bake.
238
+ function fixedProxyValue(target, property) {
239
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, property);
240
+ if (!descriptor || descriptor.configurable)
241
+ return { fixed: false };
242
+ if ("value" in descriptor && !descriptor.writable)
243
+ return { fixed: true, value: descriptor.value };
244
+ if (!("value" in descriptor) && descriptor.get === undefined)
245
+ return { fixed: true, value: undefined };
246
+ return { fixed: false };
247
+ }
248
+
249
+ function wrapImportedMember(member, receiver, invoke) {
250
+ let members = importedMemberProxies.get(receiver);
251
+ if (!members) {
252
+ members = new WeakMap();
253
+ importedMemberProxies.set(receiver, members);
254
+ }
255
+ const cached = members.get(member);
256
+ if (cached)
257
+ return cached;
258
+ const proxy = new Proxy(member, {
259
+ apply(target, _thisArgument, args) {
260
+ return invoke(target, receiver, args);
261
+ },
262
+ construct(target, args, newTarget) {
263
+ return wrapImportedCapability(Reflect.construct(target, args, newTarget));
264
+ },
265
+ });
266
+ members.set(member, proxy);
267
+ return proxy;
268
+ }
230
269
  /**
231
270
  * A remote SDK can hide its first executable launch inside a configuration
232
271
  * callback (for example an image warmup hook). Decorate callbacks in ordinary
@@ -287,6 +326,9 @@ export function wrapCapabilityObject(value, mapping) {
287
326
  return cached;
288
327
  const proxy = new Proxy(object, {
289
328
  get(target, property) {
329
+ const fixed = fixedProxyValue(target, property);
330
+ if (fixed.fixed)
331
+ return fixed.value;
290
332
  // Use the real target as the receiver so SDK getters backed by private
291
333
  // fields keep their brand check. Method calls are likewise bound below.
292
334
  const member = Reflect.get(target, property, target);
@@ -362,10 +404,13 @@ export function wrapImportedCapability(value) {
362
404
  };
363
405
  const proxy = new Proxy(object, {
364
406
  get(target, property) {
407
+ const fixed = fixedProxyValue(target, property);
408
+ if (fixed.fixed)
409
+ return fixed.value;
365
410
  const member = Reflect.get(target, property, target);
366
411
  if (typeof member !== "function")
367
412
  return wrapImportedCapability(member);
368
- return (...args) => invoke(member, target, args);
413
+ return wrapImportedMember(member, target, invoke);
369
414
  },
370
415
  ...(typeof value === "function"
371
416
  ? {
@@ -512,6 +557,11 @@ export function installLaunchSupervisor() {
512
557
  const originalLoad = moduleLoader._load;
513
558
  moduleLoader._load = function supercovCapabilityLoad(request, parent, isMain) {
514
559
  const exports = originalLoad.call(this, request, parent, isMain);
560
+ // A user's test command may itself be implemented by an installed
561
+ // package. That package can import a VM/container/process SDK without
562
+ // any project-owned module ever crossing the SDK boundary. Inspect all
563
+ // newly loaded export graphs (deduplicated by `exportedValues`) so
564
+ // capability discovery remains provider- and runner-neutral.
515
565
  inspectExports(exports);
516
566
  return exports;
517
567
  };
@@ -12,10 +12,11 @@ import { mkdirSync, readFileSync, rmSync } from "node:fs";
12
12
  import http from "node:http";
13
13
  import https from "node:https";
14
14
  import { syncBuiltinESMExports } from "node:module";
15
- import { dirname, relative, resolve, sep } from "node:path";
15
+ import { relative, resolve, sep } from "node:path";
16
16
  import * as standardPlaywright from "@playwright/test";
17
+ import * as coverageRuntime from "./runtime.js";
17
18
  import { inferTestProvenance } from "./provenance.js";
18
- import { atomicWriteFileSync } from "./atomic.js";
19
+ import { appendJsonLineDurableSync, appendJsonLineSync } from "./atomic.js";
19
20
  import { COVERAGE_PHASE_HEADER, COVERAGE_PHASE_COOKIE, COVERAGE_SCOPE_COOKIE, COVERAGE_SCOPE_HEADER, COVERAGE_CARRIER_ENV, encodeCoverageCarrier, encodeCoverageScope, serverEvidenceDirectory, serverEvidencePath, } from "./transport.js";
20
21
  export * from "@playwright/test";
21
22
  const generatedTargetModule = "__SUPERCOV_PLAYWRIGHT_MODULE__";
@@ -32,6 +33,8 @@ const adapter = (targetModule === "@playwright/test"
32
33
  const base = (adapter[targetTestExport] ?? adapter.test);
33
34
  const baseExpect = adapter.expect;
34
35
  const GENERATED_EVIDENCE_DIRECTORY = "__SUPERCOV_EVIDENCE_DIRECTORY__";
36
+ const evidenceWriterIdentity = () => (process.env.SUPERCOV_EXECUTION_LOG_SHARD ?? `pid-${process.pid}`)
37
+ .replace(/[^A-Za-z0-9_-]/g, "_");
35
38
  const GENERATED_RUN_ID = "__SUPERCOV_RUN_ID__";
36
39
  const PHASE_STORAGE_KEY = "__supercov_phase";
37
40
  const ACTION_METHODS = new Set([
@@ -329,6 +332,11 @@ class CoveragePhaseController {
329
332
  },
330
333
  });
331
334
  this.proxyCache.set(target, proxy);
335
+ // Playwright's built-in `page` fixture is created through the wrapped
336
+ // worker-scoped browser. The test-scoped fixture sees that proxy again;
337
+ // treating our own proxy as a fresh target would nest wrappers, emit two
338
+ // phases for one operation, and perform every browser activation twice.
339
+ this.proxyCache.set(proxy, proxy);
332
340
  return proxy;
333
341
  }
334
342
  createPhase(kind, operation, causedByPhaseId, source = callerSource()) {
@@ -467,7 +475,7 @@ class CoveragePhaseController {
467
475
  let activeController;
468
476
  let bridgedAssertionDepth = 0;
469
477
  const controllers = new Map();
470
- const directRuntime = () => globalThis.__SUPERCOV_DIRECT_RUNTIME__;
478
+ const directRuntime = () => globalThis.__SUPERCOV_DIRECT_RUNTIME__ ?? coverageRuntime;
471
479
  globalThis.__SUPERCOV_ASSERTION_PHASE_BRIDGE__ = (operation, source, callback) => {
472
480
  const controller = activeController;
473
481
  const runtime = directRuntime();
@@ -829,8 +837,6 @@ const instrumentedTest = base.extend({
829
837
  // Emit an artifact even when this test touched no application source.
830
838
  // A complete test-to-coverage matrix must also identify tests that are
831
839
  // removable without changing coverage.
832
- const outputPath = testInfo.outputPath("mcdc.json");
833
- mkdirSync(dirname(outputPath), { recursive: true });
834
840
  const testFile = relative(process.cwd(), testInfo.file)
835
841
  .split(sep)
836
842
  .join("/");
@@ -854,7 +860,6 @@ const instrumentedTest = base.extend({
854
860
  server,
855
861
  };
856
862
  const serialized = `${JSON.stringify(payload)}\n`;
857
- atomicWriteFileSync(outputPath, serialized);
858
863
  // Pool runners may cycle-restore a VM immediately after Playwright
859
864
  // exits, which can discard or overwrite the normal artifact copy.
860
865
  // Write one uniquely named, one-shot evidence file to the runner's
@@ -865,10 +870,10 @@ const instrumentedTest = base.extend({
865
870
  : GENERATED_EVIDENCE_DIRECTORY);
866
871
  if (evidenceDirectory) {
867
872
  const resolvedDirectory = resolve(process.cwd(), evidenceDirectory);
868
- const safeTestId = testInfo.testId.replace(/[^a-zA-Z0-9_-]/g, "_");
869
- const testEvidenceDirectory = resolve(resolvedDirectory, `${safeTestId}-${testInfo.retry}`);
870
- mkdirSync(testEvidenceDirectory, { recursive: true });
871
- atomicWriteFileSync(resolve(testEvidenceDirectory, "mcdc.json"), serialized);
873
+ const append = process.env.SUPERCOV_DURABLE_EVIDENCE_EACH_TEST === "1"
874
+ ? appendJsonLineDurableSync
875
+ : appendJsonLineSync;
876
+ append(resolve(resolvedDirectory, `playwright-worker-${evidenceWriterIdentity()}-${process.pid}.mcdc.jsonl`), serialized);
872
877
  }
873
878
  }
874
879
  finally {
@@ -1,10 +1,12 @@
1
- import { mkdirSync } from "node:fs";
2
1
  import { relative, resolve, sep } from "node:path";
3
2
  import { inferTestProvenance } from "./provenance.js";
4
- import { atomicWriteFileSync } from "./atomic.js";
3
+ import { appendJsonLineDurableSync, appendJsonLineSync } from "./atomic.js";
5
4
  const GENERATED_EVIDENCE_DIRECTORY = "__SUPERCOV_EVIDENCE_DIRECTORY__";
5
+ const evidenceWriterIdentity = () => (process.env.SUPERCOV_EXECUTION_LOG_SHARD ?? `pid-${process.pid}`)
6
+ .replace(/[^A-Za-z0-9_-]/g, "_");
6
7
  /** Records outcomes even when browser or fixture startup fails before coverage. */
7
8
  export default class SupercovPlaywrightReporter {
9
+ records = [];
8
10
  onTestEnd(test, result) {
9
11
  const evidenceDirectory = process.env["SUPERCOV_EVIDENCE_DIR"] ??
10
12
  (GENERATED_EVIDENCE_DIRECTORY.startsWith("__")
@@ -32,9 +34,18 @@ export default class SupercovPlaywrightReporter {
32
34
  browser: [],
33
35
  server: [],
34
36
  };
35
- const safeId = test.id.replace(/[^a-zA-Z0-9_-]/g, "_");
36
- const directory = resolve(process.cwd(), evidenceDirectory, `playwright-${safeId}-${result.retry}-status`);
37
- mkdirSync(directory, { recursive: true });
38
- atomicWriteFileSync(resolve(directory, "mcdc.json"), `${JSON.stringify(payload)}\n`);
37
+ this.records.push(payload);
38
+ }
39
+ onEnd() {
40
+ const evidenceDirectory = process.env["SUPERCOV_EVIDENCE_DIR"] ??
41
+ (GENERATED_EVIDENCE_DIRECTORY.startsWith("__")
42
+ ? undefined
43
+ : GENERATED_EVIDENCE_DIRECTORY);
44
+ if (!evidenceDirectory || this.records.length === 0)
45
+ return;
46
+ const append = process.env.SUPERCOV_DURABLE_EVIDENCE_EACH_TEST === "1"
47
+ ? appendJsonLineDurableSync
48
+ : appendJsonLineSync;
49
+ append(resolve(process.cwd(), evidenceDirectory, `playwright-status-${evidenceWriterIdentity()}-${process.pid}.mcdc.jsonl`), `${this.records.map(record => JSON.stringify(record)).join("\n")}\n`);
39
50
  }
40
51
  }
@@ -16,7 +16,9 @@ installLaunchSupervisor();
16
16
  // run and periodically reports public active-resource types. This deliberately
17
17
  // uses no Unix signal: a launch tree may contain uninstrumented Node children,
18
18
  // and signalling one would terminate a healthy command by default.
19
- if (!process.__SUPERCOV_DIAGNOSTIC_REPORTER__) {
19
+ const verboseDiagnostics = [process.env.SUPERCOV_VERBOSE, process.env.SUPERCOV_DEBUG]
20
+ .some(value => value === "1" || value === "true" || value === "yes");
21
+ if (verboseDiagnostics && !process.__SUPERCOV_DIAGNOSTIC_REPORTER__) {
20
22
  process.__SUPERCOV_DIAGNOSTIC_REPORTER__ = true;
21
23
  const ownerFile = process.env.SUPERCOV_DIAGNOSTIC_OWNER_FILE;
22
24
  let ownerDescriptor;
@@ -62,11 +64,18 @@ if (!process.__SUPERCOV_DIAGNOSTIC_REPORTER__) {
62
64
  });
63
65
  }
64
66
  }
65
- // Assertion-call instrumentation also uses this runtime in test processes
66
- // whose application build is handled by Vite or another compiler. Loading it
67
- // for every isolated test launch keeps node:assert attribution runner-agnostic.
68
- globalThis.__SUPERCOV_DIRECT_RUNTIME__ ??= await import("./runtime.js");
69
- process.__SUPERCOV_DIRECT_RUNTIME__ ??= globalThis.__SUPERCOV_DIRECT_RUNTIME__;
67
+ // Runner adapters and instrumented modules import the runtime when they need
68
+ // it. Keeping the preload itself thin avoids evaluating the full collector in
69
+ // npm launchers, web-server supervisors, and other Node children that never
70
+ // execute measured JavaScript.
71
+ if (process.env.SUPERCOV_DURABLE_EVIDENCE_EACH_TEST === "1") {
72
+ // A translated remote/VM command can execute an ahead-of-run transformed
73
+ // test through an opaque runner that bypasses the ordinary Playwright or
74
+ // node:test import boundary. The remote-launch adapter marks that process;
75
+ // initialize the runtime before its transformed module can evaluate.
76
+ globalThis.__SUPERCOV_DIRECT_RUNTIME__ ??= await import("./runtime.js");
77
+ process.__SUPERCOV_DIRECT_RUNTIME__ ??= globalThis.__SUPERCOV_DIRECT_RUNTIME__;
78
+ }
70
79
  // Workers are independent Node processes and an explicit `execArgv: []`
71
80
  // otherwise strips the preload that supplies the isolated runtime. Preserve
72
81
  // every user option while adding exactly one Supercov import.
@@ -105,6 +114,7 @@ const generatedPlaywrightConfig = process.env.SUPERCOV_GENERATED_PLAYWRIGHT_CONF
105
114
  const generatedJestConfig = process.env.SUPERCOV_GENERATED_JEST_CONFIG;
106
115
  const entrypoint = process.argv[1]?.replaceAll("\\", "/") ?? "";
107
116
  const playwrightTarget = process.env.SUPERCOV_PLAYWRIGHT_MODULE;
117
+ const projectRoot = process.env.SUPERCOV_PROJECT_ROOT?.replaceAll("\\", "/").replace(/\/$/, "");
108
118
  const nodeTestWrapper = new URL("./nodeTest.js", import.meta.url).href;
109
119
  const nodeAssertWrapper = new URL("./nodeAssert.js", import.meta.url).href;
110
120
  const nodeAssertStrictWrapper = new URL("./nodeAssertStrict.js", import.meta.url).href;
@@ -188,6 +198,7 @@ if (generatedPlaywrightConfig &&
188
198
  }
189
199
  if (process.env.SUPERCOV_CJS_INTERCEPT === "1" &&
190
200
  process.env.SUPERCOV_INSIDE_PLAYWRIGHT === "1" &&
201
+ !isPlaywrightEntrypoint &&
191
202
  process.env.SUPERCOV_INSIDE_VITEST !== "1" &&
192
203
  process.env.VITEST !== "true") {
193
204
  const target = playwrightTarget ?? "@playwright/test";
@@ -220,7 +231,18 @@ if (process.env.SUPERCOV_CJS_INTERCEPT === "1" &&
220
231
  // itself imports the native built-in from Supercov's generated directory, so
221
232
  // only first-party callers are redirected and recursion is impossible.
222
233
  if (process.env.SUPERCOV_CJS_INTERCEPT === "1") {
223
- const projectRoot = process.env.SUPERCOV_PROJECT_ROOT?.replaceAll("\\", "/").replace(/\/$/, "");
234
+ const entrypointBelongsToProject = Boolean(projectRoot && entrypoint.startsWith(`${projectRoot}/`));
235
+ const runnerCanLoadProjectCode = entrypointBelongsToProject ||
236
+ entrypoint === "" ||
237
+ process.env.SUPERCOV_INSIDE_VITEST === "1" ||
238
+ (process.env.SUPERCOV_INSIDE_PLAYWRIGHT === "1" && !isPlaywrightEntrypoint);
239
+ if (!runnerCanLoadProjectCode) {
240
+ // Package-manager and Playwright coordinator processes propagate the
241
+ // preload to their children but never evaluate project test modules.
242
+ // Their worker/child entrypoints install these synchronous CJS hooks.
243
+ // Avoid paying to import three runner adapters in every launcher.
244
+ }
245
+ else {
224
246
  const nodeTestAdapter = await import(__rewriteRelativeImportExtension(nodeTestWrapper));
225
247
  const cjsNodeTestAdapter = Object.assign(nodeTestAdapter.test, nodeTestAdapter);
226
248
  const nodeAssertAdapter = await import(__rewriteRelativeImportExtension(nodeAssertWrapper));
@@ -246,4 +268,5 @@ if (process.env.SUPERCOV_CJS_INTERCEPT === "1") {
246
268
  : cjsNodeAssertAdapter;
247
269
  return originalLoad.call(this, request, parent, isMain);
248
270
  };
271
+ }
249
272
  }
@@ -1014,6 +1014,52 @@ function mcdcEnd(frame, value) {
1014
1014
  }
1015
1015
  return value;
1016
1016
  }
1017
+ const directRuntimeApi = {
1018
+ activateCoverageScope,
1019
+ beginBufferedServerEvidence,
1020
+ bindCoverageContext,
1021
+ coverageCarrier,
1022
+ coverageContextEnvironment,
1023
+ coverageContextHeaders,
1024
+ coverageHit,
1025
+ coverageHitV2,
1026
+ coverageSnapshot,
1027
+ decodeProbeV2Vector,
1028
+ defaultEntered,
1029
+ defaultSelected,
1030
+ enableRuntimeSnapshotEvidence,
1031
+ flushBufferedBackgroundEvidence,
1032
+ flushBufferedServerEvidence,
1033
+ loopBegin,
1034
+ loopEnd,
1035
+ loopEntered,
1036
+ mcdcBegin,
1037
+ mcdcCondition,
1038
+ mcdcEnd,
1039
+ mcdcEndV2,
1040
+ optionalCallBegin,
1041
+ optionalCallContinued,
1042
+ optionalCallEnd,
1043
+ optionalCallReached,
1044
+ optionalSelect,
1045
+ parenthesizedAssignmentValue,
1046
+ registerProbeV2,
1047
+ resetCoverage,
1048
+ selectionBegin,
1049
+ selectionEnd,
1050
+ selectionRight,
1051
+ takeNodeAssertionPhases,
1052
+ tryBegin,
1053
+ tryCatch,
1054
+ tryEnd,
1055
+ withCoverageCarrier,
1056
+ withNodeAssertionPhase,
1057
+ withRequestPhase,
1058
+ writeExclusiveBackgroundRecord
1059
+ };
1060
+ globalThis.__SUPERCOV_DIRECT_RUNTIME__ ??= directRuntimeApi;
1061
+ if (typeof process !== "undefined")
1062
+ process.__SUPERCOV_DIRECT_RUNTIME__ ??= directRuntimeApi;
1017
1063
  export {
1018
1064
  activateCoverageScope,
1019
1065
  beginBufferedServerEvidence,