aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/install/bin.zig237
-rw-r--r--src/install/install.zig324
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();