pm: polish dedupe, prune, pm ls and pm licenses output
Problem
Five rough edges in the package manager command output, reported in #38925. The resolutions and lockfiles are correct in every case; only the output misleads.
bun dedupeprints a row like↳ undici-types 7.24.6with no arrow or target when a duplicate is dropped outright, which reads like truncated output next toname old → newrows (src/install/dedupe.rs,print_rows)- a multi-major downgrade like
@types/node 25.9.1 → 22.19.19is styled exactly like a patch bump bun prune --productionon a freshly installed tree warnsnode_modules/<pkg> is not the version bun.lock expectsplusnote: run 'bun install' first, claiming a correct tree is stale (src/install/prune.rs,HoistedTree::removable). Excluding dev deps changes which copy the hoist favors, so the hoisted position legitimately holds a different copy than the production view expects, and runningbun installchanges nothingbun pm licensesprints no command banner and no closing summary, unlike every other pm command- summary lines count different things without naming them: dedupe counts lockfile entries, prune counts installed entries,
bun pm lscounts tree positions, so the numbers look contradictory across commands
Fix
- dedupe: a dropped duplicate now prints
↳ undici-types 7.24.6 → (removed); a row whose surviving versions are all a lower major is marked(downgrade)(yellow on a terminal). Same-major downgrades stay unmarked to keep the marker meaningful - prune: when pruning with
--production/--omitand the hoisted position holds a different version, the copy is kept silently if that version exists in the lockfile under the same name (it is the copy a full install put there). A version the lockfile does not know anywhere is still a stale tree and keeps the existing warning and note. Plainbun prunebehavior is unchanged; only the messages changed, never what is kept or removed bun pm licenses: prints the standardbun pm licenses v<version> (<sha>)banner and closes withN packages across M licenses (checked K packages in bun.lock) [elapsed]. The banner prints after all validation exits so error output keeps a clean stdout, and--jsonoutput is byte-for-byte unchanged- units named: dedupe summaries say
checked N packages in bun.lock(matching the existingbun pm licensesempty-case wording), prune sayschecked N installed packages,bun pm lssaysnode_modules (N installed).bun install/bun update'sSaved bun.lock (N packages)is left alone: the count sits next to the file it describes, and changing it would churnbun installoutput wholesale - docs samples in
docs/pm/cli/{dedupe,prune,pm}.mdxand the npm-migration guide updated to match
Verified by updating the existing output assertions in test/cli/install/bun-dedupe.test.ts, bun-prune.test.ts, bun-pm-licenses.test.ts, bun-pm.test.ts and bun-install-lifecycle-scripts.test.ts; all five files pass with the debug build. The previously asserted spurious --production warnings (two prune tests) now assert empty stderr, and one of them gains a stale-tree case proving the warning still fires when the installed version is foreign to the lockfile.
The reporter also mentioned bun outdated drawing a rule between every row; that predates the pm work (reproduces on 1.3.14) and is left out of this PR.
Background
- The hoisted install tree assigns each package a position in
node_modules; when two versions of one package are needed, one wins the top-level spot and the others nest under their dependents. Which one wins depends on which dependency edges exist, so excluding dev dependencies (--production) can flip the winner bun prunecompares the tree on disk against the positions the lockfile implies for the requested view; a keep decision on mismatch was already conservative, this PR only changes when it narrates that decision as a problembun dedupecollapses duplicate versions that a single surviving version can satisfy; a duplicate whose dependents all move elsewhere leaves no target, which is the(removed)case
no test proof · iteration 0 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/cli/install/bun-install-lifecycle-scripts.test.ts test/cli/install/bun-prune.test.ts test/cli/install/isolated-relink.test.ts