Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
70 changes: 35 additions & 35 deletions src/runtime/cli/pack_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,16 +197,42 @@ pub struct BundledDep {
// exec
// ───────────────────────────────────────────────────────────────────────────

pub(crate) fn warn_lockfile_unreadable(
cause: &bun_install::lockfile::LoadResultErr,
log: &mut bun_ast::Log,
log_level: LogLevel,
) {
use bun_install::lockfile::LoadStep;
if log_level != LogLevel::Silent {
let step = match cause.step {
LoadStep::OpenFile => "open",
LoadStep::ParseFile => "parse",
LoadStep::ReadFile => "read",
LoadStep::Migrating => "migrate",
};
bun_core::warn!(
"failed to {} {}: {}, continuing without it",
step,
bstr::BStr::new(cause.lockfile_path.as_bytes()),
cause.value.name(),
);
if log.has_errors() {
let _ = log.print(std::ptr::from_mut(Output::error_writer()));
}
Output::flush();
}
log.reset();
}

impl PackCommand {
pub fn exec_with_manager(
ctx: Command::Context<'_>,
manager: &mut PackageManager,
) -> crate::Result<()> {
use bun_install::lockfile::{LoadResult, LoadStep};

if manager.options.log_level != LogLevel::Silent
&& manager.options.log_level != LogLevel::Quiet
{
let log_level = manager.options.log_level;
if log_level != LogLevel::Silent && log_level != LogLevel::Quiet {
bun_core::prettyln!(
"<r><b>bun pack <r><d>v{}<r>",
Global::package_json_version_with_sha,
Expand All @@ -227,39 +253,13 @@ impl PackCommand {
let lockfile_ref: Option<&Lockfile> = match load_from_disk_result {
LoadResult::Ok(ok) => Some(&*ok.lockfile),
LoadResult::Err(cause) => 'err: {
match cause.step {
LoadStep::OpenFile => {
if cause.value == bun_install::Error::Sys(bun_errno::SystemErrno::ENOENT) {
break 'err None;
}
Output::err_generic(
"failed to open lockfile: {}",
format_args!("{}", cause.value.name()),
);
}
LoadStep::ParseFile => {
Output::err_generic(
"failed to parse lockfile: {}",
format_args!("{}", cause.value.name()),
);
}
LoadStep::ReadFile => {
Output::err_generic(
"failed to read lockfile: {}",
format_args!("{}", cause.value.name()),
);
}
LoadStep::Migrating => {
Output::err_generic(
"failed to migrate lockfile: {}",
format_args!("{}", cause.value.name()),
);
}
}
if pm_log(manager_ptr).has_errors() {
let _ = pm_log(manager_ptr).print(std::ptr::from_mut(Output::error_writer()));
if matches!(cause.step, LoadStep::OpenFile)
&& cause.value == bun_install::Error::Sys(bun_errno::SystemErrno::ENOENT)
{
break 'err None;
}
Global::crash();
warn_lockfile_unreadable(&cause, pm_log(manager_ptr), log_level);
None
}
LoadResult::NotFound => None,
};
Expand Down
32 changes: 7 additions & 25 deletions src/runtime/cli/publish_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ impl<'a, const DIRECTORY_PUBLISH: bool> Context<'a, DIRECTORY_PUBLISH> {
manager: &'a mut PackageManager,
) -> Result<Context<'static, true>, FromWorkspaceError> {
let mut lockfile = Lockfile::default();
let log_level = manager.options.log_level;
let manager_ptr: *mut PackageManager = manager;
let log: &mut bun_ast::Log = manager.log_mut();
// SAFETY: `manager_ptr` was just derived from `manager: &'a mut PackageManager`;
Expand All @@ -477,32 +478,13 @@ impl<'a, const DIRECTORY_PUBLISH: bool> Context<'a, DIRECTORY_PUBLISH> {
LoadResult::Ok(ok) => Some(&*ok.lockfile),
LoadResult::NotFound => None,
LoadResult::Err(cause) => 'err: {
match cause.step {
LoadStep::OpenFile => {
if cause.value == bun_install::Error::Sys(bun_errno::SystemErrno::ENOENT) {
break 'err None;
}
Output::err_generic("failed to open lockfile: {}", (cause.value.name(),));
}
LoadStep::ParseFile => {
Output::err_generic("failed to parse lockfile: {}", (cause.value.name(),));
}
LoadStep::ReadFile => {
Output::err_generic("failed to read lockfile: {}", (cause.value.name(),));
}
LoadStep::Migrating => {
Output::err_generic(
"failed to migrate lockfile: {}",
(cause.value.name(),),
);
}
}

if log.has_errors() {
let _ = log.print(std::ptr::from_mut(Output::error_writer()));
if matches!(cause.step, LoadStep::OpenFile)
&& cause.value == bun_install::Error::Sys(bun_errno::SystemErrno::ENOENT)
{
break 'err None;
}

Global::crash();
pack::warn_lockfile_unreadable(&cause, log, log_level);
None
}
};

Expand Down
132 changes: 132 additions & 0 deletions test/cli/install/bun-pack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,138 @@ test("basic", async () => {
expect(tarball.entries).toMatchObject([{ "pathname": "package/package.json" }, { "pathname": "package/index.js" }]);
});

describe("unreadable lockfile", () => {
const mergeConflict = `{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "pack-bad-lockfile",
<<<<<<< HEAD
"dependencies": {},
=======
"devDependencies": {},
>>>>>>> feature
},
},
"packages": {},
}
`;

for (const { label, file, contents, diagnostic } of [
{ label: "empty bun.lock", file: "bun.lock", contents: "", diagnostic: "Missing lockfile version" },
{
label: "bun.lock with git conflict markers",
file: "bun.lock",
contents: mergeConflict,
diagnostic: `Expected string but found "<<<<<<<"`,
},
{ label: "corrupt bun.lockb", file: "bun.lockb", contents: "not a lockfile" },
]) {
test(`packs with ${label}`, async () => {
await Promise.all([
write(join(packageDir, "package.json"), JSON.stringify({ name: "pack-bad-lockfile", version: "1.0.0" })),
write(join(packageDir, "index.js"), "module.exports = 1"),
write(join(packageDir, file), contents),
]);

const { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "pm", "pack"],
cwd: packageDir,
stdout: "pipe",
stderr: "pipe",
stdin: "ignore",
env: bunEnv,
});
const [out, err, exitCode] = await Promise.all([stdout.text(), stderr.text(), exited]);

expect(err).toContain("warn:");
expect(err).toContain(`failed to parse ${file}`);
expect(err).toContain("continuing without it");
if (diagnostic) expect(err).toContain(diagnostic);
expect(out).toContain("Total files: 2");
expect(exitCode).toBe(0);

const tarball = readTarball(join(packageDir, "pack-bad-lockfile-1.0.0.tgz"));
expect(tarball.entries).toMatchObject([
{ "pathname": "package/package.json" },
{ "pathname": "package/index.js" },
]);
});
}

test("--silent suppresses the warning", async () => {
await Promise.all([
write(join(packageDir, "package.json"), JSON.stringify({ name: "pack-bad-lockfile-silent", version: "1.0.0" })),
write(join(packageDir, "index.js"), "module.exports = 1"),
write(join(packageDir, "bun.lock"), ""),
]);

const { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "pm", "pack", "--silent"],
cwd: packageDir,
stdout: "pipe",
stderr: "pipe",
stdin: "ignore",
env: bunEnv,
});
const [out, err, exitCode] = await Promise.all([stdout.text(), stderr.text(), exited]);

expect(err).toBe("");
expect(out).toContain("pack-bad-lockfile-silent-1.0.0.tgz");
expect(exitCode).toBe(0);
expect(await exists(join(packageDir, "pack-bad-lockfile-silent-1.0.0.tgz"))).toBeTrue();
});

test("still errors when a workspace version must be resolved", async () => {
await Promise.all([
write(
join(packageDir, "package.json"),
JSON.stringify({
name: "pack-bad-lockfile-workspace",
version: "1.0.0",
workspaces: ["pkgs/*"],
dependencies: { pkg1: "workspace:*" },
}),
),
write(join(packageDir, "root.js"), "console.log('root')"),
write(join(packageDir, "pkgs", "pkg1", "package.json"), JSON.stringify({ name: "pkg1", version: "1.1.1" })),
write(join(packageDir, "bun.lock"), ""),
]);

const { err } = await packExpectError(packageDir, bunEnv);
expect(err).toContain("warn:");
expect(err).toContain("failed to parse bun.lock");
expect(err).toContain(
'error: Failed to resolve workspace version for "pkg1" in `dependencies`. Run `bun install` and try again.',
);
expect(await exists(join(packageDir, "pack-bad-lockfile-workspace-1.0.0.tgz"))).toBeFalse();
});

test("bun publish --dry-run packs with corrupt lockfile", async () => {
await Promise.all([
write(join(packageDir, "package.json"), JSON.stringify({ name: "publish-bad-lockfile", version: "1.0.0" })),
write(join(packageDir, "index.js"), "module.exports = 1"),
write(join(packageDir, "bun.lock"), ""),
]);

const { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "publish", "--dry-run"],
cwd: packageDir,
stdout: "pipe",
stderr: "pipe",
stdin: "ignore",
env: bunEnv,
});
const [out, err] = await Promise.all([stdout.text(), stderr.text(), exited]);

expect(err).toContain("warn:");
expect(err).toContain("failed to parse bun.lock");
expect(err).toContain("continuing without it");
// Packing completed (auth may still fail afterwards, but packing is the behavior under test).
expect(out).toContain("Total files: 2");
});
});

test("in subdirectory", async () => {
await Promise.all([
write(
Expand Down
Loading