install: consolidate the open package manager pull requests

Merged
JA
1mo ago
+150693-85553

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.

absorbedin partnot takenobsoletetotalissues closed
464407419597133

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 (#41931; refs #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 (#40628, #37127; fixes #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) (#29794, #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 (#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) (#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 (#43401, #43400; fixes #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 (#38923; fixes #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 (#30522; refs #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 (#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 (#42001).

Decisions that need the maintainer's confirmation

  • Frozen installs start failing (2). Of 241 bun.lockb files from this repo's release tags, 6 that pass on main fail here (4 real drift).
  • A frozen install with only a foreign lockfile writes nothing: no bun.lock, no package.json edit, one note (#38804). Reverses #38333.
  • Changes 1, 9 (teams on 1.4 and 1.5 see the [] line come and go), 10, 11, 12 (removing linker = "isolated" no longer switches a tree), 14, 18.
  • The packageExtensions lockfile key (15): without it an edited or removed extension cannot be told from the package's own deps. #43104 / #43209 are out for needing a key.
  • Scripts that run now and did not: #41803 (its opposite #41866 is out), #41845, #39443, #43913, #42001, #38780, bunx --trust. No longer: root scripts under --dry-run; under frozen, scripts of an installed package trusted after the lockfile was saved (main: every frozen install; now a plain install once, frozen never, silently).
  • No state file, so a killed install goes unnoticed (19). The complete fix is one bit or list per project, read once per install.
  • Tarball cache entries named by integrity; full manifests by package name for libc; #38813; +1 renameat per linked package (#38783); a token survives only an http -> https redirect on one hostname (#38001); #39603 flips a row of #38867.
  • Hoisted thread pool on macOS: on; CI only, no stress run.
  • Open: mask URLs in source excerpts under a diagnostic (not done); should bun pm pack follow a symlinked node_modules (it does, #41925).

New features and flags

  • Yarn Berry migration (on main since): here package.json is written just before bun.lock and only if that is saved; link: stays link:; libc is honoured (#40057)
  • link:./dir, link:../x, absolute paths: symlink to that directory, relative to the declaring package.json, as Yarn / pnpm; link:foo stays a bun link name (#40025; fixes #4719, #5045)
  • packageExtensions in the root package.json adds missing deps / peers to registry packages, can mark a peer optional (#40053)
  • bun pm cache pack <file>/unpack <file>: the cache entries a lockfile needs as one file (CI); unpack adds what is missing, refuses a damaged pack whole (#40012)
  • bun pm cache prune [--max-age <days>] [--dry-run]: drops cache entries older than 30 days, not the global store (#42592)
  • bun pm fetch: downloads all deps (pinned git too) into the cache; no node_modules, scripts or lockfile write (#29509)
  • bun publish --recursive/--filter <pattern>: packs the selected workspaces, publishes deps first, skips private and already published ones; the first failure ends the run (#41292)
  • bun update --depth 0: direct deps only, transitive ones stay locked (#41720; fixes #41625)
  • bun prune --check: exit 1 when anything would be removed (#39216)
  • minimumReleaseAgeExcludes takes name@version; new --minimum-release-age-excludes a,b@1.2.3; bun audit fix honours the list (#37909, #42803; fixes #28967, #42799)
  • minimumReleaseAge accepts 90m, 12 hours, 3d, 2 weeks; nan/inf are config errors; 0 no longer fetches full manifests (#30529)
  • libc: on Linux only the glibc or musl variant of an optional native package is installed; recorded in bun.lock; --libc overrides (#38797; fixes #16282)
  • bunx --cwd <dir> (#28669; fixes #28668)
  • bunx --env-file <file> (main: unrecognised dependency format: @.env); not bun --env-file F x pkg (#35599; fixes #12430, #17759)
  • bunx --minimum-release-age, part of its cache key (#30751; fixes #30748)
  • --env-file, --no-env-file, bunfig env = false for all package manager commands (main: --env-file ci.env added the package ci.env) (#36672; fixes #12011, #31450)
  • npm-shrinkwrap.json is migrated like package-lock.json (#38861)
  • Deprecated versions are reported: one line per direct dep, one for the rest, all with --verbose (#35457; fixes #6883)
  • bun add -d | --optional | --peer <pkg> moves a package another group lists; --save-prod moves it back (#36284; fixes #4852, #5714)
  • bun audit fix reports and rewrites the overrides rule that holds a vulnerable version (#38785)
  • bun pm pkg set/delete: arrays (append, index), bracketed names (exports[./package.json]) (#38025; fixes #22035)
  • --dry-run for bun link, unlink, pm version, pm cache rm, pm trust, pm pkg, pm migrate (main: they wrote) (#41690)
  • bun publish honours publishConfig.registry and publishConfig["@scope:registry"] (#38322; fixes #18670)
  • Auto-install reads registries and credentials from .npmrc (main: @corp/x came from the default registry) (#44754)
  • NPM_CONFIG_USERCONFIG names the user .npmrc, as actions/setup-node sets it (#38047)
  • Cache-Control: no-cache, no-store, max-age under 300 s on registry metadata (#41644)
  • Workspace can depend on the root package with workspace:*, also for pack / publish (#36594; fixes #2773, #5176)
  • "gypfile": false turns off the implicit node-gyp rebuild; a trusted tarball, git, file: package with binding.gyp and no scripts is built like a registry one (#43913; fixes #43903)
  • Scripts get npm_package_name/version/json of their own package, npm_config_local_prefix, npm_package_config_*, npm_lifecycle_event, npm_lifecycle_script; inherited npm_package_* are dropped (#36690, #38110; fixes #12752)
  • Hoisted linker links packages on up to 8 threads, whatever node_modules holds (nothing, a part, other versions, --force) and after a download; not on Windows; file:/link: / workspace / trusted deps, bins and scripts stay on the main thread; everything in today's order, scripts too; a cold install is no faster (#29496)

Security

  • Registry URL that is not http(s) (htps://, no scheme): pm view, whoami, publish, audit, pm diff sent the token there over plain TCP; a bad BUN_CONFIG_REGISTRY fell back to another registry with the token -> error, no request, on every route (#41662, #41773)
  • Token, _auth, user or password with CR, LF, NUL could inject headers, as could --otp, --user-agent -> refused (#37458, #41661)
  • token = "$NPM_TOKEN" with the variable unset sent the literal text -> warning, nothing sent (#41049)
  • Passwords and tokens in registry, tarball, git URLs were printed in errors, warnings, summaries, --verbose, pm ls/why/view/diff, audit, migrations -> masked (#38977, #38997, #38817)
  • Control characters from manifests, lockfiles, registry answers reached the terminal (pm view, licenses, untrusted, audit, resolutions, bin names, errors) -> escaped (#38536, #38631, #39358, #38525)
  • Dep names .bin, node_modules, a bare @scope, with control characters, empty or scope-only were installed or requested -> refused; skipped with a warning in a third party's manifest (#44439, #38615, #43331, #41735)
  • Registry, git or tarball package could link: a directory, use a local .tgz of the project or a file: path that leaves it -> refused at resolve and install time (#39023, #38986, #43101)
  • Dep's own npm: alias could replace a same-named direct dep of the root; an alias in a refused or stale lockfile kept redirecting -> the root's dep wins; loaders register no aliases (#44412, #44294)
  • Frozen install asked the registry for a range that bun.lock links to a workspace missing on disk (dependency confusion) -> fails without a request (#43471)
  • Global store: a trusted package's script could modify shared deps; pm trust ran scripts in the store, saw nothing under isolated -> its closure is project-local (#40239, #35106)
  • Failing script of an optional workspace / link: dep deleted that folder -> only what the linker placed goes (#43226)
  • bun prune, update, dedupe removed packages behind a symlinked workspace node_modules -> prune refuses (exit 1), the others skip it (#44484; fixes #44669)
  • bun add -g, link, unlink replaced or deleted foreign files in the global bin directory -> only what Bun made (#39745)
  • bun pm cache rm could delete the project, home or Bun's directory (an empty BUN_INSTALL_CACHE_DIR meant the project) -> refused (#39750)
  • Project's bunfig.toml could point globalDir/globalBinDir anywhere -> ignored with a warning (#41775)
  • bunx installed into the shared temp directory, where another user's node_modules / package.json was loaded -> <cache>/.bunx-<uid>, 0700 (#44393, #31447)
  • Applying a patch followed symlinks and wrote through hard links -> refused (#32677)
  • Tarball could make Bun allocate the disk or memory its headers claim; sparse members keep their holes (#36542)
  • minimumReleaseAge: an exact version passed when only the abbreviated manifest was cached; so did a publish time that is not a date (#38659, #43307)
  • BUN_CONFIG_NO_VERIFY=0 turned verification off, =1 did not -> off only for 1 or true (#38310)
  • Manifest hash Bun cannot use skipped verification silently -> warning (#38484)
  • bun audit took a malformed registry answer for a clean audit -> failed audit; a scoped registry is named and skipped (#38557)
  • Pack: keys of a bundled dep that are not package names pulled in outside files; **/a/b patterns, a BOM, trailing spaces disabled ignore rules; a symlinked README was sent (#44902, #41531, #41871)
  • bun publish published with "private": "true" or 1 -> any truthy value is private (#41664)
  • Auto-install read a package.json version from the wrong buffer, an alias could redirect other packages (#42839)

Install core

  • Killed or failed install left a half-linked package that later installs accepted -> linked in a staging directory, renamed into place (#38783; fixes #43256)
  • Switching hoisted <-> isolated left a mixed tree -> whole tree replaced, workspaces' node_modules too (#38779)
  • Each linker switch kept a copy of the old tree in node_modules/.old_modules-<hex> -> deleted after the install; bun prune collects old ones
  • --dry-run ran root lifecycle scripts and wrote yarn.lock (#38830)
  • bunfig [install] dryRun = true was ignored -> honoured, also by publish, pm pack, prune, dedupe, audit fix (#34979)
  • Empty package.json (root or member) deleted the lockfile, exit 0 -> error, lockfile kept (#39666)
  • Download that failed for an optional dep was skipped (exit 0) although another dep or a non-optional peer needs it -> error: failed to download <name>@<version>, exit 1 (#43197, #43219; fixes #43214)
  • --offline failed on what is below an optional dep it had skipped -> only misses still reachable are errors (#43185)
  • --offline exited 0 without a required dep of an optional package installed in one place, missing in another
  • Required direct dep left out for os/cpu was silent -> warning; bun add reports it as skipped (#41956)
  • --production/--omit=dev failed on a workspace:, link:, file: devDependency whose target is missing -> left out (#36704; fixes #14134, #15102)
  • --production installed, and ran scripts of, a devDependency that is also an optional peer; bun prune --production removes it (#42040)
  • Commands that only read package.json opened it for writing -> work on a read-only file (#38745)
  • $TMPDIR on another file system than the cache kept a probe file per install (#38859)
  • bun add, remove, update, patch rewrote a package.json in which nothing changed
  • Infinity/NaN were written into package.json -> null (#41743)
  • Relative --cwd was applied twice by bun update, link, unlink, why (#31154; fixes #31152)
  • Warnings from native code appeared at exit -> flushed when printed, also from Workers (#43004; fixes #42989)
  • bun --bun run worked only for the first user of a shared machine -> node shim directory per user (#35565; fixes #7504)

Resolver and version picking

  • Range picked a deprecated version (@types/* stubs under *) -> prefers one that is not, like npm; a locked version stays until bun update (#36630; fixes #7571, #25314)
  • bun add pkg installed a deprecated latest -> highest release that is not deprecated; pkg@latest still follows the tag (#40317; fixes #40310)
  • Missing or dangling dist-tags.latest failed add, install, update --latest, hid the package from outdated/update -i -> highest release, with a warning (#36671; fixes #12041)
  • *, npm:pkg, npm:pkg@* skipped a prerelease latest -> follow the tag, like npm (#39774)
  • Wide range (*, >=1) nested the newest version -> shares a resolved version that satisfies it; bun update no longer re-adds what bun dedupe removed (#38770)
  • 1.0.0 || ^2.0.0, =2.5.0 <2 resolved as their first exact version (#30076)
  • >=0.5 0 was read as alternatives -> intersection, like npm (#32993)
  • ^, ~, x-ranges admitted prereleases of their upper bound (pkg@>=5.0.0-alpha.0 <8 overrode ^4) -> end at <X.Y.Z-0 (#43102; fixes #41371)
  • Exact prerelease met a range that does not admit it (overrides keys) (#43150; fixes #43138)
  • >1 meant >1.0.0 -> >=2.0.0; >1.2 -> >=1.3.0, like node-semver (#43253)
  • <x, <*, >x, >* matched everything -> nothing; ^x, ~x match any version (#43258)
  • Version number above 2^64-1 was read as 0 -> invalid (#42458)
  • Bun.semver.satisfies() was true for "", 1.2.3.4, 1.2.3 junk (#32998)
  • satisfies("1.0.0+build-1", "^1.0.0") was false; < =1 was misread
  • pkg@2x, 1beta, 1.2x were read as ranges -> dist-tags (#35259; fixes #19065)
  • Words starting with ssh/http were read as URLs -> dist-tags (#37133)
  • Leading whitespace in a version literal (tag, folder, tarball, npm:, catalog:) broke the install, lost a pin in bun add --catalog, or panicked (#38331, #38309)
  • Version published after the manifest was cached failed for up to 5 minutes -> manifest fetched again once (#39790)
  • Root dep edited to another package (npm: alias) under a range the old version fits kept the old package (#41774)
  • Plain dep got a same-named npm: alias's package outside its range (#43267)
  • New dep that follows an npm: alias asked for a garbled name (GET /1.0.0no-deps - 404) (#43515; fixes #23264)
  • file: and link: on one directory became one package (#38114)
  • file: folder in both dependencies and devDependencies was duplicated (#41833)
  • Editing package.json re-downloaded GitHub / tarball deps, re-fetched git ones that bun.lock resolves (#37142)
  • Registry package that bundles a file:../x dep failed to install (#43125)
  • Bundled dep's dep took a slot the bundling package resolves through (#38848; fixes #29263)
  • bundleDependencies were kept from one dependency group of a manifest only (#38857)
  • Tarball, folder or git package whose own name is invalid was installed under it -> refused (#38633)
  • Folder, tarball, git packages without a name got broken lockfile rows -> named after their source (#38681; fixes #17060)
  • Dangling node_modules symlink was a resolver error -> missing module; isolated: a failed optional dep goes with the links to it (#38756)

Hoisted linker

  • overlayfs: a replaced package of a lower layer kept its old files beside the new ones, exit 0
  • Install over an existing node_modules hung when the OS refused the pool's first thread
  • bun prune left the .old-<random> of a killed install
  • macOS: clonefile kept a file already at the destination -> replaced (a nested package could hold two versions' files)
  • Package was hoisted next to an out-of-range version of its peer (#36300; fixes #20376)
  • Dep nested under a package with bundledDependencies resolved the bundled version (#43377; fixes #43351)
  • After a version bump the old version of a transitive dep stayed at the root (#41053)
  • Required peer that a migrated lockfile left unbound stayed unbound (#39343)
  • Tree saved after a ranged peer gained a higher candidate differed from the next install's (#38767)
  • file: override on a registry package's or transitive dep was not linked or resolved from the wrong directory -> from the project root, linked with its deps (#38994, #44339; fixes #44299)
  • One file: folder that cannot be hard-linked (EXDEV) switched all later packages to copyfile (#40663)
  • Failed optional dep left its .bin links (#44931)

Isolated linker, global store

  • node_modules or entry that is a mount point (Docker volume) is kept and reset in place
  • bun add -g never linked bins into the global bin directory (#30451; fixes #28597, #30450)
  • Optional dep whose tarball cannot be downloaded or is corrupt failed the install -> skipped with a warning, also --offline (#43229; fixes #43212)
  • node_modules/.bun/node_modules changed between installs of one lockfile with npm: aliases -> links named by the dep, one layout (#40356; fixes #40355)
  • Packages that are peers of each other were installed twice (#29345; fixes #29343)
  • Missing link: target was accepted -> install fails, as hoisted (#38045)
  • Registry package's own file:./vendor/x dep failed -> linked from inside the package (#38856)
  • Workspace's scripts ran before the root's deps and bins were there -> after everything else; a bin such a script writes is linked for every dependent (#44868)
  • Empty directory, or one without package.json, in a link slot stayed (#41453)
  • Store names of non-ASCII paths were garbled (#40719)
  • Package used under its name and an alias got bins for one dependent only (#44446)
  • --omit=peer dropped a peer an ancestor provides; bun prune --omit=peer agrees (#41416)
  • Files deleted from a file: folder stayed in its copy (#37138)
  • Links of a renamed or removed workspace stayed in node_modules (both linkers) (#38511)
  • bun prune left dangling links to removed optional / peer deps in store entries (#42625)
  • Switching the global install's linker removed bun link registrations (#43582)
  • Global store: a package whose peer is typed by the project's @types/* was shared, TypeScript missed the types -> project-local (#36796; fixes #36795)
  • Ranged peer whose auto-installed version drops out of the saved tree was linked to nothing (#38768)

Bins

  • Hoisted: with --silent a failed bin link exited 0 and saved the lockfile -> 1, nothing saved (#44997, #44958)
  • Every install recreated .bin links, failed with EEXIST on a read-only up-to-date tree -> a right link is left alone (per bin: chmod 1 -> 0, unlink 1 -> 0, symlink 2 -> 1) (#36701, #20067; fixes #14736)
  • Several packages with one bin name: an arbitrary one won -> direct deps first, then registry / git / tarball before workspace, file:, link: packages, then by name (#31416, #41867)
  • Isolated made bin targets 0777 -> 0777 & ~umask (#41509)
  • One bin that fails to link stopped the rest (#38972)
  • bin entry whose value is not a string dropped the package's other commands (#43159; fixes #43148)
  • File system that refuses symlinks (EACCES / EPERM) failed the install -> one warning, no .bin links (#40161)
  • Bare bun update -g/bun ci -g linked no bins; a reinstalled global package gets its command back (#43384)
  • bun pm migrate/pm trust wrote uninitialised bin bytes into bun.lockb (#37581)

bun.lock, bun.lockb

  • Corrupt or crafted bun.lockb crashed or hung install, pm ls, why, outdated -> validated at load, refused with a warning; files by Bun 0.x are normalised and keep loading (#32753, #39720)
  • Two strings whose hashes collide became one in the lockfile (#41642)
  • Root file: or registry dep named like a workspace made Bun write a lockfile it refuses (Duplicate package path) -> the member beats file:; a registry range it does not satisfy gets the root slot (#37245, #37248)
  • Root alias of the registry package named like a workspace dropped the workspace (#43567)
  • file: paths from the lockfile were resolved from the wrong directory after bun update or an override removal (#38850)
  • Re-resolving install dropped packages bun.lock holds only through optional peers (#39002)
  • Package printed at several paths got its edges from the wrong row (#39347)
  • Re-saving moved a bundled peer off its entry's version, and a peer nothing satisfies (#38837, #38892)
  • bun add installed nothing when bun.lock repeats a dependency line (#43271)
  • --lockfile-only saved after an error; --silent failed on a bun.lock it ignores (#44364)
  • bun.lock, bun.lockb, yarn.lock ignored the umask (yarn.lock 0666) (#41808)
  • bun <path>.lockb printed the directory's lockfile, read .env, needed package.json -> prints the given file (git textconv works) (#44870)
  • git+git:// resolutions did not load back from bun.lock (#38798)
  • bun pm migrate/pm trust wrote another configVersion/patchedDependencies than install; yarn entries without #sha1 (#41908; fixes #38193)
  • Absolute or over-long workspace path in bun.lock / pnpm-lock.yaml crashed or counted as pruned (#43405)
  • bun install --yarn rewrote an unchanged yarn.lock every time

--frozen-lockfile, bun ci

  • bun ci failed in a workspace when nothing changed (member with a script and a latest row); one member's edit moved pins elsewhere -> members are compared row by row, keep their pins (#44919; fixes #26070)
  • Version the lockfile pins was blocked by minimumReleaseAge (#40032; fixes #40031)
  • After a migration with a root postinstall, or pm migrate to bun.lockb, the next frozen install failed on the meta hash (#44501)
  • With both overrides and resolutions only overrides applied -> both; a lockfile without those rules fails frozen, 1.4's too (#38811)
  • Direct dep pinned outside its unchanged range (one-sided merge) was installed, frozen passed -> re-resolved; frozen fails (#43375; fixes #43795)

Migrations (npm, pnpm, Yarn v1)

  • Yarn Berry: a range yarn.lock has no key for is the workspace that satisfies it; Yarn's own packageExtensions; resolutions keyed on an alias; a name in several groups of a workspace; catalog: in an extension; %xx in a folder name; clearer refusals (#45052)
  • Yarn v1, npm: a dep the lockfile has no entry for is reported (#45052)
  • npm: a scope registry with a path and no trailing slash gave InvalidNPMLockfile (#38698)
  • npm: a package's own file: directory was resolved from the project; one that climbs out is skipped (#38990)
  • npm: a file: directory copied by --install-links failed with EISDIR (#43103)
  • pnpm: pnpm-workspace.yaml was read only with a migratable lockfile; bun patch --commit then segfaulted (#38754)
  • pnpm: the pnpm block was removed from package.json -> kept, fields copied (#38775)
  • pnpm: onlyBuiltDependencies was lost -> trustedDependencies; isolated reports blocked scripts (#38780)
  • pnpm: importer rows differed from a package.json parse, the next install rewrote bun.lock (#38791)
  • pnpm: only used catalog entries were migrated (#38829)
  • pnpm: non-string keys / values in pnpm-workspace.yaml crashed -> scalars copied as written (react: 18 -> "18") (#44470)
  • Read-only commands and failed installs wrote a migrated lockfile and package.json edits -> only a command that saves a lockfile (#38804)
  • Yarn v1: workspace packages were dropped when the root has no deps (#38878)
  • Yarn v1: npm: alias packages were named after the tarball URL (#38948)
  • Yarn v1: registry entries without resolved failed (#38985)
  • Yarn v1: an entry without a buildable resolution aborted the migration -> skipped with a warning (#39001)

Workspaces, catalogs

  • Member without a name failed the install -> skipped (#38781; fixes #6317)
  • Dist-tag (latest, "") on an unpublished sibling got a 404 -> links the workspace (#35468; fixes #4830)
  • Empty workspace: spec linked the project into itself -> error; a glob like ../* made the root its own member (#41653)
  • Workspace globs skipped symlinked member directories (#41567)
  • Range linked to a workspace stayed linked after the root dropped it (#43468)
  • Workspaces of a package.json in / (or C:\) were not found (#43404)
  • workspaces glob that fails named no path (#43517)
  • bun update <name> rewrote workspace:^, file: and other non-registry entries (#38847)
  • bun add member@workspace:^ lost the spelling (#38866)
  • bun update printed no row, or another catalog's version, for a moved catalog entry (#38763)
  • bun update -i updated one of several catalogs defining a package -> a row per catalog (#36446)
  • Failed catalog: dep always suggested bun add --catalog -> only where that works (#44638)
  • npm run <script> -w <ws>/--workspaces inside a script recursed forever -> runs the workspace's script (#33800; fixes #22719, #24838, #33796, #41543)

Overrides, peers

  • Auto-installed peer took the newest version of one asker -> newest that everyone asking accepts (#36646; fixes #8292, #15711)
  • * peers resolved by manifest arrival order -> after their siblings; unprovided peers in a fixed order (#37713)
  • incorrect peer dependency was checked against any copy in the tree, hoisted only, every install -> the copy each dependent resolves, both linkers, only when something was resolved (#41936; fixes #7403)
  • Peer that a file:, link:, tarball or git dep provides was looked up in the registry (#33156)
  • Peer dependency on bun downloaded the bun npm package (80-170 MB) -> optional (#39757)
  • minimumReleaseAge blocking a peer or optional pin failed the install -> warning (#38995)
  • bun update name@version bound a peer row to a stale lockfile leftover (#38901)
  • Override replaced the link to a member of that name (#37271)
  • Changing overrides / catalogs re-resolved rows a package no longer owns (#38849)
  • bun update <name> left rows of a $name override stale, frozen passed (#43981; fixes #43975)
  • bun update --latest moved versions an override selects; -r wrote "latest" into package.json (#44485)
  • overrides/resolutions that are not objects were ignored -> error (#40729)
  • Keys foo@catalog:, my-foo@npm:foo[@^1] (Yarn) were ignored with a warning

Registries, credentials, .npmrc, bunfig

  • .npmrc credentials follow npm's key walk: //host/ covers paths below it, _auth sent as written, a line reaches registries named by --registry / env / $VAR, a tarball on another https host gets that host's line; a sibling or look-alike path inherits nothing; :443/:80 keys keep working (#40423; fixes #28233, #30311, #30513, #40549)
  • Registry URL without a trailing slash lost its last path segment (#36294; fixes #5368, #14017, #16185, #20373)
  • user:pass@ in a registry URL from an env var was dropped (#38834)
  • user@/user:@ sent nothing -> Basic auth with an empty password (#39063)
  • URL credentials were sent percent-encoded -> decoded (p%40ss sends p@ss) (#39603)
  • .npmrc: ;/# comments were part of the value; ignore-scripts=1, =True, empty did nothing -> as npm (#41916)
  • .npmrc keys and values kept Unicode whitespace (#41714)
  • --ca/--cafile were shadowed by a config file's cafile -> one setting, a later layer replaces both (#41898)
  • -g commands ignored globalDir from --config and ~/.bunfig.toml (#35454; fixes #4553; refs #12886)
  • ~ in cache.dir, globalDir, globalBinDir, cafile was a literal directory (#36667; fixes #10152, #26715)
  • [install.cache] disable = true still cached manifests (#36219)
  • Unreadable .npmrc / bunfig.toml was skipped silently -> warning (#41693)
  • bun publish, pm whoami, audit, pm view, pm diff ignored http_proxy and NODE_TLS_REJECT_UNAUTHORIZED=0 (#38075, #38093)
  • bun pm whoami sent a stub CI name -> the real vendor (ci/github-actions) (#40805)

Network, retries

  • Retries came at once, one network error halved concurrency -> 250 ms doubling to 10 s with jitter, Retry-After, 429 / 408 retried, certificate errors not (#40009)
  • Timed-out request was retried silently -> a warning per retry (#35168)
  • BUN_CONFIG_MAX_HTTP_REQUESTS was ignored (#38744)
  • Help and docs said --network-concurrency defaults to 48 -> 64 (#38759)

Cache, tarballs, extraction

  • Process could exit before the manifests it fetched were cached: --offline right after an install could fail
  • file: / URL tarballs were cached by path or URL, two projects or branches got each other's package -> by the integrity in bun.lock (#39016)
  • file: / URL tarball deps were reinstalled on every run (hoisted) -> verified by a .bun-tag (#35576; fixes #8260)
  • --force, bun update, re-adding the same path / URL kept old bytes or the old hash -> direct tarball deps are fetched again (#31868; fixes #31864)
  • Re-extraction leaked the displaced cache folder; extraction directories stayed in $TMPDIR (#38702, #33979; fixes #33977; refs #28062)
  • Two registries on one host (port, path, http) shared cache entries (#41636)
  • Concurrent installs sharing a cache deleted each other's entries (ENOENT without RENAME_EXCHANGE, NFS) -> first writer wins (#36229; fixes #36227)
  • Gzip trailer was not verified (since 1.3.14) -> a damaged tarball fails; also Bun.Archive (#41503)
  • Body that is not gzip installed as an empty package; a tarball without package.json stayed in the cache
  • Query string / fragment of a tarball URL ended up in the extraction folder name (#39011)
  • github: tarballs extracted symlink entries -> skipped, like npm (#31481)
  • Rewriting a cached manifest unlinked it first -> atomic replace (#38916)
  • --no-verify cached unchecked bytes under the lockfile's hash, for all projects -> own key; isolated kept such a copy

git, GitHub, folder deps

  • Cached git dep never saw tags / branches pushed later; bun update follows moved refs (#35566; fixes #11548, #13769, #15336, #18947)
  • git+ssh://git@host:2222/.. lost its port in the ssh fallback (#36932; fixes #36931)
  • deploy@host:org/repo.git became git@deploy@host (#35026)
  • bun add name@git+ssh://host/repo.git lost the alias for hosts other than GitHub (#36687; fixes #13891)
  • github:o/r#feat%2Fx silently installed the default branch; an escaped owner / repo is an error (#43172)
  • Optional git dep that cannot be cloned or lacks the ref failed the install -> skipped with a warning, also when locked (#43076, #43352; fixes #43350)
  • Several github: / git refs on one commit, or a file: folder declared by several packages, failed with EEXIST or wrote a lockfile Bun refuses -> one package (#42640, #42545)
  • Concurrent cold installs replaced each other's git cache folder (#42656)
  • GIT_DIR (git hooks) made install and bun create work on another repository -> 13 git variables are not passed on (#41715)
  • file: folders declared by git / tarball packages were resolved from the project (#38816)
  • Bare word or git@host/path was handed to git clone as a local path; a URL could be read as an option
  • bun link -g/unlink -g failed; bun link -g <name> removed the registration -> -g is ignored (#33142; fixes #21397, #33141)

Lifecycle scripts, trust

  • Hoisted: beyond --concurrent-scripts, the next script waited for all running ones to end -> for one
  • Failed or unstartable script left other packages in place whose scripts never ran, and never would -> removed
  • trustedDependencies entry added to a member after the first install did nothing -> its blocked scripts run once (#41803)
  • Isolated: a package trusted after it was installed never ran its scripts (#41845)
  • Package installed through an npm: alias could not be trusted consistently -> by its real name in package.json, --trust, pm trust, bun.lock (#39443)
  • bun pm untrusted/trust/ls --trusted followed a stale lockfile, knew nothing with bun.lockb -> read package.json (#41933, #34327)
  • pm untrusted/trust listed and reran workspace members (#41513)
  • Failing script of a package both optional and required deleted the package, exit 0 -> install fails (#43218; fixes #43211)
  • --concurrent-scripts did not apply to the isolated linker (#42445; fixes #42421)
  • bunfig concurrentScripts beat --concurrent-scripts (#42422)
  • Node-gyp shim was unreachable when the temp directory falls back to the cache (#38098; fixes #38079)
  • --help said dep scripts never run -> --ignore-scripts also skips trusted ones (#38893)
  • bun pm -g untrusted suggested bun pm trust -> bun pm -g trust (#44663)
  • After a failed script, a half-deleted git / tarball / patched package counted as installed

Security scanner, audit

  • Scanner saw registry packages only -> every resolved dep (git, GitHub, tarball, file:, link:), URLs without userinfo / query; a fatal advisory stops them before install and scripts (#37590)
  • --production/--omit=dev failed when the scanner is a devDependency (#31031; fixes #31028)
  • Scanner process loaded .env itself -> gets the install's environment, follows --env-file, --no-env-file, env = false (#44845)
  • bun audit fix could downgrade into an older major (#39317)
  • bun audit --help lacked --production, --omit, --dry-run, --cwd (#38836)

add, remove, update, outdated, link

  • Hoisted: bun remove foo deleted the node_modules/foo another workspace has for "foo": "npm:bar"
  • link, install <pkg>, install --analyze, update -i, remove exited 0 after Failed to install N packages -> 1 (#44989)
  • bun add ../path from below package.json, bun install -g . resolved from the wrong directory -> the cwd (#43348, #36623; fixes #5682)
  • bun add <folder | tarball | git url> of a declared package wrote it twice (#43380; fixes #8031)
  • First bun add -g used a package.json above the global directory (~/package.json) (#30659; fixes #40683)
  • bun add --dry-run in an empty directory created package.json (#36694; fixes #13244)
  • bun remove reordered package.json keys -> order, tabs, final newline kept (#35586; fixes #7215, #9351)
  • bun remove -g left dangling links in the global node_modules/.bin, deleted foreign dangling links in the bin directory (#43403)
  • bun update <name> blanked an optional / peer range no version satisfies (#43195)
  • bun update that only rewrites package.json said (no changes) (#38918; fixes #38908)
  • bun update -i repeated its prompt on mouse clicks and in short terminals (#39683)
  • update -i/outdated tables were sized in bytes -> display width (CJK, emoji), cells escaped (#43333, #43236)
  • outdated/update -i used the lockfile's ranges -> package.json's (#42005)
  • bun outdated a b needed all patterns to match -> union; !pattern removes (#43309)
  • outdated/why knew an aliased dep by one name -> either, both printed (#43338)
  • Coloured version diff dropped the build tag, left a tag that only grows (rc.1 -> rc.10) dim; long prerelease tags were garbled (#38688, #38649)
  • minimumReleaseAge error swapped range and name (#37895)
  • bun link/unlink --help described a wrong --save default (#38915)

bun pm, why, info

  • bun pm pkg get of several values altered one with a } in it, left a key unescaped (#45061)
  • bun pm cache rm --cache-dir <dir> cleared the global cache -> <dir>; project config still cannot redirect it
  • bun pm bin -g/pm ls -g failed before any global install -> work, write nothing (#36622; fixes #6382, #7501, #13019)
  • bun pm cache rm says when the global store goes with the cache; docs name --force for stale manifests (#43667)
  • bun pm version saw no repository in worktrees / submodules (.git is a file) (#41754)
  • bun pm version prerelease overflowed above 2^32; versions npm calls invalid were accepted (#42459)
  • bun pm view pkg@tag/pm diff used latest for an unknown dist-tag -> error listing the tags (#42063)
  • bun pm, pm --help, an unknown subcommand printed three help texts, unindented, without pm diff and ls -g -> one (#39487, #43314, #43098; fixes #43310)
  • bun pm bin -g said not in $PATH for an entry with a trailing slash (#28773; refs #28771)
  • bun why printed a package's dependents at every occurrence (exponential) -> once, repeats *deduped (#37922)
  • bun why name@version failed for exact / scoped names, ignored a workspace's version (#43284, #43290; fixes #43288)
  • bun why: no-match error on stdout -> stderr (#43287; fixes #43285)
  • bun why: branch left open after the last visible workspace dependent (#43292; fixes #43291)
  • bun why: a cycle back to the queried package is *circular at its first repeat (#43300; fixes #43295)
  • bun why: a root package.json without name was missing as a dependent (#43277)
  • bun why: a peer dependent was shown under the wrong copy (#41941)

pack, publish

  • workspace:/catalog: versions came from bun.lock (stale, or failed without one) -> the package.json files; no lockfile is read (#38813; fixes #20477, #20829)
  • workspace: aliases and directories were published as written -> npm: aliases; the workspace's version (#38788, #38835)
  • !pattern in .npmignore could not re-include files below an excluded directory (#36699; fixes #15602)
  • Relative --destination, --filename, bun publish ./x.tgz were resolved from the package root -> the cwd (#38739, #38704)
  • Failed pack left a partial tarball, publish --dry-run deleted an existing one, a FIFO --filename hung -> built in memory, written once complete (#38721)
  • File names that are not UTF-8 were mangled, two became one entry (#38715)
  • File systems whose readdir reports DT_UNKNOWN lost entries (#38716)
  • bin paths naming no file, .., a trailing slash in directories.bin broke pack; an empty bin hid directories.bin (#38720)
  • File under several bin names was packed several times (#41291)
  • Directory, bin or bundled dep that is there but unreadable was left out silently -> exit 1 (#44400, #44873)
  • bun publish accepted a version that is not semver -> refused after the scripts; v1.0.0 goes out as 1.0.0 (#43142)

bun patch

  • --commit from a workspace member flipped the root package.json's final newline to the member's
  • Patch adding a file in a new directory failed with EACCES for non-root users (#33573; fixes #13330, #33571, #38569)
  • Hunks were applied by line number, wrote wrong text when the file had moved; -U0 hunks landed a line off -> placed by context like git apply; hunk #N does not apply fails the install (#41733, #41695; fixes #38879)
  • Old-style diffs whose hunks delete lines starting with -- a/ were misparsed (#34870)
  • Failed copy, missing cache entry, missing git or failed git diff left a partial or no node_modules/<pkg> -> staged copy, everything put back (#43188, #43160; fixes #43164)
  • bun patch dropped the package's bundled and nested deps, --commit recorded them as deleted (#43178)
  • bun patch of a bundled dep deleted the bundled copy -> refused (#43199)
  • bun patch of a file: folder, link: or workspace package damaged node_modules -> refused (#43223; fixes #43200)
  • bun patch <path> replaced a folder with another version -> refused when the version is not locked (#43367; fixes #43353)
  • npm: alias could not be named by its package; a second bun patch <alias> lost the first patch (#43382)
  • Isolated: bun patch <transitive> made a stray node_modules/<name> -> edits the store entry (#43383)
  • bun patch replaced workspace links and symlinks above the package, could write into the global store -> links stay; anything under the cache is refused (#43406)
  • --commit on a patched package wrote .bun-tag files and cache paths into the patch, read /etc/gitconfig (#44349; fixes #19327, #44320)
  • --commit: a new binary file became empty, a changed one was skipped, exit 0 -> commit fails
  • Member's patchedDependencies were ignored silently -> warning (#39869)
  • Isolated: bun patch replaced node_modules/<pkg> by a directory that could not load its deps -> edits a copy in the store entry, the link stays; global store: --commit restores the link (#43365)

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=400branch N=400main N=1600branch N=1600
hoisted warm22.520.3146.580.1
hoisted relink24.521.6150.383.3
hoisted cold121.0122.2511.2474.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.

mainthis branch
hoisted cold215.96211.95
hoisted relink72.0070.00
hoisted warm4.004.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).

mainthis branch
bun pm hash, 32,000 workspaces (ms)6,914236
bun pm migrate, Yarn v1, 20,000 entries (ms)24,210270
peak RSS of bun install, 2,100 workspaces (KB)90,84034,728
hoisted, 1,000 packages x 30 files, cache, no node_modules, 8 cores, wall (ms)381120
same, 4 cores383174
same, 2 cores222162
same, node_modules lacks half, 8 cores14054
same, 4 cores17465
same, 2 cores161107
same, no cache, no node_modules, 8 cores363394
same, 4 cores418401
same, 2 cores477541

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

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

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

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:
mainthis branch
repeated install rewrites files26510
repeated install reports work1012
repeated install changes the tree223
lockfile depends on manifest arrival order570
exit code depends on the linker200
writes outside the project210
declared dep cannot be resolved131
dep resolves to a wrong version40
frozen / relink / cold cache fails on its own lockfile70
package.json rewritten with the same bytes70
crash or hang30
dangling symlink20
bin not executable22
leftovers in the temp directory2301
tree or exit code after kill + reinstall differs (timing)34
  • 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