aboutsummaryrefslogtreecommitdiff
path: root/test/cli/install/bun-install.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'test/cli/install/bun-install.test.ts')
-rw-r--r--test/cli/install/bun-install.test.ts2757
1 files changed, 2757 insertions, 0 deletions
diff --git a/test/cli/install/bun-install.test.ts b/test/cli/install/bun-install.test.ts
new file mode 100644
index 000000000..ee515280e
--- /dev/null
+++ b/test/cli/install/bun-install.test.ts
@@ -0,0 +1,2757 @@
+import { file, listen, spawn } from "bun";
+import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test";
+import { bunExe, bunEnv as env } from "harness";
+import { access, mkdir, readlink, writeFile } from "fs/promises";
+import { join } from "path";
+import {
+ dummyAfterAll,
+ dummyAfterEach,
+ dummyBeforeAll,
+ dummyBeforeEach,
+ dummyRegistry,
+ package_dir,
+ readdirSorted,
+ requested,
+ root_url,
+ setHandler,
+} from "./dummy.registry";
+
+beforeAll(dummyBeforeAll);
+afterAll(dummyAfterAll);
+beforeEach(dummyBeforeEach);
+afterEach(dummyAfterEach);
+
+it("should report connection errors", async () => {
+ function end(socket) {
+ socket.end();
+ }
+ const server = listen({
+ socket: {
+ data: end,
+ drain: end,
+ open: end,
+ },
+ hostname: "localhost",
+ port: 0,
+ });
+ await writeFile(
+ join(package_dir, "bunfig.toml"),
+ `
+[install]
+cache = false
+registry = "http://localhost:${server.port}/"
+`,
+ );
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ bar: "0.0.2",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err.split(/\r?\n/)).toContain("error: ConnectionRefused downloading package manifest bar");
+ expect(stdout).toBeDefined();
+ expect(await new Response(stdout).text()).toBe("");
+ expect(await exited).toBe(1);
+ try {
+ await access(join(package_dir, "bun.lockb"));
+ expect(() => {}).toThrow();
+ } catch (err: any) {
+ expect(err.code).toBe("ENOENT");
+ }
+});
+
+it("should handle missing package", async () => {
+ const urls: string[] = [];
+ setHandler(async request => {
+ expect(request.method).toBe("GET");
+ expect(request.headers.get("accept")).toBe(
+ "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
+ );
+ expect(request.headers.get("npm-auth-type")).toBe(null);
+ expect(await request.text()).toBe("");
+ urls.push(request.url);
+ return new Response("bar", { status: 404 });
+ });
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install", "foo"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err.split(/\r?\n/)).toContain('error: package "foo" not found localhost/foo 404');
+ expect(stdout).toBeDefined();
+ expect(await new Response(stdout).text()).toBe("");
+ expect(await exited).toBe(1);
+ expect(urls.sort()).toEqual([`${root_url}/foo`]);
+ expect(requested).toBe(1);
+ try {
+ await access(join(package_dir, "bun.lockb"));
+ expect(() => {}).toThrow();
+ } catch (err: any) {
+ expect(err.code).toBe("ENOENT");
+ }
+});
+
+it("should handle @scoped authentication", async () => {
+ let seen_token = false;
+ const url = `${root_url}/@foo/bar`;
+ const urls: string[] = [];
+ setHandler(async request => {
+ expect(request.method).toBe("GET");
+ expect(request.headers.get("accept")).toBe(
+ "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
+ );
+ if (request.url === url) {
+ expect(request.headers.get("authorization")).toBe("Bearer bar");
+ expect(request.headers.get("npm-auth-type")).toBe("legacy");
+ seen_token = true;
+ } else {
+ expect(request.headers.get("npm-auth-type")).toBe(null);
+ }
+ expect(await request.text()).toBe("");
+ urls.push(request.url);
+ return new Response("Feeling lucky?", { status: 555 });
+ });
+ // workaround against `writeFile(..., { flag: "a" })`
+ await writeFile(
+ join(package_dir, "bunfig.toml"),
+ `${await file(join(package_dir, "bunfig.toml")).text()}
+[install.scopes]
+foo = { token = "bar" }
+`,
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install", "@foo/bar"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err.split(/\r?\n/)).toContain(`GET ${url} - 555`);
+ expect(stdout).toBeDefined();
+ expect(await new Response(stdout).text()).toBe("");
+ expect(await exited).toBe(1);
+ expect(urls.sort()).toEqual([url]);
+ expect(seen_token).toBe(true);
+ expect(requested).toBe(1);
+ try {
+ await access(join(package_dir, "bun.lockb"));
+ expect(() => {}).toThrow();
+ } catch (err: any) {
+ expect(err.code).toBe("ENOENT");
+ }
+});
+
+it("should handle empty string in dependencies", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ bar: "",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + bar@0.0.2",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({
+ name: "bar",
+ version: "0.0.2",
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle workspaces", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ workspaces: ["bar"],
+ }),
+ );
+ await mkdir(join(package_dir, "bar"));
+ await writeFile(
+ join(package_dir, "bar", "package.json"),
+ JSON.stringify({
+ name: "Bar",
+ version: "0.0.2",
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + Bar@workspace:bar",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).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"));
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle workspaces with packages array", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ workspaces: { packages: ["bar"] },
+ }),
+ );
+ await mkdir(join(package_dir, "bar"));
+ await writeFile(
+ join(package_dir, "bar", "package.json"),
+ JSON.stringify({
+ name: "Bar",
+ version: "0.0.2",
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + Bar@workspace:bar",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).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"));
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle inter-dependency between workspaces", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ workspaces: ["bar", "packages/baz"],
+ }),
+ );
+ await mkdir(join(package_dir, "bar"));
+ await writeFile(
+ join(package_dir, "bar", "package.json"),
+ JSON.stringify({
+ name: "Bar",
+ version: "0.0.2",
+ dependencies: {
+ Baz: "0.0.3",
+ },
+ }),
+ );
+ await mkdir(join(package_dir, "packages", "baz"), { recursive: true });
+ await writeFile(
+ join(package_dir, "packages", "baz", "package.json"),
+ JSON.stringify({
+ name: "Baz",
+ version: "0.0.3",
+ dependencies: {
+ Bar: "0.0.2",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + Bar@workspace:bar",
+ " + Baz@workspace:packages/baz",
+ "",
+ " 2 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "Bar", "Baz"]);
+ expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "bar"));
+ expect(await readlink(join(package_dir, "node_modules", "Baz"))).toBe(join("..", "packages", "baz"));
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle inter-dependency between workspaces (devDependencies)", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ workspaces: ["bar", "packages/baz"],
+ }),
+ );
+ await mkdir(join(package_dir, "bar"));
+ await writeFile(
+ join(package_dir, "bar", "package.json"),
+ JSON.stringify({
+ name: "Bar",
+ version: "0.0.2",
+ devDependencies: {
+ Baz: "0.0.3",
+ },
+ }),
+ );
+ await mkdir(join(package_dir, "packages", "baz"), { recursive: true });
+ await writeFile(
+ join(package_dir, "packages", "baz", "package.json"),
+ JSON.stringify({
+ name: "Baz",
+ version: "0.0.3",
+ devDependencies: {
+ Bar: "0.0.2",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + Bar@workspace:bar",
+ " + Baz@workspace:packages/baz",
+ "",
+ " 2 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "Bar", "Baz"]);
+ expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "bar"));
+ expect(await readlink(join(package_dir, "node_modules", "Baz"))).toBe(join("..", "packages", "baz"));
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle inter-dependency between workspaces (optionalDependencies)", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ workspaces: ["bar", "packages/baz"],
+ }),
+ );
+ await mkdir(join(package_dir, "bar"));
+ await writeFile(
+ join(package_dir, "bar", "package.json"),
+ JSON.stringify({
+ name: "Bar",
+ version: "0.0.2",
+ optionalDependencies: {
+ Baz: "0.0.3",
+ },
+ }),
+ );
+ await mkdir(join(package_dir, "packages", "baz"), { recursive: true });
+ await writeFile(
+ join(package_dir, "packages", "baz", "package.json"),
+ JSON.stringify({
+ name: "Baz",
+ version: "0.0.3",
+ optionalDependencies: {
+ Bar: "0.0.2",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + Bar@workspace:bar",
+ " + Baz@workspace:packages/baz",
+ "",
+ " 2 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "Bar", "Baz"]);
+ expect(await readlink(join(package_dir, "node_modules", "Bar"))).toBe(join("..", "bar"));
+ expect(await readlink(join(package_dir, "node_modules", "Baz"))).toBe(join("..", "packages", "baz"));
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should ignore peerDependencies within workspaces", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ workspaces: ["packages/baz"],
+ peerDependencies: {
+ Bar: ">=0.0.2",
+ },
+ }),
+ );
+ await mkdir(join(package_dir, "packages", "baz"), { recursive: true });
+ await writeFile(
+ join(package_dir, "packages", "baz", "package.json"),
+ JSON.stringify({
+ name: "Baz",
+ version: "0.0.3",
+ peerDependencies: {
+ Moo: ">=0.0.4",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + Baz@workspace:packages/baz",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "Baz"]);
+ expect(await readlink(join(package_dir, "node_modules", "Baz"))).toBe(join("..", "packages", "baz"));
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle life-cycle scripts within workspaces", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ scripts: {
+ install: [bunExe(), "index.js"].join(" "),
+ },
+ workspaces: ["bar"],
+ }),
+ );
+ await writeFile(join(package_dir, "index.js"), 'console.log("[scripts:run] Foo");');
+ await mkdir(join(package_dir, "bar"));
+ await writeFile(
+ join(package_dir, "bar", "package.json"),
+ JSON.stringify({
+ name: "Bar",
+ version: "0.0.2",
+ scripts: {
+ preinstall: [bunExe(), "index.js"].join(" "),
+ },
+ }),
+ );
+ await writeFile(join(package_dir, "bar", "index.js"), 'console.log("[scripts:run] Bar");');
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ "[scripts:run] Bar",
+ " + Bar@workspace:bar",
+ "[scripts:run] Foo",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).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"));
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should ignore workspaces within workspaces", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ workspaces: ["bar"],
+ }),
+ );
+ await mkdir(join(package_dir, "bar"));
+ await writeFile(
+ join(package_dir, "bar", "package.json"),
+ JSON.stringify({
+ name: "bar",
+ version: "0.0.2",
+ workspaces: ["baz"],
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + bar@workspace:bar",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).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"));
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle ^0 in dependencies", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ bar: "^0",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + bar@0.0.2",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({
+ name: "bar",
+ version: "0.0.2",
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle ^1 in dependencies", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ bar: "^1",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err).toContain('error: No version matching "^1" found for specifier "bar" (but package exists)');
+ expect(stdout).toBeDefined();
+ expect(await new Response(stdout).text()).toBe("");
+ expect(await exited).toBe(1);
+ expect(urls.sort()).toEqual([`${root_url}/bar`]);
+ expect(requested).toBe(1);
+ try {
+ await access(join(package_dir, "bun.lockb"));
+ expect(() => {}).toThrow();
+ } catch (err: any) {
+ expect(err.code).toBe("ENOENT");
+ }
+});
+
+it("should handle ^0.0 in dependencies", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ bar: "^0.0",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + bar@0.0.2",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({
+ name: "bar",
+ version: "0.0.2",
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle ^0.1 in dependencies", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ bar: "^0.1",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err).toContain('error: No version matching "^0.1" found for specifier "bar" (but package exists)');
+ expect(stdout).toBeDefined();
+ expect(await new Response(stdout).text()).toBe("");
+ expect(await exited).toBe(1);
+ expect(urls.sort()).toEqual([`${root_url}/bar`]);
+ expect(requested).toBe(1);
+ try {
+ await access(join(package_dir, "bun.lockb"));
+ expect(() => {}).toThrow();
+ } catch (err: any) {
+ expect(err.code).toBe("ENOENT");
+ }
+});
+
+it("should handle ^0.0.0 in dependencies", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ bar: "^0.0.0",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err).toContain('error: No version matching "^0.0.0" found for specifier "bar" (but package exists)');
+ expect(stdout).toBeDefined();
+ expect(await new Response(stdout).text()).toBe("");
+ expect(await exited).toBe(1);
+ expect(urls.sort()).toEqual([`${root_url}/bar`]);
+ expect(requested).toBe(1);
+ try {
+ await access(join(package_dir, "bun.lockb"));
+ expect(() => {}).toThrow();
+ } catch (err: any) {
+ expect(err.code).toBe("ENOENT");
+ }
+});
+
+it("should handle ^0.0.2 in dependencies", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ bar: "^0.0.2",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + bar@0.0.2",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({
+ name: "bar",
+ version: "0.0.2",
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle ^0.0.2-rc in dependencies", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls, { "0.0.2-rc": { as: "0.0.2" } }));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ bar: "^0.0.2-rc",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + bar@0.0.2-rc",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({
+ name: "bar",
+ version: "0.0.2",
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle ^0.0.2-alpha.3+b4d in dependencies", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls, { "0.0.2-alpha.3": { as: "0.0.2" } }));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ bar: "^0.0.2-alpha.3+b4d",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + bar@0.0.2-alpha.3",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({
+ name: "bar",
+ version: "0.0.2",
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should prefer latest-tagged dependency", async () => {
+ const urls: string[] = [];
+ setHandler(
+ dummyRegistry(urls, {
+ "0.0.3": {
+ bin: {
+ "baz-run": "index.js",
+ },
+ },
+ "0.0.5": {
+ bin: {
+ "baz-exec": "index.js",
+ },
+ },
+ latest: "0.0.3",
+ }),
+ );
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ baz: "~0.0.2",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + baz@0.0.3",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "baz"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "baz", "index.js"));
+ expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.3",
+ bin: {
+ "baz-run": "index.js",
+ },
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle dependency aliasing", async () => {
+ const urls = [];
+ setHandler(
+ dummyRegistry(urls, {
+ "0.0.3": {
+ bin: {
+ "baz-run": "index.js",
+ },
+ },
+ }),
+ );
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ Bar: "npm:baz",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + Bar@0.0.3",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "Bar"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "Bar", "index.js"));
+ expect(await readdirSorted(join(package_dir, "node_modules", "Bar"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "Bar", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.3",
+ bin: {
+ "baz-run": "index.js",
+ },
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle dependency aliasing (versioned)", async () => {
+ const urls: string[] = [];
+ setHandler(
+ dummyRegistry(urls, {
+ "0.0.3": {
+ bin: {
+ "baz-run": "index.js",
+ },
+ },
+ }),
+ );
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ Bar: "npm:baz@0.0.3",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + Bar@0.0.3",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "Bar"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "Bar", "index.js"));
+ expect(await readdirSorted(join(package_dir, "node_modules", "Bar"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "Bar", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.3",
+ bin: {
+ "baz-run": "index.js",
+ },
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle dependency aliasing (dist-tagged)", async () => {
+ const urls: string[] = [];
+ setHandler(
+ dummyRegistry(urls, {
+ "0.0.3": {
+ bin: {
+ "baz-run": "index.js",
+ },
+ },
+ }),
+ );
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ Bar: "npm:baz@latest",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + Bar@0.0.3",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "Bar"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "Bar", "index.js"));
+ expect(await readdirSorted(join(package_dir, "node_modules", "Bar"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "Bar", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.3",
+ bin: {
+ "baz-run": "index.js",
+ },
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should not reinstall aliased dependencies", async () => {
+ const urls = [];
+ setHandler(
+ dummyRegistry(urls, {
+ "0.0.3": {
+ bin: {
+ "baz-run": "index.js",
+ },
+ },
+ }),
+ );
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ Bar: "npm:baz",
+ },
+ }),
+ );
+ const {
+ stdout: stdout1,
+ stderr: stderr1,
+ exited: exited1,
+ } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ 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([
+ " + Bar@0.0.3",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited1).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "Bar"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "Bar", "index.js"));
+ expect(await readdirSorted(join(package_dir, "node_modules", "Bar"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "Bar", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.3",
+ bin: {
+ "baz-run": "index.js",
+ },
+ });
+ await access(join(package_dir, "bun.lockb"));
+ // Performs `bun install` again, expects no-op
+ urls.length = 0;
+ 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([
+ "",
+ "Checked 1 installs across 2 packages (no changes)",
+ ]);
+ expect(await exited2).toBe(0);
+ expect(urls.sort()).toEqual([]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "Bar"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "Bar", "index.js"));
+ expect(await readdirSorted(join(package_dir, "node_modules", "Bar"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "Bar", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.3",
+ bin: {
+ "baz-run": "index.js",
+ },
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle aliased & direct dependency references", async () => {
+ const urls = [];
+ setHandler(
+ dummyRegistry(urls, {
+ "0.0.3": {
+ bin: {
+ "baz-run": "index.js",
+ },
+ },
+ }),
+ );
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ baz: "~0.0.2",
+ },
+ workspaces: ["bar"],
+ }),
+ );
+ await mkdir(join(package_dir, "bar"));
+ await writeFile(
+ join(package_dir, "bar", "package.json"),
+ JSON.stringify({
+ name: "bar",
+ version: "0.0.4",
+ dependencies: {
+ moo: "npm:baz",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + bar@workspace:bar",
+ " + baz@0.0.3",
+ "",
+ " 2 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "bar", "baz"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "baz", "index.js"));
+ expect(await readlink(join(package_dir, "node_modules", "bar"))).toBe(join("..", "bar"));
+ expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.3",
+ bin: {
+ "baz-run": "index.js",
+ },
+ });
+ expect(await readdirSorted(join(package_dir, "bar"))).toEqual(["node_modules", "package.json"]);
+ expect(await readdirSorted(join(package_dir, "bar", "node_modules"))).toEqual(["moo"]);
+ expect(await readdirSorted(join(package_dir, "bar", "node_modules", "moo"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "bar", "node_modules", "moo", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.3",
+ bin: {
+ "baz-run": "index.js",
+ },
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should not hoist if name collides with alias", async () => {
+ const urls = [];
+ setHandler(
+ dummyRegistry(urls, {
+ "0.0.2": {},
+ "0.0.3": {
+ bin: {
+ "baz-run": "index.js",
+ },
+ },
+ }),
+ );
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ bar: "npm:baz",
+ },
+ workspaces: ["moo"],
+ }),
+ );
+ await mkdir(join(package_dir, "moo"));
+ await writeFile(
+ join(package_dir, "moo", "package.json"),
+ JSON.stringify({
+ name: "moo",
+ version: "0.0.4",
+ dependencies: {
+ bar: "0.0.2",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + moo@workspace:moo",
+ " + bar@0.0.3",
+ "",
+ " 3 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([
+ `${root_url}/bar`,
+ `${root_url}/bar-0.0.2.tgz`,
+ `${root_url}/baz`,
+ `${root_url}/baz-0.0.3.tgz`,
+ ]);
+ expect(requested).toBe(4);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "bar", "moo"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-run"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(join("..", "bar", "index.js"));
+ expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.3",
+ bin: {
+ "baz-run": "index.js",
+ },
+ });
+ expect(await readlink(join(package_dir, "node_modules", "moo"))).toBe(join("..", "moo"));
+ expect(await readdirSorted(join(package_dir, "moo"))).toEqual(["node_modules", "package.json"]);
+ expect(await readdirSorted(join(package_dir, "moo", "node_modules"))).toEqual(["bar"]);
+ expect(await readdirSorted(join(package_dir, "moo", "node_modules", "bar"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "moo", "node_modules", "bar", "package.json")).json()).toEqual({
+ name: "bar",
+ version: "0.0.2",
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle unscoped alias on scoped dependency", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls, { "0.1.0": {} }));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ "@barn/moo": "latest",
+ moo: "npm:@barn/moo",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + @barn/moo@0.1.0",
+ " + moo@0.1.0",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/@barn/moo`, `${root_url}/@barn/moo-0.1.0.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "@barn", "moo"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "@barn"))).toEqual(["moo"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "@barn", "moo"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "moo", "package.json")).json()).toEqual({
+ name: "@barn/moo",
+ version: "0.1.0",
+ });
+ expect(await readdirSorted(join(package_dir, "node_modules", "moo"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "moo", "package.json")).json()).toEqual({
+ name: "@barn/moo",
+ version: "0.1.0",
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle scoped alias on unscoped dependency", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ "@baz/bar": "npm:bar",
+ bar: "latest",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + @baz/bar@0.0.2",
+ " + bar@0.0.2",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "@baz", "bar"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "@baz"))).toEqual(["bar"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "@baz", "bar"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "@baz", "bar", "package.json")).json()).toEqual({
+ name: "bar",
+ version: "0.0.2",
+ });
+ expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({
+ name: "bar",
+ version: "0.0.2",
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle GitHub URL in dependencies (user/repo)", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ uglify: "mishoo/UglifyJS",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err).toContain("Saved lockfile");
+ expect(stdout).toBeDefined();
+ let out = await new Response(stdout).text();
+ out = out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "");
+ out = out.replace(/(github:[^#]+)#[a-f0-9]+/, "$1");
+ expect(out.split(/\r?\n/)).toEqual([" + uglify@github:mishoo/UglifyJS", "", " 1 packages installed"]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([]);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([
+ ".bun-tag",
+ ".gitattributes",
+ ".github",
+ ".gitignore",
+ "CONTRIBUTING.md",
+ "LICENSE",
+ "README.md",
+ "bin",
+ "lib",
+ "package.json",
+ "test",
+ "tools",
+ ]);
+ const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json();
+ expect(package_json.name).toBe("uglify-js");
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle GitHub URL in dependencies (user/repo#commit-id)", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ uglify: "mishoo/UglifyJS#e219a9a",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + uglify@github:mishoo/UglifyJS#e219a9a",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([]);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".cache"))).toEqual([
+ "@GH@mishoo-UglifyJS-e219a9a",
+ "uglify",
+ ]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".cache", "uglify"))).toEqual([
+ "mishoo-UglifyJS-e219a9a",
+ ]);
+ expect(await readlink(join(package_dir, "node_modules", ".cache", "uglify", "mishoo-UglifyJS-e219a9a"))).toBe(
+ join(package_dir, "node_modules", ".cache", "@GH@mishoo-UglifyJS-e219a9a"),
+ );
+ expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([
+ ".bun-tag",
+ ".gitattributes",
+ ".github",
+ ".gitignore",
+ "CONTRIBUTING.md",
+ "LICENSE",
+ "README.md",
+ "bin",
+ "lib",
+ "package.json",
+ "test",
+ "tools",
+ ]);
+ const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json();
+ expect(package_json.name).toBe("uglify-js");
+ expect(package_json.version).toBe("3.14.1");
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle GitHub URL in dependencies (user/repo#tag)", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ uglify: "mishoo/UglifyJS#v3.14.1",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + uglify@github:mishoo/UglifyJS#e219a9a",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([]);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".cache"))).toEqual([
+ "@GH@mishoo-UglifyJS-e219a9a",
+ "uglify",
+ ]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".cache", "uglify"))).toEqual([
+ "mishoo-UglifyJS-e219a9a",
+ ]);
+ expect(await readlink(join(package_dir, "node_modules", ".cache", "uglify", "mishoo-UglifyJS-e219a9a"))).toBe(
+ join(package_dir, "node_modules", ".cache", "@GH@mishoo-UglifyJS-e219a9a"),
+ );
+ expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([
+ ".bun-tag",
+ ".gitattributes",
+ ".github",
+ ".gitignore",
+ "CONTRIBUTING.md",
+ "LICENSE",
+ "README.md",
+ "bin",
+ "lib",
+ "package.json",
+ "test",
+ "tools",
+ ]);
+ const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json();
+ expect(package_json.name).toBe("uglify-js");
+ expect(package_json.version).toBe("3.14.1");
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle GitHub URL in dependencies (github:user/repo#tag)", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ uglify: "github:mishoo/UglifyJS#v3.14.1",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + uglify@github:mishoo/UglifyJS#e219a9a",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([]);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "uglifyjs"))).toBe(
+ join("..", "uglify", "bin", "uglifyjs"),
+ );
+ expect(await readdirSorted(join(package_dir, "node_modules", ".cache"))).toEqual([
+ "@GH@mishoo-UglifyJS-e219a9a",
+ "uglify",
+ ]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".cache", "uglify"))).toEqual([
+ "mishoo-UglifyJS-e219a9a",
+ ]);
+ expect(await readlink(join(package_dir, "node_modules", ".cache", "uglify", "mishoo-UglifyJS-e219a9a"))).toBe(
+ join(package_dir, "node_modules", ".cache", "@GH@mishoo-UglifyJS-e219a9a"),
+ );
+ expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([
+ ".bun-tag",
+ ".gitattributes",
+ ".github",
+ ".gitignore",
+ "CONTRIBUTING.md",
+ "LICENSE",
+ "README.md",
+ "bin",
+ "lib",
+ "package.json",
+ "test",
+ "tools",
+ ]);
+ const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json();
+ expect(package_json.name).toBe("uglify-js");
+ expect(package_json.version).toBe("3.14.1");
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle GitHub URL in dependencies (https://github.com/user/repo.git)", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ uglify: "https://github.com/mishoo/UglifyJS.git",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err).toContain("Saved lockfile");
+ expect(stdout).toBeDefined();
+ let out = await new Response(stdout).text();
+ out = out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "");
+ out = out.replace(/(github:[^#]+)#[a-f0-9]+/, "$1");
+ expect(out.split(/\r?\n/)).toEqual([" + uglify@github:mishoo/UglifyJS", "", " 1 packages installed"]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([]);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([
+ ".bun-tag",
+ ".gitattributes",
+ ".github",
+ ".gitignore",
+ "CONTRIBUTING.md",
+ "LICENSE",
+ "README.md",
+ "bin",
+ "lib",
+ "package.json",
+ "test",
+ "tools",
+ ]);
+ const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json();
+ expect(package_json.name).toBe("uglify-js");
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle GitHub URL in dependencies (git://github.com/user/repo.git#commit)", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ uglify: "git://github.com/mishoo/UglifyJS.git#e219a9a",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + uglify@github:mishoo/UglifyJS#e219a9a",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([]);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "uglifyjs"))).toBe(
+ join("..", "uglify", "bin", "uglifyjs"),
+ );
+ expect(await readdirSorted(join(package_dir, "node_modules", ".cache"))).toEqual([
+ "@GH@mishoo-UglifyJS-e219a9a",
+ "uglify",
+ ]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".cache", "uglify"))).toEqual([
+ "mishoo-UglifyJS-e219a9a",
+ ]);
+ expect(await readlink(join(package_dir, "node_modules", ".cache", "uglify", "mishoo-UglifyJS-e219a9a"))).toBe(
+ join(package_dir, "node_modules", ".cache", "@GH@mishoo-UglifyJS-e219a9a"),
+ );
+ expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([
+ ".bun-tag",
+ ".gitattributes",
+ ".github",
+ ".gitignore",
+ "CONTRIBUTING.md",
+ "LICENSE",
+ "README.md",
+ "bin",
+ "lib",
+ "package.json",
+ "test",
+ "tools",
+ ]);
+ const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json();
+ expect(package_json.name).toBe("uglify-js");
+ expect(package_json.version).toBe("3.14.1");
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle GitHub URL in dependencies (git+https://github.com/user/repo.git)", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ uglify: "git+https://github.com/mishoo/UglifyJS.git",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err).toContain("Saved lockfile");
+ expect(stdout).toBeDefined();
+ let out = await new Response(stdout).text();
+ out = out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "");
+ out = out.replace(/(github:[^#]+)#[a-f0-9]+/, "$1");
+ expect(out.split(/\r?\n/)).toEqual([" + uglify@github:mishoo/UglifyJS", "", " 1 packages installed"]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([]);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([
+ ".bun-tag",
+ ".gitattributes",
+ ".github",
+ ".gitignore",
+ "CONTRIBUTING.md",
+ "LICENSE",
+ "README.md",
+ "bin",
+ "lib",
+ "package.json",
+ "test",
+ "tools",
+ ]);
+ const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json();
+ expect(package_json.name).toBe("uglify-js");
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should consider peerDependencies during hoisting", async () => {
+ const urls = [];
+ setHandler(
+ dummyRegistry(urls, {
+ "0.0.3": {
+ bin: {
+ "baz-run": "index.js",
+ },
+ },
+ "0.0.5": {
+ bin: {
+ "baz-exec": "index.js",
+ },
+ },
+ }),
+ );
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ peerDependencies: {
+ baz: ">0.0.3",
+ },
+ workspaces: ["bar", "moo"],
+ }),
+ );
+ await mkdir(join(package_dir, "bar"));
+ await writeFile(
+ join(package_dir, "bar", "package.json"),
+ JSON.stringify({
+ name: "bar",
+ version: "0.0.2",
+ dependencies: {
+ baz: "0.0.3",
+ },
+ }),
+ );
+ await mkdir(join(package_dir, "moo"));
+ await writeFile(
+ join(package_dir, "moo", "package.json"),
+ JSON.stringify({
+ name: "moo",
+ version: "0.0.4",
+ dependencies: {
+ baz: "0.0.5",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install", "--peer"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + bar@workspace:bar",
+ " + moo@workspace:moo",
+ "",
+ " 4 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`, `${root_url}/baz-0.0.5.tgz`]);
+ expect(requested).toBe(3);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "bar", "baz", "moo"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["baz-exec", "baz-run"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-exec"))).toBe(join("..", "baz", "index.js"));
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "baz-run"))).toBe(
+ join("..", "..", "bar", "node_modules", "baz", "index.js"),
+ );
+ expect(await readlink(join(package_dir, "node_modules", "bar"))).toBe(join("..", "bar"));
+ expect(await readdirSorted(join(package_dir, "bar"))).toEqual(["node_modules", "package.json"]);
+ expect(await readdirSorted(join(package_dir, "bar", "node_modules"))).toEqual(["baz"]);
+ expect(await readdirSorted(join(package_dir, "bar", "node_modules", "baz"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "bar", "node_modules", "baz", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.3",
+ bin: {
+ "baz-run": "index.js",
+ },
+ });
+ expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.5",
+ bin: {
+ "baz-exec": "index.js",
+ },
+ });
+ expect(await readlink(join(package_dir, "node_modules", "moo"))).toBe(join("..", "moo"));
+ expect(await readdirSorted(join(package_dir, "moo"))).toEqual(["package.json"]);
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should not regard peerDependencies declarations as duplicates", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ bar: "*",
+ },
+ peerDependencies: {
+ bar: "^0.0.2",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + bar@0.0.2",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/bar`, `${root_url}/bar-0.0.2.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "bar"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "bar"))).toEqual(["package.json"]);
+ expect(await file(join(package_dir, "node_modules", "bar", "package.json")).json()).toEqual({
+ name: "bar",
+ version: "0.0.2",
+ });
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should report error on invalid format for package.json", async () => {
+ await writeFile(join(package_dir, "package.json"), "foo");
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err.replace(/^bun install v.+\n/, "bun install\n").split(/\r?\n/)).toEqual([
+ "bun install",
+ "",
+ "",
+ "error: Unexpected foo",
+ "foo",
+ "^",
+ `${package_dir}/package.json:1:1 0`,
+ `ParserError parsing package.json in "${package_dir}/"`,
+ "",
+ ]);
+ expect(stdout).toBeDefined();
+ const out = await new Response(stdout).text();
+ expect(out).toEqual("");
+ expect(await exited).toBe(1);
+});
+
+it("should report error on invalid format for dependencies", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: [],
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err.replace(/^bun install v.+\n/, "bun install\n").split(/\r?\n/)).toEqual([
+ "bun install",
+ "",
+ "",
+ "error: dependencies expects a map of specifiers, e.g.",
+ '"dependencies": {',
+ ' "bun": "latest"',
+ "}",
+ '{"name":"foo","version":"0.0.1","dependencies":[]}',
+ " ^",
+ `${package_dir}/package.json:1:33 32`,
+ "",
+ ]);
+ expect(stdout).toBeDefined();
+ const out = await new Response(stdout).text();
+ expect(out).toEqual("");
+ expect(await exited).toBe(1);
+});
+
+it("should report error on invalid format for optionalDependencies", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ optionalDependencies: "bar",
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err.replace(/^bun install v.+\n/, "bun install\n").split(/\r?\n/)).toEqual([
+ "bun install",
+ "",
+ "",
+ "error: optionalDependencies expects a map of specifiers, e.g.",
+ '"optionalDependencies": {',
+ ' "bun": "latest"',
+ "}",
+ '{"name":"foo","version":"0.0.1","optionalDependencies":"bar"}',
+ " ^",
+ `${package_dir}/package.json:1:33 32`,
+ "",
+ ]);
+ expect(stdout).toBeDefined();
+ const out = await new Response(stdout).text();
+ expect(out).toEqual("");
+ expect(await exited).toBe(1);
+});
+
+it("should report error on invalid format for workspaces", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ workspaces: {
+ packages: { bar: true },
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err.replace(/^bun install v.+\n/, "bun install\n").split(/\r?\n/)).toEqual([
+ "bun install",
+ "",
+ "",
+ "error: Workspaces expects an array of strings, e.g.",
+ '"workspaces": [',
+ ' "path/to/package"',
+ "]",
+ '{"name":"foo","version":"0.0.1","workspaces":{"packages":{"bar":true}}}',
+ " ^",
+ `${package_dir}/package.json:1:33 32`,
+ "",
+ ]);
+ expect(stdout).toBeDefined();
+ const out = await new Response(stdout).text();
+ expect(out).toEqual("");
+ expect(await exited).toBe(1);
+});
+
+it("should report error on duplicated workspace packages", async () => {
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ workspaces: ["bar", "baz"],
+ }),
+ );
+ await mkdir(join(package_dir, "bar"));
+ await writeFile(
+ join(package_dir, "bar", "package.json"),
+ JSON.stringify({
+ name: "moo",
+ version: "0.0.2",
+ }),
+ );
+ await mkdir(join(package_dir, "baz"));
+ await writeFile(
+ join(package_dir, "baz", "package.json"),
+ JSON.stringify({
+ name: "moo",
+ version: "0.0.3",
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err.replace(/^bun install v.+\n/, "bun install\n").split(/\r?\n/)).toEqual([
+ "bun install",
+ "",
+ "",
+ 'error: Workspace name "moo" already exists',
+ '{"name":"foo","version":"0.0.1","workspaces":["bar","baz"]}',
+ " ^",
+ `${package_dir}/package.json:1:53 52`,
+ "",
+ ]);
+ expect(stdout).toBeDefined();
+ const out = await new Response(stdout).text();
+ expect(out).toEqual("");
+ expect(await exited).toBe(1);
+});
+
+it("should handle Git URL in dependencies", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ "uglify-js": "git+https://git@github.com/mishoo/UglifyJS.git",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err).toContain("Saved lockfile");
+ expect(stdout).toBeDefined();
+ let out = await new Response(stdout).text();
+ out = out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "");
+ out = out.replace(/(\.git)#[a-f0-9]+/, "$1");
+ expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ " + uglify-js@git+https://git@github.com/mishoo/UglifyJS.git",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([]);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify-js"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "uglifyjs"))).toBe(
+ join("..", "uglify-js", "bin", "uglifyjs"),
+ );
+ expect((await readdirSorted(join(package_dir, "node_modules", ".cache")))[0]).toBe("9694c5fe9c41ad51.git");
+ expect(await readdirSorted(join(package_dir, "node_modules", "uglify-js"))).toEqual([
+ ".bun-tag",
+ ".gitattributes",
+ ".github",
+ ".gitignore",
+ "CONTRIBUTING.md",
+ "LICENSE",
+ "README.md",
+ "bin",
+ "lib",
+ "package.json",
+ "test",
+ "tools",
+ ]);
+ const package_json = await file(join(package_dir, "node_modules", "uglify-js", "package.json")).json();
+ expect(package_json.name).toBe("uglify-js");
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle Git URL in dependencies (SCP-style)", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ uglify: "github.com:mishoo/UglifyJS.git",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err).toContain("Saved lockfile");
+ expect(stdout).toBeDefined();
+ let out = await new Response(stdout).text();
+ out = out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "");
+ out = out.replace(/(\.git)#[a-f0-9]+/, "$1");
+ expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
+ " + uglify@git+ssh://github.com:mishoo/UglifyJS.git",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([]);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "uglifyjs"))).toBe(
+ join("..", "uglify", "bin", "uglifyjs"),
+ );
+ expect((await readdirSorted(join(package_dir, "node_modules", ".cache")))[0]).toBe("87d55589eb4217d2.git");
+ expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([
+ ".bun-tag",
+ ".gitattributes",
+ ".github",
+ ".gitignore",
+ "CONTRIBUTING.md",
+ "LICENSE",
+ "README.md",
+ "bin",
+ "lib",
+ "package.json",
+ "test",
+ "tools",
+ ]);
+ const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json();
+ expect(package_json.name).toBe("uglify-js");
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should handle Git URL with committish in dependencies", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ uglify: "git+https://git@github.com/mishoo/UglifyJS.git#v3.14.1",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + uglify@git+https://git@github.com/mishoo/UglifyJS.git#e219a9a78a0d2251e4dcbd4bb9034207eb484fe8",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([]);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".bin", ".cache", "uglify"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "uglifyjs"))).toBe(
+ join("..", "uglify", "bin", "uglifyjs"),
+ );
+ expect(await readdirSorted(join(package_dir, "node_modules", ".cache"))).toEqual([
+ "9694c5fe9c41ad51.git",
+ "@G@e219a9a78a0d2251e4dcbd4bb9034207eb484fe8",
+ ]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "uglify"))).toEqual([
+ ".bun-tag",
+ ".gitattributes",
+ ".github",
+ ".gitignore",
+ "CONTRIBUTING.md",
+ "LICENSE",
+ "README.md",
+ "bin",
+ "lib",
+ "package.json",
+ "test",
+ "tools",
+ ]);
+ const package_json = await file(join(package_dir, "node_modules", "uglify", "package.json")).json();
+ expect(package_json.name).toBe("uglify-js");
+ expect(package_json.version).toBe("3.14.1");
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should fail on invalid Git URL", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ uglify: "git+http://bun.sh/no_such_repo",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err.split(/\r?\n/)).toContain('error: "git clone" for "uglify" failed');
+ expect(stdout).toBeDefined();
+ const out = await new Response(stdout).text();
+ expect(out).toBe("");
+ expect(await exited).toBe(1);
+ expect(urls.sort()).toEqual([]);
+ expect(requested).toBe(0);
+ try {
+ await access(join(package_dir, "bun.lockb"));
+ expect(() => {}).toThrow();
+ } catch (err: any) {
+ expect(err.code).toBe("ENOENT");
+ }
+});
+
+it("should fail on Git URL with invalid committish", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ uglify: "git+https://git@github.com/mishoo/UglifyJS.git#404-no_such_tag",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ stderr: "pipe",
+ env,
+ });
+ expect(stderr).toBeDefined();
+ const err = await new Response(stderr).text();
+ expect(err.split(/\r?\n/)).toContain(
+ 'error: no commit matching "404-no_such_tag" found for "uglify" (but repository exists)',
+ );
+ expect(stdout).toBeDefined();
+ const out = await new Response(stdout).text();
+ expect(out).toBe("");
+ expect(await exited).toBe(1);
+ expect(urls.sort()).toEqual([]);
+ expect(requested).toBe(0);
+ try {
+ await access(join(package_dir, "bun.lockb"));
+ expect(() => {}).toThrow();
+ } catch (err: any) {
+ expect(err.code).toBe("ENOENT");
+ }
+});
+
+it("should de-duplicate committish in Git URLs", async () => {
+ const urls: string[] = [];
+ setHandler(dummyRegistry(urls));
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "Foo",
+ version: "0.0.1",
+ dependencies: {
+ "uglify-ver": "git+https://git@github.com/mishoo/UglifyJS.git#v3.14.1",
+ "uglify-hash": "git+https://git@github.com/mishoo/UglifyJS.git#e219a9a",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + uglify-hash@git+https://git@github.com/mishoo/UglifyJS.git#e219a9a78a0d2251e4dcbd4bb9034207eb484fe8",
+ " + uglify-ver@git+https://git@github.com/mishoo/UglifyJS.git#e219a9a78a0d2251e4dcbd4bb9034207eb484fe8",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([]);
+ expect(requested).toBe(0);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([
+ ".bin",
+ ".cache",
+ "uglify-hash",
+ "uglify-ver",
+ ]);
+ expect(await readdirSorted(join(package_dir, "node_modules", ".bin"))).toEqual(["uglifyjs"]);
+ expect(await readlink(join(package_dir, "node_modules", ".bin", "uglifyjs"))).toBe(
+ join("..", "uglify-hash", "bin", "uglifyjs"),
+ );
+ expect(await readdirSorted(join(package_dir, "node_modules", ".cache"))).toEqual([
+ "9694c5fe9c41ad51.git",
+ "@G@e219a9a78a0d2251e4dcbd4bb9034207eb484fe8",
+ ]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "uglify-hash"))).toEqual([
+ ".bun-tag",
+ ".gitattributes",
+ ".github",
+ ".gitignore",
+ "CONTRIBUTING.md",
+ "LICENSE",
+ "README.md",
+ "bin",
+ "lib",
+ "package.json",
+ "test",
+ "tools",
+ ]);
+ const hash_json = await file(join(package_dir, "node_modules", "uglify-hash", "package.json")).json();
+ expect(hash_json.name).toBe("uglify-js");
+ expect(hash_json.version).toBe("3.14.1");
+ expect(await readdirSorted(join(package_dir, "node_modules", "uglify-ver"))).toEqual([
+ ".bun-tag",
+ ".gitattributes",
+ ".github",
+ ".gitignore",
+ "CONTRIBUTING.md",
+ "LICENSE",
+ "README.md",
+ "bin",
+ "lib",
+ "package.json",
+ "test",
+ "tools",
+ ]);
+ const ver_json = await file(join(package_dir, "node_modules", "uglify-ver", "package.json")).json();
+ expect(ver_json.name).toBe("uglify-js");
+ expect(ver_json.version).toBe("3.14.1");
+ await access(join(package_dir, "bun.lockb"));
+});
+
+it("should prefer optionalDependencies over dependencies of the same name", async () => {
+ const urls = [];
+ setHandler(
+ dummyRegistry(urls, {
+ "0.0.3": {},
+ "0.0.5": {},
+ }),
+ );
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ baz: "0.0.5",
+ },
+ optionalDependencies: {
+ baz: "0.0.3",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + baz@0.0.3",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.3.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "baz"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.3",
+ bin: {
+ "baz-run": "index.js",
+ },
+ });
+});
+
+it("should prefer dependencies over peerDependencies of the same name", async () => {
+ const urls = [];
+ setHandler(
+ dummyRegistry(urls, {
+ "0.0.3": {},
+ "0.0.5": {},
+ }),
+ );
+ await writeFile(
+ join(package_dir, "package.json"),
+ JSON.stringify({
+ name: "foo",
+ version: "0.0.1",
+ dependencies: {
+ baz: "0.0.5",
+ },
+ peerDependencies: {
+ baz: "0.0.3",
+ },
+ }),
+ );
+ const { stdout, stderr, exited } = spawn({
+ cmd: [bunExe(), "install"],
+ cwd: package_dir,
+ stdout: null,
+ stdin: "pipe",
+ 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([
+ " + baz@0.0.5",
+ "",
+ " 1 packages installed",
+ ]);
+ expect(await exited).toBe(0);
+ expect(urls.sort()).toEqual([`${root_url}/baz`, `${root_url}/baz-0.0.5.tgz`]);
+ expect(requested).toBe(2);
+ expect(await readdirSorted(join(package_dir, "node_modules"))).toEqual([".cache", "baz"]);
+ expect(await readdirSorted(join(package_dir, "node_modules", "baz"))).toEqual(["index.js", "package.json"]);
+ expect(await file(join(package_dir, "node_modules", "baz", "package.json")).json()).toEqual({
+ name: "baz",
+ version: "0.0.5",
+ bin: {
+ "baz-exec": "index.js",
+ },
+ });
+});