import { file, write } from "bun"; import { afterAll, beforeAll, describe, expect, setDefaultTimeout, test } from "bun:test"; import { existsSync, readlinkSync } from "fs"; import { VerdaccioRegistry, bunEnv, readdirSorted, runBunInstall } from "harness"; import { join } from "path"; const registry = new VerdaccioRegistry(); beforeAll(async () => { setDefaultTimeout(10 * 60 * 1000); await registry.start(); }); afterAll(() => { registry.stop(); }); describe("basic", () => { test("single dependency", async () => { const { packageJson, packageDir } = await registry.createTestDir(); await write( packageJson, JSON.stringify({ name: "test-pkg-1", workspaces: { nodeLinker: "isolated", }, dependencies: { "no-deps": "1.0.0", }, }), ); await runBunInstall(bunEnv, packageDir); expect(readlinkSync(join(packageDir, "node_modules", "no-deps"))).toBe( join(".bun", "no-deps@1.0.0", "node_modules", "no-deps"), ); expect(readlinkSync(join(packageDir, "node_modules", ".bun", "node_modules", "no-deps"))).toBe( join("..", "no-deps@1.0.0", "node_modules", "no-deps"), ); expect( await file( join(packageDir, "node_modules", ".bun", "no-deps@1.0.0", "node_modules", "no-deps", "package.json"), ).json(), ).toEqual({ name: "no-deps", version: "1.0.0", }); }); test("scope package", async () => { const { packageJson, packageDir } = await registry.createTestDir(); await write( packageJson, JSON.stringify({ name: "test-pkg-2", workspaces: { nodeLinker: "isolated", }, dependencies: { "@types/is-number": "1.0.0", }, }), ); await runBunInstall(bunEnv, packageDir); expect(readlinkSync(join(packageDir, "node_modules", "@types", "is-number"))).toBe( join("..", ".bun", "@types+is-number@1.0.0", "node_modules", "@types", "is-number"), ); expect(readlinkSync(join(packageDir, "node_modules", ".bun", "node_modules", "@types", "is-number"))).toBe( join("..", "..", "@types+is-number@1.0.0", "node_modules", "@types", "is-number"), ); expect( await file( join( packageDir, "node_modules", ".bun", "@types+is-number@1.0.0", "node_modules", "@types", "is-number", "package.json", ), ).json(), ).toEqual({ name: "@types/is-number", version: "1.0.0", }); }); test("transitive dependencies", async () => { const { packageJson, packageDir } = await registry.createTestDir(); await write( packageJson, JSON.stringify({ name: "test-pkg-3", workspaces: { nodeLinker: "isolated", }, dependencies: { "two-range-deps": "1.0.0", }, }), ); await runBunInstall(bunEnv, packageDir); expect(await readdirSorted(join(packageDir, "node_modules"))).toEqual([".bun", "two-range-deps"]); expect(readlinkSync(join(packageDir, "node_modules", "two-range-deps"))).toBe( join(".bun", "two-range-deps@1.0.0", "node_modules", "two-range-deps"), ); expect(readlinkSync(join(packageDir, "node_modules", ".bun", "node_modules", "two-range-deps"))).toBe( join("..", "two-range-deps@1.0.0", "node_modules", "two-range-deps"), ); expect(readlinkSync(join(packageDir, "node_modules", ".bun", "node_modules", "no-deps"))).toBe( join("..", "no-deps@1.1.0", "node_modules", "no-deps"), ); expect(readlinkSync(join(packageDir, "node_modules", ".bun", "node_modules", "@types", "is-number"))).toBe( join("..", "..", "@types+is-number@2.0.0", "node_modules", "@types", "is-number"), ); expect( await file( join( packageDir, "node_modules", ".bun", "two-range-deps@1.0.0", "node_modules", "two-range-deps", "package.json", ), ).json(), ).toEqual({ name: "two-range-deps", version: "1.0.0", dependencies: { "no-deps": "^1.0.0", "@types/is-number": ">=1.0.0", }, }); expect( await readdirSorted(join(packageDir, "node_modules", ".bun", "two-range-deps@1.0.0", "node_modules")), ).toEqual(["@types", "no-deps", "two-range-deps"]); expect( readlinkSync( join(packageDir, "node_modules", ".bun", "two-range-deps@1.0.0", "node_modules", "@types", "is-number"), ), ).toBe(join("..", "..", "..", "@types+is-number@2.0.0", "node_modules", "@types", "is-number")); expect( readlinkSync(join(packageDir, "node_modules", ".bun", "two-range-deps@1.0.0", "node_modules", "no-deps")), ).toBe(join("..", "..", "no-deps@1.1.0", "node_modules", "no-deps")); expect( await file( join(packageDir, "node_modules", ".bun", "no-deps@1.1.0", "node_modules", "no-deps", "package.json"), ).json(), ).toEqual({ name: "no-deps", version: "1.1.0", }); expect( await file( join( packageDir, "node_modules", ".bun", "@types+is-number@2.0.0", "node_modules", "@types", "is-number", "package.json", ), ).json(), ).toEqual({ name: "@types/is-number", version: "2.0.0", }); }); }); test("handles cyclic dependencies", async () => { const { packageJson, packageDir } = await registry.createTestDir(); await write( packageJson, JSON.stringify({ name: "test-pkg-cyclic", workspaces: { nodeLinker: "isolated", }, dependencies: { "a-dep-b": "1.0.0", }, }), ); await runBunInstall(bunEnv, packageDir); expect(readlinkSync(join(packageDir, "node_modules", "a-dep-b"))).toBe( join(".bun", "a-dep-b@1.0.0", "node_modules", "a-dep-b"), ); expect(readlinkSync(join(packageDir, "node_modules", ".bun", "node_modules", "a-dep-b"))).toBe( join("..", "a-dep-b@1.0.0", "node_modules", "a-dep-b"), ); expect(readlinkSync(join(packageDir, "node_modules", ".bun", "node_modules", "b-dep-a"))).toBe( join("..", "b-dep-a@1.0.0", "node_modules", "b-dep-a"), ); expect( await file( join(packageDir, "node_modules", ".bun", "a-dep-b@1.0.0", "node_modules", "a-dep-b", "package.json"), ).json(), ).toEqual({ name: "a-dep-b", version: "1.0.0", dependencies: { "b-dep-a": "1.0.0", }, }); expect(readlinkSync(join(packageDir, "node_modules", ".bun", "a-dep-b@1.0.0", "node_modules", "b-dep-a"))).toBe( join("..", "..", "b-dep-a@1.0.0", "node_modules", "b-dep-a"), ); expect( await file( join(packageDir, "node_modules", ".bun", "a-dep-b@1.0.0", "node_modules", "b-dep-a", "package.json"), ).json(), ).toEqual({ name: "b-dep-a", version: "1.0.0", dependencies: { "a-dep-b": "1.0.0", }, }); }); test("can install folder dependencies", async () => { const { packageJson, packageDir } = await registry.createTestDir(); await write( packageJson, JSON.stringify({ name: "test-pkg-folder-deps", workspaces: { nodeLinker: "isolated", }, dependencies: { "folder-dep": "file:./pkg-1", }, }), ); await write(join(packageDir, "pkg-1", "package.json"), JSON.stringify({ name: "folder-dep", version: "1.0.0" })); await runBunInstall(bunEnv, packageDir); expect(readlinkSync(join(packageDir, "node_modules", "folder-dep"))).toBe( join(".bun", "folder-dep@file+pkg-1", "node_modules", "folder-dep"), ); expect( await file( join(packageDir, "node_modules", ".bun", "folder-dep@file+pkg-1", "node_modules", "folder-dep", "package.json"), ).json(), ).toEqual({ name: "folder-dep", version: "1.0.0", }); await write(join(packageDir, "pkg-1", "index.js"), "module.exports = 'hello from pkg-1';"); await runBunInstall(bunEnv, packageDir, { savesLockfile: false }); expect(readlinkSync(join(packageDir, "node_modules", "folder-dep"))).toBe( join(".bun", "folder-dep@file+pkg-1", "node_modules", "folder-dep"), ); expect( await file( join(packageDir, "node_modules", ".bun", "folder-dep@file+pkg-1", "node_modules", "folder-dep", "index.js"), ).text(), ).toBe("module.exports = 'hello from pkg-1';"); }); describe("isolated workspaces", () => { test("basic", async () => { const { packageJson, packageDir } = await registry.createTestDir(); await Promise.all([ write( packageJson, JSON.stringify({ name: "test-pkg-workspaces", workspaces: { nodeLinker: "isolated", packages: ["pkg-1", "pkg-2"], }, dependencies: { "no-deps": "1.0.0", }, }), ), write( join(packageDir, "pkg-1", "package.json"), JSON.stringify({ name: "pkg-1", version: "1.0.0", dependencies: { "a-dep": "1.0.1", "pkg-2": "workspace:", "@types/is-number": "1.0.0", }, }), ), write( join(packageDir, "pkg-2", "package.json"), JSON.stringify({ name: "pkg-2", version: "1.0.0", dependencies: { "b-dep-a": "1.0.0", }, }), ), ]); await runBunInstall(bunEnv, packageDir); expect(existsSync(join(packageDir, "node_modules", "pkg-1"))).toBeFalse(); expect(readlinkSync(join(packageDir, "pkg-1", "node_modules", "pkg-2"))).toBe(join("..", "..", "pkg-2")); expect(await readdirSorted(join(packageDir, "node_modules"))).toEqual([".bun", "no-deps"]); expect(readlinkSync(join(packageDir, "node_modules", "no-deps"))).toBe( join(".bun", "no-deps@1.0.0", "node_modules", "no-deps"), ); expect(await readdirSorted(join(packageDir, "pkg-1", "node_modules"))).toEqual(["@types", "a-dep", "pkg-2"]); expect(await readdirSorted(join(packageDir, "pkg-2", "node_modules"))).toEqual(["b-dep-a"]); expect(await readdirSorted(join(packageDir, "node_modules", ".bun"))).toEqual([ "@types+is-number@1.0.0", "a-dep-b@1.0.0", "a-dep@1.0.1", "b-dep-a@1.0.0", "no-deps@1.0.0", "node_modules", ]); expect(readlinkSync(join(packageDir, "node_modules", ".bun", "node_modules", "no-deps"))).toBe( join("..", "no-deps@1.0.0", "node_modules", "no-deps"), ); expect( await file( join(packageDir, "node_modules", ".bun", "no-deps@1.0.0", "node_modules", "no-deps", "package.json"), ).json(), ).toEqual({ name: "no-deps", version: "1.0.0", }); }); }); test("many transitive dependencies", async () => { const { packageJson, packageDir } = await registry.createTestDir(); await write( packageJson, JSON.stringify({ name: "test-pkg-many-transitive-deps", workspaces: { nodeLinker: "isolated", }, dependencies: { "alias-loop-1": "1.0.0", "alias-loop-2": "1.0.0", "1-peer-dep-a": "1.0.0", "basic-1": "1.0.0", "is-number": "1.0.0", }, }), ); await runBunInstall(bunEnv, packageDir); expect(await readdirSorted(join(packageDir, "node_modules"))).toEqual([ ".bun", "1-peer-dep-a", "alias-loop-1", "alias-loop-2", "basic-1", "is-number", ]); expect(readlinkSync(join(packageDir, "node_modules", "alias-loop-1"))).toBe( join(".bun", "alias-loop-1@1.0.0", "node_modules", "alias-loop-1"), ); expect(readlinkSync(join(packageDir, "node_modules", ".bun", "node_modules", "alias-loop-1"))).toBe( join("..", "alias-loop-1@1.0.0", "node_modules", "alias-loop-1"), ); expect(readlinkSync(join(packageDir, "node_modules", ".bun", "node_modules", "alias-loop-2"))).toBe( join("..", "alias-loop-2@1.0.0", "node_modules", "alias-loop-2"), ); expect( await file( join(packageDir, "node_modules", ".bun", "alias-loop-1@1.0.0", "node_modules", "alias-loop-1", "package.json"), ).json(), ).toEqual({ name: "alias-loop-1", version: "1.0.0", dependencies: { "alias1": "npm:alias-loop-2@*", }, }); expect( await file( join(packageDir, "node_modules", ".bun", "alias-loop-2@1.0.0", "node_modules", "alias-loop-2", "package.json"), ).json(), ).toEqual({ name: "alias-loop-2", version: "1.0.0", dependencies: { "alias2": "npm:alias-loop-1@*", }, }); // expect(await readdirSorted(join(packageDir, "node_modules", ".bun", "alias-loop-1@1.0.0", "node_modules"))).toEqual([ // "alias1", // "alias-loop-1", // ]); // expect(readlinkSync(join(packageDir, "node_modules", ".bun", "alias-loop-1@1.0.0", "node_modules", "alias1"))).toBe( // join("..", "..", "alias-loop-2@1.0.0", "node_modules", "alias-loop-2"), // ); // expect(readlinkSync(join(packageDir, "node_modules", ".bun", "alias-loop-2@1.0.0", "node_modules", "alias2"))).toBe( // join("..", "..", "alias-loop-1@1.0.0", "node_modules", "alias-loop-1"), // ); });