Skip to content

feat(install): seed deno.lock from bun.lock#35394

Merged
bartlomieju merged 5 commits into
mainfrom
feat/install-import-bun-lockfile
Jun 20, 2026
Merged

feat(install): seed deno.lock from bun.lock#35394
bartlomieju merged 5 commits into
mainfrom
feat/install-import-bun-lockfile

Conversation

@bartlomieju

Copy link
Copy Markdown
Member

Seeds deno.lock from a sibling bun.lock on the first local deno install, continuing the per-format lockfile seeding work after
package-lock.json (#35330) and pnpm-lock.yaml (#35346). 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 used by the others.
bun.lock is the text lockfile (JSONC) that bun writes since v1.1.39,
so it is parsed with the jsonc-parser the resolver already depends on,
no new crate. The legacy binary bun.lockb has no textual form we can
read without bun itself, so it 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, resolving each package's dependency
requirements to concrete versions via the hoisted set. Per-workspace
dependency requirements live under workspaces (the root keyed by the
empty 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.packageJson and each member
to workspace.members.<path>, so a monorepo seeds completely rather
than dropping member deps (the same class of issue fixed for pnpm in
#35376). workspace:, file:, link:, git, http, npm: alias, and
catalog reqs 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 latter
verifying a root + member monorepo seeds both packages. Verified
end-to-end against a real bun-generated lockfile as well.

Towards #25815
Towards #26521

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
bartlomieju enabled auto-merge (squash) June 20, 2026 19:37
@bartlomieju
bartlomieju merged commit 1a62c3c into main Jun 20, 2026
136 checks passed
@bartlomieju
bartlomieju deleted the feat/install-import-bun-lockfile branch June 20, 2026 20:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant