diff --git a/src/bun.js/api/bun/subprocess.zig b/src/bun.js/api/bun/subprocess.zig index 4ceeb63779..9dfceca67a 100644 --- a/src/bun.js/api/bun/subprocess.zig +++ b/src/bun.js/api/bun/subprocess.zig @@ -1766,13 +1766,13 @@ pub const Subprocess = struct { if (argv0 == null) { var path_buf: bun.PathBuffer = undefined; const resolved = Which.which(&path_buf, PATH, cwd, arg0.slice()) orelse { - return globalThis.throwInvalidArguments("Executable not found in $PATH: \"{s}\"", .{arg0.slice()}); + return throwCommandNotFound(globalThis, arg0.slice()); }; argv0 = try allocator.dupeZ(u8, resolved); } else { var path_buf: bun.PathBuffer = undefined; const resolved = Which.which(&path_buf, PATH, cwd, bun.sliceTo(argv0.?, 0)) orelse { - return globalThis.throwInvalidArguments("Executable not found in $PATH: \"{s}\"", .{arg0.slice()}); + return throwCommandNotFound(globalThis, arg0.slice()); }; argv0 = try allocator.dupeZ(u8, resolved); } @@ -2314,6 +2314,15 @@ pub const Subprocess = struct { return sync_value; } + fn throwCommandNotFound(globalThis: *JSC.JSGlobalObject, command: []const u8) bun.JSError { + const message = bun.String.createFormat("Executable not found in $PATH: \"{s}\"", .{command}) catch bun.outOfMemory(); + defer message.deref(); + const err = message.toZigString().toErrorInstance(globalThis); + err.putZigString(globalThis, JSC.ZigString.static("code"), JSC.ZigString.init("ENOENT").toJS(globalThis)); + err.putZigString(globalThis, JSC.ZigString.static("path"), JSC.ZigString.init(command).toJS(globalThis)); + return globalThis.throwValue(err); + } + const node_cluster_binding = @import("./../../node/node_cluster_binding.zig"); pub fn handleIPCMessage( diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 6c9ea21c05..6caba4d5d5 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -4197,7 +4197,7 @@ pub const JSValue = enum(i64) { return cppFn("putRecord", .{ value, global, key, values_array, values_len }); } - fn putZigString(value: JSValue, global: *JSGlobalObject, key: *const ZigString, result: JSC.JSValue) void { + pub fn putZigString(value: JSValue, global: *JSGlobalObject, key: *const ZigString, result: JSC.JSValue) void { @import("./headers.zig").JSC__JSValue__put(value, global, key, result); } diff --git a/src/js/node/child_process.ts b/src/js/node/child_process.ts index 6767c6206b..44225330a0 100644 --- a/src/js/node/child_process.ts +++ b/src/js/node/child_process.ts @@ -558,20 +558,27 @@ function spawnSync(file, args, options) { } } - const { - stdout = null, - stderr = null, - success, - exitCode, - signalCode, - } = Bun.spawnSync({ - cmd: options.args, - env: options.env || undefined, - cwd: options.cwd || undefined, - stdio: bunStdio, - windowsVerbatimArguments: options.windowsVerbatimArguments, - windowsHide: options.windowsHide, - }); + var error; + try { + var { + stdout = null, + stderr = null, + success, + exitCode, + signalCode, + } = Bun.spawnSync({ + cmd: options.args, + env: options.env || undefined, + cwd: options.cwd || undefined, + stdio: bunStdio, + windowsVerbatimArguments: options.windowsVerbatimArguments, + windowsHide: options.windowsHide, + }); + } catch (err) { + error = err; + stdout = null; + stderr = null; + } const result = { signal: signalCode ?? null, @@ -580,6 +587,10 @@ function spawnSync(file, args, options) { output: [null, stdout, stderr], }; + if (error) { + result.error = error; + } + if (stdout && encoding && encoding !== "buffer") { result.output[1] = result.output[1]?.toString(encoding); } @@ -591,8 +602,11 @@ function spawnSync(file, args, options) { result.stdout = result.output[1]; result.stderr = result.output[2]; - if (!success) { + if (!success && error == null) { result.error = new SystemError(result.output[2], options.file, "spawnSync", -1, result.status); + } + + if (result.error) { result.error.spawnargs = ArrayPrototypeSlice.$call(options.args, 1); } diff --git a/test/js/node/child_process/child_process.test.ts b/test/js/node/child_process/child_process.test.ts index 11bd16de1d..f70772d421 100644 --- a/test/js/node/child_process/child_process.test.ts +++ b/test/js/node/child_process/child_process.test.ts @@ -454,3 +454,13 @@ it.if(!isWindows)("spawnSync correctly reports signal codes", () => { expect(signal).toBe("SIGTRAP"); }); + +it("spawnSync(does-not-exist)", () => { + const x = spawnSync("does-not-exist"); + expect(x.error?.code).toEqual("ENOENT"); + expect(x.error.path).toEqual("does-not-exist"); + expect(x.signal).toEqual(null); + expect(x.output).toEqual([null, null, null]); + expect(x.stdout).toEqual(null); + expect(x.stderr).toEqual(null); +});