diff options
author | 2022-02-12 01:23:19 -0800 | |
---|---|---|
committer | 2022-02-12 01:23:19 -0800 | |
commit | 89c99700f664cb4e32f9af1bc4b18ef5064129da (patch) | |
tree | 730d7f9098f279142cbffad5aa0bda40f2a851f3 /src | |
parent | 7c6386d81e2233dadd4816bb3e3029ec9befaaa8 (diff) | |
download | bun-89c99700f664cb4e32f9af1bc4b18ef5064129da.tar.gz bun-89c99700f664cb4e32f9af1bc4b18ef5064129da.tar.zst bun-89c99700f664cb4e32f9af1bc4b18ef5064129da.zip |
[bun install] Print linked bin names and improve output
Diffstat (limited to 'src')
-rw-r--r-- | src/install/bin.zig | 237 | ||||
-rw-r--r-- | src/install/install.zig | 324 |
2 files changed, 369 insertions, 192 deletions
diff --git a/src/install/bin.zig b/src/install/bin.zig index 15e768bca..563a87a60 100644 --- a/src/install/bin.zig +++ b/src/install/bin.zig @@ -9,6 +9,7 @@ const Path = @import("../resolver/resolve_path.zig"); const C = @import("../c.zig"); const Fs = @import("../fs.zig"); const stringZ = @import("../global.zig").stringZ; +const Resolution = @import("./resolution.zig").Resolution; /// Normalized `bin` field in [package.json](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#bin) /// Can be a: @@ -124,6 +125,70 @@ pub const Bin = extern struct { map = 4, }; + pub const NamesIterator = struct { + bin: Bin, + i: usize = 0, + done: bool = false, + dir_iterator: ?std.fs.Dir.Iterator = null, + package_name: String, + package_installed_node_modules: std.fs.Dir = std.fs.Dir{ .fd = std.math.maxInt(std.os.fd_t) }, + buf: [std.fs.MAX_PATH_BYTES]u8 = undefined, + string_buffer: []const u8, + + fn nextInDir(this: *NamesIterator) !?[]const u8 { + if (this.done) return null; + if (this.dir_iterator == null) { + var target = this.bin.value.dir.slice(this.string_buffer); + var parts = [_][]const u8{ this.package_name.slice(this.string_buffer), target }; + if (strings.hasPrefix(target, "./")) { + target = target[2..]; + } + var dir = this.package_installed_node_modules; + + var joined = Path.joinStringBuf(&this.buf, &parts, .auto); + this.buf[joined.len] = 0; + var joined_: [:0]u8 = this.buf[0..joined.len :0]; + var child_dir = try dir.openDirZ(joined_, .{ .iterate = true }); + this.dir_iterator = child_dir.iterate(); + } + + var iter = &this.dir_iterator.?; + if (iter.next() catch null) |entry| { + this.i += 1; + return entry.name; + } else { + this.done = true; + this.dir_iterator.?.dir.close(); + this.dir_iterator = null; + return null; + } + } + + /// next filename, e.g. "babel" instead of "cli.js" + pub fn next(this: *NamesIterator) !?[]const u8 { + switch (this.bin.tag) { + .file => { + if (this.i > 0) return null; + this.i += 1; + this.done = true; + const base = std.fs.path.basename(this.bin.value.file.slice(this.string_buffer)); + if (strings.hasPrefix(base, "./")) return base[2..]; + return base; + }, + .named_file => { + if (this.i > 0) return null; + this.i += 1; + this.done = true; + const base = std.fs.path.basename(this.bin.value.named_file[0].slice(this.string_buffer)); + if (strings.hasPrefix(base, "./")) return base[2..]; + return base; + }, + .dir => return try this.nextInDir(), + else => return null, + } + } + }; + pub const Linker = struct { bin: Bin, @@ -133,7 +198,8 @@ pub const Bin = extern struct { /// Used for generating relative paths package_name: strings.StringOrTinyString, - global_bin_dir: stringZ = "", + global_bin_dir: std.fs.Dir, + global_bin_path: stringZ = "", string_buf: []const u8, @@ -152,9 +218,6 @@ pub const Bin = extern struct { return name_[(std.mem.indexOfScalar(u8, name_, '/') orelse return name) + 1 ..]; } - // Sometimes, packages set "bin" to a file not marked as executable in the tarball - // They want it to be executable though - // so we make it executable fn setPermissions(this: *const Linker, target: [:0]const u8) void { // we use fchmodat to avoid any issues with current working directory _ = C.fchmodat(this.root_node_modules_folder, target, umask | 0o777, 0); @@ -163,15 +226,38 @@ pub const Bin = extern struct { // It is important that we use symlinkat(2) with relative paths instead of symlink() // That way, if you move your node_modules folder around, the symlinks in .bin still work // If we used absolute paths for the symlinks, you'd end up with broken symlinks - pub fn link(this: *Linker) void { + pub fn link(this: *Linker, link_global: bool) void { var target_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; var dest_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + var from_remain: []u8 = &target_buf; + var remain: []u8 = &dest_buf; + + if (!link_global) { + target_buf[0..".bin/".len].* = ".bin/".*; + from_remain = target_buf[".bin/".len..]; + dest_buf[0.."../".len].* = "../".*; + remain = dest_buf["../".len..]; + } else { + if (this.global_bin_dir.fd >= std.math.maxInt(std.os.fd_t)) { + this.err = error.MissingGlobalBinDir; + return; + } + + @memcpy(&target_buf, this.global_bin_path.ptr, this.global_bin_path.len); + from_remain = target_buf[this.global_bin_path.len..]; + from_remain[0] = std.fs.path.sep; + from_remain = from_remain[1..]; + const abs = std.os.getFdPath(this.root_node_modules_folder, &dest_buf) catch |err| { + this.err = err; + return; + }; + remain = remain[abs.len..]; + remain[0] = std.fs.path.sep; + remain = remain[1..]; + + this.root_node_modules_folder = this.global_bin_dir.fd; + } - target_buf[0..".bin/".len].* = ".bin/".*; - var from_remain: []u8 = target_buf[".bin/".len..]; - dest_buf[0.."../".len].* = "../".*; - - var remain: []u8 = dest_buf["../".len..]; const name = this.package_name.slice(); std.mem.copy(u8, remain, name); remain = remain[name.len..]; @@ -288,7 +374,10 @@ pub const Bin = extern struct { target_buf_remain = target_buf_remain[entry.name.len..]; target_buf_remain[0] = 0; var from_path: [:0]u8 = target_buf[0 .. @ptrToInt(target_buf_remain.ptr) - @ptrToInt(&target_buf) :0]; - var to_path = std.fmt.bufPrintZ(&dest_buf, ".bin/{s}", .{entry.name}) catch unreachable; + var to_path = if (!link_global) + std.fmt.bufPrintZ(&dest_buf, ".bin/{s}", .{entry.name}) catch unreachable + else + std.fmt.bufPrintZ(&dest_buf, "{s}", .{entry.name}) catch unreachable; std.os.symlinkatZ( from_path, @@ -317,131 +406,5 @@ pub const Bin = extern struct { }, } } - - // fn linkGlobalSymlink(this: *Linker, realpath: string, filename_in_terminal: string) void {} - - // pub fn linkGlobal(this: *Linker) void { - // var target_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - // const name = this.package_name.slice(); - - // if (comptime Environment.isWindows) { - // @compileError("Bin.Linker.link() needs to be updated to generate .cmd files on Windows"); - // } - - // switch (this.bin.tag) { - // .none => { - // if (comptime Environment.isDebug) { - // unreachable; - // } - // }, - // .file => { - // var target = this.bin.value.file.slice(this.string_buf); - - // if (strings.hasPrefix(target, "./")) { - // target = target[2..]; - // } - // @memcpy(&target_buf, target.ptr, target.len); - - // // we need to use the unscoped package name here - // // this is why @babel/parser would fail to link - // const unscoped_name = unscopedPackageName(name); - - // }, - // .named_file => { - // var target = this.bin.value.named_file[1].slice(this.string_buf); - // if (strings.hasPrefix(target, "./")) { - // target = target[2..]; - // } - // std.mem.copy(u8, remain, target); - // remain = remain[target.len..]; - // remain[0] = 0; - // const target_len = @ptrToInt(remain.ptr) - @ptrToInt(&dest_buf); - // remain = remain[1..]; - - // var target_path: [:0]u8 = dest_buf[0..target_len :0]; - // var name_to_use = this.bin.value.named_file[0].slice(this.string_buf); - // std.mem.copy(u8, from_remain, name_to_use); - // from_remain = from_remain[name_to_use.len..]; - // from_remain[0] = 0; - // var dest_path: [:0]u8 = target_buf[0 .. @ptrToInt(from_remain.ptr) - @ptrToInt(&target_buf) :0]; - - // std.os.symlinkatZ(target_path, this.root_node_modules_folder, dest_path) catch |err| { - // // Silently ignore PathAlreadyExists - // // Most likely, the symlink was already created by another package - // if (err == error.PathAlreadyExists) { - // this.setPermissions(dest_path); - // return; - // } - - // this.err = err; - // }; - // this.setPermissions(dest_path); - // }, - // .dir => { - // var target = this.bin.value.dir.slice(this.string_buf); - // var parts = [_][]const u8{ name, target }; - // if (strings.hasPrefix(target, "./")) { - // target = target[2..]; - // } - // std.mem.copy(u8, remain, target); - // remain = remain[target.len..]; - // remain[0] = 0; - // var dir = std.fs.Dir{ .fd = this.package_installed_node_modules }; - - // var joined = Path.joinStringBuf(&target_buf, &parts, .auto); - // target_buf[joined.len] = 0; - // var joined_: [:0]u8 = target_buf[0..joined.len :0]; - // var child_dir = dir.openDirZ(joined_, .{ .iterate = true }) catch |err| { - // this.err = err; - // return; - // }; - // defer child_dir.close(); - - // var iter = child_dir.iterate(); - - // var basedir_path = std.os.getFdPath(child_dir.fd, &target_buf) catch |err| { - // this.err = err; - // return; - // }; - // target_buf[basedir_path.len] = std.fs.path.sep; - // var target_buf_remain = target_buf[basedir_path.len + 1 ..]; - - // while (iter.next() catch null) |entry_| { - // const entry: std.fs.Dir.Entry = entry_; - // switch (entry.kind) { - // std.fs.Dir.Entry.Kind.SymLink, std.fs.Dir.Entry.Kind.File => { - // std.mem.copy(u8, target_buf_remain, entry.name); - // target_buf_remain = target_buf_remain[entry.name.len..]; - // target_buf_remain[0] = 0; - // var from_path: [:0]u8 = target_buf[0 .. @ptrToInt(target_buf_remain.ptr) - @ptrToInt(&target_buf) :0]; - // var to_path = std.fmt.bufPrintZ(&dest_buf, ".bin/{s}", .{entry.name}) catch unreachable; - - // std.os.symlinkatZ( - // from_path, - // this.root_node_modules_folder, - // to_path, - // ) catch |err| { - - // // Silently ignore PathAlreadyExists - // // Most likely, the symlink was already created by another package - // if (err == error.PathAlreadyExists) { - // this.setPermissions(to_path); - // continue; - // } - - // this.err = err; - // continue; - // }; - // this.setPermissions(to_path); - // }, - // else => {}, - // } - // } - // }, - // .map => { - // this.err = error.NotImplementedYet; - // }, - // } - // } }; }; diff --git a/src/install/install.zig b/src/install/install.zig index 8eeebb752..cc46c45cd 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -400,7 +400,7 @@ pub const Lockfile = struct { // Serialized data /// The version of the lockfile format, intended to prevent data corruption for format changes. - format: FormatVersion = .v0, + format: FormatVersion = .v1, /// packages: Lockfile.Package.List = Lockfile.Package.List{}, @@ -1074,6 +1074,8 @@ pub const Lockfile = struct { options: PackageManager.Options, successfully_installed: ?Bitset = null, + updates: []const PackageManager.UpdateRequest = &[_]PackageManager.UpdateRequest{}, + pub const Format = enum { yarn }; var lockfile_path_buf1: [std.fs.MAX_PATH_BYTES]u8 = undefined; @@ -1202,21 +1204,42 @@ pub const Lockfile = struct { var slice = this.lockfile.packages.slice(); const names: []const String = slice.items(.name); + const names_hashes: []const PackageNameHash = slice.items(.name_hash); + const bins: []const Bin = slice.items(.bin); const resolved: []const Resolution = slice.items(.resolution); if (names.len == 0) return; const resolutions_list = slice.items(.resolutions); const resolutions_buffer = this.lockfile.buffers.resolutions.items; const string_buf = this.lockfile.buffers.string_bytes.items; + var id_map = try default_allocator.alloc(PackageID, this.updates.len); + std.mem.set(PackageID, id_map, std.math.maxInt(PackageID)); + defer if (id_map.len > 0) default_allocator.free(id_map); visited.set(0); const end = @truncate(PackageID, names.len); if (this.successfully_installed) |installed| { - for (resolutions_list[0].get(resolutions_buffer)) |package_id| { - if (package_id > end or !installed.isSet(package_id)) continue; + outer: for (resolutions_list[0].get(resolutions_buffer)) |package_id| { + if (package_id > end) continue; + const is_new = installed.isSet(package_id); const package_name = names[package_id].slice(string_buf); + if (this.updates.len > 0) { + const name_hash = names_hashes[package_id]; + for (this.updates) |update, update_id| { + if (update.name.len == package_name.len and name_hash == update.name_hash) { + if (id_map[update_id] == std.math.maxInt(PackageID)) { + id_map[update_id] = @truncate(PackageID, package_id); + } + + continue :outer; + } + } + } + + if (!is_new) continue; + const fmt = comptime brk: { if (enable_ansi_colors) { break :brk Output.prettyFmt("<r> <green>+<r> <b>{s}<r><d>@{}<r>\n", enable_ansi_colors); @@ -1234,9 +1257,22 @@ pub const Lockfile = struct { ); } } else { - for (names) |name, package_id| { + outer: for (names) |name, package_id| { const package_name = name.slice(string_buf); + if (this.updates.len > 0) { + const name_hash = names_hashes[package_id]; + for (this.updates) |update, update_id| { + if (update.name.len == package_name.len and name_hash == update.name_hash) { + if (id_map[update_id] == std.math.maxInt(PackageID)) { + id_map[update_id] = @truncate(PackageID, package_id); + } + + continue :outer; + } + } + } + try writer.print( comptime Output.prettyFmt(" <r><b>{s}<r><d>@<b>{}<r>\n", enable_ansi_colors), .{ @@ -1246,6 +1282,71 @@ pub const Lockfile = struct { ); } } + + if (this.updates.len > 0) { + try writer.writeAll("\n"); + } + + for (this.updates) |_, update_id| { + const package_id = id_map[update_id]; + if (package_id == std.math.maxInt(PackageID)) continue; + const name = names[package_id]; + const bin = bins[package_id]; + + const package_name = name.slice(string_buf); + + switch (bin.tag) { + .none, .map, .dir => { + const fmt = comptime brk: { + if (enable_ansi_colors) { + break :brk Output.prettyFmt("<r> <green>installed<r> <b>{s}<r><d>@{}<r>\n", enable_ansi_colors); + } else { + break :brk Output.prettyFmt("<r> installed {s}<r><d>@{}<r>\n", enable_ansi_colors); + } + }; + + try writer.print( + comptime Output.prettyFmt(fmt, enable_ansi_colors), + .{ + package_name, + resolved[package_id].fmt(string_buf), + }, + ); + }, + .file, .named_file => { + var iterator = Bin.NamesIterator{ .bin = bin, .package_name = name, .string_buffer = string_buf }; + + const fmt = comptime brk: { + if (enable_ansi_colors) { + break :brk Output.prettyFmt("<r> <green>installed<r> {s}<r><d>@{}<r> with binaries:\n", enable_ansi_colors); + } else { + break :brk Output.prettyFmt("<r> installed {s}<r><d>@{}<r> with binaries:\n", enable_ansi_colors); + } + }; + + try writer.print( + comptime Output.prettyFmt(fmt, enable_ansi_colors), + .{ + package_name, + resolved[package_id].fmt(string_buf), + }, + ); + + while (iterator.next() catch null) |bin_name| { + try writer.print( + comptime Output.prettyFmt("<r> <d>- <r><b>{s}<r>\n", enable_ansi_colors), + .{ + bin_name, + }, + ); + } + }, + } + } + + if (this.updates.len > 0) { + try writer.writeAll("\n"); + } } }; @@ -1430,7 +1531,7 @@ pub const Lockfile = struct { }; pub fn verifyData(this: *Lockfile) !void { - std.debug.assert(this.format == .v0); + std.debug.assert(this.format == Lockfile.FormatVersion.current); { var i: usize = 0; while (i < this.packages.len) : (i += 1) { @@ -1552,7 +1653,7 @@ pub const Lockfile = struct { pub fn initEmpty(this: *Lockfile, allocator: std.mem.Allocator) !void { this.* = Lockfile{ - .format = .v0, + .format = Lockfile.FormatVersion.current, .packages = Lockfile.Package.List{}, .buffers = Buffers{}, .package_index = PackageIndex.Map.initContext(allocator, .{}), @@ -1865,7 +1966,9 @@ pub const Lockfile = struct { pub const FormatVersion = enum(u32) { v0, + v1, _, + pub const current = FormatVersion.v1; }; pub const DependencySlice = ExternalSlice(Dependency); @@ -2927,6 +3030,7 @@ pub const Lockfile = struct { try writer.writeAll(alignment_bytes_to_repeat_buffer); _ = try std.os.pwrite(stream.handle, std.mem.asBytes(&end), pos); + try std.os.ftruncate(stream.handle, try stream.getPos()); } pub fn load( lockfile: *Lockfile, @@ -2943,10 +3047,10 @@ pub const Lockfile = struct { } var format = try reader.readIntLittle(u32); - if (format != @enumToInt(Lockfile.FormatVersion.v0)) { - return error.InvalidLockfileVersion; + if (format != @enumToInt(Lockfile.FormatVersion.current)) { + return error.@"Outdated lockfile version"; } - lockfile.format = .v0; + lockfile.format = Lockfile.FormatVersion.current; lockfile.allocator = allocator; const total_buffer_size = try reader.readIntLittle(u64); if (total_buffer_size > stream.buffer.len) { @@ -4909,6 +5013,7 @@ pub const PackageManager = struct { log_level: LogLevel = LogLevel.default, global: bool = false, + global_bin_dir: std.fs.Dir = std.fs.Dir{ .fd = std.math.maxInt(std.os.fd_t) }, /// destination directory to link bins into // must be a variable due to global installs and bunx bin_path: stringZ = "node_modules/.bin", @@ -4943,6 +5048,17 @@ pub const PackageManager = struct { native_bin_link_allowlist: []const PackageNameHash = &default_native_bin_link_allowlist, max_retry_count: u16 = 5, + pub fn isBinPathInPATH(this: *const Options) bool { + // must be absolute + if (this.bin_path[0] != std.fs.path.sep) return false; + var tokenizer = std.mem.split(std.os.getenvZ("PATH") orelse "", ":"); + const spanned = std.mem.span(this.bin_path); + while (tokenizer.next()) |token| { + if (strings.eql(token, spanned)) return true; + } + return false; + } + const default_native_bin_link_allowlist = [_]PackageNameHash{ String.Builder.stringHash("esbuild"), String.Builder.stringHash("turbo"), @@ -5009,7 +5125,7 @@ pub const PackageManager = struct { if (std.os.getenvZ("XDG_CACHE_HOME") orelse std.os.getenvZ("HOME")) |home_dir| { var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; - var parts = [_]string{ "./bun", "install", "global" }; + var parts = [_]string{ ".bun", "install", "global" }; var path = Path.joinAbsStringBuf(home_dir, &buf, &parts, .auto); return try std.fs.cwd().makeOpenPath(path, .{ .iterate = true }); } @@ -5017,6 +5133,41 @@ pub const PackageManager = struct { return error.@"No global directory found"; } + pub fn openGlobalBinDir(opts_: ?*const Api.BunInstall) !std.fs.Dir { + if (std.os.getenvZ("BUN_INSTALL_BIN")) |home_dir| { + return try std.fs.cwd().makeOpenPath(home_dir, .{ .iterate = true }); + } + + if (opts_) |opts| { + if (opts.global_bin_dir) |home_dir| { + if (home_dir.len > 0) { + return try std.fs.cwd().makeOpenPath(home_dir, .{ .iterate = true }); + } + } + } + + if (std.os.getenvZ("BUN_INSTALL")) |home_dir| { + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + var parts = [_]string{ + "bin", + }; + var path = Path.joinAbsStringBuf(home_dir, &buf, &parts, .auto); + return try std.fs.cwd().makeOpenPath(path, .{ .iterate = true }); + } + + if (std.os.getenvZ("XDG_CACHE_HOME") orelse std.os.getenvZ("HOME")) |home_dir| { + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + var parts = [_]string{ + ".bun", + "bin", + }; + var path = Path.joinAbsStringBuf(home_dir, &buf, &parts, .auto); + return try std.fs.cwd().makeOpenPath(path, .{ .iterate = true }); + } + + return error.@"Missing global bin directory: try setting $BUN_INSTALL"; + } + pub fn load( this: *Options, allocator: std.mem.Allocator, @@ -5532,7 +5683,7 @@ pub const PackageManager = struct { } }; - fn init( + pub fn init( ctx: Command.Context, package_json_file_: ?std.fs.File, comptime params: []const ParamType, @@ -5605,7 +5756,9 @@ pub const PackageManager = struct { std.mem.copy(u8, package_json_cwd_buf[fs.top_level_dir.len..], "package.json"); var entries_option = try fs.fs.readDirectory(fs.top_level_dir, null); - var options = Options{}; + var options = Options{ + .global = cli.global, + }; var env_loader: *DotEnv.Loader = brk: { var map = try ctx.allocator.create(DotEnv.Map); @@ -5737,7 +5890,7 @@ pub const PackageManager = struct { clap.parseParam("<POS> ... \"name\" of packages to remove from package.json") catch unreachable, }; - const CommandLineArguments = struct { + pub const CommandLineArguments = struct { registry: string = "", cache_dir: string = "", lockfile: string = "", @@ -5890,6 +6043,7 @@ pub const PackageManager = struct { const UpdateRequest = struct { name: string = "", + name_hash: PackageNameHash = 0, resolved_version_buf: string = "", version: Dependency.Version = Dependency.Version{}, version_buf: []const u8 = "", @@ -5975,7 +6129,7 @@ pub const PackageManager = struct { const sliced = SlicedString.init(request.version_buf, request.version_buf); request.version = Dependency.parse(allocator, request.version_buf, &sliced, log) orelse Dependency.Version{}; } - + request.name_hash = String.Builder.stringHash(request.name); update_requests.append(request) catch break; } @@ -6067,10 +6221,23 @@ pub const PackageManager = struct { \\ bun add {s} \\ bun add {s} \\ bun add {s} - \\ - \\<d>Shorthand: <b>bun a<r> + \\ bun add -g git-peek \\ , .{ examples_to_print[0], examples_to_print[1], examples_to_print[2] }); + + if (manager.options.global) { + Output.prettyErrorln( + \\ + \\<d>Shorthand: <b>bun a -g<r> + \\ + , .{}); + } else { + Output.prettyErrorln( + \\ + \\<d>Shorthand: <b>bun a<r> + \\ + , .{}); + } Output.flush(); Global.exit(0); }, @@ -6093,13 +6260,25 @@ pub const PackageManager = struct { \\ bun remove {s} {s} \\ bun remove {s} \\ - \\<d>Shorthand: <b>bun rm<r> - \\ , .{ examples_to_print[0], examples_to_print[1], examples_to_print[2], }); + if (manager.options.global) { + Output.prettyErrorln( + \\ + \\<d>Shorthand: <b>bun rm -g<r> + \\ + , .{}); + } else { + Output.prettyErrorln( + \\ + \\<d>Shorthand: <b>bun rm<r> + \\ + , .{}); + } + Output.flush(); Global.exit(0); @@ -6331,20 +6510,19 @@ pub const PackageManager = struct { iterator: while (iter.next() catch null) |entry| { switch (entry.kind) { std.fs.Dir.Entry.Kind.SymLink => { - if (std.fs.path.extension(entry.name).len == 0) { - // any symlinks which we are unable to open are assumed to be dangling - // note that using access won't work here, because access doesn't resolve symlinks - std.mem.copy(u8, &node_modules_buf, entry.name); - node_modules_buf[entry.name.len] = 0; - var buf: [:0]u8 = node_modules_buf[0..entry.name.len :0]; - - var file = node_modules_bin.openFileZ(buf, .{ .read = true }) catch { - node_modules_bin.deleteFileZ(buf) catch {}; - continue :iterator; - }; - - file.close(); - } + + // any symlinks which we are unable to open are assumed to be dangling + // note that using access won't work here, because access doesn't resolve symlinks + std.mem.copy(u8, &node_modules_buf, entry.name); + node_modules_buf[entry.name.len] = 0; + var buf: [:0]u8 = node_modules_buf[0..entry.name.len :0]; + + var file = node_modules_bin.openFileZ(buf, .{ .read = true }) catch { + node_modules_bin.deleteFileZ(buf) catch {}; + continue :iterator; + }; + + file.close(); }, else => {}, } @@ -6401,6 +6579,7 @@ pub const PackageManager = struct { resolutions: []Resolution, node: *Progress.Node, has_created_bin: bool = false, + global_bin_dir: std.fs.Dir, destination_dir_subpath_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined, install_count: usize = 0, successfully_installed: Bitset, @@ -6487,8 +6666,10 @@ pub const PackageManager = struct { const bin = this.bins[package_id]; if (bin.tag != .none) { if (!this.has_created_bin) { - this.node_modules_folder.makeDirZ(".bin") catch {}; Bin.Linker.umask = C.umask(0); + if (!this.options.global) + this.node_modules_folder.makeDirZ(".bin") catch {}; + this.has_created_bin = true; } @@ -6507,15 +6688,15 @@ pub const PackageManager = struct { var bin_linker = Bin.Linker{ .bin = bin, .package_installed_node_modules = this.node_modules_folder.fd, - .global_bin_dir = this.manager.options.bin_path, + .global_bin_path = this.options.bin_path, + .global_bin_dir = this.options.global_bin_dir, // .destination_dir_subpath = destination_dir_subpath, .root_node_modules_folder = this.root_node_modules_folder.fd, .package_name = strings.StringOrTinyString.init(name), .string_buf = buf, }; - bin_linker.link(); - + bin_linker.link(this.manager.options.global); if (comptime log_level != .silent) { if (bin_linker.err) |err| { const fmt = "\n<r><red>error:<r> linking <b>{s}<r>: {s}\n"; @@ -6727,6 +6908,7 @@ pub const PackageManager = struct { .skip_verify = skip_verify, .skip_delete = skip_delete, .summary = &summary, + .global_bin_dir = this.options.global_bin_dir, .force_install = force_install, .install_count = lockfile.buffers.hoisted_packages.items.len, .successfully_installed = try Bitset.initEmpty(lockfile.packages.len, this.allocator), @@ -6818,7 +7000,9 @@ pub const PackageManager = struct { const name: string = installer.names[resolved_id].slice(lockfile.buffers.string_bytes.items); if (!installer.has_created_bin) { - node_modules_folder.makeDirZ(".bin") catch {}; + if (!this.options.global) { + node_modules_folder.makeDirZ(".bin") catch {}; + } Bin.Linker.umask = C.umask(0); installer.has_created_bin = true; } @@ -6827,12 +7011,14 @@ pub const PackageManager = struct { .bin = original_bin, .package_installed_node_modules = folder.fd, .root_node_modules_folder = node_modules_folder.fd, - .global_bin_dir = installer.manager.options.bin_path, + .global_bin_path = this.options.bin_path, + .global_bin_dir = this.options.global_bin_dir, + .package_name = strings.StringOrTinyString.init(name), .string_buf = lockfile.buffers.string_bytes.items, }; - bin_linker.link(); + bin_linker.link(this.options.global); if (comptime log_level != .silent) { if (bin_linker.err) |err| { @@ -6874,6 +7060,15 @@ pub const PackageManager = struct { return summary; } + pub fn setupGlobalDir(manager: *PackageManager, ctx: *const Command.Context) !void { + manager.options.global_bin_dir = try Options.openGlobalBinDir(ctx.install); + var out_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; + var result = try std.os.getFdPath(manager.options.global_bin_dir.fd, &out_buffer); + out_buffer[result.len] = 0; + var result_: [:0]u8 = out_buffer[0..result.len :0]; + manager.options.bin_path = std.meta.assumeSentinel(try FileSystem.instance.dirname_store.append([:0]u8, result_), 0); + } + fn installWithManager( ctx: Command.Context, manager: *PackageManager, @@ -6925,9 +7120,9 @@ pub const PackageManager = struct { } if (manager.options.enable.fail_early) { - Output.prettyError("<b>Failed to load lockfile<r>\n", .{}); + Output.prettyError("<b><red>failed to load lockfile<r>\n", .{}); } else { - Output.prettyError("<b>Ignoring lockfile<r>\n", .{}); + Output.prettyError("<b><red>ignoring lockfile<r>\n", .{}); } if (ctx.log.errors > 0) { @@ -6991,7 +7186,7 @@ pub const PackageManager = struct { if (manager.options.enable.frozen_lockfile and had_any_diffs) { if (log_level != .silent) { - Output.prettyErrorln("<r><red>error<r>: Lockfile had changes, but lockfile is frozen", .{}); + Output.prettyErrorln("<r><red>error<r>: lockfile had changes, but lockfile is frozen", .{}); } Global.exit(1); @@ -7077,7 +7272,7 @@ pub const PackageManager = struct { if (manager.options.enable.frozen_lockfile) { if (log_level != .silent) { - Output.prettyErrorln("<r><red>error<r>: Lockfile had changes, but lockfile is frozen", .{}); + Output.prettyErrorln("<r><red>error<r>: lockfile had changes, but lockfile is frozen", .{}); } Global.exit(1); @@ -7170,6 +7365,10 @@ pub const PackageManager = struct { manager.lockfile.verifyResolutions(manager.options.local_package_features, manager.options.remote_package_features, log_level); } + if (manager.options.global) { + try manager.setupGlobalDir(&ctx); + } + if (manager.options.do.save_lockfile) { save: { if (manager.lockfile.isEmpty()) { @@ -7185,7 +7384,10 @@ pub const PackageManager = struct { break :save; }; } - if (log_level != .silent) Output.prettyErrorln("No packages! Deleted empty lockfile", .{}); + if (!manager.options.global) { + if (log_level != .silent) Output.prettyErrorln("No packages! Deleted empty lockfile", .{}); + } + break :save; } @@ -7241,6 +7443,7 @@ pub const PackageManager = struct { var printer = Lockfile.Printer{ .lockfile = manager.lockfile, .options = manager.options, + .updates = manager.package_json_updates, .successfully_installed = install_summary.successfully_installed, }; if (Output.enable_ansi_colors) { @@ -7248,16 +7451,20 @@ pub const PackageManager = struct { } else { try Lockfile.Printer.Tree.print(&printer, Output.WriterType, Output.writer(), false); } - + var printed_timestamp = false; if (install_summary.success > 0) { - Output.pretty("\n <green>{d}<r> packages<r> installed ", .{install_summary.success}); + // it's confusing when it shows 3 packages and says it installed 1 + Output.pretty("\n <green>{d}<r> packages<r> installed ", .{@maximum( + install_summary.success, + @truncate( + u32, + manager.package_json_updates.len, + ), + )}); Output.printStartEndStdout(ctx.start_time, std.time.nanoTimestamp()); + printed_timestamp = true; Output.pretty("<r>\n", .{}); - if (manager.summary.update > 0) { - Output.pretty(" Updated: <cyan>{d}<r>\n", .{manager.summary.update}); - } - if (manager.summary.remove > 0) { Output.pretty(" Removed: <cyan>{d}<r>\n", .{manager.summary.remove}); } @@ -7270,8 +7477,9 @@ pub const PackageManager = struct { Output.pretty("\n <r><b>{d}<r> packages removed ", .{manager.summary.remove}); Output.printStartEndStdout(ctx.start_time, std.time.nanoTimestamp()); + printed_timestamp = true; Output.pretty("<r>\n", .{}); - } else if (install_summary.skipped > 0 and install_summary.fail == 0) { + } else if (install_summary.skipped > 0 and install_summary.fail == 0 and manager.package_json_updates.len == 0) { Output.pretty("\n", .{}); const count = @truncate(PackageID, manager.lockfile.packages.len); @@ -7281,20 +7489,26 @@ pub const PackageManager = struct { count, }); Output.printStartEndStdout(ctx.start_time, std.time.nanoTimestamp()); + printed_timestamp = true; Output.pretty("<r>\n", .{}); } else { - Output.pretty("<r> Done! Checked <green>{d} packages<r> <d>(no changes)<r> ", .{ + Output.pretty("<r> <green>Done<r>! Checked {d} packages<r> <d>(no changes)<r> ", .{ install_summary.skipped, }); Output.printStartEndStdout(ctx.start_time, std.time.nanoTimestamp()); + printed_timestamp = true; Output.pretty("<r>\n", .{}); } - } else if (manager.summary.update > 0) { - Output.prettyln(" Updated: <cyan>{d}<r>\n", .{manager.summary.update}); } if (install_summary.fail > 0) { - Output.prettyln("<r> Failed to install <red><b>{d}<r> packages", .{install_summary.fail}); + Output.prettyln("<r> Failed to install <red><b>{d}<r> packages\n", .{install_summary.fail}); + } + + if (!printed_timestamp) { + Output.printStartEndStdout(ctx.start_time, std.time.nanoTimestamp()); + Output.prettyln("<d> done<r>", .{}); + printed_timestamp = true; } } Output.flush(); |