Compare commits

...

3 Commits

Author SHA1 Message Date
Jarred-Sumner
d6531b9c6e bun run prettier 2025-06-30 05:37:58 +00:00
Jarred-Sumner
59cf7dc10e bun run zig-format 2025-06-30 05:36:41 +00:00
Cursor Agent
6e3f7539fc Support read-only package.json for publish and pack commands
Co-authored-by: jarred <jarred@bun.sh>
2025-06-30 05:18:46 +00:00
2 changed files with 144 additions and 4 deletions

View File

@@ -507,14 +507,18 @@ pub fn init(
var this_cwd: string = original_cwd;
var created_package_json = false;
const child_json = child: {
// if we are only doing `bun install` (no args), then we can open as read_only
// in all other cases we will need to write new data later.
// this is relevant because it allows us to succeed an install if package.json
// if we are only doing `bun install` (no args), `bun publish`, or `bun pack`,
// then we can open as read_only. In all other cases we will need to write new data later.
// this is relevant because it allows us to succeed an install/publish/pack if package.json
// is readable but not writable
//
// probably wont matter as if package.json isn't writable, it's likely that
// the underlying directory and node_modules isn't either.
const need_write = subcommand != .install or cli.positionals.len > 1;
const need_write = switch (subcommand) {
.install => cli.positionals.len > 1, // bun install with packages to add needs write
.publish, .pack => false, // bun publish and bun pack only need to read package.json
else => true, // all other commands need write access
};
while (true) {
var package_json_path_buf: bun.PathBuffer = undefined;

View File

@@ -0,0 +1,136 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
import { chmodSync } from "node:fs";
test("bun publish should work with read-only package.json", async () => {
const dir = tempDirWithFiles("publish-readonly-test", {
"package.json": JSON.stringify({
name: "test-package",
version: "1.0.0",
description: "Test package for read-only package.json",
main: "index.js",
private: true, // Make it private to prevent accidental publishing
}),
"index.js": `module.exports = { hello: "world" };`,
"README.md": "# Test Package\n\nThis is a test package.",
});
const packageJsonPath = `${dir}/package.json`;
// Make package.json read-only
chmodSync(packageJsonPath, 0o444); // read-only for owner, group, and others
try {
// Run bun publish with --dry-run to avoid actually publishing
await using proc = Bun.spawn({
cmd: [bunExe(), "publish", "--dry-run"],
env: bunEnv,
cwd: dir,
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
// The command should succeed (exit code 0) even with read-only package.json
expect(exitCode).toBe(0);
// Should not contain the error message about package.json needing to be writable
expect(stderr).not.toContain("package.json must be writable");
expect(stderr).not.toContain("Permission denied");
// Should show the dry-run output
expect(stdout).toContain("test-package@1.0.0");
expect(stdout).toContain("(dry-run)");
} finally {
// Restore write permissions for cleanup
chmodSync(packageJsonPath, 0o644);
}
});
test("bun pack should work with read-only package.json", async () => {
const dir = tempDirWithFiles("pack-readonly-test", {
"package.json": JSON.stringify({
name: "test-package-pack",
version: "1.0.0",
description: "Test package for read-only package.json with pack",
main: "index.js",
}),
"index.js": `module.exports = { hello: "world" };`,
"README.md": "# Test Package\n\nThis is a test package.",
});
const packageJsonPath = `${dir}/package.json`;
// Make package.json read-only
chmodSync(packageJsonPath, 0o444); // read-only for owner, group, and others
try {
// Run bun pack with --dry-run to avoid creating actual tarball
await using proc = Bun.spawn({
cmd: [bunExe(), "pm", "pack", "--dry-run"],
env: bunEnv,
cwd: dir,
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
// The command should succeed (exit code 0) even with read-only package.json
expect(exitCode).toBe(0);
// Should not contain the error message about package.json needing to be writable
expect(stderr).not.toContain("package.json must be writable");
expect(stderr).not.toContain("Permission denied");
// Should show the pack output
expect(stdout).toContain("test-package-pack-1.0.0.tgz");
} finally {
// Restore write permissions for cleanup
chmodSync(packageJsonPath, 0o644);
}
});
test("bun install with packages should still require writable package.json", async () => {
const dir = tempDirWithFiles("install-readonly-test", {
"package.json": JSON.stringify({
name: "test-package-install",
version: "1.0.0",
dependencies: {},
}),
});
const packageJsonPath = `${dir}/package.json`;
// Make package.json read-only
chmodSync(packageJsonPath, 0o444); // read-only for owner, group, and others
try {
// Run bun install with a package (which should require write access)
await using proc = Bun.spawn({
cmd: [bunExe(), "install", "lodash"],
env: bunEnv,
cwd: dir,
});
const [stdout, stderr, exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
// The command should fail because it needs to write to package.json
expect(exitCode).not.toBe(0);
// Should contain the error message about package.json needing to be writable
expect(stderr).toContain("package.json must be writable");
} finally {
// Restore write permissions for cleanup
chmodSync(packageJsonPath, 0o644);
}
});