Skip to content

install: consolidate the open package manager pull requests - #44968

Merged
Jarred-Sumner merged 344 commits into
mainfrom
claude/install-consolidated
Oct 14, 2026
Merged

Jarred-Sumner merged 344 commits into
mainfrom
claude/install-consolidated

Conversation

@Jarred-Sumner

@Jarred-Sumner Jarred-Sumner commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

What does this PR do?

It consolidates 597 open package manager pull requests into one reviewed change for Bun 1.5. Each was ported, taken in part, or left out with a reason. This is the complete change log: X -> Y = symptom on main -> behaviour now; a line without -> names a symptom that is gone; dep = dependency.

absorbed in part not taken obsolete total issues closed
464 40 74 19 597 133

699 files, +150,684 / -85,553 (src 221 files +41,383 / -46,459, net -5,076).

Behaviour changes someone will notice

  1. Unknown long flags are errors in every package manager command and bunx (main: bun install --network-timeout 100000 adds the package 100000). Drop the flag; bunx: tool flags go after the tool's name. In BUN_OPTIONS they are skipped.
  2. bun ci/--frozen-lockfile fail (note: dependencies in package.json changed since bun.lock was saved) when a dep of the root or a workspace was added, removed, re-grouped or re-ranged. Equivalent spellings, file: directory rows (warning), trustedDependencies/patchedDependencies edits pass. Run bun install, commit (install: fail --frozen-lockfile when bun install would rewrite bun.lock for a package.json edit #41931; refs bun install --frozen-lockfile allows bun.lock changes in workspaces #22689).
  3. file: folders are hard-linked real files (hoisted: was a symlink per file, their deps did not resolve). The folder's own node_modules is not taken, except bundleDependencies. Linked, and trusted scripts run, once, not on every install. Declare or bundle what the folder needs; link: / a workspace for a live link; --force reruns scripts (install(hoisted): install file: folders with the default backend instead of one symlink per file #40628, install: don't copy the project's node_modules into a file: dependency on an ancestor directory #37127; fixes [windows]: EPERM when installing the package locally at ../my-package #13379).
  4. Hoisted: what the previous bun.lock placed and the new one does not leaves node_modules, with what only it needed and its bins; not under --filter, not what no lockfile placed (bun prune) (install: prune stale workspace node_modules in hoisted installs #29794, install: remove nested copies bun.lock no longer places, with the hoisted linker #44406).
  5. One-time re-downloads: registry metadata (cache format bump), file: / URL tarballs, packages of registries on a port, path or http; bunx starts a new cache.
  6. Retries wait: a registry that refuses connections costs about 9 s before the install fails (main: at once). BUN_CONFIG_HTTP_RETRY_BACKOFF=<ms> sets the first wait.
  7. Dep names over 255 bytes per component are refused (main: a 4,100-byte name in a manifest aborts), as are the names under Security.
  8. Workspace's symlinked node_modules is replaced by a real directory, like links at @scope, .bin, .bun/** (main wrote and deleted through them). Only the root node_modules link is followed: link the root, not members (install: do not follow a symlink at a node_modules directory the installer creates #42039).
  9. "trustedDependencies": [] kept the default trusted list off only on the install that created bun.lock -> always, also in a checkout without the workspace that declares it; written to bun.lock (one line; Bun 1.4 honours it, drops it at its next save) (install: keep an empty trustedDependencies list in effect on installs from bun.lock #44765).
  10. !dir/** in workspaces also removes dir, as npm; negated entries work in every spelling (!./pkg, !pkg/) and position, add no members; bun run --filter/--workspaces follow them (install: normalize a negated workspaces entry before it is matched #43401, Apply negated workspaces entries in bun run --filter and --workspaces #43400; fixes bun install ignores a negated workspaces entry written as !./path or !path/ #43399).
  11. bun pm ls lists what is on disk, exits 1 without node_modules; isolated: a dep of the root only if its link exists (Make bun pm ls list installed packages instead of lockfile entries #38923; fixes bun pm ls counts lockfile entries but labels them node_modules #38909).
  12. Linker sticks: while node_modules/.bun exists, a command that names no linker keeps the isolated layout (main: lays hoisted over it). Back: --linker=hoisted or delete node_modules.
  13. NODE_ENV=development/test: bun install reads the .env files bun run reads, not the production ones.
  14. --provenance, --provenance-file, publishConfig.provenance, NPM_CONFIG_PROVENANCE, provenance=true in .npmrc are errors (main: published without it). Unset them or publish with npm (publish: add --provenance (npm provenance via Sigstore keyless signing) #30522; refs Prevent publishing when publishConfig.provenance set to true in package.json #18611).
  15. packageExtensions adds a key to bun.lock, a trailing section to bun.lockb, only when an extension added something; older versions load both.
  16. Fresh resolves pick differently (locked versions stay): see Resolver; Overrides. glibc hosts stop installing -musl variants (--libc='*' for both).
  17. New warn: lines: incorrect peers, deprecated versions, skipped required deps, stale / misplaced patchedDependencies, unreadable config files, retries, unusable hashes.
  18. Dep's script finds its own .bin, then the inherited PATH, then shared .bins, so an untrusted package's node bin cannot run inside a trusted script. Cost: a global node-gyp wins over the project's, for dep scripts only (install: scope the lifecycle script PATH to the script owner's own bins #44488).
  19. If bun install itself is killed while a trusted dep's script runs, the next install calls the package installed (registry packages: as main; file: folders, local tarballs: the rerun on every install hid it). bun install --force (install: list unfinished lifecycle scripts per project, so the install after a killed one runs them #42001).

Decisions that need the maintainer's confirmation

New features and flags

Security

Install core

Resolver and version picking

Hoisted linker

Isolated linker, global store

Bins

bun.lock, bun.lockb

--frozen-lockfile, bun ci

Migrations (npm, pnpm, Yarn v1)

Workspaces, catalogs

Overrides, peers

Registries, credentials, .npmrc, bunfig

Network, retries

Cache, tarballs, extraction

git, GitHub, folder deps

Lifecycle scripts, trust

Security scanner, audit

add, remove, update, outdated, link

bun pm, why, info

pack, publish

bun patch

bunx

  • bunx pkg@latest relinked everything each run, a day-old tree was refreshed every run -> once; served with a warning when the registry gives no answer (#41226; fixes #23597, #41211)
  • Several bunx <pkg> at once corrupted the shared install -> file lock (#37851; fixes #37830)
  • directories.bin was resolved from the wrong directory (#39096)

bun create

  • Windows: a GitHub template silently overwrote files (its conflict list was always empty) and wrote through junctions -> listed; --force replaces the link
  • bun create <local template> <dir> deleted an existing directory -> merges, lists conflicts (links in the destination too), overwrites only with --force (#36351; fixes #27948, #36341)
  • git init / commit raced tasks and install -> finished first; the first commit holds exactly the template (#41342; fixes #36953)
  • bun-create tasks ran without a shell, not at all without deps, failures ignored -> run like package.json scripts, with npm_package_name; a failing task stops with its exit code (#41598, #38955)
  • Failed nested bun install was ignored -> exits with its code (#38485)
  • Absolute template path crashed -> local folder; ., .., an empty name are refused (#41770)
  • bun create ./Component.tsx overwrote your stylesheet, styles/globals.css, lib/utils.ts -> kept and listed; --force overwrites (#44668)
  • Template's package.json indentation was lost (#36965; fixes #36964)
  • Missing-export hint printed {{ }} (#37847)

Auto-install

  • Timers and I/O callbacks ran inside require(), import(), Bun.resolveSync() while a package was fetched -> the thread waits; git deps are no longer cloned at run time (#36159; fixes #43842)
  • Second version (pkg@2 after pkg@1) with the manifest in the disk cache failed with Unexpected (#36659; fixes #10411)
  • Failed lookup failed until the process exited -> a 404 stands as long as a cached manifest (5 min, or its Cache-Control); any other failure is asked again once the event loop has gone around (#42883)
  • Failed download limited fetch() to 4 concurrent requests for the rest of the process (#42945)
  • --prefer-offline still asked the registry -> cached manifest (#36776)
  • Resolve errors did not say why (#38180)
  • bunfig install.prefer worked for bun run <file> only -> also bun <file>, bun test
  • Dep whose name or version has a JSON escape fell back to latest

Completions, installer

  • Second completions line was added to a .zshrc that already sources ~/.bun/_bun (#35561; fixes #10897)
  • fish completions went to /etc when ~/.config/fish/completions is missing (#41659)
  • install.ps1: the another bun.exe is already in PATH check never fired (#38567)

Crashes, panics, hangs

  • Hang until out of memory on a dependency cycle -> dependency cycle cannot be laid out in node_modules, exit 1; a bundled back-edge installs (#38976)
  • Hang: a dangling node_modules/@scope symlink (hoisted)
  • Hang: --concurrent-scripts=0 -> the default (#42379)
  • Hang: isolated, a tarball URL that cannot be requested (ftp://) (#39672)
  • Hang: isolated, cold cache, a git dep that is scp-form, a peer, from overrides or spelled differently in bun.lock; after a failed clone / checkout (#44448, #37156, #43075)
  • Panic: paths that do not fit the path buffer in both linkers' walks, link: targets, long linked names, <alias>/package.json -> ENAMETOOLONG/Invalid dependency name (#37424, #37400, #38369, #38571, #38575)
  • Panic: over-long --cafile, --cwd, add --filter; $BUN_INSTALL/$HOME with -g; bunx bin names; pnpm link:/file: paths; progress bar names (#41702, #38413, #37394, #43537, #38558)
  • Abort of all package manager commands, bun run, bunx in a cwd within 13 bytes of PATH_MAX -> ENAMETOOLONG, or the file counts as unreadable (#42502)
  • Panic: outdated, update, update -i on a bun.lock without packages (#41647)
  • Segfault: package.json nested ~8,400 levels; a deep YAML alias chain in pnpm / Berry files
  • Panic: all bunx install flags at once
  • Use-after-free in bun add --trust and at the end of an isolated install (#31245, #39640)
  • Bad system call on Android / Termux (openat2, fchmodat2 under seccomp) (#39084)

Windows

  • Bins whose shebang uses env -S did not run (#31391; refs #22422, #27924, #31387)
  • .bunx shims broke when the package is on another drive (#38018)
  • bun remove left .bunx/.exe shims (#35585; refs #11970)
  • bun install failed in the root of a drive (#43437; refs #29273)
  • EINVAL: Failed to replace old lockfile on exFAT / FAT32 (#33029; refs #10169)
  • Concurrently published cache entry was deleted; renames failed while a virus scanner holds a new file -> retried for 5 s (BUN_INSTALL_WINDOWS_RENAME_RETRY_MS) (#33884, #35568; refs #11250, #28062)
  • bunx.exe was written when the hard link fails -> bunx.cmd (#35686)

Performance

User-space instructions of bun install (perf stat, median of 3, millions), generated project of N packages, loopback registry; warm = nothing to do, relink = cache + lockfile, no node_modules.

main N=400 branch N=400 main N=1600 branch N=1600
hoisted warm 22.5 20.3 146.5 80.1
hoisted relink 24.5 21.6 150.3 83.3
hoisted cold 121.0 122.2 511.2 474.6

System calls per package (strace -c, slope between 100 and 300 packages of 31 files). Per run: a few more lstat (change 8), one read per file: directory dep.

main this branch
hoisted cold 215.96 211.95
hoisted relink 72.00 70.00
hoisted warm 4.00 4.00

Quadratic scans in the bun.lock workspace parse, yarn.lock printer (#34537) and Yarn v1 migration (#37244) are gone; no allocator heap per cached package.json (#44610).

main this branch
bun pm hash, 32,000 workspaces (ms) 6,914 236
bun pm migrate, Yarn v1, 20,000 entries (ms) 24,210 270
peak RSS of bun install, 2,100 workspaces (KB) 90,840 34,728
hoisted, 1,000 packages x 30 files, cache, no node_modules, 8 cores, wall (ms) 381 120
same, 4 cores 383 174
same, 2 cores 222 162
same, node_modules lacks half, 8 cores 140 54
same, 4 cores 174 65
same, 2 cores 161 107
same, no cache, no node_modules, 8 cores 363 394
same, 4 cores 418 401
same, 2 cores 477 541

Internal, no user-visible effect

  • Earlier fold of 171 PRs is the first layer; 12 of them were taken out again (#39403)
  • One implementation each: trusted set, required-dep predicate, failed-optional discard, bin linking, package.json walk, --cwd, cache publish, specifier -> version (was 4), lockfile writer, registry request
  • pack() consumes its context; one binding.gyp predicate; Yarn v1 migrator 2,032 -> 888 lines, no unsafe (#37670, #43912, #37539, #38303, #37865)
  • Debug builds: opt-in stack dump, node shim directory kept, manifest cross-check deleted (#37335, #37384, #43170)
  • Binary size pass (outlined helpers, shared flag tables, index sorts): +464 KB -> see Performance; dead auto-install disk scan removed. unsafe in src/install: 585 -> 540

Test-only PRs

  • verdaccio replaced by a registry on Bun.serve (on main since): #44055
  • No public network: #35035 #35377 #35384 #36717 #36718 #36721 #36889 #38185 #38467 #39022 #39655 #40087 #40538 #40956 #42800 #42802 #43289 (fixes #43286)
  • Concurrent, a cache per test: #38464 #38504 #40095 #40635 #39646 #40198 #40450 #39956 #39740 #41110
  • Exact output / lockfile / layout (nested-overrides, bun-add-filter, lockfile-sync, frozen-lockfile-pruned, pm-licenses, isolated-relink): #39648 #39746 #40452 #40454 #40456 #41118
  • Flake causes removed (stdin / pty races, racing ranges, cold manifest cache, un-awaited matchers): #34175 #41401 #41591 #38858 #39190 #38627 #37333 #33279
  • Faster (streaming-extract, git-deps, npm-arborist): #35915 #40447 #40977
  • New guards (.. tarball entries, Bun's own bun.lock passes frozen, hoisted layouts, what a linker verifies): #37431 #42384 #43878 #41864
  • Harness (isRoot, peak RSS, pm trust CPU test on Windows): #39232 #43192 #35626 (refs #11252)

Taken in part (what is in. Not: what is not)

  • #19637: opt-in bunx --trust (runs the scripts of the package and its whole closure). Not: trust by default
  • #29794: change 4. Not: removing a stale copy no lockfile placed (bun prune)
  • #30522: change 14. Not: signing; #15601 open (refs #18611)
  • #31327: a junk integrity in a v2+ bun.lock is an error. Not: pinning a computed sha512; refusing an empty integrity under frozen
  • #32022: 10 helpers redone on current code, 26 of 28 pinning tests. Not: non-install parts
  • #35253: bun create without a git identity stages the files, says the commit was skipped. Not: a made-up author
  • #35807: npm_config_ignore_scripts=true turns scripts off. Not: false/0 turning them on over a config file
  • #36387: false Workspace name already exists on a hash collision. Not: the same for trustedDependencies names (one stays blocked) (refs #36386)
  • #36973: docs of the store layout as it is. Not: a stability promise
  • #38001: a token survives an http -> https redirect on one hostname. Not: another port (still stripped) (refs #15516)
  • #38832: its forced-arrival-order tests, expecting one copy (#38770). Not: its two-copies policy
  • #39505: cold generics -> runtime flags in summary printer, pack, lockfile read. Not: typed arrays, coverage
  • #39886: an override on a peer was lost behind a same-named root npm: alias. Not: a peer without an override shares the alias (react -> npm:@preact/compat)
  • #40053: features, change 15. Not: [install.packageExtensions] in bunfig; pnpm.packageExtensions without a migration; catalog: values (ignored, warning)
  • #40110: warning for a patchedDependencies key that matches no installed version. Not: failing frozen
  • #41724: moot for tarballs (named by integrity). Not: new names for bare git clones
  • #41759: a package.json in a sticky shared directory above the cwd (/tmp) is ignored, with a warning. Not: the owner rule
  • #41863: pm pkg set --json with a value that is not JSON is an error, nothing is written. Not: strict JSON (0x10, .5 are normalised)
  • #41921: Linux: a script no longer outlives a killed install. Not: signal forwarding; macOS
  • #41925: symlinked bins / directories.bin are neither packed nor listed. Not: race-proof opener, Windows, a symlinked node_modules
  • #41931: change 2. Not: failing on trusted / patched edits or equivalent spellings (refs #22689)
  • #42001: after a failed script the next install reruns it and what it blocked. Not: change 18
  • #42030: a plain install re-reads a file: directory's package.json. Not: failing frozen (warns)
  • #42039: change 8; bun prune refuses a linked .bun. Not: the root link (on purpose); a package's own directory on a warm run
  • #42052: bun pm view/info: no project needed, several fields, npm paths (maintainers[0].name), semver-sorted versions, bin:, DEPRECATED. Not: new --json errors (refs #20673)
  • #42139: npmrc.test.ts: isolation per case, concurrent. Not: exact pins
  • #42400: pm diff compares a folder of this project as pack publishes it. Not: folders of other projects
  • #42597: nothing of the project's .env reaches install's git. Not: proxy / TLS variables from .env
  • #43067: the 255-byte name bound, the install cases. Not: the repo-wide sweep
  • #43375: which package owns an override row; direct deps outside their range. Not: transitive rows
  • #43556: bun patch never writes into the global store. Not: a project-local copy that installs keep
  • #43581: a registry package's peer / optional edge to a workspace missing on disk is dropped. Not: the pre-flight report
  • #43793: the walker looks at link entries; the @scope hang. Not: materialising symlinked cache entries; #43788 unverified
  • #44058: Windows: node shim next to bun.exe when %TEMP% is on another drive (never run). Not: a missing %TEMP% (warning) (refs #44033)
  • #44406: change 4. Not: a copy no loaded lockfile names
  • #44488: a dep's script finds its own .bin, then PATH, then shared .bin: another package's bin cannot shadow node/sh. Not: bins per script owner
  • #44641: its install test and docs hunk. Not: 13 HTTP / fetch test files
  • #44793: Windows: the launcher of a .cmd/.bat bin (any case) refuses cmd.exe special characters. Not: bun run <bin>.cmd
  • #44798: a node_modules a git dep commits is dropped unless it bundles (then without links). Not: bundled closure only
  • #44847: a workspace's node_modules is reset when only the root's was deleted. Not: moving stale directories aside; a marker

Not taken

  • #20178: the isolated linker covers it; #6850 open
  • #28515: prints the repo URL, no changelog
  • #29512: bun pm sbom: own PR
  • #30289: a bun link name would override every project
  • #30340: checks after the fetch, fails open
  • #30582: config surface without demand
  • #30863 #40976 #43643: not install: install.sh, bun upgrade
  • #31585 #41750 #43144 #43242 #44509: not install: Bun.Archive
  • #31979 #37596 #37646 #37678 #39165 #40312: refactor nothing needs
  • #33113: needs a Windows measurement
  • #33972: masks a flake; cause gone with #37713
  • #35105: distrusts the cache directory
  • #35371: borrow refactor, no longer applies
  • #35571: re-lays existing monorepos; #9838 open
  • #35602: false positives, harmful advice
  • #36081: needs the public registry
  • #36464: stale; weakens lockfile parsing
  • #36476: the next step after this PR
  • #36556: reverses an earlier hardening
  • #36557: its code is deleted (#44393)
  • #36587: restores a dead warning
  • #36631: contradicts a landed test; #7869 open
  • #36682: the typo already gets a clear error
  • #36688: a landed test pins the opposite; #13509 open
  • #36696: own feature (config root for bunx / create); #13247, #5361 open
  • #36888 #39741 #39742 #39957 #40639: test rewrite without measurable gain
  • #37143: cause removed by #44919
  • #37551: part of #40284
  • #37644 #38365 #39206 #39936 #40855 #44031 #44425: not install (transpiler, bun run, Bun.write, pipe reader, repo-wide)
  • #38741: lockfile v2 has shipped
  • #38777: follows links out; main case gone with change 3
  • #38810: turned down earlier; #10297 open
  • #38814: draft; rows grow per path
  • #39217: little demand
  • #39499: codemod for an unlanded change
  • #39724: refuses installable tarballs; see #41503
  • #40045: 16 public JSON formats: own PR
  • #40073: test flag that hides a race
  • #40277: second store layout
  • #40284: stacked on branches that have not landed (needs new task, request and archive types in six other crates); follows separately
  • #40300: Windows bunx.exe identity; #40298 open
  • #40717: opposite of #36690
  • #40753: loosens default-trust hardening
  • #41764: subset of #43322
  • #41866: opposite of #41803
  • #43104: needs a new bun.lock key
  • #43209: needs #43104's key
  • #43322: breaking; realpath per workspace
  • #44117: stacked on #44058
  • #44621 #44809 #44813: draft
  • #44622: changes patch hash and cache names; #25932 open

Obsolete (on main via the PR in brackets, or moot): #18582 (the Zig build is gone) #35542 (#38333) #35603 (#41854) #36352 (verdaccio is gone) #36670 (#38269) #36977 (#40010; --tarball-dir, #7956 stay open) #37126 (#40734) #37137 (#38269) #37250 (#38333) #37616 (#40734) #37925 (#37426) #38648 (#40063) #38671 (#39229) #38687 (#40406) #38776 (#38796) #38869 (#41302) #38891 (verdaccio is gone) #40766 (verdaccio is gone) #41667 (#42881)

Fixed issues

Fixes #2773. Fixes #4553. Fixes #4719. Fixes #4830. Fixes #4852. Fixes #5045. Fixes #5176. Fixes #5368. Fixes #5682. Fixes #5714. Fixes #6317.
Fixes #6382. Fixes #6883. Fixes #7215. Fixes #7403. Fixes #7501. Fixes #7504. Fixes #7571. Fixes #8031. Fixes #8260. Fixes #8292. Fixes #9351.
Fixes #10152. Fixes #10411. Fixes #10897. Fixes #11548. Fixes #12011. Fixes #12041. Fixes #12430. Fixes #12752. Fixes #13019. Fixes #13244. Fixes #13330.
Fixes #13379. Fixes #13769. Fixes #13891. Fixes #14017. Fixes #14134. Fixes #14736. Fixes #15102. Fixes #15336. Fixes #15602. Fixes #15711. Fixes #16185.
Fixes #16282. Fixes #17060. Fixes #17759. Fixes #18670. Fixes #18947. Fixes #19065. Fixes #19327. Fixes #20373. Fixes #20376. Fixes #20477. Fixes #20829.
Fixes #21397. Fixes #22035. Fixes #22719. Fixes #23264. Fixes #23597. Fixes #24838. Fixes #25314. Fixes #26070. Fixes #26715. Fixes #27948. Fixes #28233.
Fixes #28597. Fixes #28668. Fixes #28967. Fixes #29263. Fixes #29343. Fixes #30311. Fixes #30450. Fixes #30513. Fixes #30748. Fixes #31028. Fixes #31152.
Fixes #31450. Fixes #31864. Fixes #33141. Fixes #33571. Fixes #33796. Fixes #33977. Fixes #36227. Fixes #36341. Fixes #36795. Fixes #36931. Fixes #36953.
Fixes #36964. Fixes #37830. Fixes #38079. Fixes #38193. Fixes #38569. Fixes #38879. Fixes #38908. Fixes #38909. Fixes #40031. Fixes #40310. Fixes #40355.
Fixes #40549. Fixes #40683. Fixes #41211. Fixes #41371. Fixes #41543. Fixes #41625. Fixes #42421. Fixes #42799. Fixes #42989. Fixes #43138. Fixes #43148.
Fixes #43164. Fixes #43200. Fixes #43211. Fixes #43212. Fixes #43214. Fixes #43256. Fixes #43285. Fixes #43286. Fixes #43288. Fixes #43291. Fixes #43295.
Fixes #43310. Fixes #43350. Fixes #43351. Fixes #43353. Fixes #43399. Fixes #43795. Fixes #43842. Fixes #43903. Fixes #43975. Fixes #44299. Fixes #44320.
Fixes #44669.

Expected fixed, first exercised by CI (Windows; close by hand): #28062 #11250 #11970 #29273 #10169 #22422 #27924 #31387 #11252 #28771 #44033 #12886

Covered by a PR taken in part (close by hand): #18611 #36386 #15516 #22689 #20673

Credits

  • @Jarred-Sumner: #39403 (the earlier fold), #20067, #31245, #31416, #33884, #37590, the design of #31447, part of #31327.
  • @MarshallOfSound: #40057, #40025, #40053, #40012, #40009.
  • @alii: #45052, #45051, #40423, #31481, #33279, helpers of #32022, half of #39505, review findings in #35259 #35468 #35586 #38047.
  • @dylan-conway: #19637 (opt-in), the order-dependent shapes of #36476 (tests here), the off-by-one fixes of #33113, the objection behind #37138.
  • @nikelborm: #41291.

How did you verify your code works?

  • Suite (offline, root): main's tests on main 146 files, 9,099 pass, 306 fail; this branch 174 files, 16,067 pass, 42 fail; 6,277 passing tests are new, 268 that fail on main pass. Of the 42, 40 fail on main too; 1 needs Docker, 1 test deps.
  • Absorbed = a test here covers the claim, passes here, fails on main. Exceptions: test-only PRs, guards, performance PRs (measured); by reading only: #39640 #37335 (no test possible), #34175 (Docker), #38858 #39190 #41401 #41591 (flakes), #40447 #39740, the refactors.
  • Randomized differential: 600 generated projects (every dep kind, both linkers, both lockfile formats) through installs, edits, prune, patch, damage, kills; all output compared. First install succeeds: 545 -> 559. Projects that violate an invariant:
main this branch
repeated install rewrites files 265 10
repeated install reports work 101 2
repeated install changes the tree 22 3
lockfile depends on manifest arrival order 57 0
exit code depends on the linker 20 0
writes outside the project 21 0
declared dep cannot be resolved 13 1
dep resolves to a wrong version 4 0
frozen / relink / cold cache fails on its own lockfile 7 0
package.json rewritten with the same bytes 7 0
crash or hang 3 0
dangling symlink 2 0
bin not executable 2 2
leftovers in the temp directory 230 1
tree or exit code after kill + reinstall differs (timing) 3 4
  • Credentials: 805 requests with main, this branch, npm 11.17.0: 534 as main; 170 wider (npm too in 140, 30 deliberate); 82 narrower; 19 another credential. 142 non-http(s) registry URLs x 16 routes: no request (main: 66).
  • Secrets: a secret in every input of 698 projects x 6,490 commands, not masked: data asked for on stdout, source excerpts under a diagnostic, echoed arguments.
  • Lockfiles: 90,000 mutated bun.lockb x 7 commands: no crash or hang.
  • Version picking: npm-pick-manifest 11.0.3 over 200,000 generated pairs: differs only for a dangling written latest and ^1.0.0-a.0.
  • Debug + ASAN: 9 files (1,253 tests); 200 rounds of #31245's race. Two installs at once, 252 packages: tree right in 150 of 150 rounds, 2 of 300 exit 1 (main: 35 of 40 wrong, 69 of 80).
  • Hoisted thread pool vs main thread, one binary: 704 generated projects, 8,800 fuzz cases, CPU starvation x 4 backends, 432 injected I/O errors, thread and fd limits, script order: equal.

Not verified:

  • Windows, macOS: CI only. Android (#39084): a seccomp filter on Linux, no device.
  • By script, no test: --trust with an alias, an aborted upgrade leaves no holes, two installs at once. overlayfs: a simulated EXDEV.
  • Known limits: change 19.

Co-authored-by: nikelborm 29357907+nikelborm@users.noreply.github.com

robobun and others added 30 commits August 17, 2026 01:25
…ile: tarballs relative to their folder package (#38867)
…_parent_pkg_of_dependency, is_local_package, EscapeControlChars)
robobun added a commit to robobun/soup that referenced this pull request Oct 30, 2026
Bun.escapeHTML() had no inverse. HTMLRewriter returns text and attribute
values as they are written in the document, so `&amp;` stays `&amp;`, and
a script that wants the text a person reads had to install a package to
decode it, although the table of the named character references of HTML
is already in the binary for Bun.markdown.

Bun.unescapeHTML(input, options?) replaces the character references of a
string the way the HTML tokenizer does: the 2,125 names with their `;`,
the 106 names of HTML 4 also without it (the longest match wins), and
decimal and hexadecimal references, with the replacements HTML gives to
zero, to surrogates, to numbers above U+10FFFF and to the Windows-1252
block. Text that is not a reference stays as it is, so no input throws.
With `attribute: true` a name without its `;` is left alone when `=`, a
letter or a digit follows it. That is the rule of the tokenizer for
attribute values, and it keeps the `&copy=` of a query string intact.

A string without a reference is returned as it is. Otherwise the output
is allocated once at the length of the input and handed to JavaScriptCore
without a copy. Latin-1 input stays Latin-1 until a reference names a
character outside of it, and UTF-16 input is read as UTF-16, so a lone
surrogate passes through. bun_md::entity::lookup is made public for the
names.

SOUP.md also records that the patch that merged a bun.lock with git
conflict markers was dropped in the rebase onto oven-sh#44968.
robobun added a commit to robobun/soup that referenced this pull request Oct 30, 2026
When two branches change dependencies, git leaves conflict markers in
bun.lock. The lockfile then failed to parse, bun install ignored it and
resolved every dependency again, so every pinned version in the project
could move. With --frozen-lockfile the error did not name the cause.

The loader now reads both sides of the conflict and merges them. A
package that one side locks keeps its version. When the sides put two
versions of a package at one path, the higher version keeps the path,
what is below the path comes from the side of that version, and a
dependency whose range does not take it gets the version of its own
side. Every dependency is checked against its range after the merge,
with the rules the resolver uses for overrides, catalogs, peers and
npm: aliases. bun install then compares the merge with package.json.
The packages of both sides stay until that is done, so a range that
package.json kept gets the version that its side locked. A dependency
with a package on neither side goes to the registry.

bun install --frozen-lockfile, bun ci and --production fail with an
error that names the conflict. Commands that only read the lockfile read
the merge when it is complete. bun dedupe, bun pm trust and bun audit
fix ask for a bun install first. When the sides cannot be merged, for
example with two integrity hashes for one version, the lockfile is
ignored as before and a note says why.

A lockfile that parses does no new work: the merge starts from the arm
that handles the parse error.

This patch left the stack in the rebase onto oven-sh#44968, which
rewrote the lockfile loader and the commands it hooks into. It is
applied again here on the new code: the hooks are placed by hand, and
merge_conflict.rs changed only where upstream now does the work itself
(the loader no longer records npm: aliases, and the alias rule of the
resolver is asked through Group::is_satisfied_by_a_bound_of).
robobun added a commit to robobun/soup that referenced this pull request Oct 31, 2026
Bun.escapeHTML() had no inverse. HTMLRewriter returns text and attribute
values as they are written in the document, so `&amp;` stays `&amp;`, and
a script that wants the text a person reads had to install a package to
decode it, although the table of the named character references of HTML
is already in the binary for Bun.markdown.

Bun.unescapeHTML(input, options?) replaces the character references of a
string the way the HTML tokenizer does: the 2,125 names with their `;`,
the 106 names of HTML 4 also without it (the longest match wins), and
decimal and hexadecimal references, with the replacements HTML gives to
zero, to surrogates, to numbers above U+10FFFF and to the Windows-1252
block. Text that is not a reference stays as it is, so no input throws.
With `attribute: true` a name without its `;` is left alone when `=`, a
letter or a digit follows it. That is the rule of the tokenizer for
attribute values, and it keeps the `&copy=` of a query string intact.

A string without a reference is returned as it is. Otherwise the output
is allocated once at the length of the input and handed to JavaScriptCore
without a copy. Latin-1 input stays Latin-1 until a reference names a
character outside of it, and UTF-16 input is read as UTF-16, so a lone
surrogate passes through. bun_md::entity::lookup is made public for the
names.

SOUP.md also records that the patch that merged a bun.lock with git
conflict markers was dropped in the rebase onto oven-sh#44968.
robobun added a commit to robobun/soup that referenced this pull request Oct 31, 2026
When two branches change dependencies, git leaves conflict markers in
bun.lock. The lockfile then failed to parse, bun install ignored it and
resolved every dependency again, so every pinned version in the project
could move. With --frozen-lockfile the error did not name the cause.

The loader now reads both sides of the conflict and merges them. A
package that one side locks keeps its version. When the sides put two
versions of a package at one path, the higher version keeps the path,
what is below the path comes from the side of that version, and a
dependency whose range does not take it gets the version of its own
side. Every dependency is checked against its range after the merge,
with the rules the resolver uses for overrides, catalogs, peers and
npm: aliases. bun install then compares the merge with package.json.
The packages of both sides stay until that is done, so a range that
package.json kept gets the version that its side locked. A dependency
with a package on neither side goes to the registry.

bun install --frozen-lockfile, bun ci and --production fail with an
error that names the conflict. Commands that only read the lockfile read
the merge when it is complete. bun dedupe, bun pm trust and bun audit
fix ask for a bun install first. When the sides cannot be merged, for
example with two integrity hashes for one version, the lockfile is
ignored as before and a note says why.

A lockfile that parses does no new work: the merge starts from the arm
that handles the parse error.

This patch left the stack in the rebase onto oven-sh#44968, which
rewrote the lockfile loader and the commands it hooks into. It is
applied again here on the new code: the hooks are placed by hand, and
merge_conflict.rs changed only where upstream now does the work itself
(the loader no longer records npm: aliases, and the alias rule of the
resolver is asked through Group::is_satisfied_by_a_bound_of).
robobun added a commit to robobun/soup that referenced this pull request Oct 31, 2026
Bun.escapeHTML() had no inverse. HTMLRewriter returns text and attribute
values as they are written in the document, so `&amp;` stays `&amp;`, and
a script that wants the text a person reads had to install a package to
decode it, although the table of the named character references of HTML
is already in the binary for Bun.markdown.

Bun.unescapeHTML(input, options?) replaces the character references of a
string the way the HTML tokenizer does: the 2,125 names with their `;`,
the 106 names of HTML 4 also without it (the longest match wins), and
decimal and hexadecimal references, with the replacements HTML gives to
zero, to surrogates, to numbers above U+10FFFF and to the Windows-1252
block. Text that is not a reference stays as it is, so no input throws.
With `attribute: true` a name without its `;` is left alone when `=`, a
letter or a digit follows it. That is the rule of the tokenizer for
attribute values, and it keeps the `&copy=` of a query string intact.

A string without a reference is returned as it is. Otherwise the output
is allocated once at the length of the input and handed to JavaScriptCore
without a copy. Latin-1 input stays Latin-1 until a reference names a
character outside of it, and UTF-16 input is read as UTF-16, so a lone
surrogate passes through. bun_md::entity::lookup is made public for the
names.

SOUP.md also records that the patch that merged a bun.lock with git
conflict markers was dropped in the rebase onto oven-sh#44968.
robobun added a commit to robobun/soup that referenced this pull request Oct 31, 2026
When two branches change dependencies, git leaves conflict markers in
bun.lock. The lockfile then failed to parse, bun install ignored it and
resolved every dependency again, so every pinned version in the project
could move. With --frozen-lockfile the error did not name the cause.

The loader now reads both sides of the conflict and merges them. A
package that one side locks keeps its version. When the sides put two
versions of a package at one path, the higher version keeps the path,
what is below the path comes from the side of that version, and a
dependency whose range does not take it gets the version of its own
side. Every dependency is checked against its range after the merge,
with the rules the resolver uses for overrides, catalogs, peers and
npm: aliases. bun install then compares the merge with package.json.
The packages of both sides stay until that is done, so a range that
package.json kept gets the version that its side locked. A dependency
with a package on neither side goes to the registry.

bun install --frozen-lockfile, bun ci and --production fail with an
error that names the conflict. Commands that only read the lockfile read
the merge when it is complete. bun dedupe, bun pm trust and bun audit
fix ask for a bun install first. When the sides cannot be merged, for
example with two integrity hashes for one version, the lockfile is
ignored as before and a note says why.

A lockfile that parses does no new work: the merge starts from the arm
that handles the parse error.

This patch left the stack in the rebase onto oven-sh#44968, which
rewrote the lockfile loader and the commands it hooks into. It is
applied again here on the new code: the hooks are placed by hand, and
merge_conflict.rs changed only where upstream now does the work itself
(the loader no longer records npm: aliases, and the alias rule of the
resolver is asked through Group::is_satisfied_by_a_bound_of).
robobun added a commit to robobun/soup that referenced this pull request Nov 1, 2026
Bun.escapeHTML() had no inverse. HTMLRewriter returns text and attribute
values as they are written in the document, so `&amp;` stays `&amp;`, and
a script that wants the text a person reads had to install a package to
decode it, although the table of the named character references of HTML
is already in the binary for Bun.markdown.

Bun.unescapeHTML(input, options?) replaces the character references of a
string the way the HTML tokenizer does: the 2,125 names with their `;`,
the 106 names of HTML 4 also without it (the longest match wins), and
decimal and hexadecimal references, with the replacements HTML gives to
zero, to surrogates, to numbers above U+10FFFF and to the Windows-1252
block. Text that is not a reference stays as it is, so no input throws.
With `attribute: true` a name without its `;` is left alone when `=`, a
letter or a digit follows it. That is the rule of the tokenizer for
attribute values, and it keeps the `&copy=` of a query string intact.

A string without a reference is returned as it is. Otherwise the output
is allocated once at the length of the input and handed to JavaScriptCore
without a copy. Latin-1 input stays Latin-1 until a reference names a
character outside of it, and UTF-16 input is read as UTF-16, so a lone
surrogate passes through. bun_md::entity::lookup is made public for the
names.

SOUP.md also records that the patch that merged a bun.lock with git
conflict markers was dropped in the rebase onto oven-sh#44968.
robobun added a commit to robobun/soup that referenced this pull request Nov 1, 2026
When two branches change dependencies, git leaves conflict markers in
bun.lock. The lockfile then failed to parse, bun install ignored it and
resolved every dependency again, so every pinned version in the project
could move. With --frozen-lockfile the error did not name the cause.

The loader now reads both sides of the conflict and merges them. A
package that one side locks keeps its version. When the sides put two
versions of a package at one path, the higher version keeps the path,
what is below the path comes from the side of that version, and a
dependency whose range does not take it gets the version of its own
side. Every dependency is checked against its range after the merge,
with the rules the resolver uses for overrides, catalogs, peers and
npm: aliases. bun install then compares the merge with package.json.
The packages of both sides stay until that is done, so a range that
package.json kept gets the version that its side locked. A dependency
with a package on neither side goes to the registry.

bun install --frozen-lockfile, bun ci and --production fail with an
error that names the conflict. Commands that only read the lockfile read
the merge when it is complete. bun dedupe, bun pm trust and bun audit
fix ask for a bun install first. When the sides cannot be merged, for
example with two integrity hashes for one version, the lockfile is
ignored as before and a note says why.

A lockfile that parses does no new work: the merge starts from the arm
that handles the parse error.

This patch left the stack in the rebase onto oven-sh#44968, which
rewrote the lockfile loader and the commands it hooks into. It is
applied again here on the new code: the hooks are placed by hand, and
merge_conflict.rs changed only where upstream now does the work itself
(the loader no longer records npm: aliases, and the alias rule of the
resolver is asked through Group::is_satisfied_by_a_bound_of).
robobun added a commit to robobun/soup that referenced this pull request Nov 2, 2026
Bun.escapeHTML() had no inverse. HTMLRewriter returns text and attribute
values as they are written in the document, so `&amp;` stays `&amp;`, and
a script that wants the text a person reads had to install a package to
decode it, although the table of the named character references of HTML
is already in the binary for Bun.markdown.

Bun.unescapeHTML(input, options?) replaces the character references of a
string the way the HTML tokenizer does: the 2,125 names with their `;`,
the 106 names of HTML 4 also without it (the longest match wins), and
decimal and hexadecimal references, with the replacements HTML gives to
zero, to surrogates, to numbers above U+10FFFF and to the Windows-1252
block. Text that is not a reference stays as it is, so no input throws.
With `attribute: true` a name without its `;` is left alone when `=`, a
letter or a digit follows it. That is the rule of the tokenizer for
attribute values, and it keeps the `&copy=` of a query string intact.

A string without a reference is returned as it is. Otherwise the output
is allocated once at the length of the input and handed to JavaScriptCore
without a copy. Latin-1 input stays Latin-1 until a reference names a
character outside of it, and UTF-16 input is read as UTF-16, so a lone
surrogate passes through. bun_md::entity::lookup is made public for the
names.

SOUP.md also records that the patch that merged a bun.lock with git
conflict markers was dropped in the rebase onto oven-sh#44968.
robobun added a commit to robobun/soup that referenced this pull request Nov 2, 2026
When two branches change dependencies, git leaves conflict markers in
bun.lock. The lockfile then failed to parse, bun install ignored it and
resolved every dependency again, so every pinned version in the project
could move. With --frozen-lockfile the error did not name the cause.

The loader now reads both sides of the conflict and merges them. A
package that one side locks keeps its version. When the sides put two
versions of a package at one path, the higher version keeps the path,
what is below the path comes from the side of that version, and a
dependency whose range does not take it gets the version of its own
side. Every dependency is checked against its range after the merge,
with the rules the resolver uses for overrides, catalogs, peers and
npm: aliases. bun install then compares the merge with package.json.
The packages of both sides stay until that is done, so a range that
package.json kept gets the version that its side locked. A dependency
with a package on neither side goes to the registry.

bun install --frozen-lockfile, bun ci and --production fail with an
error that names the conflict. Commands that only read the lockfile read
the merge when it is complete. bun dedupe, bun pm trust and bun audit
fix ask for a bun install first. When the sides cannot be merged, for
example with two integrity hashes for one version, the lockfile is
ignored as before and a note says why.

A lockfile that parses does no new work: the merge starts from the arm
that handles the parse error.

This patch left the stack in the rebase onto oven-sh#44968, which
rewrote the lockfile loader and the commands it hooks into. It is
applied again here on the new code: the hooks are placed by hand, and
merge_conflict.rs changed only where upstream now does the work itself
(the loader no longer records npm: aliases, and the alias rule of the
resolver is asked through Group::is_satisfied_by_a_bound_of).
dylan-conway pushed a commit that referenced this pull request Nov 2, 2026
)

Lint-ratchet maintenance, no reachable defect: `main` already refuses a
graph for a module with more than 65535 requests. #44968 left out #39165
and #40312 of this category ("refactor nothing needs"). Merged #39169
has this shape. @alii decides whether this is wanted.

### Problem
- `serialize()` in `src/bundler/prelinked_module_graph.rs` packs a `u32`
request index into 16 bits of each import and export entry, bounded
there only by a `debug_assert!`.
- The release bound is one function up, in `build()`, on the request
count. mordant reports `bound_checked_only_in_debug`.

### Fix
- `parse_module` converts the request count once
(`u16::try_from(count).ok()?`) and takes every index from
`0..request_count`, as a `u16`. The const, the count test and the
assertion are gone.
- Removes
`"bound_checked_only_in_debug:src/bundler/prelinked_module_graph.rs" =
1` from `mordant-baseline.toml`.
- Verified: `bun run rust:mordant` (564c8da, 3 targets) exits 0. Empty
baseline: 0 findings in the file (`main`: 1).
`test/bundler/bundler_compile_prelinked.test.ts`: 83 pass (debug build).
Same bytes as `main` on 4033 graphs in a model of the file.
- Self-reviewed: 9 concerns raised, 9 addressed.

### Background
- `bun build --compile --bytecode` embeds a pre-resolved module graph
for JavaScriptCore. A request is one module that a chunk imports.
`PrelinkedModuleGraph::tryCreate` refuses more than 65535 per module.
- Considered an `assert!` at the shift: it costs a compare and a branch
per entry.

### Downsides
- Two new test cases build executables with 65535 and 65536 imports: 1
to 3 s each on release, 13 to 35 s on debug with ASAN, no CI timing yet.
- They pass on `main` too, so they hold the limit and prove no fix. The
65536 case builds the `module_info` fallback executable and does not run
it.

Behaviour change: none

<details><summary>Notes</summary>

**Why nothing observable changes**

- On `main`, `build()` returns `None` when `module.requests.len() >
MAX_REQUESTS_PER_MODULE` (65535), and the only call of `serialize()`
comes after that test. So the shift never sees an index above 65534 in
any build.
- The limit was stated twice. The const and its count test are one
statement. The `debug_assert!` on the index is the second, with a `u32`
between them, and it is a different inequality (it allows index 65535).
- mordant reports the shift because the lint looks at one function, and
the release bound is in another one.

**Measurements**

- mordant (564c8da, 3 targets): `bun run rust:mordant` exits 0 with the
baseline line deleted. Findings in `prelinked_module_graph.rs` with an
empty baseline: 0 (`main`: 1). The baseline goes from 495 accepted
findings to 494.
- Limit on a real binary (debug build of this PR, `bun build --compile
--bytecode --format=esm --packages=external`): 65535 requests give a
graph with `requestCount` 65535 and largest packed index 65534. 65536
requests give exit 0 and no graph. A release build with `main`'s version
of the file gives the same two results.
- Serialized bytes: a model that compiles the real file text against
stubbed dependencies gives byte-identical output for `main`'s file and
this PR's file on 33 boundary graphs and 4000 random graphs (0
differences). The model is not a bun build.
- Release-profile object of `bun_bundler` (one codegen unit, no LTO),
`main` against this PR: `.text` 1,893,032 to 1,892,349 bytes (-683),
`.rodata` 83,806 to 83,806. Symbols that name `prelinked_module_graph`:
34,043 to 33,360 bytes. `build` (with `parse_module` and `serialize`
inlined): 3267 to 3175 instructions, 336 to 328 conditional branches.
Limit compares against `0xffff`: 1 per module and 0 per request in both.
- Not measured: the linked release binary. The numbers above are from
the crate's object before the link.
- Struct sizes (`-Zprint-type-sizes`): `Import` 28 to 24 bytes, `Export`
28 to 24 bytes, a star-export element 4 to 2 bytes. These exist only
during the build. The serialized layout does not change, and `VERSION`
stays 2.
- Over-limit module (65536 requests, `build` returns `None`), same model
with a counting allocator: 84 to 11 allocator calls, 14.7 MB to 5.2 MB
requested. The refusal now happens before the module's requests are
parsed.
- Diff: `prelinked_module_graph.rs` +21/-26, `mordant-baseline.toml`
+0/-1, test +68/-0.

**The new test cases**

- `compile/prelinked/RequestLimit65535` and `RequestLimit65536` build an
entry with that many external imports and read the executable. They do
not run it.
- They pass before and after this change (checked on a release build of
`367d939d9`, which has `main`'s version of the file). They hold a limit
that had no test and that now depends on one line. A build with that
line changed to `.unwrap_or(u16::MAX)` fails the 65536 case.
- Time per case: 0.5 to 2.6 s on a release build (three runs). On a
debug build with ASAN: 12.7 s and 25.1 s in a run of the whole file,
18.0 s and 34.7 s when only these two run. An existing case of this file
takes about 30 s on that debug build.
- The entry is built as a `Buffer`. `itBundled` sends a string through
`dedent()`. With the entry as a string, the two cases took 42 s and 57 s
in a run of the whole file on the debug build.
- The build subprocess of `itBundled` has a 60 s limit. The build step
of these cases takes 12 to 17 s on the debug build.
- A module over the limit is the only way an executable gets per-module
`module_info` bodies in place of a graph. No other bundler test builds a
module over the limit. `RequestLimit65536` has no `run`, so nothing
executes that path.

**What moved**

- The refusal of a module over the limit now happens at the head of
`parse_module`'s request section, before the requests are parsed.
`build()` interns the same strings before that point as it did before.
- The check reads the length of `requested_modules_keys`. The old check
read the length of the zipped request list. The three request vectors of
a `ModuleInfo` have one writer and the same length, so the two are
equal.
- The four `as usize` reads of the index became `usize::from`, because
the type now allows it.

**Left as they are**

- `bound_checked_only_in_debug:src/bundler/Chunk.rs = 3` (`Query::new`,
`EntryPoint::non_entry_point`, `set_entry_point_id`): same lint and
crate, no shared type with this file.
- The `kind` and `resolution` fields of the same packed word are `u32`
values from a `repr(u8)` enum and a five-value table. mordant does not
flag them.
- A local export keeps the placeholder `request: 0`. Nothing reads it as
an index.

</details>
robobun added a commit to robobun/soup that referenced this pull request Nov 4, 2026
Bun.escapeHTML() had no inverse. HTMLRewriter returns text and attribute
values as they are written in the document, so `&amp;` stays `&amp;`, and
a script that wants the text a person reads had to install a package to
decode it, although the table of the named character references of HTML
is already in the binary for Bun.markdown.

Bun.unescapeHTML(input, options?) replaces the character references of a
string the way the HTML tokenizer does: the 2,125 names with their `;`,
the 106 names of HTML 4 also without it (the longest match wins), and
decimal and hexadecimal references, with the replacements HTML gives to
zero, to surrogates, to numbers above U+10FFFF and to the Windows-1252
block. Text that is not a reference stays as it is, so no input throws.
With `attribute: true` a name without its `;` is left alone when `=`, a
letter or a digit follows it. That is the rule of the tokenizer for
attribute values, and it keeps the `&copy=` of a query string intact.

A string without a reference is returned as it is. Otherwise the output
is allocated once at the length of the input and handed to JavaScriptCore
without a copy. Latin-1 input stays Latin-1 until a reference names a
character outside of it, and UTF-16 input is read as UTF-16, so a lone
surrogate passes through. bun_md::entity::lookup is made public for the
names.

SOUP.md also records that the patch that merged a bun.lock with git
conflict markers was dropped in the rebase onto oven-sh#44968.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment