Skip to content

fix: resolve bare package imports against the "." exports subpath#1276

Merged
sorccu merged 1 commit intomainfrom
claude/strange-zhukovsky
Apr 6, 2026
Merged

fix: resolve bare package imports against the "." exports subpath#1276
sorccu merged 1 commit intomainfrom
claude/strange-zhukovsky

Conversation

@thebiglabasky
Copy link
Copy Markdown
Contributor

Affected Components

  • CLI

Notes for the Reviewer

Follow-up to the investigation in #1274 — this is the incidental bug flagged there, split into its own PR for independent review.

The bug

resolveExportPath('') — the call made for bare package imports like import x from 'foo' — used to blindly prepend ./ to the lookup key, turning '' into './'. That never matched the . key stored in the resolved exports map, so the export lookup silently returned zero paths.

In practice the bug was masked for packages that also declared a main field, because resolveSourceFile falls back to mainPaths when the exports lookup returns nothing and exportPath === ''. Packages that only declared exports (no main) would not resolve their bare entry point and the parser would fail to walk their dependency graph.

The fix

Normalize both '' and '.' to . before handing off to the resolver, so the lookup matches the canonical . subpath key used by both the string-shorthand (exports: "./foo.js") and the object form (exports: { ".": "./foo.js" }). Subpath imports (either bare sub or ./sub) continue to work unchanged.

if (exportPath === '' || exportPath === '.') {
  exportPath = '.'
} else if (!exportPath.startsWith('./')) {
  exportPath = `./${exportPath}`
}

Tests

All tests written TDD-first (watched failing, then fix, then watched passing).

  • Plain string exports (exports: "./index.js") via bare import
  • Object exports with explicit . key via bare import
  • Single-level conditional exports under . via bare import (exercises both import and require condition paths)
  • Explicit . as the exportPath (some callers pass . directly instead of '')
  • Regression guard: non-bare subpath imports (./sub and bare sub) still resolve correctly alongside the . entry

Check-parser suite: 51 passed baseline → 56 passed after fix (+5 new, zero regressions). Same 3 pre-existing execa infrastructure failures as on main, unrelated.

Intentionally NOT in scope

The "sugar for main export" variant — where exports is an object with no keys starting with . and should be treated as conditions for the . subpath, e.g. exports: { import: "./esm.mjs", require: "./cjs.cjs" }. The current #resolveExports treats import/require as subpath names in that case. Happy to do a third PR if it's worth fixing; the workaround is to use the more explicit { ".": { ... } } form.

Test plan

  • npx vitest run src/services/check-parser/package-files/__tests__/package-json-file.spec.ts — all 9 tests green
  • Run against a real project that uses a package with only exports (no main) and confirm the bare import resolves

🤖 Generated with Claude Code

@thebiglabasky
Copy link
Copy Markdown
Contributor Author

That's arguably a lot of tests for such a small fix, but while at it...

@sorccu
Copy link
Copy Markdown
Member

sorccu commented Apr 6, 2026

Can you rebase?

`resolveExportPath('')` — the call made for bare package imports like
`import x from 'foo'` — used to blindly prepend './' to the lookup key,
turning '' into './'. That never matched the '.' key stored in the
resolved exports map, so the export lookup silently returned zero paths.

In practice the bug was masked for packages that also declared a `main`
field, because `resolveSourceFile` falls back to `mainPaths` when the
exports lookup returns nothing and `exportPath === ''`. Packages that
only declared `exports` (no `main`) would not resolve their bare entry
point and the parser would fail to walk their dependency graph.

Normalize both '' and '.' to '.' so the lookup matches the canonical
'.' subpath key used by both the string-shorthand `exports: "./foo.js"`
form and the object form `exports: { ".": "./foo.js" }`. Subpath imports
(either bare 'sub' or './sub') continue to work unchanged.

Tests added:
- Plain string exports resolved via bare import
- Object exports with '.' key resolved via bare import
- Single-level conditional exports under '.' resolved via bare import
- Explicit '.' exportPath accepted
- Regression guard for non-bare subpath imports

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@thebiglabasky thebiglabasky force-pushed the claude/strange-zhukovsky branch from bce7993 to 3333725 Compare April 6, 2026 16:31
@thebiglabasky
Copy link
Copy Markdown
Contributor Author

@sorccu Done: trimmed a dupe test along the way

@sorccu sorccu merged commit f9d203f into main Apr 6, 2026
4 checks passed
@sorccu sorccu deleted the claude/strange-zhukovsky branch April 6, 2026 17:02
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