diff --git a/src/cli.zig b/src/cli.zig index 86650847b1..6f270421f9 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -567,6 +567,8 @@ pub const Command = struct { RootCommandMatcher.case("run") => .RunCommand, RootCommandMatcher.case("help") => .HelpCommand, + RootCommandMatcher.case("node") => .NodeCommand, + RootCommandMatcher.case("exec") => .ExecCommand, RootCommandMatcher.case("outdated") => .OutdatedCommand, @@ -997,6 +999,7 @@ pub const Command = struct { .BuildCommand => Arguments.build_params, .TestCommand => Arguments.test_params, .BunxCommand => Arguments.run_params, + .NodeCommand => Arguments.auto_only_params, else => Arguments.base_params_ ++ Arguments.runtime_params_ ++ Arguments.transpiler_params_, }; } diff --git a/src/cli/node_command.zig b/src/cli/node_command.zig index c1d6bcb083..086d01db60 100644 --- a/src/cli/node_command.zig +++ b/src/cli/node_command.zig @@ -145,17 +145,35 @@ pub const NodeCommand = struct { const cache_dir = try getNodeCacheDir(allocator); defer allocator.free(cache_dir); - std.fs.makeDirAbsolute(cache_dir) catch |err| { - if (err != error.PathAlreadyExists) return err; - }; + const cache_dirZ = try allocator.dupeZ(u8, cache_dir); + defer allocator.free(cache_dirZ); + + switch (bun.sys.mkdir(cache_dirZ, 0o755)) { + .result => {}, + .err => |err| { + if (err.errno != @intFromEnum(bun.sys.E.EXIST)) { + return err.toZigErr(); + } + }, + } const cache_file = try std.fs.path.join(allocator, &.{ cache_dir, ".version-cache" }); defer allocator.free(cache_file); - var file = try std.fs.createFileAbsolute(cache_file, .{}); - defer file.close(); + const cache_fileZ = try allocator.dupeZ(u8, cache_file); + defer allocator.free(cache_fileZ); - try file.writeAll(data); + const fd = switch (bun.sys.open(cache_fileZ, bun.O.WRONLY | bun.O.CREAT | bun.O.TRUNC, 0o644)) { + .result => |fd| fd, + .err => |err| return err.toZigErr(), + }; + defer fd.close(); + + var file = bun.sys.File{ .handle = fd }; + switch (file.writeAll(data)) { + .result => {}, + .err => |err| return err.toZigErr(), + } } fn fetchNodeVersion(allocator: std.mem.Allocator, filter: VersionFilter, major_version: ?[]const u8) ![]const u8 { @@ -325,13 +343,29 @@ pub const NodeCommand = struct { if (parent_dir) |parent| { std.fs.cwd().makePath(parent) catch {}; } - std.fs.makeDirAbsolute(dest_dir) catch |err| { - if (err != error.PathAlreadyExists) return err; - }; + + const dest_dirZ = try allocator.dupeZ(u8, dest_dir); + defer allocator.free(dest_dirZ); + + switch (bun.sys.mkdir(dest_dirZ, 0o755)) { + .result => {}, + .err => |err| { + if (err.errno != @intFromEnum(bun.sys.E.EXIST)) { + if (is_quiet) Output.prettyErrorln("", .{}); + Output.prettyErrorln("error: Failed to create directory {s}: {}", .{ dest_dir, err }); + Global.exit(1); + } + }, + } const temp_archive = try std.fmt.allocPrint(allocator, "{s}.download." ++ node_archive_ext, .{dest_dir}); defer allocator.free(temp_archive); - defer std.fs.deleteFileAbsolute(temp_archive) catch {}; + defer { + if (allocator.dupeZ(u8, temp_archive)) |temp_archiveZ| { + defer allocator.free(temp_archiveZ); + _ = bun.sys.unlink(temp_archiveZ); + } else |_| {} + } const url = URL.parse(url_str); var response_buffer = try MutableString.init(allocator, 0); @@ -351,30 +385,48 @@ pub const NodeCommand = struct { Global.exit(1); } - var file = try std.fs.createFileAbsolute(temp_archive, .{}); - defer file.close(); + const temp_archiveZ = try allocator.dupeZ(u8, temp_archive); + defer allocator.free(temp_archiveZ); - try file.writeAll(response_buffer.list.items); + const fd = switch (bun.sys.open(temp_archiveZ, bun.O.WRONLY | bun.O.CREAT | bun.O.TRUNC, 0o644)) { + .result => |fd| fd, + .err => |err| { + if (is_quiet) Output.prettyErrorln("", .{}); + Output.prettyErrorln("error: Failed to create file {s}: {}", .{ temp_archive, err }); + Global.exit(1); + }, + }; + defer fd.close(); - try extractNodeArchive(allocator, temp_archive, dest_dir, version); + var file = bun.sys.File{ .handle = fd }; + switch (file.writeAll(response_buffer.list.items)) { + .result => {}, + .err => |err| { + if (is_quiet) Output.prettyErrorln("", .{}); + Output.prettyErrorln("error: Failed to write archive {s}: {}", .{ temp_archive, err }); + Global.exit(1); + }, + } + + try extractNodeArchive(allocator, temp_archive, dest_dir, version, is_quiet); } - fn extractNodeArchive(allocator: std.mem.Allocator, archive_path: []const u8, dest_dir: []const u8, version: []const u8) !void { - // Read the archive file into memory + fn extractNodeArchive(allocator: std.mem.Allocator, archive_path: []const u8, dest_dir: []const u8, version: []const u8, is_quiet: bool) !void { + _ = version; const archive_data = std.fs.cwd().readFileAlloc(allocator, archive_path, std.math.maxInt(usize)) catch |err| { + if (is_quiet) Output.prettyErrorln("", .{}); Output.prettyErrorln("error: Failed to read archive {s}: {}", .{ archive_path, err }); Global.exit(1); }; defer allocator.free(archive_data); - // Open the destination directory var dest_dir_handle = std.fs.openDirAbsolute(dest_dir, .{}) catch |err| { + if (is_quiet) Output.prettyErrorln("", .{}); Output.prettyErrorln("error: Failed to open destination directory {s}: {}", .{ dest_dir, err }); Global.exit(1); }; defer dest_dir_handle.close(); - // Use Bun's built-in libarchive-based extraction _ = Archiver.extractToDir( archive_data, dest_dir_handle, @@ -382,32 +434,78 @@ pub const NodeCommand = struct { void, {}, .{ - .depth_to_skip = 0, - .close_handles = false, + .depth_to_skip = 1, + .close_handles = true, }, ) catch |err| { + if (is_quiet) Output.prettyErrorln("", .{}); Output.prettyErrorln("error: Failed to extract archive {s}: {}", .{ archive_path, err }); Global.exit(1); }; - const extracted_dir = try std.fmt.allocPrint(allocator, "{s}/node-v{s}-" ++ node_platform, .{ dest_dir, version }); - defer allocator.free(extracted_dir); - - const src_binary = try std.fs.path.join(allocator, &.{ extracted_dir, "bin", node_binary_name }); + const src_binary = if (Env.isWindows) + try std.fs.path.join(allocator, &.{ dest_dir, node_binary_name }) + else + try std.fs.path.join(allocator, &.{ dest_dir, "bin", node_binary_name }); defer allocator.free(src_binary); const dest_binary = try std.fs.path.join(allocator, &.{ dest_dir, node_binary_name }); defer allocator.free(dest_binary); - try std.fs.copyFileAbsolute(src_binary, dest_binary, .{}); + if (!Env.isWindows or !strings.eql(src_binary, dest_binary)) { + if (Env.isWindows) { + const src_binaryZ = try allocator.dupeZ(u8, src_binary); + defer allocator.free(src_binaryZ); + const dest_binaryZ = try allocator.dupeZ(u8, dest_binary); + defer allocator.free(dest_binaryZ); - if (Env.isPosix) { - var dest_file = try std.fs.openFileAbsolute(dest_binary, .{ .mode = .read_write }); - defer dest_file.close(); - try dest_file.chmod(0o755); + bun.copyFile(src_binaryZ, dest_binaryZ).unwrap() catch |err| { + if (is_quiet) Output.prettyErrorln("", .{}); + Output.prettyErrorln("error: Failed to copy binary: {}", .{err}); + Global.exit(1); + }; + } else { + const src_fd = switch (bun.sys.open(try allocator.dupeZ(u8, src_binary), bun.O.RDONLY, 0)) { + .result => |fd| fd, + .err => |err| { + if (is_quiet) Output.prettyErrorln("", .{}); + Output.prettyErrorln("error: Failed to open source binary: {}", .{err}); + Global.exit(1); + }, + }; + defer src_fd.close(); + + const dest_fd = switch (bun.sys.open(try allocator.dupeZ(u8, dest_binary), bun.O.WRONLY | bun.O.CREAT | bun.O.TRUNC, 0o755)) { + .result => |fd| fd, + .err => |err| { + if (is_quiet) Output.prettyErrorln("", .{}); + Output.prettyErrorln("error: Failed to create dest binary: {}", .{err}); + Global.exit(1); + }, + }; + defer dest_fd.close(); + + bun.copyFile(src_fd, dest_fd).unwrap() catch |err| { + if (is_quiet) Output.prettyErrorln("", .{}); + Output.prettyErrorln("error: Failed to copy binary: {}", .{err}); + Global.exit(1); + }; + } } - std.fs.deleteTreeAbsolute(extracted_dir) catch {}; + if (Env.isPosix) { + const dest_binaryZ = try allocator.dupeZ(u8, dest_binary); + defer allocator.free(dest_binaryZ); + + switch (bun.sys.chmod(dest_binaryZ, 0o755)) { + .result => {}, + .err => |err| { + if (is_quiet) Output.prettyErrorln("", .{}); + Output.prettyErrorln("error: Failed to chmod {s}: {}", .{ dest_binary, err }); + Global.exit(1); + }, + } + } } fn installNodeVersion(ctx: Command.Context, version_spec: []const u8, set_as_default: bool) !void { @@ -421,14 +519,14 @@ pub const NodeCommand = struct { const version_dir = try std.fmt.allocPrint(allocator, "{s}/node-{s}", .{ cache_dir, version }); defer allocator.free(version_dir); - const version_binary = try std.fmt.allocPrint(allocator, "{s}/" ++ node_binary_name, .{version_dir}); + const version_binary = try std.fmt.allocPrintZ(allocator, "{s}/" ++ node_binary_name, .{version_dir}); defer allocator.free(version_binary); - if (std.fs.accessAbsolute(version_binary, .{})) |_| { + if (bun.sys.access(version_binary, 0) == .result) { if (set_as_default) { Output.prettyln(" Node.js v{s} is already installed", .{version}); } - } else |_| { + } else { if (set_as_default) { try downloadNode(allocator, version, version_dir); Output.prettyln(" Successfully installed Node.js v{s}", .{version}); @@ -454,50 +552,70 @@ pub const NodeCommand = struct { const bin_dir = try getNodeBinDir(allocator); defer allocator.free(bin_dir); - std.fs.makeDirAbsolute(bin_dir) catch |err| { - if (err != error.PathAlreadyExists) return err; - }; + const bin_dirZ = try allocator.dupeZ(u8, bin_dir); + defer allocator.free(bin_dirZ); - const version_binary = try std.fmt.allocPrint(allocator, "{s}/node-{s}/" ++ node_binary_name, .{ cache_dir, version }); + switch (bun.sys.mkdir(bin_dirZ, 0o755)) { + .result => {}, + .err => |err| { + if (err.errno != @intFromEnum(bun.sys.E.EXIST)) { + return err.toZigErr(); + } + }, + } + + const version_binary = try std.fmt.allocPrintZ(allocator, "{s}/node-{s}/" ++ node_binary_name, .{ cache_dir, version }); defer allocator.free(version_binary); - std.fs.accessAbsolute(version_binary, .{}) catch { + if (bun.sys.access(version_binary, 0) != .result) { const version_dir = try std.fmt.allocPrint(allocator, "{s}/node-{s}", .{ cache_dir, version }); defer allocator.free(version_dir); Output.prettyErrorln("warn: Node.js v{s} binary not found, downloading...", .{version}); try downloadNode(allocator, version, version_dir); - }; + } - const global_binary = try std.fs.path.join(allocator, &.{ bin_dir, node_binary_name }); + const global_binary = try std.fs.path.joinZ(allocator, &.{ bin_dir, node_binary_name }); defer allocator.free(global_binary); - std.fs.deleteFileAbsolute(global_binary) catch {}; + _ = bun.sys.unlink(global_binary); - if (Env.isWindows) { - std.fs.createFileAbsolute(global_binary, .{}) catch {}; - std.fs.deleteFileAbsolute(global_binary) catch {}; + switch (bun.sys.link(u8, version_binary, global_binary)) { + .result => {}, + .err => |err| switch (err.getErrno()) { + .XDEV => { + if (Env.isWindows) { + bun.copyFile(version_binary, global_binary).unwrap() catch |copy_err| { + Output.prettyErrorln("error: Failed to copy Node binary: {}", .{copy_err}); + Global.exit(1); + }; + } else { + const src_fd = switch (bun.sys.open(version_binary, bun.O.RDONLY, 0)) { + .result => |fd| fd, + .err => |open_err| { + Output.prettyErrorln("error: Failed to open source binary: {}", .{open_err}); + Global.exit(1); + }, + }; + defer src_fd.close(); - std.fs.Dir.hardLink( - std.fs.cwd(), - version_binary, - std.fs.cwd(), - global_binary, - ) catch |err| { - if (err == error.NotSameFileSystem) { - try std.fs.copyFileAbsolute(version_binary, global_binary, .{}); - } else { - return err; - } - }; - } else { - std.posix.link(version_binary, global_binary) catch |err| { - if (err == error.CrossDevice) { - try std.fs.copyFileAbsolute(version_binary, global_binary, .{}); - } else { - return err; - } - }; + const dest_fd = switch (bun.sys.open(global_binary, bun.O.WRONLY | bun.O.CREAT | bun.O.TRUNC, 0o755)) { + .result => |fd| fd, + .err => |open_err| { + Output.prettyErrorln("error: Failed to create dest binary: {}", .{open_err}); + Global.exit(1); + }, + }; + defer dest_fd.close(); + + bun.copyFile(src_fd, dest_fd).unwrap() catch |copy_err| { + Output.prettyErrorln("error: Failed to copy Node binary: {}", .{copy_err}); + Global.exit(1); + }; + } + }, + else => return err.toZigErr(), + }, } } @@ -512,11 +630,19 @@ pub const NodeCommand = struct { const bin_dir = try getNodeBinDir(allocator); defer allocator.free(bin_dir); - std.fs.makeDirAbsolute(bin_dir) catch |err| { - if (err != error.PathAlreadyExists) return err; - }; + const bin_dirZ2 = try allocator.dupeZ(u8, bin_dir); + defer allocator.free(bin_dirZ2); - const global_binary = try std.fs.path.join(allocator, &.{ bin_dir, node_binary_name }); + switch (bun.sys.mkdir(bin_dirZ2, 0o755)) { + .result => {}, + .err => |err| { + if (err.errno != @intFromEnum(bun.sys.E.EXIST)) { + return err.toZigErr(); + } + }, + } + + const global_binary = try std.fs.path.joinZ(allocator, &.{ bin_dir, node_binary_name }); defer allocator.free(global_binary); const bun_exe = bun.selfExePath() catch { @@ -524,29 +650,47 @@ pub const NodeCommand = struct { Global.crash(); }; - std.fs.deleteFileAbsolute(global_binary) catch {}; + _ = bun.sys.unlink(global_binary); - if (Env.isWindows) { - std.fs.Dir.hardLink( - std.fs.cwd(), - bun_exe, - std.fs.cwd(), - global_binary, - ) catch |err| { - if (err == error.NotSameFileSystem) { - try std.fs.copyFileAbsolute(bun_exe, global_binary, .{}); - } else { - return err; - } - }; - } else { - std.posix.link(bun_exe, global_binary) catch |err| { - if (err == error.CrossDevice) { - try std.fs.copyFileAbsolute(bun_exe, global_binary, .{}); - } else { - return err; - } - }; + switch (bun.sys.link(u8, bun_exe, global_binary)) { + .result => {}, + .err => |err| switch (err.getErrno()) { + .XDEV => { + if (Env.isWindows) { + const bun_exeZ = try allocator.dupeZ(u8, bun_exe); + defer allocator.free(bun_exeZ); + + bun.copyFile(bun_exeZ, global_binary).unwrap() catch |copy_err| { + Output.prettyErrorln("error: Failed to copy Bun binary: {}", .{copy_err}); + Global.exit(1); + }; + } else { + const src_fd = switch (bun.sys.open(try allocator.dupeZ(u8, bun_exe), bun.O.RDONLY, 0)) { + .result => |fd| fd, + .err => |open_err| { + Output.prettyErrorln("error: Failed to open Bun binary: {}", .{open_err}); + Global.exit(1); + }, + }; + defer src_fd.close(); + + const dest_fd = switch (bun.sys.open(global_binary, bun.O.WRONLY | bun.O.CREAT | bun.O.TRUNC, 0o755)) { + .result => |fd| fd, + .err => |open_err| { + Output.prettyErrorln("error: Failed to create dest binary: {}", .{open_err}); + Global.exit(1); + }, + }; + defer dest_fd.close(); + + bun.copyFile(src_fd, dest_fd).unwrap() catch |copy_err| { + Output.prettyErrorln("error: Failed to copy Bun binary: {}", .{copy_err}); + Global.exit(1); + }; + } + }, + else => return err.toZigErr(), + }, } Output.prettyln(" Successfully aliased 'node' to Bun", .{}); @@ -566,9 +710,12 @@ pub const NodeCommand = struct { const version_binary = try std.fmt.allocPrint(allocator, "{s}/node-{s}/" ++ node_binary_name, .{ cache_dir, version }); defer allocator.free(version_binary); - if (std.fs.accessAbsolute(version_binary, .{})) |_| { + const version_binaryZ3 = try allocator.dupeZ(u8, version_binary); + defer allocator.free(version_binaryZ3); + + if (bun.sys.access(version_binaryZ3, 0) == .result) { try runNode(allocator, version_binary, args); - } else |_| { + } else { try installNodeVersion(ctx, version_spec, false); try runNode(allocator, version_binary, args); } @@ -580,13 +727,13 @@ pub const NodeCommand = struct { const bin_dir = try getNodeBinDir(allocator); defer allocator.free(bin_dir); - const node_symlink = try std.fs.path.join(allocator, &.{ bin_dir, node_binary_name }); + const node_symlink = try std.fs.path.joinZ(allocator, &.{ bin_dir, node_binary_name }); defer allocator.free(node_symlink); - if (std.fs.accessAbsolute(node_symlink, .{})) |_| { + if (bun.sys.access(node_symlink, 0) == .result) { try runNode(allocator, node_symlink, args); return; - } else |_| {} + } var path_buf2: bun.PathBuffer = undefined; const path_env2 = bun.getenvZ("PATH") orelse ""; @@ -740,11 +887,11 @@ pub const NodeCommand = struct { } break; } else if (entry.len > 0) { - const test_node = try std.fs.path.join(allocator, &.{ entry, "node" }); + const test_node = try std.fs.path.joinZ(allocator, &.{ entry, "node" }); defer allocator.free(test_node); - if (std.fs.accessAbsolute(test_node, .{})) |_| { + if (bun.sys.access(test_node, 0) == .result) { found_other_dir = true; - } else |_| {} + } } }