Skip to content

fix(coverage): use dynamic import for ast-v8-to-istanbul to fix tsx#159

Merged
sohil-kshirsagar merged 2 commits into
mainfrom
sohil/fix/coverage-tsx-dynamic-import
May 7, 2026
Merged

fix(coverage): use dynamic import for ast-v8-to-istanbul to fix tsx#159
sohil-kshirsagar merged 2 commits into
mainfrom
sohil/fix/coverage-tsx-dynamic-import

Conversation

@sohil-kshirsagar
Copy link
Copy Markdown
Contributor

@sohil-kshirsagar sohil-kshirsagar commented May 7, 2026

Switch require("ast-v8-to-istanbul") to await import("ast-v8-to-istanbul") in coverageProcessor.ts so coverage works for SDK consumers running their service via tsx.

The bug

ast-v8-to-istanbul is ESM-only and pulls in estree-walker, whose package.json exports field declares only an import condition (no require). Under plain Node, require() of an ESM module loaded via require-esm resolves transitive imports with the import condition and the chain works. Under tsx's ESM loader, the same require resolves transitive imports with the require condition, falls off estree-walker's exports map, and throws ERR_PACKAGE_PATH_NOT_EXPORTED — every coverage snapshot fails and no per-test coverage data is sent back to the CLI.

The error caught from CI logs:

[TuskDrift] [ProtobufCommunicator] Coverage snapshot error: Error [ERR_PACKAGE_PATH_NOT_EXPORTED]:
No "exports" main defined in .../node_modules/.pnpm/ast-v8-to-istanbul@1.0.0/node_modules/estree-walker/package.json
    at exportsNotFound (node:internal/modules/esm/resolve:322:10)
    at packageExportsResolve (node:internal/modules/esm/resolve:613:13)
    at resolveExports (node:internal/modules/cjs/loader:638:36)
    at Module._findPath (node:internal/modules/cjs/loader:711:31)
    at nextResolveSimple (.../tsx@4.21.0/dist/register-D46fvsV_.cjs:4:1004)
    ...
  code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'

The tsx@4.21.0/.../register-D46fvsV_.cjs frame is what flips resolution into the require condition.

Why dynamic import vs other approaches

  • createRequire(import.meta.url)("ast-v8-to-istanbul") has the same failure mode — it's still going through Node's require path.
  • Pinning estree-walker to a version with a require condition isn't possible — it doesn't have one in any version.
  • Inlining ast-v8-to-istanbul is heavy and removes a maintained dep.

Dynamic import() is the right primitive here: it resolves with the import condition unconditionally, regardless of which loader is active. The function is already async so there's no structural change.

ast-v8-to-istanbul is ESM-only and pulls in estree-walker, whose
package.json `exports` field has no `require` condition. Under tsx's
ESM loader, `require("ast-v8-to-istanbul")` resolves transitive imports
with the require condition and dies on estree-walker with
ERR_PACKAGE_PATH_NOT_EXPORTED — every coverage snapshot fails and no
data is uploaded.

Switching to dynamic `await import()` resolves with the import
condition throughout and works under both plain Node and tsx.
…cast

Tighten V8FunctionCoverage to match Node's Profiler.FunctionCoverage
contract (functionName and isBlockCoverage are always emitted by V8),
which lets script.functions pass through to ast-v8-to-istanbul.convert
without a cast. Update the test fixture accordingly. Source map still
needs a cast since loadSourceMap returns Record<string, unknown>.
@sohil-kshirsagar sohil-kshirsagar marked this pull request as ready for review May 7, 2026 00:48
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 2 files

@tusk-dev
Copy link
Copy Markdown
Contributor

tusk-dev Bot commented May 7, 2026

Code Review

Tusk Review: No issues found


Unit Tests

Generated 7 tests - 7 passed

Commit tests View tests

Test Summary

  • processV8CoverageFile - 5 ✓
  • takeAndProcessSnapshot - 2 ✓

Results

Tusk's tests all pass. The critical test here is the dynamic import validation — it confirms that switching from require() to await import() for ast-v8-to-istanbul actually resolves the ERR_PACKAGE_PATH_NOT_EXPORTED error that was breaking coverage under tsx. The rest of the suite validates that the core coverage processing logic (processV8CoverageFile and takeAndProcessSnapshot) still works correctly: filtering by sourceRoot, handling the includeAll flag, merging line counts across multiple files, and cleaning up temp files. This is a surgical fix to an ESM module resolution issue, and Tusk's tests confirm the fix works without breaking existing coverage behavior.

Avg +35% line coverage gain across 1 file
Source file Line Branch
src/core/coverageProcessor.ts 78% (+35%) 72%

Coverage is calculated by running tests directly associated with each source file, learn more here.

Last run on fa0fad8. Comment @use-tusk generate to create a test suite on the latest commit. Configure here.

View check history

Commit Unit Tests Created (UTC)
fa0fad8 7 ✓, 0 ✗ · Tests May 7, 2026 12:48AM

Was Tusk helpful? Give feedback by reacting with 👍 or 👎

@sohil-kshirsagar sohil-kshirsagar merged commit 8ec85d2 into main May 7, 2026
20 checks passed
@sohil-kshirsagar sohil-kshirsagar deleted the sohil/fix/coverage-tsx-dynamic-import branch May 7, 2026 01:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants