packageImportsResolve misses wildcard keys with a trailer (#*.js, #lib/*.js) because the pattern base is computed with key.slice(0, -1) instead of key.slice(0, patternIndex).
https://github.com/unjs/exsolve/blob/main/src/internal/resolve.ts#L788
if (patternIndex !== -1 && name.startsWith(key.slice(0, -1))) {
For key #*.js this tests name.startsWith("#*."), which never matches, so the key is skipped and resolution throws ERR_PACKAGE_IMPORT_NOT_DEFINED. Keys that end in * (#internal/*) work by accident since slice(0, -1) and slice(0, patternIndex) coincide there.
The exports counterpart a few hundred lines up is already correct — the asymmetry is the whole bug:
https://github.com/unjs/exsolve/blob/main/src/internal/resolve.ts#L673
packageSubpath.startsWith(key.slice(0, patternIndex))
Node uses StringPrototypeSlice(key, 0, patternIndex) in both places.
Reproduction
import { resolveModulePath } from "exsolve";
// from node_modules/pkg/index.mjs
resolveModulePath("#internal/marker.js", { from: parent, conditions: ["node", "import"] });
// exsolve 1.1.0: Cannot resolve module "#internal/marker.js"
// node: resolves to node_modules/pkg/runtime/internal/marker.js
Node resolves this fine (createRequire(parent).resolve("#internal/marker.js") returns the expected path), so exsolve diverges from the reference implementation here.
Fix
-if (patternIndex !== -1 && name.startsWith(key.slice(0, -1))) {
+if (patternIndex !== -1 && name.startsWith(key.slice(0, patternIndex))) {
I applied this one-character change to the installed exsolve@1.1.0 dist locally and verified all three cases now resolve identically to Node:
| specifier |
imports map |
before |
after |
#internal/marker.js |
"#*.js": "./runtime/*.js" |
throws |
runtime/internal/marker.js |
#ok/x.js |
"#ok/*.js": "./local/*.js" |
throws |
local/x.js |
#lib/y.js |
"#lib/*.js": "dep/*.js" (external target) |
throws |
node_modules/dep/y.js |
Trailing-star keys and the exports path were unaffected.
Worth a regression test with a trailer-pattern imports key alongside the existing exports fixtures.
Context
Found while reviewing unjs/nf3#66 — @vercel/nft has the same class of bug in its own resolver, and exsolve was the natural patch-free fallback for nf3 until it turned out to share the defect.
packageImportsResolvemisses wildcard keys with a trailer (#*.js,#lib/*.js) because the pattern base is computed withkey.slice(0, -1)instead ofkey.slice(0, patternIndex).https://github.com/unjs/exsolve/blob/main/src/internal/resolve.ts#L788
For key
#*.jsthis testsname.startsWith("#*."), which never matches, so the key is skipped and resolution throwsERR_PACKAGE_IMPORT_NOT_DEFINED. Keys that end in*(#internal/*) work by accident sinceslice(0, -1)andslice(0, patternIndex)coincide there.The
exportscounterpart a few hundred lines up is already correct — the asymmetry is the whole bug:https://github.com/unjs/exsolve/blob/main/src/internal/resolve.ts#L673
Node uses
StringPrototypeSlice(key, 0, patternIndex)in both places.Reproduction
Node resolves this fine (
createRequire(parent).resolve("#internal/marker.js")returns the expected path), so exsolve diverges from the reference implementation here.Fix
I applied this one-character change to the installed
exsolve@1.1.0dist locally and verified all three cases now resolve identically to Node:#internal/marker.js"#*.js": "./runtime/*.js"runtime/internal/marker.js#ok/x.js"#ok/*.js": "./local/*.js"local/x.js#lib/y.js"#lib/*.js": "dep/*.js"(external target)node_modules/dep/y.jsTrailing-star keys and the
exportspath were unaffected.Worth a regression test with a trailer-pattern
importskey alongside the existing exports fixtures.Context
Found while reviewing unjs/nf3#66 —
@vercel/nfthas the same class of bug in its own resolver, and exsolve was the natural patch-free fallback for nf3 until it turned out to share the defect.