mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
### What does this PR do? Adds `"configVersion"` to bun.lock(b). The version will be used to keep default settings the same if they would be breaking across bun versions. fixes ENG-21389 fixes ENG-21388 ### How did you verify your code works? TODO: - [ ] new project - [ ] existing project without configVersion - [ ] existing project with configVersion - [ ] same as above but with bun.lockb - [ ] configVersion@0 defaults to hoisted linker - [ ] new projects use isolated linker --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
1952 lines
54 KiB
TypeScript
1952 lines
54 KiB
TypeScript
import { file, spawn, write } from "bun";
|
|
import { install_test_helpers } from "bun:internal-for-testing";
|
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
import { mkdirSync, rmSync, writeFileSync } from "fs";
|
|
import { cp, exists, mkdir, rm } from "fs/promises";
|
|
import {
|
|
assertManifestsPopulated,
|
|
bunExe,
|
|
bunEnv as env,
|
|
readdirSorted,
|
|
runBunInstall,
|
|
toMatchNodeModulesAt,
|
|
VerdaccioRegistry,
|
|
} from "harness";
|
|
import { join } from "path";
|
|
|
|
const { parseLockfile } = install_test_helpers;
|
|
|
|
expect.extend({ toMatchNodeModulesAt });
|
|
|
|
// not necessary, but verdaccio will be added to this file in the near future
|
|
|
|
var verdaccio: VerdaccioRegistry;
|
|
var packageDir: string;
|
|
var packageJson: string;
|
|
|
|
beforeAll(async () => {
|
|
verdaccio = new VerdaccioRegistry();
|
|
await verdaccio.start();
|
|
});
|
|
|
|
afterAll(() => {
|
|
verdaccio.stop();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
({ packageDir, packageJson } = await verdaccio.createTestDir());
|
|
env.BUN_INSTALL_CACHE_DIR = join(packageDir, ".bun-cache");
|
|
env.BUN_TMPDIR = env.TMPDIR = env.TEMP = join(packageDir, ".bun-tmp");
|
|
});
|
|
|
|
test("dependency on workspace without version in package.json", async () => {
|
|
await Promise.all([
|
|
write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["packages/*"],
|
|
}),
|
|
),
|
|
|
|
write(
|
|
join(packageDir, "packages", "mono", "package.json"),
|
|
JSON.stringify({
|
|
name: "no-deps",
|
|
}),
|
|
),
|
|
]);
|
|
|
|
mkdirSync(join(packageDir, "packages", "bar"), { recursive: true });
|
|
|
|
const shouldWork: string[] = [
|
|
"*",
|
|
"*.*.*",
|
|
"=*",
|
|
"kjwoehcojrgjoj", // dist-tag does not exist, should choose local workspace
|
|
"*.1.*",
|
|
"*-pre",
|
|
];
|
|
const shouldNotWork: string[] = [
|
|
"1",
|
|
"1.*",
|
|
"1.1.*",
|
|
"1.1.0",
|
|
"*-pre+build",
|
|
"*+build",
|
|
"latest", // dist-tag exists, should choose package from npm
|
|
"",
|
|
];
|
|
|
|
for (const version of shouldWork) {
|
|
writeFileSync(
|
|
join(packageDir, "packages", "bar", "package.json"),
|
|
JSON.stringify({
|
|
name: "bar",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": version,
|
|
},
|
|
}),
|
|
);
|
|
|
|
const { out } = await runBunInstall(env, packageDir);
|
|
const lockfile = parseLockfile(packageDir);
|
|
expect(lockfile).toMatchNodeModulesAt(packageDir);
|
|
expect(
|
|
JSON.stringify(lockfile, null, 2).replaceAll(/http:\/\/localhost:\d+/g, "http://localhost:1234"),
|
|
).toMatchSnapshot(`version: ${version}`);
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"2 packages installed",
|
|
]);
|
|
rmSync(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
rmSync(join(packageDir, "bun.lock"), { recursive: true, force: true });
|
|
}
|
|
|
|
// downloads the package from the registry instead of
|
|
// using the workspace locally
|
|
for (const version of shouldNotWork) {
|
|
writeFileSync(
|
|
join(packageDir, "packages", "bar", "package.json"),
|
|
JSON.stringify({
|
|
name: "bar",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": version,
|
|
},
|
|
}),
|
|
);
|
|
|
|
const { out } = await runBunInstall(env, packageDir);
|
|
const lockfile = parseLockfile(packageDir);
|
|
expect(lockfile).toMatchNodeModulesAt(packageDir);
|
|
expect(
|
|
JSON.stringify(lockfile, null, 2).replaceAll(/http:\/\/localhost:\d+/g, "http://localhost:1234"),
|
|
).toMatchSnapshot(`version: ${version}`);
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"3 packages installed",
|
|
]);
|
|
rmSync(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
rmSync(join(packageDir, "packages", "bar", "node_modules"), { recursive: true, force: true });
|
|
rmSync(join(packageDir, "bun.lock"), { recursive: true, force: true });
|
|
}
|
|
}, 20_000);
|
|
|
|
test("allowing negative workspace patterns", async () => {
|
|
await Promise.all([
|
|
write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "root",
|
|
workspaces: ["packages/*", "!packages/pkg2"],
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
},
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg2", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg2",
|
|
dependencies: {
|
|
"doesnt-exist-oops": "1.2.3",
|
|
},
|
|
}),
|
|
),
|
|
]);
|
|
|
|
const { exited } = await runBunInstall(env, packageDir);
|
|
expect(await exited).toBe(0);
|
|
|
|
expect(await file(join(packageDir, "node_modules", "no-deps", "package.json")).json()).toEqual({
|
|
name: "no-deps",
|
|
version: "1.0.0",
|
|
});
|
|
});
|
|
|
|
test("dependency on same name as workspace and dist-tag", async () => {
|
|
await Promise.all([
|
|
write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["packages/*"],
|
|
}),
|
|
),
|
|
|
|
write(
|
|
join(packageDir, "packages", "mono", "package.json"),
|
|
JSON.stringify({
|
|
name: "no-deps",
|
|
version: "4.17.21",
|
|
}),
|
|
),
|
|
|
|
write(
|
|
join(packageDir, "packages", "bar", "package.json"),
|
|
JSON.stringify({
|
|
name: "bar",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "latest",
|
|
},
|
|
}),
|
|
),
|
|
]);
|
|
|
|
const { out } = await runBunInstall(env, packageDir);
|
|
const lockfile = parseLockfile(packageDir);
|
|
expect(
|
|
JSON.stringify(lockfile, null, 2).replaceAll(/http:\/\/localhost:\d+/g, "http://localhost:1234"),
|
|
).toMatchSnapshot("with version");
|
|
expect(lockfile).toMatchNodeModulesAt(packageDir);
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"3 packages installed",
|
|
]);
|
|
});
|
|
|
|
test("successfully installs workspace when path already exists in node_modules", async () => {
|
|
await Promise.all([
|
|
write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["pkg1"],
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
}),
|
|
),
|
|
|
|
// stale package in node_modules
|
|
write(
|
|
join(packageDir, "node_modules", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg2",
|
|
}),
|
|
),
|
|
]);
|
|
|
|
await runBunInstall(env, packageDir);
|
|
expect(await file(join(packageDir, "node_modules", "pkg1", "package.json")).json()).toEqual({
|
|
name: "pkg1",
|
|
});
|
|
});
|
|
|
|
test("adding workspace in workspace edits package.json with correct version (workspace:*)", async () => {
|
|
await Promise.all([
|
|
write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["packages/*", "apps/*"],
|
|
}),
|
|
),
|
|
|
|
write(
|
|
join(packageDir, "packages", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
version: "1.0.0",
|
|
}),
|
|
),
|
|
|
|
write(
|
|
join(packageDir, "apps", "pkg2", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg2",
|
|
version: "1.0.0",
|
|
}),
|
|
),
|
|
]);
|
|
|
|
const { stdout, exited } = Bun.spawn({
|
|
cmd: [bunExe(), "add", "pkg2@workspace:*"],
|
|
cwd: join(packageDir, "packages", "pkg1"),
|
|
stdout: "pipe",
|
|
stderr: "inherit",
|
|
env,
|
|
});
|
|
const out = await stdout.text();
|
|
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun add v1."),
|
|
"",
|
|
"installed pkg2@workspace:apps/pkg2",
|
|
"",
|
|
"2 packages installed",
|
|
]);
|
|
|
|
expect(await exited).toBe(0);
|
|
|
|
expect(await Bun.file(join(packageDir, "packages", "pkg1", "package.json")).json()).toEqual({
|
|
name: "pkg1",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
pkg2: "workspace:*",
|
|
},
|
|
});
|
|
});
|
|
|
|
test("workspaces with invalid versions should still install", async () => {
|
|
await Promise.all([
|
|
write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "📦",
|
|
workspaces: ["packages/*"],
|
|
dependencies: {
|
|
emoji1: "workspace:*",
|
|
emoji2: "workspace:>=0",
|
|
pre: "*",
|
|
build: "workspace:^",
|
|
},
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "emoji1", "package.json"),
|
|
JSON.stringify({
|
|
name: "emoji1",
|
|
version: "😃",
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "emoji2", "package.json"),
|
|
JSON.stringify({
|
|
name: "emoji2",
|
|
version: "👀",
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pre", "package.json"),
|
|
JSON.stringify({
|
|
name: "pre",
|
|
version: "3.0.0_pre",
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "build", "package.json"),
|
|
JSON.stringify({
|
|
name: "build",
|
|
version: "3.0.0_pre+bui_ld",
|
|
}),
|
|
),
|
|
]);
|
|
|
|
await runBunInstall(env, packageDir);
|
|
|
|
const results = await Promise.all([
|
|
file(join(packageDir, "node_modules", "emoji1", "package.json")).json(),
|
|
file(join(packageDir, "node_modules", "emoji2", "package.json")).json(),
|
|
file(join(packageDir, "node_modules", "pre", "package.json")).json(),
|
|
file(join(packageDir, "node_modules", "build", "package.json")).json(),
|
|
]);
|
|
|
|
expect(results[0]).toEqual({
|
|
name: "emoji1",
|
|
version: "😃",
|
|
});
|
|
expect(results[1]).toEqual({
|
|
name: "emoji2",
|
|
version: "👀",
|
|
});
|
|
expect(results[2]).toEqual({
|
|
name: "pre",
|
|
version: "3.0.0_pre",
|
|
});
|
|
expect(results[3]).toEqual({
|
|
name: "build",
|
|
version: "3.0.0_pre+bui_ld",
|
|
});
|
|
});
|
|
|
|
describe("workspace aliases", async () => {
|
|
test("combination", async () => {
|
|
await Promise.all([
|
|
write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["packages/*"],
|
|
dependencies: {
|
|
"a0": "workspace:@org/a@latest",
|
|
},
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "@org/a",
|
|
dependencies: {
|
|
"a1": "workspace:@org/b@ ",
|
|
"a2": "workspace:c@*",
|
|
},
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg2", "package.json"),
|
|
JSON.stringify({
|
|
name: "@org/b",
|
|
dependencies: {
|
|
"a3": "workspace:c@ ",
|
|
"a4": "workspace:@org/a@latest",
|
|
},
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg3", "package.json"),
|
|
JSON.stringify({
|
|
name: "c",
|
|
dependencies: {
|
|
"a5": "workspace:@org/a@*",
|
|
},
|
|
}),
|
|
),
|
|
]);
|
|
|
|
await runBunInstall(env, packageDir);
|
|
const files = await Promise.all(
|
|
["a0", "a1", "a2", "a3", "a4", "a5"].map(name =>
|
|
file(join(packageDir, "node_modules", name, "package.json")).json(),
|
|
),
|
|
);
|
|
|
|
expect(files).toMatchObject([
|
|
{ name: "@org/a" },
|
|
{ name: "@org/b" },
|
|
{ name: "c" },
|
|
{ name: "c" },
|
|
{ name: "@org/a" },
|
|
{ name: "@org/a" },
|
|
]);
|
|
});
|
|
var shouldPass: string[] = [
|
|
"workspace:@org/b@latest",
|
|
"workspace:@org/b@*",
|
|
// missing version after `@`
|
|
"workspace:@org/b@",
|
|
];
|
|
for (const version of shouldPass) {
|
|
test(`version range ${version} and workspace with no version`, async () => {
|
|
await Promise.all([
|
|
write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["packages/*"],
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "@org/a",
|
|
dependencies: {
|
|
"a1": version,
|
|
},
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg2", "package.json"),
|
|
JSON.stringify({
|
|
name: "@org/b",
|
|
}),
|
|
),
|
|
]);
|
|
|
|
await runBunInstall(env, packageDir);
|
|
const files = await Promise.all([
|
|
file(join(packageDir, "node_modules", "@org", "a", "package.json")).json(),
|
|
file(join(packageDir, "node_modules", "@org", "b", "package.json")).json(),
|
|
file(join(packageDir, "node_modules", "a1", "package.json")).json(),
|
|
]);
|
|
|
|
expect(files).toMatchObject([{ name: "@org/a" }, { name: "@org/b" }, { name: "@org/b" }]);
|
|
});
|
|
}
|
|
let shouldFail: string[] = ["workspace:@org/b@1.0.0", "workspace:@org/b@1", "workspace:@org/b"];
|
|
for (const version of shouldFail) {
|
|
test(`version range ${version} and workspace with no version (should fail)`, async () => {
|
|
await Promise.all([
|
|
write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["packages/*"],
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "@org/a",
|
|
dependencies: {
|
|
"a1": version,
|
|
},
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg2", "package.json"),
|
|
JSON.stringify({
|
|
name: "@org/b",
|
|
}),
|
|
),
|
|
]);
|
|
|
|
const { stderr, exited } = Bun.spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
if (version === "workspace:@org/b") {
|
|
expect(err).toContain('Workspace dependency "a1" not found');
|
|
} else {
|
|
expect(err).toContain(`No matching version for workspace dependency "a1". Version: "${version}"`);
|
|
}
|
|
expect(await exited).toBe(1);
|
|
});
|
|
}
|
|
});
|
|
|
|
for (const glob of [true, false]) {
|
|
test(`does not crash when root package.json is in "workspaces"${glob ? " (glob)" : ""}`, async () => {
|
|
await Promise.all([
|
|
write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: glob ? ["**"] : ["pkg1", "./*"],
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
}),
|
|
),
|
|
]);
|
|
|
|
await runBunInstall(env, packageDir);
|
|
expect(await file(join(packageDir, "node_modules", "pkg1", "package.json")).json()).toEqual({
|
|
name: "pkg1",
|
|
});
|
|
});
|
|
}
|
|
|
|
test("cwd in workspace script is not the symlink path on windows", async () => {
|
|
await Promise.all([
|
|
write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["pkg1"],
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
scripts: {
|
|
postinstall: 'bun -e \'require("fs").writeFileSync("cwd", process.cwd())\'',
|
|
},
|
|
}),
|
|
),
|
|
]);
|
|
|
|
await runBunInstall(env, packageDir);
|
|
|
|
expect(await file(join(packageDir, "node_modules", "pkg1", "cwd")).text()).toBe(join(packageDir, "pkg1"));
|
|
});
|
|
|
|
describe("relative tarballs", async () => {
|
|
test("from package.json", async () => {
|
|
await Promise.all([
|
|
write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["pkgs/*"],
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "pkgs", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
dependencies: {
|
|
"qux": "../../qux-0.0.2.tgz",
|
|
},
|
|
}),
|
|
),
|
|
cp(join(import.meta.dir, "qux-0.0.2.tgz"), join(packageDir, "qux-0.0.2.tgz")),
|
|
]);
|
|
|
|
await runBunInstall(env, packageDir);
|
|
|
|
expect(await file(join(packageDir, "node_modules", "qux", "package.json")).json()).toMatchObject({
|
|
name: "qux",
|
|
version: "0.0.2",
|
|
});
|
|
});
|
|
test("from cli", async () => {
|
|
await Promise.all([
|
|
write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["pkgs/*"],
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "pkgs", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
}),
|
|
),
|
|
cp(join(import.meta.dir, "qux-0.0.2.tgz"), join(packageDir, "qux-0.0.2.tgz")),
|
|
]);
|
|
|
|
const { stderr, exited } = Bun.spawn({
|
|
cmd: [bunExe(), "install", "../../qux-0.0.2.tgz"],
|
|
cwd: join(packageDir, "pkgs", "pkg1"),
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("failed to resolve");
|
|
expect(await exited).toBe(0);
|
|
|
|
const results = await Promise.all([
|
|
file(join(packageDir, "node_modules", "qux", "package.json")).json(),
|
|
file(join(packageDir, "pkgs", "pkg1", "package.json")).json(),
|
|
]);
|
|
|
|
expect(results[0]).toMatchObject({
|
|
name: "qux",
|
|
version: "0.0.2",
|
|
});
|
|
|
|
expect(results[1]).toMatchObject({
|
|
name: "pkg1",
|
|
dependencies: {
|
|
qux: "../../qux-0.0.2.tgz",
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
test("$npm_package_config_ works in root", async () => {
|
|
await write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["pkgs/*"],
|
|
config: { foo: "bar" },
|
|
scripts: { sample: "echo $npm_package_config_foo $npm_package_config_qux" },
|
|
}),
|
|
);
|
|
await write(
|
|
join(packageDir, "pkgs", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
config: { qux: "tab" },
|
|
scripts: { sample: "echo $npm_package_config_foo $npm_package_config_qux" },
|
|
}),
|
|
);
|
|
const p = Bun.spawn({
|
|
cmd: [bunExe(), "run", "sample"],
|
|
cwd: packageDir,
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
env,
|
|
});
|
|
expect(await p.exited).toBe(0);
|
|
expect(await new Response(p.stderr).text()).toBe(`$ echo $npm_package_config_foo $npm_package_config_qux\n`);
|
|
expect(await new Response(p.stdout).text()).toBe(`bar\n`);
|
|
});
|
|
test("$npm_package_config_ works in root in subpackage", async () => {
|
|
await write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["pkgs/*"],
|
|
config: { foo: "bar" },
|
|
scripts: { sample: "echo $npm_package_config_foo $npm_package_config_qux" },
|
|
}),
|
|
);
|
|
await write(
|
|
join(packageDir, "pkgs", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
config: { qux: "tab" },
|
|
scripts: { sample: "echo $npm_package_config_foo $npm_package_config_qux" },
|
|
}),
|
|
);
|
|
const p = Bun.spawn({
|
|
cmd: [bunExe(), "run", "sample"],
|
|
cwd: join(packageDir, "pkgs", "pkg1"),
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
env,
|
|
});
|
|
expect(await p.exited).toBe(0);
|
|
expect(await new Response(p.stderr).text()).toBe(`$ echo $npm_package_config_foo $npm_package_config_qux\n`);
|
|
expect(await new Response(p.stdout).text()).toBe(`tab\n`);
|
|
});
|
|
|
|
test("adding packages in a subdirectory of a workspace", async () => {
|
|
await write(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "root",
|
|
workspaces: ["foo"],
|
|
}),
|
|
);
|
|
|
|
await mkdir(join(packageDir, "folder1"));
|
|
await mkdir(join(packageDir, "foo", "folder2"), { recursive: true });
|
|
await write(
|
|
join(packageDir, "foo", "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
}),
|
|
);
|
|
|
|
// add package to root workspace from `folder1`
|
|
let { stdout, exited } = spawn({
|
|
cmd: [bunExe(), "add", "no-deps"],
|
|
cwd: join(packageDir, "folder1"),
|
|
stdout: "pipe",
|
|
stderr: "inherit",
|
|
env,
|
|
});
|
|
let out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun add v1."),
|
|
"",
|
|
"installed no-deps@2.0.0",
|
|
"",
|
|
"2 packages installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await file(packageJson).json()).toEqual({
|
|
name: "root",
|
|
workspaces: ["foo"],
|
|
dependencies: {
|
|
"no-deps": "^2.0.0",
|
|
},
|
|
});
|
|
|
|
// add package to foo from `folder2`
|
|
({ stdout, exited } = spawn({
|
|
cmd: [bunExe(), "add", "what-bin"],
|
|
cwd: join(packageDir, "foo", "folder2"),
|
|
stdout: "pipe",
|
|
stderr: "inherit",
|
|
env,
|
|
}));
|
|
out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun add v1."),
|
|
"",
|
|
"installed what-bin@1.5.0 with binaries:",
|
|
" - what-bin",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await file(join(packageDir, "foo", "package.json")).json()).toEqual({
|
|
name: "foo",
|
|
dependencies: {
|
|
"what-bin": "^1.5.0",
|
|
},
|
|
});
|
|
|
|
// now delete node_modules and bun.lock and install
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
await rm(join(packageDir, "bun.lock"));
|
|
|
|
({ stdout, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: join(packageDir, "folder1"),
|
|
stdout: "pipe",
|
|
stderr: "inherit",
|
|
env,
|
|
}));
|
|
out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ no-deps@2.0.0",
|
|
"",
|
|
"3 packages installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await readdirSorted(join(packageDir, "node_modules"))).toEqual([".bin", "foo", "no-deps", "what-bin"]);
|
|
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
await rm(join(packageDir, "bun.lock"));
|
|
|
|
({ stdout, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: join(packageDir, "foo", "folder2"),
|
|
stdout: "pipe",
|
|
stderr: "inherit",
|
|
env,
|
|
}));
|
|
out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ what-bin@1.5.0",
|
|
"",
|
|
"3 packages installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await readdirSorted(join(packageDir, "node_modules"))).toEqual([".bin", "foo", "no-deps", "what-bin"]);
|
|
});
|
|
test("adding packages in workspaces", async () => {
|
|
await write(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["packages/*"],
|
|
dependencies: {
|
|
"bar": "workspace:*",
|
|
},
|
|
}),
|
|
);
|
|
|
|
await mkdir(join(packageDir, "packages", "bar"), { recursive: true });
|
|
await mkdir(join(packageDir, "packages", "boba"));
|
|
await mkdir(join(packageDir, "packages", "pkg5"));
|
|
|
|
await write(join(packageDir, "packages", "bar", "package.json"), JSON.stringify({ name: "bar" }));
|
|
await write(
|
|
join(packageDir, "packages", "boba", "package.json"),
|
|
JSON.stringify({ name: "boba", version: "1.0.0", dependencies: { "pkg5": "*" } }),
|
|
);
|
|
await write(
|
|
join(packageDir, "packages", "pkg5", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg5",
|
|
version: "1.2.3",
|
|
dependencies: {
|
|
"bar": "workspace:*",
|
|
},
|
|
}),
|
|
);
|
|
|
|
let { stdout, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "inherit",
|
|
env,
|
|
});
|
|
|
|
let out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ bar@workspace:packages/bar",
|
|
"",
|
|
"3 packages installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules", "bar"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "node_modules", "boba"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "node_modules", "pkg5"))).toBeTrue();
|
|
|
|
// add a package to the root workspace
|
|
({ stdout, exited } = spawn({
|
|
cmd: [bunExe(), "add", "no-deps"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "inherit",
|
|
env,
|
|
}));
|
|
|
|
out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun add v1."),
|
|
"",
|
|
"installed no-deps@2.0.0",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await file(packageJson).json()).toEqual({
|
|
name: "foo",
|
|
workspaces: ["packages/*"],
|
|
dependencies: {
|
|
bar: "workspace:*",
|
|
"no-deps": "^2.0.0",
|
|
},
|
|
});
|
|
|
|
// add a package in a workspace
|
|
({ stdout, exited } = spawn({
|
|
cmd: [bunExe(), "add", "two-range-deps"],
|
|
cwd: join(packageDir, "packages", "boba"),
|
|
stdout: "pipe",
|
|
stderr: "inherit",
|
|
env,
|
|
}));
|
|
|
|
out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun add v1."),
|
|
"",
|
|
"installed two-range-deps@1.0.0",
|
|
"",
|
|
"3 packages installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await file(join(packageDir, "packages", "boba", "package.json")).json()).toEqual({
|
|
name: "boba",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"pkg5": "*",
|
|
"two-range-deps": "^1.0.0",
|
|
},
|
|
});
|
|
expect(await readdirSorted(join(packageDir, "node_modules"))).toEqual([
|
|
"@types",
|
|
"bar",
|
|
"boba",
|
|
"no-deps",
|
|
"pkg5",
|
|
"two-range-deps",
|
|
]);
|
|
|
|
// add a dependency to a workspace with the same name as another workspace
|
|
({ stdout, exited } = spawn({
|
|
cmd: [bunExe(), "add", "bar@0.0.7"],
|
|
cwd: join(packageDir, "packages", "boba"),
|
|
stdout: "pipe",
|
|
stderr: "inherit",
|
|
env,
|
|
}));
|
|
|
|
out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun add v1."),
|
|
"",
|
|
"installed bar@0.0.7",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await file(join(packageDir, "packages", "boba", "package.json")).json()).toEqual({
|
|
name: "boba",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"pkg5": "*",
|
|
"two-range-deps": "^1.0.0",
|
|
"bar": "0.0.7",
|
|
},
|
|
});
|
|
expect(await readdirSorted(join(packageDir, "node_modules"))).toEqual([
|
|
"@types",
|
|
"bar",
|
|
"boba",
|
|
"no-deps",
|
|
"pkg5",
|
|
"two-range-deps",
|
|
]);
|
|
expect(await file(join(packageDir, "node_modules", "boba", "node_modules", "bar", "package.json")).json()).toEqual({
|
|
name: "bar",
|
|
version: "0.0.7",
|
|
description: "not a workspace",
|
|
});
|
|
});
|
|
test("it should detect duplicate workspace dependencies", async () => {
|
|
await write(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["packages/*"],
|
|
}),
|
|
);
|
|
|
|
await mkdir(join(packageDir, "packages", "pkg1"), { recursive: true });
|
|
await write(join(packageDir, "packages", "pkg1", "package.json"), JSON.stringify({ name: "pkg1" }));
|
|
await mkdir(join(packageDir, "packages", "pkg2"), { recursive: true });
|
|
await write(join(packageDir, "packages", "pkg2", "package.json"), JSON.stringify({ name: "pkg1" }));
|
|
|
|
var { stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
var err = await stderr.text();
|
|
expect(err).toContain('Workspace name "pkg1" already exists');
|
|
expect(await exited).toBe(1);
|
|
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
await rm(join(packageDir, "bun.lock"), { force: true });
|
|
|
|
({ stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: join(packageDir, "packages", "pkg1"),
|
|
stdout: "pipe",
|
|
stdin: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
err = await stderr.text();
|
|
expect(err).toContain('Workspace name "pkg1" already exists');
|
|
expect(await exited).toBe(1);
|
|
});
|
|
|
|
const versions = ["workspace:1.0.0", "workspace:*", "workspace:^1.0.0", "1.0.0", "*"];
|
|
|
|
for (const rootVersion of versions) {
|
|
for (const packageVersion of versions) {
|
|
test(`it should allow duplicates, root@${rootVersion}, package@${packageVersion}`, async () => {
|
|
await write(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
workspaces: ["packages/*"],
|
|
dependencies: {
|
|
pkg2: rootVersion,
|
|
},
|
|
}),
|
|
);
|
|
|
|
await mkdir(join(packageDir, "packages", "pkg1"), { recursive: true });
|
|
await write(
|
|
join(packageDir, "packages", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
pkg2: packageVersion,
|
|
},
|
|
}),
|
|
);
|
|
|
|
await mkdir(join(packageDir, "packages", "pkg2"), { recursive: true });
|
|
await write(
|
|
join(packageDir, "packages", "pkg2", "package.json"),
|
|
JSON.stringify({ name: "pkg2", version: "1.0.0" }),
|
|
);
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
var err = await stderr.text();
|
|
var out = await stdout.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
`+ pkg2@workspace:packages/pkg2`,
|
|
"",
|
|
"2 packages installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: join(packageDir, "packages", "pkg1"),
|
|
stdout: "pipe",
|
|
stdin: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
err = await stderr.text();
|
|
out = await stdout.text();
|
|
expect(err).not.toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"Checked 2 installs across 3 packages (no changes)",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
await rm(join(packageDir, "bun.lock"), { recursive: true, force: true });
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: join(packageDir, "packages", "pkg1"),
|
|
stdout: "pipe",
|
|
stdin: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
err = await stderr.text();
|
|
out = await stdout.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
`+ pkg2@workspace:packages/pkg2`,
|
|
"",
|
|
"2 packages installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
err = await stderr.text();
|
|
out = await stdout.text();
|
|
expect(err).not.toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"Checked 2 installs across 3 packages (no changes)",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
});
|
|
}
|
|
}
|
|
|
|
for (const version of versions) {
|
|
test(`it should allow listing workspace as dependency of the root package version ${version}`, async () => {
|
|
await write(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["packages/*"],
|
|
dependencies: {
|
|
"workspace-1": version,
|
|
},
|
|
}),
|
|
);
|
|
|
|
await mkdir(join(packageDir, "packages", "workspace-1"), { recursive: true });
|
|
await write(
|
|
join(packageDir, "packages", "workspace-1", "package.json"),
|
|
JSON.stringify({
|
|
name: "workspace-1",
|
|
version: "1.0.0",
|
|
}),
|
|
);
|
|
// install first from the root, the workspace package
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
var err = await stderr.text();
|
|
var out = await stdout.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("already exists");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("Duplicate dependency");
|
|
expect(err).not.toContain('workspace dependency "workspace-1" not found');
|
|
expect(err).not.toContain("error:");
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
`+ workspace-1@workspace:packages/workspace-1`,
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await file(join(packageDir, "node_modules", "workspace-1", "package.json")).json()).toEqual({
|
|
name: "workspace-1",
|
|
version: "1.0.0",
|
|
});
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: join(packageDir, "packages", "workspace-1"),
|
|
stdout: "pipe",
|
|
stdin: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
err = await stderr.text();
|
|
out = await stdout.text();
|
|
expect(err).not.toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("already exists");
|
|
expect(err).not.toContain("Duplicate dependency");
|
|
expect(err).not.toContain('workspace dependency "workspace-1" not found');
|
|
expect(err).not.toContain("error:");
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"Checked 1 install across 2 packages (no changes)",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await file(join(packageDir, "node_modules", "workspace-1", "package.json")).json()).toEqual({
|
|
name: "workspace-1",
|
|
version: "1.0.0",
|
|
});
|
|
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
await rm(join(packageDir, "bun.lock"), { recursive: true, force: true });
|
|
|
|
// install from workspace package then from root
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: join(packageDir, "packages", "workspace-1"),
|
|
stdout: "pipe",
|
|
stdin: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
err = await stderr.text();
|
|
out = await stdout.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("already exists");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("Duplicate dependency");
|
|
expect(err).not.toContain('workspace dependency "workspace-1" not found');
|
|
expect(err).not.toContain("error:");
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
expect(await file(join(packageDir, "node_modules", "workspace-1", "package.json")).json()).toEqual({
|
|
name: "workspace-1",
|
|
version: "1.0.0",
|
|
});
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
err = await stderr.text();
|
|
out = await stdout.text();
|
|
expect(err).not.toContain("Saved lockfile");
|
|
expect(err).not.toContain("already exists");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("Duplicate dependency");
|
|
expect(err).not.toContain('workspace dependency "workspace-1" not found');
|
|
expect(err).not.toContain("error:");
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"Checked 1 install across 2 packages (no changes)",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await file(join(packageDir, "node_modules", "workspace-1", "package.json")).json()).toEqual({
|
|
name: "workspace-1",
|
|
version: "1.0.0",
|
|
});
|
|
});
|
|
}
|
|
|
|
describe("install --filter", () => {
|
|
test("does not run root scripts if root is filtered out", async () => {
|
|
await Promise.all([
|
|
write(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "root",
|
|
workspaces: ["packages/*"],
|
|
scripts: {
|
|
postinstall: `${bunExe()} root.js`,
|
|
},
|
|
}),
|
|
),
|
|
write(join(packageDir, "root.js"), `require("fs").writeFileSync("root.txt", "")`),
|
|
write(
|
|
join(packageDir, "packages", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
scripts: {
|
|
postinstall: `${bunExe()} pkg1.js`,
|
|
},
|
|
}),
|
|
),
|
|
write(join(packageDir, "packages", "pkg1", "pkg1.js"), `require("fs").writeFileSync("pkg1.txt", "")`),
|
|
]);
|
|
|
|
var { exited } = spawn({
|
|
cmd: [bunExe(), "install", "--filter", "pkg1"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "ignore",
|
|
env,
|
|
});
|
|
|
|
expect(await exited).toBe(0);
|
|
|
|
expect(await exists(join(packageDir, "root.txt"))).toBeFalse();
|
|
expect(await exists(join(packageDir, "packages", "pkg1", "pkg1.txt"))).toBeTrue();
|
|
|
|
await rm(join(packageDir, "packages", "pkg1", "pkg1.txt"));
|
|
|
|
({ exited } = spawn({
|
|
cmd: [bunExe(), "install", "--filter", "root"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "ignore",
|
|
env,
|
|
}));
|
|
|
|
expect(await exited).toBe(0);
|
|
|
|
expect(await exists(join(packageDir, "root.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "packages", "pkg1.txt"))).toBeFalse();
|
|
});
|
|
|
|
test("basic", async () => {
|
|
await Promise.all([
|
|
write(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "root",
|
|
workspaces: ["packages/*"],
|
|
dependencies: {
|
|
"a-dep": "1.0.1",
|
|
},
|
|
}),
|
|
),
|
|
]);
|
|
|
|
var { exited } = spawn({
|
|
cmd: [bunExe(), "install", "--filter", "pkg1"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
expect(await exited).toBe(0);
|
|
expect(
|
|
await Promise.all([
|
|
exists(join(packageDir, "node_modules", "a-dep")),
|
|
exists(join(packageDir, "node_modules", "no-deps")),
|
|
]),
|
|
).toEqual([false, false]);
|
|
|
|
// add workspace
|
|
await write(
|
|
join(packageDir, "packages", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "2.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
({ exited } = spawn({
|
|
cmd: [bunExe(), "install", "--filter", "pkg1"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
expect(await exited).toBe(0);
|
|
expect(
|
|
await Promise.all([
|
|
exists(join(packageDir, "node_modules", "a-dep")),
|
|
exists(join(packageDir, "node_modules", "no-deps")),
|
|
]),
|
|
).toEqual([false, true]);
|
|
});
|
|
|
|
test("all but one or two", async () => {
|
|
await Promise.all([
|
|
write(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "root",
|
|
workspaces: ["packages/*"],
|
|
dependencies: {
|
|
"a-dep": "1.0.1",
|
|
},
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "2.0.0",
|
|
},
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg2", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg2",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
},
|
|
}),
|
|
),
|
|
]);
|
|
|
|
var { exited } = spawn({
|
|
cmd: [bunExe(), "install", "--filter", "!pkg2", "--save-text-lockfile"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
expect(await exited).toBe(0);
|
|
expect(
|
|
await Promise.all([
|
|
exists(join(packageDir, "node_modules", "a-dep")),
|
|
file(join(packageDir, "node_modules", "no-deps", "package.json")).json(),
|
|
exists(join(packageDir, "node_modules", "pkg2")),
|
|
]),
|
|
).toEqual([true, { name: "no-deps", version: "2.0.0" }, false]);
|
|
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
|
|
// exclude the root by name
|
|
({ exited } = spawn({
|
|
cmd: [bunExe(), "install", "--filter", "!root"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
expect(await exited).toBe(0);
|
|
expect(
|
|
await Promise.all([
|
|
exists(join(packageDir, "node_modules", "a-dep")),
|
|
exists(join(packageDir, "node_modules", "no-deps")),
|
|
exists(join(packageDir, "node_modules", "pkg1")),
|
|
exists(join(packageDir, "node_modules", "pkg2")),
|
|
]),
|
|
).toEqual([false, true, true, true]);
|
|
});
|
|
|
|
test("matched workspace depends on filtered workspace", async () => {
|
|
await Promise.all([
|
|
write(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "root",
|
|
workspaces: ["packages/*"],
|
|
dependencies: {
|
|
"a-dep": "1.0.1",
|
|
},
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "2.0.0",
|
|
},
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg2", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg2",
|
|
dependencies: {
|
|
"pkg1": "1.0.0",
|
|
},
|
|
}),
|
|
),
|
|
]);
|
|
|
|
var { exited } = spawn({
|
|
cmd: [bunExe(), "install", "--filter", "!pkg1"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
expect(await exited).toBe(0);
|
|
expect(
|
|
await Promise.all([
|
|
exists(join(packageDir, "node_modules", "a-dep")),
|
|
file(join(packageDir, "node_modules", "no-deps", "package.json")).json(),
|
|
exists(join(packageDir, "node_modules", "pkg1")),
|
|
exists(join(packageDir, "node_modules", "pkg2")),
|
|
]),
|
|
).toEqual([true, { name: "no-deps", version: "2.0.0" }, true, true]);
|
|
});
|
|
|
|
test("filter with a path", async () => {
|
|
await Promise.all([
|
|
write(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "path-pattern",
|
|
workspaces: ["packages/*"],
|
|
dependencies: {
|
|
"a-dep": "1.0.1",
|
|
},
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
dependencies: {
|
|
"no-deps": "2.0.0",
|
|
},
|
|
}),
|
|
),
|
|
]);
|
|
|
|
async function checkRoot() {
|
|
expect(
|
|
await Promise.all([
|
|
exists(join(packageDir, "node_modules", "a-dep")),
|
|
exists(join(packageDir, "node_modules", "no-deps", "package.json")),
|
|
exists(join(packageDir, "node_modules", "pkg1")),
|
|
]),
|
|
).toEqual([true, false, false]);
|
|
}
|
|
|
|
async function checkWorkspace() {
|
|
expect(
|
|
await Promise.all([
|
|
exists(join(packageDir, "node_modules", "a-dep")),
|
|
file(join(packageDir, "node_modules", "no-deps", "package.json")).json(),
|
|
exists(join(packageDir, "node_modules", "pkg1")),
|
|
]),
|
|
).toEqual([false, { name: "no-deps", version: "2.0.0" }, true]);
|
|
}
|
|
|
|
var { exited } = spawn({
|
|
cmd: [bunExe(), "install", "--filter", "./packages/pkg1"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
expect(await exited).toBe(0);
|
|
await checkWorkspace();
|
|
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
|
|
({ exited } = spawn({
|
|
cmd: [bunExe(), "install", "--filter", "./packages/*"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
expect(await exited).toBe(0);
|
|
await checkWorkspace();
|
|
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
|
|
({ exited } = spawn({
|
|
cmd: [bunExe(), "install", "--filter", "!./packages/pkg1"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
expect(await exited).toBe(0);
|
|
await checkRoot();
|
|
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
|
|
({ exited } = spawn({
|
|
cmd: [bunExe(), "install", "--filter", "!./packages/*"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
expect(await exited).toBe(0);
|
|
await checkRoot();
|
|
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
|
|
({ exited } = spawn({
|
|
cmd: [bunExe(), "install", "--filter", "!./"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
expect(await exited).toBe(0);
|
|
await checkWorkspace();
|
|
});
|
|
});
|
|
|
|
test("can override npm package with workspace package under a different name", async () => {
|
|
await Promise.all([
|
|
write(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["packages/*"],
|
|
dependencies: {
|
|
"one-dep": "1.0.0",
|
|
},
|
|
overrides: {
|
|
"no-deps": "workspace:packages/pkg1",
|
|
},
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
version: "2.2.2",
|
|
}),
|
|
),
|
|
]);
|
|
|
|
var { exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
expect(await exited).toBe(0);
|
|
expect(await file(join(packageDir, "node_modules", "no-deps", "package.json")).json()).toEqual({
|
|
name: "pkg1",
|
|
version: "2.2.2",
|
|
});
|
|
|
|
// another install can use the existing bun.lock successfully
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
({ exited } = spawn({
|
|
cmd: [bunExe(), "install", "--frozen-lockfile"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
expect(await exited).toBe(0);
|
|
expect(await file(join(packageDir, "node_modules", "no-deps", "package.json")).json()).toEqual({
|
|
name: "pkg1",
|
|
version: "2.2.2",
|
|
});
|
|
});
|
|
|
|
describe("LinkWorkspacePackages", () => {
|
|
let bunfigPath: string;
|
|
|
|
beforeEach(async () => {
|
|
bunfigPath = join(packageDir, "bunfig.toml");
|
|
|
|
await Promise.all([
|
|
write(
|
|
join(packageDir, "package.json"),
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["packages/*"],
|
|
}),
|
|
),
|
|
|
|
write(
|
|
join(packageDir, "packages", "mono", "package.json"),
|
|
JSON.stringify({
|
|
name: "no-deps",
|
|
version: "2.0.0",
|
|
}),
|
|
),
|
|
]);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await Promise.all([
|
|
rm(bunfigPath, { force: true }),
|
|
rm(join(packageDir, "node_modules"), { recursive: true, force: true }),
|
|
rm(join(packageDir, "packages"), { recursive: true, force: true }),
|
|
rm(join(packageDir, "package.json"), { force: true }),
|
|
]);
|
|
});
|
|
|
|
test("linkWorkspacePackages = false uses registry instead of linking workspace packages", async () => {
|
|
// Create bunfig.toml with linkWorkspacePackages set to false
|
|
await Promise.all([
|
|
write(
|
|
bunfigPath,
|
|
`
|
|
[install]
|
|
linkWorkspacePackages = false
|
|
registry = "${verdaccio.registryUrl()}"
|
|
`,
|
|
),
|
|
|
|
write(
|
|
join(packageDir, "packages", "bar", "package.json"),
|
|
JSON.stringify({
|
|
name: "bar",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "2.0.0", // Use Same version as workspace package and it shouldn't link
|
|
},
|
|
}),
|
|
),
|
|
]);
|
|
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), `-c=${bunfigPath}`, "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
const out = await stdout.text();
|
|
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("error:");
|
|
expect(await exited).toBe(0);
|
|
const lockfile = parseLockfile(packageDir);
|
|
|
|
// Check the resolution tag to ensure it's not a workspace link
|
|
const barPackage = lockfile.packages.find(p => p.name === "bar");
|
|
expect(barPackage.dependencies.length).toEqual(1);
|
|
const barDependency = lockfile.dependencies.find(p => p.id === barPackage.dependencies[0]);
|
|
expect(barDependency).toBeDefined();
|
|
|
|
// Verify that the dependency linked to the bar package is the npm version, not the workspace version
|
|
expect(lockfile.packages.find(p => p.id === barDependency?.package_id).resolution.tag).toEqual("npm");
|
|
});
|
|
|
|
test("linkWorkspacePackages = false but workspace: prefix still links workspace", async () => {
|
|
// Create bunfig.toml with linkWorkspacePackages set to false
|
|
await Promise.all([
|
|
write(
|
|
bunfigPath,
|
|
`
|
|
[install]
|
|
linkWorkspacePackages = false
|
|
registry = "${verdaccio.registryUrl()}"
|
|
`,
|
|
),
|
|
|
|
write(
|
|
join(packageDir, "packages", "bar", "package.json"),
|
|
JSON.stringify({
|
|
name: "bar",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"no-deps": "workspace:*", // Explicit workspace: prefix should still link
|
|
},
|
|
}),
|
|
),
|
|
]);
|
|
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), `-c=${bunfigPath}`, "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
const out = await stdout.text();
|
|
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("error:");
|
|
expect(await exited).toBe(0);
|
|
const lockfile = parseLockfile(packageDir);
|
|
|
|
// Check the resolution tag to ensure it's not a workspace link
|
|
const barPackage = lockfile.packages.find(p => p.name === "bar");
|
|
expect(barPackage.dependencies.length).toEqual(1);
|
|
const barDependency = lockfile.dependencies.find(p => p.id === barPackage.dependencies[0]);
|
|
expect(barDependency).toBeDefined();
|
|
|
|
// Verify that the dependency linked to the bar package is the workspace version (using the workspace: prefix), not the npm version
|
|
expect(lockfile.packages.find(p => p.id === barDependency?.package_id).resolution.tag).toEqual("workspace");
|
|
});
|
|
});
|
|
|
|
test("matching workspace devDependency and npm peerDependency", async () => {
|
|
await Promise.all([
|
|
write(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
workspaces: ["packages/*"],
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
version: "1.0.0",
|
|
devDependencies: {
|
|
"no-deps": "workspace:*", // resolves to ./packages/pkg2
|
|
},
|
|
peerDependencies: {
|
|
"no-deps": "2.0.0", // npm peerDependency
|
|
},
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "packages", "pkg2", "package.json"),
|
|
JSON.stringify({
|
|
name: "no-deps",
|
|
version: "1.0.0",
|
|
}),
|
|
),
|
|
]);
|
|
|
|
// first install should resolve both
|
|
let { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install", "--save-text-lockfile"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
expect(await exited).toBe(0);
|
|
|
|
// both dependencies should be included in the lockfile
|
|
expect((await file(join(packageDir, "bun.lock")).text()).replaceAll(/localhost:\d+/g, "localhost:1234"))
|
|
.toMatchInlineSnapshot(`
|
|
"{
|
|
"lockfileVersion": 1,
|
|
"configVersion": 1,
|
|
"workspaces": {
|
|
"": {
|
|
"name": "foo",
|
|
},
|
|
"packages/pkg1": {
|
|
"name": "pkg1",
|
|
"version": "1.0.0",
|
|
"devDependencies": {
|
|
"no-deps": "workspace:*",
|
|
},
|
|
"peerDependencies": {
|
|
"no-deps": "2.0.0",
|
|
},
|
|
},
|
|
"packages/pkg2": {
|
|
"name": "no-deps",
|
|
"version": "1.0.0",
|
|
},
|
|
},
|
|
"packages": {
|
|
"no-deps": ["no-deps@workspace:packages/pkg2"],
|
|
|
|
"pkg1": ["pkg1@workspace:packages/pkg1"],
|
|
}
|
|
}
|
|
"
|
|
`);
|
|
|
|
// another install does not think there's a diff between lockfile and package.jsons
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install", "--verbose"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env,
|
|
}));
|
|
|
|
expect(await exited).toBe(0);
|
|
|
|
const out = await stdout.text();
|
|
const err = await stderr.text();
|
|
expect(err).not.toContain("Saved lockfile");
|
|
expect(err).not.toContain("updated");
|
|
expect(out).toContain("no changes");
|
|
});
|