install: migrate pnpm importer rows in the shape package.json parsing produces - #38791
install: migrate pnpm importer rows in the shape package.json parsing produces#38791robobun wants to merge 1 commit into
Conversation
… produces The pnpm-lock.yaml migration stored a root or workspace row differently from what parsing the same package.json yields: a `catalog:` row became the catalog's range, a row pnpm linked to a workspace member kept the parsed literal instead of the member's path, and workspace versions were read from the importer entry, which never carries one. The differ on the following install therefore saw every such importer as changed and re-resolved it, moving any range with a second in-lock version (vuejs/core: compiler-sfc's postcss ^8.5.19 went from the locked 8.5.23 to 8.5.25), and `bun pm migrate` followed by `bun install --frozen-lockfile` failed on any project using catalogs. Catalog rows now stay `catalog:` references (the entry's existence is still checked), importer edges bound to a member adopt the workspace shape the bun.lock reader already uses, and member versions come from the member's package.json under the same rule as the workspaces array parser.
|
Warning Review limit reached
Next review available in: 2 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (2)
📒 Files selected for processing (5)
Comment |
|
Status: ready for review and merge. Build 97041 finished with 177 of 179 jobs green; the two remaining jobs ( Reproduced on a vuejs/core checkout ( With this branch both flows on vuejs/core write byte-identical bun.lock files and the frozen install passes. Scope note: the macro is applied to the seven bindings in the root and workspace importer loops; the three plain assignments left in the loop over registry packages are not importer rows, and the differ never compares them. One neighbouring gap is left as is: a catalog entry that no importer uses is in |
There was a problem hiding this comment.
I reviewed this PR and didn't find any bugs. Because it changes how migrated importer rows are shaped (catalog literals kept, workspace edges rewritten, member versions sourced from package.json) in src/install/, a human look would still be worthwhile.
What was reviewed:
adopt_workspace_resolutionextraction is byte-for-byte with the oldmap_dep_to_pkgblock;bind_importer_dependency!is applied to all 8 importer-loop bindings and correctly skips the transitive-package loop atworkspace_pkgs_end...CatalogMap::findalready handles the""/"default"equivalence the removed normalization did, so the missing-entry check is unchanged.- Workspace version rule (
parsed.valid && wildcard == None) matches theworkspacesarray parser atPackage.rs:2865; the old branch readvalue.get("version")from the pnpm importer entry, which pnpm never writes. - Snapshot updates checked against their fixtures — every new
"version"row corresponds to a workspace package.json that declares one.
Extended reasoning...
Overview
This PR fixes pnpm lockfile migration so that migrated importer rows are shaped identically to what a fresh package.json parse produces, preventing the post-migration differ from re-resolving importers and moving locked versions. Three shapes are corrected: catalog: rows keep their literal instead of the resolved range; edges bound to workspace members adopt the workspace tag carrying the member path (via a shared adopt_workspace_resolution extracted from bun.lock.rs's map_dep_to_pkg); and workspace_versions is now populated from the member's own package.json instead of a field pnpm never writes. Files touched: src/install/pnpm.rs (main fix), src/install/lockfile/bun.lock.rs (helper extraction), four test/snapshot files, and one docs bullet.
Security risks
None identified. The change is confined to how in-memory lockfile rows are shaped during pnpm→bun migration. No new untrusted-input parsing paths, no allocation-size arithmetic, no filesystem-write surface change. The catalog check still fails migration on a missing entry.
Level of scrutiny
Moderate-to-high. src/install/ is production-critical: an incorrectly shaped row could cause frozen-lockfile false positives/negatives or silently move locked versions across a whole ecosystem of pnpm workspaces migrating to Bun. The change also alters user-visible bun.lock output (catalog rows now print catalog: instead of the resolved range; workspace members gain "version" rows). While the change is well-reasoned and well-tested, it is a behavioral change to a critical subsystem rather than a mechanical fix, so it should get a human sign-off.
Other factors
- The PR description is unusually thorough and the code matches it exactly. I traced each of the three shape mismatches to the corresponding fix and confirmed the reference implementations (
Package.rsversion-parsing rule,CatalogMap::finddefault equivalence,map_dep_to_pkgworkspace-adoption block) agree with the new code. - The remaining direct
resolutions[dep_id] = pkg_idassignments (lines ~1589/1612/1632) are in the transitive-package loop, not importer rows, so leaving them un-adopted is correct — the differ only compares importer rows. - Test coverage is strong: four new tests cover
catalog:,workspace:*, link-workspace-packages ranges, and an unparsable member version, each asserting both the migratedbun.lockshape and the actually-installed version (1.0.0 stays 1.0.0 while 1.0.1 is also in the lock). All snapshot updates are consistent with fixtures having the corresponding"version"field. - No prior reviews on this PR; only a CodeRabbit rate-limit notice.
|
Updated 9:05 PM PT - Aug 14th, 2026
❌ @robobun, your commit d049ee9 has some failures in 🧪 To try this PR locally: bunx bun-pr 38791That installs a local version of the PR into your bun-38791 --bun |
Problem
bun installon a pnpm workspace migratespnpm-lock.yamland then moves direct dependencies pnpm had locked to an older version onto the newest version already in the lockfile. vuejs/core:packages/compiler-sfcdeclarespostcss: ^8.5.19, pnpm-lock.yaml has it at 8.5.23 (vite brings 8.5.25); afterbun install, compiler-sfc gets 8.5.25 and 8.5.23 is gone.bun pm migrateby itself keeps 8.5.23, so the conversion is right and the install step after it moves things. Repros on b7a0431 too, so it predates install: pnpm parity — dedupe, prune, pm licenses, audit fix, add --filter/--catalog, nested overrides, transitive update, and workspace fixes #38333.bun pm migratefollowed bybun install --frozen-lockfilefails withlockfile had changes, but lockfile is frozenon any project whose package.json usescatalog:.src/install/pnpm.rs) builds root and workspace rows in a different shape than parsing the same package.json produces, so the differ (Diff::generate_inner,src/install/lockfile/Package.rs) counts them as updated and re-resolves the whole importer; a re-resolved range takes the highest in-lock version. Three shapes differ:append_importer_dependencyreplaced acatalog:row with the catalog entry's range (bun.lock got"@babel/parser": "^7.29.8"where package.json sayscatalog:); package.json parsing and the bun.lock reader keep thecatalog:reference.link:(aworkspace:*row, or a plain range under link-workspace-packages) kept its parsed literal value (*or an npm range), whilePackage::parse_dependencystores the member's path under theworkspacetag, which is whatDependency::eqlcompares.workspace_versionsstayed empty and migrated bun.lock files had no member"version"rows (the spurious re-resolve was what added them back).Fix
append_importer_dependencyonly checks that the catalog entry exists (same error as before) and appends the row from itscatalog:specifier like any other row; the importer'sversion:field binds it as before.bind_importer_dependency!, which sets the resolution and, when the target is a workspace package, gives the edge the workspace shape. That rewrite is the block the bun.lock reader already had inmap_dep_to_pkg, split out asbun_lock::adopt_workspace_resolutionand shared;map_dep_to_pkgis unchanged in behavior.workspacesarray parser's rule (parses, no wildcard); the old branch would have failed the migration on an unparsable version.bun pm migrateoutput looked right while the in-process install did not.test/cli/install/migration/pnpm-lock-v9.test.ts, newimporter rows match a package.json parseblock.pm migrate+--frozen-lockfileon a catalog project;bun installstraight from pnpm-lock.yaml writing the same bun.lock aspm migrateand installing the locked 1.0.0 while 1.0.1 is also in the lock, for acatalog:user, aworkspace:*user and a linked-range user; a member with an unparsable version gets no version row. All four fail on the unfixed build (the direct-install diff showsno-deps@1.0.0replaced by 1.0.1).pnpm-migration-complete.test.tsasserted the old"react": "18.2.0"row; updated tocatalog:with its snapshot, and two workspace snapshots gain"version"rows.bun pm migrateandbun install --lockfile-onlynow write byte-identical bun.lock files (postcss 8.5.23 at the root,vite/postcss8.5.25),--frozen-lockfileafterpm migratepasses, and the only differences from the previouspm migrateoutput are the catalog literals and 16 version rows.bun-lock,catalogs,bun-workspacesandmigratepass locally. The yarn and package-lock.json migrations build neithercatalog:nor linked rows, so they are not affected.Background
importerssection, one entry per workspace package plus.for the root, listing each declared dependency'sspecifier(what package.json says) andversion(what it resolved to;link:../xfor a sibling). The migration turns each importer into a lockfile package whose rows come from those specifiers.catalog:specifier names a range kept in the workspace catalog (pnpm-workspace.yaml, moved into package.jsonworkspaces.catalogby the migration). bun keeps such rows as references and looks the range up when resolving, so bun.lock printscatalog:and a catalog edit is detected as a change.workspace_versionsis the lockfile's member name to version map; the bun.lock writer prints it as each member's"version"row.