Skip to content

install: name yarn.lock npm: alias packages after the alias spec, not the tarball URL - #38948

Open
robobun wants to merge 5 commits into
mainfrom
farm/4381ba69/yarn-alias-name-from-spec
Open

robobun wants to merge 5 commits into
mainfrom
farm/4381ba69/yarn-alias-name-from-spec

Conversation

@robobun

@robobun robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator

Follow-up to #38806 (#27781): that change fixed what the tarball URL heuristic returns for scoped packages; this one stops the yarn migrator from using a URL heuristic for alias names at all. There is no user report for the three outputs below; they were found while following up #38806. Affected users are those migrating a yarn.lock with npm: aliases resolved from a registry without the <name>/-/ tarball layout (GitHub Packages and similar) and those whose yarn.lock uses one alias name for two different packages at the same version; the second case installs the wrong package silently.

Problem

  • bun pm migrate / bun install on a yarn v1 lockfile names an npm: alias package after its resolved tarball URL (Entry::get_package_name_from_resolved_url, src/install/yarn.rs:241 on main, used at :830, :927 and :1588) and falls back to the alias name, and consolidates entries by alias name (YarnLock::consolidate_and_append_entry, :472). The alias spec that keys every such entry, alias@npm:<name>@<range>, is never read for the name. Three wrong outputs follow, all reproduced on the released build and on main:
    • A registry without the <name>/-/<file>.tgz tarball layout (GitHub Packages, https://npm.pkg.github.com/download/@corp/foo/1.0.0/<hex>): "gh-alias": ["gh-alias@1.0.0", ...] instead of @corp/foo@1.0.0, and the post-migration manifest pass asks the registry for gh-alias.
    • A tarball URL whose /-/ directly follows the host (https://registry.npmjs.org/-/bar-1.0.0.tgz#sha1): "host-alias": ["registry.npmjs.org@1.0.0", ...] instead of bar@1.0.0.
    • Two aliases with the same alias name and version that point at different packages (x depends on same "npm:foo@1.0.0", y on same "npm:baz@1.0.0", two yarn entries): consolidation merges the two entries, one package disappears from bun.lock, and the other parent's dependency silently resolves to the wrong package.
  • Entry::parse_npm_alias (:221) also split npm:@scope/name@range at the scope's @; nothing observable consumed that, but it is the parser the fix builds on.

Fix

  • Entry gets a name field, set when the key line is parsed: the target of the first alias@npm:<name>@<range> spec on the line, otherwise the name of the first spec (Entry::package_name_of). parse_npm_alias now returns the target name as well and skips a leading scope @ before looking for the separator, the same split dependency::parse_with_tag uses for npm: versions; a spec without a range (alias@npm:@corp/bare) yields the whole target as the name.
  • Consolidation and the package-id pass both key on (Entry::dedupe_name(), version): the repository for a git entry (which is how the package-id pass already identified git entries) and entry.name for everything else. The second naming pass uses entry.name in place of the URL lookup, and the index pass registers every spec whose name differs from entry.name as an alias. get_package_name_from_resolved_url has no callers left and is deleted; the two copies of the "is this a direct URL dependency" spec scan become Entry::has_direct_url_spec.
  • dedupe_name is there because keying consolidation on the package name alone (this PR's first revision) merged a git entry keyed under a package's name (lodash@git+.../lodash-fork.git, a fork keeping upstream's name and version) with my-lodash@npm:lodash@4.17.21; the package-id pass kept those apart by repository, and consolidation now uses the same identity. Git entries without a parsable repository (non-GitHub hosts) are still identified by their spec name exactly as before this PR; that such an entry can collapse into a registry package of the same name and version is pre-existing and reported separately.
  • Why this is correct: the alias spec is the one piece of the entry that states which package was installed. It is what yarn resolved (yarn's own dependency entries, same "npm:foo@1.0.0", are matched against exactly these specs), it does not depend on the registry's URL layout, and it is present even when resolved is not. The URL only ever agreed with it by convention, so for every entry the old code named correctly the new name is the same, and the existing alias, scoped-alias and yarn-stuff coverage (aliases sharing a key line with plain specs, aliases on separate lines deduplicating into one package, two versions of the same aliased package) is unchanged.
  • Consolidating by real name also merges a plain lodash@4.17.21 entry with a my-lodash@npm:lodash@4.17.21 entry on another line. The package-id pass already gave those one package before, so the output is the same; it now happens one step earlier.
  • Scope: only alias naming changes. Direct URL dependencies and other tarball entries are still named by the name_to_use block, which still reads the name out of default-registry URLs; that block is untouched here because install: fix default-trusted lifecycle scripts being blocked after yarn.lock migration #38795 and install: fix yarn.lock migration panic on tarball URLs with "/-/" right after the host #38803 are reworking it. install: fix yarn.lock migration panic on tarball URLs with "/-/" right after the host #38803 currently modifies the deleted function for that block, and its new test pins the alias-name fallback for the /-/-after-host alias row (alias-of-odd-tarball@1.0.0), which this change turns into whatever@1.0.0; install: make yarn.lock migration linear in lock size #37244 keys its consolidation map on the spec name and would switch to entry.name. Whichever of these lands second has a one-hunk conflict to resolve.
  • Verified:
    • test/cli/install/migration/yarn-lock-migration.test.ts, "yarn.lock npm aliases are named after the alias spec, not the tarball URL" (an alias without a range, the GitHub Packages layout with a scoped target, /-/ after the host, and yarn's usual "x-cjs@npm:x@^4.2.0", x@^4.1.0: line) and "yarn.lock aliases of one name and version to different packages stay separate". Both assert the full packages object of the migrated bun.lock and, through a loopback registry, the names the migration fetches manifests for. Without the src/ change the first fails with gh-alias@1.0.0 / registry.npmjs.org@1.0.0 (the range-less row passes on main and is there to cover the parser), the second with both parents resolving to baz@1.0.0 and foo missing; both pass with this change. "yarn.lock git dependency keyed by a package name stays separate from an alias of it" pins the git case above: it passes on main, failed on this PR's first revision (my-lodash resolved to the fork), and passes now. The scoped-alias test from install: keep the scope when a yarn.lock npm: alias points at a scoped package #38806 stays as the regression guard and now shares the loopback-registry helper with the new tests.
    • The rest of yarn-lock-migration.test.ts (21 tests in total, 14 snapshots including yarn-cli-repo and yarn-stuff), lockfile-only.test.ts, the yarn cases of nested-overrides.test.ts and migrate.test.ts pass unchanged with the debug build (yarn-cli-repo needs --timeout on a loaded machine, the pre-existing debug+ASAN timing tracked in test(install): fix yarn-lock-migration yarn-cli-repo case under debug+ASAN #35377; its snapshot is unchanged); cargo clippy -p bun_install and the source lints are clean.

Background

  • A yarn v1 lockfile entry is keyed by the dependency specs that resolved to it. A plain dependency gives name@range; an npm: alias ("my-lodash": "npm:lodash@4" in package.json) gives my-lodash@npm:lodash@^4.0.0. yarn writes every spec that resolved to the same tarball on one key line, so an alias spec and plain specs of the same package can share an entry. The entry body has the version, the tarball URL and integrity, but no name.
  • The migrator turns each entry into one bun.lock package, ["<name>@<version>", "<registry url or empty>", {meta}, "<integrity>"], keyed by its node_modules path, and hangs it in the tree under the alias. The name in the first element is what bun install later downloads and what the post-migration manifest pass (fetch_necessary_package_metadata_after_yarn_or_pnpm_migration, which fills in bin/os/cpu) requests, so a wrong name there becomes a 404 or a different package.
  • consolidate_and_append_entry merges entries that describe the same package at the same version into one (yarn can list the same package under several key lines); it needs the package's real name to tell two aliases of the same name apart.

[review] gate passed · iteration 5 · 2 files touched

fails on main (without fix)
ASAN without fix: 3 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/cli/install/migration/yarn-lock-migration.test.ts
bun test v1.4.0 (1072523b5)

test/cli/install/migration/yarn-lock-migration.test.ts:
(pass) yarn.lock migration basic > simple yarn.lock migration produces correct bun.lock [322.30ms]
(pass) yarn.lock migration basic > yarn.lock with packages containing long build tags [2245.55ms]
(pass) yarn.lock migration basic > yarn.lock with extremely long build tags (regression test) [347.58ms]
(pass) yarn.lock migration basic > complex yarn.lock with multiple dependencies and versions [3083.35ms]
(pass) yarn.lock migration basic > yarn.lock with npm aliases [1198.38ms]
(pass) yarn.lock migration basic > yarn.lock with npm aliases of a scoped package keep the scope [230.77ms]
961 |   integrity ${stringWidthIntegrity}
962 | `,
963 |     });
964 | 
965 |     const { packages, requestedManifests } = await migrateYarnLock(tmpDir);
966 |     expect(packages).toStrictEqual({
                           ^
error: expect(received).toStrictEqual(expected)

  {
    "bare-alias": [
      "@corp/bare@2.0.0
... (truncated)

release without fix: 6 FAILED
bun test v1.4.0-canary.1 (eabb96de7)

test/cli/install/migration/yarn-lock-migration.test.ts:
(pass) yarn.lock migration basic > simple yarn.lock migration produces correct bun.lock [53.61ms]
(pass) yarn.lock migration basic > yarn.lock with packages containing long build tags [328.74ms]
(pass) yarn.lock migration basic > yarn.lock with extremely long build tags (regression test) [43.50ms]
(pass) yarn.lock migration basic > complex yarn.lock with multiple dependencies and versions [376.54ms]
(pass) yarn.lock migration basic > yarn.lock with npm aliases [144.13ms]
890 |     types-alias "npm:@types/node@20.11.5"
891 | `,
892 |     });
893 | 
894 |     const { packages, requestedManifests } = await migrateYarnLock(tmpDir);
895 |     expect(packages).toStrictEqual({
                           ^
error: expect(received).toStrictEqual(expected)

  {
    "@aliased/node-types": [
-     "@types/node@20.11.5",
+     "node@20.11.5",
      "",
      {},
      "sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==",
    ],
    "@types/node": [
      "@types/node@18.19.0",
      "",
      {},
      "sha512-AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBA
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/cli/install/migration/yarn-lock-migration.test.ts
bun test v1.4.0 (1072523b5)

test/cli/install/migration/yarn-lock-migration.test.ts:
(pass) yarn.lock migration basic > simple yarn.lock migration produces correct bun.lock [325.78ms]
(pass) yarn.lock migration basic > yarn.lock with packages containing long build tags [1502.42ms]
(pass) yarn.lock migration basic > yarn.lock with extremely long build tags (regression test) [271.51ms]
(pass) yarn.lock migration basic > complex yarn.lock with multiple dependencies and versions [1986.06ms]
(pass) yarn.lock migration basic > yarn.lock with npm aliases [866.61ms]
(pass) yarn.lock migration basic > yarn.lock with npm aliases of a scoped package keep the scope [170.51ms]
(pass) yarn.lock migration basic > yarn.lock npm aliases are named after the alias spec, not the tarball URL [151.23ms]
(pass) yarn.lock migration basic > yarn.lock aliases of one name and version to different packages stay separate [142.83ms]
(pass) yarn.lock migration basic > yarn.lock git dependency keyed by a package 
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     1072523b5a
  features     baseline

22 deps, 123 codegen, 1176 objects in 708ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1238] install /workspace/bun
bun install v1.4.0-canary.1 (eabb96de7)

Checked 107 installs across 153 packages (no changes) [5.00ms]
[2/1238] gen ErrorCode+*.h
[3/1238] gen bindgenv2
[4/1238] install /workspace/bun/packages/bun-error
bun install v1.4.0-canary.1 (eabb96de7)

Checked 1 install across 2 packages (no changes) [2.00ms]
[5/1238] install /workspace/bun/src/node-fallbacks
bun install v1.4.0-canary.1 (eabb96de7)

Checked 129 installs across 147 packages (no changes) [8.00ms]
[6/1238] gen .bind.ts → GeneratedBindings.cpp
[7/1238] fetch zlib
[zlib] up to date
[8/1238] fetch tinycc
[tinycc] up to date
[9/1237] gen ProcessBindingConstants.lut.h
Generating /workspace/bun/build/release/codegen/ProcessBindingConstants.lut.h from /workspace/bun/src/jsc/bindings/ProcessBindingConstants.cpp
[10/1237] gen JSBuffer.lut.h
Generating /workspace/bun/bui
... (truncated)
diff hotspot
src/install/yarn.rs                                | 156 ++++++--------
 .../install/migration/yarn-lock-migration.test.ts  | 239 ++++++++++++++++++---
 2 files changed, 269 insertions(+), 126 deletions(-)

gate history · 1 passed · 0 rejected · iteration 5

evidence per changed file
file                                                    reads  edits  tests
src/install/yarn.rs                                        17     23      0
test/cli/install/migration/yarn-lock-migration.test.ts     10     10      0

@coderabbitai

coderabbitai Bot commented Aug 15, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 10 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 40b0554a-ae52-4066-a956-0ea3a8a70c81

📥 Commits

Reviewing files that changed from the base of the PR and between b04b530 and 272e010.

📒 Files selected for processing (2)
  • src/install/yarn.rs
  • test/cli/install/migration/yarn-lock-migration.test.ts

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8803a1b7-6a00-4d60-9caa-998517960cd8

📥 Commits

Reviewing files that changed from the base of the PR and between 88a6398 and b04b530.

📒 Files selected for processing (2)
  • src/install/yarn.rs
  • test/cli/install/migration/yarn-lock-migration.test.ts

Walkthrough

Yarn lock migration now normalizes package names across npm aliases and direct URLs. It updates entry consolidation, package identity selection, and alias indexing. Migration tests cover registry, GitHub, tarball, and distinct-resolution alias cases.

Changes

Yarn alias migration

Layer / File(s) Summary
Entry name normalization
src/install/yarn.rs
Entries store normalized package names. npm alias parsing preserves target names, supports scoped names, and defaults missing ranges to *.
Migration identity and alias indexing
src/install/yarn.rs
Entry consolidation, package creation, Git repository fallback, and alias registration use normalized names and direct URL detection.
Migration regression coverage
test/cli/install/migration/yarn-lock-migration.test.ts
Shared migration setup supports tests for registry, GitHub, tarball URL, and distinct-resolution aliases.

Possibly related PRs

  • oven-sh/bun#38190: Both changes preserve npm alias targets in different install paths.
  • oven-sh/bun#38224: Both changes improve npm alias parsing and target-name preservation.
  • oven-sh/bun#38333: Both changes normalize aliases and non-registry dependency specifications during migration.

Suggested reviewers: jarred-sumner

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the main change: naming Yarn npm alias packages from the alias spec instead of the tarball URL.
Description check ✅ Passed The description explains the problem, fix, scope, affected cases, and verification results, although it does not use the template headings.

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: reproduced all three shapes with bun pm migrate -f on the released build and on main (GitHub Packages alias recorded as gh-alias@1.0.0, /-/-after-host alias recorded as registry.npmjs.org@1.0.0, two same@npm:... entries merged into one package). The three new cases in test/cli/install/migration/yarn-lock-migration.test.ts fail without the src/install/yarn.rs change (the git-vs-alias one against this PR's first revision, which review caught and 7519f61 fixed) and pass with it; the other 18 tests in that file (14 snapshots) are unchanged.

CI: build 98671 (current head, rebased on main) has no failing tests; yarn-lock-migration.test.ts passes on every lane, including the darwin lane. The entries it lists are retries that passed. The earlier builds' red tests (test/bake/deinitialization.test.ts on Windows, and a batch of leak tests timing out on the ASAN shards in build 98600) are unrelated to this change and were reported to main-break triage.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this PR and didn't find any bugs. Because it redesigns how the yarn migrator derives package names (which drives what bun install downloads) and the description flags overlap with three in-flight PRs (#38795, #38803, #37244), a human look would still be worthwhile.

What was reviewed

  • parse_npm_alias scope handling — verified npm:@scope/pkg@range now splits after the scope; traced both callers (process_deps, the version parse branch) to confirm the returned .version change is safe.
  • consolidate_and_append_entry keying on entry.name — checked it separates same-alias/different-target entries and does not over-merge non-alias entries relative to the old first-spec-name key.
  • The two naming passes and the alias-index loop — confirmed the entry.name / git_repo_name / has_direct_url_spec reshaping is behavior-preserving for plain, git, and direct-URL entries.
Extended reasoning...

Overview

The PR replaces the tarball-URL heuristic (get_package_name_from_resolved_url) with a new Entry.name field derived from the yarn key line's alias@npm:<name>@<range> spec. parse_npm_alias gains a name return and now skips a leading scope @ before splitting. Consolidation keys on (entry.name, version); both package-id naming passes and the alias-index loop switch to entry.name. The two hand-rolled "is direct URL" scans become Entry::has_direct_url_spec. Two new tests assert the full bun.lock packages object plus, via a loopback registry, which manifest names the post-migration pass fetches; the existing scoped-alias test is refactored onto the shared migrateYarnLock helper.

Security risks

None identified. Input is a local yarn.lock and package.json; the new parsing operates on borrowed byte slices with bounds-checked helpers (strip_prefix, split_at, index_of_char_usize) and no allocation or unsafe. The change does not touch integrity verification, network, or filesystem paths.

Level of scrutiny

Medium-high. The migrator's package name is what bun install later downloads and what fetch_necessary_package_metadata_after_yarn_or_pnpm_migration requests, so a wrong name silently installs the wrong package. This is a deliberate redesign (spec-derived vs URL-derived) rather than a mechanical fix, and I traced each of the four rewritten call sites against the old branching to confirm non-alias entries name the same as before. I also spot-checked npm_alias_target against @my/alias@npm:@scope/pkg@1.0.0, foo@npm:npm@1.0.0, and foo@npm: (empty target → falls back to first-spec name).

Other factors

  • The PR description explicitly notes small conflicts with #38795, #38803 (which edits the deleted function and pins a test row this PR changes) and #37244 (which keys a consolidation map on the spec name). Coordinating merge order is a human decision.
  • Test coverage is strong: toStrictEqual on the full packages object plus registry-fetch verification, and the PR states the 14 existing snapshots (including yarn-cli-repo and yarn-stuff) are unchanged. The extracted migrateYarnLock helper follows harness conventions (drains stdout/stderr concurrently, await using, port: 0, per-test cache dir).
  • No prior review comments from me on this PR.

@robobun

robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator Author

Update: #38803 no longer touches get_package_name_from_resolved_url or the alias call sites (it is back to a separate helper used only by the name_to_use block, ef0d36c), so deleting the function here is fine and git merge-tree shows the two branches merge cleanly in either order. Nothing to coordinate anymore.

Comment thread src/install/yarn.rs
Comment thread src/install/yarn.rs Outdated
Comment thread src/install/yarn.rs Outdated
Comment thread src/install/yarn.rs Outdated
Comment thread src/install/yarn.rs Outdated
Comment thread src/install/yarn.rs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The git-vs-alias regression I flagged earlier is fixed — dedupe_name() now keys git entries by git_repo_name (mirroring how the old package-id pass identified them), and the new "git dependency keyed by a package name stays separate from an alias of it" test pins it. No further issues found this run.

Because this reworks the identity used for both consolidation and the package-id pass in install-correctness code (a wrong merge silently installs a different package), and because it needs coordination with #38803/#37244 on the deleted get_package_name_from_resolved_url, a human look is still worthwhile.

What was reviewed

  • dedupe_name() runs after git_repo_name is populated (consolidation happens on the fully-parsed entry) and its !has_direct_url_spec() guard matches the old package-id pass ordering.
  • parse_npm_alias scope-@ handling and the range-less npm:@corp/bare case.
  • has_direct_url_spec() correctly excludes @git+https:// specs; the doc-comment length flags from comment-cop are addressed in the latest commits.
Extended reasoning...

Overview

Follow-up to #38806 that stops the yarn v1 migrator from naming npm: alias packages via a tarball-URL heuristic and instead reads the target name straight from the alias spec. Entry gains a name field computed at parse time, parse_npm_alias is fixed to handle scoped targets and range-less specs, and consolidation + the package-id pass now share one identity function dedupe_name(). get_package_name_from_resolved_url is deleted (no callers left in this PR's tree). Three new tests cover the GitHub Packages layout, /-/-after-host, same-alias-name-different-packages, and the git-vs-alias case my earlier review raised; the existing scoped-alias test is refactored onto a shared migrateYarnLock helper.

Prior review addressed

My earlier inline comment (now resolved) flagged that keying consolidation on entry.name alone would merge a git fork (lodash@git+…/lodash-fork.git → name lodash) with an npm alias targeting lodash at the same version. The revision introduces dedupe_name(), which returns git_repo_name for git entries (guarded by !has_direct_url_spec() to preserve the old package-id-pass ordering) and entry.name otherwise; both consolidation and the package-id pass now use it. I traced the parse loop to confirm git_repo_name is set before consolidate_and_append_entry runs (it's called on the previous, fully-populated entry when the next header is seen). The new git-vs-alias test pins exactly the scenario I described and the PR description confirms it failed on the first revision and passes now. The one remaining gap — non-GitHub git hosts without a parsable repo name still fall through to entry.name — is pre-existing (the old package-id pass had the same behavior) and the description says it's tracked separately.

Security risks

This is package-install correctness: a wrong dedupe_name merge means bun.lock records the wrong package under a dependency, and a later install fetches it silently. That's the class this PR fixes for the alias-vs-alias case, and the identity function used for merging is the load-bearing invariant. No new untrusted-input parsing surface beyond what the migrator already had; parse_npm_alias's new slicing is bounds-safe (strip_prefix, split_at on an index found within the slice).

Level of scrutiny

Medium-high. The diff is compact and well-reasoned, existing snapshots (yarn-cli-repo, yarn-stuff) are unchanged, and the tests assert full packages objects plus the manifest names actually requested. But identity/consolidation changes in the installer have subtle interaction with every other resolution kind (git, file:, direct-URL, workspace), one such interaction already surfaced in review, and the PR description itself flags a one-hunk merge conflict with two in-flight PRs (#38803 deletes-vs-keeps get_package_name_from_resolved_url, #37244 keys its consolidation map differently). That coordination is a human call.

Other factors

The comment-cop bot flagged several long doc comments; the two follow-up commits (shorten the yarn Entry doc comments, one-line doc comments on the yarn Entry helpers) reduced them to single lines, which is what the current diff shows. robobun independently reproduced all three failure shapes on main and confirmed the new tests fail without / pass with the src change.

@robobun

robobun commented Aug 15, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 3:05 PM PT - Aug 15th, 2026

❌ @robobun, your commit 272e010 has some failures in Build #98671 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 38948

That installs a local version of the PR into your bun-38948 executable, so you can run:

bun-38948 --bun

… the tarball URL

An alias entry in a yarn v1 lockfile is keyed alias@npm:<name>@<range>, so
the key line carries the real package name. The migrator instead guessed it
from the resolved tarball URL and fell back to the alias name, which named
the package wrongly for registries without the <name>/-/ tarball layout and
for URLs with /-/ directly after the host, and it consolidated entries by
alias name and version, merging aliases of the same name that point at
different packages into one package.

Each entry now records the package name taken from its alias spec (or the
spec name when the key line has no alias), and consolidation, both naming
passes and the alias index registration use it. The URL lookup for alias
names is deleted; tarball entries are still named by the name_to_use block.
…ge pass uses

Keying consolidation on the package name alone merged a git entry keyed
under a package's name (a fork keeping upstream's name and version) with an
npm: alias of that package. The package-id pass identifies git entries by
their repository; consolidation now uses the same Entry::dedupe_name, which
keeps the two apart as before.
@robobun
robobun force-pushed the farm/4381ba69/yarn-alias-name-from-spec branch from a4abd56 to 1072523 Compare August 15, 2026 19:15

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed this PR again after the dedupe_name() fix in 7519f61 and found no new issues — the git-vs-alias merge I flagged earlier is now keyed by repository and pinned by the new test, and I verified the non-GitHub git-host case the description calls out was already collapsed by the package-id pass on main (so consolidating it one step earlier is not a new regression). Because this changes the identity that consolidation and the package-id pass share, and it interacts with #38803 and #37244, a human look would still be worthwhile.

Extended reasoning...

Overview

This PR changes how the yarn.lock migrator names npm: alias packages: instead of parsing the package name out of the resolved tarball URL (which fails for GitHub Packages and other registries without the <name>/-/<file>.tgz layout), it reads the target name directly from the alias@npm:<name>@<range> spec on the entry's key line. Entry gains a name field computed at parse time, parse_npm_alias is fixed to skip a scoped name's leading @ when locating the range separator, consolidation and the package-id pass now share Entry::dedupe_name() (repository for git entries, package name otherwise), and get_package_name_from_resolved_url is deleted. Three new hermetic tests cover the alias-spec naming, same-alias-name-different-target consolidation, and the git-vs-alias separation that my first-pass review flagged.

Security risks

None identified. The change only affects how package names are derived from a yarn v1 lockfile the user already trusts; no new external inputs are parsed and the loopback-registry tests confirm the migrator requests the intended package names.

Level of scrutiny

Medium-high. Lockfile migration is a correctness-critical path — a wrong consolidation key silently installs a different package than yarn resolved. The first revision of this PR introduced exactly that class of regression (git fork keyed under a package name merged with an npm: alias of the same name/version), which was caught in review and fixed with dedupe_name() plus a pinning test. I re-traced the fix: git_repo_name is set from the resolved line before consolidate_and_append_entry runs on that entry, so the git entry is correctly identified as lodash-fork rather than lodash at consolidation time. I also traced the non-GitHub-host edge the description mentions and confirmed the old package-id pass already gave both entries the same id, so the observable output is unchanged.

Other factors

The fix is well-tested (three new test.concurrent cases asserting the full packages object and the exact manifest names fetched, plus 14 unchanged snapshots including yarn-cli-repo and yarn-stuff), the comment-cop feedback about long doc comments has been addressed, and CI is green apart from an unrelated Windows bake failure. Deferring rather than approving because the identity change is subtle enough to have needed one round of correction already, and the description notes one-hunk conflicts with #38803 and #37244 that whoever merges second will need to resolve.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The git-vs-alias merge I flagged is fixed by dedupe_name() (7519f61) and pinned by the new test; I have no further findings. Because this reworks the identity that migration keys packages on — where a wrong merge silently installs a different package — and interacts with #38795/#38803/#37244, a maintainer sign-off is still worthwhile.

What was reviewed:

  • dedupe_name() now matches the package-id pass's git-entry identity, so the fork/alias case stays two packages; the non-GitHub-git collision is confirmed pre-existing (old package-id pass already keyed both on lodash).
  • parse_npm_alias scope handling and the range-less npm:@corp/bare case.
  • The alias-registration loop now runs without a resolved URL; for non-alias entries get_name_from_spec(specs[0]) == entry.name so it's a no-op there.
  • New tests assert the full packages object plus the manifest names actually fetched, and use test.concurrent with a per-test loopback registry.
Extended reasoning...

Overview

Changes how the yarn v1 → bun.lock migrator names npm: alias packages: instead of parsing the tarball URL, it reads the target name from the alias@npm:<name>@<range> key spec. Adds an Entry::name field set at parse time, a dedupe_name() helper so consolidation and the package-id pass key on the same identity (repository for git entries, package name otherwise), and deletes the now-unused URL-name extractor. ~156 lines changed in src/install/yarn.rs (net −30), ~+200 lines of tests.

Security risks

None. This is offline lockfile-to-lockfile migration; no new parsing of untrusted network data, no path handling, no auth. The correctness risk (wrong package installed after a bad merge) is a supply-chain-adjacent concern but is what the change fixes, not introduces.

Level of scrutiny

Medium-high. Package-manager identity logic where a wrong consolidation silently resolves a dependency to a different package. My first-pass review found exactly that class of regression (git fork merged with an npm alias of the same name/version); it was fixed with dedupe_name() and a regression test. The remaining non-GitHub-git-host edge case is verified as pre-existing behavior (the old package-id pass already collapsed those). The fix is well-reasoned and the tests are strong (full packages object + observed manifest fetches), but the change is not mechanical and the PR notes one-hunk conflicts with three other in-flight PRs touching adjacent code.

Other factors

  • All prior review threads (mine and comment-cop's) are resolved; comments were shortened to one line each.
  • CI is green on all lanes per robobun; the one Windows failure is unrelated and failing on main.
  • 21 tests in the file pass including 14 unchanged snapshots (yarn-cli-repo, yarn-stuff).
  • Given the first revision had a real regression and the identity change touches every migrated entry, I'm deferring rather than auto-approving so a maintainer can confirm the dedupe_name approach and the coordination with #38803/#37244.

Jarred-Sumner added a commit that referenced this pull request Aug 17, 2026
… expectations for combined behaviour in catalogs, pnpm migration and redacted logs
Jarred-Sumner pushed a commit that referenced this pull request Sep 18, 2026
…ht after the host (#43189)

Replaces #38803: the same commit on current main, plus the review
changes.

### Problem
- `bun install` and `bun pm migrate` abort on a `yarn.lock` entry such
as `"x@https://evil.example/-/registry.npmjs.org/x.tgz"`: `panic: slice
index starts at 42 but ends at 20`, top frame
`bun_install::yarn::migrate_yarn_lockfile`. A `/-/` directly after the
registry host panics too (`27..26`). The tarball URL of the real package
`-` has that shape.
- Cause: the `name_to_use` block (`src/install/yarn.rs:941` on main)
searches the URL for `/-/` and for `registry.` independently, then
slices between the two indexes.

### Fix
- `Entry::get_package_name_from_default_registry_url` parses from left
to right: the scheme, then `registry.npmjs.org/` or
`registry.yarnpkg.com/`, then one name segment (two for `@scope/name`),
then `/-/`. `strings::is_npm_package_name` must accept the name. Every
other URL keeps the spec name, as before.
- Each slice bound comes from one forward scan, so the bounds are in
order. Real registry URLs give the same names as before.
- From the review: `@scope/-` now keeps its full name. The first `/-/`
of that URL is inside the name.
- Verified: the new case in
`test/cli/install/migration/yarn-lock-migration.test.ts` fails on canary
b52d513 with the panic and passes here. Also ran `migrate.test.ts`,
`lockfile-only.test.ts`, and the yarn cases of
`nested-overrides.test.ts`.

### Background
- A yarn v1 entry has spec keys (`name@range`, or `name@https://...` for
a URL dependency) and a `resolved` tarball URL. The migrator cannot
download the tarball to learn the package name.
- A registry tarball URL is `<registry>/<name>/-/<file>.tgz`. On the two
default registries `<name>` is the real package name, so the migrator
uses it.
- A `bun.lock` package entry starts with `"<name>@<version or url>"`.

<details><summary>Notes</summary>

No issue reports this crash. It was found by a read of the code. Every
shape except the `-` package needs a hand-edited `yarn.lock`, which `bun
install` must still survive.

Each URL as the only dependency of a project, `bun pm migrate -f`, name
recorded in `bun.lock`:

| `resolved` URL | canary b52d513 | this PR |
|---|---|---|
| `https://evil.example/-/registry.npmjs.org/x.tgz` | panic `42..20` |
spec name |
| `https://registry.npmjs.org/-/y.tgz` | panic `27..26` | spec name |
| `https://registry.npmjs.org/-/-/--0.0.1.tgz` | panic `27..26` | `-` |
| `https://registry.npmjs.org//-/z.tgz` | empty name | spec name |
|
`https://registry.mirror.example/registry.npmjs.org/other/-/other-1.0.0.tgz`
| `registry.npmjs.org/other` | spec name |
| `https://registry.npmjs.org/@scope/-/-/--0.0.1.tgz` | `@scope` |
`@scope/-` |
| `https://registry.npmjs.org/@scope/-/y.tgz` | `@scope` | spec name |
| `https://registry.npmjs.org/@scope//-/z.tgz` | `@scope/` | spec name |
| `https://registry.npmjs.org/a/b/-/b-1.0.0.tgz` | `a/b` | spec name |
| `https://registry.npmjs.org/../-/x-1.0.0.tgz` | `..` | spec name |
| `https://registry.npmjs.org/w.tgz` | spec name | spec name |
| `https://registry.npmjs.org/@scope/real/-/real-1.0.0.tgz` |
`@scope/real` | `@scope/real` |
| `http://registry.npmjs.org/real-a/-/real-a-1.0.0.tgz` | `real-a` |
`real-a` |
| `https://registry.yarnpkg.com/real-b/-/real-b-1.0.0.tgz` | `real-b` |
`real-b` |
| `http://registry.yarnpkg.com/real-c/-/real-c-1.0.0.tgz` | `real-c` |
`real-c` |

The test runs one `bun pm migrate` over all of these rows. It asserts
the full `packages` object of `bun.lock`, and that the loopback registry
received no request.

Sites not in this PR:
- `Entry::get_package_name_from_resolved_url`, the parser for `npm:`
alias entries. It does not panic. For a URL with `/-/` directly after
the host it returns the host as the name. #38948 names an alias from its
spec and deletes that parser, so this PR does not change it. The two PRs
merge cleanly in either order (`git merge-tree`).
- The `is_default_registry` check in the resolution block of
`migrate_yarn_lockfile`. It decides how the URL is stored and accepts
`https://` only. A shared predicate would change one of the two
behaviors.
- I searched `yarn.rs` for other slices between two independent
searches. This block was the only one.

Relation to #41908: it changes the condition around this block to
`is_tarball_dep` and keeps the index arithmetic, so the two PRs conflict
in this one block. The resolution is the `is_tarball_dep` condition
around the one helper call. No test row depends on that PR.

`strings::is_npm_package_name` does not accept `~ ' ! ( ) *` in an
unscoped name. npm accepted such names in the past. A URL dependency on
such a package keeps the spec name, as a tarball on any other host does.

Test runs with the debug build: `yarn-lock-migration.test.ts` 19 pass,
14 snapshots unchanged. `migrate.test.ts` and `lockfile-only.test.ts`
148 pass, 1 todo. `nested-overrides.test.ts -t yarn` 6 pass.
`test/internal/source-lints/byte-search.test.ts` 3 pass.

The earlier revisions and their review are in #38803.

</details>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant