supercov 0.0.19 → 0.0.21

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supercov",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "Zero-edit, runner-aware coverage completeness for JavaScript test suites",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -68,12 +68,12 @@
68
68
  "prepublishOnly": "npm run release:check"
69
69
  },
70
70
  "optionalDependencies": {
71
- "@supercov/cli-darwin-arm64": "0.0.19",
72
- "@supercov/cli-darwin-x64": "0.0.19",
73
- "@supercov/cli-linux-arm64-gnu": "0.0.19",
74
- "@supercov/cli-linux-arm64-musl": "0.0.19",
75
- "@supercov/cli-linux-x64-gnu": "0.0.19",
76
- "@supercov/cli-linux-x64-musl": "0.0.19"
71
+ "@supercov/cli-darwin-arm64": "0.0.21",
72
+ "@supercov/cli-darwin-x64": "0.0.21",
73
+ "@supercov/cli-linux-arm64-gnu": "0.0.21",
74
+ "@supercov/cli-linux-arm64-musl": "0.0.21",
75
+ "@supercov/cli-linux-x64-gnu": "0.0.21",
76
+ "@supercov/cli-linux-x64-musl": "0.0.21"
77
77
  },
78
78
  "peerDependencies": {
79
79
  "@playwright/test": ">=1.55.0",
@@ -195,7 +195,7 @@ export function guestCoverageEnvironment(mapping, coverageEnvironment = process.
195
195
  key,
196
196
  value === undefined ? value : replaceRoot(value, mapping),
197
197
  ]));
