types: add lockfileVersion 3 and the object form of overrides to BunLockFile - #38868
types: add lockfileVersion 3 and the object form of overrides to BunLockFile#38868robobun wants to merge 2 commits into
Conversation
…ockFile bun install writes "lockfileVersion": 3 and object-valued overrides entries for nested and version-scoped override rules. BunLockFile still declared lockfileVersion as 0 | 1 | 2 and overrides as Record<string, string>, so code that reads bun.lock through this type fails to compile on either. Widen lockfileVersion to 0 | 1 | 2 | 3, type overrides values as string | BunLockFileScopedOverrides (a one-level Record<string, string> whose "." key is the selected package itself), and document the versions. The object form is not tied to lockfileVersion 3 because the writer keeps a lockfile at version 1 in some cases while still writing the objects, and the parser accepts them at every version. The test spawns tsc like the existing Bun.mmap case so it also runs on debug builds, where the LanguageService-based fixture checks are skipped.
|
Warning Review limit reached
Next review available in: 5 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: ready for review.
|
There was a problem hiding this comment.
LGTM — types-only update bringing BunLockFile in line with what bun install now writes.
What was reviewed:
- Verified
lockfileVersion: 0 | 1 | 2 | 3matches theVersionenum insrc/install/lockfile/bun.lock.rs(CURRENT = V3). - Confirmed the
*/bun.lockmodule declaration inextensions.d.tsre-exportsBunLockFile, so the test'simport imported from "./bun.lock"is typed via the declaration (no file needed on disk). - Checked the
tsc()helper extraction preserves the existingBun.mmaptest's assertions (trim moved into the helper,exitCodecheck unchanged).
Extended reasoning...
Overview
This PR widens two fields on the BunLockFile type in packages/bun-types/bun.d.ts: lockfileVersion gains | 3 and overrides values gain | BunLockFileScopedOverrides (a new Record<string, string> alias). It adds JSDoc for both, and adds a test case in test/integration/bun-types/bun-types.test.ts that spawns tsc over a fixture asserting the new shapes type-check and that version 4 / two-level nesting are still rejected via @ts-expect-error. The existing Bun.mmap test's tsc-spawn setup is extracted into a shared tsc() helper.
Security risks
None. This is a pure .d.ts change plus a test; no runtime code is touched.
Level of scrutiny
Low. Type declarations catching up to runtime reality that already shipped in #38333. I verified against the source of truth: src/install/lockfile/bun.lock.rs defines Version::{V0,V1,V2,V3} with CURRENT = V3, and the PR description's account of Stringifier::version_to_write (object overrides can appear at versions below 3, so no discriminated union) matches the code. The new BunLockFileScopedOverrides = Record<string, string> is exactly as wide as what the writer emits and the parser accepts.
Other factors
- The test refactor is behavior-preserving: the mmap test's three assertions (
stderr,stdout,exitCode) are unchanged, with.trim()simply moved into the helper's return. - The new test pins the negative contract (
@ts-expect-erroronlockfileVersion: 4and on nested-object override values), so future accidental widening will fail CI. - The
await using procinside the extracted helper disposes at helper-scope exit, afterproc.exitedhas already resolved — no lifetime issue. - No prior reviews from me on this PR; no outstanding reviewer comments.
|
Updated 8:44 AM PT - Aug 15th, 2026
✅ @robobun, your commit de5c9f87c57d48bd529aedcb229583dfd7e746c9 passed in 🧪 To try this PR locally: bunx bun-pr 38868That installs a local version of the PR into your bun-38868 --bun |
Leaves the existing spawned-tsc block as it is and adds the new case as a sibling block, so this change stays additive in bun-types.test.ts.
There was a problem hiding this comment.
I reviewed this and didn't find any bugs — the type widening matches Version::V3 = CURRENT and the object-valued override shape in src/install/lockfile/bun.lock.rs, and the new test pins both the accepted shapes and the rejected ones with @ts-expect-error. Deferring only because packages/bun-types/ is CODEOWNER-gated.
Checked that extensions.d.ts's */bun.lock module declaration flows through to the fixture's import imported from "./bun.lock".
Checked that BunLockFileScopedOverrides = Record<string, string> matches the one-level nesting the writer emits and the parser accepts.
Noted: the PR description mentions extracting a shared tsc() helper, but the final commit keeps the two cases separate (which is fine — just a stale description line).
Extended reasoning...
Overview
Types-only change in packages/bun-types/bun.d.ts: widens BunLockFile.lockfileVersion from 0 | 1 | 2 to 0 | 1 | 2 | 3 and widens overrides values from string to string | BunLockFileScopedOverrides (a new Record<string, string> alias). Adds JSDoc for each version and the scoped-overrides shape. A new test case in test/integration/bun-types/bun-types.test.ts spawns tsc over a fixture that exercises the new shapes and pins the negative cases (two-level nesting, version 4) with @ts-expect-error.
Security risks
None. This is a .d.ts change with no runtime effect.
Level of scrutiny
Low on correctness — I verified against src/install/lockfile/bun.lock.rs that Version::V3 is CURRENT and that write_override_rules emits the object form described. The test follows the exact pattern of the existing Bun.mmap case in the same file (spawn tsc, drain both pipes, assert empty output then exit code). The @ts-expect-error assertions guard against over-widening.
Other factors
packages/bun-types/ is listed in .github/CODEOWNERS, so I'm deferring for the owner's sign-off rather than approving. The robobun CI comment flags a failure on an earlier commit (9aedc80); the head commit de5c9f8 was pushed after that and only reworks the test, so worth confirming CI is green on head before merge. The PR description mentions extracting a shared tsc() helper that the final diff does not contain — the second commit message ("add the BunLockFile tsc case without touching the Bun.mmap case") explains the helper was dropped, so the code is consistent even if the description is slightly stale.
|
The description now matches the final diff: the new case is a sibling of the existing Bun.mmap case, and the dropped helper is only mentioned under "Earlier revision". The bun.d.ts change is the same in both pushes; the TypeScript types check is green on de5c9f8 and the BuildKite build for it is in progress. |
Problem
bun installwrites"lockfileVersion": 3and object-valuedoverridesentries since install: pnpm parity — dedupe, prune, pm licenses, audit fix, add --filter/--catalog, nested overrides, transitive update, and workspace fixes #38333 (nested and version-scoped overrides), for example"one-dep": { "no-deps": "2.0.0" }and"no-deps@1": { ".": "1.1.0" }(pinned by the inline snapshots intest/cli/install/nested-overrides.test.ts).BunLockFileinpackages/bun-types/bun.d.tsstill declareslockfileVersion: 0 | 1 | 2(line 9811) andoverrides?: Record<string, string>(line 9816). install: bump default lockfileVersion to 2, gate stricter parse checks behind it #31539 added| 2when version 2 was introduced; install: pnpm parity — dedupe, prune, pm licenses, audit fix, add --filter/--catalog, nested overrides, transitive update, and workspace fixes #38333 did not update the type for version 3.BunLockFile, whether through a cast or throughimport lock from "./bun.lock"(typed byextensions.d.ts), fails to compile:lock.lockfileVersion === 3isTS2367: This comparison appears to be unintentional because the types '0 | 1 | 2' and '3' have no overlap, and each object value isTS2322: Type '{ ".": string; }' is not assignable to type 'string'.@types/bunthat ships with it. Types only, no runtime change.Fix
lockfileVersionbecomes0 | 1 | 2 | 3, with a JSDoc line per version. The versions are theVersionenum insrc/install/lockfile/bun.lock.rs(V3isVersion::CURRENT).overridesvalues becomestring | BunLockFileScopedOverrides, a newRecord<string, string>alias whose JSDoc explains thename/name@rangekeys and the"."entry. This is exactly what the writer emits (write_override_rulesinbun.lock.rs) and what the parser accepts (theoverridesloop inparse_into_binary_lockfile: a value is a string, or an object whose values are all strings), so the alias is as wide as the format and no wider. The object's values are plainstringlike the siblingRecord<string, string>fields, soObject.entrieson one keeps yielding strings.lockfileVersion === 3(no discriminated union):Stringifier::version_to_writekeeps a lockfile at version 1 when one of its packages fails the version 2 checks and still writes the override objects, and the parser reads the objects at every version. A union keyed on the version would reject lockfiles Bun itself writes.BunLockFilePackageArraytuples also lag the writer (a tarball entry may carry a trailing integrity string, a git or github entry may carry one as a fourth element), so a lockfile with such a dependency still needs the tuple changes in Document bun.lock format and complete BunLockFile tuple types #34204 to type-check as a whole. That PR rewrites the tuple union; this one only touches the two fields above and addsBunLockFileScopedOverrides, so the two do not overlap.test/integration/bun-types/bun-types.test.ts, new case "BunLockFile accepts lockfileVersion 3 and the object form of overrides". It spawnstscover a file that assigns the shapes above toBunLockFile, compareslockfileVersion === 3on both aBunLockFilevalue and a./bun.lockimport, narrows an override value to the object form, and pins with@ts-expect-errorthat two-level nesting and a version outside the union are still rejected. Without thebun.d.tschange it fails with 9 diagnostics (the two quoted above plusTS2305for the missing alias); with it the file type-checks cleanly.USE_SYSTEM_BUN=1 bun test test/integration/bun-types/bun-types.test.ts: 16 pass (the fixture-based cases under every lib configuration still pass with the widened type). This is also what thebun-typesGitHub workflow runs; that check is green on this PR.bun bd test test/integration/bun-types/bun-types.test.ts: 4 pass, 12 skipped (the skipped ones are the release-only LanguageService cases; the new case runs on debug builds too).Bun.mmapspawned-tsccase rather than by extracting a shared helper: several open types PRs already rewrite that block in different ways, so this PR leaves it untouched.Background
bun.lockis a JSONC file whose first key,lockfileVersion, gates compatibility: a Bun release that does not know the number refuses the file. Versions 1, 2 and 3 share the same content layout; 2 only turns on stricter parse checks, and 3 marks thatoverridescarries scoped rules."parent": { "child": "..." },"parent>child") and version-scoped ("pkg@1") rules apply to one parent's dependency, or only to dependents declaring a matching range. Inbun.lockevery such rule is normalized to one object per selector (nameorname@range); inside it,"."is the rule for the selected package itself and any other key is the rule for that dependency of it. Bun supports one level of nesting, so the object's values are always strings.bun-types.test.tsis excluded from the BuildKite test lanes and runs in thebun-typesGitHub workflow instead (triggered by changes underpackages/bun-typesand this test directory). Its fixture-based cases drive the TypeScript LanguageService in-process and are skipped on debug builds; spawningtscover one file is the pattern the file already uses for a case that also has to run there.Earlier revision
The first push extracted the
Bun.mmapcase'stscspawning into a helper shared with the new case. That was dropped in favor of an additive block (see the last Fix bullet); thebun.d.tschange is unchanged.