diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | src/install/install.zig | 7 | ||||
-rw-r--r-- | src/install/lockfile.zig | 70 | ||||
-rw-r--r-- | test/cli/install/bun-install.test.ts | 33 |
4 files changed, 97 insertions, 17 deletions
@@ -18,7 +18,7 @@ CPU_TARGET ?= native MARCH_NATIVE = -mtune=$(CPU_TARGET) NATIVE_OR_OLD_MARCH = -MMD_IF_LOCAL = +MMD_IF_LOCAL = DEFAULT_MIN_MACOS_VERSION= ARCH_NAME := DOCKER_BUILDARCH = @@ -1907,7 +1907,7 @@ regenerate-bindings: ## compile src/js/builtins + all c++ code, does not link @make bindings -j$(CPU_COUNT) .PHONY: setup -setup: vendor-dev identifier-cache clean-bindings +setup: vendor-dev identifier-cache clean-bindings js make jsc-check make bindings -j$(CPU_COUNT) @echo "" diff --git a/src/install/install.zig b/src/install/install.zig index 2d5edacde..0a0c21637 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -4320,6 +4320,7 @@ pub const PackageManager = struct { }, local_package_features: Features = .{ .dev_dependencies = true, + .workspaces = true, }, // The idea here is: // 1. package has a platform-specific binary to install @@ -7502,6 +7503,7 @@ pub const PackageManager = struct { manager.summary = try Package.Diff.generate( ctx.allocator, + ctx.log, manager.lockfile, &lockfile, &root, @@ -7519,13 +7521,12 @@ pub const PackageManager = struct { Global.crash(); } - // If you changed packages, we will copy over the new package from the new lockfile - const new_dependencies = maybe_root.dependencies.get(lockfile.buffers.dependencies.items); - if (had_any_diffs) { var builder_ = manager.lockfile.stringBuilder(); // ensure we use one pointer to reference it instead of creating new ones and potentially aliasing var builder = &builder_; + // If you changed packages, we will copy over the new package from the new lockfile + const new_dependencies = maybe_root.dependencies.get(lockfile.buffers.dependencies.items); for (new_dependencies) |new_dep| { new_dep.count(lockfile.buffers.string_bytes.items, *Lockfile.StringBuilder, builder); diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig index 7263a2c63..ff4f82400 100644 --- a/src/install/lockfile.zig +++ b/src/install/lockfile.zig @@ -2385,25 +2385,35 @@ pub const Package = extern struct { }; pub fn generate( - _: Allocator, + allocator: Allocator, + log: *logger.Log, from_lockfile: *Lockfile, to_lockfile: *Lockfile, from: *Lockfile.Package, to: *Lockfile.Package, - mapping: []PackageID, + id_mapping: ?[]PackageID, ) !Summary { var summary = Summary{}; const to_deps = to.dependencies.get(to_lockfile.buffers.dependencies.items); const from_deps = from.dependencies.get(from_lockfile.buffers.dependencies.items); + const from_resolutions = from.resolutions.get(from_lockfile.buffers.resolutions.items); + var to_i: usize = 0; for (from_deps, 0..) |*from_dep, i| { - // common case: dependency is present in both versions and in the same position - const to_i = if (to_deps.len > i and to_deps[i].name_hash == from_dep.name_hash) - i - else brk: { + found: { + const prev_i = to_i; + + // common case, dependency is present in both versions: + // - in the same position + // - shifted by a constant offset + while (to_i < to_deps.len) : (to_i += 1) { + if (from_dep.name_hash == to_deps[to_i].name_hash) break :found; + } + // less common, o(n^2) case - for (to_deps, 0..) |to_dep, j| { - if (from_dep.name_hash == to_dep.name_hash) break :brk j; + to_i = 0; + while (to_i < prev_i) : (to_i += 1) { + if (from_dep.name_hash == to_deps[to_i].name_hash) break :found; } // We found a removed dependency! @@ -2411,11 +2421,47 @@ pub const Package = extern struct { // It will be cleaned up later summary.remove += 1; continue; - }; + } if (to_deps[to_i].eql(from_dep, to_lockfile.buffers.string_bytes.items, from_lockfile.buffers.string_bytes.items)) { - mapping[to_i] = @as(PackageID, @truncate(i)); - continue; + if (id_mapping) |mapping| { + const version = to_deps[to_i].version; + if (switch (version.tag) { + .workspace => if (to_lockfile.workspace_paths.getPtr(@truncate(from_dep.name_hash))) |path_ptr| brk: { + const path = to_lockfile.str(path_ptr); + var file = std.fs.cwd().openFile(Path.join( + &[_]string{ path, "package.json" }, + .auto, + ), .{ .mode = .read_only }) catch break :brk false; + defer file.close(); + const bytes = try file.readToEndAlloc(allocator, std.math.maxInt(usize)); + defer allocator.free(bytes); + const source = logger.Source.initPathString(path, bytes); + + var workspace = Package{}; + try workspace.parseMain(to_lockfile, allocator, log, source, Features.workspace); + + var from_pkg = from_lockfile.packages.get(from_resolutions[i]); + const diff = try generate( + allocator, + log, + from_lockfile, + to_lockfile, + &from_pkg, + &workspace, + null, + ); + + break :brk diff.add == 0 and diff.remove == 0 and diff.update == 0; + } else false, + else => true, + }) { + mapping[to_i] = @as(PackageID, @truncate(i)); + continue; + } + } else { + continue; + } } // We found a changed dependency! @@ -2654,7 +2700,7 @@ pub const Package = extern struct { } const this_dep = Dependency{ - .behavior = group.behavior.setWorkspace(in_workspace), + .behavior = if (in_workspace) group.behavior.setWorkspace(in_workspace) else group.behavior, .name = external_alias.value, .name_hash = external_alias.hash, .version = dependency_version, diff --git a/test/cli/install/bun-install.test.ts b/test/cli/install/bun-install.test.ts index bdd098126..b8f2508c4 100644 --- a/test/cli/install/bun-install.test.ts +++ b/test/cli/install/bun-install.test.ts @@ -724,6 +724,7 @@ it("should handle life-cycle scripts during re-installation", async () => { }); expect(stderr2).toBeDefined(); const err2 = await new Response(stderr2).text(); + expect(err2).not.toContain("error:"); expect(err2).not.toContain("Saved lockfile"); expect(stdout2).toBeDefined(); const out2 = await new Response(stdout2).text(); @@ -739,6 +740,38 @@ it("should handle life-cycle scripts during re-installation", async () => { expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual(["Bar"]); expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "bar")); await access(join(package_dir, "bun.lockb")); + // Perform `bun install --production` with lockfile from before + await rm(join(package_dir, "node_modules"), { force: true, recursive: true }); + const { + stdout: stdout3, + stderr: stderr3, + exited: exited3, + } = spawn({ + cmd: [bunExe(), "install", "--production"], + cwd: package_dir, + stdout: null, + stdin: "pipe", + stderr: "pipe", + env, + }); + expect(stderr3).toBeDefined(); + const err3 = await new Response(stderr3).text(); + expect(err3).not.toContain("error:"); + expect(err3).not.toContain("Saved lockfile"); + expect(stdout3).toBeDefined(); + const out3 = await new Response(stdout3).text(); + expect(out3.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([ + "[scripts:run] Bar", + " + Bar@workspace:bar", + "[scripts:run] Foo", + "", + " 1 packages installed", + ]); + expect(await exited3).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")); + await access(join(package_dir, "bun.lockb")); }); it("should ignore workspaces within workspaces", async () => { |