Skip to content

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

Open
exabird wants to merge 1 commit intogarrytan:mainfrom
exabird:fix/externalize-ngrok-in-node-server-build
Open

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

Conversation

@exabird
Copy link
Copy Markdown

@exabird exabird commented Apr 9, 2026

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

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