mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
## Summary - The default trusted dependencies list should only apply to packages installed from npm - Non-npm sources (file:, link:, git:, github:) now require explicit trustedDependencies - This prevents malicious packages from spoofing trusted names through local paths or git repos ## Test plan - [x] Added test: file: dependency named "esbuild" does NOT auto-run postinstall scripts - [x] Added test: file: dependency runs scripts when explicitly added to trustedDependencies - [x] Verified tests fail with system bun (old behavior) and pass with new build - [x] Build compiles successfully 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com> Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
3091 lines
105 KiB
TypeScript
3091 lines
105 KiB
TypeScript
import { file, spawn, write } from "bun";
|
|
import { afterAll, beforeAll, beforeEach, describe, expect, setDefaultTimeout, test } from "bun:test";
|
|
import { exists, mkdir, rm, writeFile } from "fs/promises";
|
|
import {
|
|
VerdaccioRegistry,
|
|
assertManifestsPopulated,
|
|
bunExe,
|
|
bunEnv as env,
|
|
isLinux,
|
|
isWindows,
|
|
readdirSorted,
|
|
runBunInstall,
|
|
stderrForInstall,
|
|
} from "harness";
|
|
import { join, sep } from "path";
|
|
|
|
var verdaccio = new VerdaccioRegistry();
|
|
var packageDir: string;
|
|
var packageJson: string;
|
|
|
|
beforeAll(async () => {
|
|
setDefaultTimeout(1000 * 60 * 5);
|
|
await verdaccio.start();
|
|
});
|
|
|
|
afterAll(() => {
|
|
verdaccio.stop();
|
|
});
|
|
|
|
function splitErrLines(err: string): string[] {
|
|
return err.split(/\r?\n/).filter(s => !s.startsWith("WARNING: ASAN interferes"));
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
({ packageDir, packageJson } = await verdaccio.createTestDir({ bunfigOpts: { linker: "hoisted" } }));
|
|
env.BUN_INSTALL_CACHE_DIR = join(packageDir, ".bun-cache");
|
|
env.BUN_TMPDIR = env.TMPDIR = env.TEMP = join(packageDir, ".bun-tmp");
|
|
});
|
|
|
|
// waiter thread is only a thing on Linux.
|
|
for (const forceWaiterThread of isLinux ? [false, true] : [false]) {
|
|
describe("lifecycle scripts" + (forceWaiterThread ? " (waiter thread)" : ""), async () => {
|
|
test("root package with all lifecycle scripts", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
const writeScript = async (name: string) => {
|
|
const contents = `
|
|
import { writeFileSync, existsSync, rmSync } from "fs";
|
|
import { join } from "path";
|
|
|
|
const file = join(import.meta.dir, "${name}.txt");
|
|
|
|
if (existsSync(file)) {
|
|
rmSync(file);
|
|
writeFileSync(file, "${name} exists!");
|
|
} else {
|
|
writeFileSync(file, "${name}!");
|
|
}
|
|
`;
|
|
await writeFile(join(packageDir, `${name}.js`), contents);
|
|
};
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
preinstall: `${bunExe()} preinstall.js`,
|
|
install: `${bunExe()} install.js`,
|
|
postinstall: `${bunExe()} postinstall.js`,
|
|
preprepare: `${bunExe()} preprepare.js`,
|
|
prepare: `${bunExe()} prepare.js`,
|
|
postprepare: `${bunExe()} postprepare.js`,
|
|
},
|
|
}),
|
|
);
|
|
|
|
await writeScript("preinstall");
|
|
await writeScript("install");
|
|
await writeScript("postinstall");
|
|
await writeScript("preprepare");
|
|
await writeScript("prepare");
|
|
await writeScript("postprepare");
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
var err = await stderr.text();
|
|
var out = await stdout.text();
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "preinstall.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "install.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "postinstall.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "preprepare.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "prepare.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "postprepare.txt"))).toBeTrue();
|
|
expect(await file(join(packageDir, "preinstall.txt")).text()).toBe("preinstall!");
|
|
expect(await file(join(packageDir, "install.txt")).text()).toBe("install!");
|
|
expect(await file(join(packageDir, "postinstall.txt")).text()).toBe("postinstall!");
|
|
expect(await file(join(packageDir, "preprepare.txt")).text()).toBe("preprepare!");
|
|
expect(await file(join(packageDir, "prepare.txt")).text()).toBe("prepare!");
|
|
expect(await file(join(packageDir, "postprepare.txt")).text()).toBe("postprepare!");
|
|
|
|
// add a dependency with all lifecycle scripts
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
preinstall: `${bunExe()} preinstall.js`,
|
|
install: `${bunExe()} install.js`,
|
|
postinstall: `${bunExe()} postinstall.js`,
|
|
preprepare: `${bunExe()} preprepare.js`,
|
|
prepare: `${bunExe()} prepare.js`,
|
|
postprepare: `${bunExe()} postprepare.js`,
|
|
},
|
|
dependencies: {
|
|
"all-lifecycle-scripts": "1.0.0",
|
|
},
|
|
trustedDependencies: ["all-lifecycle-scripts"],
|
|
}),
|
|
);
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
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."),
|
|
"",
|
|
"+ all-lifecycle-scripts@1.0.0",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await file(join(packageDir, "preinstall.txt")).text()).toBe("preinstall exists!");
|
|
expect(await file(join(packageDir, "install.txt")).text()).toBe("install exists!");
|
|
expect(await file(join(packageDir, "postinstall.txt")).text()).toBe("postinstall exists!");
|
|
expect(await file(join(packageDir, "preprepare.txt")).text()).toBe("preprepare exists!");
|
|
expect(await file(join(packageDir, "prepare.txt")).text()).toBe("prepare exists!");
|
|
expect(await file(join(packageDir, "postprepare.txt")).text()).toBe("postprepare exists!");
|
|
|
|
const depDir = join(packageDir, "node_modules", "all-lifecycle-scripts");
|
|
|
|
expect(await exists(join(depDir, "preinstall.txt"))).toBeTrue();
|
|
expect(await exists(join(depDir, "install.txt"))).toBeTrue();
|
|
expect(await exists(join(depDir, "postinstall.txt"))).toBeTrue();
|
|
expect(await exists(join(depDir, "preprepare.txt"))).toBeFalse();
|
|
expect(await exists(join(depDir, "prepare.txt"))).toBeTrue();
|
|
expect(await exists(join(depDir, "postprepare.txt"))).toBeFalse();
|
|
|
|
expect(await file(join(depDir, "preinstall.txt")).text()).toBe("preinstall!");
|
|
expect(await file(join(depDir, "install.txt")).text()).toBe("install!");
|
|
expect(await file(join(depDir, "postinstall.txt")).text()).toBe("postinstall!");
|
|
expect(await file(join(depDir, "prepare.txt")).text()).toBe("prepare!");
|
|
|
|
await rm(join(packageDir, "preinstall.txt"));
|
|
await rm(join(packageDir, "install.txt"));
|
|
await rm(join(packageDir, "postinstall.txt"));
|
|
await rm(join(packageDir, "preprepare.txt"));
|
|
await rm(join(packageDir, "prepare.txt"));
|
|
await rm(join(packageDir, "postprepare.txt"));
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
await rm(join(packageDir, "bun.lock"));
|
|
|
|
// all at once
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
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."),
|
|
"",
|
|
"+ all-lifecycle-scripts@1.0.0",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
|
|
expect(await file(join(packageDir, "preinstall.txt")).text()).toBe("preinstall!");
|
|
expect(await file(join(packageDir, "install.txt")).text()).toBe("install!");
|
|
expect(await file(join(packageDir, "postinstall.txt")).text()).toBe("postinstall!");
|
|
expect(await file(join(packageDir, "preprepare.txt")).text()).toBe("preprepare!");
|
|
expect(await file(join(packageDir, "prepare.txt")).text()).toBe("prepare!");
|
|
expect(await file(join(packageDir, "postprepare.txt")).text()).toBe("postprepare!");
|
|
|
|
expect(await file(join(depDir, "preinstall.txt")).text()).toBe("preinstall!");
|
|
expect(await file(join(depDir, "install.txt")).text()).toBe("install!");
|
|
expect(await file(join(depDir, "postinstall.txt")).text()).toBe("postinstall!");
|
|
expect(await file(join(depDir, "prepare.txt")).text()).toBe("prepare!");
|
|
});
|
|
|
|
test("workspace lifecycle scripts", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
workspaces: ["packages/*"],
|
|
scripts: {
|
|
preinstall: `touch preinstall.txt`,
|
|
install: `touch install.txt`,
|
|
postinstall: `touch postinstall.txt`,
|
|
preprepare: `touch preprepare.txt`,
|
|
prepare: `touch prepare.txt`,
|
|
postprepare: `touch postprepare.txt`,
|
|
},
|
|
}),
|
|
);
|
|
|
|
await mkdir(join(packageDir, "packages", "pkg1"), { recursive: true });
|
|
await writeFile(
|
|
join(packageDir, "packages", "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
preinstall: `touch preinstall.txt`,
|
|
install: `touch install.txt`,
|
|
postinstall: `touch postinstall.txt`,
|
|
preprepare: `touch preprepare.txt`,
|
|
prepare: `touch prepare.txt`,
|
|
postprepare: `touch postprepare.txt`,
|
|
},
|
|
}),
|
|
);
|
|
|
|
await mkdir(join(packageDir, "packages", "pkg2"), { recursive: true });
|
|
await writeFile(
|
|
join(packageDir, "packages", "pkg2", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg2",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
preinstall: `touch preinstall.txt`,
|
|
install: `touch install.txt`,
|
|
postinstall: `touch postinstall.txt`,
|
|
preprepare: `touch preprepare.txt`,
|
|
prepare: `touch prepare.txt`,
|
|
postprepare: `touch postprepare.txt`,
|
|
},
|
|
}),
|
|
);
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
var err = await stderr.text();
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).toContain("Saved lockfile");
|
|
var out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"2 packages installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "preinstall.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "install.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "postinstall.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "preprepare.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "prepare.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "postprepare.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "packages", "pkg1", "preinstall.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "packages", "pkg1", "install.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "packages", "pkg1", "postinstall.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "packages", "pkg1", "preprepare.txt"))).toBeFalse();
|
|
expect(await exists(join(packageDir, "packages", "pkg1", "prepare.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "packages", "pkg1", "postprepare.txt"))).toBeFalse();
|
|
expect(await exists(join(packageDir, "packages", "pkg2", "preinstall.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "packages", "pkg2", "install.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "packages", "pkg2", "postinstall.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "packages", "pkg2", "preprepare.txt"))).toBeFalse();
|
|
expect(await exists(join(packageDir, "packages", "pkg2", "prepare.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "packages", "pkg2", "postprepare.txt"))).toBeFalse();
|
|
});
|
|
|
|
test("dependency lifecycle scripts run before root lifecycle scripts", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
const script = '[[ -f "./node_modules/uses-what-bin-slow/what-bin.txt" ]]';
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"uses-what-bin-slow": "1.0.0",
|
|
},
|
|
trustedDependencies: ["uses-what-bin-slow"],
|
|
scripts: {
|
|
install: script,
|
|
postinstall: script,
|
|
preinstall: script,
|
|
prepare: script,
|
|
postprepare: script,
|
|
preprepare: script,
|
|
},
|
|
}),
|
|
);
|
|
|
|
// uses-what-bin-slow will wait one second then write a file to disk. The root package should wait for
|
|
// for this to happen before running its lifecycle scripts.
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
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(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
});
|
|
|
|
test("install a dependency with lifecycle scripts, then add to trusted dependencies and install again", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"all-lifecycle-scripts": "1.0.0",
|
|
},
|
|
trustedDependencies: [],
|
|
}),
|
|
);
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
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\]$/m, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ all-lifecycle-scripts@1.0.0",
|
|
"",
|
|
"1 package installed",
|
|
"",
|
|
"Blocked 3 postinstalls. Run `bun pm untrusted` for details.",
|
|
"",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
const depDir = join(packageDir, "node_modules", "all-lifecycle-scripts");
|
|
expect(await exists(join(depDir, "preinstall.txt"))).toBeFalse();
|
|
expect(await exists(join(depDir, "install.txt"))).toBeFalse();
|
|
expect(await exists(join(depDir, "postinstall.txt"))).toBeFalse();
|
|
expect(await exists(join(depDir, "preprepare.txt"))).toBeFalse();
|
|
expect(await exists(join(depDir, "prepare.txt"))).toBeTrue();
|
|
expect(await exists(join(depDir, "postprepare.txt"))).toBeFalse();
|
|
expect(await file(join(depDir, "prepare.txt")).text()).toBe("prepare!");
|
|
|
|
// add to trusted dependencies
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"all-lifecycle-scripts": "1.0.0",
|
|
},
|
|
trustedDependencies: ["all-lifecycle-scripts"],
|
|
}),
|
|
);
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
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."),
|
|
"",
|
|
expect.stringContaining("Checked 1 install across 2 packages (no changes)"),
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await file(join(depDir, "preinstall.txt")).text()).toBe("preinstall!");
|
|
expect(await file(join(depDir, "install.txt")).text()).toBe("install!");
|
|
expect(await file(join(depDir, "postinstall.txt")).text()).toBe("postinstall!");
|
|
expect(await file(join(depDir, "prepare.txt")).text()).toBe("prepare!");
|
|
expect(await exists(join(depDir, "preprepare.txt"))).toBeFalse();
|
|
expect(await exists(join(depDir, "postprepare.txt"))).toBeFalse();
|
|
});
|
|
|
|
test("adding a package without scripts to trustedDependencies", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"what-bin": "1.0.0",
|
|
},
|
|
trustedDependencies: ["what-bin"],
|
|
}),
|
|
);
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
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."),
|
|
"",
|
|
expect.stringContaining("+ what-bin@1.0.0"),
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await readdirSorted(join(packageDir, "node_modules"))).toEqual([".bin", "what-bin"]);
|
|
const what_bin_bins = !isWindows ? ["what-bin"] : ["what-bin.bunx", "what-bin.exe"];
|
|
// prettier-ignore
|
|
expect(await readdirSorted(join(packageDir, "node_modules", ".bin"))).toEqual(what_bin_bins);
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
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 1 install across 2 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"));
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: { "what-bin": "1.0.0" },
|
|
}),
|
|
);
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
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."),
|
|
"",
|
|
expect.stringContaining("+ what-bin@1.0.0"),
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await readdirSorted(join(packageDir, "node_modules"))).toEqual([".bin", "what-bin"]);
|
|
expect(await readdirSorted(join(packageDir, "node_modules", ".bin"))).toEqual(what_bin_bins);
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
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 1 install across 2 packages (no changes)",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await readdirSorted(join(packageDir, "node_modules"))).toEqual([".bin", "what-bin"]);
|
|
expect(await readdirSorted(join(packageDir, "node_modules", ".bin"))).toEqual(what_bin_bins);
|
|
|
|
// add it to trusted dependencies
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"what-bin": "1.0.0",
|
|
},
|
|
trustedDependencies: ["what-bin"],
|
|
}),
|
|
);
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
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."),
|
|
"",
|
|
"Checked 1 install across 2 packages (no changes)",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await readdirSorted(join(packageDir, "node_modules"))).toEqual([".bin", "what-bin"]);
|
|
expect(await readdirSorted(join(packageDir, "node_modules", ".bin"))).toEqual(what_bin_bins);
|
|
});
|
|
|
|
test("lifecycle scripts run if node_modules is deleted", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"lifecycle-postinstall": "1.0.0",
|
|
},
|
|
trustedDependencies: ["lifecycle-postinstall"],
|
|
}),
|
|
);
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
var err = await stderr.text();
|
|
var out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ lifecycle-postinstall@1.0.0",
|
|
"",
|
|
// @ts-ignore
|
|
"1 package installed",
|
|
]);
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(await exists(join(packageDir, "node_modules", "lifecycle-postinstall", "postinstall.txt"))).toBeTrue();
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
await rm(join(packageDir, "node_modules"), { force: true, recursive: true });
|
|
await rm(join(packageDir, ".bun-cache"), { recursive: true, force: true });
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
err = await stderr.text();
|
|
out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ lifecycle-postinstall@1.0.0",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(err).not.toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(await exists(join(packageDir, "node_modules", "lifecycle-postinstall", "postinstall.txt"))).toBeTrue();
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
});
|
|
|
|
test("INIT_CWD is set to the correct directory", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
install: "bun install.js",
|
|
},
|
|
dependencies: {
|
|
"lifecycle-init-cwd": "1.0.0",
|
|
"another-init-cwd": "npm:lifecycle-init-cwd@1.0.0",
|
|
},
|
|
trustedDependencies: ["lifecycle-init-cwd", "another-init-cwd"],
|
|
}),
|
|
);
|
|
|
|
await writeFile(
|
|
join(packageDir, "install.js"),
|
|
`
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
fs.writeFileSync(
|
|
path.join(__dirname, "test.txt"),
|
|
process.env.INIT_CWD || "does not exist"
|
|
);
|
|
`,
|
|
);
|
|
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
const 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."),
|
|
"",
|
|
"+ another-init-cwd@1.0.0",
|
|
"+ lifecycle-init-cwd@1.0.0",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await file(join(packageDir, "test.txt")).text()).toBe(packageDir);
|
|
expect(await file(join(packageDir, "node_modules/lifecycle-init-cwd/test.txt")).text()).toBe(packageDir);
|
|
expect(await file(join(packageDir, "node_modules/another-init-cwd/test.txt")).text()).toBe(packageDir);
|
|
});
|
|
|
|
test("failing lifecycle script should print output", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"lifecycle-failing-postinstall": "1.0.0",
|
|
},
|
|
trustedDependencies: ["lifecycle-failing-postinstall"],
|
|
}),
|
|
);
|
|
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
expect(err).toContain("hello");
|
|
expect(await exited).toBe(1);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
const out = await stdout.text();
|
|
expect(out).toEqual(expect.stringContaining("bun install v1."));
|
|
});
|
|
|
|
test("failing root lifecycle script should print output correctly", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "fooooooooo",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
preinstall: `${bunExe()} -e "throw new Error('Oops!')"`,
|
|
},
|
|
}),
|
|
);
|
|
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
expect(await exited).toBe(1);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await stdout.text()).toEqual(expect.stringContaining("bun install v1."));
|
|
const err = await stderr.text();
|
|
expect(err).toContain("error: Oops!");
|
|
expect(err).toContain('error: preinstall script from "fooooooooo" exited with 1');
|
|
});
|
|
|
|
test("exit 0 in lifecycle scripts works", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
postinstall: "exit 0",
|
|
prepare: "exit 0",
|
|
postprepare: "exit 0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
expect(err).toContain("No packages! Deleted empty lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
const out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
expect.stringContaining("done"),
|
|
"",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
});
|
|
|
|
test("--ignore-scripts should skip lifecycle scripts", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
dependencies: {
|
|
"lifecycle-failing-postinstall": "1.0.0",
|
|
},
|
|
trustedDependencies: ["lifecycle-failing-postinstall"],
|
|
}),
|
|
);
|
|
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install", "--ignore-scripts"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
stdin: "ignore",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("hello");
|
|
const out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ lifecycle-failing-postinstall@1.0.0",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
});
|
|
|
|
test("it should add `node-gyp rebuild` as the `install` script when `install` and `postinstall` don't exist and `binding.gyp` exists in the root of the package", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"binding-gyp-scripts": "1.5.0",
|
|
},
|
|
trustedDependencies: ["binding-gyp-scripts"],
|
|
}),
|
|
);
|
|
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
const out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ binding-gyp-scripts@1.5.0",
|
|
"",
|
|
"2 packages installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules/binding-gyp-scripts/build.node"))).toBeTrue();
|
|
});
|
|
|
|
test("automatic node-gyp scripts should not run for untrusted dependencies, and should run after adding to `trustedDependencies`", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
const packageJSON: any = {
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"binding-gyp-scripts": "1.5.0",
|
|
},
|
|
};
|
|
await writeFile(packageJson, JSON.stringify(packageJSON));
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
let err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
const out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]$/m, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ binding-gyp-scripts@1.5.0",
|
|
"",
|
|
"2 packages installed",
|
|
"",
|
|
"Blocked 1 postinstall. Run `bun pm untrusted` for details.",
|
|
"",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules", "binding-gyp-scripts", "build.node"))).toBeFalse();
|
|
|
|
packageJSON.trustedDependencies = ["binding-gyp-scripts"];
|
|
await writeFile(packageJson, JSON.stringify(packageJSON));
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules", "binding-gyp-scripts", "build.node"))).toBeTrue();
|
|
});
|
|
|
|
test("automatic node-gyp scripts work in package root", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"node-gyp": "1.5.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
await writeFile(join(packageDir, "binding.gyp"), "");
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
const out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ node-gyp@1.5.0",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "build.node"))).toBeTrue();
|
|
|
|
await rm(join(packageDir, "build.node"));
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "build.node"))).toBeTrue();
|
|
});
|
|
|
|
test("auto node-gyp scripts work when scripts exists other than `install` and `preinstall`", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"node-gyp": "1.5.0",
|
|
},
|
|
scripts: {
|
|
postinstall: "exit 0",
|
|
prepare: "exit 0",
|
|
postprepare: "exit 0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
await writeFile(join(packageDir, "binding.gyp"), "");
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
const out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ node-gyp@1.5.0",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "build.node"))).toBeTrue();
|
|
});
|
|
|
|
for (const script of ["install", "preinstall"]) {
|
|
test(`does not add auto node-gyp script when ${script} script exists`, async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
const packageJSON: any = {
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"node-gyp": "1.5.0",
|
|
},
|
|
scripts: {
|
|
[script]: "exit 0",
|
|
},
|
|
};
|
|
await writeFile(packageJson, JSON.stringify(packageJSON));
|
|
await writeFile(join(packageDir, "binding.gyp"), "");
|
|
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
const out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ node-gyp@1.5.0",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "build.node"))).toBeFalse();
|
|
});
|
|
}
|
|
|
|
test("git dependencies also run `preprepare`, `prepare`, and `postprepare` scripts", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"lifecycle-install-test": "dylan-conway/lifecycle-install-test#3ba6af5b64f2d27456e08df21d750072dffd3eee",
|
|
},
|
|
}),
|
|
);
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
let err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
const out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]$/m, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ lifecycle-install-test@github:dylan-conway/lifecycle-install-test#3ba6af5",
|
|
"",
|
|
"1 package installed",
|
|
"",
|
|
"Blocked 6 postinstalls. Run `bun pm untrusted` for details.",
|
|
"",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules", "lifecycle-install-test", "preprepare.txt"))).toBeFalse();
|
|
expect(await exists(join(packageDir, "node_modules", "lifecycle-install-test", "prepare.txt"))).toBeFalse();
|
|
expect(await exists(join(packageDir, "node_modules", "lifecycle-install-test", "postprepare.txt"))).toBeFalse();
|
|
expect(await exists(join(packageDir, "node_modules", "lifecycle-install-test", "preinstall.txt"))).toBeFalse();
|
|
expect(await exists(join(packageDir, "node_modules", "lifecycle-install-test", "install.txt"))).toBeFalse();
|
|
expect(await exists(join(packageDir, "node_modules", "lifecycle-install-test", "postinstall.txt"))).toBeFalse();
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"lifecycle-install-test": "dylan-conway/lifecycle-install-test#3ba6af5b64f2d27456e08df21d750072dffd3eee",
|
|
},
|
|
trustedDependencies: ["lifecycle-install-test"],
|
|
}),
|
|
);
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules", "lifecycle-install-test", "preprepare.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "node_modules", "lifecycle-install-test", "prepare.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "node_modules", "lifecycle-install-test", "postprepare.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "node_modules", "lifecycle-install-test", "preinstall.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "node_modules", "lifecycle-install-test", "install.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "node_modules", "lifecycle-install-test", "postinstall.txt"))).toBeTrue();
|
|
});
|
|
|
|
test("root lifecycle scripts should wait for dependency lifecycle scripts", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"uses-what-bin-slow": "1.0.0",
|
|
},
|
|
trustedDependencies: ["uses-what-bin-slow"],
|
|
scripts: {
|
|
install: '[[ -f "./node_modules/uses-what-bin-slow/what-bin.txt" ]]',
|
|
},
|
|
}),
|
|
);
|
|
|
|
// Package `uses-what-bin-slow` has an install script that will sleep for 1 second
|
|
// before writing `what-bin.txt` to disk. The root package has an install script that
|
|
// checks if this file exists. If the root package install script does not wait for
|
|
// the other to finish, it will fail.
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
const out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ uses-what-bin-slow@1.0.0",
|
|
"",
|
|
"2 packages installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
});
|
|
|
|
async function createPackagesWithScripts(
|
|
packagesCount: number,
|
|
scripts: Record<string, string>,
|
|
): Promise<string[]> {
|
|
const dependencies: Record<string, string> = {};
|
|
const dependenciesList: string[] = [];
|
|
|
|
async function iterate(i) {
|
|
const packageName: string = "stress-test-package-" + i;
|
|
const packageVersion = "1.0." + i;
|
|
|
|
dependencies[packageName] = "file:./" + packageName;
|
|
dependenciesList[i] = packageName;
|
|
|
|
const packagePath = join(packageDir, packageName);
|
|
|
|
await Bun.write(
|
|
join(packagePath, "package.json"),
|
|
JSON.stringify({
|
|
name: packageName,
|
|
version: packageVersion,
|
|
scripts,
|
|
}),
|
|
);
|
|
}
|
|
|
|
await Promise.all(Array.from({ length: packagesCount }, (_, i) => iterate(i)));
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "stress-test",
|
|
version: "1.0.0",
|
|
dependencies,
|
|
trustedDependencies: dependenciesList,
|
|
}),
|
|
);
|
|
|
|
return dependenciesList;
|
|
}
|
|
|
|
test("reach max concurrent scripts", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
const scripts = {
|
|
"preinstall": `${bunExe()} -e 'Bun.sleepSync(500)'`,
|
|
};
|
|
|
|
const dependenciesList = await createPackagesWithScripts(4, scripts);
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install", "--concurrent-scripts=2"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const [err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
|
|
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(out).not.toContain("Blocked");
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
...dependenciesList.map(dep => `+ ${dep}@${dep}`),
|
|
"",
|
|
"4 packages installed",
|
|
]);
|
|
expect(exitCode).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
});
|
|
|
|
test("stress test", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
const dependenciesList = await createPackagesWithScripts(500, {
|
|
"postinstall": `${bunExe()} --version`,
|
|
});
|
|
|
|
// the script is quick, default number for max concurrent scripts
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const [err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
|
|
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(out).not.toContain("Blocked");
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
...dependenciesList.map(dep => `+ ${dep}@${dep}`).sort((a, b) => a.localeCompare(b)),
|
|
"",
|
|
"500 packages installed",
|
|
]);
|
|
|
|
expect(exitCode).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
});
|
|
|
|
test("it should install and use correct binary version", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
// this should install `what-bin` in two places:
|
|
//
|
|
// - node_modules/.bin/what-bin@1.5.0
|
|
// - node_modules/uses-what-bin/node_modules/.bin/what-bin@1.0.0
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
"what-bin": "1.5.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
var err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
var out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]$/m, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
expect.stringContaining("+ uses-what-bin@1.0.0"),
|
|
"+ what-bin@1.5.0",
|
|
"",
|
|
"3 packages installed",
|
|
"",
|
|
"Blocked 1 postinstall. Run `bun pm untrusted` for details.",
|
|
"",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await file(join(packageDir, "node_modules", "what-bin", "what-bin.js")).text()).toContain(
|
|
"what-bin@1.5.0",
|
|
);
|
|
expect(
|
|
await file(join(packageDir, "node_modules", "uses-what-bin", "node_modules", "what-bin", "what-bin.js")).text(),
|
|
).toContain("what-bin@1.0.0");
|
|
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
await rm(join(packageDir, "bun.lock"));
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
"uses-what-bin": "1.5.0",
|
|
"what-bin": "1.0.0",
|
|
},
|
|
scripts: {
|
|
install: "what-bin",
|
|
},
|
|
trustedDependencies: ["uses-what-bin"],
|
|
}),
|
|
);
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
|
|
expect(await exited).toBe(0);
|
|
const firstLockfile = await (
|
|
await file(join(packageDir, "bun.lock")).text()
|
|
).replaceAll(/localhost:\d+/g, "localhost:1234");
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await file(join(packageDir, "node_modules", "what-bin", "what-bin.js")).text()).toContain(
|
|
"what-bin@1.0.0",
|
|
);
|
|
expect(
|
|
await file(join(packageDir, "node_modules", "uses-what-bin", "node_modules", "what-bin", "what-bin.js")).text(),
|
|
).toContain("what-bin@1.5.0");
|
|
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
out = await stdout.text();
|
|
err = await stderr.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."),
|
|
"",
|
|
expect.stringContaining("+ uses-what-bin@1.5.0"),
|
|
expect.stringContaining("+ what-bin@1.0.0"),
|
|
"",
|
|
"3 packages installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
const secondLockfile = await (
|
|
await file(join(packageDir, "bun.lock")).text()
|
|
).replaceAll(/localhost:\d+/g, "localhost:1234");
|
|
expect(firstLockfile).toEqual(secondLockfile);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
});
|
|
|
|
test("node-gyp should always be available for lifecycle scripts", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
install: "node-gyp --version",
|
|
},
|
|
}),
|
|
);
|
|
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
expect(err).not.toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
const out = await stdout.text();
|
|
|
|
// if node-gyp isn't available, it would return a non-zero exit code
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
});
|
|
|
|
// if this test fails, `electron` might be removed from the default list
|
|
test("default trusted dependencies should work", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.2.3",
|
|
dependencies: {
|
|
"electron": "1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
const out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ electron@1.0.0",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(out).not.toContain("Blocked");
|
|
expect(await exists(join(packageDir, "node_modules", "electron", "preinstall.txt"))).toBeTrue();
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
});
|
|
|
|
test("default trusted dependencies should not be used of trustedDependencies is populated", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.2.3",
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
// fake electron package because it's in the default trustedDependencies list
|
|
"electron": "1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
// electron lifecycle scripts should run, uses-what-bin scripts should not run
|
|
var err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
var out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]$/m, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ electron@1.0.0",
|
|
expect.stringContaining("+ uses-what-bin@1.0.0"),
|
|
"",
|
|
"3 packages installed",
|
|
"",
|
|
"Blocked 1 postinstall. Run `bun pm untrusted` for details.",
|
|
"",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules", "uses-what-bin", "what-bin.txt"))).toBeFalse();
|
|
expect(await exists(join(packageDir, "node_modules", "electron", "preinstall.txt"))).toBeTrue();
|
|
|
|
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
|
|
await rm(join(packageDir, ".bun-cache"), { recursive: true, force: true });
|
|
await rm(join(packageDir, "bun.lock"));
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.2.3",
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
"electron": "1.0.0",
|
|
},
|
|
trustedDependencies: ["uses-what-bin"],
|
|
}),
|
|
);
|
|
|
|
// now uses-what-bin scripts should run and electron scripts should not run.
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]$/m, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ electron@1.0.0",
|
|
expect.stringContaining("+ uses-what-bin@1.0.0"),
|
|
"",
|
|
"3 packages installed",
|
|
"",
|
|
"Blocked 1 postinstall. Run `bun pm untrusted` for details.",
|
|
"",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules", "uses-what-bin", "what-bin.txt"))).toBeTrue();
|
|
expect(await exists(join(packageDir, "node_modules", "electron", "preinstall.txt"))).toBeFalse();
|
|
});
|
|
|
|
test("does not run any scripts if trustedDependencies is an empty list", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.2.3",
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
"electron": "1.0.0",
|
|
},
|
|
trustedDependencies: [],
|
|
}),
|
|
);
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
const 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\]$/m, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ electron@1.0.0",
|
|
expect.stringContaining("+ uses-what-bin@1.0.0"),
|
|
"",
|
|
"3 packages installed",
|
|
"",
|
|
"Blocked 2 postinstalls. Run `bun pm untrusted` for details.",
|
|
"",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules", "uses-what-bin", "what-bin.txt"))).toBeFalse();
|
|
expect(await exists(join(packageDir, "node_modules", "electron", "preinstall.txt"))).toBeFalse();
|
|
});
|
|
|
|
test("default trusted dependencies should only apply to npm packages, not file: dependencies", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
// Create a file: dependency named "esbuild" (which is in the default trusted dependencies list)
|
|
// with a postinstall script that would fail if it ran
|
|
const esbuildPath = join(packageDir, "local-esbuild");
|
|
await mkdir(esbuildPath, { recursive: true });
|
|
await writeFile(
|
|
join(esbuildPath, "package.json"),
|
|
JSON.stringify({
|
|
name: "esbuild",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
postinstall: "exit 1",
|
|
},
|
|
}),
|
|
);
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
// file: dependency named "esbuild" - should NOT use default trusted list
|
|
esbuild: "file:./local-esbuild",
|
|
},
|
|
}),
|
|
);
|
|
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
const out = await stdout.text();
|
|
|
|
// The install should succeed because the postinstall script should NOT run
|
|
// (file: dependencies don't use default trusted list, even if name matches)
|
|
// The postinstall is blocked (not trusted), so we expect the "Blocked" message
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]$/m, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ esbuild@local-esbuild",
|
|
"",
|
|
"1 package installed",
|
|
"",
|
|
"Blocked 1 postinstall. Run `bun pm untrusted` for details.",
|
|
"",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
});
|
|
|
|
test("file: dependency with default trusted name should run scripts when explicitly added to trustedDependencies", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
// Create a file: dependency named "esbuild" with a postinstall script that creates a marker file
|
|
const esbuildPath = join(packageDir, "local-esbuild");
|
|
await mkdir(esbuildPath, { recursive: true });
|
|
await writeFile(
|
|
join(esbuildPath, "package.json"),
|
|
JSON.stringify({
|
|
name: "esbuild",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
postinstall: `${bunExe()} -e "require('fs').writeFileSync('postinstall-ran.txt', 'ran')"`,
|
|
},
|
|
}),
|
|
);
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
esbuild: "file:./local-esbuild",
|
|
},
|
|
// Explicitly trust the file: dependency
|
|
trustedDependencies: ["esbuild"],
|
|
}),
|
|
);
|
|
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
const 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\]$/m, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ esbuild@local-esbuild",
|
|
"",
|
|
"1 package installed",
|
|
"",
|
|
]);
|
|
expect(out).not.toContain("Blocked");
|
|
expect(await exited).toBe(0);
|
|
|
|
// The postinstall script should have run because we explicitly trusted it
|
|
expect(await exists(join(packageDir, "node_modules", "esbuild", "postinstall-ran.txt"))).toBeTrue();
|
|
});
|
|
|
|
test("will run default trustedDependencies after install that didn't include them", async () => {
|
|
await verdaccio.writeBunfig(packageDir, { saveTextLockfile: false, linker: "hoisted" });
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.2.3",
|
|
dependencies: {
|
|
electron: "1.0.0",
|
|
},
|
|
trustedDependencies: ["blah"],
|
|
}),
|
|
);
|
|
|
|
// first install does not run electron scripts
|
|
|
|
var { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
var err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
var out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]$/m, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ electron@1.0.0",
|
|
"",
|
|
"1 package installed",
|
|
"",
|
|
"Blocked 1 postinstall. Run `bun pm untrusted` for details.",
|
|
"",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules", "electron", "preinstall.txt"))).toBeFalse();
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.2.3",
|
|
dependencies: {
|
|
electron: "1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
// The electron scripts should run now because it's in default trusted dependencies.
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
out = await stdout.text();
|
|
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 exists(join(packageDir, "node_modules", "electron", "preinstall.txt"))).toBeTrue();
|
|
});
|
|
|
|
describe("--trust", async () => {
|
|
test("unhoisted untrusted scripts, none at root node_modules", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await Promise.all([
|
|
write(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
dependencies: {
|
|
// prevents real `uses-what-bin` from hoisting to root
|
|
"uses-what-bin": "npm:a-dep@1.0.3",
|
|
},
|
|
workspaces: ["pkg1"],
|
|
}),
|
|
),
|
|
write(
|
|
join(packageDir, "pkg1", "package.json"),
|
|
JSON.stringify({
|
|
name: "pkg1",
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
}),
|
|
),
|
|
]);
|
|
|
|
await runBunInstall(testEnv, packageDir);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
const results = await Promise.all([
|
|
exists(join(packageDir, "node_modules", "pkg1", "node_modules", "uses-what-bin")),
|
|
exists(join(packageDir, "node_modules", "pkg1", "node_modules", "uses-what-bin", "what-bin.txt")),
|
|
]);
|
|
|
|
expect(results).toEqual([true, false]);
|
|
|
|
const { stderr, exited } = spawn({
|
|
cmd: [bunExe(), "pm", "trust", "--all"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = await stderr.text();
|
|
expect(err).not.toContain("error:");
|
|
|
|
expect(await exited).toBe(0);
|
|
|
|
expect(
|
|
await exists(join(packageDir, "node_modules", "pkg1", "node_modules", "uses-what-bin", "what-bin.txt")),
|
|
).toBeTrue();
|
|
});
|
|
const trustTests = [
|
|
{
|
|
label: "only name",
|
|
packageJson: {
|
|
name: "foo",
|
|
},
|
|
},
|
|
{
|
|
label: "empty dependencies",
|
|
packageJson: {
|
|
name: "foo",
|
|
dependencies: {},
|
|
},
|
|
},
|
|
{
|
|
label: "populated dependencies",
|
|
packageJson: {
|
|
name: "foo",
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
label: "empty trustedDependencies",
|
|
packageJson: {
|
|
name: "foo",
|
|
trustedDependencies: [],
|
|
},
|
|
},
|
|
|
|
{
|
|
label: "populated dependencies, empty trustedDependencies",
|
|
packageJson: {
|
|
name: "foo",
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
trustedDependencies: [],
|
|
},
|
|
},
|
|
|
|
{
|
|
label: "populated dependencies and trustedDependencies",
|
|
packageJson: {
|
|
name: "foo",
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
trustedDependencies: ["uses-what-bin"],
|
|
},
|
|
},
|
|
|
|
{
|
|
label: "empty dependencies and trustedDependencies",
|
|
packageJson: {
|
|
name: "foo",
|
|
dependencies: {},
|
|
trustedDependencies: [],
|
|
},
|
|
},
|
|
];
|
|
for (const { label, packageJson } of trustTests) {
|
|
test(label, async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(join(packageDir, "package.json"), JSON.stringify(packageJson));
|
|
|
|
let { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "i", "--trust", "uses-what-bin@1.0.0"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
stdin: "ignore",
|
|
env: testEnv,
|
|
});
|
|
|
|
let err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
let out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun add v1."),
|
|
"",
|
|
"installed uses-what-bin@1.0.0",
|
|
"",
|
|
"2 packages installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
expect(await exists(join(packageDir, "node_modules", "uses-what-bin", "what-bin.txt"))).toBeTrue();
|
|
expect(await file(join(packageDir, "package.json")).json()).toEqual({
|
|
name: "foo",
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
trustedDependencies: ["uses-what-bin"],
|
|
});
|
|
|
|
// another install should not error with json SyntaxError
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "i"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
stdin: "ignore",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = stderrForInstall(await stderr.text());
|
|
expect(err).not.toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
out = await stdout.text();
|
|
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);
|
|
});
|
|
}
|
|
describe("packages without lifecycle scripts", async () => {
|
|
test("initial install", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
}),
|
|
);
|
|
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "i", "--trust", "no-deps@1.0.0"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
stdin: "ignore",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
const 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@1.0.0",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
expect(await exists(join(packageDir, "node_modules", "no-deps"))).toBeTrue();
|
|
expect(await file(packageJson).json()).toEqual({
|
|
name: "foo",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
},
|
|
});
|
|
});
|
|
test("already installed", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
}),
|
|
);
|
|
let { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "i", "no-deps"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
stdin: "ignore",
|
|
env: testEnv,
|
|
});
|
|
|
|
let err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
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",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
expect(await exists(join(packageDir, "node_modules", "no-deps"))).toBeTrue();
|
|
expect(await file(packageJson).json()).toEqual({
|
|
name: "foo",
|
|
dependencies: {
|
|
"no-deps": "^2.0.0",
|
|
},
|
|
});
|
|
|
|
// oops, I wanted to run the lifecycle scripts for no-deps, I'll install
|
|
// again with --trust.
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "i", "--trust", "no-deps"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
stdin: "ignore",
|
|
env: testEnv,
|
|
}));
|
|
|
|
// oh, I didn't realize no-deps doesn't have
|
|
// any lifecycle scripts. It shouldn't automatically add to
|
|
// trustedDependencies.
|
|
|
|
err = await stderr.text();
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
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",
|
|
"",
|
|
expect.stringContaining("done"),
|
|
"",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
expect(await exists(join(packageDir, "node_modules", "no-deps"))).toBeTrue();
|
|
expect(await file(packageJson).json()).toEqual({
|
|
name: "foo",
|
|
dependencies: {
|
|
"no-deps": "^2.0.0",
|
|
},
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("updating trustedDependencies", async () => {
|
|
test("existing trustedDependencies, unchanged trustedDependencies", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
trustedDependencies: ["uses-what-bin"],
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
let { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "i"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
stdin: "ignore",
|
|
env: testEnv,
|
|
});
|
|
|
|
let err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
let out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
expect.stringContaining("+ uses-what-bin@1.0.0"),
|
|
"",
|
|
"2 packages installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules", "uses-what-bin", "what-bin.txt"))).toBeTrue();
|
|
expect(await file(packageJson).json()).toEqual({
|
|
name: "foo",
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
trustedDependencies: ["uses-what-bin"],
|
|
});
|
|
|
|
// no changes, lockfile shouldn't be saved
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "i"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
stdin: "ignore",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = stderrForInstall(await stderr.text());
|
|
expect(err).not.toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
out = await stdout.text();
|
|
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());
|
|
});
|
|
|
|
test("existing trustedDependencies, removing trustedDependencies", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
trustedDependencies: ["uses-what-bin"],
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
let { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "i"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
stdin: "ignore",
|
|
env: testEnv,
|
|
});
|
|
|
|
let err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
let out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
expect.stringContaining("+ uses-what-bin@1.0.0"),
|
|
"",
|
|
"2 packages installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules", "uses-what-bin", "what-bin.txt"))).toBeTrue();
|
|
expect(await file(packageJson).json()).toEqual({
|
|
name: "foo",
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
trustedDependencies: ["uses-what-bin"],
|
|
});
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
// this script should not run because uses-what-bin is no longer in trustedDependencies
|
|
await rm(join(packageDir, "node_modules", "uses-what-bin", "what-bin.txt"), { force: true });
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "i"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
stdin: "ignore",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
out = await stdout.text();
|
|
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());
|
|
|
|
expect(await file(packageJson).json()).toEqual({
|
|
name: "foo",
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
});
|
|
expect(await exists(join(packageDir, "node_modules", "uses-what-bin", "what-bin.txt"))).toBeFalse();
|
|
});
|
|
|
|
test("non-existent trustedDependencies, then adding it", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
dependencies: {
|
|
"electron": "1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
let { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "i"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
stdin: "ignore",
|
|
env: testEnv,
|
|
});
|
|
|
|
let err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
let out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
"+ electron@1.0.0",
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules", "electron", "preinstall.txt"))).toBeTrue();
|
|
expect(await file(packageJson).json()).toEqual({
|
|
name: "foo",
|
|
dependencies: {
|
|
"electron": "1.0.0",
|
|
},
|
|
});
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
trustedDependencies: ["electron"],
|
|
dependencies: {
|
|
"electron": "1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
await rm(join(packageDir, "node_modules", "electron", "preinstall.txt"), { force: true });
|
|
|
|
// lockfile should save evenn though there are no changes to trustedDependencies due to
|
|
// the default list
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "i"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
stdin: "ignore",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
out = await stdout.text();
|
|
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 exists(join(packageDir, "node_modules", "electron", "preinstall.txt"))).toBeTrue();
|
|
});
|
|
});
|
|
|
|
test("node -p should work in postinstall scripts", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
postinstall: `node -p "require('fs').writeFileSync('postinstall.txt', 'postinstall')"`,
|
|
},
|
|
}),
|
|
);
|
|
|
|
const originalPath = env.PATH;
|
|
env.PATH = "";
|
|
|
|
let { stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stdin: "ignore",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
env.PATH = originalPath;
|
|
|
|
let err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("No packages! Deleted empty lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "postinstall.txt"))).toBeTrue();
|
|
});
|
|
|
|
test("ensureTempNodeGypScript works", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
preinstall: "node-gyp --version",
|
|
},
|
|
}),
|
|
);
|
|
|
|
const originalPath = env.PATH;
|
|
env.PATH = "";
|
|
|
|
let { stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
stdin: "ignore",
|
|
env,
|
|
});
|
|
|
|
env.PATH = originalPath;
|
|
|
|
let err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("No packages! Deleted empty lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
});
|
|
|
|
test("bun pm trust and untrusted on missing package", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
dependencies: {
|
|
"uses-what-bin": "1.5.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
let { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "i"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
let err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
let out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]$/m, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
expect.stringContaining("+ uses-what-bin@1.5.0"),
|
|
"",
|
|
"2 packages installed",
|
|
"",
|
|
"Blocked 1 postinstall. Run `bun pm untrusted` for details.",
|
|
"",
|
|
]);
|
|
expect(await exists(join(packageDir, "node_modules", "uses-what-bin", "what-bin.txt"))).toBeFalse();
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
// remove uses-what-bin from node_modules, bun pm trust and untrusted should handle missing package
|
|
await rm(join(packageDir, "node_modules", "uses-what-bin"), { recursive: true, force: true });
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "pm", "untrusted"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("bun pm untrusted");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
out = await stdout.text();
|
|
expect(out).toContain("Found 0 untrusted dependencies with scripts");
|
|
expect(await exited).toBe(0);
|
|
|
|
({ stderr, exited } = spawn({
|
|
cmd: [bunExe(), "pm", "trust", "uses-what-bin"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
expect(await exited).toBe(1);
|
|
|
|
err = await stderr.text();
|
|
expect(err).toContain("bun pm trust");
|
|
expect(err).toContain("0 scripts ran");
|
|
expect(err).toContain("uses-wha");
|
|
});
|
|
|
|
describe("add trusted, delete, then add again", async () => {
|
|
// when we change bun install to delete dependencies from node_modules
|
|
// for both cases, we need to update this test
|
|
for (const withRm of [true, false]) {
|
|
test(withRm ? "withRm" : "withoutRm", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await verdaccio.writeBunfig(packageDir, { saveTextLockfile: false, linker: "hoisted" });
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
let { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
let err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
let out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]$/m, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"",
|
|
expect.stringContaining("+ no-deps@1.0.0"),
|
|
expect.stringContaining("+ uses-what-bin@1.0.0"),
|
|
"",
|
|
"3 packages installed",
|
|
"",
|
|
"Blocked 1 postinstall. Run `bun pm untrusted` for details.",
|
|
"",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules", "uses-what-bin", "what-bin.txt"))).toBeFalse();
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "pm", "trust", "uses-what-bin"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = stderrForInstall(await stderr.text());
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
out = await stdout.text();
|
|
expect(out).toContain("1 script ran across 1 package");
|
|
expect(await exited).toBe(0);
|
|
|
|
expect(await exists(join(packageDir, "node_modules", "uses-what-bin", "what-bin.txt"))).toBeTrue();
|
|
expect(await file(packageJson).json()).toEqual({
|
|
name: "foo",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
trustedDependencies: ["uses-what-bin"],
|
|
});
|
|
|
|
// now remove and install again
|
|
if (withRm) {
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "rm", "uses-what-bin"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
out = await stdout.text();
|
|
expect(out).toContain("1 package removed");
|
|
expect(out).toContain("uses-what-bin");
|
|
expect(await exited).toBe(0);
|
|
}
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
out = await stdout.text();
|
|
let expected = withRm
|
|
? ["", "Checked 1 install across 2 packages (no changes)"]
|
|
: ["", expect.stringContaining("1 package removed")];
|
|
expected = [expect.stringContaining("bun install v1."), ...expected];
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual(expected);
|
|
expect(await exited).toBe(0);
|
|
expect(await exists(join(packageDir, "node_modules", "uses-what-bin"))).toBe(!withRm);
|
|
|
|
// add again, bun pm untrusted should report it as untrusted
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "i"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = stderrForInstall(await stderr.text());
|
|
expect(err).toContain("Saved lockfile");
|
|
expect(err).not.toContain("not found");
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
out = await stdout.text();
|
|
expected = withRm
|
|
? [
|
|
"",
|
|
expect.stringContaining("+ uses-what-bin@1.0.0"),
|
|
"",
|
|
"1 package installed",
|
|
"",
|
|
"Blocked 1 postinstall. Run `bun pm untrusted` for details.",
|
|
"",
|
|
]
|
|
: ["", expect.stringContaining("Checked 3 installs across 4 packages (no changes)"), ""];
|
|
expected = [expect.stringContaining("bun install v1."), ...expected];
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]$/m, "").split(/\r?\n/)).toEqual(expected);
|
|
|
|
({ stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "pm", "untrusted"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
}));
|
|
|
|
err = stderrForInstall(await stderr.text());
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
out = await stdout.text();
|
|
expect(out).toContain("./node_modules/uses-what-bin @1.0.0".replaceAll("/", sep));
|
|
expect(await exited).toBe(0);
|
|
});
|
|
}
|
|
});
|
|
|
|
describe.if(!forceWaiterThread || process.platform === "linux")("does not use 100% cpu", async () => {
|
|
test("install", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
scripts: {
|
|
preinstall: `${bunExe()} -e 'Bun.sleepSync(1000)'`,
|
|
},
|
|
}),
|
|
);
|
|
|
|
const proc = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "ignore",
|
|
stdin: "ignore",
|
|
env: testEnv,
|
|
});
|
|
|
|
expect(await proc.exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(proc.resourceUsage()?.cpuTime.total).toBeLessThan(750_000);
|
|
});
|
|
|
|
// https://github.com/oven-sh/bun/issues/11252
|
|
test.todoIf(isWindows)("bun pm trust", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
const dep = isWindows ? "uses-what-bin-slow-window" : "uses-what-bin-slow";
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.0.0",
|
|
dependencies: {
|
|
[dep]: "1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
var { exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "ignore",
|
|
env: testEnv,
|
|
});
|
|
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
|
|
expect(await exists(join(packageDir, "node_modules", dep, "what-bin.txt"))).toBeFalse();
|
|
|
|
const proc = spawn({
|
|
cmd: [bunExe(), "pm", "trust", "--all"],
|
|
cwd: packageDir,
|
|
stdout: "ignore",
|
|
stderr: "ignore",
|
|
env: testEnv,
|
|
});
|
|
|
|
expect(await proc.exited).toBe(0);
|
|
|
|
expect(await exists(join(packageDir, "node_modules", dep, "what-bin.txt"))).toBeTrue();
|
|
|
|
expect(proc.resourceUsage()?.cpuTime.total).toBeLessThan(750_000 * (isWindows ? 5 : 1));
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("stdout/stderr is inherited from root scripts during install", async () => {
|
|
test("without packages", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
const exe = bunExe().replace(/\\/g, "\\\\");
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.2.3",
|
|
scripts: {
|
|
"preinstall": `${exe} -e 'process.stderr.write("preinstall stderr 🍦\\n")'`,
|
|
"install": `${exe} -e 'process.stdout.write("install stdout 🚀\\n")'`,
|
|
"prepare": `${exe} -e 'Bun.sleepSync(200); process.stdout.write("prepare stdout done ✅\\n")'`,
|
|
},
|
|
}),
|
|
);
|
|
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = stderrForInstall(await stderr.text());
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
expect(splitErrLines(err)).toEqual([
|
|
"No packages! Deleted empty lockfile",
|
|
"",
|
|
`$ ${exe} -e 'process.stderr.write("preinstall stderr 🍦\\n")'`,
|
|
"preinstall stderr 🍦",
|
|
`$ ${exe} -e 'process.stdout.write("install stdout 🚀\\n")'`,
|
|
`$ ${exe} -e 'Bun.sleepSync(200); process.stdout.write("prepare stdout done ✅\\n")'`,
|
|
"",
|
|
]);
|
|
const out = await stdout.text();
|
|
expect(out.split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"install stdout 🚀",
|
|
"prepare stdout done ✅",
|
|
"",
|
|
expect.stringContaining("done"),
|
|
"",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
});
|
|
|
|
test("with a package", async () => {
|
|
const testEnv = forceWaiterThread ? { ...env, BUN_FEATURE_FLAG_FORCE_WAITER_THREAD: "1" } : env;
|
|
|
|
const exe = bunExe().replace(/\\/g, "\\\\");
|
|
await writeFile(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.2.3",
|
|
scripts: {
|
|
"preinstall": `${exe} -e 'process.stderr.write("preinstall stderr 🍦\\n")'`,
|
|
"install": `${exe} -e 'process.stdout.write("install stdout 🚀\\n")'`,
|
|
"prepare": `${exe} -e 'Bun.sleepSync(200); process.stdout.write("prepare stdout done ✅\\n")'`,
|
|
},
|
|
dependencies: {
|
|
"no-deps": "1.0.0",
|
|
},
|
|
}),
|
|
);
|
|
|
|
const { stdout, stderr, exited } = spawn({
|
|
cmd: [bunExe(), "install"],
|
|
cwd: packageDir,
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
env: testEnv,
|
|
});
|
|
|
|
const err = stderrForInstall(await stderr.text());
|
|
expect(err).not.toContain("error:");
|
|
expect(err).not.toContain("warn:");
|
|
expect(splitErrLines(err)).toEqual([
|
|
"Resolving dependencies",
|
|
expect.stringContaining("Resolved, downloaded and extracted "),
|
|
"Saved lockfile",
|
|
"",
|
|
`$ ${exe} -e 'process.stderr.write("preinstall stderr 🍦\\n")'`,
|
|
"preinstall stderr 🍦",
|
|
`$ ${exe} -e 'process.stdout.write("install stdout 🚀\\n")'`,
|
|
`$ ${exe} -e 'Bun.sleepSync(200); process.stdout.write("prepare stdout done ✅\\n")'`,
|
|
"",
|
|
]);
|
|
const out = await stdout.text();
|
|
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
|
|
expect.stringContaining("bun install v1."),
|
|
"install stdout 🚀",
|
|
"prepare stdout done ✅",
|
|
"",
|
|
expect.stringContaining("+ no-deps@1.0.0"),
|
|
"",
|
|
"1 package installed",
|
|
]);
|
|
expect(await exited).toBe(0);
|
|
assertManifestsPopulated(join(packageDir, ".bun-cache"), verdaccio.registryUrl());
|
|
});
|
|
});
|
|
}
|
|
|
|
test("ignore-scripts is read from npmrc", async () => {
|
|
await Promise.all([
|
|
write(
|
|
packageJson,
|
|
JSON.stringify({
|
|
name: "foo",
|
|
version: "1.2.3",
|
|
dependencies: {
|
|
"uses-what-bin": "1.0.0",
|
|
},
|
|
scripts: {
|
|
postinstall: `${bunExe()} -e 'await Bun.write("postinstall.txt", "postinstall!!")'`,
|
|
},
|
|
trustedDependencies: ["uses-what-bin"],
|
|
}),
|
|
),
|
|
write(join(packageDir, ".npmrc"), "ignore-scripts=true"),
|
|
]);
|
|
|
|
async function checkScripts(): Promise<boolean[]> {
|
|
return Promise.all([
|
|
exists(join(packageDir, "node_modules", "uses-what-bin", "what-bin.txt")),
|
|
exists(join(packageDir, "postinstall.txt")),
|
|
]);
|
|
}
|
|
|
|
await runBunInstall(env, packageDir);
|
|
expect(await checkScripts()).toEqual([false, false]);
|
|
|
|
await write(join(packageDir, ".npmrc"), "ignore-scripts=false");
|
|
|
|
await runBunInstall(env, packageDir, { savesLockfile: false });
|
|
expect(await checkScripts()).toEqual([false, true]);
|
|
|
|
await Promise.all([
|
|
rm(join(packageDir, "postinstall.txt")),
|
|
rm(join(packageDir, "node_modules"), { recursive: true, force: true }),
|
|
]);
|
|
expect(await checkScripts()).toEqual([false, false]);
|
|
|
|
await runBunInstall(env, packageDir, { savesLockfile: false });
|
|
expect(await checkScripts()).toEqual([true, true]);
|
|
});
|