install: keep trustedDependencies and patchedDependencies order in bun.lock
Merged
RO
3mo ago+138+138+012345678901234567890123456789-3-3-0123456789
Problem
- The first
bun add/bun remove/bun update <pkg>run by a 1.4 build on a bun.lock written by 1.3.x rewrites thetrustedDependenciesarray (and thepatchedDependenciesobject) in a different order. Nothing in either section changed, so this is an unrelated hunk in every dependency PR, and a 1.3.x build doing the next add/remove reorders it back. - Both sections are written in the iteration order of the map they are collected in (
src/install/lockfile/bun.lock.rs,found_trusted_dependencies/found_patched_dependencies, written at the"trustedDependencies"/"patchedDependencies"loops insave_from_binary_inner). That order has never been sorted, so it is effectively part of the file format. - The Zig writer collected them in a
std.AutoHashMapUnmanaged(u64, ...): slot index = wyhash of the key's 8 bytes, masked by a capacity derived from thetrustedDependencies/patchedDependenciescount.bun_collections::HashMapis a port of that layout (src/collections/zig_hash_map.rs), but itsAutoHashContexthashes integer keys throughOneShotHasher::write_u64(a singlemummultiply,src/wyhash/lib.rs), so the same keys land in different slots and the sections come out permuted. - Reproduced with 1.3.14 and a 1.4 canary on the same package.json (7 trusted names, 5 patched): 1.3.14 writes
bcrypt, esbuild, sharp, @prisma/engines, @prisma/client, core-js, prisma(the order reported), the 1.4 build writesbcrypt, esbuild, prisma, @prisma/engines, core-js, @prisma/client, sharp, andpatchedDependenciesswaps two entries.
Fix
- Adds a
HashContextfor those two maps that hashes the key as wyhash over its little-endian bytes (bun_wyhash::hashis the same wyhashstd.hash.Wyhashimplements), so with the unchanged map layout and the unchangedreservecounts the slots, and therefore the written order, are the ones every earlier version produced. No other caller of the map is affected. - Restoring the old order rather than sorting: sorting would still rewrite both sections once in every existing lockfile and would still ping-pong against 1.3.x, so it would need to be tied to a
lockfileVersionbump to be churn-free. This change makes 1.3.x and 1.4 agree byte for byte. - Test:
test/cli/install/bun-lock.test.ts, "writes trustedDependencies and patchedDependencies in the order earlier versions wrote them". Two shapes, each installed as a workspace and compared against the text bun 1.3.14 writes for that package.json: the reported 7 trusted / 5 patched set, and a 13 trusted / 6 patched set that puts the trusted map in the next capacity and fills the patched map to its growth threshold, so a change to the hash or to how the maps size themselves shows up. Each shape is re-saved after adding a package and checked again. Both fail on current main (the first with the same two hunks as the report) and pass with this change. - Also checked: 1.3.14-written lockfile,
bun addwith this build produces only the added lines, andbun removewith 1.3.14 afterwards restores a byte-identical file; a differential run of 60 random workspace layouts (1 to 24 packages, so map capacities 8 through 32, some trusted names absent from the tree) writes identical sections with 1.3.14 and this build, while the unfixed build differs in 11 of 12; the rest ofbun-lock.test.tspasses unchanged.
Background
- bun.lock is the text lockfile.
trustedDependencieslists the packages whose lifecycle scripts may run,patchedDependenciesmapsname@versionto a patch file; the writer only emits the entries that are actually in the resolved tree, which is why they are collected into a map during the tree walk instead of being copied from package.json. bun_collections::HashMapis an open-addressing table with linear probing whose iteration order is its slot order, which is determined by the hash function, the capacity, and insertion order on collisions. AHashContextis the type parameter that supplies the hash function for a given map, so one map can use a different hash than the default without touching the others.
no test proof · iteration 2 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/cli/install/bun-lock.test.ts