feat(install): seed deno.lock from bun.lock#35394
Merged
Merged
Conversation
Seeds `deno.lock` from a sibling `bun.lock` on the first local `deno
install`, after `package-lock.json` and `pnpm-lock.yaml`. As with the
other importers, seeding only kicks in for the local `deno install`
subcommand and only when no `deno.lock` already exists. When several
lockfiles coexist the order is `package-lock.json`, `pnpm-lock.yaml`,
`bun.lock`.
The new importer lives in `libs/resolver/bun_lockfile_import.rs` and
parses to the same deno.lock v5 JSON intermediate. `bun.lock` is the
text lockfile (JSONC) bun writes since v1.1.39, so it is parsed with the
`jsonc-parser` the resolver already depends on. The legacy binary
`bun.lockb` has no textual form we can read without bun itself and is
not handled.
Every resolved package lives flat under `packages`, where each entry is
a tuple `["name@version", registry, { info }, integrity]`; the npm
section is built from those. Per-workspace dependency requirements live
under `workspaces` (root keyed by the empty string, members keyed by
their path), so unlike yarn classic, bun records member deps explicitly.
The importer iterates every workspace, mapping the root to
`workspace.packageJson` and each member to `workspace.members.<path>`,
so a monorepo seeds completely rather than dropping member deps.
`workspace:`, `file:`, `link:`, git, http, `npm:` alias, and `catalog`
reqs are skipped from specifiers.
Towards #25815
Towards #26521
Self-review found that transitive dependencies were resolved by name against the hoisted set only, so a dependency pinned to a non-hoisted version (which bun stores nested under `<requester-key>/<dep>`) was recorded against the hoisted version instead. `deno install` trusts the seeded npm graph, so the wrong edge persisted into the final lockfile (e.g. [email protected] depending on is-number@^6 ended up pointing at the hoisted is-number@7, and the 6.x copy was dropped entirely). Anchor dependency resolution on the requesting package's key: prefer the entry nested directly under it before falling back to the hoisted version. Adds a unit test for the conflict case; verified end-to-end that the resulting deno.lock now lists [email protected] for is-odd.
Document the flat-npm-graph assumption behind keeping the first `name@version` entry seen (the same version can appear under both a hoisted and a nested key, but deno.lock holds one resolution per version), and rename the workspace-specifier helper's `resolved` parameter to `hoisted` to reflect that top-level specifiers intentionally track the hoisted version.
…un-lockfile # Conflicts: # libs/resolver/lockfile.rs
bartlomieju
enabled auto-merge (squash)
June 20, 2026 19:37
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Seeds
deno.lockfrom a siblingbun.lockon the first localdeno install, continuing the per-format lockfile seeding work afterpackage-lock.json(#35330) andpnpm-lock.yaml(#35346). As with theother importers, seeding only kicks in for the local
deno installsubcommand and only when no
deno.lockalready exists. When severallockfiles coexist the order is
package-lock.json,pnpm-lock.yaml,bun.lock.The new importer lives in
libs/resolver/bun_lockfile_import.rsandparses to the same deno.lock v5 JSON intermediate used by the others.
bun.lockis the text lockfile (JSONC) that bun writes since v1.1.39,so it is parsed with the
jsonc-parserthe resolver already depends on,no new crate. The legacy binary
bun.lockbhas no textual form we canread without bun itself, so it is not handled.
Every resolved package lives flat under
packages, where each entry isa tuple
["name@version", registry, { info }, integrity]; thenpmsection is built from those, resolving each package's dependency
requirements to concrete versions via the hoisted set. Per-workspace
dependency requirements live under
workspaces(the root keyed by theempty string, members keyed by their path), so unlike yarn classic, bun
records workspace member deps explicitly. The importer iterates every
workspace, mapping the root to
workspace.packageJsonand each memberto
workspace.members.<path>, so a monorepo seeds completely ratherthan dropping member deps (the same class of issue fixed for pnpm in
#35376).
workspace:,file:,link:, git, http,npm:alias, andcatalogreqs are skipped from specifiers and left for resolution.Covered by unit tests in the new module and two spec tests
(
import_from_bun_lock,import_from_bun_lock_workspace), the latterverifying a root + member monorepo seeds both packages. Verified
end-to-end against a real bun-generated lockfile as well.
Towards #25815
Towards #26521