install: warn when a link: dependency declares peerDependencies - #35602
install: warn when a link: dependency declares peerDependencies#35602robobun wants to merge 9 commits into
Conversation
A package installed via `bun link <name>` (or a `link:` dependency) is a symlink into its real source tree. Node and bun both realpath that symlink before walking node_modules, so peers installed in the consumer project are invisible to the linked package unless it has them in its own node_modules. npm and pnpm have the same limitation. pnpm prints a warning listing the peers and explaining why; bun previously said nothing, leaving users to discover the resolution failure at runtime. Print a matching warning that lists each peer dependency and points at `--preserve-symlinks`, which makes the linked package resolve from the consumer's node_modules. The warning is suppressed under --silent and does not change install behavior. Fixes #13676
WalkthroughChangesLinked packages declaring Linked package peer dependency warnings
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
Status: ready for review at 490dc6e. All review feedback addressed; every review thread is resolved. The new test in |
|
Updated 1:13 PM PT - Jul 25th, 2026
❌ @robobun, your commit 490dc6e has some failures in 🧪 To try this PR locally: bunx bun-pr 35602That installs a local version of the PR into your bun-35602 --bun |
Reads peerDependenciesMeta so entries marked optional are shown as "name@version (optional)". The warning still fires when every peer is optional (matching pnpm), since the realpath behavior applies to optional peers too when the consumer does install them.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/cli/install/bun-link.test.ts`:
- Around line 503-514: Update the subprocess assertions in the shown `bun link`
test and the corresponding cases around the referenced ranges to drain stdout,
stderr, and exited concurrently rather than awaiting them sequentially. Start
all three promises before awaiting, then assert the captured combined results
while preserving the existing success and error expectations.
- Around line 527-532: Strengthen the warning assertions around stderrForInstall
in both the bun link and bun install cases. Verify the linked-package warning
header occurs exactly once, and assert the warning includes the
real-disk-location explanation alongside the existing peer dependency and
--preserve-symlinks checks. Keep the existing optional and non-optional
dependency assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 152a0582-77d9-4708-a293-204706371128
📒 Files selected for processing (2)
src/install/lockfile/Package.rstest/cli/install/bun-link.test.ts
There was a problem hiding this comment.
Thanks for addressing the peerDependenciesMeta feedback — optional peers are now annotated and the comment is condensed. I didn't find any correctness issues in this revision, but since this adds new user-facing warning copy to bun install/bun link, a maintainer should sign off on the wording and on whether to still warn when every peer is optional.
What was reviewed:
FEATURES == Features::LINKgate —FeaturesderivesPartialEqand onlyfolder_resolver.rspassesLINK, so this fires exactly onlink:depspeerDependenciesMetaoptional lookup matches the existing parse at line ~2222;log_level != Silentmatches the pattern used acrosssrc/install/--preserve-symlinksis a real resolver flag, so the remedy is accurate- Test covers register/link/install/
--silentand the(optional)annotation; sequential pipe reads match every other test in this file (output is tiny)
Extended reasoning...
Overview
Adds a ~38-line advisory block in parse_with_json_impl (src/install/lockfile/Package.rs) that fires when parsing a link: dependency's package.json with a non-empty peerDependencies object. It prints a bun_core::warn! header, one line per peer (annotated (optional) when peerDependenciesMeta[key].optional === true), and a two-line hint pointing at --preserve-symlinks. Suppressed under --silent. A new test in test/cli/install/bun-link.test.ts covers all four flows.
Security risks
None. Read-only inspection of already-parsed JSON; output goes to stderr; no change to resolution, lockfile, or exit codes.
Level of scrutiny
Medium. The Rust logic is straightforward and mirrors existing patterns (bun_core::warn!/pretty_errorln!/Output::flush() are used identically in migration.rs and extract_tarball.rs; the log_level != Silent gate appears throughout src/install/; the EBoolean match copies the block at line ~2228). But it introduces new user-facing CLI output on a very common command, and REVIEW.md treats warning/error copy as reviewed word-for-word — the exact phrasing, whether to suppress when all peers are optional, and whether --preserve-symlinks is the recommendation Bun wants to lead with are UX calls a maintainer should make.
Other factors
- My earlier inline comment about ignoring
peerDependenciesMetais addressed (commit24483cb2) and the thread is resolved. The comment-cop bot's paragraph-comment complaint is addressed (commit1aa41ed8). - CodeRabbit's two open nits (concurrent pipe drain, stronger assertions) are minor: the new test copies the exact spawn/await shape used by every other test in
bun-link.test.ts, andbun linkoutput is a handful of lines so pipe backpressure isn't a real risk here. - robobun confirmed the test fails on
mainand passes with the change, so the test is load-bearing.
…re remedy, soften wording - Only list peers that are NOT already present in the linked package's own node_modules, so the common case (bun install was run in the linked package) no longer warns at all. - Soften "will not resolve" to "may not resolve". - Under --linker isolated, do not recommend --preserve-symlinks (it breaks the isolated store layout); suggest installing peers in the linked package instead. Hoisted/auto keeps the --preserve-symlinks hint. - Pull the warning into a #[cold] helper so the hot parse path stays compact. - Test now covers: peer already installed (skipped), optional annotation, isolated linker remedy text, one warning per link, --silent, bun install.
…lly safe - bun_sys::exists_z takes the &ZStr directly, avoiding a PathBuffer copy. - The effective linker (Auto -> Hoisted/Isolated) is not decided until install time, so branching on pm.options.node_linker reads Auto for the common workspace-default case. --preserve-symlinks breaks the isolated store layout, so rather than risk recommending it there, keep the remedy to the universally-correct "install in the linked package's own node_modules". - Test now also covers the case where every peer is installed under the linked package (no warning).
Matches the cleanup convention used by the sibling tests in this file so a persistent runner does not accumulate dangling global-link symlinks.
The register step creates the global symlink; keep it inside the try so the finally-unlink still runs if one of its assertions throws. Drop the --silent sub-case to keep the test under the default timeout on debug builds (the log_level != Silent guard is a one-line comparison).
What
bun link <name>(andbun installwith alink:dependency) now prints a warning when the linked package declarespeerDependenciesthat are not already present in the linked package's ownnode_modules:Peers marked optional in
peerDependenciesMetaare annotated(optional). Peers already installed under the linked package are skipped, so runningbun installinside the linked package silences the warning.--silentsuppresses it entirely. Install behavior is unchanged.Why
A
link:dependency is a symlink into the package's real source tree. Both bun and Node realpath that symlink before walkingnode_modules, so a peer installed in the consumer's tree is invisible to the linked package unless the linked package also has it in its ownnode_modules. That is inherent to symlink-based linking; npm and pnpm have the same limitation with theirlinkcommands, and pnpm prints an equivalent warning.Before this change bun said nothing:
Features::LINKhaspeer_dependencies: false, so the linked package's peers were never read. Users only discovered the problem at runtime asCannot find package 'react' from '/real/path/to/linked/index.js'(or, worse, as two copies of the peer being loaded).This does not make the linked package magically find the consumer's peers; doing that would require either a resolver change or a new install option. The warning surfaces the situation and the universally-safe remedy (install the peer under the linked package).
--preserve-symlinksis another option for hoisted installs but is not mentioned in the warning because it breaks the isolated linker's store layout and the effective linker is not known at the point the warning is emitted.Refs #13676.
How
parse_with_json_implalready has the parsedpackage.jsonand theFeaturesit was called with. When that isFeatures::LINK(only thelink:resolver passes this), the newwarn_linked_peer_dependencieshelper walkspeerDependencies, skips entries that already exist under the linked package'snode_modules, annotates optional peers frompeerDependenciesMeta, and prints the warning. The check runs once per linked package per install (the folder-resolver result is cached).Test
test/cli/install/bun-link.test.tsgains a case covering:bun link <name>prints the warning once with each unresolved peer listed and the real-location explanationnode_modulesis not listedpeerDependenciesMetaoptional entries are annotatedbun installwith alink:dependency inpackage.jsonprints the same warning