Compare commits

...

2 Commits

Author SHA1 Message Date
Michael H
c493e3fc1e Merge branch 'main' into claude/fix-empty-trusted-dependencies-lockfile 2025-11-29 11:47:49 +11:00
Claude Bot
056afdd818 Fix empty trustedDependencies array not being written to bun.lock
When trustedDependencies is set to an empty array [] in package.json,
it should be preserved in bun.lock to distinguish it from the absence
of trustedDependencies (which uses the default list).

Previously, the text lockfile writer only wrote the trustedDependencies
field if it found matching packages in the dependency tree. With an
empty array, no packages match, so the field was omitted entirely.

This caused empty trustedDependencies [] to be treated the same as
missing trustedDependencies, falling back to the default trusted list
instead of blocking all lifecycle scripts as intended.

The fix checks if lockfile.trusted_dependencies exists (even if empty)
and writes the field accordingly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-01 10:50:53 +00:00
2 changed files with 137 additions and 10 deletions

View File

@@ -253,23 +253,27 @@ pub const Stringifier = struct {
TreeSortCtx.isLessThan,
);
if (found_trusted_dependencies.count() > 0) {
// Write trustedDependencies if it exists in lockfile, even if empty
if (lockfile.trusted_dependencies) |_| {
try writeIndent(writer, indent);
try writer.writeAll(
\\"trustedDependencies": [
\\
);
indent.* += 1;
var values_iter = found_trusted_dependencies.valueIterator();
while (values_iter.next()) |dep_name| {
try writeIndent(writer, indent);
try writer.print(
\\"{s}",
\\
, .{dep_name.slice(buf)});
if (found_trusted_dependencies.count() > 0) {
indent.* += 1;
var values_iter = found_trusted_dependencies.valueIterator();
while (values_iter.next()) |dep_name| {
try writeIndent(writer, indent);
try writer.print(
\\"{s}",
\\
, .{dep_name.slice(buf)});
}
try decIndent(writer, indent);
}
try decIndent(writer, indent);
try writer.writeAll(
\\],
\\

View File

@@ -0,0 +1,123 @@
import { spawn } from "bun";
import { expect, test } from "bun:test";
import { bunExe, bunEnv as env, tempDir } from "harness";
import { join } from "path";
test("empty trustedDependencies array should be preserved in bun.lock", async () => {
using packageDir = tempDir("trusted-deps-test-", {
"package.json": JSON.stringify({
name: "test-empty-trusted-deps",
version: "1.0.0",
trustedDependencies: [],
workspaces: ["packages/*"], // workspaces force lockfile creation
}),
"packages/a/package.json": JSON.stringify({
name: "a",
version: "1.0.0",
}),
});
// Run bun install with text lockfile
const { exited, stdout, stderr } = spawn({
cmd: [bunExe(), "install", "--save-text-lockfile"],
cwd: String(packageDir),
env,
stdout: "pipe",
stderr: "pipe",
});
const exitCode = await exited;
if (exitCode !== 0) {
console.log("stdout:", await stdout.text());
console.log("stderr:", await stderr.text());
}
expect(exitCode).toBe(0);
// Read the generated bun.lock file
const lockfilePath = join(String(packageDir), "bun.lock");
const lockfileContent = await Bun.file(lockfilePath).text();
// Verify that trustedDependencies field exists in the lockfile and is an empty array
expect(lockfileContent).toContain('"trustedDependencies":');
// Should be an empty array (might be formatted with newlines)
expect(lockfileContent).toMatch(/"trustedDependencies"\s*:\s*\[\s*\]/);
});
test("trustedDependencies missing vs empty should behave differently", async () => {
// Test 1: No trustedDependencies field (should use default list)
using packageDir1 = tempDir("trusted-deps-test-missing-", {
"package.json": JSON.stringify({
name: "test-default-trusted",
version: "1.0.0",
workspaces: ["packages/*"],
// No trustedDependencies field
}),
"packages/a/package.json": JSON.stringify({
name: "a",
version: "1.0.0",
}),
});
const {
exited: exited1,
stdout: stdout1,
stderr: stderr1,
} = spawn({
cmd: [bunExe(), "install", "--save-text-lockfile"],
cwd: String(packageDir1),
env,
stdout: "pipe",
stderr: "pipe",
});
const exitCode1 = await exited1;
if (exitCode1 !== 0) {
console.log("stdout:", await stdout1.text());
console.log("stderr:", await stderr1.text());
}
expect(exitCode1).toBe(0);
const lockfile1 = await Bun.file(join(String(packageDir1), "bun.lock")).text();
// Should NOT contain trustedDependencies field when it's not in package.json
expect(lockfile1).not.toContain('"trustedDependencies"');
// Test 2: Empty trustedDependencies field (should block all)
using packageDir2 = tempDir("trusted-deps-test-empty-", {
"package.json": JSON.stringify({
name: "test-empty-trusted",
version: "1.0.0",
workspaces: ["packages/*"],
trustedDependencies: [],
}),
"packages/a/package.json": JSON.stringify({
name: "a",
version: "1.0.0",
}),
});
const {
exited: exited2,
stdout: stdout2,
stderr: stderr2,
} = spawn({
cmd: [bunExe(), "install", "--save-text-lockfile"],
cwd: String(packageDir2),
env,
stdout: "pipe",
stderr: "pipe",
});
const exitCode2 = await exited2;
if (exitCode2 !== 0) {
console.log("stdout:", await stdout2.text());
console.log("stderr:", await stderr2.text());
}
expect(exitCode2).toBe(0);
const lockfile2 = await Bun.file(join(String(packageDir2), "bun.lock")).text();
// SHOULD contain trustedDependencies field as empty array
expect(lockfile2).toContain('"trustedDependencies":');
expect(lockfile2).toMatch(/"trustedDependencies"\s*:\s*\[\s*\]/);
});