From 2faf660ac1298af0205ebb34028ff45ad5293145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Thu, 22 Oct 2026 11:28:16 +0000 Subject: [PATCH] fix(ci): packed consumer installs keep same-day dependency bumps on every Bun release (#6059) * fix(ci): packed consumer installs keep same-day dependency bumps on every Bun release Bun 1.4.4 swapped the quoted package and range in its minimum-release-age error, so the retry excluded the range instead of the package and the install failed again. The package is now the token that bun's "@ failed to resolve" line pairs with the other one, and an unrecognised message fails visibly instead of excluding the wrong name. CI installs the newest Bun when nothing pins it, which let the 1.4.4 release turn every PR's support tests red mid-day. engines.bun in the root package.json pins it for every setup-bun step that does not set a version (packageManager would make corepack's pnpm refuse the repo). * fix(ci): keep the release-age parser's doc comment to one line --- package.json | 3 +++ scripts/verify-packed-manifests.mjs | 14 ++++++++++--- scripts/verify-packed-manifests.test.mjs | 25 ++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 566099bb41..c332c9fc03 100644 --- a/package.json +++ b/package.json @@ -97,5 +97,8 @@ "@types/react-dom": "^19.0.0", "react": "^19.0.0", "react-dom": "^19.0.0" + }, + "engines": { + "bun": "1.4.4" } } diff --git a/scripts/verify-packed-manifests.mjs b/scripts/verify-packed-manifests.mjs index bc57ed57d7..fc6ea3a09a 100644 --- a/scripts/verify-packed-manifests.mjs +++ b/scripts/verify-packed-manifests.mjs @@ -479,7 +479,15 @@ function writeConsumerFixture(packDir, packedWorkspaces) { export const CONSUMER_MIN_RELEASE_AGE_SECONDS = 24 * 60 * 60; const BLOCKED_BY_AGE = - /No version matching "[^"]+" found for specifier "([^"]+)" \(blocked by minimum-release-age/g; + /No version matching "([^"]+)" found for specifier "([^"]+)" \(blocked by minimum-release-age/g; + +/** Packages bun refused as too new, or null when its output does not pair a name with a range. */ +export function ageBlockedPackages(stderr) { + const names = [...stderr.matchAll(BLOCKED_BY_AGE)].map(([, a, b]) => + [a, b].find((name) => stderr.includes(`${name}@${name === a ? b : a} failed to resolve`)), + ); + return names.length && names.every(Boolean) ? names : null; +} export function installConsumer(dir, { args = [], env = process.env } = {}) { const install = () => @@ -498,8 +506,8 @@ export function installConsumer(dir, { args = [], env = process.env } = {}) { } catch (error) { // A same-day dependency bump: every version its range allows is younger than the age, and the repo // installed it already, so its tarball is served. Only those packages skip the age. - const blocked = [...`${error.stderr ?? ""}`.matchAll(BLOCKED_BY_AGE)].map((m) => m[1]); - if (!blocked.length) throw error; + const blocked = ageBlockedPackages(`${error.stderr ?? ""}`); + if (!blocked) throw error; console.log(`Consumer install: newer than the release age, allowed: ${blocked.join(", ")}`); writeFileSync( join(dir, "bunfig.toml"), diff --git a/scripts/verify-packed-manifests.test.mjs b/scripts/verify-packed-manifests.test.mjs index fb05b1a15c..06af0d7abe 100644 --- a/scripts/verify-packed-manifests.test.mjs +++ b/scripts/verify-packed-manifests.test.mjs @@ -7,6 +7,7 @@ import { dirname, join } from "node:path"; import { describe, it } from "node:test"; import { Worker } from "node:worker_threads"; import { + ageBlockedPackages, CONSUMER_TSCONFIG, countTypeErrorsByPackage, installConsumer, @@ -353,6 +354,30 @@ async function withRegistry(name, versions, run) { } } +describe("ageBlockedPackages", () => { + const blockedBy = (first, second, name, range) => + `Resolving dependencies\nerror: No version matching "${first}" found for specifier "${second}" (blocked by minimum-release-age: 86400 seconds)\nerror: ${name}@${range} failed to resolve\n`; + + it("names the package whichever order bun quotes it in", () => { + assert.deepEqual(ageBlockedPackages(blockedBy("fake-dep", "^1.1.0", "fake-dep", "^1.1.0")), [ + "fake-dep", + ]); + assert.deepEqual(ageBlockedPackages(blockedBy("^1.1.0", "fake-dep", "fake-dep", "^1.1.0")), [ + "fake-dep", + ]); + assert.deepEqual( + ageBlockedPackages(blockedBy("^2.0.1", "@scope/pkg", "@scope/pkg", "^2.0.1")), + ["@scope/pkg"], + ); + }); + + it("names none when bun's output does not say which token is the package", () => { + const unpaired = `error: No version matching "^1.1.0" found for specifier "fake-dep" (blocked by minimum-release-age: 86400 seconds)\n`; + assert.equal(ageBlockedPackages(unpaired), null); + assert.equal(ageBlockedPackages("error: fake-dep@^9.0.0 failed to resolve\n"), null); + }); +}); + describe("packed consumer install", () => { /** Installs `deps` into a fresh consumer against the registry; returns the installed fake-dep version. */ function install(registry, dir, deps) {