diff --git a/src/js/builtins/shell.ts b/src/js/builtins/shell.ts index a7aa575fc3..2f5f86c55a 100644 --- a/src/js/builtins/shell.ts +++ b/src/js/builtins/shell.ts @@ -244,25 +244,25 @@ export function createBunShellTemplateFunction(createShellInterpreter_, createPa if (!this.#hasRun || !this.#interpreter) { return false; } - + let sig = 15; // SIGTERM by default if (typeof signal === "number") { sig = signal; } else if (typeof signal === "string") { // Convert named signals to numbers const namedSignals: Record = { - 'SIGTERM': 15, - 'SIGKILL': 9, - 'SIGINT': 2, - 'SIGQUIT': 3, - 'SIGHUP': 1, - 'SIGUSR1': 10, - 'SIGUSR2': 12, + "SIGTERM": 15, + "SIGKILL": 9, + "SIGINT": 2, + "SIGQUIT": 3, + "SIGHUP": 1, + "SIGUSR1": 10, + "SIGUSR2": 12, }; - + const upperSignal = signal.toUpperCase(); - const normalizedSignal = upperSignal.startsWith('SIG') ? upperSignal : `SIG${upperSignal}`; - + const normalizedSignal = upperSignal.startsWith("SIG") ? upperSignal : `SIG${upperSignal}`; + if (namedSignals[normalizedSignal] !== undefined) { sig = namedSignals[normalizedSignal]; } else { @@ -271,7 +271,7 @@ export function createBunShellTemplateFunction(createShellInterpreter_, createPa } else if (signal !== undefined) { throw new TypeError("Signal must be a number or string"); } - + return this.#interpreter.kill(sig); } diff --git a/src/shell/interpreter.zig b/src/shell/interpreter.zig index 3bfe647cf8..05acd1144d 100644 --- a/src/shell/interpreter.zig +++ b/src/shell/interpreter.zig @@ -1280,16 +1280,16 @@ pub const Interpreter = struct { pub fn kill(this: *ThisInterpreter, globalThis: *JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!jsc.JSValue { _ = globalThis; // autofix - + const args_ = callframe.arguments_old(1); const args = args_.ptr[0..args_.len]; - const signal: i32 = if (args.len > 0 and args[0].isNumber()) - args[0].toInt32() - else + const signal: i32 = if (args.len > 0 and args[0].isNumber()) + args[0].toInt32() + else 15; // SIGTERM by default var killed_count: u32 = 0; - + // Kill all active subprocesses for (this.active_subprocesses.items) |subprocess| { if (!subprocess.hasExited()) { @@ -1299,7 +1299,7 @@ pub const Interpreter = struct { } } } - + return jsc.JSValue.jsBoolean(killed_count > 0); } diff --git a/src/shell/states/Cmd.zig b/src/shell/states/Cmd.zig index e318257eea..5d8d1d048b 100644 --- a/src/shell/states/Cmd.zig +++ b/src/shell/states/Cmd.zig @@ -528,7 +528,7 @@ fn initSubproc(this: *Cmd) Yield { }, }; subproc.ref(); - + // Register subprocess for kill() support this.base.interpreter.registerSubprocess(subproc); this.spawn_arena_freed = true; @@ -701,10 +701,10 @@ pub fn deinit(this: *Cmd) void { if (this.exec != .none) { if (this.exec == .subproc) { var cmd = this.exec.subproc.child; - + // Unregister subprocess from interpreter tracking this.base.interpreter.unregisterSubprocess(cmd); - + if (cmd.hasExited()) { cmd.unref(true); } else { diff --git a/test/js/bun/shell/shell-kill.test.ts b/test/js/bun/shell/shell-kill.test.ts index 7509005423..3d1d6275ad 100644 --- a/test/js/bun/shell/shell-kill.test.ts +++ b/test/js/bun/shell/shell-kill.test.ts @@ -1,21 +1,20 @@ import { $ } from "bun"; -import { describe, test, expect } from "bun:test"; -import { tempDirWithFiles } from "harness"; +import { describe, expect, test } from "bun:test"; describe("Shell kill() method", () => { test("should be able to kill a long-running process", async () => { const proc = $`sleep 10`; - + // Start the process const promise = proc.run(); - + // Give it a moment to start await Bun.sleep(100); - + // Kill the process const killed = proc.kill(); expect(killed).toBe(true); - + // The process should exit with an error due to being killed const result = await promise; expect(result.exitCode).not.toBe(0); @@ -23,17 +22,17 @@ describe("Shell kill() method", () => { test("should be able to kill with specific signal", async () => { const proc = $`sleep 10`; - + // Start the process const promise = proc.run(); - + // Give it a moment to start await Bun.sleep(100); - + // Kill the process with SIGKILL const killed = proc.kill(9); expect(killed).toBe(true); - + // The process should exit with an error due to being killed const result = await promise; expect(result.exitCode).not.toBe(0); @@ -41,17 +40,17 @@ describe("Shell kill() method", () => { test("should be able to kill with named signal", async () => { const proc = $`sleep 10`; - + // Start the process const promise = proc.run(); - + // Give it a moment to start await Bun.sleep(100); - + // Kill the process with SIGTERM const killed = proc.kill("SIGTERM"); expect(killed).toBe(true); - + // The process should exit with an error due to being killed const result = await promise; expect(result.exitCode).not.toBe(0); @@ -59,17 +58,17 @@ describe("Shell kill() method", () => { test("should be able to kill with signal name without SIG prefix", async () => { const proc = $`sleep 10`; - + // Start the process const promise = proc.run(); - + // Give it a moment to start await Bun.sleep(100); - + // Kill the process with TERM (without SIG prefix) const killed = proc.kill("TERM"); expect(killed).toBe(true); - + // The process should exit with an error due to being killed const result = await promise; expect(result.exitCode).not.toBe(0); @@ -77,7 +76,7 @@ describe("Shell kill() method", () => { test("should return false when trying to kill a process that hasn't started", () => { const proc = $`echo hello`; - + // Try to kill before starting const killed = proc.kill(); expect(killed).toBe(false); @@ -85,10 +84,10 @@ describe("Shell kill() method", () => { test("should return false when trying to kill a process that already exited", async () => { const proc = $`echo hello`; - + // Start and wait for process to finish await proc.run(); - + // Try to kill after it's done (should return false) const killed = proc.kill(); expect(killed).toBe(false); @@ -96,16 +95,16 @@ describe("Shell kill() method", () => { test("should handle invalid signal names", async () => { const proc = $`sleep 10`; - + // Start the process const promise = proc.run(); - + // Give it a moment to start await Bun.sleep(100); - + // Try to kill with invalid signal expect(() => proc.kill("INVALID")).toThrow("Unknown signal: INVALID"); - + // Clean up proc.kill(); await promise.catch(() => {}); @@ -113,16 +112,16 @@ describe("Shell kill() method", () => { test("should handle invalid signal type", async () => { const proc = $`sleep 10`; - + // Start the process const promise = proc.run(); - + // Give it a moment to start await Bun.sleep(100); - + // Try to kill with invalid signal type expect(() => proc.kill({} as any)).toThrow("Signal must be a number or string"); - + // Clean up proc.kill(); await promise.catch(() => {}); @@ -130,17 +129,17 @@ describe("Shell kill() method", () => { test("should be able to kill multiple processes in a pipeline", async () => { const proc = $`sleep 10 | sleep 10`; - + // Start the pipeline const promise = proc.run(); - + // Give it a moment to start await Bun.sleep(100); - + // Kill the pipeline const killed = proc.kill(); expect(killed).toBe(true); - + // The pipeline should exit with an error due to being killed const result = await promise; expect(result.exitCode).not.toBe(0); @@ -148,13 +147,13 @@ describe("Shell kill() method", () => { test("should work with promises (await)", async () => { const proc = $`sleep 10`; - + // Start process via await setTimeout(() => { proc.kill(); }, 100); - + const result = await proc; expect(result.exitCode).not.toBe(0); }); -}); \ No newline at end of file +});