mirror of
https://github.com/oven-sh/bun
synced 2026-02-14 12:51:54 +00:00
fix(install): handle lifecycle script spawn failures gracefully instead of crashing (#21054)
Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: jarred-sumner-bot <220441119+jarred-sumner-bot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
c5f64036a7
commit
7f29446d9b
@@ -413,7 +413,13 @@ pub const LifecycleScriptSubprocess = struct {
|
||||
Lockfile.Scripts.names[new_script_index],
|
||||
@errorName(err),
|
||||
});
|
||||
Global.exit(1);
|
||||
if (this.ctx) |this_ctx| {
|
||||
this_ctx.installer.store.entries.items(.step)[this_ctx.entry_id.get()].store(.done, .monotonic);
|
||||
this_ctx.installer.onTaskComplete(this_ctx.entry_id, .fail);
|
||||
}
|
||||
_ = this.manager.pending_lifecycle_script_tasks.fetchSub(1, .monotonic);
|
||||
this.deinit();
|
||||
return;
|
||||
};
|
||||
return;
|
||||
}
|
||||
@@ -571,6 +577,12 @@ pub const LifecycleScriptSubprocess = struct {
|
||||
Lockfile.Scripts.names[list.first_index],
|
||||
@errorName(err),
|
||||
});
|
||||
if (lifecycle_subprocess.ctx) |subprocess_ctx| {
|
||||
subprocess_ctx.installer.store.entries.items(.step)[subprocess_ctx.entry_id.get()].store(.done, .monotonic);
|
||||
subprocess_ctx.installer.onTaskComplete(subprocess_ctx.entry_id, .fail);
|
||||
}
|
||||
_ = manager.pending_lifecycle_script_tasks.fetchSub(1, .monotonic);
|
||||
lifecycle_subprocess.deinit();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
94
test/regression/issue/14945-lifecycle-script-crash.test.ts
Normal file
94
test/regression/issue/14945-lifecycle-script-crash.test.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
|
||||
|
||||
test("lifecycle script should handle directory deletion gracefully", async () => {
|
||||
const dir = tempDirWithFiles("lifecycle-crash-test", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "test-package",
|
||||
version: "1.0.0",
|
||||
scripts: {
|
||||
preinstall: process.platform === "win32" ? "rmdir /s /q ." : "rm -rf .",
|
||||
postinstall: "echo hello world",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// Run bun install and expect it to handle the directory deletion gracefully
|
||||
// without crashing with assertions
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
env: bunEnv,
|
||||
cwd: dir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
// The process should not crash with assertion failures
|
||||
// It may fail (non-zero exit code) but should fail gracefully
|
||||
// and not with internal assertion errors
|
||||
expect(stderr).not.toContain("assertion");
|
||||
expect(stderr).not.toContain("atIndex");
|
||||
expect(stderr).not.toContain("panic");
|
||||
|
||||
// Should contain an error message about the script failure
|
||||
expect(stderr.includes("script") && (stderr.includes("exited with") || stderr.includes("Failed to run script"))).toBe(
|
||||
true,
|
||||
);
|
||||
|
||||
// The process should exit with a non-zero code due to the script failure
|
||||
expect(exitCode).not.toBe(0);
|
||||
});
|
||||
|
||||
test("lifecycle script with optional dependency should handle directory deletion", async () => {
|
||||
const depDir = tempDirWithFiles("optional-dep", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "optional-dep",
|
||||
version: "1.0.0",
|
||||
scripts: {
|
||||
preinstall: process.platform === "win32" ? "rmdir /s /q ." : "rm -rf .",
|
||||
postinstall: "echo hello from optional dep",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const mainDir = tempDirWithFiles("main-package", {
|
||||
"package.json": JSON.stringify({
|
||||
name: "main-package",
|
||||
version: "1.0.0",
|
||||
optionalDependencies: {
|
||||
"optional-dep": `file:${depDir}`,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
// Run bun install and expect it to handle the optional dependency
|
||||
// directory deletion gracefully
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "install"],
|
||||
env: bunEnv,
|
||||
cwd: mainDir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([
|
||||
new Response(proc.stdout).text(),
|
||||
new Response(proc.stderr).text(),
|
||||
proc.exited,
|
||||
]);
|
||||
|
||||
// The process should not crash with assertion failures
|
||||
expect(stderr).not.toContain("assertion");
|
||||
expect(stderr).not.toContain("atIndex");
|
||||
expect(stderr).not.toContain("panic");
|
||||
|
||||
// For optional dependencies, the install should succeed even if scripts fail
|
||||
// The process may warn about deleting the optional dependency
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
Reference in New Issue
Block a user