diff --git a/src/install/PackageManager.zig b/src/install/PackageManager.zig index 308f1b9a22..591e9db5a7 100644 --- a/src/install/PackageManager.zig +++ b/src/install/PackageManager.zig @@ -195,7 +195,6 @@ pub const Subcommand = enum { // TODO: make all subcommands find root and chdir pub fn shouldChdirToRoot(this: Subcommand) bool { return switch (this) { - .link => false, else => true, }; } @@ -599,6 +598,7 @@ pub fn init( var workspace_name_hash: ?PackageNameHash = null; var root_package_json_name_at_time_of_init: []const u8 = ""; + var found_workspace_root = false; // Step 1. Find the nearest package.json directory // @@ -680,7 +680,6 @@ pub fn init( const child_cwd = strings.withoutSuffixComptime(original_package_json_path, std.fs.path.sep_str ++ "package.json"); // Check if this is a workspace; if so, use root package - var found = false; if (subcommand.shouldChdirToRoot()) { if (!created_package_json) { while (std.fs.path.dirname(this_cwd)) |parent| : (this_cwd = parent) { @@ -696,7 +695,7 @@ pub fn init( ) catch { continue; }; - defer if (!found) json_file.close(); + defer if (!found_workspace_root) json_file.close(); const json_stat_size = try json_file.getEndPos(); const json_buf = try ctx.allocator.alloc(u8, json_stat_size + 64); defer ctx.allocator.free(json_buf); @@ -746,7 +745,7 @@ pub fn init( if (strings.eqlLong(maybe_workspace_path, path, true)) { fs.top_level_dir = try bun.default_allocator.dupeZ(u8, parent); - found = true; + found_workspace_root = true; child_json.close(); if (comptime Environment.isWindows) { try json_file.seekTo(0); @@ -772,6 +771,10 @@ pub fn init( cwd_buf[fs.top_level_dir.len] = 0; fs.top_level_dir = cwd_buf[0..fs.top_level_dir.len :0]; root_package_json_path = try bun.getFdPathZ(.fromStdFile(root_package_json_file), &root_package_json_path_buf); + // Update original_package_json_path to point to the workspace root when found + if (found_workspace_root) { + original_package_json_path = root_package_json_path; + } const entries_option = try fs.fs.readDirectory(fs.top_level_dir, null, 0, true); if (entries_option.* == .err) { diff --git a/test/regression/issue/24190.test.ts b/test/regression/issue/24190.test.ts new file mode 100644 index 0000000000..95a8889ab2 --- /dev/null +++ b/test/regression/issue/24190.test.ts @@ -0,0 +1,67 @@ +import { expect, test } from "bun:test"; +import { bunEnv, bunExe, tempDir } from "harness"; + +test("bun link within workspace that depends on sibling workspace", async () => { + using dir = tempDir("bun-link-workspace", { + "work/package.json": JSON.stringify({ workspaces: ["foo", "bar"] }), + "work/foo/package.json": JSON.stringify({ name: "foo", dependencies: { bar: "workspace:*" } }), + "work/bar/package.json": JSON.stringify({ name: "bar" }), + "dep/package.json": JSON.stringify({ name: "dep" }), + }); + + // First, run `bun install` in the workspace root to set up the workspace + await using installProc = Bun.spawn({ + cmd: [bunExe(), "install"], + cwd: `${dir}/work`, + env: bunEnv, + stderr: "pipe", + stdout: "pipe", + }); + + const [installStdout, installStderr, installExitCode] = await Promise.all([ + installProc.stdout.text(), + installProc.stderr.text(), + installProc.exited, + ]); + + expect(installExitCode).toBe(0); + + // Register the dep package as a linked package + await using linkProc = Bun.spawn({ + cmd: [bunExe(), "link"], + cwd: `${dir}/dep`, + env: bunEnv, + stderr: "pipe", + stdout: "pipe", + }); + + const [linkStdout, linkStderr, linkExitCode] = await Promise.all([ + linkProc.stdout.text(), + linkProc.stderr.text(), + linkProc.exited, + ]); + + expect(linkStderr).not.toContain("Workspace dependency"); + expect(linkStderr).not.toContain("not found"); + expect(linkExitCode).toBe(0); + + // Now try to link the dep package from within the foo workspace + // This should not fail with "Workspace dependency 'bar' not found" + await using linkDepProc = Bun.spawn({ + cmd: [bunExe(), "link", "dep"], + cwd: `${dir}/work/foo`, + env: bunEnv, + stderr: "pipe", + stdout: "pipe", + }); + + const [linkDepStdout, linkDepStderr, linkDepExitCode] = await Promise.all([ + linkDepProc.stdout.text(), + linkDepProc.stderr.text(), + linkDepProc.exited, + ]); + + expect(linkDepStderr).not.toContain("Workspace dependency"); + expect(linkDepStderr).not.toContain("not found"); + expect(linkDepExitCode).toBe(0); +});