From 7ed9946256f4a9fd13a1a54c2cbc708439ea2ab2 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Sat, 15 Aug 2026 21:25:19 +0000 Subject: [PATCH 1/2] shell(rm): accept --force as the long form of -f --- src/runtime/shell/builtin/rm.rs | 5 ++++ test/js/bun/shell/commands/rm.test.ts | 40 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/src/runtime/shell/builtin/rm.rs b/src/runtime/shell/builtin/rm.rs index 8c4cde84e18d..917a1e2a6469 100644 --- a/src/runtime/shell/builtin/rm.rs +++ b/src/runtime/shell/builtin/rm.rs @@ -494,6 +494,11 @@ impl Rm { if flag.len() > 2 && flag[1] == b'-' { return match flag { b"--preserve-root" | b"--no-preserve-root" => RmParseFlag::ContinueParsing, + b"--force" => { + opts.force = true; + opts.prompt_behaviour = PromptBehaviour::Never; + RmParseFlag::ContinueParsing + } b"--recursive" => { opts.recursive = true; RmParseFlag::ContinueParsing diff --git a/test/js/bun/shell/commands/rm.test.ts b/test/js/bun/shell/commands/rm.test.ts index d442eb197773..9111fd9e84be 100644 --- a/test/js/bun/shell/commands/rm.test.ts +++ b/test/js/bun/shell/commands/rm.test.ts @@ -53,6 +53,46 @@ describe.concurrent("bunshell rm", () => { } }); + test("--force is the long form of -f", async () => { + await using tempdir = tempDir("rmlongforce", { + "existent.txt": "", + "after-interactive.txt": "", + "dir/sub/file.txt": "", + "mixed/file.txt": "", + "kept.txt": "", + }); + const run = async (cmd: $.ShellPromise) => { + const { stdout, stderr, exitCode } = await cmd.quiet(); + return { stdout: stdout.toString(), stderr: stderr.toString(), exitCode }; + }; + const ok = { stdout: "", stderr: "", exitCode: 0 }; + + // Nonexistent operand: ignored, same as -f. + expect(await run($`rm --force ${tempdir}/non_existent.txt`)).toEqual(ok); + + // Existing operand is still removed. + expect(await run($`rm --force ${tempdir}/existent.txt`)).toEqual(ok); + expect(existsSync(`${tempdir}/existent.txt`)).toBeFalse(); + + // Like -f, a later --force cancels an earlier -i (which is otherwise rejected). + expect(await run($`rm -i --force ${tempdir}/after-interactive.txt`)).toEqual(ok); + expect(existsSync(`${tempdir}/after-interactive.txt`)).toBeFalse(); + + // Combined with other long flags, in either order, with missing operands mixed in. + expect(await run($`rm --force --recursive ${tempdir}/dir ${tempdir}/non_existent_dir`)).toEqual(ok); + expect(existsSync(`${tempdir}/dir`)).toBeFalse(); + expect(await run($`rm -r --force ${tempdir}/mixed`)).toEqual(ok); + expect(existsSync(`${tempdir}/mixed`)).toBeFalse(); + + // Only the exact spelling is accepted. + expect(await run($`rm --forc ${tempdir}/kept.txt`)).toEqual({ + stdout: "", + stderr: "rm: illegal option -- -\n", + exitCode: 1, + }); + expect(existsSync(`${tempdir}/kept.txt`)).toBeTrue(); + }); + test("recursive", async () => { const files = { "existent.txt": "", From a89af9de223c5c4764a595bb6426eab2f2063ee1 Mon Sep 17 00:00:00 2001 From: t Date: Sun, 16 Aug 2026 01:14:34 +0000 Subject: [PATCH 2/2] test(shell): place the rm --force case after the recursive test --- test/js/bun/shell/commands/rm.test.ts | 80 +++++++++++++-------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/test/js/bun/shell/commands/rm.test.ts b/test/js/bun/shell/commands/rm.test.ts index 9111fd9e84be..43d66757c186 100644 --- a/test/js/bun/shell/commands/rm.test.ts +++ b/test/js/bun/shell/commands/rm.test.ts @@ -53,46 +53,6 @@ describe.concurrent("bunshell rm", () => { } }); - test("--force is the long form of -f", async () => { - await using tempdir = tempDir("rmlongforce", { - "existent.txt": "", - "after-interactive.txt": "", - "dir/sub/file.txt": "", - "mixed/file.txt": "", - "kept.txt": "", - }); - const run = async (cmd: $.ShellPromise) => { - const { stdout, stderr, exitCode } = await cmd.quiet(); - return { stdout: stdout.toString(), stderr: stderr.toString(), exitCode }; - }; - const ok = { stdout: "", stderr: "", exitCode: 0 }; - - // Nonexistent operand: ignored, same as -f. - expect(await run($`rm --force ${tempdir}/non_existent.txt`)).toEqual(ok); - - // Existing operand is still removed. - expect(await run($`rm --force ${tempdir}/existent.txt`)).toEqual(ok); - expect(existsSync(`${tempdir}/existent.txt`)).toBeFalse(); - - // Like -f, a later --force cancels an earlier -i (which is otherwise rejected). - expect(await run($`rm -i --force ${tempdir}/after-interactive.txt`)).toEqual(ok); - expect(existsSync(`${tempdir}/after-interactive.txt`)).toBeFalse(); - - // Combined with other long flags, in either order, with missing operands mixed in. - expect(await run($`rm --force --recursive ${tempdir}/dir ${tempdir}/non_existent_dir`)).toEqual(ok); - expect(existsSync(`${tempdir}/dir`)).toBeFalse(); - expect(await run($`rm -r --force ${tempdir}/mixed`)).toEqual(ok); - expect(existsSync(`${tempdir}/mixed`)).toBeFalse(); - - // Only the exact spelling is accepted. - expect(await run($`rm --forc ${tempdir}/kept.txt`)).toEqual({ - stdout: "", - stderr: "rm: illegal option -- -\n", - exitCode: 1, - }); - expect(existsSync(`${tempdir}/kept.txt`)).toBeTrue(); - }); - test("recursive", async () => { const files = { "existent.txt": "", @@ -154,6 +114,46 @@ foo/ } }); + test("--force is the long form of -f", async () => { + await using tempdir = tempDir("rmlongforce", { + "existent.txt": "", + "after-interactive.txt": "", + "dir/sub/file.txt": "", + "mixed/file.txt": "", + "kept.txt": "", + }); + const run = async (cmd: $.ShellPromise) => { + const { stdout, stderr, exitCode } = await cmd.quiet(); + return { stdout: stdout.toString(), stderr: stderr.toString(), exitCode }; + }; + const ok = { stdout: "", stderr: "", exitCode: 0 }; + + // Nonexistent operand: ignored, same as -f. + expect(await run($`rm --force ${tempdir}/non_existent.txt`)).toEqual(ok); + + // Existing operand is still removed. + expect(await run($`rm --force ${tempdir}/existent.txt`)).toEqual(ok); + expect(existsSync(`${tempdir}/existent.txt`)).toBeFalse(); + + // Like -f, a later --force cancels an earlier -i (which is otherwise rejected). + expect(await run($`rm -i --force ${tempdir}/after-interactive.txt`)).toEqual(ok); + expect(existsSync(`${tempdir}/after-interactive.txt`)).toBeFalse(); + + // Combined with other long flags, in either order, with missing operands mixed in. + expect(await run($`rm --force --recursive ${tempdir}/dir ${tempdir}/non_existent_dir`)).toEqual(ok); + expect(existsSync(`${tempdir}/dir`)).toBeFalse(); + expect(await run($`rm -r --force ${tempdir}/mixed`)).toEqual(ok); + expect(existsSync(`${tempdir}/mixed`)).toBeFalse(); + + // Only the exact spelling is accepted. + expect(await run($`rm --forc ${tempdir}/kept.txt`)).toEqual({ + stdout: "", + stderr: "rm: illegal option -- -\n", + exitCode: 1, + }); + expect(existsSync(`${tempdir}/kept.txt`)).toBeTrue(); + }); + test("dir", async () => { const files = { "existent.txt": "",