aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Dylan Conway <35280289+dylan-conway@users.noreply.github.com> 2023-09-26 21:53:14 -0700
committerGravatar GitHub <noreply@github.com> 2023-09-26 21:53:14 -0700
commit0268807be282720da663414c2e1a245db3343968 (patch)
tree9c472b6d632f65e9f6555c675533b260766203a9
parentf354a29683ab56db3ddf4fc8f603fd1e360645e4 (diff)
downloadbun-0268807be282720da663414c2e1a245db3343968.tar.gz
bun-0268807be282720da663414c2e1a245db3343968.tar.zst
bun-0268807be282720da663414c2e1a245db3343968.zip
fix workspace dependency install (#6092)
* handle `*` * test * always use the package name * more tests * install dependency in each
-rw-r--r--src/install/install.zig6
-rw-r--r--test/cli/install/bun-install.test.ts408
2 files changed, 411 insertions, 3 deletions
diff --git a/src/install/install.zig b/src/install/install.zig
index 2044039b2..7c58190da 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -2681,8 +2681,8 @@ pub const PackageManager = struct {
}
},
.workspace => {
- // relative to cwd
- const workspace_path: *const String = this.lockfile.workspace_paths.getPtr(@truncate(String.Builder.stringHash(this.lockfile.str(&version.value.workspace)))) orelse &version.value.workspace;
+ // package name hash should be used to find workspace path from map
+ const workspace_path: *const String = this.lockfile.workspace_paths.getPtr(@truncate(name_hash)) orelse &version.value.workspace;
const res = FolderResolution.getOrPut(.{ .relative = .workspace }, version, this.lockfile.str(workspace_path), this);
@@ -2956,7 +2956,7 @@ pub const PackageManager = struct {
const name = dependency.realname();
const name_hash = switch (dependency.version.tag) {
- .dist_tag, .git, .github, .npm, .tarball => String.Builder.stringHash(this.lockfile.str(&name)),
+ .dist_tag, .git, .github, .npm, .tarball, .workspace => String.Builder.stringHash(this.lockfile.str(&name)),
else => dependency.name_hash,
};
const version = dependency.version;
diff --git a/test/cli/install/bun-install.test.ts b/test/cli/install/bun-install.test.ts
index 6fb303b5d..bf095f6e4 100644
--- a/test/cli/install/bun-install.test.ts
+++ b/test/cli/install/bun-install.test.ts
@@ -5358,6 +5358,414 @@ it("should handle `workspaces:bar` and `workspace:bar` gracefully", async () =>
await access(join(package_dir, "bun.lockb"));
});
+it("should handle installing packages from inside a workspace with `*`", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "main",
+ workspaces: ["packages/*"],
+ private: true,
+ }),
+ );
+ await mkdir(join(package_dir, "packages", "yolo"), { recursive: true });
+ const yolo_package = JSON.stringify({
+ name: "yolo",
+ version: "0.0.1",
+ dependencies: {
+ swag: "workspace:*",
+ },
+ });
+ await writeFile(join(package_dir, "packages", "yolo", "package.json"), yolo_package);
+ await mkdir(join(package_dir, "packages", "swag"));
+ const swag_package = JSON.stringify({
+ name: "swag",
+ version: "0.0.1",
+ });
+ await writeFile(join(package_dir, "packages", "swag", "package.json"), swag_package);
+ const {
+ stdout: stdout1,
+ stderr: stderr1,
+ exited: exited1,
+ } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: join(package_dir, "packages", "yolo"),
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ 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([
+ " + swag@workspace:packages/swag",
+ " + yolo@workspace:packages/yolo",
+ "",
+ " 2 packages installed",
+ ]);
+ expect(await exited1).toBe(0);
+ expect(requested).toBe(0);
+ await access(join(package_dir, "bun.lockb"));
+
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+
+ const {
+ stdout: stdout2,
+ stderr: stderr2,
+ exited: exited2,
+ } = spawn({
+ cmd: [bunExe(), "install", "bar"],
+ cwd: join(package_dir, "packages", "yolo"),
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr1).toBeDefined();
+ const err2 = await new Response(stderr2).text();
+ expect(err2).toContain("Saved lockfile");
+ expect(stdout2).toBeDefined();
+ const out2 = await new Response(stdout2).text();
+ expect(out2).toContain("installed bar");
+ expect(await exited2).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]);
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle installing packages inside workspaces with difference versions", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "main",
+ workspaces: ["packages/*"],
+ private: true,
+ }),
+ );
+ {
+ await mkdir(join(package_dir, "packages", "package1"), { recursive: true });
+ const package1 = JSON.stringify({
+ name: "package1",
+ version: "0.0.2",
+ });
+ await writeFile(join(package_dir, "packages", "package1", "package.json"), package1);
+ }
+ {
+ await mkdir(join(package_dir, "packages", "package2"));
+ const package2 = JSON.stringify({
+ name: "package2",
+ version: "0.0.1",
+ dependencies: {
+ package1: "workspace:*",
+ },
+ });
+ await writeFile(join(package_dir, "packages", "package2", "package.json"), package2);
+ }
+ {
+ await mkdir(join(package_dir, "packages", "package3"));
+ const package3 = JSON.stringify({
+ name: "package3",
+ version: "0.0.1",
+ dependencies: {
+ package1: "workspace:^",
+ },
+ });
+ await writeFile(join(package_dir, "packages", "package3", "package.json"), package3);
+ }
+ {
+ await mkdir(join(package_dir, "packages", "package4"));
+ const package4 = JSON.stringify({
+ name: "package4",
+ version: "0.0.1",
+ dependencies: {
+ package1: "workspace:../package1",
+ },
+ });
+ await writeFile(join(package_dir, "packages", "package4", "package.json"), package4);
+ }
+ {
+ await mkdir(join(package_dir, "packages", "package5"));
+ const package5 = JSON.stringify({
+ name: "package5",
+ version: "0.0.1",
+ dependencies: {
+ package1: "workspace:0.0.2",
+ },
+ });
+ await writeFile(join(package_dir, "packages", "package5", "package.json"), package5);
+ }
+
+ const {
+ stdout: stdout1,
+ stderr: stderr1,
+ exited: exited1,
+ } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: join(package_dir, "packages", "package2"),
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr1).toBeDefined();
+ const err1 = await new Response(stderr1).text();
+ expect(err1).toContain("Saved lockfile");
+ const out1 = await new Response(stdout1).text();
+ expect(out1.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ " + package1@workspace:packages/package1",
+ " + package2@workspace:packages/package2",
+ " + package3@workspace:packages/package3",
+ " + package4@workspace:packages/package4",
+ " + package5@workspace:packages/package5",
+ "",
+ " 5 packages installed",
+ ]);
+ expect(await exited1).toBe(0);
+ expect(requested).toBe(0);
+ await access(join(package_dir, "bun.lockb"));
+
+ var urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+
+ const {
+ stdout: stdout1_2,
+ stderr: stderr1_2,
+ exited: exited1_2,
+ } = spawn({
+ cmd: [bunExe(), "install", "bar"],
+ cwd: join(package_dir, "packages", "package2"),
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr1_2).toBeDefined();
+ expect(stderr1_2).toBeDefined();
+ const err1_2 = await new Response(stderr1_2).text();
+ expect(err1_2).toContain("Saved lockfile");
+ expect(stdout1_2).toBeDefined();
+ const out1_2 = await new Response(stdout1_2).text();
+ expect(out1_2).toContain("installed bar");
+ expect(await exited1_2).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]);
+ await access(join(package_dir, "bun.lockb"));
+
+ await rm(join(package_dir, "node_modules"), { force: true, recursive: true });
+ await rm(join(package_dir, "bun.lockb"), { force: true, recursive: true });
+
+ const {
+ stdout: stdout2,
+ stderr: stderr2,
+ exited: exited2,
+ } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: join(package_dir, "packages", "package3"),
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr2).toBeDefined();
+ const err2 = await new Response(stderr2).text();
+ expect(err2).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([
+ " + package1@workspace:packages/package1",
+ " + package2@workspace:packages/package2",
+ " + package3@workspace:packages/package3",
+ " + package4@workspace:packages/package4",
+ " + package5@workspace:packages/package5",
+ "",
+ " 6 packages installed",
+ ]);
+ expect(await exited2).toBe(0);
+
+ const {
+ stdout: stdout2_2,
+ stderr: stderr2_2,
+ exited: exited2_2,
+ } = spawn({
+ cmd: [bunExe(), "install", "bar"],
+ cwd: join(package_dir, "packages", "package3"),
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr2_2).toBeDefined();
+ expect(stderr2_2).toBeDefined();
+ const err2_2 = await new Response(stderr2_2).text();
+ expect(err2_2).toContain("Saved lockfile");
+ expect(stdout2_2).toBeDefined();
+ const out2_2 = await new Response(stdout2_2).text();
+ expect(out2_2).toContain("installed bar");
+ expect(await exited2_2).toBe(0);
+ await access(join(package_dir, "bun.lockb"));
+
+ await rm(join(package_dir, "node_modules"), { force: true, recursive: true });
+ await rm(join(package_dir, "bun.lockb"), { force: true, recursive: true });
+
+ const {
+ stdout: stdout3,
+ stderr: stderr3,
+ exited: exited3,
+ } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: join(package_dir, "packages", "package4"),
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr2).toBeDefined();
+ const err3 = await new Response(stderr3).text();
+ expect(err3).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([
+ " + package1@workspace:packages/package1",
+ " + package2@workspace:packages/package2",
+ " + package3@workspace:packages/package3",
+ " + package4@workspace:packages/package4",
+ " + package5@workspace:packages/package5",
+ "",
+ " 6 packages installed",
+ ]);
+ expect(await exited3).toBe(0);
+
+ const {
+ stdout: stdout3_2,
+ stderr: stderr3_2,
+ exited: exited3_2,
+ } = spawn({
+ cmd: [bunExe(), "install", "bar"],
+ cwd: join(package_dir, "packages", "package4"),
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr3_2).toBeDefined();
+ expect(stderr3_2).toBeDefined();
+ const err3_2 = await new Response(stderr3_2).text();
+ expect(err3_2).toContain("Saved lockfile");
+ expect(stdout3_2).toBeDefined();
+ const out3_2 = await new Response(stdout3_2).text();
+ expect(out3_2).toContain("installed bar");
+ expect(await exited3_2).toBe(0);
+ await access(join(package_dir, "bun.lockb"));
+
+ await rm(join(package_dir, "node_modules"), { force: true, recursive: true });
+ await rm(join(package_dir, "bun.lockb"), { force: true, recursive: true });
+
+ const {
+ stdout: stdout4,
+ stderr: stderr4,
+ exited: exited4,
+ } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: join(package_dir, "packages", "package5"),
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr4).toBeDefined();
+ const err4 = await new Response(stderr4).text();
+ expect(err4).toContain("Saved lockfile");
+ expect(stdout4).toBeDefined();
+ const out4 = await new Response(stdout4).text();
+ expect(out4.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ " + package1@workspace:packages/package1",
+ " + package2@workspace:packages/package2",
+ " + package3@workspace:packages/package3",
+ " + package4@workspace:packages/package4",
+ " + package5@workspace:packages/package5",
+ "",
+ " 6 packages installed",
+ ]);
+ expect(await exited4).toBe(0);
+
+ const {
+ stdout: stdout4_2,
+ stderr: stderr4_2,
+ exited: exited4_2,
+ } = spawn({
+ cmd: [bunExe(), "install", "bar"],
+ cwd: join(package_dir, "packages", "package5"),
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr4_2).toBeDefined();
+ expect(stderr4_2).toBeDefined();
+ const err4_2 = await new Response(stderr4_2).text();
+ expect(err4_2).toContain("Saved lockfile");
+ expect(stdout4_2).toBeDefined();
+ const out4_2 = await new Response(stdout4_2).text();
+ expect(out4_2).toContain("installed bar");
+ expect(await exited4_2).toBe(0);
+ await access(join(package_dir, "bun.lockb"));
+
+ // from the root
+ await rm(join(package_dir, "node_modules"), { force: true, recursive: true });
+ await rm(join(package_dir, "bun.lockb"), { force: true, recursive: true });
+
+ const {
+ stdout: stdout5,
+ stderr: stderr5,
+ exited: exited5,
+ } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: join(package_dir),
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr5).toBeDefined();
+ const err5 = await new Response(stderr5).text();
+ expect(err5).toContain("Saved lockfile");
+ expect(stdout5).toBeDefined();
+ const out5 = await new Response(stdout5).text();
+ expect(out5.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ " + package1@workspace:packages/package1",
+ " + package2@workspace:packages/package2",
+ " + package3@workspace:packages/package3",
+ " + package4@workspace:packages/package4",
+ " + package5@workspace:packages/package5",
+ "",
+ " 6 packages installed",
+ ]);
+ expect(await exited5).toBe(0);
+
+ const {
+ stdout: stdout5_2,
+ stderr: stderr5_2,
+ exited: exited5_2,
+ } = spawn({
+ cmd: [bunExe(), "install", "bar"],
+ cwd: join(package_dir),
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr5_2).toBeDefined();
+ expect(stderr5_2).toBeDefined();
+ const err5_2 = await new Response(stderr5_2).text();
+ expect(err5_2).toContain("Saved lockfile");
+ expect(stdout5_2).toBeDefined();
+ const out5_2 = await new Response(stdout5_2).text();
+ expect(out5_2).toContain("installed bar");
+ expect(await exited5_2).toBe(0);
+ await access(join(package_dir, "bun.lockb"));
+});
+
it("should override npm dependency by matching workspace", async () => {
const urls: string[] = [];
setHandler(dummyRegistry(urls));