aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/cli/package_manager_command.zig4
-rw-r--r--src/install/install.zig65
-rw-r--r--src/install/lockfile.zig36
-rwxr-xr-xtest/bun.lockbbin150672 -> 149736 bytes
-rw-r--r--test/cli/install/bun-install.test.ts164
5 files changed, 187 insertions, 82 deletions
diff --git a/src/cli/package_manager_command.zig b/src/cli/package_manager_command.zig
index 4408568e0..cb03b9e3f 100644
--- a/src/cli/package_manager_command.zig
+++ b/src/cli/package_manager_command.zig
@@ -51,7 +51,7 @@ pub const PackageManagerCommand = struct {
@memcpy(lockfile_buffer[0..lockfile_.len], lockfile_);
lockfile_buffer[lockfile_.len] = 0;
var lockfile = lockfile_buffer[0..lockfile_.len :0];
- var pm = try PackageManager.init(ctx, null, PackageManager.Subcommand.pm);
+ var pm = try PackageManager.init(ctx, PackageManager.Subcommand.pm);
const load_lockfile = pm.lockfile.loadFromDisk(ctx.allocator, ctx.log, lockfile);
handleLoadLockfileErrors(load_lockfile, pm);
@@ -87,7 +87,7 @@ pub const PackageManagerCommand = struct {
var args = try std.process.argsAlloc(ctx.allocator);
args = args[1..];
- var pm = PackageManager.init(ctx, null, PackageManager.Subcommand.pm) catch |err| {
+ var pm = PackageManager.init(ctx, PackageManager.Subcommand.pm) catch |err| {
// TODO: error messages here
// if (err == error.MissingPackageJSON) {
// // TODO: error messages
diff --git a/src/install/install.zig b/src/install/install.zig
index 0a0c21637..2c78c81a2 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -5088,15 +5088,13 @@ pub const PackageManager = struct {
pub fn init(
ctx: Command.Context,
- package_json_file_: ?std.fs.File,
comptime subcommand: Subcommand,
) !*PackageManager {
- return initMaybeInstall(ctx, package_json_file_, subcommand);
+ return initMaybeInstall(ctx, subcommand);
}
fn initMaybeInstall(
ctx: Command.Context,
- package_json_file_: ?std.fs.File,
comptime subcommand: Subcommand,
) !*PackageManager {
const cli = try CommandLineArguments.parse(ctx.allocator, subcommand);
@@ -5108,12 +5106,11 @@ pub const PackageManager = struct {
}
var _ctx = ctx;
- return initWithCLI(&_ctx, package_json_file_, cli, subcommand);
+ return initWithCLI(&_ctx, cli, subcommand);
}
fn initWithCLI(
ctx: *Command.Context,
- package_json_file_: ?std.fs.File,
cli: CommandLineArguments,
comptime subcommand: Subcommand,
) !*PackageManager {
@@ -5138,7 +5135,7 @@ pub const PackageManager = struct {
//
// We will walk up from the cwd, calling chdir on each directory until we find a package.json
// If we fail to find one, we will report an error saying no packages to install
- const package_json_file = package_json_file_ orelse brk: {
+ const package_json_file = brk: {
var this_cwd = original_cwd;
const child_json = child: {
while (true) {
@@ -5437,13 +5434,13 @@ pub const PackageManager = struct {
return manager;
}
- fn attemptToCreatePackageJSON() !std.fs.File {
- var package_json_file = std.fs.cwd().createFileZ("package.json", .{ .read = true }) catch |err| {
+ fn attemptToCreatePackageJSON() !void {
+ const package_json_file = std.fs.cwd().createFileZ("package.json", .{ .read = true }) catch |err| {
Output.prettyErrorln("<r><red>error:<r> {s} create package.json", .{@errorName(err)});
Global.crash();
};
+ defer package_json_file.close();
try package_json_file.pwriteAll("{\"dependencies\": {}}", 0);
- return package_json_file;
}
pub inline fn add(
@@ -5461,16 +5458,13 @@ pub const PackageManager = struct {
pub inline fn link(
ctx: Command.Context,
) !void {
- var manager = PackageManager.init(ctx, null, .link) catch |err| brk: {
- switch (err) {
- error.MissingPackageJSON => {
- var package_json_file = try attemptToCreatePackageJSON();
- break :brk try PackageManager.init(ctx, package_json_file, .link);
- },
- else => return err,
+ var manager = PackageManager.init(ctx, .link) catch |err| brk: {
+ if (err == error.MissingPackageJSON) {
+ try attemptToCreatePackageJSON();
+ break :brk try PackageManager.init(ctx, .link);
}
- unreachable;
+ return err;
};
if (manager.options.shouldPrintCommandName()) {
@@ -5616,16 +5610,13 @@ pub const PackageManager = struct {
pub inline fn unlink(
ctx: Command.Context,
) !void {
- var manager = PackageManager.init(ctx, null, .unlink) catch |err| brk: {
- switch (err) {
- error.MissingPackageJSON => {
- var package_json_file = try attemptToCreatePackageJSON();
- break :brk try PackageManager.init(ctx, package_json_file, .unlink);
- },
- else => return err,
+ var manager = PackageManager.init(ctx, .unlink) catch |err| brk: {
+ if (err == error.MissingPackageJSON) {
+ try attemptToCreatePackageJSON();
+ break :brk try PackageManager.init(ctx, .unlink);
}
- unreachable;
+ return err;
};
if (manager.options.shouldPrintCommandName()) {
@@ -6091,21 +6082,18 @@ pub const PackageManager = struct {
comptime op: Lockfile.Package.Diff.Op,
comptime subcommand: Subcommand,
) !void {
- var manager = PackageManager.init(ctx, null, subcommand) catch |err| brk: {
- switch (err) {
- error.MissingPackageJSON => {
- if (op == .add or op == .update) {
- var package_json_file = try attemptToCreatePackageJSON();
- break :brk try PackageManager.init(ctx, package_json_file, subcommand);
- }
-
+ var manager = PackageManager.init(ctx, subcommand) catch |err| brk: {
+ if (err == error.MissingPackageJSON) {
+ if (op == .remove) {
Output.prettyErrorln("<r>No package.json, so nothing to remove\n", .{});
Global.crash();
- },
- else => return err,
+ }
+
+ try attemptToCreatePackageJSON();
+ break :brk try PackageManager.init(ctx, subcommand);
}
- unreachable;
+ return err;
};
if (manager.options.shouldPrintCommandName()) {
@@ -6515,7 +6503,7 @@ pub const PackageManager = struct {
var package_json_cwd: string = "";
pub inline fn install(ctx: Command.Context) !void {
- var manager = initMaybeInstall(ctx, null, .install) catch |err| {
+ var manager = initMaybeInstall(ctx, .install) catch |err| {
if (err == error.SwitchToBunAdd) {
return add(ctx);
}
@@ -7511,8 +7499,7 @@ pub const PackageManager = struct {
mapping,
);
- const sum = manager.summary.add + manager.summary.remove + manager.summary.update;
- had_any_diffs = had_any_diffs or sum > 0;
+ had_any_diffs = had_any_diffs or manager.summary.hasDiffs();
if (manager.options.enable.frozen_lockfile and had_any_diffs) {
if (comptime log_level != .silent) {
diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig
index ff4f82400..7d29084c6 100644
--- a/src/install/lockfile.zig
+++ b/src/install/lockfile.zig
@@ -2374,13 +2374,15 @@ pub const Package = extern struct {
add: u32 = 0,
remove: u32 = 0,
update: u32 = 0,
- deduped: u32 = 0,
pub inline fn sum(this: *Summary, that: Summary) void {
this.add += that.add;
this.remove += that.remove;
this.update += that.update;
- this.deduped += that.deduped;
+ }
+
+ pub inline fn hasDiffs(this: Summary) bool {
+ return this.add > 0 or this.remove > 0 or this.update > 0;
}
};
@@ -2422,6 +2424,7 @@ pub const Package = extern struct {
summary.remove += 1;
continue;
}
+ defer to_i += 1;
if (to_deps[to_i].eql(from_dep, to_lockfile.buffers.string_bytes.items, from_lockfile.buffers.string_bytes.items)) {
if (id_mapping) |mapping| {
@@ -2452,11 +2455,11 @@ pub const Package = extern struct {
null,
);
- break :brk diff.add == 0 and diff.remove == 0 and diff.update == 0;
+ break :brk !diff.hasDiffs();
} else false,
else => true,
}) {
- mapping[to_i] = @as(PackageID, @truncate(i));
+ mapping[to_i] = @truncate(i);
continue;
}
} else {
@@ -2468,15 +2471,7 @@ pub const Package = extern struct {
summary.update += 1;
}
- outer: for (to_deps, 0..) |to_dep, i| {
- if (from_deps.len > i and from_deps[i].name_hash == to_dep.name_hash) continue;
-
- for (from_deps) |from_dep| {
- if (from_dep.name_hash == to_dep.name_hash) continue :outer;
- }
-
- summary.add += 1;
- }
+ summary.add = @truncate(to_deps.len - (from_deps.len - summary.remove));
return summary;
}
@@ -2628,16 +2623,18 @@ pub const Package = extern struct {
if (workspace_range) |range| {
if (workspace_version) |ver| {
if (range.satisfies(ver)) {
+ dependency_version.literal = path;
dependency_version.value.workspace = path;
}
}
} else {
+ dependency_version.literal = path;
dependency_version.value.workspace = path;
}
} else {
{
const workspace = dependency_version.value.workspace.slice(buf);
- var path = string_builder.append(
+ const path = string_builder.append(
String,
if (strings.eqlComptime(workspace, "*")) "*" else Path.relative(
FileSystem.instance.top_level_dir,
@@ -2651,20 +2648,25 @@ pub const Package = extern struct {
),
),
);
- defer dependency_version.value.workspace = path;
+ if (comptime Environment.allow_assert) {
+ std.debug.assert(path.len() > 0);
+ std.debug.assert(!std.fs.path.isAbsolute(path.slice(buf)));
+ }
+ dependency_version.literal = path;
+ dependency_version.value.workspace = path;
+
var workspace_entry = try lockfile.workspace_paths.getOrPut(allocator, @truncate(name_hash));
if (workspace_entry.found_existing) {
const old_path = workspace_entry.value_ptr.*;
if (strings.eqlComptime(workspace, "*")) {
- path = old_path;
return null;
} else if (strings.eqlComptime(old_path.slice(buf), "*")) brk: {
workspace_entry.value_ptr.* = path;
for (package_dependencies[0..dependencies_count]) |*package_dep| {
if (String.Builder.stringHash(package_dep.realname().slice(buf)) == name_hash) {
if (package_dep.version.tag != .workspace) break :brk;
- package_dep.version.value.workspace = path;
+ package_dep.version = dependency_version;
return null;
}
}
diff --git a/test/bun.lockb b/test/bun.lockb
index 735613c54..f3d337bb0 100755
--- a/test/bun.lockb
+++ b/test/bun.lockb
Binary files differ
diff --git a/test/cli/install/bun-install.test.ts b/test/cli/install/bun-install.test.ts
index b8f2508c4..cd467c97e 100644
--- a/test/cli/install/bun-install.test.ts
+++ b/test/cli/install/bun-install.test.ts
@@ -251,7 +251,11 @@ it("should handle workspaces", async () => {
}),
);
- const { stdout, stderr, exited } = spawn({
+ const {
+ stdout: stdout1,
+ stderr: stderr1,
+ exited: exited1,
+ } = spawn({
cmd: [bunExe(), "install"],
cwd: package_dir,
stdout: null,
@@ -259,12 +263,12 @@ it("should handle workspaces", async () => {
stderr: "pipe",
env,
});
- expect(stderr).toBeDefined();
- const err = await new Response(stderr).text();
- expect(err).toContain("Saved lockfile");
- expect(stdout).toBeDefined();
- const out = await new Response(stdout).text();
- expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ expect(stderr1).toBeDefined();
+ const err1 = await new Response(stderr1).text();
+ expect(err1).toContain("Saved lockfile");
+ expect(stdout1).toBeDefined();
+ const out1 = await new Response(stdout1).text();
+ expect(out1.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
" + @org/nominally-scoped@workspace:packages/nominally-scoped",
" + Asterisk@workspace:packages/asterisk",
" + AsteriskTheSecond@workspace:packages/second-asterisk",
@@ -272,7 +276,7 @@ it("should handle workspaces", async () => {
"",
" 4 packages installed",
]);
- expect(await exited).toBe(0);
+ expect(await exited1).toBe(0);
expect(requested).toBe(0);
expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([
".cache",
@@ -290,6 +294,51 @@ it("should handle workspaces", async () => {
join("..", "..", "packages", "nominally-scoped"),
);
await access(join(package_dir, "bun.lockb"));
+
+ // Perform `bun install` again but with lockfile from before
+ await rm(join(package_dir, "node_modules"), { force: true, recursive: true });
+ const {
+ stdout: stdout2,
+ stderr: stderr2,
+ exited: exited2,
+ } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr2).toBeDefined();
+ const err2 = await new Response(stderr2).text();
+ expect(err2).not.toContain("Saved lockfile");
+ expect(stdout2).toBeDefined();
+ const out2 = await new Response(stdout2).text();
+ expect(out2.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ " + @org/nominally-scoped@workspace:packages/nominally-scoped",
+ " + Asterisk@workspace:packages/asterisk",
+ " + AsteriskTheSecond@workspace:packages/second-asterisk",
+ " + Bar@workspace:bar",
+ "",
+ " 4 packages installed",
+ ]);
+ expect(await exited2).toBe(0);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([
+ "@org",
+ "Asterisk",
+ "AsteriskTheSecond",
+ "Bar",
+ ]);
+ expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "bar"));
+ expect(await readlink(join(package_dir, "node_modules", "Asterisk"))).toBe(join("..", "packages", "asterisk"));
+ expect(await readlink(join(package_dir, "node_modules", "AsteriskTheSecond"))).toBe(
+ join("..", "packages", "second-asterisk"),
+ );
+ expect(await readlink(join(package_dir, "node_modules", "@org", "nominally-scoped"))).toBe(
+ join("..", "..", "packages", "nominally-scoped"),
+ );
+ await access(join(package_dir, "bun.lockb"));
});
it("should handle `workspace:` specifier", async () => {
@@ -311,7 +360,11 @@ it("should handle `workspace:` specifier", async () => {
version: "0.0.2",
}),
);
- const { stdout, stderr, exited } = spawn({
+ const {
+ stdout: stdout1,
+ stderr: stderr1,
+ exited: exited1,
+ } = spawn({
cmd: [bunExe(), "install"],
cwd: package_dir,
stdout: null,
@@ -319,21 +372,50 @@ it("should handle `workspace:` specifier", async () => {
stderr: "pipe",
env,
});
- expect(stderr).toBeDefined();
- const err = await new Response(stderr).text();
- expect(err).toContain("Saved lockfile");
- expect(stdout).toBeDefined();
- const out = await new Response(stdout).text();
- expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ expect(stderr1).toBeDefined();
+ const err1 = await new Response(stderr1).text();
+ expect(err1).toContain("Saved lockfile");
+ expect(stdout1).toBeDefined();
+ const out1 = await new Response(stdout1).text();
+ expect(out1.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
" + Bar@workspace:path/to/bar",
"",
" 1 packages installed",
]);
- expect(await exited).toBe(0);
+ expect(await exited1).toBe(0);
expect(requested).toBe(0);
expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "Bar"]);
expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "path", "to", "bar"));
await access(join(package_dir, "bun.lockb"));
+ // Perform `bun install` again but with lockfile from before
+ await rm(join(package_dir, "node_modules"), { force: true, recursive: true });
+ const {
+ stdout: stdout2,
+ stderr: stderr2,
+ exited: exited2,
+ } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr2).toBeDefined();
+ const err2 = await new Response(stderr2).text();
+ expect(err2).not.toContain("Saved lockfile");
+ expect(stdout2).toBeDefined();
+ const out2 = await new Response(stdout2).text();
+ expect(out2.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ " + Bar@workspace:path/to/bar",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited2).toBe(0);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual(["Bar"]);
+ expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "path", "to", "bar"));
+ await access(join(package_dir, "bun.lockb"));
});
it("should handle workspaces with packages array", async () => {
@@ -4640,7 +4722,11 @@ it("should handle `workspaces:*` and `workspace:*` gracefully", async () => {
version: "0.0.1",
});
await writeFile(join(package_dir, "bar", "package.json"), bar_package);
- const { stdout, stderr, exited } = spawn({
+ const {
+ stdout: stdout1,
+ stderr: stderr1,
+ exited: exited1,
+ } = spawn({
cmd: [bunExe(), "install"],
cwd: package_dir,
stdout: null,
@@ -4648,22 +4734,52 @@ it("should handle `workspaces:*` and `workspace:*` gracefully", async () => {
stderr: "pipe",
env,
});
- expect(stderr).toBeDefined();
- const err = await new Response(stderr).text();
- expect(err).toContain("Saved lockfile");
- expect(stdout).toBeDefined();
- const out = await new Response(stdout).text();
- expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ expect(stderr1).toBeDefined();
+ const err1 = await new Response(stderr1).text();
+ expect(err1).toContain("Saved lockfile");
+ expect(stdout1).toBeDefined();
+ const out1 = await new Response(stdout1).text();
+ expect(out1.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
" + bar@workspace:bar",
"",
" 1 packages installed",
]);
- expect(await exited).toBe(0);
+ expect(await exited1).toBe(0);
expect(requested).toBe(0);
expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]);
expect(await readlink(join(package_dir, "node_modules", "bar"))).toBe(join("..", "bar"));
expect(await file(join(package_dir, "node_modules", "bar", "package.json")).text()).toEqual(bar_package);
await access(join(package_dir, "bun.lockb"));
+ // Perform `bun install` again but with lockfile from before
+ await rm(join(package_dir, "node_modules"), { force: true, recursive: true });
+ const {
+ stdout: stdout2,
+ stderr: stderr2,
+ exited: exited2,
+ } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr2).toBeDefined();
+ const err2 = await new Response(stderr2).text();
+ expect(err2).not.toContain("Saved lockfile");
+ expect(stdout2).toBeDefined();
+ const out2 = await new Response(stdout2).text();
+ expect(out2.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ " + bar@workspace:bar",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited2).toBe(0);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual(["bar"]);
+ expect(await readlink(join(package_dir, "node_modules", "bar"))).toBe(join("..", "bar"));
+ expect(await file(join(package_dir, "node_modules", "bar", "package.json")).text()).toEqual(bar_package);
+ await access(join(package_dir, "bun.lockb"));
});
it("should handle `workspaces:bar` and `workspace:*` gracefully", async () => {