Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/install/PackageManager/install_with_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1858,7 +1858,20 @@ fn root_package_json_source(
root_package_json_path.as_bytes(),
Default::default(),
) {
WorkspacePackageJsonCacheResult::Entry(entry) => return Ok(entry.source.clone()),
WorkspacePackageJsonCacheResult::Entry(entry) => {
// The parser turns an empty file into `{}`, which would delete the lockfile.
if entry.source.contents.is_empty() {
Output::err_generic(
"failed to parse '{}': file is empty",
(bstr::BStr::new(root_package_json_path.as_bytes()),),
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
bun_core::note!(
"Restore package.json, or write {{}} to it to start without dependencies"
);
Global::exit(1);
}
return Ok(entry.source.clone());
Comment thread
robobun marked this conversation as resolved.
}
WorkspacePackageJsonCacheResult::ReadErr(err) => ("read", err),
WorkspacePackageJsonCacheResult::ParseErr(err) => ("parse", err),
};
Expand Down
22 changes: 21 additions & 1 deletion test/cli/install/bun-install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5221,14 +5221,20 @@ describe.concurrent("bun-install", () => {
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stdout).toStartWith("bun install v1.");
return { stderr: normalizeBunSnapshot(stderr, String(dir)), exitCode };
return {
stderr: normalizeBunSnapshot(stderr, String(dir)),
exitCode,
lockfileKept: await exists(join(String(dir), "bun.lock")),
};
}

const unparseable = (packageJsonPath: string) => writeFile(packageJsonPath, "foo");
const unreadable = async (packageJsonPath: string) => {
await rm(packageJsonPath);
await mkdir(packageJsonPath);
};
// What a writer that died between truncating and writing leaves behind.
const empty = (packageJsonPath: string) => writeFile(packageJsonPath, "");

for (const [lockfile, withLockfile] of [
["with a bun.lock", true],
Expand All @@ -5253,6 +5259,20 @@ describe.concurrent("bun-install", () => {
expect(stderr).toBe("EISDIR: failed to read '<dir>/package.json'");
expect(exitCode).toBe(1);
});

// An empty file parses as `{}` elsewhere. For the root that would mean "no
// dependencies", and bun install would delete the lockfile.
it(`rejects an empty file and keeps the lockfile ${lockfile}`, async () => {
const result = await installWithBrokenRootPackageJson(withLockfile, empty);
expect(result).toEqual({
stderr: [
"error: failed to parse '<dir>/package.json': file is empty",
"note: Restore package.json, or write {} to it to start without dependencies",
].join("\n"),
exitCode: 1,
lockfileKept: withLockfile,
});
});
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Loading