Environment
I've seen this bug in both node and deno
node v25.8.1
deno 2.8.2 (stable, release, aarch64-apple-darwin)
v8 14.9.207.2-rusty
typescript 6.0.3
Reproduction
See description...
Describe the bug
Summary
resolvePath currently preserves trailing slashes when a specifier resolves to a node: builtin.
That is a problem because normal package imports may legally use a trailing slash, but node: builtin specifiers must not.
For example, Node accepts:
await import("punycode/")
but rejects:
await import("node:punycode/")
with:
Error [ERR_UNKNOWN_BUILTIN_MODULE]: No such built-in module: node:punycode/
Real-world case
This showed up with punycode, which exists both as an npm package and as a Node builtin.
A dependency imported punycode/, and unenv converted that to the builtin form. The trailing slash was preserved, so the final specifier became node:punycode/, which is invalid in Node.
Reproduction
const { env } = defineEnv({
nodeCompat: false,
resolve: true,
overrides: {
alias: {
punycode: "node:punycode",
foo: "punycode/",
},
},
});
Current result:
Expected result:
Expected behavior
When resolvePath resolves a specifier to a builtin module, it should normalize a trailing slash away before returning the node: form.
This normalization should only apply to builtins. Regular non-builtin imports like my-module/ should remain unchanged.
Additional context
No response
Logs
Environment
I've seen this bug in both node and deno
node v25.8.1
deno 2.8.2 (stable, release, aarch64-apple-darwin)
v8 14.9.207.2-rusty
typescript 6.0.3
Reproduction
See description...
Describe the bug
Summary
resolvePathcurrently preserves trailing slashes when a specifier resolves to anode:builtin.That is a problem because normal package imports may legally use a trailing slash, but
node:builtin specifiers must not.For example, Node accepts:
but rejects:
with:
Real-world case
This showed up with punycode, which exists both as an npm package and as a Node builtin.
A dependency imported punycode/, and unenv converted that to the builtin form. The trailing slash was preserved, so the final specifier became node:punycode/, which is invalid in Node.
Reproduction
Current result:
"node:punycode/"Expected result:
"node:punycode"Expected behavior
When resolvePath resolves a specifier to a builtin module, it should normalize a trailing slash away before returning the node: form.
This normalization should only apply to builtins. Regular non-builtin imports like my-module/ should remain unchanged.
Additional context
No response
Logs