mirror of
https://github.com/oven-sh/bun
synced 2026-02-11 11:29:02 +00:00
[bun install] Print linked bin names and improve output
This commit is contained in:
@@ -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;
|
||||
|
||||
target_buf[0..".bin/".len].* = ".bin/".*;
|
||||
var from_remain: []u8 = target_buf[".bin/".len..];
|
||||
dest_buf[0.."../".len].* = "../".*;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
// 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];
|
||||
|
||||
file.close();
|
||||
}
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user