Skip to content

Commit 8ec85d2

Browse files
fix(coverage): use dynamic import for ast-v8-to-istanbul to fix tsx (#159)
1 parent 09222c9 commit 8ec85d2

2 files changed

Lines changed: 16 additions & 7 deletions

File tree

src/core/coverageProcessor.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ test("takeAndProcessSnapshot: skips coverage files without user scripts", async
6060
{
6161
functionName: "",
6262
ranges: [{ startOffset: 0, endOffset: 10, count: 1 }],
63+
isBlockCoverage: false,
6364
},
6465
],
6566
},

src/core/coverageProcessor.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ export interface V8CoverageRange {
3838
}
3939

4040
export interface V8FunctionCoverage {
41-
functionName?: string;
41+
// V8 always emits these per Node's Profiler.FunctionCoverage contract; matching that
42+
// shape lets us pass V8FunctionCoverage[] to ast-v8-to-istanbul.convert without a cast.
43+
functionName: string;
4244
ranges: V8CoverageRange[];
43-
isBlockCoverage?: boolean;
45+
isBlockCoverage: boolean;
4446
}
4547

4648
export interface V8ScriptCoverage {
@@ -258,10 +260,13 @@ export async function processV8CoverageFile(
258260
includeAll: boolean = false,
259261
preParsedData?: V8CoverageData,
260262
): Promise<CoverageResult> {
261-
// Lazy-loaded: these are only needed when coverage is enabled, which is opt-in.
262-
// Using require() avoids loading them on every SDK startup (adds ~50ms + memory).
263-
// eslint-disable-next-line @typescript-eslint/no-var-requires
264-
const { convert } = require("ast-v8-to-istanbul");
263+
// Lazy-loaded: only needed when coverage is enabled (opt-in), saves ~50ms + memory at SDK startup.
264+
// Using dynamic import (not require) because ast-v8-to-istanbul is ESM-only and pulls in
265+
// estree-walker, whose package.json `exports` field has no `require` condition. Under tsx's
266+
// ESM loader, `require("ast-v8-to-istanbul")` resolves transitive imports with the require
267+
// condition and dies on estree-walker with ERR_PACKAGE_PATH_NOT_EXPORTED. Dynamic import
268+
// resolves with the import condition throughout and works under both plain Node and tsx.
269+
const { convert } = await import("ast-v8-to-istanbul");
265270
const data: V8CoverageData = preParsedData ?? JSON.parse(fs.readFileSync(v8FilePath, "utf-8"));
266271
const coverage: CoverageResult = {};
267272

@@ -328,7 +333,10 @@ export async function processV8CoverageFile(
328333
code: codeForConvert,
329334
ast,
330335
coverage: { functions: script.functions, url: script.url },
331-
...(sourceMap ? { sourceMap } : {}),
336+
// sourceMap is parsed JSON — loadSourceMap returns Record<string, unknown> rather
337+
// than committing to a specific source-map type from a transitive dep. Cast to the
338+
// shape convert expects.
339+
...(sourceMap ? { sourceMap: sourceMap as Parameters<typeof convert>[0]["sourceMap"] } : {}),
332340
...(cjsWrapperLength ? { wrapperLength: cjsWrapperLength } : {}),
333341
});
334342

0 commit comments

Comments
 (0)