fix(install): update overrides/resolutions when running bun add

When `bun add pkg@version` is used to update a package that has an
existing entry in the `overrides` (npm) or `resolutions` (yarn)
section of package.json, the override/resolution entry was not being
updated. This caused the old version from the override to be installed
instead of the newly requested version.

This fix adds an `editOverrides` function that updates any matching
overrides/resolutions entries when a package is added or updated.
The function is called twice:
1. Before install: Uses the literal version from CLI to ensure the
   install uses the correct version
2. After install: Uses the resolved version from the lockfile to
   ensure the final package.json has resolved versions (e.g., when
   using dist-tags like "latest")

Fixes #25843

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2026-01-06 18:09:14 +00:00
parent 27ff6aaae0
commit 3108b5c19f
3 changed files with 134 additions and 0 deletions

View File

@@ -775,6 +775,57 @@ pub fn edit(
}
}
/// Updates the overrides (npm) or resolutions (yarn) section of package.json
/// when a dependency is being added/updated. This ensures overrides don't
/// conflict with the new dependency version.
/// When before_install is true, uses the literal version from the CLI.
/// When before_install is false, uses the resolved version from e_string (set by edit()).
pub fn editOverrides(
allocator: std.mem.Allocator,
updates: []const UpdateRequest,
current_package_json: *Expr,
before_install: bool,
) !void {
// Try both "overrides" (npm) and "resolutions" (yarn)
inline for ([_]string{ "overrides", "resolutions" }) |override_key| {
if (current_package_json.asProperty(override_key)) |query| {
if (query.expr.data == .e_object) {
for (query.expr.data.e_object.properties.slice()) |*prop| {
const key = prop.key orelse continue;
if (key.data != .e_string) continue;
const override_name = key.data.e_string.slice(allocator);
// Check if this override matches any of our update requests
for (updates) |request| {
const name = request.getName();
if (!strings.eqlLong(override_name, name, true)) continue;
// Found a matching override - update it with the new version
const value = prop.value orelse continue;
if (value.data != .e_string) continue;
// Get the version - either from literal (before install) or resolved (after)
const new_version = if (before_install)
request.version.literal.slice(request.version_buf)
else if (request.e_string) |e_string|
e_string.data
else
continue;
if (new_version.len == 0) continue;
// Update the override value
prop.value = JSAst.Expr.allocate(allocator, JSAst.E.String, .{
.data = try allocator.dupe(u8, new_version),
}, logger.Loc.Empty);
break;
}
}
}
}
}
}
const trusted_dependencies_string = "trustedDependencies";
const string = []const u8;

View File

@@ -196,6 +196,14 @@ fn updatePackageJSONAndInstallWithManagerWithUpdates(
.before_install = true,
},
);
// Also update any matching overrides/resolutions with the literal version
// This ensures the install uses the correct version
try PackageJSONEditor.editOverrides(
manager.allocator,
updates.*,
&current_package_json.root,
true, // before_install = use literal version
);
} else if (subcommand == .update) {
try PackageJSONEditor.editUpdateNoArgs(
manager,
@@ -375,6 +383,13 @@ fn updatePackageJSONAndInstallWithManagerWithUpdates(
.add_trusted_dependencies = manager.options.do.trust_dependencies_from_args,
},
);
// Update any matching overrides/resolutions with the resolved versions
try PackageJSONEditor.editOverrides(
manager.allocator,
updates.*,
&new_package_json,
false, // before_install = use resolved version from e_string
);
}
var buffer_writer_two = JSPrinter.BufferWriter.init(manager.allocator);
try buffer_writer_two.buffer.list.ensureTotalCapacity(manager.allocator, source.contents.len + 1);

View File

@@ -247,3 +247,71 @@ test("overrides do not apply to workspaces", async () => {
expect(await exited).toBe(0);
expect(await stderr.text()).not.toContain("Saved lockfile");
});
test("bun add updates overrides when adding new version of overridden package", async () => {
// Regression test for https://github.com/oven-sh/bun/issues/25843
// When running `bun add pkg@newversion`, if there's an override for that package,
// the override should also be updated to prevent conflicts.
const tmp = tmpdirSync();
writeFileSync(
join(tmp, "package.json"),
JSON.stringify({
dependencies: {
lodash: "4.0.0",
},
overrides: {
lodash: "4.0.0",
},
}),
);
// First install to set up the lockfile with the old version
install(tmp, ["install"]);
expect(versionOf(tmp, "node_modules/lodash/package.json")).toBe("4.0.0");
// Now use bun add to update to a new version
install(tmp, ["add", "lodash@4.17.21"]);
// Verify the new version is installed
expect(versionOf(tmp, "node_modules/lodash/package.json")).toBe("4.17.21");
// Verify the package.json was updated correctly (both dependencies and overrides)
const packageJson = JSON.parse(readFileSync(join(tmp, "package.json")).toString());
expect(packageJson.dependencies.lodash).toBe("4.17.21");
expect(packageJson.overrides.lodash).toBe("4.17.21");
ensureLockfileDoesntChangeOnBunI(tmp);
});
test("bun add updates resolutions when adding new version of package with resolution", async () => {
// Similar to overrides test but for yarn-style resolutions
const tmp = tmpdirSync();
writeFileSync(
join(tmp, "package.json"),
JSON.stringify({
dependencies: {
lodash: "4.0.0",
},
resolutions: {
lodash: "4.0.0",
},
}),
);
// First install to set up the lockfile with the old version
install(tmp, ["install"]);
expect(versionOf(tmp, "node_modules/lodash/package.json")).toBe("4.0.0");
// Now use bun add to update to a new version
install(tmp, ["add", "lodash@4.17.21"]);
// Verify the new version is installed
expect(versionOf(tmp, "node_modules/lodash/package.json")).toBe("4.17.21");
// Verify the package.json was updated correctly (both dependencies and resolutions)
const packageJson = JSON.parse(readFileSync(join(tmp, "package.json")).toString());
expect(packageJson.dependencies.lodash).toBe("4.17.21");
expect(packageJson.resolutions.lodash).toBe("4.17.21");
ensureLockfileDoesntChangeOnBunI(tmp);
});