pack/publish: resolve workspace: and catalog: specs from the package.json files, not bun.lock - #38813
pack/publish: resolve workspace: and catalog: specs from the package.json files, not bun.lock#38813robobun wants to merge 8 commits into
Conversation
… files, not bun.lock `bun pm pack` and `bun publish` substituted `workspace:^|~|*` and `catalog:` dependency specs with the versions recorded in bun.lock, which only holds what the package.json files said at the last install. A release bumps versions (`bun pm version`, changesets) and edits catalogs after that install, so the tarball and the registry manifest carried the pre-bump versions while the package's own version was the new one. Pack no longer reads the lockfile at all. When the manifest being packed has a spec that needs substituting, it parses the root package.json and the workspaces it lists the same way `bun install` does (WorkspaceManifests, built on the existing ScratchManifests) and resolves from that. Packages without such specs never touch the other manifests. The package.json cache is cleared after lifecycle scripts so versions they write to other workspaces are seen too. Fixes #20477
|
Updated 2:38 PM PT - Aug 15th, 2026
✅ @robobun, your commit de34ed949c0e07c761e5ba1f013f1fd3c3a2e7ae passed in 🧪 To try this PR locally: bunx bun-pr 38813That installs a local version of the PR into your bun-38813 --bun |
|
Warning Review limit reached
Next review available in: 3 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 selected for processing (7)
Comment |
|
Reproduced with the repro in the description on the current release: after Tests: Commits: 0031b3e the fix; 1f5fdca the #36260 tests; 56dd781 shorter comments; 662c8ee errors the manifest parse only logs fail pack like they fail CI: builds 97108 (first commit) and 97783 (through 15e94df, full matrix including macOS) passed. Builds 98253 and 98576 (490b252 and an empty rerun) were red only on Related: #36279 and #36260 are closed in favor of this PR. #38835 ( |
There was a problem hiding this comment.
I reviewed this PR and the automated bug-hunting pass found no issues. Because it changes the data source bun pm pack/bun publish use to resolve workspace: and catalog: specs (lockfile → on-disk manifests), reworks edit_root_package_json, and changes user-facing error messages, a human sign-off on the behavior change would still be worthwhile.
Checked: Substitution::for_spec matches the old prefix/trim logic for both protocols; needs_workspace_manifests and edit_root_package_json share the same classifier so the .expect() on workspace_manifests cannot fire; the re-read of json after WorkspaceManifests::load correctly handles the hashbrown cache growing; map.clear() after lifecycle scripts is a strict superset of the old single-entry removal (and drops the Windows-only path-separator conversion). The removed lockfile field on pack::Context has no remaining consumers.
Extended reasoning...
Overview
This PR changes bun pm pack and bun publish to resolve workspace:^|~|* and catalog: specifiers from the workspace's package.json files on disk instead of bun.lock. It adds WorkspaceManifests (wrapping the existing ScratchManifests) in src/install/PackageManager/workspace_manifests.rs, removes lockfile loading from both pack_command.rs and publish_command.rs, restructures edit_root_package_json around a shared Substitution classifier, and broadens post-lifecycle-script cache invalidation from one entry to the whole WorkspacePackageJSONCache. ~250 lines of Rust changed plus ~200 lines of new test coverage across three test files.
Security risks
None identified. This is CLI-side manifest reading using the same Package::parse_with_json path bun install already uses; no new untrusted-input parsing surface. bun publish sends data to a registry, but the substituted values come from local package.json files the user controls, same as before (just fresher).
Level of scrutiny
High. bun publish is irreversible (packages hit a public registry), and this PR changes what version ranges get written into published manifests. It also swaps a data source (lockfile → live manifests), which is a design decision — it now fails hard if the workspace root's manifests don't parse where before it would fall back to whatever the lockfile had. The PR description argues this is correct (matches pnpm, avoids stale versions), but that's a maintainer call. User-visible error messages change too.
Other factors
- Test coverage is thorough: stale-lockfile case (the actual bug), no-lockfile case, packing the root, prepack scripts editing sibling workspaces, both error-message paths, and a mock-registry check on the actual PUT body
bun publishsends. An existing test asserting the old "Run `bun install`" error message was correctly updated. - The
crash()refactor inworkspace_manifests.rspreserves the existingrelation_graphbehavior (log-first, generic message as fallback). - The pointer-safety note about re-reading
jsonafterWorkspaceManifests::loadgrows the cache is correct —ScratchManifests::parse_rootcallsfetch_entry, which inserts into the sameworkspace_package_json_cachemap. - The PR mentions it subsumes #36279 and #36260; a maintainer should confirm those can be closed.
|
Nothing to change from the automated review. The two points it leaves to a maintainer are the ones called out in the description: (1) when a package uses |
0031b3e to
b21925d
Compare
b21925d to
1f5fdca
Compare
CatalogMap::parse_append_group logs "Invalid dependency version" and "Duplicate catalog" and returns Ok, so ScratchManifests::parse_root returned Ok with errors in its log. bun install fails on those; pack silently dropped them and went on to report the catalog entry as missing. parse_root and parse_member now fail when the parse logged errors, so pack (and the --filter relation graph) print them the way bun install does.
|
Two follow-up commits from the reviews: 56dd781 shortens the comments the comment lint flagged (the ones kept are one- or two-line contracts, replies on each thread say which), and 662c8ee addresses the finding on |
There was a problem hiding this comment.
Both earlier findings are addressed (662c8ee surfaces logged catalog-parse errors; 15e94df drains stdout in the new test), and this run found no further issues. This is a well-scoped fix with thorough coverage, but it changes what pack/publish read from and adds fail_on_logged_errors to the shared ScratchManifests (so --filter relation graphs now also fail on log-only errors) — a maintainer should confirm those trade-offs.
Reviewed: the Substitution/needs_workspace_manifests split matches edit_root_package_json's use of manifests(); the cache re-read after WorkspaceManifests::load covers the hashbrown-move hazard the PR describes; map.clear() after lifecycle scripts is safe (only pack's own entry was live); the removed lockfile-load in from_workspace leaves no other consumer of ctx.lockfile.
Extended reasoning...
Overview
The PR changes bun pm pack and bun publish to resolve workspace:^|~|* and catalog: specifiers from the on-disk package.json files (via a new WorkspaceManifests wrapper around the existing ScratchManifests) instead of from bun.lock. It removes the lockfile load from both exec_pack and Context::from_workspace, restructures edit_root_package_json around a shared Substitution classifier, clears the whole WorkspacePackageJSONCache after lifecycle scripts instead of one entry, and adds fail_on_logged_errors to ScratchManifests::parse_root/parse_member. ~200 lines of Rust across three files plus ~300 lines of new tests in three test files.
Security risks
None identified. The change reads package.json files that bun install already reads with the same parser (Package::parse_with_json). No new external input surface, no path handling changes, no network/registry changes beyond removing a lockfile read.
Level of scrutiny
Medium-high. This is package-manager correctness code on the publish path — a wrong substitution ships to the registry. The refactor is disciplined (the Substitution enum keeps needs_workspace_manifests and edit_root_package_json in lockstep, so the .expect() on workspace_manifests is sound), and the test matrix is broad (stale lockfile, no lockfile, unreadable lockfile × 3, packing the root, prepack editing a sibling, both error messages, publish manifest capture, invalid catalog range). But there are two intentional behavioral changes a maintainer should confirm: (1) when a package has workspace:/catalog: specs and the root manifests don't parse, pack now fails with bun install's error instead of packing from stale lockfile data; (2) fail_on_logged_errors now also applies to relation_graph (the --filter path), so a workspace with a log-only manifest error (invalid catalog range, duplicate catalog entry) will now fail bun add --filter '...' where it previously didn't.
Other factors
Both of my earlier findings were addressed in follow-up commits (662c8ee, 15e94df). The comment-cop bot flagged several multi-line comments; the author shortened most in 56dd781 and replied on each thread explaining which were kept and why. The PR supersedes two closed PRs (#36279, #36260) and two more (#38835, #38788) are stacked on it, so a maintainer should be aware of the sequencing. CI on the head commit was still running per the last robobun update. No human reviewer has looked at it yet.
|
No further changes from the latest automated pass. The one trade-off it asks a maintainer to confirm, |
WorkspaceManifests::load parsed the root with Features::main(), which also runs bun install's checks on the root's own dependency sections. A workspace:<range> there that no workspace satisfies made bun pm pack fail, for the root and for members alike, but only when some other spec made pack load the manifests. Only the workspaces walk and the catalogs are what pack substitutes from, so load() parses just those.
|
490b252 narrows what Tests added to the |
There was a problem hiding this comment.
I reviewed this and found no bugs. My two earlier findings (the dropped scratch.log on the Ok path, and the undrained stdout in the new catalogs test) are both addressed. Deferring to a maintainer for the behavior trade-offs the description already calls out — pack now failing on manifest parse errors when a workspace:/catalog: spec is present, and fail_on_logged_errors reaching the --filter relation graph — rather than for anything unresolved in the diff.
Also checked in this pass:
Substitution::for_specvs. the old inline logic —workspace:^|~|*,workspace:<range>,catalog:andcatalog:<name>(with whitespace) all classify the same as before;needs_workspace_manifestsandedit_root_package_jsonshare it so they cannot disagree.- 490b252's
Features { dependencies: false, peer_dependencies: false, ... }— confirmedworkspace_versionsandcatalogsare populated viais_main/workspaces, independent of the root's own dependency sections; the new "workspace: range in the root that no workspace satisfies" tests cover this. fail_on_logged_errorsreachingsync_lockfileviaparse_root— that path only runs after a successful resolve, which already fails on the same inputs, so no new failure surface there.
Extended reasoning...
Overview
Changes the data source bun pm pack / bun publish use to substitute workspace:^|~|* and catalog: specs: instead of loading bun.lock (stale after a version bump), pack() parses the root and workspace package.json files via a new WorkspaceManifests wrapper around the existing ScratchManifests. The lockfile load is removed from both exec_pack and Context::from_workspace; the Context.lockfile field is deleted. edit_root_package_json is refactored around a Substitution enum shared with a new needs_workspace_manifests gate so packages without such specs never load the manifests. ScratchManifests::parse_root/parse_member now return InstallFailed when the parse logged errors (662c8ee), and parse_root gained a Features parameter (490b252) so pack can skip the root's own dependency sections. ~270 lines of new tests across bun-pack.test.ts, bun-publish.test.ts, and catalogs.test.ts.
Security risks
None identified. No new untrusted-input parsing (reuses Package::parse_with_json), no network/credential handling changes, no path construction from user data. The removed lockfile load path had no security relevance either.
Level of scrutiny
High. This is a behavioral change to a production package-manager path (bun pm pack / bun publish) that decides what dependency ranges get published to registries. It also widens ScratchManifests' error surface, which is shared with --filter relation graphs and package_json_write_back::sync_lockfile. The change is well-scoped and thoroughly tested, but the two trade-offs the author flagged (pack failing on manifest parse errors; the --filter side effect) are product decisions a maintainer should confirm.
Other factors
- Both of my earlier findings were fixed (662c8ee, 15e94df) and covered by new tests.
- Test coverage is strong: stale lockfile, no lockfile, unreadable lockfile (empty / conflict markers / corrupt
.lockb), packing the root, prepack editing a sibling, unversioned/missing workspace, missingworkspacesentry, invalid catalog range, and the publish manifest received by a mock registry. All stated to fail on the release build and pass on the debug build. - The one open thread is a comment-cop lint on the 3-line comment above the
Featuresblock inload(); it explains whydependencies: falseis set and reads as design rationale rather than a workaround. - Supersedes #36279 and #36260; #38835 and #38788 are stacked on this branch.
|
Reviewed 490b252 (see the comment above it for the motivating case): agreed, and it is the right narrowing. |
There was a problem hiding this comment.
I reviewed this PR and the automated bug-hunting pass found no issues; the two earlier findings (dropped scratch.log on the Ok path, undrained stdout in the catalogs test) were addressed in 662c8ee and 15e94df. Given the scope — swapping pack/publish's data source from bun.lock to on-disk manifests, and the fail_on_logged_errors change to shared ScratchManifests that also affects --filter relation graphs and sync_lockfile — a maintainer should confirm the design trade-offs called out in the description before this lands.
What was reviewed:
Substitution::for_spec/needs_workspace_manifestsagree on which specs need the manifests, andWorkspaceRangeis correctly excluded so packages without^/~/*/catalog:specs still pack regardless of workspace state.- The re-read of
jsonafterWorkspaceManifests::load(hashbrown insert can move entries) and aftermap.clear()post-lifecycle-scripts. fail_on_logged_errorsinparse_root/parse_memberand its effect on the two otherScratchManifestscallers;sync_lockfileruns after resolution has already printed those errors, so no user-visible regression there.- The new tests cover stale lockfile, no lockfile, unreadable lockfile, packing the root, prepack editing a sibling, unsatisfied
workspace:<range>in the root, and a missingworkspacesentry.
Extended reasoning...
Overview
This PR changes bun pm pack and bun publish to resolve workspace:^|~|* and catalog: dependency specs from the package.json files on disk instead of bun.lock, fixing #20477 (stale versions published after a version bump). It touches src/runtime/cli/pack_command.rs (removes lockfile loading, adds Substitution classifier and needs_workspace_manifests, restructures edit_root_package_json), src/runtime/cli/publish_command.rs (removes lockfile loading), src/install/PackageManager/workspace_manifests.rs (new WorkspaceManifests type, fail_on_logged_errors on ScratchManifests, parse_root now takes Features), and src/install/PackageManager/package_json_write_back.rs (passes Features::main() to the changed signature). ~270 net new test lines across three test files.
Security risks
None identified. The change reads local package.json files the same way bun install already does (via the existing ScratchManifests / parse_with_json path). No new untrusted-input parsing, no path handling changes, no network or auth surface.
Level of scrutiny
High. bun pm pack and bun publish are production-critical — a bug here ships wrong dependency ranges to the registry, which is exactly the class of bug this PR fixes. The change also alters shared infrastructure: ScratchManifests::parse_root / parse_member now return InstallFailed when the parse logged errors, which changes behavior for the --filter relation graph and sync_lockfile in package_json_write_back.rs. The PR description and follow-up comments explicitly call out two design decisions for maintainer confirmation: (1) pack now fails with bun install's error when manifests don't parse rather than falling back to stale bun.lock data, and (2) the fail_on_logged_errors behavior propagating to --filter.
Other factors
The PR is thoroughly tested (workspace versions, catalogs, no lockfile, unreadable lockfile, packing the root, prepack editing a sibling, error messages, unsatisfied workspace:<range> in root, missing workspaces entry), and CI has passed on earlier commits with only an unrelated ASAN failure on the latest. My two earlier inline findings were addressed. Two follow-up PRs (#38835, #38788) are based on this branch, and it supersedes #36279 and #36260. The comment-cop lint threads are all resolved with stated rationales for the comments kept. Given the scope, the cross-cutting ScratchManifests behavior change, and the author's own note that maintainer confirmation is expected on the trade-offs, this warrants human review rather than auto-approval.
…rkspace-catalog-from-manifests
|
The ASAN red on builds 98253 and 98576 ( |
There was a problem hiding this comment.
I reviewed this PR and didn't find any bugs (my two earlier findings — the dropped scratch.log on the Ok path and the undrained stdout pipe — were addressed in 662c8ee and 15e94df). Because it changes the data source for workspace:/catalog: substitution in pack/publish, widens ScratchManifests error behavior to --filter and the add/remove write-back, and supersedes two other open PRs, a maintainer sign-off on the design trade-offs the author flagged would still be worthwhile.
What was reviewed:
Substitution::for_spec/needs_workspace_manifests/edit_root_package_json— checked that the classifier and the substitution agree on which specs need manifests, and thatWorkspaceRangestill packs the range as written.WorkspaceManifests::loadwith the narrowedFeatures— verified the root's own dependency rows are not validated, so an unsatisfiedworkspace:<range>in the root no longer blocks packing (pinned by the two new tests).- The
fail_on_logged_errorspropagation intosync_lockfileandrelation_graph— the description's claim thatbun installalready fails on the same inputs before those points holds for the catalog-parse errors I traced. - The cache re-read after
WorkspaceManifests::loadand after lifecycle scripts — the hashbrown-insert invalidation is real and both re-reads are placed correctly.
Extended reasoning...
Overview
This PR changes bun pm pack and bun publish to resolve workspace:^|~|* and catalog: dependency specs from the on-disk package.json files instead of bun.lock, fixing #20477/#20829 where a version bump between bun install and bun publish produced tarballs with stale dependency versions. It adds WorkspaceManifests (wrapping the existing ScratchManifests), removes the lockfile load from both commands entirely, restructures edit_root_package_json around a Substitution classifier, and clears the whole package.json cache after lifecycle scripts. It also makes ScratchManifests::parse_root/parse_member return InstallFailed when the parse logged errors, which propagates to --filter relation graphs and the add/remove write-back (sync_lockfile). ~400 lines of new tests across three files.
Security risks
None identified. The change reads package.json files the process already had to read, using the same parser bun install uses. No new external input surface, no path handling changes, no network or auth code touched (the publish diff only removes the lockfile load).
Level of scrutiny
Medium-high. This is user-facing package-manager behavior with a real correctness bug being fixed, but the fix is a data-source swap plus a restructuring, not new algorithm work. The blast radius extends beyond pack/publish because fail_on_logged_errors affects every ScratchManifests caller. The author has explicitly flagged two decisions for maintainer confirmation: (1) pack now fails with bun install's error when a workspace:/catalog: spec is present and the root manifests do not parse, instead of silently using stale lockfile data; (2) this supersedes #36279 and #36260, which took different approaches.
Other factors
- My two earlier findings on this PR were both fixed (662c8ee for the dropped log, 15e94df for the undrained pipe); all review threads including the comment-cop lint threads are resolved.
- Test coverage is thorough: stale lockfile, no lockfile, unreadable lockfile (three variants), packing the root vs. a member, prepack editing a sibling, error messages for missing workspace / missing catalog entry / invalid catalog range / missing
workspacesdirectory, and theFeatures::main()regression case. The publish side is covered with a mock registry asserting the manifest body. - CI: build 98253 had one unrelated ASAN failure (issue 09041,
bun runstdin UAF) and two expired macOS jobs; 98576 is a clean rerun in progress. - Downstream PRs #38835 and #38788 are based on this branch, and #36279/#36260 would be closed if this lands — a maintainer should confirm that direction.
- The
Featuresnarrowing in 490b252 is subtle enough (deliberately not validating the root's own dependency sections) that the retained multi-line comment explaining it is justified, and the two tests pin it.
Given the explicit maintainer-confirmation points the author raised and the cross-cutting ScratchManifests change, this should get a human look rather than auto-approval.
|
Build 98630 on the merged head confirms the diagnosis above: 177 of 179 jobs passed, including every x64 ASAN shard (the 09041 test passes now that the branch contains #38886); the remaining two jobs are the macOS 14 arm64 lanes waiting for agents. The latest automated pass reports nothing new and every review thread has a reply and is resolved, so this is ready for a maintainer. |
Problem
bun pm packandbun publishreplaceworkspace:*/workspace:^/workspace:~andcatalog:specs with the versions recorded inbun.lock, which only holds what the package.json files said at the lastbun install(edit_root_package_jsoninsrc/runtime/cli/pack_command.rs:lockfile.workspace_versionsandlockfile.catalogs).bun pm version, changesets) and edits catalogs after that install, so the tarball and the manifest sent to the registry carry the pre-bump dependency versions next to the package's own new version. With this repo state,@acme/utils@0.4.1gets published with"@acme/core": "1.2.3","^1.2.3"and"react": "^18.3.1"while@acme/core@1.3.0is published in the same run; consumers of a first release getNo matching version, later releases silently pin the previous core. Exit code 0, no warning. Same in 1.3.14; pnpm publishes the on-disk values.bun.lockis a cache of the manifests, and it is stale in exactly the situation pack runs in.Fix
pack()parses the root package.json and the workspaces it lists from disk the waybun installdoes (WorkspaceManifests::loadinsrc/install/PackageManager/workspace_manifests.rs, built on the existingScratchManifests) andedit_root_package_jsonresolvesworkspace:versions andcatalog:ranges from that.Package::parse_with_json) that produces theworkspace_versionsandcatalogsbun.lockis written from, run with only the parts that produce them enabled: theworkspaceswalk and the catalogs (Features { is_main, workspaces }inWorkspaceManifests::load). So every input that resolved before resolves to the same thing, just from the current files, and the root's own dependency sections are not parsed:bun install's checks on them (aworkspace:<range>in the root that no workspace satisfies) do not decide whether the root or a member packs, and such a range is still packed as written.parse_roottakes the features as a parameter; the--filterrelation graph and the add/remove write-back keepFeatures::main(), which they need for the dependency rows. No lockfile is needed anymore, so these specs also resolve in a checkout that has not runbun install.workspace:^|~|*orcatalog:spec is present (needs_workspace_manifests), so a package without them packs regardless of the state of the workspace around it, as before. When one is present and the root manifests do not parse (aworkspacesentry pointing at a missing directory, duplicate workspace names), pack now prints the same errorbun installprints instead of falling back to stale data. That includes the errors the parse only logs before continuing (an invalid catalog range, a duplicate catalog entry):ScratchManifests::parse_root/parse_membernow returnInstallFailedwhen the parse logged errors.ScratchManifestsis shared with the--filterrelation graph (bun add --filter '...x'), which therefore also fails on such a root instead of building the graph without the affected entries;bun installfails on the same input either way. The add/remove package.json write-back (sync_lockfile) re-parses throughScratchManifeststoo; it runs after resolution has already printed those same errors (install_with_managerprints and resets the log before it), so on such a root the command now fails at that point instead of at thehas_errorscheck a few steps later, with the same output.workspace:now reports"<root>/package.json" has no workspace named "x", or its package.json has no version; thecatalog:"no matching catalog dependency" message is unchanged and "catalogs require a lockfile" is gone.prepack/prepublishOnlyscript writes to other workspaces are seen as well (this also removes the Windows-only cache key conversion). The package.json being packed is re-read after loading the manifests because loading can add entries to the hashbrown-backed cache the entry lives in.edit_root_package_jsonis restructured around aSubstitutionclassifier shared withneeds_workspace_manifests, so the two cannot disagree about which specs need the manifests.test/cli/install/bun-pack.test.ts(workspace versions, catalogs, no lockfile, packing the root, prepack editing a sibling, error messages),test/cli/install/catalogs.test.ts(catalog edited after install, invalid catalog range reported at its definition),test/cli/install/bun-publish.test.ts(manifest received by a mock registry). The new tests fail on the current release and pass with this change; the fullbun-pack,catalogs,bun-add-catalogandbun-add-filterfiles pass with the debug build.bun-pack.test.tsalso pins the root case (workspace:9.9.9in the root next to aworkspace:*: packing the root and packing a member both succeed; both failed withNo matching version for workspace dependencywhile the root was parsed withFeatures::main()) and aworkspacesentry that does not exist (Workspace not foundfor a pack that has a spec to resolve, success for a sibling that has none).bun-pack.test.tsandbun-publish.test.tsalso cover an emptybun.lock, abun.lockwith git conflict markers and a corruptbun.lockb: packing a package without workspace specs, packing one withworkspace:*, and publishing one withworkspace:^all succeed with nothing on stderr. The current release exits 1 withfailed to parse lockfilein all three cases because it loaded the lockfile unconditionally.fails when no workspace with a version matches). pack/publish: warn and continue when the lockfile is unreadable #36260 made the lockfile load errors a warning; the load no longer exists.Fixes #20477
Fixes #20829 (closed as a duplicate of #20477; same scenario with a peer dependency)
The pack half of #28935 is the same bug; the other half,
bun pm version/bun installnot refreshing the workspace versions inbun.lock, is #18906 (#36285, #28936) and is unaffected by this change, which just stops pack from depending on it.Background
workspace:andcatalog:are protocols that only mean something inside the monorepo:workspace:^means "the sibling workspace package, and publish it as^<its current version>",catalog:/catalog:<name>means "the range the root package.json defines for this dependency in that catalog". Pack has to rewrite both into plain ranges because registry consumers have neither the workspace nor the catalog.bun.lockstores the workspace versions and the catalogs it saw at the last install (itsworkspacesandcatalogsections);bun installcomputes them by parsing the root package.json, whoseworkspacesglobs lead it to read every member's package.json.ScratchManifestsruns that same root parse into a throw-awayLockfileand already existed for--filterrelation graphs;WorkspaceManifestswraps it and exposes the two lookups pack needs.PackageManager::inithas already located the workspace root (and chdir'd there) by the time pack runs, which is whyroot_package_json_path()is the root's package.json when packing a member and the package's own otherwise.WorkspacePackageJSONCacheis a hash map of parsed package.json files keyed by path;pack()holds a reference into it, and inserting into a hash map can move its entries, hence the re-read after the manifests are loaded.Repro (before / after)
Before:
"@acme/core": "1.2.3", peer"^1.2.3","react": "^18.3.1".After:
"@acme/core": "1.3.0", peer"^1.3.0","react": "^19.1.0", and the same withbun.lockdeleted.Error paths with this change:
no test proof · iteration 7 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/cli/install/bun-pack.test.ts test/cli/install/bun-publish.test.ts