Skip to content

fix(browse): externalize @ngrok/ngrok in node server build#947

Closed
exabird wants to merge 1 commit into
garrytan:mainfrom
exabird:fix/externalize-ngrok-in-node-server-build
Closed

fix(browse): externalize @ngrok/ngrok in node server build#947
exabird wants to merge 1 commit into
garrytan:mainfrom
exabird:fix/externalize-ngrok-in-node-server-build

Conversation

@exabird

@exabird exabird commented Apr 9, 2026

Copy link
Copy Markdown

Problem

Running ./setup (or browse/scripts/build-node-server.sh directly) fails with:

Building Node-compatible server bundle...
error: cannot write multiple output files without an output directory

Reproduced on: bun 1.3.11, macOS arm64, gstack 0.16.2.0.

Root cause

@ngrok/ngrok was added (I believe in 0.16.x for /pair-agent tunneling). That package ships two native .node binaries as assets:

  • ngrok.darwin-universal-*.node (~19 MB)
  • ngrok.darwin-arm64-*.node (~9 MB)

When bun bundles server.ts, it statically analyzes @ngrok/ngrok (even though the actual import is dynamic via await import('@ngrok/ngrok')) and tries to emit those .node files as additional outputs next to server-node.mjs. Since --outfile can only produce a single file, bun aborts with the "cannot write multiple output files without an output directory" error.

Fix

Add --external "@ngrok/ngrok" to the bun build command in browse/scripts/build-node-server.sh.

This is safe because @ngrok/ngrok is already loaded dynamically at runtime in server.ts:

// server.ts:1568
const ngrok = await import('@ngrok/ngrok');
tunnelListener = await ngrok.forward(forwardOpts);

At runtime, Node resolves @ngrok/ngrok from node_modules/ just like the other runtime-loaded externals (playwright, playwright-core, diff).

Verification

$ bash browse/scripts/build-node-server.sh
Building Node-compatible server bundle...
Bundled 23 modules in 5ms

  server-node.mjs  0.31 MB  (entry point)

Node server bundle ready: /.../browse/dist/server-node.mjs

Both server-node.mjs (307 KB) and bun-polyfill.cjs are generated correctly.

Notes

  • Tested on macOS arm64. I don't have a Windows box to smoke-test the actual Windows Node runtime, but since ngrok is loaded via dynamic import() on an opt-in code path (/tunnel/start), externalizing it should not break any eager initialization.
  • Only 2 lines changed (one line + continuation backslash).

The node-compatible server bundle build fails with:

    error: cannot write multiple output files without an output directory

Root cause: `@ngrok/ngrok` (added for /pair-agent tunneling) ships two
native .node binaries (darwin-universal and darwin-arm64, ~28 MB total)
that bun tries to emit as assets alongside the JS bundle. Since
`--outfile` can only produce a single file, bun refuses.

`@ngrok/ngrok` is already loaded dynamically at runtime via
`await import('@ngrok/ngrok')` in server.ts, so externalizing it is
safe — the resolver finds it from node_modules at runtime.

Tested on bun 1.3.11 / darwin-arm64: server-node.mjs now builds
successfully (307 KB, 23 modules).
@lijishuai

Copy link
Copy Markdown

niubility

@garrytan

Copy link
Copy Markdown
Owner

Closing — duplicate of several PRs fixing the same @ngrok/ngrok Windows build issue. We'll pick the best approach and merge separately. Thank you for the contribution!

@garrytan garrytan closed this Apr 16, 2026
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.

3 participants