fix(bun): Preserve overrides objects, trustedDependencies, workspace bins and git integrity through prune; accept lockfileVersion 3 (#13740)
### Description
Bun's next release changes a few things about `bun.lock` that
`crates/turborepo-lockfiles/src/bun/` does not handle yet, and while
going through the format we found a handful of existing fields that
`turbo prune` drops or rewrites into a shape Bun does not read. This PR
fixes all of them in one pass over the Bun parser/emitter; every item is
independent and small.
Background on the Bun side:
- `lockfileVersion: 2` shipped in oven-sh/bun#31539 and turbo accepts it
since #13119 (2.10.3). oven-sh/bun#38333 (the upcoming package-manager
work) does not change the text format further.
- The next Bun release adds *nested overrides*: rules scoped to the
dependencies of one parent package. They are stored inside the existing
`overrides` section as object values, and the file is stamped
`lockfileVersion: 3` only when at least one such rule exists (it goes
back to 2 when they are removed). Objects are accepted by Bun's reader
at every version. The shape is:
```jsonc
"overrides": {
"lodash": "4.17.21", // flat rule, unchanged
"micromatch": { ".": "4.0.5", "picomatch": "2.3.2" }, // "." = flat rule
for micromatch itself
"webpack@^4": { "terser": "4.8.1" }, // only applies under webpack
matching ^4
},
```
Today either the object value (`invalid type: map, expected a string`)
or the version stamp makes `BunLockfile::from_str` fail, so the lockfile
is treated as absent and `turbo prune` errors with `Cannot prune without
parsed lockfile` as soon as a repo adds one nested rule.
#### 1. Object values in `overrides`, `lockfileVersion: 3`
`overrides` is now `Map<String, OverrideValue>` with an untagged
string-or-object value. Prune keeps copying the whole section verbatim,
exactly as it already does for flat overrides (`subgraph.rs`): Bun's
`--frozen-lockfile` diffs the complete override set in the lockfile
against the root package.json, which prune also copies verbatim, so
trimming any rule (flat or nested) would fail pruned installs.
`apply_overrides` applies string rules and an object's `"."` entry; keys
carrying a parent range (`"webpack@^4"`) never match a bare name, and
nested child rules need no resolution logic in turbo because Bun
materializes them as nested lockfile keys (`webpack/terser`), which
resolution already follows.
`LockfileVersion::V3` is added. Versions above the newest known one now
parse with a `tracing::warn!` instead of an error, since every Bun
revision so far has only added to the schema and the npm/pnpm parsers in
this crate already behave that way; negative versions are still
rejected. Adding or removing the first nested rule flips 2 <-> 3 and
therefore registers as a global lockfile change once each; that seems
correct and is left as is.
#### 2. `trustedDependencies` copied verbatim on prune
Prune currently emits the section empty. Bun diffs the lockfile's
`trustedDependencies` against the set declared in the (verbatim-copied)
package.json files on every install, so an empty section registers every
declared entry as newly added. On released Bun versions this is only
bookkeeping (`--frozen-lockfile` does not compare it), but Bun's next
release uses "did the manifest diff report anything" to decide how
aggressively to clean the tree before the frozen comparison, which can
turn that spurious diff into a visible difference; that signal is
expected to be tightened on the Bun side as well, but copying the
section is the consistent choice either way and matches how
`overrides`/`catalog(s)` are handled. Trade-off worth knowing: if the
only declarer of a trusted name was a workspace that got pruned away
(the `bun-v1-issue-12744` fixture declares it in `apps/bot`), the copied
entry is now reported as removed instead of nothing being reported.
Bun's frozen check compares the resolved tree, not this section, so
neither variant should affect `bun install --frozen-lockfile` on
released Bun versions, and the root package.json is the documented place
for the field.
#### 3. Workspace-level `bin` / `binDir`
Bun writes a workspace's `bin`/`binDir` into its `workspaces` entry and
the installer links workspace bins from there. `WorkspaceEntry` did not
have the fields, so a pruned install linked no bins for workspace
packages. They are now carried through (`bin` is a string or an object).
#### 4. git/github integrity element
git/github entries are `[ident, INFO, bun-tag, integrity?]`; the
deserializer stopped after the bun-tag and the emitter always wrote 3
elements, silently removing the content pin from every git dependency in
the pruned lockfile. `PackageEntry` gains an `integrity` field that is
only read/written for git/github entries.
#### 5. Root workspace `name` is optional
Bun omits `name` from the `""` workspace entry when the root
package.json has no name; `WorkspaceEntry.name` was required, so such
repos failed to parse entirely. It now defaults to empty and is skipped
on output, which is also how Bun represents it internally.
#### 6. Local tarball and `name@root:` entries
`PackageIdent::Tarball` only matched the literal string `tarball` (a
misreading of the schema comment), so `["bar@./bar-0.0.2.tgz", INFO,
integrity]` was classified as a registry package and re-emitted as
`[ident, "", INFO, integrity]`, which Bun rejects with `Expected an
object`. Tarballs are now recognized the way Bun does it (by
`.tgz`/`.tar.gz` suffix) and use the existing `[ident, INFO,
integrity?]` path. Similarly `name@root:` entries were parsed into
`RootInfo` but the emitter never consulted it, and `RootInfo.bin` could
not hold an object bin; they are now written as `[ident, { bin, binDir
}]`.
#### 7. `turborepo-devtools` watcher
`RELEVANT_FILES` listed `bun.lockb` but not `bun.lock` (the package
watcher has both), so the devtools graph never rebuilt on text lockfile
changes.
Not included: a Bun-generated `lockfile-tests` fixture. All existing Bun
fixtures there pin `packageManagerVersion` <= 1.3.x, and the v3 shape
needs a Bun that is not released yet; happy to add a `bun-v2-*` fixture
now and a `bun-v3-nested-overrides` one once that release is out, if you
want them in this PR or a follow-up.
### Testing Instructions
- `cargo test -p turborepo-lockfiles` (312 passed). New unit tests in
`bun/test.rs`: v3 file with object overrides (parse, `"."` applied,
ranged/nested rules not applied, verbatim through prune, reparse),
object overrides at v1, unknown newer version accepted / negative
rejected, `trustedDependencies` through prune, workspace `bin`/`binDir`
(string, object, `binDir`) through prune, root workspace without a name,
git/github 4-element entries through prune, local tarball / remote
tarball / `@root:` entries (with string, object and no bins) through
prune. `de.rs`/`ser.rs` gain matching `test_case`s, `types.rs` gains
tarball/root ident tests.
- `cargo test -p turborepo-devtools watcher`, `cargo test -p
turborepo-repository bun`.
- `cargo fmt`, `cargo clippy -p turborepo-lockfiles --all-targets`
clean.
- Not run: the `lockfile-tests` e2e harness (see above; the encoded
shapes asserted in the new tests are copied from what Bun's writer
produces).
This PR was written by Claude on behalf of the Bun team; a Bun
maintainer is sponsoring it and will respond to review.