import { test, expect } from "bun:test"; import { bunEnv, bunExe, tempDirWithFiles } from "harness"; import { join } from "path"; import { mkdirSync, rmSync } from "fs"; test("workspace devDependencies should take priority over peerDependencies for resolution", async () => { const dir = tempDirWithFiles("dev-peer-priority", { "package.json": JSON.stringify({ name: "test-monorepo", version: "1.0.0", workspaces: { packages: ["packages/*"], nodeLinker: "isolated" }, }), "packages/lib/package.json": JSON.stringify({ name: "lib", version: "1.0.0", dependencies: {}, devDependencies: { "my-dep": "workspace:*" // Use workspace protocol for dev }, peerDependencies: { "my-dep": "^1.0.0" // Range that wants 1.x }, }), "packages/lib/test.js": `const dep = require("my-dep"); console.log(dep.version);`, // Only provide workspace package with version 2.0.0 "packages/my-dep/package.json": JSON.stringify({ name: "my-dep", version: "2.0.0", main: "index.js", }), "packages/my-dep/index.js": `module.exports = { version: "2.0.0" };`, }); // Run bun install with a dead registry to ensure no network requests const { stdout, stderr, exitCode } = await new Promise<{ stdout: string; stderr: string; exitCode: number }>((resolve) => { const proc = Bun.spawn({ cmd: [bunExe(), "install", "--no-progress", "--no-summary"], cwd: dir, env: { ...bunEnv, NPM_CONFIG_REGISTRY: "http://localhost:9999/", // Dead URL - will fail if used }, stdout: "pipe", stderr: "pipe", }); proc.exited.then((exitCode) => { Promise.all([ new Response(proc.stdout).text(), new Response(proc.stderr).text(), ]).then(([stdout, stderr]) => { resolve({ stdout, stderr, exitCode }); }); }); }); if (exitCode !== 0) { console.error("Install failed with exit code:", exitCode); console.error("stdout:", stdout); console.error("stderr:", stderr); } expect(exitCode).toBe(0); // Check that no network requests were made for packages that should be resolved locally expect(stderr).not.toContain("GET"); expect(stderr).not.toContain("http"); // Check that the lockfile was created correctly const lockfilePath = join(dir, "bun.lock"); expect(await Bun.file(lockfilePath).exists()).toBe(true); // Verify that version 2.0.0 (devDependency) was linked // If peerDependency range ^1.0.0 was used, it would try to fetch from npm and fail const testResult = await new Promise((resolve) => { const proc = Bun.spawn({ cmd: [bunExe(), "packages/lib/test.js"], cwd: dir, env: bunEnv, stdout: "pipe", }); new Response(proc.stdout).text().then(resolve); }); expect(testResult.trim()).toBe("2.0.0"); }); test("devDependencies and peerDependencies with different versions should coexist", async () => { const dir = tempDirWithFiles("dev-peer-different-versions", { "package.json": JSON.stringify({ name: "test-monorepo", version: "1.0.0", workspaces: { packages: ["packages/*"], nodeLinker: "isolated" }, }), "packages/lib/package.json": JSON.stringify({ name: "lib", version: "1.0.0", dependencies: {}, devDependencies: { "utils": "1.0.0" }, peerDependencies: { "utils": "^1.0.0" }, }), "packages/lib/index.js": `console.log("lib");`, "packages/utils/package.json": JSON.stringify({ name: "utils", version: "1.0.0", main: "index.js", }), "packages/utils/index.js": `console.log("utils");`, }); // Run bun install in the monorepo const { stdout, stderr, exitCode } = await new Promise<{ stdout: string; stderr: string; exitCode: number }>((resolve) => { const proc = Bun.spawn({ cmd: [bunExe(), "install", "--no-progress", "--no-summary"], cwd: dir, env: bunEnv, stdout: "pipe", stderr: "pipe", }); proc.exited.then((exitCode) => { Promise.all([ new Response(proc.stdout).text(), new Response(proc.stderr).text(), ]).then(([stdout, stderr]) => { resolve({ stdout, stderr, exitCode }); }); }); }); if (exitCode !== 0) { console.error("Install failed with exit code:", exitCode); console.error("stdout:", stdout); console.error("stderr:", stderr); } expect(exitCode).toBe(0); // Check that the lockfile was created correctly const lockfilePath = join(dir, "bun.lock"); expect(await Bun.file(lockfilePath).exists()).toBe(true); }); test("dependency behavior comparison prioritizes devDependencies", async () => { const dir = tempDirWithFiles("behavior-comparison", { "package.json": JSON.stringify({ name: "test-app", version: "1.0.0", dependencies: {}, devDependencies: { "typescript": "^5.0.0" }, peerDependencies: { "typescript": "^4.0.0 || ^5.0.0" }, }), "index.js": `console.log("app");`, }); // Run bun install const { stdout, stderr, exitCode } = await new Promise<{ stdout: string; stderr: string; exitCode: number }>((resolve) => { const proc = Bun.spawn({ cmd: [bunExe(), "install", "--no-progress", "--no-summary"], cwd: dir, env: bunEnv, stdout: "pipe", stderr: "pipe", }); proc.exited.then((exitCode) => { Promise.all([ new Response(proc.stdout).text(), new Response(proc.stderr).text(), ]).then(([stdout, stderr]) => { resolve({ stdout, stderr, exitCode }); }); }); }); if (exitCode !== 0) { console.error("Install failed with exit code:", exitCode); console.error("stdout:", stdout); console.error("stderr:", stderr); } expect(exitCode).toBe(0); // Check that the lockfile was created correctly const lockfilePath = join(dir, "bun.lock"); expect(await Bun.file(lockfilePath).exists()).toBe(true); }); test("Next.js monorepo scenario should not make unnecessary network requests", async () => { const dir = tempDirWithFiles("nextjs-monorepo", { "package.json": JSON.stringify({ name: "nextjs-monorepo", version: "1.0.0", workspaces: { packages: ["packages/*"], nodeLinker: "isolated" }, }), "packages/web/package.json": JSON.stringify({ name: "web", version: "1.0.0", dependencies: {}, devDependencies: { "next": "15.0.0-canary.119" // Specific canary version for dev }, peerDependencies: { "next": "^14.0.0 || ^15.0.0" // Range that would accept 14.x or 15.x stable }, }), "packages/web/test.js": `const next = require("next/package.json"); console.log(next.version);`, // Only provide the canary version that matches devDependencies "packages/next/package.json": JSON.stringify({ name: "next", version: "15.0.0-canary.119", main: "index.js", }), "packages/next/index.js": `console.log("next workspace");`, }); // Run bun install with dead registry const { stdout, stderr, exitCode } = await new Promise<{ stdout: string; stderr: string; exitCode: number }>((resolve) => { const proc = Bun.spawn({ cmd: [bunExe(), "install", "--no-progress", "--no-summary"], cwd: dir, env: { ...bunEnv, NPM_CONFIG_REGISTRY: "http://localhost:9999/", // Dead URL }, stdout: "pipe", stderr: "pipe", }); proc.exited.then((exitCode) => { Promise.all([ new Response(proc.stdout).text(), new Response(proc.stderr).text(), ]).then(([stdout, stderr]) => { resolve({ stdout, stderr, exitCode }); }); }); }); expect(exitCode).toBe(0); // The key test: should not make network requests for packages that exist in workspace // When devDependencies are prioritized over peerDependencies, the workspace version should be used expect(stderr).not.toContain("GET"); expect(stderr).not.toContain("404"); expect(stderr).not.toContain("http"); // Check that the lockfile was created correctly const lockfilePath = join(dir, "bun.lock"); expect(await Bun.file(lockfilePath).exists()).toBe(true); // Verify that version 15.0.0-canary.119 (devDependency) was used // If peer range was used, it would try to fetch a stable version from npm and fail const testResult = await new Promise((resolve) => { const proc = Bun.spawn({ cmd: [bunExe(), "packages/web/test.js"], cwd: dir, env: bunEnv, stdout: "pipe", }); new Response(proc.stdout).text().then(resolve); }); expect(testResult.trim()).toBe("15.0.0-canary.119"); });