bunx: honor --minimum-release-age by keying the install cache on it

Closed
RO
1mo ago
+756-5

Fixes #30748

Problem

  • bunx --minimum-release-age=N pkg ran the newest version; bun add with 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::parse accepts both flag forms, rejects bad values with bun add's message (and values too large for the key), and forwards the raw value to bun add. Any other leading flag now prints a warning instead of vanishing; --help prints usage and exits 0.
  • The cache directory name gains +min-age=<ms> whenever the flag is given. Every probe, the $PATH entry, the 24h stale check and the child's cwd derive from that name. bun pm cache rm still 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 ran bunx.test.ts.

Background

  • bunx installs into $TMPDIR/bunx-<uid>-<package_fmt>/ via a spawned bun 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 Option fields, three None checks, 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 Vec per run and one cache directory per distinct gate value, =0 included.
  • 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] minimumReleaseAge is 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