kind: Kind,
stdin: BuiltinIO.Input,
stdout: BuiltinIO.Output,
stderr: BuiltinIO.Output,
exit_code: ?ExitCode = null,

export_env: *EnvMap,
cmd_local_env: *EnvMap,

arena: *bun.ArenaAllocator,
/// The following are allocated with the above arena
args: *const std.ArrayList(?[*:0]const u8),
args_slice: ?[]const [:0]const u8 = null,
cwd: bun.FileDescriptor,

impl: Impl,

pub const Impl = union(Kind) {
    cat: Cat,
    touch: Touch,
    mkdir: Mkdir,
    @"export": Export,
    cd: Cd,
    echo: Echo,
    pwd: Pwd,
    which: Which,
    rm: Rm,
    mv: Mv,
    ls: Ls,
    exit: Exit,
    true: True,
    false: False,
    yes: Yes,
    seq: Seq,
    dirname: Dirname,
    basename: Basename,
    cp: Cp,
};

pub const Result = @import("../result.zig").Result;

// Note: this enum uses @tagName, choose wisely!
pub const Kind = enum {
    cat,
    touch,
    mkdir,
    @"export",
    cd,
    echo,
    pwd,
    which,
    rm,
    mv,
    ls,
    exit,
    true,
    false,
    yes,
    seq,
    dirname,
    basename,
    cp,

    pub const DISABLED_ON_POSIX: []const Kind = &.{ .cat, .cp };

    pub fn parentType(this: Kind) type {
        _ = this;
    }

    pub fn usageString(this: Kind) []const u8 {
        return switch (this) {
            .cat => "usage: cat [-belnstuv] [file ...]\n",
            .touch => "usage: touch [-A [-][[hh]mm]SS] [-achm] [-r file] [-t [[CC]YY]MMDDhhmm[.SS]]\n       [-d YYYY-MM-DDThh:mm:SS[.frac][tz]] file ...\n",
            .mkdir => "usage: mkdir [-pv] [-m mode] directory_name ...\n",
            .@"export" => "",
            .cd => "",
            .echo => "",
            .pwd => "",
            .which => "",
            .rm => "usage: rm [-f | -i] [-dIPRrvWx] file ...\n       unlink [--] file\n",
            .mv => "usage: mv [-f | -i | -n] [-hv] source target\n       mv [-f | -i | -n] [-v] source ... directory\n",
            .ls => "usage: ls [-@ABCFGHILOPRSTUWabcdefghiklmnopqrstuvwxy1%,] [--color=when] [-D format] [file ...]\n",
            .exit => "usage: exit [n]\n",
            .true => "",
            .false => "",
            .yes => "usage: yes [expletive]\n",
            .seq => "usage: seq [-w] [-f format] [-s string] [-t string] [first [incr]] last\n",
            .dirname => "usage: dirname string\n",
            .basename => "usage: basename string\n",
            .cp => "usage: cp [-R [-H | -L | -P]] [-fi | -n] [-aclpsvXx] source_file target_file\n       cp [-R [-H | -L | -P]] [-fi | -n] [-aclpsvXx] source_file ... target_directory\n",
        };
    }

    fn forceEnableOnPosix() bool {
        return bun.getRuntimeFeatureFlag(.BUN_ENABLE_EXPERIMENTAL_SHELL_BUILTINS);
    }

    pub fn fromStr(str: []const u8) ?Builtin.Kind {
        const result = std.meta.stringToEnum(Builtin.Kind, str) orelse return null;
        if (bun.Environment.isWindows) return result;
        if (forceEnableOnPosix()) return result;
        inline for (Builtin.Kind.DISABLED_ON_POSIX) |disabled| {
            if (disabled == result) {
                log("{s} builtin disabled on posix for now", .{@tagName(disabled)});
                return null;
            }
        }
        return result;
    }
};

