diff options
-rw-r--r-- | src/bun.js/api/bun/subprocess.zig | 218 | ||||
-rw-r--r-- | test/bun.js/bash-echo.sh | 3 | ||||
-rw-r--r-- | test/bun.js/spawn.test.ts | 32 |
3 files changed, 150 insertions, 103 deletions
diff --git a/src/bun.js/api/bun/subprocess.zig b/src/bun.js/api/bun/subprocess.zig index 3c26393d8..5681f5bae 100644 --- a/src/bun.js/api/bun/subprocess.zig +++ b/src/bun.js/api/bun/subprocess.zig @@ -787,17 +787,18 @@ pub const Subprocess = struct { return JSC.JSValue.jsNull(); } - pub fn spawn(globalThis: *JSC.JSGlobalObject, args: JSValue) JSValue { - return spawnMaybeSync(globalThis, args, false); + pub fn spawn(globalThis: *JSC.JSGlobalObject, args: JSValue, secondaryArgsValue: ?JSValue) JSValue { + return spawnMaybeSync(globalThis, args, secondaryArgsValue, false); } - pub fn spawnSync(globalThis: *JSC.JSGlobalObject, args: JSValue) JSValue { - return spawnMaybeSync(globalThis, args, true); + pub fn spawnSync(globalThis: *JSC.JSGlobalObject, args: JSValue, secondaryArgsValue: ?JSValue) JSValue { + return spawnMaybeSync(globalThis, args, secondaryArgsValue, true); } pub fn spawnMaybeSync( globalThis: *JSC.JSGlobalObject, - args: JSValue, + args_: JSValue, + secondaryArgsValue: ?JSValue, comptime is_sync: bool, ) JSValue { var arena = std.heap.ArenaAllocator.init(bun.default_allocator); @@ -815,8 +816,8 @@ pub const Subprocess = struct { var stdio = [3]Stdio{ .{ .ignore = .{} }, - .{ .inherit = .{} }, .{ .pipe = null }, + .{ .inherit = {} }, }; if (comptime is_sync) { @@ -827,138 +828,155 @@ pub const Subprocess = struct { var on_exit_callback = JSValue.zero; var PATH = globalThis.bunVM().bundler.env.get("PATH") orelse ""; var argv: std.ArrayListUnmanaged(?[*:0]const u8) = undefined; + var cmd_value = JSValue.zero; + var args = args_; { - var cmd_value = args.get(globalThis, "cmd") orelse { - globalThis.throwInvalidArguments("cmd must be an array of strings", .{}); - return JSValue.jsUndefined(); - }; - - var cmds_array = cmd_value.arrayIterator(globalThis); - argv = @TypeOf(argv).initCapacity(allocator, cmds_array.len) catch { - globalThis.throw("out of memory", .{}); - return JSValue.jsUndefined(); - }; - - if (cmd_value.isEmptyOrUndefinedOrNull()) { - globalThis.throwInvalidArguments("cmd must be an array of strings", .{}); + if (args.isEmptyOrUndefinedOrNull()) { + globalThis.throwInvalidArguments("cmds must be an array", .{}); return JSValue.jsUndefined(); } - if (cmds_array.len == 0) { - globalThis.throwInvalidArguments("cmd must not be empty", .{}); + const args_type = args.jsType(); + if (args_type.isArray()) { + cmd_value = args; + args = secondaryArgsValue orelse JSValue.zero; + } else if (args.get(globalThis, "cmd")) |cmd_value_| { + cmd_value = cmd_value_; + } else { + globalThis.throwInvalidArguments("cmds must be an array", .{}); return JSValue.jsUndefined(); } { - var first_cmd = cmds_array.next().?; - var arg0 = first_cmd.toSlice(globalThis, allocator); - defer arg0.deinit(); - var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; - var resolved = Which.which(&path_buf, PATH, cwd, arg0.slice()) orelse { - globalThis.throwInvalidArguments("cmd not in $PATH: {s}", .{arg0}); + var cmds_array = cmd_value.arrayIterator(globalThis); + argv = @TypeOf(argv).initCapacity(allocator, cmds_array.len) catch { + globalThis.throw("out of memory", .{}); return JSValue.jsUndefined(); }; - argv.appendAssumeCapacity(allocator.dupeZ(u8, bun.span(resolved)) catch { - globalThis.throw("out of memory", .{}); + + if (cmd_value.isEmptyOrUndefinedOrNull()) { + globalThis.throwInvalidArguments("cmd must be an array of strings", .{}); return JSValue.jsUndefined(); - }); - } + } - while (cmds_array.next()) |value| { - argv.appendAssumeCapacity(value.getZigString(globalThis).toOwnedSliceZ(allocator) catch { - globalThis.throw("out of memory", .{}); + if (cmds_array.len == 0) { + globalThis.throwInvalidArguments("cmd must not be empty", .{}); return JSValue.jsUndefined(); - }); - } + } - if (argv.items.len == 0) { - globalThis.throwInvalidArguments("cmd must be an array of strings", .{}); - return JSValue.jsUndefined(); - } + { + var first_cmd = cmds_array.next().?; + var arg0 = first_cmd.toSlice(globalThis, allocator); + defer arg0.deinit(); + var path_buf: [bun.MAX_PATH_BYTES]u8 = undefined; + var resolved = Which.which(&path_buf, PATH, cwd, arg0.slice()) orelse { + globalThis.throwInvalidArguments("cmd not in $PATH: {s}", .{arg0}); + return JSValue.jsUndefined(); + }; + argv.appendAssumeCapacity(allocator.dupeZ(u8, bun.span(resolved)) catch { + globalThis.throw("out of memory", .{}); + return JSValue.jsUndefined(); + }); + } - if (args.get(globalThis, "cwd")) |cwd_| { - if (!cwd_.isEmptyOrUndefinedOrNull()) { - cwd = cwd_.getZigString(globalThis).toOwnedSliceZ(allocator) catch { + while (cmds_array.next()) |value| { + argv.appendAssumeCapacity(value.getZigString(globalThis).toOwnedSliceZ(allocator) catch { globalThis.throw("out of memory", .{}); return JSValue.jsUndefined(); - }; + }); + } + + if (argv.items.len == 0) { + globalThis.throwInvalidArguments("cmd must be an array of strings", .{}); + return JSValue.jsUndefined(); } } - if (args.get(globalThis, "onExit")) |onExit_| { - if (!onExit_.isEmptyOrUndefinedOrNull()) { - if (!onExit_.isCell() or !onExit_.isCallable(globalThis.vm())) { - globalThis.throwInvalidArguments("onExit must be a function or undefined", .{}); - return JSValue.jsUndefined(); + if (args != .zero and args.isObject()) { + if (args.get(globalThis, "cwd")) |cwd_| { + if (!cwd_.isEmptyOrUndefinedOrNull()) { + cwd = cwd_.getZigString(globalThis).toOwnedSliceZ(allocator) catch { + globalThis.throw("out of memory", .{}); + return JSValue.jsUndefined(); + }; } - on_exit_callback = onExit_; } - } - if (args.get(globalThis, "env")) |object| { - if (!object.isEmptyOrUndefinedOrNull()) { - if (!object.isObject()) { - globalThis.throwInvalidArguments("env must be an object", .{}); - return JSValue.jsUndefined(); + if (args.get(globalThis, "onExit")) |onExit_| { + if (!onExit_.isEmptyOrUndefinedOrNull()) { + if (!onExit_.isCell() or !onExit_.isCallable(globalThis.vm())) { + globalThis.throwInvalidArguments("onExit must be a function or undefined", .{}); + return JSValue.jsUndefined(); + } + on_exit_callback = onExit_; } + } - var object_iter = JSC.JSPropertyIterator(.{ - .skip_empty_name = false, - .include_value = true, - }).init(globalThis, object.asObjectRef()); - defer object_iter.deinit(); - env_array.ensureTotalCapacityPrecise(allocator, object_iter.len) catch { - globalThis.throw("out of memory", .{}); - return JSValue.jsUndefined(); - }; + if (args.get(globalThis, "env")) |object| { + if (!object.isEmptyOrUndefinedOrNull()) { + if (!object.isObject()) { + globalThis.throwInvalidArguments("env must be an object", .{}); + return JSValue.jsUndefined(); + } - while (object_iter.next()) |key| { - var value = object_iter.value; - var line = std.fmt.allocPrintZ(allocator, "{}={}", .{ key, value.getZigString(globalThis) }) catch { + var object_iter = JSC.JSPropertyIterator(.{ + .skip_empty_name = false, + .include_value = true, + }).init(globalThis, object.asObjectRef()); + defer object_iter.deinit(); + env_array.ensureTotalCapacityPrecise(allocator, object_iter.len) catch { globalThis.throw("out of memory", .{}); return JSValue.jsUndefined(); }; - if (key.eqlComptime("PATH")) { - PATH = bun.span(line["PATH=".len..]); + while (object_iter.next()) |key| { + var value = object_iter.value; + var line = std.fmt.allocPrintZ(allocator, "{}={}", .{ key, value.getZigString(globalThis) }) catch { + globalThis.throw("out of memory", .{}); + return JSValue.jsUndefined(); + }; + + if (key.eqlComptime("PATH")) { + PATH = bun.span(line["PATH=".len..]); + } + env_array.append(allocator, line) catch { + globalThis.throw("out of memory", .{}); + return JSValue.jsUndefined(); + }; } - env_array.append(allocator, line) catch { - globalThis.throw("out of memory", .{}); - return JSValue.jsUndefined(); - }; } } - } - if (args.get(globalThis, "stdio")) |stdio_val| { - if (!stdio_val.isEmptyOrUndefinedOrNull()) { - if (stdio_val.jsType().isArray()) { - var stdio_iter = stdio_val.arrayIterator(globalThis); - stdio_iter.len = @minimum(stdio_iter.len, 3); - var i: usize = 0; - while (stdio_iter.next()) |value| : (i += 1) { - if (!extractStdio(globalThis, i, value, &stdio)) - return JSC.JSValue.jsUndefined(); + if (args.get(globalThis, "stdio")) |stdio_val| { + if (!stdio_val.isEmptyOrUndefinedOrNull()) { + if (stdio_val.jsType().isArray()) { + var stdio_iter = stdio_val.arrayIterator(globalThis); + stdio_iter.len = @minimum(stdio_iter.len, 3); + var i: usize = 0; + while (stdio_iter.next()) |value| : (i += 1) { + if (!extractStdio(globalThis, i, value, &stdio)) + return JSC.JSValue.jsUndefined(); + } + } else { + globalThis.throwInvalidArguments("stdio must be an array", .{}); + return JSValue.jsUndefined(); } - } else { - globalThis.throwInvalidArguments("stdio must be an array", .{}); - return JSValue.jsUndefined(); } - } - } else { - if (args.get(globalThis, "stdin")) |value| { - if (!extractStdio(globalThis, std.os.STDIN_FILENO, value, &stdio)) - return JSC.JSValue.jsUndefined(); - } + } else { + if (args.get(globalThis, "stdin")) |value| { + if (!extractStdio(globalThis, std.os.STDIN_FILENO, value, &stdio)) + return JSC.JSValue.jsUndefined(); + } - if (args.get(globalThis, "stderr")) |value| { - if (!extractStdio(globalThis, std.os.STDERR_FILENO, value, &stdio)) - return JSC.JSValue.jsUndefined(); - } + if (args.get(globalThis, "stderr")) |value| { + if (!extractStdio(globalThis, std.os.STDERR_FILENO, value, &stdio)) + return JSC.JSValue.jsUndefined(); + } - if (args.get(globalThis, "stdout")) |value| { - if (!extractStdio(globalThis, std.os.STDOUT_FILENO, value, &stdio)) - return JSC.JSValue.jsUndefined(); + if (args.get(globalThis, "stdout")) |value| { + if (!extractStdio(globalThis, std.os.STDOUT_FILENO, value, &stdio)) + return JSC.JSValue.jsUndefined(); + } } } } diff --git a/test/bun.js/bash-echo.sh b/test/bun.js/bash-echo.sh index ad5eabdd6..7e6e64cb4 100644 --- a/test/bun.js/bash-echo.sh +++ b/test/bun.js/bash-echo.sh @@ -1,7 +1,4 @@ #!/usr/bin/env bash -echoerr() { echo "$@" 1>&2; } - myvar=$(cat /dev/stdin) -# echoerr ${#myvar} chars echo -e "$myvar" diff --git a/test/bun.js/spawn.test.ts b/test/bun.js/spawn.test.ts index 6829791ce..9e4144024 100644 --- a/test/bun.js/spawn.test.ts +++ b/test/bun.js/spawn.test.ts @@ -11,6 +11,14 @@ for (let [gcTick, label] of [ describe("spawnSync", () => { const hugeString = "hello".repeat(10000).slice(); + it("as an array", () => { + const { stdout } = spawnSync(["echo", "hi"]); + + // stdout is a Buffer + const text = stdout.toString(); + expect(text).toBe("hi\n"); + }); + it("Uint8Array works as stdin", async () => { const { stdout, stderr } = spawnSync({ cmd: ["cat"], @@ -25,6 +33,30 @@ for (let [gcTick, label] of [ describe("spawn", () => { const hugeString = "hello".repeat(10000).slice(); + it("as an array", async () => { + const { stdout, exited } = spawn(["echo", "hello"], { + stdout: "pipe", + }); + gcTick(); + expect(await new Response(stdout).text()).toBe("hello\n"); + }); + + it("as an array with options object", async () => { + const { stdout } = spawn(["printenv", "FOO"], { + cwd: "/tmp", + env: { + ...process.env, + FOO: "bar", + }, + stdin: null, + stdout: "pipe", + stderr: "inherit", + }); + + const text = await new Response(stdout).text(); + expect(text).toBe("bar\n"); + }); + it("Uint8Array works as stdin", async () => { rmSync("/tmp/out.123.txt", { force: true }); gcTick(); |