Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
14 changes: 13 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,19 @@ 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 reads an empty file as `{}`. For the root that would
// install nothing and delete the lockfile, which is the wrong
// answer when the file was truncated by an interrupted write.
Comment thread
robobun marked this conversation as resolved.
Outdated
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
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
19 changes: 18 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,17 @@ 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",
exitCode: 1,
lockfileKept: withLockfile,
});
});
}
});

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