pub const BuiltinIO = struct {
    /// in the case of array buffer we simply need to write to the pointer
    /// in the case of blob, we write to the file descriptor
    pub const Output = union(enum) {
        fd: struct { writer: *IOWriter, captured: ?*bun.ByteList = null },
        /// array list not owned by this type
        buf: std.ArrayList(u8),
        arraybuf: ArrayBuf,
        blob: *Blob,
        ignore,

        const FdOutput = struct {
            writer: *IOWriter,
            captured: ?*bun.ByteList = null,

            // pub fn
        };

        pub fn ref(this: *Output) *Output {
            switch (this.*) {
                .fd => {
                    this.fd.writer.ref();
                },
                .blob => this.blob.ref(),
                else => {},
            }
            return this;
        }

        pub fn deref(this: *Output) void {
            switch (this.*) {
                .fd => {
                    this.fd.writer.deref();
                },
                .blob => this.blob.deref(),
                else => {},
            }
        }

        pub fn needsIO(this: *Output) ?OutputNeedsIOSafeGuard {
            return switch (this.*) {
                .fd => .output_needs_io,
                else => null,
            };
        }

        /// You must check that `.needsIO() == true` before calling this!
        /// e.g.
        ///
        /// ```zig
        /// if (this.stderr.neesdIO()) |safeguard| {
        ///   this.bltn.stderr.enqueueFmtBltn(this, .cd, fmt, args, safeguard);
        /// }
        /// ```
        pub fn enqueueFmtBltn(
            this: *@This(),
            ptr: anytype,
            comptime kind: ?Interpreter.Builtin.Kind,
            comptime fmt_: []const u8,
            args: anytype,
            _: OutputNeedsIOSafeGuard,
        ) void {
            this.fd.writer.enqueueFmtBltn(ptr, this.fd.captured, kind, fmt_, args);
        }

        pub fn enqueue(this: *@This(), ptr: anytype, buf: []const u8, _: OutputNeedsIOSafeGuard) void {
            this.fd.writer.enqueue(ptr, this.fd.captured, buf);
        }
    };

    pub const Input = union(enum) {
        fd: *IOReader,
        /// array list not ownedby this type
        buf: std.ArrayList(u8),
        arraybuf: ArrayBuf,
        blob: *Blob,
        ignore,

        pub fn ref(this: *Input) *Input {
            switch (this.*) {
                .fd => {
                    this.fd.ref();
                },
                .blob => this.blob.ref(),
                else => {},
            }
            return this;
        }

        pub fn deref(this: *Input) void {
            switch (this.*) {
                .fd => {
                    this.fd.deref();
                },
                .blob => this.blob.deref(),
                else => {},
            }
        }

        pub fn needsIO(this: *Input) bool {
            return switch (this.*) {
                .fd => true,
                else => false,
            };
        }
    };

    const ArrayBuf = struct {
        buf: JSC.ArrayBuffer.Strong,
        i: u32 = 0,
    };

    const Blob = struct {
        const RefCount = bun.ptr.RefCount(@This(), "ref_count", @This().deinit, .{});
        pub const ref = RefCount.ref;
        pub const deref = RefCount.deref;

        ref_count: RefCount,
        blob: bun.webcore.Blob,

        fn deinit(this: *Blob) void {
            this.blob.deinit();
            bun.destroy(this);
        }
    };
};

pub fn argsSlice(this: *Builtin) []const [*:0]const u8 {
    const args_raw = this.args.items[1..];
    const args_len = std.mem.indexOfScalar(?[*:0]const u8, args_raw, null) orelse @panic("bad");
    if (args_len == 0)
        return &[_][*:0]const u8{};

    const args_ptr = args_raw.ptr;
    return @as([*][*:0]const u8, @ptrCast(args_ptr))[0..args_len];
}

