aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-09-24 19:03:31 -0700
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-09-25 13:14:23 -0700
commit9833841101c75c3d511a64daf32e8c273d7d928f (patch)
tree543d3de0426447161a991b0475aa749c1305f08d
parent1cd67b62e9d012c02d1534accf295014469be7e6 (diff)
downloadbun-9833841101c75c3d511a64daf32e8c273d7d928f.tar.gz
bun-9833841101c75c3d511a64daf32e8c273d7d928f.tar.zst
bun-9833841101c75c3d511a64daf32e8c273d7d928f.zip
wip
-rw-r--r--src/bun.js/api/bun.classes.ts51
-rw-r--r--src/bun.js/api/bun.zig761
-rw-r--r--src/bun.js/api/bun/spawn.zig256
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h3
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h3
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h6
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h7
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses.cpp379
-rw-r--r--src/bun.js/bindings/ZigGeneratedClasses.h127
-rw-r--r--src/bun.js/bindings/bindings.zig21
-rw-r--r--src/bun.js/bindings/generated_classes.zig94
-rw-r--r--src/bun.js/bindings/generated_classes_list.zig1
-rw-r--r--src/bun.js/builtins/cpp/ReadableStreamBuiltins.cpp4
-rw-r--r--src/bun.js/builtins/cpp/ReadableStreamInternalsBuiltins.cpp6
-rw-r--r--src/bun.js/builtins/js/ReadableStream.js2
-rw-r--r--src/bun.js/builtins/js/ReadableStreamInternals.js4
-rw-r--r--src/bun.js/event_loop.zig48
-rw-r--r--src/bun.js/javascript.zig1
-rw-r--r--src/bun.js/node/syscall.zig1
-rw-r--r--src/bun.js/webcore/streams.zig32
-rw-r--r--src/deps/uws.zig2
-rw-r--r--src/env_loader.zig20
-rw-r--r--src/jsc.zig1
-rw-r--r--test/bun.js/filesink.test.ts129
24 files changed, 1939 insertions, 20 deletions
diff --git a/src/bun.js/api/bun.classes.ts b/src/bun.js/api/bun.classes.ts
new file mode 100644
index 000000000..3a74549d2
--- /dev/null
+++ b/src/bun.js/api/bun.classes.ts
@@ -0,0 +1,51 @@
+import { define } from "../scripts/class-definitions";
+
+export default [
+ define({
+ name: "Subprocess",
+ construct: true,
+ finalize: true,
+ klass: {},
+ JSType: "0b11101110",
+ proto: {
+ pid: {
+ getter: "getPid",
+ },
+ stdin: {
+ getter: "getStdin",
+ cache: true,
+ },
+ stdout: {
+ getter: "getStdout",
+ cache: true,
+ },
+ stderr: {
+ getter: "getStderr",
+ cache: true,
+ },
+
+ ref: {
+ fn: "doRef",
+ length: 0,
+ },
+ unref: {
+ fn: "doUnref",
+ length: 0,
+ },
+
+ kill: {
+ fn: "kill",
+ length: 1,
+ },
+
+ killed: {
+ getter: "getKilled",
+ },
+
+ exitStatus: {
+ getter: "getExitStatus",
+ cache: true,
+ },
+ },
+ }),
+];
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;
+ }
+};
diff --git a/src/bun.js/api/bun/spawn.zig b/src/bun.js/api/bun/spawn.zig
new file mode 100644
index 000000000..95ce7fa73
--- /dev/null
+++ b/src/bun.js/api/bun/spawn.zig
@@ -0,0 +1,256 @@
+const JSC = @import("javascript_core");
+const bun = @import("../../../global.zig");
+const string = bun.string;
+const std = @import("std");
+const system = std.os.system;
+const Maybe = JSC.Node.Maybe;
+
+const fd_t = std.os.fd_t;
+const pid_t = std.os.pid_t;
+const toPosixPath = std.os.toPosixPath;
+const errno = std.os.errno;
+const mode_t = std.os.mode_t;
+const unexpectedErrno = std.os.unexpectedErrno;
+
+pub const WaitPidResult = struct {
+ pid: pid_t,
+ status: u32,
+};
+
+// mostly taken from zig's posix_spawn.zig
+pub const PosixSpawn = struct {
+ pub const Attr = struct {
+ attr: system.posix_spawnattr_t,
+
+ pub fn init() !Attr {
+ var attr: system.posix_spawnattr_t = undefined;
+ switch (errno(system.posix_spawnattr_init(&attr))) {
+ .SUCCESS => return Attr{ .attr = attr },
+ .NOMEM => return error.SystemResources,
+ .INVAL => unreachable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ pub fn deinit(self: *Attr) void {
+ system.posix_spawnattr_destroy(&self.attr);
+ self.* = undefined;
+ }
+
+ pub fn get(self: Attr) !u16 {
+ var flags: c_short = undefined;
+ switch (errno(system.posix_spawnattr_getflags(&self.attr, &flags))) {
+ .SUCCESS => return @bitCast(u16, flags),
+ .INVAL => unreachable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ pub fn set(self: *Attr, flags: u16) !void {
+ switch (errno(system.posix_spawnattr_setflags(&self.attr, @bitCast(c_short, flags)))) {
+ .SUCCESS => return,
+ .INVAL => unreachable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ };
+
+ pub const Actions = struct {
+ actions: system.posix_spawn_file_actions_t,
+
+ pub fn init() !Actions {
+ var actions: system.posix_spawn_file_actions_t = undefined;
+ switch (errno(system.posix_spawn_file_actions_init(&actions))) {
+ .SUCCESS => return Actions{ .actions = actions },
+ .NOMEM => return error.SystemResources,
+ .INVAL => unreachable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ pub fn deinit(self: *Actions) void {
+ system.posix_spawn_file_actions_destroy(&self.actions);
+ self.* = undefined;
+ }
+
+ pub fn open(self: *Actions, fd: fd_t, path: []const u8, flags: u32, mode: mode_t) !void {
+ const posix_path = try toPosixPath(path);
+ return self.openZ(fd, &posix_path, flags, mode);
+ }
+
+ pub fn openZ(self: *Actions, fd: fd_t, path: [*:0]const u8, flags: u32, mode: mode_t) !void {
+ switch (errno(system.posix_spawn_file_actions_addopen(&self.actions, fd, path, @bitCast(c_int, flags), mode))) {
+ .SUCCESS => return,
+ .BADF => return error.InvalidFileDescriptor,
+ .NOMEM => return error.SystemResources,
+ .NAMETOOLONG => return error.NameTooLong,
+ .INVAL => unreachable, // the value of file actions is invalid
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ pub fn close(self: *Actions, fd: fd_t) !void {
+ switch (errno(system.posix_spawn_file_actions_addclose(&self.actions, fd))) {
+ .SUCCESS => return,
+ .BADF => return error.InvalidFileDescriptor,
+ .NOMEM => return error.SystemResources,
+ .INVAL => unreachable, // the value of file actions is invalid
+ .NAMETOOLONG => unreachable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ pub fn dup2(self: *Actions, fd: fd_t, newfd: fd_t) !void {
+ switch (errno(system.posix_spawn_file_actions_adddup2(&self.actions, fd, newfd))) {
+ .SUCCESS => return,
+ .BADF => return error.InvalidFileDescriptor,
+ .NOMEM => return error.SystemResources,
+ .INVAL => unreachable, // the value of file actions is invalid
+ .NAMETOOLONG => unreachable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ pub fn inherit(self: *Actions, fd: fd_t) !void {
+ switch (errno(system.posix_spawn_file_actions_addinherit_np(&self.actions, fd))) {
+ .SUCCESS => return,
+ .BADF => return error.InvalidFileDescriptor,
+ .NOMEM => return error.SystemResources,
+ .INVAL => unreachable, // the value of file actions is invalid
+ .NAMETOOLONG => unreachable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ pub fn chdir(self: *Actions, path: []const u8) !void {
+ const posix_path = try toPosixPath(path);
+ return self.chdirZ(&posix_path);
+ }
+
+ pub fn chdirZ(self: *Actions, path: [*:0]const u8) !void {
+ switch (errno(system.posix_spawn_file_actions_addchdir_np(&self.actions, path))) {
+ .SUCCESS => return,
+ .NOMEM => return error.SystemResources,
+ .NAMETOOLONG => return error.NameTooLong,
+ .BADF => unreachable,
+ .INVAL => unreachable, // the value of file actions is invalid
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ pub fn fchdir(self: *Actions, fd: fd_t) !void {
+ switch (errno(system.posix_spawn_file_actions_addfchdir_np(&self.actions, fd))) {
+ .SUCCESS => return,
+ .BADF => return error.InvalidFileDescriptor,
+ .NOMEM => return error.SystemResources,
+ .INVAL => unreachable, // the value of file actions is invalid
+ .NAMETOOLONG => unreachable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+ };
+
+ pub fn spawn(
+ path: []const u8,
+ actions: ?Actions,
+ attr: ?Attr,
+ argv: [*:null]?[*:0]const u8,
+ envp: [*:null]?[*:0]const u8,
+ ) !pid_t {
+ const posix_path = try toPosixPath(path);
+ return spawnZ(&posix_path, actions, attr, argv, envp);
+ }
+
+ pub fn spawnZ(
+ path: [*:0]const u8,
+ actions: ?Actions,
+ attr: ?Attr,
+ argv: [*:null]?[*:0]const u8,
+ envp: [*:null]?[*:0]const u8,
+ ) Maybe(pid_t) {
+ var pid: pid_t = undefined;
+ const rc = system.posix_spawn(
+ &pid,
+ path,
+ if (actions) |a| &a.actions else null,
+ if (attr) |a| &a.attr else null,
+ argv,
+ envp,
+ );
+
+ if (Maybe(pid_t).errno(rc)) |err| {
+ return err;
+ }
+
+ return Maybe(pid_t){ .result = pid };
+ }
+
+ pub fn spawnp(
+ file: []const u8,
+ actions: ?Actions,
+ attr: ?Attr,
+ argv: [*:null]?[*:0]const u8,
+ envp: [*:null]?[*:0]const u8,
+ ) !pid_t {
+ const posix_file = try toPosixPath(file);
+ return spawnpZ(&posix_file, actions, attr, argv, envp);
+ }
+
+ pub fn spawnpZ(
+ file: [*:0]const u8,
+ actions: ?Actions,
+ attr: ?Attr,
+ argv: [*:null]?[*:0]const u8,
+ envp: [*:null]?[*:0]const u8,
+ ) !pid_t {
+ var pid: pid_t = undefined;
+ switch (errno(system.posix_spawnp(
+ &pid,
+ file,
+ if (actions) |a| &a.actions else null,
+ if (attr) |a| &a.attr else null,
+ argv,
+ envp,
+ ))) {
+ .SUCCESS => return pid,
+ .@"2BIG" => return error.TooBig,
+ .NOMEM => return error.SystemResources,
+ .BADF => return error.InvalidFileDescriptor,
+ .ACCES => return error.PermissionDenied,
+ .IO => return error.InputOutput,
+ .LOOP => return error.FileSystem,
+ .NAMETOOLONG => return error.NameTooLong,
+ .NOENT => return error.FileNotFound,
+ .NOEXEC => return error.InvalidExe,
+ .NOTDIR => return error.NotDir,
+ .TXTBSY => return error.FileBusy,
+ .BADARCH => return error.InvalidExe,
+ .BADEXEC => return error.InvalidExe,
+ .FAULT => unreachable,
+ .INVAL => unreachable,
+ else => |err| return unexpectedErrno(err),
+ }
+ }
+
+ /// Use this version of the `waitpid` wrapper if you spawned your child process using `posix_spawn`
+ /// or `posix_spawnp` syscalls.
+ /// See also `std.os.waitpid` for an alternative if your child process was spawned via `fork` and
+ /// `execve` method.
+ pub fn waitpid(pid: pid_t, flags: u32) !WaitPidResult {
+ const Status = c_int;
+ var status: Status = undefined;
+ while (true) {
+ const rc = system.waitpid(pid, &status, @intCast(c_int, flags));
+ switch (errno(rc)) {
+ .SUCCESS => return WaitPidResult{
+ .pid = @intCast(pid_t, rc),
+ .status = @bitCast(u32, status),
+ },
+ .INTR => continue,
+ .CHILD => return error.ChildExecFailed,
+ .INVAL => unreachable, // Invalid flags.
+ else => unreachable,
+ }
+ }
+ }
+};
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
index b4d389d46..cd67081d1 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMClientIsoSubspaces.h
@@ -1,4 +1,5 @@
-std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSHA1;
+std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSubprocess;
+std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSubprocessConstructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSHA1;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSHA1Constructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMD5;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMD5Constructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMD4;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForMD4Constructor;std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForSHA224;
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
index b23b98853..4d1104f99 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+DOMIsoSubspaces.h
@@ -1,4 +1,5 @@
-std::unique_ptr<IsoSubspace> m_subspaceForSHA1;
+std::unique_ptr<IsoSubspace> m_subspaceForSubprocess;
+std::unique_ptr<IsoSubspace> m_subspaceForSubprocessConstructor;std::unique_ptr<IsoSubspace> m_subspaceForSHA1;
std::unique_ptr<IsoSubspace> m_subspaceForSHA1Constructor;std::unique_ptr<IsoSubspace> m_subspaceForMD5;
std::unique_ptr<IsoSubspace> m_subspaceForMD5Constructor;std::unique_ptr<IsoSubspace> m_subspaceForMD4;
std::unique_ptr<IsoSubspace> m_subspaceForMD4Constructor;std::unique_ptr<IsoSubspace> m_subspaceForSHA224;
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
index 31100e3a4..c37518fe5 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h
@@ -1,3 +1,9 @@
+JSC::Structure* JSSubprocessStructure() { return m_JSSubprocess.getInitializedOnMainThread(this); }
+ JSC::JSObject* JSSubprocessConstructor() { return m_JSSubprocess.constructorInitializedOnMainThread(this); }
+ JSC::JSValue JSSubprocessPrototype() { return m_JSSubprocess.prototypeInitializedOnMainThread(this); }
+ JSC::LazyClassStructure m_JSSubprocess;
+ bool hasJSSubprocessSetterValue { false };
+ mutable JSC::WriteBarrier<JSC::Unknown> m_JSSubprocessSetterValue;
JSC::Structure* JSSHA1Structure() { return m_JSSHA1.getInitializedOnMainThread(this); }
JSC::JSObject* JSSHA1Constructor() { return m_JSSHA1.constructorInitializedOnMainThread(this); }
JSC::JSValue JSSHA1Prototype() { return m_JSSHA1.prototypeInitializedOnMainThread(this); }
diff --git a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
index d5d28ec81..1b6ac7ade 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h
@@ -1,4 +1,10 @@
void GlobalObject::initGeneratedLazyClasses() {
+ m_JSSubprocess.initLater(
+ [](LazyClassStructure::Initializer& init) {
+ init.setPrototype(WebCore::JSSubprocess::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global)));
+ init.setStructure(WebCore::JSSubprocess::createStructure(init.vm, init.global, init.prototype));
+ init.setConstructor(WebCore::JSSubprocessConstructor::create(init.vm, init.global, WebCore::JSSubprocessConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()), jsCast<WebCore::JSSubprocessPrototype*>(init.prototype)));
+ });
m_JSSHA1.initLater(
[](LazyClassStructure::Initializer& init) {
init.setPrototype(WebCore::JSSHA1::createPrototype(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.global)));
@@ -75,6 +81,7 @@ void GlobalObject::initGeneratedLazyClasses() {
template<typename Visitor>
void GlobalObject::visitGeneratedLazyClasses(GlobalObject *thisObject, Visitor& visitor)
{
+ thisObject->m_JSSubprocess.visit(visitor); visitor.append(thisObject->m_JSSubprocessSetterValue);
thisObject->m_JSSHA1.visit(visitor); visitor.append(thisObject->m_JSSHA1SetterValue);
thisObject->m_JSMD5.visit(visitor); visitor.append(thisObject->m_JSMD5SetterValue);
thisObject->m_JSMD4.visit(visitor); visitor.append(thisObject->m_JSMD4SetterValue);
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.cpp b/src/bun.js/bindings/ZigGeneratedClasses.cpp
index 90c628c01..78f1bb179 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses.cpp
+++ b/src/bun.js/bindings/ZigGeneratedClasses.cpp
@@ -23,7 +23,384 @@ namespace WebCore {
using namespace JSC;
using namespace Zig;
-extern "C" void* SHA1Class__construct(JSC::JSGlobalObject*, JSC::CallFrame*);
+extern "C" void* SubprocessClass__construct(JSC::JSGlobalObject*, JSC::CallFrame*);
+JSC_DECLARE_CUSTOM_GETTER(jsSubprocessConstructor);
+extern "C" void SubprocessClass__finalize(void*);
+
+extern "C" JSC::EncodedJSValue SubprocessPrototype__getExitStatus(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject);
+JSC_DECLARE_CUSTOM_GETTER(SubprocessPrototype__exitStatusGetterWrap);
+
+
+extern "C" EncodedJSValue SubprocessPrototype__kill(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(SubprocessPrototype__killCallback);
+
+
+extern "C" JSC::EncodedJSValue SubprocessPrototype__getKilled(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject);
+JSC_DECLARE_CUSTOM_GETTER(SubprocessPrototype__killedGetterWrap);
+
+
+extern "C" JSC::EncodedJSValue SubprocessPrototype__getPid(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject);
+JSC_DECLARE_CUSTOM_GETTER(SubprocessPrototype__pidGetterWrap);
+
+
+extern "C" EncodedJSValue SubprocessPrototype__doRef(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(SubprocessPrototype__refCallback);
+
+
+extern "C" JSC::EncodedJSValue SubprocessPrototype__getStderr(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject);
+JSC_DECLARE_CUSTOM_GETTER(SubprocessPrototype__stderrGetterWrap);
+
+
+extern "C" JSC::EncodedJSValue SubprocessPrototype__getStdin(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject);
+JSC_DECLARE_CUSTOM_GETTER(SubprocessPrototype__stdinGetterWrap);
+
+
+extern "C" JSC::EncodedJSValue SubprocessPrototype__getStdout(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject);
+JSC_DECLARE_CUSTOM_GETTER(SubprocessPrototype__stdoutGetterWrap);
+
+
+extern "C" EncodedJSValue SubprocessPrototype__doUnref(void* ptr, JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame);
+JSC_DECLARE_HOST_FUNCTION(SubprocessPrototype__unrefCallback);
+
+
+STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSSubprocessPrototype, JSSubprocessPrototype::Base);
+
+
+ static const HashTableValue JSSubprocessPrototypeTableValues[] = {
+{ "exitStatus"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, SubprocessPrototype__exitStatusGetterWrap, 0 } } ,
+{ "kill"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, SubprocessPrototype__killCallback, 1 } } ,
+{ "killed"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, SubprocessPrototype__killedGetterWrap, 0 } } ,
+{ "pid"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, SubprocessPrototype__pidGetterWrap, 0 } } ,
+{ "ref"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, SubprocessPrototype__refCallback, 0 } } ,
+{ "stderr"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, SubprocessPrototype__stderrGetterWrap, 0 } } ,
+{ "stdin"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, SubprocessPrototype__stdinGetterWrap, 0 } } ,
+{ "stdout"_s, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DOMAttribute), NoIntrinsic, { HashTableValue::GetterSetterType, SubprocessPrototype__stdoutGetterWrap, 0 } } ,
+{ "unref"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, SubprocessPrototype__unrefCallback, 0 } }
+ };
+
+
+const ClassInfo JSSubprocessPrototype::s_info = { "Subprocess"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSSubprocessPrototype) };
+
+
+
+JSC_DEFINE_CUSTOM_GETTER(jsSubprocessConstructor, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName))
+{
+ VM& vm = JSC::getVM(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ auto* globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ auto* prototype = jsDynamicCast<JSSubprocessPrototype*>(JSValue::decode(thisValue));
+
+ if (UNLIKELY(!prototype))
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ return JSValue::encode(globalObject->JSSubprocessConstructor());
+}
+
+
+
+JSC_DEFINE_CUSTOM_GETTER(SubprocessPrototype__exitStatusGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
+{
+ auto& vm = lexicalGlobalObject->vm();
+ Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ JSSubprocess* thisObject = jsCast<JSSubprocess*>(JSValue::decode(thisValue));
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+ if (JSValue cachedValue = thisObject->m_exitStatus.get())
+ return JSValue::encode(cachedValue);
+
+ JSC::JSValue result = JSC::JSValue::decode(
+ SubprocessPrototype__getExitStatus(thisObject->wrapped(), globalObject)
+ );
+ RETURN_IF_EXCEPTION(throwScope, {});
+ thisObject->m_exitStatus.set(vm, thisObject, result);
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
+}
+
+
+JSC_DEFINE_HOST_FUNCTION(SubprocessPrototype__killCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ JSSubprocess* thisObject = jsDynamicCast<JSSubprocess*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+ return SubprocessPrototype__kill(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
+
+JSC_DEFINE_CUSTOM_GETTER(SubprocessPrototype__killedGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
+{
+ auto& vm = lexicalGlobalObject->vm();
+ Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ JSSubprocess* thisObject = jsCast<JSSubprocess*>(JSValue::decode(thisValue));
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+ JSC::EncodedJSValue result = SubprocessPrototype__getKilled(thisObject->wrapped(), globalObject);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ RELEASE_AND_RETURN(throwScope, result);
+}
+
+
+JSC_DEFINE_CUSTOM_GETTER(SubprocessPrototype__pidGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
+{
+ auto& vm = lexicalGlobalObject->vm();
+ Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ JSSubprocess* thisObject = jsCast<JSSubprocess*>(JSValue::decode(thisValue));
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+ JSC::EncodedJSValue result = SubprocessPrototype__getPid(thisObject->wrapped(), globalObject);
+ RETURN_IF_EXCEPTION(throwScope, {});
+ RELEASE_AND_RETURN(throwScope, result);
+}
+
+
+JSC_DEFINE_HOST_FUNCTION(SubprocessPrototype__refCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ JSSubprocess* thisObject = jsDynamicCast<JSSubprocess*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+ return SubprocessPrototype__doRef(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
+
+JSC_DEFINE_CUSTOM_GETTER(SubprocessPrototype__stderrGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
+{
+ auto& vm = lexicalGlobalObject->vm();
+ Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ JSSubprocess* thisObject = jsCast<JSSubprocess*>(JSValue::decode(thisValue));
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+ if (JSValue cachedValue = thisObject->m_stderr.get())
+ return JSValue::encode(cachedValue);
+
+ JSC::JSValue result = JSC::JSValue::decode(
+ SubprocessPrototype__getStderr(thisObject->wrapped(), globalObject)
+ );
+ RETURN_IF_EXCEPTION(throwScope, {});
+ thisObject->m_stderr.set(vm, thisObject, result);
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
+}
+
+
+JSC_DEFINE_CUSTOM_GETTER(SubprocessPrototype__stdinGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
+{
+ auto& vm = lexicalGlobalObject->vm();
+ Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ JSSubprocess* thisObject = jsCast<JSSubprocess*>(JSValue::decode(thisValue));
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+ if (JSValue cachedValue = thisObject->m_stdin.get())
+ return JSValue::encode(cachedValue);
+
+ JSC::JSValue result = JSC::JSValue::decode(
+ SubprocessPrototype__getStdin(thisObject->wrapped(), globalObject)
+ );
+ RETURN_IF_EXCEPTION(throwScope, {});
+ thisObject->m_stdin.set(vm, thisObject, result);
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
+}
+
+
+JSC_DEFINE_CUSTOM_GETTER(SubprocessPrototype__stdoutGetterWrap, (JSGlobalObject * lexicalGlobalObject, EncodedJSValue thisValue, PropertyName attributeName))
+{
+ auto& vm = lexicalGlobalObject->vm();
+ Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ JSSubprocess* thisObject = jsCast<JSSubprocess*>(JSValue::decode(thisValue));
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+ if (JSValue cachedValue = thisObject->m_stdout.get())
+ return JSValue::encode(cachedValue);
+
+ JSC::JSValue result = JSC::JSValue::decode(
+ SubprocessPrototype__getStdout(thisObject->wrapped(), globalObject)
+ );
+ RETURN_IF_EXCEPTION(throwScope, {});
+ thisObject->m_stdout.set(vm, thisObject, result);
+ RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
+}
+
+
+JSC_DEFINE_HOST_FUNCTION(SubprocessPrototype__unrefCallback, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
+{
+ auto& vm = lexicalGlobalObject->vm();
+
+ JSSubprocess* thisObject = jsDynamicCast<JSSubprocess*>(callFrame->thisValue());
+
+ if (UNLIKELY(!thisObject)) {
+ auto throwScope = DECLARE_THROW_SCOPE(vm);
+ return throwVMTypeError(lexicalGlobalObject, throwScope);
+ }
+
+ JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
+
+ return SubprocessPrototype__doUnref(thisObject->wrapped(), lexicalGlobalObject, callFrame);
+}
+
+
+void JSSubprocessPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
+{
+ Base::finishCreation(vm);
+ reifyStaticProperties(vm, JSSubprocess::info(), JSSubprocessPrototypeTableValues, *this);
+ JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
+}
+
+void JSSubprocessConstructor::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSSubprocessPrototype* prototype)
+{
+ Base::finishCreation(vm, 0, "Subprocess"_s, PropertyAdditionMode::WithoutStructureTransition);
+
+ putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly);
+ ASSERT(inherits(info()));
+}
+
+JSSubprocessConstructor* JSSubprocessConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSSubprocessPrototype* prototype) {
+ JSSubprocessConstructor* ptr = new (NotNull, JSC::allocateCell<JSSubprocessConstructor>(vm)) JSSubprocessConstructor(vm, structure, construct);
+ ptr->finishCreation(vm, globalObject, prototype);
+ return ptr;
+}
+
+JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES JSSubprocessConstructor::construct(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame)
+{
+ Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
+ JSC::VM &vm = globalObject->vm();
+ JSObject* newTarget = asObject(callFrame->newTarget());
+ auto* constructor = globalObject->JSSubprocessConstructor();
+ Structure* structure = globalObject->JSSubprocessStructure();
+ if (constructor != newTarget) {
+ auto scope = DECLARE_THROW_SCOPE(vm);
+
+ auto* functionGlobalObject = reinterpret_cast<Zig::GlobalObject*>(
+ // ShadowRealm functions belong to a different global object.
+ getFunctionRealm(globalObject, newTarget)
+ );
+ RETURN_IF_EXCEPTION(scope, {});
+ structure = InternalFunction::createSubclassStructure(
+ globalObject,
+ newTarget,
+ functionGlobalObject->JSSubprocessStructure()
+ );
+ }
+
+ void* ptr = SubprocessClass__construct(globalObject, callFrame);
+
+ if (UNLIKELY(!ptr)) {
+ return JSValue::encode(JSC::jsUndefined());
+ }
+
+ JSSubprocess* instance = JSSubprocess::create(vm, globalObject, structure, ptr);
+
+ return JSValue::encode(instance);
+}
+
+extern "C" EncodedJSValue Subprocess__create(Zig::GlobalObject* globalObject, void* ptr) {
+ auto &vm = globalObject->vm();
+ JSC::Structure* structure = globalObject->JSSubprocessStructure();
+ JSSubprocess* instance = JSSubprocess::create(vm, globalObject, structure, ptr);
+ return JSValue::encode(instance);
+}
+
+void JSSubprocessConstructor::initializeProperties(VM& vm, JSC::JSGlobalObject* globalObject, JSSubprocessPrototype* prototype)
+{
+
+}
+
+const ClassInfo JSSubprocessConstructor::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSSubprocessConstructor) };
+
+
+ extern "C" EncodedJSValue Subprocess__getConstructor(Zig::GlobalObject* globalObject) {
+ return JSValue::encode(globalObject->JSSubprocessConstructor());
+ }
+
+JSSubprocess::~JSSubprocess()
+{
+ if (m_ctx) {
+ SubprocessClass__finalize(m_ctx);
+ }
+}
+void JSSubprocess::destroy(JSCell* cell)
+{
+ static_cast<JSSubprocess*>(cell)->JSSubprocess::~JSSubprocess();
+}
+
+const ClassInfo JSSubprocess::s_info = { "Subprocess"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSSubprocess) };
+
+void JSSubprocess::finishCreation(VM& vm)
+{
+ Base::finishCreation(vm);
+ ASSERT(inherits(info()));
+}
+
+JSSubprocess* JSSubprocess::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx) {
+ JSSubprocess* ptr = new (NotNull, JSC::allocateCell<JSSubprocess>(vm)) JSSubprocess(vm, structure, ctx);
+ ptr->finishCreation(vm);
+ return ptr;
+}
+
+
+extern "C" void* Subprocess__fromJS(JSC::EncodedJSValue value) {
+ JSSubprocess* object = JSC::jsDynamicCast<JSSubprocess*>(JSValue::decode(value));
+ if (!object)
+ return nullptr;
+
+ return object->wrapped();
+}
+
+extern "C" bool Subprocess__dangerouslySetPtr(JSC::EncodedJSValue value, void* ptr) {
+ JSSubprocess* object = JSC::jsDynamicCast<JSSubprocess*>(JSValue::decode(value));
+ if (!object)
+ return false;
+
+ object->m_ctx = ptr;
+ return true;
+}
+
+
+extern "C" const size_t Subprocess__ptrOffset = JSSubprocess::offsetOfWrapped();
+
+void JSSubprocess::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer)
+{
+ auto* thisObject = jsCast<JSSubprocess*>(cell);
+ if (void* wrapped = thisObject->wrapped()) {
+ // if (thisObject->scriptExecutionContext())
+ // analyzer.setLabelForCell(cell, "url " + thisObject->scriptExecutionContext()->url().string());
+ }
+ Base::analyzeHeap(cell, analyzer);
+}
+
+JSObject* JSSubprocess::createPrototype(VM& vm, JSDOMGlobalObject* globalObject)
+{
+ return JSSubprocessPrototype::create(vm, globalObject, JSSubprocessPrototype::createStructure(vm, globalObject, globalObject->objectPrototype()));
+}
+
+template<typename Visitor>
+void JSSubprocess::visitChildrenImpl(JSCell* cell, Visitor& visitor)
+{
+ JSSubprocess* thisObject = jsCast<JSSubprocess*>(cell);
+ ASSERT_GC_OBJECT_INHERITS(thisObject, info());
+ Base::visitChildren(thisObject, visitor);
+ visitor.append(thisObject->m_exitStatus);
+ visitor.append(thisObject->m_stderr);
+ visitor.append(thisObject->m_stdin);
+ visitor.append(thisObject->m_stdout);
+}
+
+DEFINE_VISIT_CHILDREN(JSSubprocess);extern "C" void* SHA1Class__construct(JSC::JSGlobalObject*, JSC::CallFrame*);
JSC_DECLARE_CUSTOM_GETTER(jsSHA1Constructor);
extern "C" void SHA1Class__finalize(void*);
diff --git a/src/bun.js/bindings/ZigGeneratedClasses.h b/src/bun.js/bindings/ZigGeneratedClasses.h
index e315093c7..feae81ae0 100644
--- a/src/bun.js/bindings/ZigGeneratedClasses.h
+++ b/src/bun.js/bindings/ZigGeneratedClasses.h
@@ -15,6 +15,133 @@ namespace WebCore {
using namespace Zig;
using namespace JSC;
+class JSSubprocess final : public JSC::JSDestructibleObject {
+ public:
+ using Base = JSC::JSDestructibleObject;
+ static JSSubprocess* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, void* ctx);
+
+ DECLARE_EXPORT_INFO;
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+ return WebCore::subspaceForImpl<JSSubprocess, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForSubprocess.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForSubprocess = WTFMove(space); },
+ [](auto& spaces) { return spaces.m_subspaceForSubprocess.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForSubprocess = WTFMove(space); });
+ }
+
+ static void destroy(JSC::JSCell*);
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(static_cast<JSC::JSType>(0b11101110), StructureFlags), info());
+ }
+
+ static JSObject* createPrototype(VM& vm, JSDOMGlobalObject* globalObject);
+
+ ~JSSubprocess();
+
+ void* wrapped() const { return m_ctx; }
+
+ void detach()
+ {
+ m_ctx = nullptr;
+ }
+
+ static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
+ static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSSubprocess, m_ctx); }
+
+ void* m_ctx { nullptr };
+
+
+ JSSubprocess(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr)
+ : Base(vm, structure)
+ {
+ m_ctx = sinkPtr;
+ }
+
+ void finishCreation(JSC::VM&);
+
+ DECLARE_VISIT_CHILDREN;
+
+ mutable JSC::WriteBarrier<JSC::Unknown> m_exitStatus;
+mutable JSC::WriteBarrier<JSC::Unknown> m_stderr;
+mutable JSC::WriteBarrier<JSC::Unknown> m_stdin;
+mutable JSC::WriteBarrier<JSC::Unknown> m_stdout;
+ };
+class JSSubprocessPrototype final : public JSC::JSNonFinalObject {
+ public:
+ using Base = JSC::JSNonFinalObject;
+
+ static JSSubprocessPrototype* create(JSC::VM& vm, JSGlobalObject* globalObject, JSC::Structure* structure)
+ {
+ JSSubprocessPrototype* ptr = new (NotNull, JSC::allocateCell<JSSubprocessPrototype>(vm)) JSSubprocessPrototype(vm, globalObject, structure);
+ ptr->finishCreation(vm, globalObject);
+ return ptr;
+ }
+
+ DECLARE_INFO;
+ template<typename CellType, JSC::SubspaceAccess>
+ static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ return &vm.plainObjectSpace();
+ }
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
+ }
+
+ private:
+ JSSubprocessPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
+ : Base(vm, structure)
+ {
+ }
+
+ void finishCreation(JSC::VM&, JSC::JSGlobalObject*);
+ };
+
+ class JSSubprocessConstructor final : public JSC::InternalFunction {
+ public:
+ using Base = JSC::InternalFunction;
+ static JSSubprocessConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSSubprocessPrototype* prototype);
+
+ static constexpr unsigned StructureFlags = Base::StructureFlags;
+ static constexpr bool needsDestruction = false;
+
+ static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
+ {
+ return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
+ }
+
+ template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
+ {
+ if constexpr (mode == JSC::SubspaceAccess::Concurrently)
+ return nullptr;
+ return WebCore::subspaceForImpl<JSSubprocessConstructor, WebCore::UseCustomHeapCellType::No>(
+ vm,
+ [](auto& spaces) { return spaces.m_clientSubspaceForSubprocessConstructor.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForSubprocessConstructor = WTFMove(space); },
+ [](auto& spaces) { return spaces.m_subspaceForSubprocessConstructor.get(); },
+ [](auto& spaces, auto&& space) { spaces.m_subspaceForSubprocessConstructor = WTFMove(space); });
+ }
+
+
+ void initializeProperties(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSSubprocessPrototype* prototype);
+
+ // Must be defined for each specialization class.
+ static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*);
+ DECLARE_EXPORT_INFO;
+ private:
+ JSSubprocessConstructor(JSC::VM& vm, JSC::Structure* structure, JSC::NativeFunction nativeFunction)
+ : Base(vm, structure, nativeFunction, nativeFunction)
+
+ {
+ }
+
+ void finishCreation(JSC::VM&, JSC::JSGlobalObject* globalObject, JSSubprocessPrototype* prototype);
+ };
class JSSHA1 final : public JSC::JSDestructibleObject {
public:
using Base = JSC::JSDestructibleObject;
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index f737c783e..3b41c9fa4 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -1896,6 +1896,27 @@ pub const JSGlobalObject = extern struct {
}
}
+ pub fn throwError(
+ this: *JSGlobalObject,
+ err: anyerror,
+ comptime fmt: string,
+ ) void {
+ var str = ZigString.init(std.fmt.allocPrint(this.bunVM().allocator, "{s} " ++ fmt, .{@errorName(err)}) catch return);
+ str.markUTF8();
+ var err_value = str.toErrorInstance(this);
+ this.vm().throwError(this, err_value);
+ this.bunVM().allocator.free(ZigString.untagged(str.ptr)[0..str.len]);
+ }
+
+ pub fn handleError(
+ this: *JSGlobalObject,
+ err: anyerror,
+ comptime fmt: string,
+ ) JSValue {
+ this.throwError(err, fmt);
+ return JSValue.jsUndefined();
+ }
+
// pub fn createError(globalObject: *JSGlobalObject, error_type: ErrorType, message: *String) *JSObject {
// return cppFn("createError", .{ globalObject, error_type, message });
// }
diff --git a/src/bun.js/bindings/generated_classes.zig b/src/bun.js/bindings/generated_classes.zig
index 494ad1270..425d70a44 100644
--- a/src/bun.js/bindings/generated_classes.zig
+++ b/src/bun.js/bindings/generated_classes.zig
@@ -7,6 +7,99 @@ pub const StaticGetterType = fn (*JSC.JSGlobalObject, JSC.JSValue, JSC.JSValue)
pub const StaticSetterType = fn (*JSC.JSGlobalObject, JSC.JSValue, JSC.JSValue, JSC.JSValue) callconv(.C) bool;
pub const StaticCallbackType = fn (*JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue;
+pub const JSSubprocess = struct {
+ const Subprocess = Classes.Subprocess;
+ const GetterType = fn (*Subprocess, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
+ const SetterType = fn (*Subprocess, *JSC.JSGlobalObject, JSC.JSValue) callconv(.C) bool;
+ const CallbackType = fn (*Subprocess, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) JSC.JSValue;
+
+ /// Return the pointer to the wrapped object.
+ /// If the object does not match the type, return null.
+ pub fn fromJS(value: JSC.JSValue) ?*Subprocess {
+ JSC.markBinding();
+ return Subprocess__fromJS(value);
+ }
+
+ /// Get the Subprocess constructor value.
+ /// This loads lazily from the global object.
+ pub fn getConstructor(globalObject: *JSC.JSGlobalObject) JSC.JSValue {
+ JSC.markBinding();
+ return Subprocess__getConstructor(globalObject);
+ }
+
+ /// Create a new instance of Subprocess
+ pub fn toJS(this: *Subprocess, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
+ JSC.markBinding();
+ if (comptime Environment.allow_assert) {
+ const value__ = Subprocess__create(globalObject, this);
+ std.debug.assert(value__.as(Subprocess).? == this); // If this fails, likely a C ABI issue.
+ return value__;
+ } else {
+ return Subprocess__create(globalObject, this);
+ }
+ }
+
+ /// Modify the internal ptr to point to a new instance of Subprocess.
+ pub fn dangerouslySetPtr(value: JSC.JSValue, ptr: ?*Subprocess) bool {
+ JSC.markBinding();
+ return Subprocess__dangerouslySetPtr(value, ptr);
+ }
+
+ extern fn Subprocess__fromJS(JSC.JSValue) ?*Subprocess;
+ extern fn Subprocess__getConstructor(*JSC.JSGlobalObject) JSC.JSValue;
+
+ extern fn Subprocess__create(globalObject: *JSC.JSGlobalObject, ptr: ?*Subprocess) JSC.JSValue;
+
+ extern fn Subprocess__dangerouslySetPtr(JSC.JSValue, ?*Subprocess) bool;
+
+ comptime {
+ if (@TypeOf(Subprocess.constructor) != (fn (*JSC.JSGlobalObject, *JSC.CallFrame) callconv(.C) ?*Subprocess)) {
+ @compileLog("Subprocess.constructor is not a constructor");
+ }
+
+ if (@TypeOf(Subprocess.finalize) != (fn (*Subprocess) callconv(.C) void)) {
+ @compileLog("Subprocess.finalize is not a finalizer");
+ }
+
+ if (@TypeOf(Subprocess.getExitStatus) != GetterType)
+ @compileLog("Expected Subprocess.getExitStatus to be a getter");
+
+ if (@TypeOf(Subprocess.kill) != CallbackType)
+ @compileLog("Expected Subprocess.kill to be a callback");
+ if (@TypeOf(Subprocess.getKilled) != GetterType)
+ @compileLog("Expected Subprocess.getKilled to be a getter");
+
+ if (@TypeOf(Subprocess.getPid) != GetterType)
+ @compileLog("Expected Subprocess.getPid to be a getter");
+
+ if (@TypeOf(Subprocess.doRef) != CallbackType)
+ @compileLog("Expected Subprocess.doRef to be a callback");
+ if (@TypeOf(Subprocess.getStderr) != GetterType)
+ @compileLog("Expected Subprocess.getStderr to be a getter");
+
+ if (@TypeOf(Subprocess.getStdin) != GetterType)
+ @compileLog("Expected Subprocess.getStdin to be a getter");
+
+ if (@TypeOf(Subprocess.getStdout) != GetterType)
+ @compileLog("Expected Subprocess.getStdout to be a getter");
+
+ if (@TypeOf(Subprocess.doUnref) != CallbackType)
+ @compileLog("Expected Subprocess.doUnref to be a callback");
+ if (!JSC.is_bindgen) {
+ @export(Subprocess.constructor, .{ .name = "SubprocessClass__construct" });
+ @export(Subprocess.doRef, .{ .name = "SubprocessPrototype__doRef" });
+ @export(Subprocess.doUnref, .{ .name = "SubprocessPrototype__doUnref" });
+ @export(Subprocess.finalize, .{ .name = "SubprocessClass__finalize" });
+ @export(Subprocess.getExitStatus, .{ .name = "SubprocessPrototype__getExitStatus" });
+ @export(Subprocess.getKilled, .{ .name = "SubprocessPrototype__getKilled" });
+ @export(Subprocess.getPid, .{ .name = "SubprocessPrototype__getPid" });
+ @export(Subprocess.getStderr, .{ .name = "SubprocessPrototype__getStderr" });
+ @export(Subprocess.getStdin, .{ .name = "SubprocessPrototype__getStdin" });
+ @export(Subprocess.getStdout, .{ .name = "SubprocessPrototype__getStdout" });
+ @export(Subprocess.kill, .{ .name = "SubprocessPrototype__kill" });
+ }
+ }
+};
pub const JSSHA1 = struct {
const SHA1 = Classes.SHA1;
const GetterType = fn (*SHA1, *JSC.JSGlobalObject) callconv(.C) JSC.JSValue;
@@ -1020,6 +1113,7 @@ pub const JSBlob = struct {
};
comptime {
+ _ = JSSubprocess;
_ = JSSHA1;
_ = JSMD5;
_ = JSMD4;
diff --git a/src/bun.js/bindings/generated_classes_list.zig b/src/bun.js/bindings/generated_classes_list.zig
index f90cc95d4..dbfa6c792 100644
--- a/src/bun.js/bindings/generated_classes_list.zig
+++ b/src/bun.js/bindings/generated_classes_list.zig
@@ -13,4 +13,5 @@ pub const Classes = struct {
pub const SHA512_256 = JSC.API.Bun.Crypto.SHA512_256;
pub const TextDecoder = JSC.WebCore.TextDecoder;
pub const Blob = JSC.WebCore.Blob;
+ pub const Subprocess = JSC.Subprocess;
};
diff --git a/src/bun.js/builtins/cpp/ReadableStreamBuiltins.cpp b/src/bun.js/builtins/cpp/ReadableStreamBuiltins.cpp
index 2b0797381..830fbfd51 100644
--- a/src/bun.js/builtins/cpp/ReadableStreamBuiltins.cpp
+++ b/src/bun.js/builtins/cpp/ReadableStreamBuiltins.cpp
@@ -214,7 +214,7 @@ const char* const s_readableStreamReadableStreamToBlobCode =
const JSC::ConstructAbility s_readableStreamConsumeReadableStreamCodeConstructAbility = JSC::ConstructAbility::CannotConstruct;
const JSC::ConstructorKind s_readableStreamConsumeReadableStreamCodeConstructorKind = JSC::ConstructorKind::None;
const JSC::ImplementationVisibility s_readableStreamConsumeReadableStreamCodeImplementationVisibility = JSC::ImplementationVisibility::Private;
-const int s_readableStreamConsumeReadableStreamCodeLength = 3718;
+const int s_readableStreamConsumeReadableStreamCodeLength = 3736;
static const JSC::Intrinsic s_readableStreamConsumeReadableStreamCodeIntrinsic = JSC::NoIntrinsic;
const char* const s_readableStreamConsumeReadableStreamCode =
"(function (nativePtr, nativeType, inputStream) {\n" \
@@ -309,6 +309,8 @@ const char* const s_readableStreamConsumeReadableStreamCode =
" } else {\n" \
" return -1;\n" \
" }\n" \
+ "\n" \
+ " \n" \
" }\n" \
"\n" \
" readMany() {\n" \
diff --git a/src/bun.js/builtins/cpp/ReadableStreamInternalsBuiltins.cpp b/src/bun.js/builtins/cpp/ReadableStreamInternalsBuiltins.cpp
index e39802ee5..a862fc0b4 100644
--- a/src/bun.js/builtins/cpp/ReadableStreamInternalsBuiltins.cpp
+++ b/src/bun.js/builtins/cpp/ReadableStreamInternalsBuiltins.cpp
@@ -2253,7 +2253,7 @@ const char* const s_readableStreamInternalsReadableStreamDefaultControllerCanClo
const JSC::ConstructAbility s_readableStreamInternalsLazyLoadStreamCodeConstructAbility = JSC::ConstructAbility::CannotConstruct;
const JSC::ConstructorKind s_readableStreamInternalsLazyLoadStreamCodeConstructorKind = JSC::ConstructorKind::None;
const JSC::ImplementationVisibility s_readableStreamInternalsLazyLoadStreamCodeImplementationVisibility = JSC::ImplementationVisibility::Public;
-const int s_readableStreamInternalsLazyLoadStreamCodeLength = 2505;
+const int s_readableStreamInternalsLazyLoadStreamCodeLength = 2701;
static const JSC::Intrinsic s_readableStreamInternalsLazyLoadStreamCodeIntrinsic = JSC::NoIntrinsic;
const char* const s_readableStreamInternalsLazyLoadStreamCode =
"(function (stream, autoAllocateChunkSize) {\n" \
@@ -2277,6 +2277,8 @@ const char* const s_readableStreamInternalsLazyLoadStreamCode =
" handleResult = function handleResult(result, controller, view) {\n" \
" \"use strict\";\n" \
"\n" \
+ " console.log(\"handleResult\", result, controller, view);\n" \
+ " \n" \
" if (result && @isPromise(result)) {\n" \
" return result.then(\n" \
" handleNativeReadableStreamPromiseResult.bind({\n" \
@@ -2287,8 +2289,10 @@ const char* const s_readableStreamInternalsLazyLoadStreamCode =
" );\n" \
" } else if (result !== false) {\n" \
" if (view && view.byteLength === result) {\n" \
+ " console.log(\"view\", result, controller.byobRequest);\n" \
" controller.byobRequest.respondWithNewView(view);\n" \
" } else {\n" \
+ " console.log(\"result\", result, controller.byobRequest);\n" \
" controller.byobRequest.respond(result);\n" \
" }\n" \
" }\n" \
diff --git a/src/bun.js/builtins/js/ReadableStream.js b/src/bun.js/builtins/js/ReadableStream.js
index 8496b2d4b..42c0fbdbc 100644
--- a/src/bun.js/builtins/js/ReadableStream.js
+++ b/src/bun.js/builtins/js/ReadableStream.js
@@ -244,6 +244,8 @@ function consumeReadableStream(nativePtr, nativeType, inputStream) {
} else {
return -1;
}
+
+
}
readMany() {
diff --git a/src/bun.js/builtins/js/ReadableStreamInternals.js b/src/bun.js/builtins/js/ReadableStreamInternals.js
index 7be9410db..a1e496290 100644
--- a/src/bun.js/builtins/js/ReadableStreamInternals.js
+++ b/src/bun.js/builtins/js/ReadableStreamInternals.js
@@ -1855,6 +1855,8 @@ function lazyLoadStream(stream, autoAllocateChunkSize) {
handleResult = function handleResult(result, controller, view) {
"use strict";
+ console.log("handleResult", result, controller, view);
+
if (result && @isPromise(result)) {
return result.then(
handleNativeReadableStreamPromiseResult.bind({
@@ -1865,8 +1867,10 @@ function lazyLoadStream(stream, autoAllocateChunkSize) {
);
} else if (result !== false) {
if (view && view.byteLength === result) {
+ console.log("view", result, controller.byobRequest);
controller.byobRequest.respondWithNewView(view);
} else {
+ console.log("result", result, controller.byobRequest);
controller.byobRequest.respond(result);
}
}
diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig
index ff3dfa9e7..1d011c509 100644
--- a/src/bun.js/event_loop.zig
+++ b/src/bun.js/event_loop.zig
@@ -212,6 +212,8 @@ pub const EventLoop = struct {
waker: ?AsyncIO.Waker = null,
start_server_on_next_tick: bool = false,
defer_count: std.atomic.Atomic(usize) = std.atomic.Atomic(usize).init(0),
+ pending_processes_to_exit: std.AutoArrayHashMap(*JSC.Subprocess, void) = undefined,
+
pub const Queue = std.fifo.LinearFifo(Task, .Dynamic);
pub fn tickWithCount(this: *EventLoop) u32 {
@@ -421,6 +423,14 @@ pub const EventLoop = struct {
pub fn afterUSocketsTick(this: *EventLoop) void {
this.defer_count.store(0, .Monotonic);
+ const processes = this.pending_processes_to_exit.keys();
+ if (processes.len > 0) {
+ for (processes) |process| {
+ process.onExitNotification();
+ }
+ this.pending_processes_to_exit.clearRetainingCapacity();
+ }
+
this.tick();
}
@@ -443,7 +453,7 @@ pub const Poller = struct {
/// 0 == unset
loop: ?*uws.Loop = null,
- pub fn dispatchKQueueEvent(loop: *uws.Loop, kqueue_event: *const std.os.Kevent) void {
+ pub fn dispatchKQueueEvent(loop: *uws.Loop, kqueue_event: *const std.os.system.kevent64_s) void {
if (comptime !Environment.isMac) {
unreachable;
}
@@ -455,7 +465,21 @@ pub const Poller = struct {
loop.active -= 1;
loader.onPoll(@bitCast(i64, kqueue_event.data), kqueue_event.flags);
},
- else => unreachable,
+ @field(Pollable.Tag, "Subprocess") => {
+ var loader = ptr.as(JSC.Subprocess);
+
+ loop.num_polls -= 1;
+
+ // kqueue sends the same notification multiple times in the same tick potentially
+ // so we have to dedupe it
+ _ = loader.globalThis.bunVM().eventLoop().pending_processes_to_exit.getOrPut(loader) catch unreachable;
+ },
+ else => |tag| {
+ bun.Output.panic(
+ "Internal error\nUnknown pollable tag: {d}\n",
+ .{@enumToInt(tag)},
+ );
+ },
}
}
@@ -476,13 +500,15 @@ pub const Poller = struct {
const FileBlobLoader = JSC.WebCore.FileBlobLoader;
const FileSink = JSC.WebCore.FileSink;
+ const Subprocess = JSC.Subprocess;
+
/// epoll only allows one pointer
/// We unfortunately need two pointers: one for a function call and one for the context
/// We use a tagged pointer union and then call the function with the context pointer
pub const Pollable = TaggedPointerUnion(.{
FileBlobLoader,
FileSink,
- AsyncIO.Waker,
+ Subprocess,
});
const Kevent = std.os.Kevent;
const kevent = std.c.kevent;
@@ -538,6 +564,15 @@ pub const Poller = struct {
.flags = std.c.EV_ADD | std.c.EV_ENABLE | std.c.EV_ONESHOT,
.ext = .{ 0, 0 },
},
+ .process => .{
+ .ident = @intCast(u64, fd),
+ .filter = std.os.system.EVFILT_PROC,
+ .data = 0,
+ .fflags = std.c.NOTE_EXIT,
+ .udata = @ptrToInt(Pollable.init(ctx).ptr()),
+ .flags = std.c.EV_ADD | std.c.EV_ENABLE | std.c.EV_ONESHOT,
+ .ext = .{ 0, 0 },
+ },
};
// output events only include change errors
@@ -567,6 +602,7 @@ pub const Poller = struct {
}
const errno = std.c.getErrno(rc);
+
if (errno == .SUCCESS) {
this.loop.?.num_polls += 1;
this.loop.?.active += 1;
@@ -597,7 +633,11 @@ pub const Poller = struct {
dispatchEpollEvent(loop, &loop.ready_polls[@intCast(usize, loop.current_ready_poll)]);
}
- pub const Flag = enum { read, write };
+ pub const Flag = enum {
+ read,
+ write,
+ process,
+ };
comptime {
@export(onTick, .{ .name = "Bun__internal_dispatch_ready_poll" });
diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig
index 08aa7b418..b9c381a14 100644
--- a/src/bun.js/javascript.zig
+++ b/src/bun.js/javascript.zig
@@ -567,6 +567,7 @@ pub const VirtualMachine = struct {
VirtualMachine.vm.regular_event_loop.tasks = EventLoop.Queue.init(
default_allocator,
);
+ VirtualMachine.vm.regular_event_loop.pending_processes_to_exit = std.AutoArrayHashMap(*JSC.Subprocess, void).init(allocator);
VirtualMachine.vm.regular_event_loop.tasks.ensureUnusedCapacity(64) catch unreachable;
VirtualMachine.vm.regular_event_loop.concurrent_tasks = .{};
VirtualMachine.vm.event_loop = &VirtualMachine.vm.regular_event_loop;
diff --git a/src/bun.js/node/syscall.zig b/src/bun.js/node/syscall.zig
index edb6937f5..2af73ef21 100644
--- a/src/bun.js/node/syscall.zig
+++ b/src/bun.js/node/syscall.zig
@@ -99,6 +99,7 @@ pub const Tag = enum(u8) {
kevent,
kqueue,
epoll_ctl,
+ kill,
pub var strings = std.EnumMap(Tag, JSC.C.JSStringRef).initFull(null);
};
const PathString = @import("../../global.zig").PathString;
diff --git a/src/bun.js/webcore/streams.zig b/src/bun.js/webcore/streams.zig
index dd15bf45a..abf220ad7 100644
--- a/src/bun.js/webcore/streams.zig
+++ b/src/bun.js/webcore/streams.zig
@@ -53,6 +53,10 @@ pub const ReadableStream = struct {
value: JSValue,
ptr: Source,
+ pub fn toJS(this: *const ReadableStream) JSValue {
+ return this.value;
+ }
+
pub fn done(this: *const ReadableStream) void {
this.value.unprotect();
}
@@ -254,7 +258,7 @@ pub const StreamStart = union(Tag) {
input_path: PathOrFileDescriptor,
truncate: bool = true,
close: bool = false,
- mode: JSC.Node.Mode = 0,
+ mode: JSC.Node.Mode = 0o664,
},
HTTPSResponseSink: void,
HTTPResponseSink: void,
@@ -462,6 +466,7 @@ pub const StreamResult = union(Tag) {
pub fn toPromised(globalThis: *JSGlobalObject, promise: *JSPromise, pending: *Writable.Pending) void {
var frame = bun.default_allocator.create(@Frame(Writable.toPromisedWrap)) catch unreachable;
+ pending.state = .pending;
frame.* = async Writable.toPromisedWrap(globalThis, promise, pending);
pending.frame = frame;
}
@@ -564,6 +569,7 @@ pub const StreamResult = union(Tag) {
pub fn toPromised(globalThis: *JSGlobalObject, promise: *JSPromise, pending: *Pending) void {
var frame = bun.default_allocator.create(@Frame(toPromisedWrap)) catch unreachable;
+ pending.state = .pending;
frame.* = async toPromisedWrap(globalThis, promise, pending);
pending.frame = frame;
}
@@ -933,6 +939,9 @@ pub const FileSink = struct {
head: usize = 0,
requested_end: bool = false,
+ prevent_process_exit: bool = false,
+ reachable_from_js: bool = true,
+
pub fn prepare(this: *FileSink, input_path: PathOrFileDescriptor, mode: JSC.Node.Mode) JSC.Node.Maybe(void) {
var file_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
const auto_close = this.auto_close;
@@ -1083,7 +1092,7 @@ pub const FileSink = struct {
this.opened_fd = std.math.maxInt(JSC.Node.FileDescriptor);
}
- if (this.buffer.len > 0) {
+ if (this.buffer.cap > 0) {
this.buffer.listManaged(this.allocator).deinit();
this.buffer = bun.ByteList.init("");
this.done = true;
@@ -1093,8 +1102,10 @@ pub const FileSink = struct {
pub fn finalize(this: *FileSink) void {
this.cleanup();
+ this.reachable_from_js = false;
- this.allocator.destroy(this);
+ if (!this.prevent_process_exit)
+ this.allocator.destroy(this);
}
pub fn init(allocator: std.mem.Allocator, next: ?Sink) !*FileSink {
@@ -2878,8 +2889,8 @@ pub const FileBlobLoader = struct {
return;
}
- this.pending.result = this.handleReadChunk(@as(usize, this.concurrent.read));
- resume this.pending.frame;
+ this.pending.result = this.handleReadChunk(@as(usize, this.concurrent.read), protected_view);
+ this.pending.run();
this.scheduled_count -= 1;
if (this.pending.result.isDone()) {
this.finalize();
@@ -3058,7 +3069,7 @@ pub const FileBlobLoader = struct {
}
}
- fn handleReadChunk(this: *FileBlobLoader, result: usize) StreamResult {
+ fn handleReadChunk(this: *FileBlobLoader, result: usize, view: JSC.JSValue) StreamResult {
std.debug.assert(this.started);
this.total_read += @intCast(Blob.SizeType, result);
@@ -3079,10 +3090,10 @@ pub const FileBlobLoader = struct {
const has_more = remaining > 0;
if (!has_more) {
- return .{ .into_array_and_done = .{ .len = @truncate(Blob.SizeType, result) } };
+ return .{ .into_array_and_done = .{ .len = @truncate(Blob.SizeType, result), .value = view } };
}
- return .{ .into_array = .{ .len = @truncate(Blob.SizeType, result) } };
+ return .{ .into_array = .{ .len = @truncate(Blob.SizeType, result), .value = view } };
}
pub fn read(
@@ -3125,7 +3136,7 @@ pub const FileBlobLoader = struct {
return .{ .err = sys };
},
.result => |result| {
- return this.handleReadChunk(result);
+ return this.handleReadChunk(result, view);
},
}
}
@@ -3136,6 +3147,7 @@ pub const FileBlobLoader = struct {
this.scheduled_count -= 1;
const protected_view = this.protected_view;
defer protected_view.unprotect();
+
this.protected_view = JSValue.zero;
var available_to_read: usize = std.math.maxInt(usize);
@@ -3169,7 +3181,7 @@ pub const FileBlobLoader = struct {
this.buf.len = @minimum(this.buf.len, available_to_read);
}
- this.pending.result = this.read(this.buf, this.protected_view);
+ this.pending.result = this.read(this.buf, protected_view);
this.pending.run();
}
diff --git a/src/deps/uws.zig b/src/deps/uws.zig
index b26020714..497cd660c 100644
--- a/src/deps/uws.zig
+++ b/src/deps/uws.zig
@@ -287,7 +287,7 @@ pub const Loop = extern struct {
/// The list of ready polls
ready_polls: [1024]EventType,
- const EventType = if (Environment.isLinux) std.os.linux.epoll_event else if (Environment.isMac) std.os.Kevent;
+ const EventType = if (Environment.isLinux) std.os.linux.epoll_event else if (Environment.isMac) std.os.system.kevent64_s;
pub const InternalLoopData = extern struct {
pub const us_internal_async = opaque {};
diff --git a/src/env_loader.zig b/src/env_loader.zig
index 97edbc3c2..4c5f75c09 100644
--- a/src/env_loader.zig
+++ b/src/env_loader.zig
@@ -1004,6 +1004,26 @@ pub const Map = struct {
map: HashTable,
+ pub fn createNullDelimitedEnvMap(this: *Map, arena: std.mem.Allocator) ![:null]?[*:0]u8 {
+ var env_map = &this.map;
+
+ const envp_count = env_map.count();
+ const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null);
+ {
+ var it = env_map.iterator();
+ var i: usize = 0;
+ while (it.next()) |pair| : (i += 1) {
+ const env_buf = try arena.allocSentinel(u8, pair.key_ptr.len + pair.value_ptr.len + 1, 0);
+ std.mem.copy(u8, env_buf, pair.key_ptr.*);
+ env_buf[pair.key_ptr.len] = '=';
+ std.mem.copy(u8, env_buf[pair.key_ptr.len + 1 ..], pair.value_ptr.*);
+ envp_buf[i] = env_buf.ptr;
+ }
+ std.debug.assert(i == envp_count);
+ }
+ return envp_buf;
+ }
+
pub fn cloneToEnvMap(this: *Map, allocator: std.mem.Allocator) !std.process.EnvMap {
var env_map = std.process.EnvMap.init(allocator);
diff --git a/src/jsc.zig b/src/jsc.zig
index 4f1265287..f4adbcc3a 100644
--- a/src/jsc.zig
+++ b/src/jsc.zig
@@ -48,5 +48,6 @@ pub const jsBoolean = @This().JSValue.jsBoolean;
pub inline fn markBinding() void {
if (comptime is_bindgen) unreachable;
}
+pub const Subprocess = @import("./bun.js/api/bun.zig").Subprocess;
pub const Codegen = @import("./bun.js/bindings/generated_classes.zig");
diff --git a/test/bun.js/filesink.test.ts b/test/bun.js/filesink.test.ts
new file mode 100644
index 000000000..28d51a173
--- /dev/null
+++ b/test/bun.js/filesink.test.ts
@@ -0,0 +1,129 @@
+import { ArrayBufferSink } from "bun";
+import { describe, expect, it } from "bun:test";
+
+describe("FileSink", () => {
+ const fixtures = [
+ [
+ ["abcdefghijklmnopqrstuvwxyz"],
+ new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz"),
+ "abcdefghijklmnopqrstuvwxyz",
+ ],
+ [
+ ["abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"],
+ new TextEncoder().encode(
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ ),
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
+ ],
+ [
+ ["😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"],
+ new TextEncoder().encode(
+ "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"
+ ),
+ "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌",
+ ],
+ [
+ [
+ "abcdefghijklmnopqrstuvwxyz",
+ "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌",
+ ],
+ new TextEncoder().encode(
+ "abcdefghijklmnopqrstuvwxyz" +
+ "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"
+ ),
+ "abcdefghijklmnopqrstuvwxyz" +
+ "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌",
+ ],
+ [
+ [
+ "abcdefghijklmnopqrstuvwxyz",
+ "😋",
+ " Get Emoji — All Emojis",
+ " to ✂️ Copy and 📋 Paste 👌",
+ ],
+ new TextEncoder().encode(
+ "abcdefghijklmnopqrstuvwxyz" +
+ "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"
+ ),
+ "(rope) " +
+ "abcdefghijklmnopqrstuvwxyz" +
+ "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌",
+ ],
+ [
+ [
+ new TextEncoder().encode("abcdefghijklmnopqrstuvwxyz"),
+ "😋",
+ " Get Emoji — All Emojis",
+ " to ✂️ Copy and 📋 Paste 👌",
+ ],
+ new TextEncoder().encode(
+ "abcdefghijklmnopqrstuvwxyz" +
+ "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌"
+ ),
+ "(array) " +
+ "abcdefghijklmnopqrstuvwxyz" +
+ "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌",
+ ],
+ ];
+
+ for (const [input, expected, label] of fixtures) {
+ it(`${JSON.stringify(label)}`, async () => {
+ const path = `/tmp/bun-test-${Bun.hash(label).toString(10)}.txt`;
+ try {
+ require("fs").unlinkSync(path);
+ } catch (e) {}
+
+ const sink = Bun.file(path).writer();
+ for (let i = 0; i < input.length; i++) {
+ sink.write(input[i]);
+ }
+ await sink.end();
+
+ const output = new Uint8Array(await Bun.file(path).arrayBuffer());
+ for (let i = 0; i < expected.length; i++) {
+ expect(output[i]).toBe(expected[i]);
+ }
+ expect(output.byteLength).toBe(expected.byteLength);
+ });
+
+ it(`flushing -> ${JSON.stringify(label)}`, async () => {
+ const path = `/tmp/bun-test-${Bun.hash(label).toString(10)}.txt`;
+ try {
+ require("fs").unlinkSync(path);
+ } catch (e) {}
+
+ const sink = Bun.file(path).writer();
+ for (let i = 0; i < input.length; i++) {
+ sink.write(input[i]);
+ await sink.flush();
+ }
+ await sink.end();
+
+ const output = new Uint8Array(await Bun.file(path).arrayBuffer());
+ for (let i = 0; i < expected.length; i++) {
+ expect(output[i]).toBe(expected[i]);
+ }
+ expect(output.byteLength).toBe(expected.byteLength);
+ });
+
+ it(`highWaterMark -> ${JSON.stringify(label)}`, async () => {
+ const path = `/tmp/bun-test-${Bun.hash(label).toString(10)}.txt`;
+ try {
+ require("fs").unlinkSync(path);
+ } catch (e) {}
+
+ const sink = Bun.file(path).writer({ highWaterMark: 1 });
+ for (let i = 0; i < input.length; i++) {
+ sink.write(input[i]);
+ await sink.flush();
+ }
+ await sink.end();
+
+ const output = new Uint8Array(await Bun.file(path).arrayBuffer());
+ for (let i = 0; i < expected.length; i++) {
+ expect(output[i]).toBe(expected[i]);
+ }
+ expect(output.byteLength).toBe(expected.byteLength);
+ });
+ }
+});