198
- const registerUrl = pathToFileURL(resolve(mapping.guestRoot, ".supercov/register.mjs")).href;
198
+ const registerUrl = pathToFileURL(resolve(mapping.guestRoot, ".supercov/node_modules/register.mjs")).href;
199
199
  return {
200
200
  ...existingEnvironment,
201
201
  ...translated,
@@ -546,7 +546,14 @@ function injectChildEnvironment(method, args) {
546
546
  const environment = existingEnvironment
547
547
  ? { ...existingEnvironment, ...inherited }
548
548
  : { ...process.env, ...inherited };
549
- environment.NODE_OPTIONS = appendNodeImport(existingEnvironment?.NODE_OPTIONS ?? process.env.NODE_OPTIONS, pathToFileURL(resolve(process.env["SUPERCOV_PROJECT_ROOT"] ?? process.cwd(), ".supercov/register.mjs")).href);
549
+ // The register path must come from THIS module's own location, never from
550
+ // the environment or the working directory. Monorepo runners both defeat
551
+ // the old derivation at once: turbo's strict env strips SUPERCOV_PROJECT_ROOT
552
+ // from task children, and it runs each task with cwd inside the package --
553
+ // so the fallback fabricated packages/<name>/.supercov/register.mjs and
554
+ // every task aborted with ERR_MODULE_NOT_FOUND. launchSupervisor.js is
555
+ // generated into the same .supercov directory as register.mjs.
556
+ environment.NODE_OPTIONS = appendNodeImport(existingEnvironment?.NODE_OPTIONS ?? process.env.NODE_OPTIONS, new URL("./register.mjs", import.meta.url).href);
550
557
  next[index] = { ...options, env: environment };
551
558
  if (typeof original === "function")
552
559
  next.splice(index + 1, 0, original);
@@ -1,4 +1,6 @@
1
1
  import * as native from "node:test";
2
+ import { fileURLToPath } from "node:url";
3
+ import vm from "node:vm";
2
4
  import { beginBufferedServerEvidence, flushBufferedServerEvidence, takeNodeAssertionPhases, withCoverageCarrier, } from "./runtime.js";
3
5
  import { callerLocation, runnerExecutionScope, writeRunnerEvidence, } from "./runnerEvidence.js";
4
6
  function callbackIndex(args) {
@@ -14,6 +16,76 @@ function testOptions(args, index) {
14
16
  const candidate = args.slice(0, index).find((value) => value && typeof value === "object" && !Array.isArray(value));
15
17
  return candidate;
16
18
  }
19
+ // node:test derives a test's reported location from the direct caller of the
20
+ // registration call, and the adapter is that caller: every failing test
21
+ // reported "test at .../nodeTest.js". A compiled trampoline carrying the
22
+ // user's call site as its script origin registers the test instead, so the
23
+ // runner sees the location a direct call would have produced. The padded
24
+ // second line puts the call expression at the user's exact line and column.
25
+ const registrationSites = new Map();
26
+ function registrationAt(location) {
27
+ if (!location.file ||
28
+ location.line === undefined ||
29
+ location.column === undefined ||
30
+ location.line < 2)
31
+ return undefined;
32
+ const key = `${location.file}:${location.line}:${location.column}`;
33
+ let site = registrationSites.get(key);
34
+ if (site === undefined) {
35
+ try {
36
+ const filename = location.file.startsWith("file://")
37
+ ? fileURLToPath(location.file)
38
+ : location.file;
39
+ site = vm.compileFunction(`return (\n${" ".repeat(Math.max(0, location.column - 1))}original(...args));`, ["original", "args"], { filename, lineOffset: location.line - 2 });
40
+ }
41
+ catch {
42
+ site = null;
43
+ }
44
+ registrationSites.set(key, site);
45
+ }
46
+ return site ?? undefined;
47
+ }
48
+ // A failing test's stack was captured while Supercov's wrappers were on the
49
+ // call path and while the code ran inside the mirrored workspace. Neither is
50
+ // part of the user's program: drop adapter frames and map workspace paths
51
+ // back to the source project so the report matches an uninstrumented run.
52
+ const restoredErrors = new WeakSet();
53
+ function restoreUserError(error, depth = 0) {
54
+ if (depth > 4 ||
55
+ !error ||
56
+ typeof error !== "object" ||
57
+ restoredErrors.has(error))
58
+ return error;
59
+ restoredErrors.add(error);
60
+ const workspaceRoot = process.env["SUPERCOV_PROJECT_ROOT"];
61
+ const sourceRoot = process.env["SUPERCOV_SOURCE_PROJECT_ROOT"];
62
+ const remap = (text) => workspaceRoot && sourceRoot && workspaceRoot !== sourceRoot
63
+ ? text.split(workspaceRoot).join(sourceRoot)
64
+ : text;
65
+ try {
66
+ if (typeof error.stack === "string")
67
+ error.stack = remap(error.stack
68
+ .split("\n")
69
+ .filter((line) => !(line.trimStart().startsWith("at ") &&
70
+ line.includes("/.supercov/")))
71
+ .join("\n"));
72
+ if (typeof error.message === "string")
73
+ error.message = remap(error.message);
74
+ }
75
+ catch {
76
+ // Frozen or accessor-backed errors keep their original form.
77
+ }
78
+ try {
79
+ restoreUserError(error.cause, depth + 1);
80
+ if (Array.isArray(error.errors))
81
+ for (const aggregated of error.errors)
82
+ restoreUserError(aggregated, depth + 1);
83
+ }
84
+ catch {
85
+ // A throwing accessor never replaces the user's error.
86
+ }
87
+ return error;
88
+ }
17
89
  function wrappedRegistration(original) {
18
90
  const wrapped = function supercovNodeTest(...args) {
19
91
  const index = callbackIndex(args);
@@ -72,8 +144,10 @@ function wrappedRegistration(original) {
72
144
  try {
73
145
  if (callback.length >= 2) {
74
146
  const callbackDone = (error) => {
75
- if (error)
147
+ if (error) {
76
148
  status = "failed";
149
+ restoreUserError(error);
150
+ }
77
151
  emit();
78
152
  done?.(error);
79
153
  };
@@ -87,7 +161,7 @@ function wrappedRegistration(original) {
87
161
  }, (error) => {
88
162
  status = "failed";
89
163
  emit();
90
- throw error;
164
+ throw restoreUserError(error);
91
165
  });
92
166
  emit();
93
167
  return result;
@@ -95,7 +169,7 @@ function wrappedRegistration(original) {
95
169
  catch (error) {
96
170
  status = "failed";
97
171
  emit();
98
- throw error;
172
+ throw restoreUserError(error);
99
173
  }
100
174
  };
101
175
  // node:test uses callback arity to distinguish promise/synchronous tests
@@ -107,6 +181,9 @@ function wrappedRegistration(original) {
107
181
  : function supercovNodeTestCallback(context) {
108
182
  return execute(this, context);
109
183
  };
184
+ const registration = registrationAt(location);
185
+ if (registration)
186
+ return registration(this === undefined ? original : original.bind(this), next);
110
187
  return Reflect.apply(original, this, next);
111
188
  };
112
189
  for (const property of ["skip", "todo", "only"]) {
@@ -120,14 +197,51 @@ function wrappedRegistration(original) {
120
197
  }
121
198
  return wrapped;
122
199
  }
200
+ // Hooks carry no coverage evidence, but an error thrown inside one still
201
+ // reaches the report with the adapter's execution context in its stack.
202
+ // Restore it at the same boundary the test wrapper uses.
203
+ function restoringHook(original) {
204
+ return function supercovNodeTestHook(...args) {
205
+ const index = callbackIndex(args);
206
+ if (index < 0)
207
+ return Reflect.apply(original, this, args);
208
+ const callback = args[index];
209
+ const next = [...args];
210
+ // node:test uses callback arity to distinguish promise/synchronous
211
+ // hooks from the legacy done-callback form. Preserve it exactly.
212
+ next[index] = callback.length >= 2
213
+ ? function supercovNodeTestHookDoneCallback(context, done) {
214
+ const restoringDone = (error) => {
215
+ if (error)
216
+ restoreUserError(error);
217
+ done?.(error);
218
+ };
219
+ return callback.call(this, context, restoringDone);
220
+ }
221
+ : function supercovNodeTestHookCallback(context) {
222
+ try {
223
+ const result = callback.call(this, context);
224
+ if (result && typeof result.then === "function")
225
+ return Promise.resolve(result).then(undefined, (error) => {
226
+ throw restoreUserError(error);
227
+ });
228
+ return result;
229
+ }
230
+ catch (error) {
231
+ throw restoreUserError(error);
232
+ }
233
+ };
234
+ return Reflect.apply(original, this, next);
235
+ };
236
+ }
123
237
  export const test = wrappedRegistration(native.test);
124
238
  export const it = wrappedRegistration(native.it);
125
239
  export const suite = native.suite;
126
240
  export const describe = native.describe;
127
- export const before = native.before;
128
- export const after = native.after;
129
- export const beforeEach = native.beforeEach;
130
- export const afterEach = native.afterEach;
241
+ export const before = restoringHook(native.before);
242
+ export const after = restoringHook(native.after);
243
+ export const beforeEach = restoringHook(native.beforeEach);
244
+ export const afterEach = restoringHook(native.afterEach);
131
245
  export const mock = native.mock;
132
246
  export const snapshot = native.snapshot;
133
247
  export const run = native.run;
@@ -82,7 +82,7 @@ if (process.env.SUPERCOV_DURABLE_EVIDENCE_EACH_TEST === "1") {
82
82
  const workerThreads = Module._load("node:worker_threads", undefined, false);
83
83
  const NativeWorker = workerThreads.Worker;
84
84
  const registerArgument = `--import=${new URL("./register.mjs", import.meta.url).href}`;
85
- const appendRegister = values => values.some(value => value === registerArgument || value.includes("/.supercov/register.mjs"))
85
+ const appendRegister = values => values.some(value => value === registerArgument || value.includes("/.supercov/node_modules/register.mjs"))
86
86
  ? values
87
87
  : [...values, registerArgument];
88
88
  workerThreads.Worker = class SupercovWorker extends NativeWorker {
@@ -4,7 +4,7 @@ const GENERATED_TARGET = "__SUPERCOV_PLAYWRIGHT_MODULE__";
4
4
  const TARGET = process.env.SUPERCOV_PLAYWRIGHT_MODULE ??
5
5
  (GENERATED_TARGET.startsWith("__") ? "@playwright/test" : GENERATED_TARGET);
6
6
  const REPLACEMENT = process.env.SUPERCOV_PLAYWRIGHT_WRAPPER ??
7
- "./.supercov/playwright.js";
7
+ "./.supercov/node_modules/playwright.js";
8
8
  const PROJECT_ROOT = process.env.SUPERCOV_PROJECT_ROOT;
9
9
  const ORIGINAL_CONFIG = process.env.SUPERCOV_ORIGINAL_PLAYWRIGHT_CONFIG;
10
10
  function belongsToProject(parentURL) {
@@ -29,7 +29,7 @@ export async function resolve(specifier, context, nextResolve) {
29
29
  // source-local copy keeps strict rootDir compilers happy; this fallback
30
30
  // resolves the emitted import to Supercov's generated runtime without
31
31
  // requiring the project's build to copy our helper directory.
32
- if (specifier.endsWith("/.supercov/runtime.js") &&
32
+ if (specifier.endsWith("/.supercov/node_modules/runtime.js") &&
33
33
  belongsToProject(context.parentURL)) {
34
34
  return {
35
35
  url: new URL("./runtime.js", import.meta.url).href,