pub inline fn callImpl(this: *Builtin, comptime Ret: type, comptime field: []const u8, args_: anytype) Ret {
    return switch (this.kind) {
        .cat => this.callImplWithType(Cat, Ret, "cat", field, args_),
        .touch => this.callImplWithType(Touch, Ret, "touch", field, args_),
        .mkdir => this.callImplWithType(Mkdir, Ret, "mkdir", field, args_),
        .@"export" => this.callImplWithType(Export, Ret, "export", field, args_),
        .echo => this.callImplWithType(Echo, Ret, "echo", field, args_),
        .cd => this.callImplWithType(Cd, Ret, "cd", field, args_),
        .which => this.callImplWithType(Which, Ret, "which", field, args_),
        .rm => this.callImplWithType(Rm, Ret, "rm", field, args_),
        .pwd => this.callImplWithType(Pwd, Ret, "pwd", field, args_),
        .mv => this.callImplWithType(Mv, Ret, "mv", field, args_),
        .ls => this.callImplWithType(Ls, Ret, "ls", field, args_),
        .exit => this.callImplWithType(Exit, Ret, "exit", field, args_),
        .true => this.callImplWithType(True, Ret, "true", field, args_),
        .false => this.callImplWithType(False, Ret, "false", field, args_),
        .yes => this.callImplWithType(Yes, Ret, "yes", field, args_),
        .seq => this.callImplWithType(Seq, Ret, "seq", field, args_),
        .dirname => this.callImplWithType(Dirname, Ret, "dirname", field, args_),
        .basename => this.callImplWithType(Basename, Ret, "basename", field, args_),
        .cp => this.callImplWithType(Cp, Ret, "cp", field, args_),
    };
}

fn callImplWithType(this: *Builtin, comptime BuiltinImpl: type, comptime Ret: type, comptime union_field: []const u8, comptime field: []const u8, args_: anytype) Ret {
    const self = &@field(this.impl, union_field);
    const args = brk: {
        var args: std.meta.ArgsTuple(@TypeOf(@field(BuiltinImpl, field))) = undefined;
        args[0] = self;

        var i: usize = 1;
        inline for (args_) |a| {
            args[i] = a;
            i += 1;
        }

        break :brk args;
    };
    return @call(.auto, @field(BuiltinImpl, field), args);
}

pub inline fn allocator(this: *Builtin) Allocator {
    return this.parentCmd().base.interpreter.allocator;
}

