Skip to content

Commit bb2f8cb

Browse files
NathanWalkerclaude
andcommitted
fix(node-api): decode percent-encoded path before process.dlopen
`URL.pathname` is percent-encoded, but `process.dlopen` expects a filesystem path. Any space in the package's path therefore reaches dlopen as "%20" and the addon fails to load: TypeError: dlopen(/Applications/My%20App.app/.../NativeScript, 0x0001): tried: '.../My%20App.app/...' (no such file) This breaks every packaged app whose path contains a space — for example an .app installed to /Applications with a display name like "My App.app". It goes unnoticed in development because the package normally resolves from a node_modules path with no spaces, where decoding is a no-op; only a distributed bundle exercises the failing path. Wrap the resolved pathname in decodeURIComponent() at all three dlopen sites: - packages/macos-node-api/index.mjs - packages/macos-node-api/index.cjs - packages/ios-node-api/index.js Verified on macOS by copying packages/macos-node-api together with its built NativeScript.framework into a directory whose name contains a space, then importing it under Deno: - before: dlopen fails with the %20 path shown above - after: the addon loads and init() runs Note: paths containing "#" or "?" are still mishandled, because the base URL is built by string concatenation and those characters are parsed as fragment/query delimiters. Spaces are by far the common case; a complete fix would resolve the addon path without going through URL at all. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent f116e29 commit bb2f8cb

3 files changed

Lines changed: 20 additions & 6 deletions

File tree

packages/ios-node-api/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@ if (typeof interop === "undefined") {
1515

1616
const module = { exports: {} };
1717

18+
// `URL.pathname` is percent-encoded, but `process.dlopen` takes a filesystem path. Without
19+
// decoding, a space in the path arrives as "%20" and the load fails with "no such file" —
20+
// which happens for any app bundle whose path contains a space, e.g.
21+
// /Applications/My App.app/… → /Applications/My%20App.app/…
1822
// deno-lint-ignore no-process-globals
19-
process.dlopen(module, new URL(path, metaURL).pathname);
23+
process.dlopen(module, decodeURIComponent(new URL(path, metaURL).pathname));
2024

2125
module.exports.init(
2226
// deno-lint-ignore no-process-globals

packages/macos-node-api/index.cjs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,18 @@ if (!isNativeScriptRuntime) {
1616
// ===
1717

1818
const module = { exports: {} };
19+
// `URL.pathname` is percent-encoded, but `process.dlopen` takes a filesystem path. Without
20+
// decoding, a space in the path arrives as "%20" and the load fails with "no such file" —
21+
// which happens for any app bundle whose path contains a space, e.g.
22+
// /Applications/My App.app/… → /Applications/My%20App.app/…
1923
process.dlopen(
2024
module,
21-
new URL(
22-
"./build/RelWithDebInfo/NativeScript.apple.node/macos-arm64/NativeScript.framework/Versions/A/NativeScript",
23-
`file://${__filename}`,
24-
).pathname,
25+
decodeURIComponent(
26+
new URL(
27+
"./build/RelWithDebInfo/NativeScript.apple.node/macos-arm64/NativeScript.framework/Versions/A/NativeScript",
28+
`file://${__filename}`,
29+
).pathname,
30+
),
2531
);
2632
module.exports.init(process.env.METADATA_PATH);
2733
} else if (typeof require !== "undefined") {

packages/macos-node-api/index.mjs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@ if (!isNativeScriptRuntime) {
2424

2525
const module = { exports: {} };
2626

27+
// `URL.pathname` is percent-encoded, but `process.dlopen` takes a filesystem path. Without
28+
// decoding, a space in the path arrives as "%20" and the load fails with "no such file" —
29+
// which happens for any app bundle whose path contains a space, e.g.
30+
// /Applications/My App.app/… → /Applications/My%20App.app/…
2731
// deno-lint-ignore no-process-globals
28-
process.dlopen(module, new URL(path, metaURL).pathname);
32+
process.dlopen(module, decodeURIComponent(new URL(path, metaURL).pathname));
2933

3034
module.exports.init(
3135
// deno-lint-ignore no-process-globals

0 commit comments

Comments
 (0)