Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions src/runtime/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -937,10 +937,8 @@ pub mod command {
let Some(mut first_arg_name) = iter.next() else {
return Tag::AutoCommand;
};
while !first_arg_name.is_empty()
&& first_arg_name[0] == b'-'
&& !(first_arg_name.len() > 1 && first_arg_name[1] == b'e')
{
// A lone `-` (stdin script) ends the search like `-e`: the rest of argv is the script's.
while first_arg_name.len() > 1 && first_arg_name[0] == b'-' && first_arg_name[1] != b'e' {
// `--interactive` stays on AutoCommand: Arguments.rs parses it and the no-target check
// routes to RunCommand::exec_node_repl. An early ReplCommand return here would bypass
// that and boot the legacy `bun repl` implementation instead.
Expand Down
38 changes: 37 additions & 1 deletion test/cli/run/run-eval.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { SyncSubprocess } from "bun";
import { describe, expect, test } from "bun:test";
import { rmSync, writeFileSync } from "fs";
import { bunEnv, bunExe, isWindows, tmpdirSync } from "harness";
import { bunEnv, bunExe, isWindows, tempDir, tmpdirSync } from "harness";
import { tmpdir } from "os";
import { join, sep } from "path";

Expand Down Expand Up @@ -276,6 +276,42 @@ describe("echo | bun run -", () => {
group(run);
});

// `-` is the stdin entry point, not a flag: whatever follows it belongs to the
// script (`node - help` runs stdin with argv ["-", "help"]). The subcommand
// lookup used to skip the `-` and dispatch on the next argument instead, so
// `bun - help` printed bun's help and `bun - add x` ran `bun add x`.
describe("bun - <args> runs stdin even when an arg is a subcommand name", () => {
const cases: { flags: string[]; args: string[] }[] = [
{ flags: [], args: ["help"] },
{ flags: [], args: ["add", "--help"] },
{ flags: [], args: ["test"] },
// Landed on `bun run`, which happened to read stdin but dropped "run" from argv.
{ flags: [], args: ["run", "x"] },
// The `-` is reached after stepping over a real flag.
{ flags: ["--silent"], args: ["upgrade", "--help"] },
];

for (const { flags, args } of cases) {
test.concurrent(["bun", ...flags, "-", ...args].join(" "), async () => {
// An empty cwd: if the args are dispatched as a subcommand instead, it
// must not find a project (or test files) to act on.
using dir = tempDir("bun-dash-stdin", {});
await using proc = Bun.spawn({
cmd: [bunExe(), ...flags, "-", ...args],
cwd: String(dir),
env: bunEnv,
stdin: Buffer.from("console.log(JSON.stringify(process.argv.slice(1)))"),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
expect(stdout).toBe(JSON.stringify(["-", ...args]) + "\n");
expect(exitCode).toBe(0);
});
}
});

test("process._eval (undefined for normal run)", async () => {
const cwd = tmpdirSync();
const file = join(cwd, "test.js");
Expand Down
Loading