pub fn init(
    cmd: *Cmd,
    interpreter: *Interpreter,
    kind: Kind,
    arena: *bun.ArenaAllocator,
    node: *const ast.Cmd,
    args: *const std.ArrayList(?[*:0]const u8),
    export_env: *EnvMap,
    cmd_local_env: *EnvMap,
    cwd: bun.FileDescriptor,
    io: *IO,
) CoroutineResult {
    const stdin: BuiltinIO.Input = switch (io.stdin) {
        .fd => |fd| .{ .fd = fd.refSelf() },
        .ignore => .ignore,
    };
    const stdout: BuiltinIO.Output = switch (io.stdout) {
        .fd => |val| .{ .fd = .{ .writer = val.writer.refSelf(), .captured = val.captured } },
        .pipe => .{ .buf = std.ArrayList(u8).init(bun.default_allocator) },
        .ignore => .ignore,
    };
    const stderr: BuiltinIO.Output = switch (io.stderr) {
        .fd => |val| .{ .fd = .{ .writer = val.writer.refSelf(), .captured = val.captured } },
        .pipe => .{ .buf = std.ArrayList(u8).init(bun.default_allocator) },
        .ignore => .ignore,
    };

    cmd.exec = .{
        .bltn = Builtin{
            .kind = kind,
            .stdin = stdin,
            .stdout = stdout,
            .stderr = stderr,
            .exit_code = null,
            .arena = arena,
            .args = args,
            .export_env = export_env,
            .cmd_local_env = cmd_local_env,
            .cwd = cwd,
            .impl = undefined,
        },
    };

    switch (kind) {
        .rm => {
            cmd.exec.bltn.impl = .{
                .rm = Rm{
                    .opts = .{},
                },
            };
        },
        .echo => {
            cmd.exec.bltn.impl = .{
                .echo = Echo{
                    .output = std.ArrayList(u8).init(arena.allocator()),
                },
            };
        },
        inline else => |tag| {
            cmd.exec.bltn.impl = @unionInit(Impl, @tagName(tag), .{});
        },
    }

    if (node.redirect_file) |file| {
        switch (file) {
            .atom => {
                if (cmd.redirection_file.items.len == 0) {
                    cmd.writeFailingError("bun: ambiguous redirect: at `{s}`\n", .{@tagName(kind)});
                    return .yield;
                }

                // Regular files are not pollable on linux
                const is_pollable: bool = if (bun.Environment.isLinux) false else true;

                const path = cmd.redirection_file.items[0..cmd.redirection_file.items.len -| 1 :0];
                log("EXPANDED REDIRECT: {s}\n", .{cmd.redirection_file.items[0..]});
                const perm = 0o666;
                const is_nonblocking = false;
                const flags = node.redirect.toFlags();
                const redirfd = switch (ShellSyscall.openat(cmd.base.shell.cwd_fd, path, flags, perm)) {
                    .err => |e| {
                        cmd.writeFailingError("bun: {s}: {s}", .{ e.toShellSystemError().message, path });
                        return .yield;
                    },
                    .result => |f| f,
                };
                if (node.redirect.stdin) {
                    cmd.exec.bltn.stdin.deref();
                    cmd.exec.bltn.stdin = .{ .fd = IOReader.init(redirfd, cmd.base.eventLoop()) };
                }
                if (node.redirect.stdout) {
                    cmd.exec.bltn.stdout.deref();
                    cmd.exec.bltn.stdout = .{ .fd = .{ .writer = IOWriter.init(redirfd, .{ .pollable = is_pollable, .nonblocking = is_nonblocking }, cmd.base.eventLoop()) } };
                }
                if (node.redirect.stderr) {
                    cmd.exec.bltn.stderr.deref();
                    cmd.exec.bltn.stderr = .{ .fd = .{ .writer = IOWriter.init(redirfd, .{ .pollable = is_pollable, .nonblocking = is_nonblocking }, cmd.base.eventLoop()) } };
                }
            },
            .jsbuf => |val| {
                const globalObject = interpreter.event_loop.js.global;
                if (interpreter.jsobjs[file.jsbuf.idx].asArrayBuffer(globalObject)) |buf| {
                    const arraybuf: BuiltinIO.ArrayBuf = .{ .buf = JSC.ArrayBuffer.Strong{
                        .array_buffer = buf,
                        .held = .create(buf.value, globalObject),
                    }, .i = 0 };

                    if (node.redirect.stdin) {
                        cmd.exec.bltn.stdin.deref();
                        cmd.exec.bltn.stdin = .{ .arraybuf = arraybuf };
                    }

                    if (node.redirect.stdout) {
                        cmd.exec.bltn.stdout.deref();
                        cmd.exec.bltn.stdout = .{ .arraybuf = arraybuf };
                    }

                    if (node.redirect.stderr) {
                        cmd.exec.bltn.stderr.deref();
                        cmd.exec.bltn.stderr = .{ .arraybuf = arraybuf };
                    }
                } else if (interpreter.jsobjs[file.jsbuf.idx].as(JSC.WebCore.Body.Value)) |body| {
                    if ((node.redirect.stdout or node.redirect.stderr) and !(body.* == .Blob and !body.Blob.needsToReadFile())) {
                        // TODO: Locked->stream -> file -> blob conversion via .toBlobIfPossible() except we want to avoid modifying the Response/Request if unnecessary.
                        cmd.base.interpreter.event_loop.js.global.throw("Cannot redirect stdout/stderr to an immutable blob. Expected a file", .{}) catch {};
                        return .yield;
                    }

                    var original_blob = body.use();
                    defer original_blob.deinit();

                    const blob: *BuiltinIO.Blob = bun.new(BuiltinIO.Blob, .{
                        .ref_count = .init(),
                        .blob = original_blob.dupe(),
                    });

                    if (node.redirect.stdin) {
                        cmd.exec.bltn.stdin.deref();
                        cmd.exec.bltn.stdin = .{ .blob = blob };
                    }

                    if (node.redirect.stdout) {
                        cmd.exec.bltn.stdout.deref();
                        cmd.exec.bltn.stdout = .{ .blob = blob };
                    }

                    if (node.redirect.stderr) {
                        cmd.exec.bltn.stderr.deref();
                        cmd.exec.bltn.stderr = .{ .blob = blob };
                    }
                } else if (interpreter.jsobjs[file.jsbuf.idx].as(JSC.WebCore.Blob)) |blob| {
                    if ((node.redirect.stdout or node.redirect.stderr) and !blob.needsToReadFile()) {
                        // TODO: Locked->stream -> file -> blob conversion via .toBlobIfPossible() except we want to avoid modifying the Response/Request if unnecessary.
                        cmd.base.interpreter.event_loop.js.global.throw("Cannot redirect stdout/stderr to an immutable blob. Expected a file", .{}) catch {};
                        return .yield;
                    }

                    const theblob: *BuiltinIO.Blob = bun.new(BuiltinIO.Blob, .{
                        .ref_count = .init(),
                        .blob = blob.dupe(),
                    });

                    if (node.redirect.stdin) {
                        cmd.exec.bltn.stdin.deref();
                        cmd.exec.bltn.stdin = .{ .blob = theblob };
                    } else if (node.redirect.stdout) {
                        cmd.exec.bltn.stdout.deref();
                        cmd.exec.bltn.stdout = .{ .blob = theblob };
                    } else if (node.redirect.stderr) {
                        cmd.exec.bltn.stderr.deref();
                        cmd.exec.bltn.stderr = .{ .blob = theblob };
                    }
                } else {
                    const jsval = cmd.base.interpreter.jsobjs[val.idx];
                    cmd.base.interpreter.event_loop.js.global.throw("Unknown JS value used in shell: {}", .{jsval.fmtString(globalObject)}) catch {};
                    return .yield;
                }
            },
        }
    } else if (node.redirect.duplicate_out) {
        if (node.redirect.stdout) {
            cmd.exec.bltn.stderr.deref();
            cmd.exec.bltn.stderr = cmd.exec.bltn.stdout.ref().*;
        }

        if (node.redirect.stderr) {
            cmd.exec.bltn.stdout.deref();
            cmd.exec.bltn.stdout = cmd.exec.bltn.stderr.ref().*;
        }
    }

    return .cont;
}

