install: fix default-trusted lifecycle scripts being blocked after yarn.lock migration
Found while looking at lifecycle scripts after lockfile migration (the trustedDependencies side of that is #38773); this part is about the URLs the yarn.lock migrator records and is independent of it. Touches the same name_to_use block as #38803 (a crash fix in the name extraction); see "Relation to #38803" below.
Problem
- After migrating a
yarn.lockwhose entries resolve to any registry other thanregistry.yarnpkg.com/registry.npmjs.org(a private registry, or the test Verdaccio), packages on bun's default trusted list (electron,esbuild, ...) have their lifecycle scripts blocked on the install that follows. The samebun installwithout theyarn.lockruns them. - yarn v1 writes tarballs as
resolved "<url>#<sha1>". The migrator (src/install/yarn.rs, resolution block) stored that string verbatim as the package's npm tarball URL, sobun.lockends up with
"electron": ["electron@1.0.0", "http://localhost:PORT/electron/-/electron-1.0.0.tgz#f1b8bc2c...", {}, "sha512-..."].
Lockfile::has_trusted_dependency(src/install/lockfile.rs:3105) only grants default-list trust when the stored URL equals the canonical<registry>/<name>/-/<name>-<version>.tgz, so the suffix fails the comparison. - Entries whose URL has no suffix (what
bun install --yarnitself writes, and what registries without a shasum produce) took a different wrong turn: theresolved.ends_with(".tgz")check turned them into remote tarball packages ("electron": ["electron@http://.../electron-1.0.0.tgz", ...]), which are never default-trusted and also skip the post-migrationbin/os/cpumanifest fill-in.package-lock.jsonandpnpm-lock.yamlmigration already store the bare canonical URL for both shapes. - The same suffix on a GitHub archive download (
resolved "https://github.com/o/r/archive/v1.tar.gz#<sha1>") was read as a git commit, becauseEntry::is_git_dependencymatched everyhttps://github.com/URL. The entry migrated to["r/archive/v1.tar.gz@github:o/r/archive/v1.tar.gz#<sha1 prefix>", {}, ""], which the nextbun install --frozen-lockfilerejects as an invalid git tag. - The canonical-URL check landed in #31339 and is not in a release yet (
LATESTis still 1.3.14). Everybun.lockthe yarn migrator has written so far on a non-default registry carries the suffix, and a plainbun installkeeps the recorded URL, so without a reader-side fix the first release with #31339 would start blocking those projects' default-trusted scripts even though this PR fixes new migrations.
Fix
Entry::url_without_hashstrips the suffix; the migrator computes the stripped URL once per entry and uses it both to name the package and to build the resolution, so the npm URL and the remote tarball URL of direct URL dependencies are both stored without it.- A non-URL dependency with a semver
versionis always stored as an npm package whose tarball URL isresolved(empty for the two default registries, as before). The.tgzto remote-tarball fallback now only applies to entries whoseversionis not semver, the one case where it was not misclassifying registry packages. Direct URL dependencies (foo@https://...specs) still become remote tarballs. is_git_dependencyclassifieshttps://github.com/...with the dependency parser's existingis_github_tarball_pathrule (applied to the URL without the suffix), so archive downloads become remote tarballs like any other URL dependency and repositories are still git.has_trusted_dependencycompares the stored URL without its fragment. This is what keeps already-migrated lockfiles working once #31339 ships. It is exactly as strict as before: the request path bun sends isURL.pathname, whichbun_urlcuts at#(src/url/lib.rs:649-668, used bybuild_requestinsrc/http/lib.rs), so<canonical>#anythingdownloads the canonical tarball and nothing else; a URL whose path differs still fails, fragment or not.- The name block now only reads a package name out of the URL for direct URL dependencies; registry entries take the spec's name, which is what every suffixed entry already did. With the reclassification above, a default-registry entry's name is what the download URL is built from, so it should come from the spec rather than from however the URL happened to spell it.
- Verified (each new test fails on the build without
src/changes and passes with them, except the two guards noted):test/cli/install/bun-install-lifecycle-scripts.test.ts,describe("default trusted dependencies after yarn.lock migration"):electron@1.0.0viayarn.lockwith and without the suffix runs its preinstall and records the canonical URL; ayarn.lockpointingelectronatall-lifecycle-scripts's tarball keeps the scripts blocked; a hand-writtenbun.lockin the shape the migrator produced until now (...electron-1.0.0.tgz#<sha1>) runs the scripts, and the same lockfile pointing at the other package's tarball (also suffixed) stays blocked (guard, passes before and after). The pre-existing "require the canonical registry tarball URL" test (wrong path, wrong origin) still passes.test/cli/install/migration/yarn-lock-migration.test.ts,describe("yarn.lock migration of registry tarball URLs"):bun pm migrateagainst a local 404 registry checks the migratedbun.lockfor a private registry (suffixed and suffix-less, with a dependency edge between them), the default registry without a suffix (plain andnpm:alias), a direct URL dependency on a registry host and on a GitHub archive, and a github.com repository URL still migrating as git (guard).- Existing snapshots: only two lines change, both losing a
#sha1suffix (onetimein yarn's own lockfile, anhttp://registry.npmjs.org/URL, and theremotedirect URL dependency inyarn-stuff). The other ~1000 entries of theyarn-cli-reposnapshot, including its git and codeload entries, are unchanged. - Also run: the whole
yarn-lock-migration.test.tsfile,nested-overrides.test.ts"yarn.lock resolutions paths become nested rules" (suffix-less local-registry URLs, migrate + install), the trust tests inbun-pm.test.tsandbun-install-registry.test.ts, and the full lifecycle scripts file (124 pass; the 3 failures neednodeon PATH and fail on main in this environment too).
Relation to #38803
- #38803 replaces the index arithmetic inside the
name_to_useblock (a panic on crafted URLs, found while working on this). This PR only changes that block's guard (is_direct_url_dep) and the URL it reads (resolved_url), so whichever lands second has a one-block conflict: the combined shape is #38803's extraction, gated onis_direct_url_dep, reading the stripped URL. #38803'spinnedrow (a semver entry resolved to a suffix-less tarball) becomes a registry package with this PR, as its description already notes. Landing #38803 first is simplest; this branch gets rebased either way.
Background
- Default trusted dependencies: when
package.jsonhas notrustedDependencies, bun runs lifecycle scripts only for packages on a built-in list (src/install/default-trusted-dependencies.txt). Because the grant is keyed by name,has_trusted_dependency(since #31339) additionally requires the package to be an npm package whose tarball URL is either empty (default registry) or exactly the canonical tarball URL on the configured registry, so a lockfile cannot obtain the grant by recording a different tarball under a trusted name. - npm resolution URL: an npm package in the lockfile carries the tarball URL the registry reported (
dist.tarball);bun.lockprints it as""when it is under the default registry and verbatim otherwise. A remote tarball resolution is a different package kind used for dependencies declared as a URL; it has no version and no registry, so it never qualifies for the default list. - yarn v1
resolvedfield:<tarball url>#<hex sha1 of the tarball>for registry tarballs, URL dependencies and GitHub archives alike; the suffix is omitted when the registry did not report a shasum. For git dependencies the part after#is the commit instead. bun's ownbun install --yarnprinter writes URLs without a suffix. - URL fragment: the part of a URL after
#. HTTP clients never transmit it; bun's URL parser puts it inhashand excludes it frompathname, which is what becomes the request path.
Earlier shape of this PR
The first revision only stripped the suffix in the migrator and reclassified suffix-less registry entries. Self-review turned up the GitHub archive case (same suffix, misread as a commit one layer up) and the population of lockfiles that were already migrated with the suffix, which the unreleased canonical-URL check would otherwise start blocking; both were folded in, the strip was hoisted so the name block sees the same URL as the resolution block, and the tests above were extended to cover them.
no test proof · iteration 4 · 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