bunx: honor --minimum-release-age by keying the install cache on it
Fixes #30748
Problem
bunx --minimum-release-age=N pkgran the newest version;bun addwith the same flag refuses it. bunx never parsed the flag, and the spaced form made the number the package name.- The bunx cache key is
<name>@<version>only (package_fmt,src/runtime/cli/bunx_command.rs), so a warm entry can serve a gated run a forbidden version.
Fix
Options::parseaccepts both flag forms, rejects bad values withbun add's message (and values too large for the key), and forwards the raw value tobun add. Any other leading flag now prints a warning instead of vanishing;--helpprints usage and exits 0.- The cache directory name gains
+min-age=<ms>whenever the flag is given. Every probe, the$PATHentry, the 24h stale check and the child's cwd derive from that name.bun pm cache rmstill sweeps by prefix. - Correct because a directory built under gate N only holds versions that satisfied N, and time moves forward. No path deletes anything new.
- Verified:
test/cli/install/minimum-release-age.test.ts,describe("bunx"), 19 cases, 68/68 locally. Also ranbunx.test.ts.
Background
- bunx installs into
$TMPDIR/bunx-<uid>-<package_fmt>/via a spawnedbun add, then runs the bin from it. A warm entry is reused for 24h. - This PR first forced every warm hit stale and wiped the shared directory. Review found a delete race between concurrent gated runs, no 24h cache for gated runs, and gated installs leaking into the ungated entry. The cache key fixes all three.
Downsides
- Callers who never pass the flag: two
Optionfields, threeNonechecks, 8 stack bytes. 0 new syscalls, allocations or file reads. Binary size delta not measured (no release build here). - Callers who pass the flag: one
Vecper run and one cache directory per distinct gate value,=0included. - Scripts that pass bunx a flag it does not know (
bunx --registry=... pkg) now see one warning line on stderr. Behavior is otherwise unchanged: the flag was dropped before and still is. - Open, owned by #36696: a project
bunfig.toml[install] minimumReleaseAgeis still not seen by bunx.
Notes
Cache directory name: bunx-<uid>-<name>@<version>+min-age=<ms>, with <ms> the validated value in integer milliseconds, matching what bun add computes internally. + and = are not in BANNED_PATH_CHARS. The suffix is appended after both package_fmt branches (plain and the hashed fallback for URL specifiers), in the same spot where #36696 appends its bunfig-path hash, so the two compose.
Key granularity: the key is the gate in integer milliseconds. Two values that differ by under 1ms share an entry; registry publish times have millisecond resolution, so this cannot admit a version the stricter gate rejects. Values above about 5.8e8 years saturate (secs * 1000.0) as u64; every such gate rejects all versions, so no install exists under it to reuse.
Why =0 is keyed separately: a global ~/.bunfig.toml gate applies to the child bun add, so the plain <name>@<version> entry may already hold a gated install. An explicit =0 must neither reuse nor overwrite it.
Why the bunfig sibling is out of scope: bunx skips bunfig loading (uses_global_options=false) and the child runs with the cache dir as cwd, so honoring it means a bunfig read on every bunx run. #36696 does that and folds the bunfig path into the cache key.
Tests use a new bunx-package fixture in the mock registry (1.0.0 at 30 days, 2.0.0 at 10, 2.1.0 at 6, 3.0.0 at 1) whose tarball ships a bin that prints its version, so tests observe which version ran. Scoped-package fixtures chmod the intermediate bunx-<uid>-@scope directory too, because is_trusted_cache_root walks every component and mkdirSync under umask 002 yields 0o775.
Local verification before the latest container reset: 64/64 under umask 022 and 002. The pushed code is a byte-identical re-application; CI confirmed 64/64 (darwin-aarch64).
Self-reviewed: 7 concerns raised across two rounds, 7 addressed: the unknown-flag class (warning, not an error, since bun's own parser skips unknown flags elsewhere), the cache-key grammar comment, a test that runs on Windows for the key name, --help exit code, concurrent tests, subprocess disposal, and the u64 bound on the key.
Composition with #36696: both PRs append a +key=value tail at the same package_fmt site and both bump the spawn BoundedArray capacity by one; whichever lands second needs capacity 10.
Earlier rounds (first design, superseded): spaced form, validation before filesystem mutation, umask-002 fixture portability, pipe draining in tests, comments trimmed to the repo's 3-line limit.
no test proof · iteration 29 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/cli/install/minimum-release-age.test.ts