pub inline fn eventLoop(this: *const Builtin) JSC.EventLoopHandle {
    return this.parentCmd().base.eventLoop();
}

pub inline fn throw(this: *const Builtin, err: *const bun.shell.ShellErr) void {
    this.parentCmd().base.throw(err) catch {};
}

pub inline fn parentCmd(this: *const Builtin) *const Cmd {
    const union_ptr: *const Cmd.Exec = @fieldParentPtr("bltn", this);
    return @fieldParentPtr("exec", union_ptr);
}

pub inline fn parentCmdMut(this: *Builtin) *Cmd {
    const union_ptr: *Cmd.Exec = @fieldParentPtr("bltn", this);
    return @fieldParentPtr("exec", union_ptr);
}

pub fn done(this: *Builtin, exit_code: anytype) void {
    const code: ExitCode = switch (@TypeOf(exit_code)) {
        bun.sys.E => @intFromEnum(exit_code),
        u1, u8, u16 => exit_code,
        comptime_int => exit_code,
        else => @compileError("Invalid type: " ++ @typeName(@TypeOf(exit_code))),
    };
    this.exit_code = code;

    var cmd = this.parentCmdMut();
    log("builtin done ({s}: exit={d}) cmd to free: ({x})", .{ @tagName(this.kind), code, @intFromPtr(cmd) });
    cmd.exit_code = this.exit_code.?;

    // Aggregate output data if shell state is piped and this cmd is piped
    if (cmd.io.stdout == .pipe and cmd.io.stdout == .pipe and this.stdout == .buf) {
        cmd.base.shell.buffered_stdout().append(bun.default_allocator, this.stdout.buf.items[0..]) catch bun.outOfMemory();
    }
    // Aggregate output data if shell state is piped and this cmd is piped
    if (cmd.io.stderr == .pipe and cmd.io.stderr == .pipe and this.stderr == .buf) {
        cmd.base.shell.buffered_stderr().append(bun.default_allocator, this.stderr.buf.items[0..]) catch bun.outOfMemory();
    }

    cmd.parent.childDone(cmd, this.exit_code.?);
}

