install: keep the scope when a yarn.lock npm: alias points at a scoped package - #38806
Conversation
…d package get_package_name_from_resolved_url recovers the real package name of an alias entry from its registry tarball URL. It tested the last path segment before "/-/" for a leading "@", but for a scoped package that segment is the unscoped name and the scope is the segment before it, so "npm:@types/node" was migrated as the package "node". Derive the name from the last one or two path segments instead, and return None for URLs with no name segment rather than a garbage slice. Fixes #27781
|
Warning Review limit reached
Next review available in: 20 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 (2)
Comment |
|
Status: merged (e9b5e63), #27781 closed. Reproduced on the released canary (b7a0431) with a two-line project: Self-review surfaced a broader follow-up that this PR does not cover and whose regression guard it provides: alias entries are still identified by the alias name or the tarball URL rather than the |
There was a problem hiding this comment.
LGTM — focused fix to get_package_name_from_resolved_url so scoped tarball URLs yield @scope/name instead of the unscoped tail.
What was reviewed:
- Traced the new segment-split logic against scoped (
@types/node/-/…), unscoped (lodash/-/…), and degenerate (foo/-/…,/-/…) URLs; scoped now returns@types/node, unscoped is unchanged, and malformed inputs returnNonewhere the old loop returned an off-by-one slice — all three callers already fall back to the spec name onNone. - Confirmed
strings::rsplit_once_char/last_index_of_charexist inbun_core::stringwith the expected semantics. - Test is hermetic (loopback
Bun.serveregistry, temp cache dir), drains pipes concurrently, asserts the fullpackagesobject plus the requested-manifest set, and follows the file's existingtempDir/bunEnvconventions.
Extended reasoning...
Overview
Rewrites Entry::get_package_name_from_resolved_url in src/install/yarn.rs (~12 lines net) to correctly extract the package name from a registry tarball URL when the package is scoped, and adds one test to test/cli/install/migration/yarn-lock-migration.test.ts covering direct, scoped-alias, and transitive npm:@scope/name aliases plus a co-existing plain @types/node entry.
Security risks
None. The function is a pure byte-slice parser over a URL string already read from a yarn.lock file; it only affects which package name is written into the migrated bun.lock. No new I/O, allocation, or unsafe code; the ? on index_of/rsplit_once_char guards every slice.
Level of scrutiny
Low-to-medium. The change is confined to one 10-line helper in the yarn v1 lockfile migrator (a one-shot CLI path, not runtime hot code) and a purely additive test. The three call sites are enumerated in the PR description; I checked that the behavioural change on None (previously an off-by-one slice) is handled by each caller's existing .unwrap_or_else(|| get_name_from_spec(...)) fallback, and that the third caller's alias-detection loop simply stops firing spuriously for non-aliased scoped packages.
Other factors
- The new implementation reuses the in-tree
bun_core::stringshelpers (rsplit_once_char,last_index_of_char) rather than the hand-rolled backward loop, which is idiomatic per the repo's "grep for the in-tree helper" rule. - The test uses a loopback registry and the recorded manifest names as an external witness of the fix, so it fails observably on the released build (aliases would resolve to
node) — matching the REVIEW.md "prove the test fails for the right reason" requirement. - No CODEOWNERS entry covers
src/install/or this test file. - PR description notes the existing 14 snapshots (including the large
yarn-cli-repofixture with many scoped packages) pass byte-identically, so the unscoped path is regression-covered.
Problem
npm:alias points at a scoped package records the unscoped tail of the name. For"my-node-types@npm:@types/node@20.11.5"resolved fromhttps://registry.yarnpkg.com/@types/node/-/node-20.11.5.tgz#...,bun.lockgets"my-node-types": ["node@20.11.5", ...]instead of["@types/node@20.11.5", ...], and the post-migration manifest pass asks the registry fornode. The same migration runs insidebun installwhen only ayarn.lockexists, which is theGET https://registry.npmjs.org/monaco-editor-treemended/-/monaco-editor-treemended-1.83.16.tgz - 404reported in yarn.lock migration strips scope from npm: aliased packages, causing 404s #27781. Reproduces on the released canary (b7a0431) and on main.Entry::get_package_name_from_resolved_url(src/install/yarn.rs:239), which names alias entries after their tarball URL, walks back from/-/and checks the last path segment for a leading@. In a scoped tarball URL the last segment is the unscoped name and the scope is the segment before it, so the check never fires and only the last segment is returned. The Zig original had the same bug; fix(install): preserve scope in scoped packages during yarn.lock migration #27782 and fix: yarn.lock migration preserves @scope for npm: aliased packages #27889 fixed it there and were closed when the Zig sources went away.Fix
get_package_name_from_resolved_urlnow splits the path before/-/into its last segment and the one before it, and returns both when the earlier one starts with@, otherwise just the last one. It returnsNone(callers then fall back to the spec name) when there is no name segment, where the old code returned an off-by-one slice or an empty name.<registry>/<name>/-/<basename>-<version>.tgz, and<name>is one segment for plain packages and exactly two (@scope/name) for scoped ones, so the segment before the name starting with@is the only thing that distinguishes the two shapes; nothing else in the URL encodes it. Plain packages take the same path as before, so their output is unchanged.yarn.rs:941), the(name, version)key used to give the alias and a plain entry of the same package one package id (yarn.rs:843), and the alias detection that registers an entry under its spec name only when it differs from the real name (yarn.rs:1603), which previously fired for every scoped package.Entry::parse_npm_aliasalso mis-splitsnpm:@scope/name@rangeat the scope's@, but its only consumers are the first dependency pass, whose slices are overwritten by the second pass atyarn.rs:1695onward, and aversionfield spellednpm:..., which yarn v1 never writes. Nothing observable depends on it.test/cli/install/migration/yarn-lock-migration.test.ts, "yarn.lock with npm aliases of a scoped package keep the scope". One migration covers an unscoped alias, a scoped alias and a transitive alias of@types/node@20.11.5(sharing one yarn entry, as yarn writes patterns that resolve to the same tarball) next to a plain@types/node@18.19.0, asserts the fullpackagesobject of the resultingbun.lock, and runs against a loopback registry that records which manifests the migration asks for (@types/nodeand the consumer, nonode). Fails on the released build with the three alias entries readingnode@20.11.5, passes with this change.yarn-lock-migration.test.ts(scoped-package and real-world fixtures, 14 snapshots),migrate.test.tsandlockfile-only.test.tspass unchanged with the debug build; theyarn-cli-repooutput is byte-identical to its snapshot. That case still brushes its 5s timeout under debug+ASAN, which is pre-existing and tracked by test(install): fix yarn-lock-migration yarn-cli-repo case under debug+ASAN #35377.name_to_useblock for tarball entries and leave this function as is (after install: fix default-trusted lifecycle scripts being blocked after yarn.lock migration #38795 it becomes the only source of alias package names).Fixes #27781
Background
npm:alias entry is keyed by the alias spec (alias@npm:real-name@range) and carries the real package'sversion,resolvedtarball URL andintegrity. The migrator stores the package under its real name and hangs it in the tree under the alias, so it needs the real name, and it currently recovers it from the tarball URL.bun.lockpackagesentries are["<real name>@<version>", "<registry, empty for the default>", {meta}, "<integrity>"]keyed by the node_modules path. The first element is whatbun installlater downloads, which is why a wrong name there turns into a 404 or a different package.fetch_necessary_package_metadata_after_yarn_or_pnpm_migrationfetches the manifest of every migrated npm package by the name recorded in the lockfile to fill in bin/os/cpu. The test points that pass at a loopback server and uses the requested names as a second, external view of which package each alias was resolved to.