aboutsummaryrefslogtreecommitdiff
path: root/src/bun.js/api/bun.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/bun.js/api/bun.zig')
-rw-r--r--src/bun.js/api/bun.zig761
1 files changed, 761 insertions, 0 deletions
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig
index f36549485..739449f3f 100644
--- a/src/bun.js/api/bun.zig
+++ b/src/bun.js/api/bun.zig
@@ -1195,6 +1195,9 @@ pub const Class = NewClass(
.which = .{
.rfn = which,
},
+ .spawn = .{
+ .rfn = JSC.wrapWithHasContainer(Subprocess, "spawn", false, false, false),
+ },
},
.{
.main = .{
@@ -3367,3 +3370,761 @@ pub const JSZlib = struct {
return array_buffer.toJSWithContext(globalThis.ref(), reader, reader_deallocator, null);
}
};
+
+pub const Subprocess = struct {
+ pub usingnamespace JSC.Codegen.JSSubprocess;
+
+ pid: std.os.pid_t,
+ stdin: Writable,
+ stdout: Readable,
+ stderr: Readable,
+
+ killed: bool = false,
+ has_ref: bool = false,
+
+ exit_promise: JSValue = JSValue.zero,
+ this_jsvalue: JSValue = JSValue.zero,
+
+ exit_code: ?u8 = null,
+
+ has_waitpid_task: bool = false,
+ notification_task: JSC.AnyTask = undefined,
+ waitpid_task: JSC.AnyTask = undefined,
+ wait_task: JSC.ConcurrentTask = .{},
+
+ finalized: bool = false,
+
+ globalThis: *JSC.JSGlobalObject,
+
+ pub fn constructor(
+ _: *JSC.JSGlobalObject,
+ _: *JSC.CallFrame,
+ ) callconv(.C) ?*Subprocess {
+ return null;
+ }
+
+ const Readable = union(enum) {
+ fd: JSC.Node.FileDescriptor,
+ pipe: JSC.WebCore.ReadableStream,
+ inherit: void,
+ ignore: void,
+ closed: void,
+
+ pub fn init(stdio: std.meta.Tag(Stdio), fd: i32, globalThis: *JSC.JSGlobalObject) Readable {
+ return switch (stdio) {
+ .inherit => Readable{ .inherit = {} },
+ .ignore => Readable{ .ignore = {} },
+ .pipe => brk: {
+ var blob = JSC.WebCore.Blob.findOrCreateFileFromPath(.{ .fd = fd }, globalThis);
+ defer blob.detach();
+
+ var stream = JSC.WebCore.ReadableStream.fromBlob(globalThis, &blob, 0);
+
+ break :brk Readable{ .pipe = JSC.WebCore.ReadableStream.fromJS(stream, globalThis).? };
+ },
+ .callback, .fd, .path, .blob => Readable{ .fd = @intCast(JSC.Node.FileDescriptor, fd) },
+ };
+ }
+
+ pub fn close(this: *Readable) void {
+ switch (this.*) {
+ .fd => |fd| {
+ _ = JSC.Node.Syscall.close(fd);
+ },
+ .pipe => |pipe| {
+ pipe.done();
+ },
+ else => {},
+ }
+
+ this.* = .closed;
+ }
+
+ pub fn toJS(this: Readable) JSValue {
+ switch (this) {
+ .fd => |fd| {
+ return JSValue.jsNumber(fd);
+ },
+ .pipe => |pipe| {
+ return pipe.toJS();
+ },
+ else => {
+ return JSValue.jsUndefined();
+ },
+ }
+ }
+ };
+
+ pub fn getStderr(
+ this: *Subprocess,
+ _: *JSGlobalObject,
+ ) callconv(.C) JSValue {
+ return this.stderr.toJS();
+ }
+
+ pub fn getStdin(
+ this: *Subprocess,
+ globalThis: *JSGlobalObject,
+ ) callconv(.C) JSValue {
+ return this.stdin.toJS(globalThis);
+ }
+
+ pub fn getStdout(
+ this: *Subprocess,
+ _: *JSGlobalObject,
+ ) callconv(.C) JSValue {
+ return this.stdout.toJS();
+ }
+
+ pub fn kill(
+ this: *Subprocess,
+ globalThis: *JSGlobalObject,
+ callframe: *JSC.CallFrame,
+ ) callconv(.C) JSValue {
+ var arguments = callframe.arguments(1);
+ var sig: i32 = 0;
+
+ if (arguments.len > 0) {
+ sig = arguments.ptr[0].toInt32();
+ }
+
+ if (!(sig > -1 and sig < std.math.maxInt(u8))) {
+ globalThis.throwInvalidArguments("Invalid signal: must be > -1 and < 255", .{});
+ return JSValue.jsUndefined();
+ }
+
+ if (this.killed) {
+ return JSValue.jsUndefined();
+ }
+
+ const err = std.c.kill(this.pid, sig);
+ if (err != 0) {
+ return JSC.Node.Syscall.Error.fromCode(std.c.getErrno(err), .kill).toJSC(globalThis);
+ }
+
+ return JSValue.jsUndefined();
+ }
+
+ pub fn onKill(
+ this: *Subprocess,
+ ) void {
+ if (this.killed) {
+ return;
+ }
+
+ this.killed = true;
+ this.closePorts();
+ }
+
+ pub fn closePorts(this: *Subprocess) void {
+ if (this.stdout == .pipe) {
+ this.stdout.pipe.isLocked()
+ this.stdout.pipe.cancel(this.globalThis);
+ }
+
+ if (this.stderr == .pipe) {
+ this.stderr.pipe.cancel(this.globalThis);
+ }
+
+ this.stdin.close();
+ this.stdout.close();
+ this.stderr.close();
+ }
+
+ pub fn unref(this: *Subprocess) void {
+ if (!this.has_ref)
+ return;
+ this.has_ref = false;
+ this.globalThis.bunVM().active_tasks -= 1;
+ }
+
+ pub fn ref(this: *Subprocess) void {
+ if (this.has_ref)
+ return;
+ this.has_ref = true;
+ this.globalThis.bunVM().active_tasks += 1;
+ }
+
+ pub fn doRef(this: *Subprocess, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue {
+ this.ref();
+ return JSC.JSValue.jsUndefined();
+ }
+
+ pub fn doUnref(this: *Subprocess, _: *JSC.JSGlobalObject, _: *JSC.CallFrame) callconv(.C) JSValue {
+ this.unref();
+ return JSC.JSValue.jsUndefined();
+ }
+
+ pub fn getPid(
+ this: *Subprocess,
+ _: *JSGlobalObject,
+ ) callconv(.C) JSValue {
+ return JSValue.jsNumber(this.pid);
+ }
+
+ pub fn getKilled(
+ this: *Subprocess,
+ _: *JSGlobalObject,
+ ) callconv(.C) JSValue {
+ return JSValue.jsBoolean(this.killed);
+ }
+
+ const Writable = union(enum) {
+ pipe: *JSC.WebCore.FileSink,
+ fd: JSC.Node.FileDescriptor,
+ inherit: void,
+ ignore: void,
+
+ pub fn init(stdio: std.meta.Tag(Stdio), fd: i32, globalThis: *JSC.JSGlobalObject) !Writable {
+ switch (stdio) {
+ .path, .pipe, .callback => {
+ var sink = try globalThis.bunVM().allocator.create(JSC.WebCore.FileSink);
+ sink.* = .{
+ .opened_fd = fd,
+ .buffer = bun.ByteList.init(&.{}),
+ .allocator = globalThis.bunVM().allocator,
+ };
+
+ return Writable{ .pipe = sink };
+ },
+ .blob, .fd => {
+ return Writable{ .fd = @intCast(JSC.Node.FileDescriptor, fd) };
+ },
+ .inherit => {
+ return Writable{ .inherit = {} };
+ },
+ .ignore => {
+ return Writable{ .ignore = {} };
+ },
+ }
+ }
+
+ pub fn toJS(this: Writable, globalThis: *JSC.JSGlobalObject) JSValue {
+ return switch (this) {
+ .pipe => |pipe| pipe.toJS(globalThis),
+ .fd => |fd| JSValue.jsNumber(fd),
+ .ignore => JSValue.jsUndefined(),
+ .inherit => JSValue.jsUndefined(),
+ };
+ }
+
+ pub fn close(this: *Writable) void {
+ return switch (this.*) {
+ .pipe => |pipe| {
+ _ = pipe.end(null);
+ },
+ .fd => |fd| {
+ _ = JSC.Node.Syscall.close(fd);
+ },
+ .ignore => {},
+ .inherit => {},
+ };
+ }
+ };
+
+ pub fn finalize(this: *Subprocess) callconv(.C) void {
+ this.unref();
+ this.closePorts();
+ this.finalized = true;
+
+ if (this.exit_code != null)
+ bun.default_allocator.destroy(this);
+ }
+
+ pub fn getExitStatus(
+ this: *Subprocess,
+ globalThis: *JSGlobalObject,
+ ) callconv(.C) JSValue {
+ if (this.exit_code) |code| {
+ return JSC.JSPromise.resolvedPromiseValue(globalThis, JSC.JSValue.jsNumber(code));
+ }
+
+ if (this.exit_promise == .zero) {
+ this.exit_promise = JSC.JSPromise.create(globalThis).asValue(globalThis);
+ }
+
+ return this.exit_promise;
+ }
+
+ pub fn spawn(globalThis: *JSC.JSGlobalObject, args: JSValue) JSValue {
+ var arena = std.heap.ArenaAllocator.init(bun.default_allocator);
+ defer arena.deinit();
+ var allocator = arena.allocator();
+
+ var env: [*:null]?[*:0]const u8 = undefined;
+
+ var env_array = std.ArrayListUnmanaged(?[*:0]const u8){
+ .items = &.{},
+ .capacity = 0,
+ };
+
+ var cwd = globalThis.bunVM().bundler.fs.top_level_dir;
+
+ var stdio = [3]Stdio{
+ .{ .ignore = .{} },
+ .{ .inherit = .{} },
+ .{ .pipe = .{} },
+ };
+
+ var PATH = globalThis.bunVM().bundler.env.get("PATH") orelse "";
+ var argv: std.ArrayListUnmanaged(?[*:0]const u8) = undefined;
+ {
+ 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", .{});
+ return JSValue.jsUndefined();
+ }
+
+ if (cmds_array.len == 0) {
+ globalThis.throwInvalidArguments("cmd must not be empty", .{});
+ 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();
+ });
+ }
+
+ 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, "cwd")) |cwd_| {
+ if (!cwd_.isEmptyOrUndefinedOrNull()) {
+ cwd = cwd_.getZigString(globalThis).toOwnedSliceZ(allocator) 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();
+ }
+
+ 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();
+ };
+
+ 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();
+ };
+ }
+ }
+ }
+
+ 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 {
+ 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, "stdout")) |value| {
+ if (!extractStdio(globalThis, std.os.STDOUT_FILENO, value, &stdio))
+ return JSC.JSValue.jsUndefined();
+ }
+ }
+ }
+
+ var attr = PosixSpawn.Attr.init() catch {
+ globalThis.throw("out of memory", .{});
+ return JSValue.jsUndefined();
+ };
+
+ defer attr.deinit();
+ var actions = PosixSpawn.Actions.init() catch |err| return globalThis.handleError(err, "in posix_spawn");
+ attr.set(
+ os.darwin.POSIX_SPAWN_CLOEXEC_DEFAULT | os.darwin.POSIX_SPAWN_SETSIGDEF | os.darwin.POSIX_SPAWN_SETSIGMASK,
+ ) catch |err| return globalThis.handleError(err, "in posix_spawn");
+ defer actions.deinit();
+
+ if (env_array.items.len == 0) {
+ env_array.items = globalThis.bunVM().bundler.env.map.createNullDelimitedEnvMap(allocator) catch |err| return globalThis.handleError(err, "in posix_spawn");
+ env_array.capacity = env_array.items.len;
+ }
+
+ const any_ignore = stdio[0] == .ignore or stdio[1] == .ignore or stdio[2] == .ignore;
+ const dev_null_fd = @intCast(
+ i32,
+ if (any_ignore)
+ std.os.openZ("/dev/null", std.os.O.RDONLY | std.os.O.WRONLY, 0) catch |err| {
+ globalThis.throw("failed to open /dev/null: {s}", .{err});
+ return JSValue.jsUndefined();
+ }
+ else
+ -1,
+ );
+
+ const stdin_pipe = if (stdio[0].isPiped()) os.pipe2(os.O.NONBLOCK) catch |err| {
+ globalThis.throw("failed to create stdin pipe: {s}", .{err});
+ return JSValue.jsUndefined();
+ } else undefined;
+ errdefer if (stdio[0].isPiped()) destroyPipe(stdin_pipe);
+
+ const stdout_pipe = if (stdio[1].isPiped()) os.pipe2(os.O.NONBLOCK) catch |err| {
+ globalThis.throw("failed to create stdout pipe: {s}", .{err});
+ return JSValue.jsUndefined();
+ } else undefined;
+ errdefer if (stdio[1].isPiped()) destroyPipe(stdout_pipe);
+
+ const stderr_pipe = if (stdio[2].isPiped()) os.pipe2(os.O.NONBLOCK) catch |err| {
+ globalThis.throw("failed to create stderr pipe: {s}", .{err});
+ return JSValue.jsUndefined();
+ } else undefined;
+ errdefer if (stdio[2].isPiped()) destroyPipe(stderr_pipe);
+
+ stdio[0].setUpChildIoPosixSpawn(
+ &actions,
+ stdin_pipe,
+ std.os.STDIN_FILENO,
+ dev_null_fd,
+ ) catch |err| return globalThis.handleError(err, "in configuring child stdin");
+
+ stdio[1].setUpChildIoPosixSpawn(
+ &actions,
+ stdout_pipe,
+ std.os.STDOUT_FILENO,
+ dev_null_fd,
+ ) catch |err| return globalThis.handleError(err, "in configuring child stdout");
+
+ stdio[2].setUpChildIoPosixSpawn(
+ &actions,
+ stderr_pipe,
+ std.os.STDERR_FILENO,
+ dev_null_fd,
+ ) catch |err| return globalThis.handleError(err, "in configuring child stderr");
+
+ actions.chdir(cwd) catch |err| return globalThis.handleError(err, "in chdir()");
+
+ argv.append(allocator, null) catch {
+ globalThis.throw("out of memory", .{});
+ return JSValue.jsUndefined();
+ };
+
+ if (env_array.items.len > 0) {
+ env_array.append(allocator, null) catch {
+ globalThis.throw("out of memory", .{});
+ return JSValue.jsUndefined();
+ };
+ env = @ptrCast(@TypeOf(env), env_array.items.ptr);
+ }
+
+ const pid = switch (PosixSpawn.spawnZ(argv.items[0].?, actions, attr, @ptrCast([*:null]?[*:0]const u8, argv.items[0..].ptr), env)) {
+ .err => |err| return err.toJSC(globalThis),
+ .result => |pid_| pid_,
+ };
+
+ var subprocess = globalThis.allocator().create(Subprocess) catch {
+ globalThis.throw("out of memory", .{});
+ return JSValue.jsUndefined();
+ };
+
+ subprocess.* = Subprocess{
+ .globalThis = globalThis,
+ .pid = pid,
+ .stdin = Writable.init(std.meta.activeTag(stdio[std.os.STDIN_FILENO]), stdin_pipe[1], globalThis) catch {
+ globalThis.throw("out of memory", .{});
+ return JSValue.jsUndefined();
+ },
+ .stdout = Readable.init(std.meta.activeTag(stdio[std.os.STDOUT_FILENO]), stdout_pipe[0], globalThis),
+ .stderr = Readable.init(std.meta.activeTag(stdio[std.os.STDERR_FILENO]), stderr_pipe[0], globalThis),
+ };
+
+ subprocess.this_jsvalue = subprocess.toJS(globalThis);
+ subprocess.this_jsvalue.ensureStillAlive();
+
+ switch (globalThis.bunVM().poller.watch(
+ @intCast(JSC.Node.FileDescriptor, pid),
+ .process,
+ Subprocess,
+ subprocess,
+ )) {
+ .result => {},
+ .err => |err| {
+ if (err.getErrno() == .SRCH) {
+ @panic("This shouldn't happen");
+ }
+
+ // process has already exited
+ // https://cs.github.com/libuv/libuv/blob/b00d1bd225b602570baee82a6152eaa823a84fa6/src/unix/process.c#L1007
+ subprocess.onExitNotification();
+ },
+ }
+
+ return subprocess.this_jsvalue;
+ }
+
+ pub fn onExitNotification(
+ this: *Subprocess,
+ ) void {
+ this.wait(this.globalThis.bunVM());
+ }
+
+ pub fn wait(this: *Subprocess, vm: *JSC.VirtualMachine) void {
+ if (this.has_waitpid_task) {
+ return;
+ }
+
+ vm.uws_event_loop.?.active -|= 1;
+
+ this.has_waitpid_task = true;
+ const pid = this.pid;
+ const status = PosixSpawn.waitpid(pid, 0) catch |err| {
+ Output.debug("waitpid({d}) failed: {s}", .{ pid, @errorName(err) });
+ return;
+ };
+
+ this.exit_code = @truncate(u8, status.status);
+ this.waitpid_task = JSC.AnyTask.New(Subprocess, onExit).init(this);
+ vm.eventLoop().enqueueTask(JSC.Task.init(&this.waitpid_task));
+ }
+
+ fn onExit(this: *Subprocess) void {
+ this.closePorts();
+
+ this.has_waitpid_task = false;
+
+ if (this.exit_promise != .zero) {
+ var promise = this.exit_promise;
+ this.exit_promise = .zero;
+ promise.asPromise().?.resolve(this.globalThis, JSValue.jsNumber(this.exit_code.?));
+ }
+
+ this.unref();
+
+ if (this.finalized) {
+ this.finalize();
+ }
+ }
+
+ const os = std.os;
+ fn destroyPipe(pipe: [2]os.fd_t) void {
+ os.close(pipe[0]);
+ if (pipe[0] != pipe[1]) os.close(pipe[1]);
+ }
+
+ const PosixSpawn = @import("./bun/spawn.zig").PosixSpawn;
+
+ const Stdio = union(enum) {
+ inherit: void,
+ ignore: void,
+ fd: JSC.Node.FileDescriptor,
+ path: JSC.Node.PathLike,
+ blob: JSC.WebCore.Blob,
+ pipe: void,
+ callback: JSC.JSValue,
+
+ pub fn isPiped(self: Stdio) bool {
+ return switch (self) {
+ .blob, .callback, .pipe => true,
+ else => false,
+ };
+ }
+
+ fn setUpChildIoPosixSpawn(
+ stdio: @This(),
+ actions: *PosixSpawn.Actions,
+ pipe_fd: [2]i32,
+ std_fileno: i32,
+ _: i32,
+ ) !void {
+ switch (stdio) {
+ .blob, .callback, .pipe => {
+ const idx: usize = if (std_fileno == 0) 0 else 1;
+ try actions.dup2(pipe_fd[idx], std_fileno);
+ try actions.close(pipe_fd[1 - idx]);
+ },
+ .fd => |fd| {
+ try actions.dup2(fd, std_fileno);
+ },
+ .path => |pathlike| {
+ const flag = if (std_fileno == std.os.STDIN_FILENO) @as(u32, os.O.WRONLY) else @as(u32, std.os.O.RDONLY);
+ try actions.open(std_fileno, pathlike.slice(), flag | std.os.O.CREAT, 0o664);
+ },
+ .inherit => {
+ try actions.inherit(std_fileno);
+ },
+ .ignore => {
+ const flag = if (std_fileno == std.os.STDIN_FILENO) @as(u32, os.O.RDONLY) else @as(u32, std.os.O.WRONLY);
+ try actions.openZ(std_fileno, "/dev/null", flag, 0o664);
+ },
+ }
+ }
+ };
+
+ fn extractStdio(
+ globalThis: *JSC.JSGlobalObject,
+ i: usize,
+ value: JSValue,
+ stdio_array: []Stdio,
+ ) bool {
+ if (value.isEmptyOrUndefinedOrNull()) {
+ return true;
+ }
+
+ if (value.isString()) {
+ const str = value.getZigString(globalThis);
+ if (str.eqlComptime("inherit")) {
+ stdio_array[i] = Stdio{ .inherit = {} };
+ } else if (str.eqlComptime("ignore")) {
+ stdio_array[i] = Stdio{ .ignore = {} };
+ } else if (str.eqlComptime("pipe")) {
+ stdio_array[i] = Stdio{ .pipe = {} };
+ } else {
+ globalThis.throwInvalidArguments("stdio must be an array of 'inherit', 'ignore', or null", .{});
+ return false;
+ }
+
+ return true;
+ } else if (value.isNumber()) {
+ const fd_ = value.toInt64();
+ if (fd_ < 0) {
+ globalThis.throwInvalidArguments("file descriptor must be a positive integer", .{});
+ return false;
+ }
+
+ const fd = @intCast(JSC.Node.FileDescriptor, fd_);
+
+ switch (@intCast(std.os.fd_t, i)) {
+ std.os.STDIN_FILENO => {
+ if (i == std.os.STDERR_FILENO or i == std.os.STDOUT_FILENO) {
+ globalThis.throwInvalidArguments("stdin cannot be used for stdout or stderr", .{});
+ return false;
+ }
+ },
+
+ std.os.STDOUT_FILENO, std.os.STDERR_FILENO => {
+ if (i == std.os.STDIN_FILENO) {
+ globalThis.throwInvalidArguments("stdout and stderr cannot be used for stdin", .{});
+ return false;
+ }
+ },
+ else => {},
+ }
+
+ stdio_array[i] = Stdio{ .fd = fd };
+
+ return true;
+ } else if (value.as(JSC.WebCore.Blob)) |blob| {
+ var store = blob.store orelse {
+ globalThis.throwInvalidArguments("Blob is detached (in stdio)", .{});
+ return false;
+ };
+
+ if (i == std.os.STDIN_FILENO and store.data == .bytes) {
+ stdio_array[i] = .{ .blob = blob.dupe() };
+ return true;
+ }
+
+ if (store.data != .file) {
+ globalThis.throwInvalidArguments("Blob is not a file (in stdio)", .{});
+ return false;
+ }
+
+ if (store.data.file.pathlike == .fd) {
+ if (store.data.file.pathlike.fd == @intCast(JSC.Node.FileDescriptor, i)) {
+ stdio_array[i] = Stdio{ .inherit = {} };
+ } else {
+ switch (@intCast(std.os.fd_t, i)) {
+ std.os.STDIN_FILENO => {
+ if (i == std.os.STDERR_FILENO or i == std.os.STDOUT_FILENO) {
+ globalThis.throwInvalidArguments("stdin cannot be used for stdout or stderr", .{});
+ return false;
+ }
+ },
+
+ std.os.STDOUT_FILENO, std.os.STDERR_FILENO => {
+ if (i == std.os.STDIN_FILENO) {
+ globalThis.throwInvalidArguments("stdout and stderr cannot be used for stdin", .{});
+ return false;
+ }
+ },
+ else => {},
+ }
+
+ stdio_array[i] = Stdio{ .fd = store.data.file.pathlike.fd };
+ }
+
+ return true;
+ }
+
+ stdio_array[i] = .{ .path = store.data.file.pathlike.path };
+ return true;
+ } else if (value.isCallable(globalThis.vm())) {
+ stdio_array[i] = .{ .callback = value };
+ value.ensureStillAlive();
+ return true;
+ }
+
+ globalThis.throwInvalidArguments("stdio must be an array of 'inherit', 'ignore', or null", .{});
+ return false;
+ }
+};