diff --git a/src/install/PackageManager/PackageJSONEditor.zig b/src/install/PackageManager/PackageJSONEditor.zig index cf03e110d1..1838b64f1d 100644 --- a/src/install/PackageManager/PackageJSONEditor.zig +++ b/src/install/PackageManager/PackageJSONEditor.zig @@ -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; diff --git a/src/install/PackageManager/updatePackageJSONAndInstall.zig b/src/install/PackageManager/updatePackageJSONAndInstall.zig index 2e504d6815..efa713d2b0 100644 --- a/src/install/PackageManager/updatePackageJSONAndInstall.zig +++ b/src/install/PackageManager/updatePackageJSONAndInstall.zig @@ -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.*, + ¤t_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); diff --git a/test/cli/install/overrides.test.ts b/test/cli/install/overrides.test.ts index 9ea001d97e..ce86e69381 100644 --- a/test/cli/install/overrides.test.ts +++ b/test/cli/install/overrides.test.ts @@ -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); +});