pub fn start(this: *Builtin) Maybe(void) {
    switch (this.callImpl(Maybe(void), "start", .{})) {
        .err => |e| return Maybe(void).initErr(e),
        .result => {},
    }

    return Maybe(void).success;
}

pub fn deinit(this: *Builtin) void {
    this.callImpl(void, "deinit", .{});

    // No need to free it because it belongs to the parent cmd
    // _ = Syscall.close(this.cwd);

    this.stdout.deref();
    this.stderr.deref();
    this.stdin.deref();

    // Parent cmd frees this
    // this.arena.deinit();
}

/// If the stdout/stderr is supposed to be captured then get the bytelist associated with that
pub fn stdBufferedBytelist(this: *Builtin, comptime io_kind: @Type(.enum_literal)) ?*bun.ByteList {
    if (comptime io_kind != .stdout and io_kind != .stderr) {
        @compileError("Bad IO" ++ @tagName(io_kind));
    }

    const io: *BuiltinIO = &@field(this, @tagName(io_kind));
    return switch (io.*) {
        .captured => if (comptime io_kind == .stdout) this.parentCmd().base.shell.buffered_stdout() else this.parentCmd().base.shell.buffered_stderr(),
        else => null,
    };
}

pub fn readStdinNoIO(this: *Builtin) []const u8 {
    return switch (this.stdin) {
        .arraybuf => |buf| buf.buf.slice(),
        .buf => |buf| buf.items[0..],
        .blob => |blob| blob.blob.sharedView(),
        else => "",
    };
}

/// **WARNING** You should make sure that stdout/stderr does not need IO (e.g. `.needsIO(.stderr)` is false before caling `.writeNoIO(.stderr, buf)`)
pub fn writeNoIO(this: *Builtin, comptime io_kind: @Type(.enum_literal), buf: []const u8) Maybe(usize) {
    if (comptime io_kind != .stdout and io_kind != .stderr) {
        @compileError("Bad IO" ++ @tagName(io_kind));
    }

    if (buf.len == 0) return Maybe(usize).initResult(0);

    var io: *BuiltinIO.Output = &@field(this, @tagName(io_kind));

    switch (io.*) {
        .fd => @panic("writeNoIO(. " ++ @tagName(io_kind) ++ ", buf) can't write to a file descriptor, did you check that needsIO(." ++ @tagName(io_kind) ++ ") was false?"),
        .buf => {
            log("{s} write to buf len={d} str={s}{s}\n", .{ @tagName(this.kind), buf.len, buf[0..@min(buf.len, 16)], if (buf.len > 16) "..." else "" });
            io.buf.appendSlice(buf) catch bun.outOfMemory();
            return Maybe(usize).initResult(buf.len);
        },
        .arraybuf => {
            if (io.arraybuf.i >= io.arraybuf.buf.array_buffer.byte_len) {
                return Maybe(usize).initErr(bun.sys.Error.fromCode(bun.sys.E.NOSPC, .write));
            }

            const len = buf.len;
            if (io.arraybuf.i + len > io.arraybuf.buf.array_buffer.byte_len) {
                // std.ArrayList(comptime T: type)
            }
            const write_len = if (io.arraybuf.i + len > io.arraybuf.buf.array_buffer.byte_len)
                io.arraybuf.buf.array_buffer.byte_len - io.arraybuf.i
            else
                len;

            const slice = io.arraybuf.buf.slice()[io.arraybuf.i .. io.arraybuf.i + write_len];
            @memcpy(slice, buf[0..write_len]);
            io.arraybuf.i +|= @truncate(write_len);
            log("{s} write to arraybuf {d}\n", .{ @tagName(this.kind), write_len });
            return Maybe(usize).initResult(write_len);
        },
        .blob, .ignore => return Maybe(usize).initResult(buf.len),
    }
}

