Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
3062ef8471 fix(install): skip missing patch files for packages not in dependency graph
When `patchedDependencies` references a patch file that doesn't exist on
disk, `bun install` now only errors if the patched package is actually
part of the resolved dependency graph. This fixes pruned monorepo
contexts (e.g. `turbo prune --docker`) where the root `package.json`
may reference patches for packages only used by other workspaces.

Closes #26969

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-12 15:05:01 +00:00
3 changed files with 52 additions and 2 deletions

View File

@@ -780,10 +780,16 @@ pub fn cleanWithLogger(
{
var builder = new.stringBuilder();
for (old.patched_dependencies.values()) |patched_dep| builder.count(patched_dep.path.slice(old.buffers.string_bytes.items));
for (old.patched_dependencies.values()) |patched_dep| {
// Skip entries whose patch hash was never calculated — this means
// the patched package was not part of the resolved dependency graph
// (e.g. patchedDependencies in a pruned monorepo root).
if (patched_dep.patchfile_hash_is_null) continue;
builder.count(patched_dep.path.slice(old.buffers.string_bytes.items));
}
try builder.allocate();
for (old.patched_dependencies.keys(), old.patched_dependencies.values()) |k, v| {
bun.assert(!v.patchfile_hash_is_null);
if (v.patchfile_hash_is_null) continue;
var patchdep = v;
patchdep.path = builder.append(String, patchdep.path.slice(old.buffers.string_bytes.items));
try new.patched_dependencies.put(new.allocator, k, patchdep);

View File

@@ -147,6 +147,12 @@ pub const PatchTask = struct {
// need to switch on version.tag and handle each case appropriately
const calc_hash = &this.callback.calc_hash;
const hash = calc_hash.result orelse {
// For pre-calculation tasks, a null result means the patch file was not
// found on disk. This is not fatal — the patched package may not be in
// the current dependency graph (e.g. pruned monorepo / turbo prune).
// If the package IS later resolved, determinePreinstallState will
// trigger a new (non-pre) hash calculation that will properly error.
if (this.pre) return;
if (log_level != .silent) {
if (calc_hash.logger.hasErrors()) {
calc_hash.logger.print(Output.errorWriter()) catch {};
@@ -419,6 +425,13 @@ pub const PatchTask = struct {
const stat: bun.Stat = switch (bun.sys.stat(absolute_patchfile_path)) {
.err => |e| {
if (e.getErrno() == .NOENT) {
// If this is a pre-calculation task (no associated package in the
// dependency graph yet), the patch file being missing is not an error —
// the patched package may simply not be part of the current dependency
// graph (e.g. in a pruned monorepo). If the package IS later resolved,
// determinePreinstallState will trigger a non-pre hash calculation
// that will properly report the error.
if (this.pre) return null;
bun.handleOom(log.addErrorFmt(null, Loc.Empty, this.manager.allocator, "Couldn't find patch file: '{s}'\n\nTo create a new patch file run:\n\n <cyan>bun patch {s}<r>", .{
this.callback.calc_hash.patchfile_path,
this.manager.lockfile.patched_dependencies.get(this.callback.calc_hash.name_and_version_hash).?.path.slice(this.manager.lockfile.buffers.string_bytes.items),

View File

@@ -0,0 +1,31 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
// https://github.com/oven-sh/bun/issues/26969
// bun install should not fail when patchedDependencies references a patch file
// that doesn't exist, as long as the patched package is not in the dependency graph.
test("bun install succeeds when patchedDependencies patch file is missing but package is not in dependency graph", async () => {
using dir = tempDir("issue-26969", {
"package.json": JSON.stringify({
name: "repro-26969",
private: true,
patchedDependencies: {
"next-auth@5.0.0": "patches/next-auth@5.0.0.patch",
},
}),
});
await using proc = Bun.spawn({
cmd: [bunExe(), "install"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Should not error about missing patch file
expect(stderr).not.toContain("Couldn't find patch file");
expect(exitCode).toBe(0);
});