157 lines
5.2 KiB
TypeScript
157 lines
5.2 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { bunEnv, bunExe, isWindows, tempDir } from "harness";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
|
|
// https://github.com/oven-sh/bun/issues/13316
|
|
// bunx cowsay "" panicked on Windows due to improper handling of empty string arguments
|
|
// The issue was in the BunXFastPath.tryLaunch function which didn't properly quote
|
|
// empty string arguments for the Windows command line.
|
|
describe.if(isWindows)("#13316 - bunx with empty string arguments", () => {
|
|
test("bunx does not panic with empty string argument", async () => {
|
|
// Create a minimal package that echoes its arguments
|
|
using dir = tempDir("issue-13316", {
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"echo-args-test": "file:./echo-args-test",
|
|
},
|
|
}),
|
|
"echo-args-test/package.json": JSON.stringify({
|
|
name: "echo-args-test",
|
|
version: "1.0.0",
|
|
bin: {
|
|
"echo-args-test": "./index.js",
|
|
},
|
|
}),
|
|
"echo-args-test/index.js": `#!/usr/bin/env node
|
|
console.log(JSON.stringify(process.argv.slice(2)));
|
|
`,
|
|
});
|
|
|
|
// Install to create the .bunx shim in node_modules/.bin
|
|
await using installProc = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stderr: "pipe",
|
|
});
|
|
await installProc.exited;
|
|
|
|
// Verify the .bunx file was created (this is what triggers the fast path)
|
|
const bunxPath = path.join(String(dir), "node_modules", ".bin", "echo-args-test.bunx");
|
|
expect(fs.existsSync(bunxPath)).toBe(true);
|
|
|
|
// Run with an empty string argument - this was triggering the panic
|
|
// We use `bun run` which goes through the same BunXFastPath when .bunx exists
|
|
await using proc = Bun.spawn({
|
|
cmd: [bunExe(), "run", "echo-args-test", ""],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
|
|
|
// The main assertion is that the process doesn't panic (exit code 3)
|
|
// If the bug is present, this would crash with "reached unreachable code"
|
|
expect(exitCode).not.toBe(3); // panic exit code
|
|
expect(exitCode).toBe(0);
|
|
|
|
// The empty string argument should be passed correctly
|
|
expect(JSON.parse(stdout.trim())).toEqual([""]);
|
|
});
|
|
|
|
test("bunx handles multiple arguments including empty strings", async () => {
|
|
using dir = tempDir("issue-13316-multi", {
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"echo-args-test": "file:./echo-args-test",
|
|
},
|
|
}),
|
|
"echo-args-test/package.json": JSON.stringify({
|
|
name: "echo-args-test",
|
|
version: "1.0.0",
|
|
bin: {
|
|
"echo-args-test": "./index.js",
|
|
},
|
|
}),
|
|
"echo-args-test/index.js": `#!/usr/bin/env node
|
|
console.log(JSON.stringify(process.argv.slice(2)));
|
|
`,
|
|
});
|
|
|
|
await using installProc = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stderr: "pipe",
|
|
});
|
|
await installProc.exited;
|
|
|
|
await using proc = Bun.spawn({
|
|
cmd: [bunExe(), "run", "echo-args-test", "hello", "", "world"],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
|
|
|
expect(exitCode).not.toBe(3); // panic exit code
|
|
expect(exitCode).toBe(0);
|
|
expect(JSON.parse(stdout.trim())).toEqual(["hello", "", "world"]);
|
|
});
|
|
|
|
// Related to #18275 - bunx concurrently "command with spaces"
|
|
// Arguments containing spaces must be preserved as single arguments
|
|
test("bunx preserves arguments with spaces", async () => {
|
|
using dir = tempDir("issue-13316-spaces", {
|
|
"package.json": JSON.stringify({
|
|
name: "test-project",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"echo-args-test": "file:./echo-args-test",
|
|
},
|
|
}),
|
|
"echo-args-test/package.json": JSON.stringify({
|
|
name: "echo-args-test",
|
|
version: "1.0.0",
|
|
bin: {
|
|
"echo-args-test": "./index.js",
|
|
},
|
|
}),
|
|
"echo-args-test/index.js": `#!/usr/bin/env node
|
|
console.log(JSON.stringify(process.argv.slice(2)));
|
|
`,
|
|
});
|
|
|
|
await using installProc = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stderr: "pipe",
|
|
});
|
|
await installProc.exited;
|
|
|
|
// This simulates: bunx concurrently "bun --version"
|
|
// The shell strips the outer quotes, so bunx receives ["concurrently", "bun --version"]
|
|
// This must be preserved as a single argument with spaces
|
|
await using proc = Bun.spawn({
|
|
cmd: [bunExe(), "run", "echo-args-test", "bun --version", "echo hello world"],
|
|
env: bunEnv,
|
|
cwd: String(dir),
|
|
stderr: "pipe",
|
|
});
|
|
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
|
|
|
expect(exitCode).toBe(0);
|
|
// Each argument with spaces should be preserved as a single argument
|
|
expect(JSON.parse(stdout.trim())).toEqual(["bun --version", "echo hello world"]);
|
|
});
|
|
});
|