-
Notifications
You must be signed in to change notification settings - Fork 5k
install: keep workspace and other non-registry entries as written on bun update <name> #38847
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
robobun
wants to merge
5
commits into
main
Choose a base branch
from
farm/b4f39c55/update-keeps-workspace-entries
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+73
−2
Open
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
95f3d76
install: keep workspace member entries as written on bun update <name>
robobun 1b4c23d
install: keep workspace: entries out of both named-update passes; bin…
robobun 7066cc3
install: shorten the comments on the workspace entry checks
robobun c826a98
install: keep every non-registry entry out of the named update passes
robobun 2776a09
test: drop the frozen reinstall from the byte-identical update cases
robobun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 The
Workspace if Update => continuearm relies on the before-install pass leaving the entry byte-identical, but for a plain range like"pkg1": "^1.0.0"+bun update pkg1@1.0.0the!is_npm()skip does not fire and the before-install pass rewrites to"1.0.0", so the^is dropped on disk — the registry path restores it viaupdating_packages[name].original_version_literal, this arm does not. On main the same input wroteworkspace:*(also not kept-as-written), so this is a lateral change on a narrow input rather than a regression; the test.each covers["^1.0.0", ["pkg1"]]and["workspace:^", ["pkg1@^1.0.0"]]but not the cross-product["^1.0.0", ["pkg1@1.0.0"]].Extended reasoning...
What the bug is
The new after-install arm
assumes the before-install pass left the entry untouched. That now holds for
workspace:/file:/catalog:literals via the widened!dependency::Tag::infer(e_string.data.slice()).is_npm()skip, but not for a plain npm range that links a member. With root"pkg1": "^1.0.0"(memberpkg1at1.0.0) andbun update pkg1@1.0.0:Tag::infer("^1.0.0") == Npm, sois_npm()is true and the!is_npm()skip does not fire. In the unresolved branch,request.version.tag == Npm(the positionalpkg1@1.0.0parses as an npm range), so theSome(existing) if request.version.tag != Npm && !explicit_dist_tagguard is bypassed andversion_literal = requested = "1.0.0".with_alias_of("^1.0.0", "1.0.0")returns"1.0.0"(split_npm_alias("^1.0.0")isNone).e_string.databecomes"1.0.0". Meanwhile the'add_packages_to_updateblock does captureupdating_packages["pkg1"].original_version_literal = "^1.0.0"(the tag check there is== Npm, which passes)."1.0.0"is satisfied by the member's version, so the row resolves to the workspace member.bind_update_requests(with the PR's newis_workspace()skip) binds the request to the root'sdependenciesrow, whose resolved package'sresolution.tag == Workspace.e_stringto the"1.0.0"node.Tag::infer("1.0.0").is_npm()is true, so the skip again does not fire.resolutions[package_id].tag == Workspaceandsubcommand == Update→ the new arm fires andcontinues, leaving"1.0.0"in place.Result:
"pkg1": "^1.0.0"→"pkg1": "1.0.0"in package.json and bun.lock. The declared^operator is dropped.Why the existing code does not prevent it
The
!is_npm()skip is the fix for the previous review round's finding (theworkspace:^+@<spec>case), and it works for every literal whoseTag::inferis notNpm/DistTag— but a plain range is exactlyNpm, so it falls through. TheSome(existing) if tag != Npm && !explicit_dist_tagguard keeps the existing literal only for a barebun update pkg1(which parses as an emptyDistTag), which is why the tested["^1.0.0", ["pkg1"]]case passes. An explicit@<npm-range>setsrequest.version.tag == Npmand takes the requested literal.Inconsistency with the registry path
For the equivalent registry input —
"no-deps": "^1.0.0"+bun update no-deps@1.0.1— theresolution::Tag::Npmarm consultsmanager.updating_packages.get(request.name)and callsupdated_version_literal(&entry.original_version_literal, resolved, …), producing"^1.0.1"(the existing test "bun update<name>@<version>-r keeps each workspace's operator" asserts exactly this). TheWorkspacearm never readsupdating_packages, sooriginal_version_literal = "^1.0.0"— which is populated — is ignored.Impact
"workspace:*"— also not kept as written, but at least always linking the member. With this PR the entry becomes an exact/narrower npm range, which stops matching the member the next time its version is bumped past it. So on this specific narrow input the outcome is arguably worse than main's, but it is not a regression of previously-correct behavior — main was differently broken here.workspace:), (b) an explicit@<npm-range>on that member's name, and (c) the member's version satisfying that range. The--latestand@latestvariants of the plain-range case rewrite to"latest"before install, which then fails (registry 404 or DependencyLoop per install: fix npm dependencies that share a workspace member's name #37248) and never reaches disk, so only@<npm-range>with a satisfied range is observable.@1.0.0, so writing"1.0.0"is defensible from their perspective — it is just inconsistent with how the same command treats registry packages.Test gap
The PR's
test.eachcovers["^1.0.0", ["pkg1"]](bare) and["workspace:^", ["pkg1@^1.0.0"]]/["workspace:^", ["pkg1@latest"]](explicit spec on aworkspace:literal), but not the cross-product["^1.0.0", ["pkg1@1.0.0"]]— the one variant where the plain-range literal meets an explicit@<spec>. Per REVIEW.md's "Cover the variant matrix, not just the repro", that row belongs in the table.How to fix
Have the
Workspace if Updatearm restoreupdating_packages[name].original_version_literalwhen present instead of a barecontinue, mirroring how theNpmarm uses that map:Alternatively, also skip the before-install rewrite when the entry's existing lockfile row resolves to a workspace — but that requires the lockfile in the before-install pass, which currently only sees
resolutions == &[].Step-by-step proof
package.json:{"name": "root", "workspaces": ["packages/*"], "dependencies": {"pkg1": "^1.0.0"}};packages/pkg1/package.json:{"name": "pkg1", "version": "1.0.0"}.bun install.bun update pkg1@1.0.0.edit(): scan setsrequest.e_string→ the"^1.0.0"node.Tag::infer("^1.0.0")=Npm(discriminant < 3),is_npm()= true → skip does not fire. Unresolved branch:existing = Some("^1.0.0"),requested = "1.0.0",request.version.tag == Npm,explicit_dist_tag = false→ guardtag != Npm && !explicit_dist_tagis false →version_literal = "1.0.0".with_alias_of("^1.0.0", "1.0.0")="1.0.0".e_string.data = "1.0.0"."pkg1": "1.0.0"matches memberpkg1@1.0.0→ linked as workspace.bind_update_requests: implicitWORKSPACErow skipped by the PR's newdep.behavior.is_workspace() → continue; thedependenciesrow matches →request.package_id= member;resolutions[member].tag == Workspace.edit():e_string→"1.0.0".Tag::infer("1.0.0").is_npm()= true → skip does not fire. Match onresolutions[package_id].tag:Workspaceandsubcommand == Update→continue.e_string.datastays"1.0.0".flushsees"1.0.0" != "^1.0.0"on disk → writes"pkg1": "1.0.0"to package.json and bun.lock.