pack/publish: pack() consumes Context, returns Publish::Context<'a> - #37670
pack/publish: pack() consumes Context, returns Publish::Context<'a>#37670robobun wants to merge 1 commit into
Conversation
pack() borrowed its pack::Context and returned Publish::Context<'static>, so both return sites re-derived manager and command_ctx from raw pointers, from_workspace inherited the 'static return type and re-derived its manager the same way, and exec_with_manager, from_workspace and publish exec reached the manager, the log and the command context through further raw pointers. pack::Context is now Context<'a, 'l>: manager and command_ctx carry 'a and the caller-local lockfile carries 'l. pack() takes the context by value and moves manager and command_ctx into the returned Publish::Context<'a, true>, and from_workspace returns Context<'a, true>. The log is reached through PackageManager::log_mut() instead of the local pm_log helper, and publish exec reborrows context.command_ctx directly instead of going through cmd_ctx_ptr; 15 unsafe blocks go away. The lifetimes are compile-time only and the references are moved rather than cast; the context is passed by value once per pack() call and the Publish::Context layout is unchanged.
|
Warning Review limit reached
Next review available in: 1 minute 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 (2)
Comment |
|
Updated 4:45 PM PT - Aug 11th, 2026
✅ @robobun, your commit 6f281acdc09f884a037f82bdac2cafacb6b9737a passed in 🧪 To try this PR locally: bunx bun-pr 37670That installs a local version of the PR into your bun-37670 --bun |
There was a problem hiding this comment.
LGTM — mechanical lifetime refactor that removes 15 unsafe blocks with no behavior change.
What was reviewed:
- Confirmed
PackageManager::log_mut()takes&selfsolet log = manager.log_mut(); load_from_cwd(Some(&mut *manager), log)borrow-checks without the old raw-pointer split. Command::Context<'a> = &'a mut ContextData, so passingcontext.command_ctxtorun_package_script_foregroundis a plain reborrow; readinguse_system_shellonce beforehand matchespm_version_command.PackReturnandpack::Contexthave no consumers outside the two changed files; the new'llifetime param reaches every use site.
Extended reasoning...
Overview
This PR threads proper lifetimes through bun pm pack / bun publish so the &mut PackageManager and &mut ContextData flow into the returned Publish::Context<'a> as ordinary moves instead of being reconstructed from raw pointers with 'static. Concretely: pack::Context gains a second lifetime 'l for the caller-local lockfile, pack() consumes its context by value and returns Publish::Context<'a, true>, from_workspace returns Context<'a, true>, the pm_log raw-pointer helper is deleted in favor of manager.log_mut(), and PublishCommand::exec reborrows context.command_ctx directly for the two lifecycle-script calls. Net: 15 unsafe blocks removed, none added; PackReturn type alias deleted.
Security risks
None. Single-threaded CLI code; the change replaces raw-pointer reborrows with compiler-checked borrows of the same objects. No new I/O, parsing, or trust-boundary crossings.
Level of scrutiny
Low-to-moderate. This is a Rust type-system hardening change where the borrow checker is the primary verifier — if it compiles, the lifetime relationships the old SAFETY comments asserted are now enforced. I checked that log_mut() takes &self (so the log + &mut *manager split at both load_from_cwd sites is a real disjoint borrow, not a hidden alias), that Command::Context<'a> is &'a mut ContextData (so the script-runner reborrow is sound and the use_system_shell hoist mirrors pm_version_command.rs), and that pack::Context/PackReturn have no consumers outside these two files.
Other factors
- The one remaining raw-pointer projection (
pm_workspace_cache) is intentionally kept and its comment now states the actual reason (the&mut MapEntryoutlives otherctx.manageruses). - Existing coverage:
bun-pack.test.ts76/76 pass;bun-publish.test.tsfailures match main exactly (pre-existing debug-build timeout cascade). No new tests needed for a zero-behavior-change refactor. - Part of a series of small standalone hardening PRs; scope is tight and self-contained.
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
|
On the duplicate flag: #35399 is a 40 file sweep from July that, among other things, drops the same |
Problem
bun pm packandbun publishbehave the same; this cleans up how the pack code hands its state on to the publish code.pack()borrowed itsContextbut returned aPublish::Context<'static>, so both return sites rebuilt the manager and command context from raw pointers, justified by SAFETY comments calling them process-lifetime singletons.Fix
pack()takes itsContextby value and returnsPublish::Context<'a>,'abeing the lifetime of the manager and command context it was given. Both return sites move those fields across instead of casting.'l, since both callers keep it in a local and one shared lifetime would pin the returned publish context to that local.log_mut()and plain reborrows;pm_logandPackReturnare deleted. 15 unsafe blocks removed, none added. One raw projection (pm_workspace_cache) stays becausepack()holds the entry it yields across other manager uses.cargo checkandclippyclean, the pack test file passes, and the publish test file has 14 failures reported as identical on main (a debug-build timeout kills the local registry).Background
PackageManageris the state object shared by bun's install, pack and publish commands. ItsLogcollects errors and warnings, is a separate allocation, and is reached throughlog_mut().pack::Contextbundles what packing needs: the manager, the CLI command context, and an optional lockfile used only to resolve workspace versions.pack::<FOR_PUBLISH>()serves both commands. For publish it returns aPublish::Contextholding the tarball path plus the same manager and command context, whichbun publishuses to upload and to run the publish and postpublish scripts.'staticreference in a return type claims the value lives for the whole process, and making one from a shorter borrow needs unsafe. Returning the caller's lifetime instead lets the borrow checker verify the claim.Original description
What
pack_command::pack()borrowed itspack::Context<'a>but returnedOption<Publish::Context<'static, true>>(through thePackReturnalias), so both return sites (dry run and real pack) rebuiltmanagerandcommand_ctxfrom a raw pointer with SAFETY comments appealing to "process-lifetime singletons".Publish::Context::from_workspaceinherited the'staticreturn type and used amanager_ptrof its own for the lockfile load, the package.json path and thepack::Contextliteral;PackCommand::exec_with_managerdid the same withlog_ptr/manager_ptr;pm_logwas a raw-pointer helper for reachingmanager.loginsidepack(); andPublishCommand::execkept amanager_ptrfor one error message and acmd_ctx_ptrto passcontext.command_ctxto the publish/postpublish script runner.The second lifetime is what makes this work: both callers load the lockfile into a function local, so with one lifetime the returned
Publish::Contextwould be pinned to that local.pack()now takes the context by value and both return sites writemanager: ctx.manager, command_ctx: ctx.command_ctx. The four helpers that take the context (archive_package_json,add_archive_entry,print_archived_files_and_packages, andpackitself) take or ownContext<'_, '_>and the five pass-through calls become&mut ctx.exec_with_managerandfrom_workspacetakelet log = manager.log_mut();, calllockfile.load_from_cwd(Some(&mut *manager), log)(theLoadResultonly borrows the local lockfile), report errors throughlog, and build thepack::Contextliteral withmanagerdirectly; the fivepm_log(manager_ptr)uses insidepack()becomectx.manager.log_mut()andpm_logis deleted;PublishCommand::execprints throughmanager.log_mut()in thefrom_tarball_patherror arm and reborrowscontext.command_ctxfor the two script runs, readinguse_system_shellonce beforehand aspm_version_commandalready does.PackReturnis deleted.pm_workspace_cachestays a raw projection becausepack()keeps the&mut MapEntryit yields alive across every other use ofctx.manager; its comment now says so. Two call sites ofpack(), 15 unsafe blocks removed (7 inpack_command.rs, 8 inpublish_command.rs), none added;Publish::Contextitself is unchanged.Why
The
&mut PackageManagerand&mut ContextDatathatbun publishruns on now flow fromexecthroughfrom_workspaceandpack()intoPublish::Contextas ordinary moves with a single lifetime'a, so the type signatures say where they come from and how long they live instead of'staticplus comments, and the lockfile's shorter lifetime is visible as'l. It is zero-cost: the lifetimes are compile-time only, the moves replace pointer casts of the same two words,log_mut()is the same inline pointer loadpm_logperformed,use_system_shellwas already read before each call, and the context is passed by value exactly once perbun pm pack/bun publishinvocation.Part of a series of small type-system hardening changes; each PR stands alone.
Verification
cargo checkandcargo clippyare clean for the touched crates. Debug build succeeds. bun bd test test/cli/install/bun-pack.test.ts: 76 pass, 0 fail; test/cli/install/bun-publish.test.ts: 24 pass, 14 fail: identical 14 failures on main (lifecycle scripts > should run in orderhits the 5s timeout under the debug build, which kills the local Verdaccio registry and cascades into 13 ConnectionRefused failures in the rest of the file), so pre-existing and unrelated to this change.