Skip to content

Commit 56c8985

Browse files
cramforceclaude
andcommitted
Fix Dynamic require of "tty" crash under ESM Node (#211)
The esbuild dynamic-require shim in the ESM Node bundles had no real `require` to delegate to at chunk-init, so any runtime `require("tty")` / `require("os")` from the `file-type` → `debug` → `supports-color` chain threw "Dynamic require of \"tty\" is not supported". This made the `file` command crash for every consumer importing just-bash as ESM and for the `just-bash` CLI binary. Provide `createRequire(import.meta.url)` via the build banner for `build:lib`, `build:cli`, and `build:shell`. CJS and browser bundles are unchanged. Add regression tests covering both the bundled CLI binary and the ESM library bundle running the `file` command. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 5fdc8f6 commit 56c8985

3 files changed

Lines changed: 47 additions & 3 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"just-bash": patch
3+
---
4+
5+
Fix `Dynamic require of "tty" is not supported` crash when invoking commands that transitively load `debug` / `supports-color` (notably `file`) under ESM Node consumers and via the `just-bash` CLI binary.
6+
7+
The esbuild dynamic-require shim emitted into the ESM Node bundles had no `require` to delegate to at chunk-init under ESM, so any runtime `require("tty")` / `require("os")` from `file-type``debug` chain threw. Build banners now provide `createRequire(import.meta.url)` for `build:lib`, `build:cli`, and `build:shell`. CJS and browser bundles are unchanged.
8+
9+
Fixes #211.

packages/just-bash/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,11 @@
6060
"build": "rm -rf dist && tsc && pnpm build:lib && pnpm build:lib:cjs && pnpm build:browser && pnpm build:cli && pnpm build:shell && pnpm build:worker && pnpm build:clean && cp dist/index.d.ts dist/index.d.cts && sed '1,/^-->/d' AGENTS.npm.md > dist/AGENTS.md",
6161
"build:clean": "find dist -name '*.test.js' -delete && find dist -name '*.test.d.ts' -delete",
6262
"build:worker": "esbuild src/commands/python3/worker.ts --bundle --platform=node --format=esm --outfile=src/commands/python3/worker.js --external:../../../vendor/cpython-emscripten/* && cp src/commands/python3/worker.js dist/commands/python3/worker.js && mkdir -p dist/bin/chunks && cp src/commands/python3/worker.js dist/bin/chunks/worker.js && mkdir -p dist/bundle/chunks && cp src/commands/python3/worker.js dist/bundle/chunks/worker.js && esbuild src/commands/js-exec/js-exec-worker.ts --bundle --platform=node --format=esm --outfile=src/commands/js-exec/js-exec-worker.js --external:quickjs-emscripten && cp src/commands/js-exec/js-exec-worker.js dist/commands/js-exec/js-exec-worker.js && cp src/commands/js-exec/js-exec-worker.js dist/bin/chunks/js-exec-worker.js && cp src/commands/js-exec/js-exec-worker.js dist/bundle/chunks/js-exec-worker.js && esbuild src/commands/sqlite3/worker.ts --bundle --platform=node --format=esm --outfile=src/commands/sqlite3/worker.js --external:sql.js && mkdir -p dist/commands/sqlite3 && cp src/commands/sqlite3/worker.js dist/commands/sqlite3/worker.js && cp src/commands/sqlite3/worker.js dist/bin/chunks/sqlite3-worker.js && cp src/commands/sqlite3/worker.js dist/bundle/chunks/sqlite3-worker.js",
63-
"build:lib": "esbuild dist/index.js --bundle --splitting --platform=node --format=esm --minify --outdir=dist/bundle --chunk-names=chunks/[name]-[hash] --external:diff --external:minimatch --external:sprintf-js --external:turndown --external:sql.js --external:quickjs-emscripten --external:@mongodb-js/zstd --external:node-liblzma --external:seek-bzip",
63+
"build:lib": "esbuild dist/index.js --bundle --splitting --platform=node --format=esm --minify --outdir=dist/bundle --chunk-names=chunks/[name]-[hash] --banner:js='import{createRequire} from\"node:module\";const require=createRequire(import.meta.url);' --external:diff --external:minimatch --external:sprintf-js --external:turndown --external:sql.js --external:quickjs-emscripten --external:@mongodb-js/zstd --external:node-liblzma --external:seek-bzip",
6464
"build:lib:cjs": "esbuild dist/index.js --bundle --platform=node --format=cjs --minify --outfile=dist/bundle/index.cjs --external:diff --external:minimatch --external:sprintf-js --external:turndown --external:sql.js --external:quickjs-emscripten --external:@mongodb-js/zstd --external:node-liblzma --external:seek-bzip",
6565
"build:browser": "esbuild dist/browser.js --bundle --platform=browser --format=esm --minify --outfile=dist/bundle/browser.js --external:diff --external:minimatch --external:sprintf-js --external:turndown --external:node:zlib --external:@mongodb-js/zstd --external:node-liblzma --external:seek-bzip --define:__BROWSER__=true --alias:node:dns=./src/shims/browser-unsupported.js",
66-
"build:cli": "esbuild dist/cli/just-bash.js --bundle --splitting --platform=node --format=esm --minify --outdir=dist/bin --entry-names=[name] --chunk-names=chunks/[name]-[hash] --banner:js='#!/usr/bin/env node' --external:sql.js --external:quickjs-emscripten --external:@mongodb-js/zstd --external:node-liblzma --external:seek-bzip",
67-
"build:shell": "esbuild dist/cli/shell.js --bundle --splitting --platform=node --format=esm --minify --outdir=dist/bin/shell --entry-names=[name] --chunk-names=chunks/[name]-[hash] --banner:js='#!/usr/bin/env node' --external:sql.js --external:quickjs-emscripten --external:@mongodb-js/zstd --external:node-liblzma --external:seek-bzip",
66+
"build:cli": "esbuild dist/cli/just-bash.js --bundle --splitting --platform=node --format=esm --minify --outdir=dist/bin --entry-names=[name] --chunk-names=chunks/[name]-[hash] --banner:js='#!/usr/bin/env node\nimport{createRequire} from\"node:module\";const require=createRequire(import.meta.url);' --external:sql.js --external:quickjs-emscripten --external:@mongodb-js/zstd --external:node-liblzma --external:seek-bzip",
67+
"build:shell": "esbuild dist/cli/shell.js --bundle --splitting --platform=node --format=esm --minify --outdir=dist/bin/shell --entry-names=[name] --chunk-names=chunks/[name]-[hash] --banner:js='#!/usr/bin/env node\nimport{createRequire} from\"node:module\";const require=createRequire(import.meta.url);' --external:sql.js --external:quickjs-emscripten --external:@mongodb-js/zstd --external:node-liblzma --external:seek-bzip",
6868
"prepublishOnly": "pnpm test:dist",
6969
"validate": "pnpm lint && pnpm knip && pnpm typecheck && pnpm build && pnpm check:worker-sync && pnpm test:run && pnpm test:wasm && pnpm test:dist",
7070
"typecheck": "tsc --noEmit",

packages/just-bash/src/cli/just-bash.bundle.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,21 @@ describe("just-bash bundled binary", () => {
9696
expect(result.exitCode).toBe(1);
9797
});
9898

99+
// Regression test for https://github.com/vercel-labs/just-bash/issues/211:
100+
// file-type → debug → supports-color does runtime require("tty")/require("os"),
101+
// which the esbuild dynamic-require shim throws for under ESM Node unless the
102+
// build banner provides createRequire.
103+
it("should run file (regression: dynamic require under ESM Node)", async () => {
104+
const result = await runBin([
105+
"-c",
106+
"echo content > /tmp/x.txt && file /tmp/x.txt",
107+
"--allow-write",
108+
]);
109+
expect(result.stderr).toBe("");
110+
expect(result.stdout).toBe("/tmp/x.txt: ASCII text\n");
111+
expect(result.exitCode).toBe(0);
112+
});
113+
99114
it("should lazy-load commands (sqlite3 with external sql.js)", async () => {
100115
const result = await runBin([
101116
"-c",
@@ -186,3 +201,23 @@ describe("just-bash CJS bundle", () => {
186201
expect(result.exitCode).toBe(0);
187202
});
188203
});
204+
205+
// Regression test for https://github.com/vercel-labs/just-bash/issues/211.
206+
// The ESM Node bundle (what consumers import via `import { Bash } from "just-bash"`)
207+
// has its own dynamic-require shim — file-type → debug → supports-color does
208+
// runtime require("tty")/require("os") that the shim throws for unless the
209+
// build banner provides createRequire.
210+
describe("just-bash ESM bundle", () => {
211+
it("should be importable and run file command", async () => {
212+
const esmBundlePath = resolve(__dirname, "../../dist/bundle/index.js");
213+
const mod = await import(esmBundlePath);
214+
expect(mod.Bash).toBeDefined();
215+
const fs = new mod.InMemoryFs();
216+
await fs.writeFile("/x.txt", "hello\n");
217+
const bash = new mod.Bash({ fs, cwd: "/" });
218+
const result = await bash.exec("file /x.txt");
219+
expect(result.stderr).toBe("");
220+
expect(result.stdout).toBe("/x.txt: ASCII text\n");
221+
expect(result.exitCode).toBe(0);
222+
});
223+
});

0 commit comments

Comments
 (0)