/// Error messages formatted to match bash
pub fn taskErrorToString(this: *Builtin, comptime kind: Kind, err: anytype) []const u8 {
    switch (@TypeOf(err)) {
        Syscall.Error => {
            if (err.getErrorCodeTagName()) |entry| {
                _, const sys_errno = entry;
                if (bun.sys.coreutils_error_map.get(sys_errno)) |message| {
                    if (err.path.len > 0) {
                        return this.fmtErrorArena(kind, "{s}: {s}\n", .{ err.path, message });
                    }
                    return this.fmtErrorArena(kind, "{s}\n", .{message});
                }
            }
            return this.fmtErrorArena(kind, "unknown error {d}\n", .{err.errno});
        },
        JSC.SystemError => {
            if (err.path.length() == 0) return this.fmtErrorArena(kind, "{s}\n", .{err.message.byteSlice()});
            return this.fmtErrorArena(kind, "{s}: {s}\n", .{ err.message.byteSlice(), err.path });
        },
        bun.shell.ShellErr => return switch (err) {
            .sys => this.taskErrorToString(kind, err.sys),
            .custom => this.fmtErrorArena(kind, "{s}\n", .{err.custom}),
            .invalid_arguments => this.fmtErrorArena(kind, "{s}\n", .{err.invalid_arguments.val}),
            .todo => this.fmtErrorArena(kind, "{s}\n", .{err.todo}),
        },
        else => @compileError("Bad type: " ++ @typeName(err)),
    }
}

pub fn fmtErrorArena(this: *Builtin, comptime kind: ?Kind, comptime fmt_: []const u8, args: anytype) []u8 {
    const cmd_str = comptime if (kind) |k| @tagName(k) ++ ": " else "";
    const fmt = cmd_str ++ fmt_;
    return std.fmt.allocPrint(this.arena.allocator(), fmt, args) catch bun.outOfMemory();
}

// --- Shell Builtin Commands ---
pub const Cat = @import("./builtin/cat.zig");
pub const Touch = @import("./builtin/touch.zig");
pub const Mkdir = @import("./builtin/mkdir.zig");
pub const Export = @import("./builtin/export.zig");
pub const Cd = @import("./builtin/cd.zig");
pub const Ls = @import("./builtin/ls.zig");
pub const Pwd = @import("./builtin/pwd.zig");
pub const Echo = @import("./builtin/echo.zig");
pub const Which = @import("./builtin/which.zig");
pub const Rm = @import("./builtin/rm.zig");
pub const Exit = @import("./builtin/exit.zig");
pub const True = @import("./builtin/true.zig");
pub const False = @import("./builtin/false.zig");
pub const Yes = @import("./builtin/yes.zig");
pub const Seq = @import("./builtin/seq.zig");
pub const Dirname = @import("./builtin/dirname.zig");
pub const Basename = @import("./builtin/basename.zig");
pub const Cp = @import("./builtin/cp.zig");
pub const Mv = @import("./builtin/mv.zig");
// --- End Shell Builtin Commands ---

const std = @import("std");
const bun = @import("bun");

const shell = bun.shell;
const Interpreter = shell.interpret.Interpreter;
const Builtin = Interpreter.Builtin;

const JSC = bun.JSC;
const Maybe = bun.sys.Maybe;
const ExitCode = shell.interpret.ExitCode;
const EnvMap = shell.interpret.EnvMap;
const log = shell.interpret.log;
const Syscall = bun.sys;
const IOWriter = Interpreter.IOWriter;
const IOReader = Interpreter.IOReader;
const OutputNeedsIOSafeGuard = shell.interpret.OutputNeedsIOSafeGuard;
const Cmd = Interpreter.Cmd;
const ShellSyscall = shell.interpret.ShellSyscall;
const Allocator = std.mem.Allocator;
const ast = shell.AST;
const IO = shell.interpret.IO;
const CoroutineResult = shell.interpret.CoroutineResult;
