From 5097150c9b7bfe1008d3df20c9920b84ad4e2a20 Mon Sep 17 00:00:00 2001 From: elkaix Date: Thu, 1 Oct 2026 23:00:00 -0400 Subject: [PATCH] fix(release): read the Homebrew lane version from the formula's version stanza --- scripts/release/release-status.mjs | 2 +- scripts/release/release-status.test.mjs | 34 ++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/scripts/release/release-status.mjs b/scripts/release/release-status.mjs index f95102849..2e8493fa7 100644 --- a/scripts/release/release-status.mjs +++ b/scripts/release/release-status.mjs @@ -83,7 +83,7 @@ async function fetchText(fetchImpl, url, label, init = {}) { } function brewFormulaVersion(formula) { - const match = typeof formula === 'string' ? /pythinker-code-(\d+\.\d+\.\d+)\.tgz/u.exec(formula) : null; + const match = typeof formula === 'string' ? /^\s*version "([^"]+)"\s*$/mu.exec(formula) : null; return match === null ? undefined : validVersion(match[1]); } diff --git a/scripts/release/release-status.test.mjs b/scripts/release/release-status.test.mjs index 79d481d23..4dd80326c 100644 --- a/scripts/release/release-status.test.mjs +++ b/scripts/release/release-status.test.mjs @@ -47,19 +47,30 @@ function json(body, status = 200) { }); } +function brewFormulaText(version) { + const tag = encodeURIComponent(`@pymodel/pythinker-code@${version}`); + return [ + 'class PythinkerCode < Formula', + ` version "${version}"`, + ` url "https://github.com/PyModel/pythinker-code/releases/download/${tag}/pythinker-code-darwin-arm64.tar.gz"`, + '', + ].join('\n'); +} + function fixtureFetch({ cliAssets = expectedCliAssets, nightlyAssets = desktopAssets(desktopNightlyVersion, 'nightly'), brewVersion = '1.3.0', + brewFormula, } = {}) { return async (input) => { const url = new URL(String(input)); if (url.hostname === 'registry.npmjs.org') return json({ latest: '1.3.0' }); if (url.hostname === 'raw.githubusercontent.com') { - return new Response( - `url "https://registry.npmjs.org/@pymodel/pythinker-code/-/pythinker-code-${brewVersion}.tgz"\n`, - { status: 200, headers: { 'content-type': 'text/plain' } }, - ); + return new Response(brewFormula ?? brewFormulaText(brewVersion), { + status: 200, + headers: { 'content-type': 'text/plain' }, + }); } if (url.hostname === 'code.pythinker.com') { return json({ @@ -165,6 +176,21 @@ void test('fails when the Homebrew formula lags the published CLI version', asyn assert.equal(brew?.observed, '1.2.0'); }); +void test('fails when the Homebrew formula has no version stanza', async (t) => { + const rootDir = await fixtureRoot(t); + const result = await collectReleaseStatus({ + rootDir, + desktopCommitCount, + fetchImpl: fixtureFetch({ brewFormula: 'class PythinkerCode < Formula\n' }), + }); + + assert.equal(result.ok, false); + const brew = result.rows.find((row) => row.lane === 'Homebrew'); + assert.equal(brew?.ok, false); + assert.equal(brew?.observed, 'unavailable'); + assert.match(brew?.details ?? '', /no pythinker-code tarball version/u); +}); + void test('fails when the current desktop Nightly release is incomplete', async (t) => { const rootDir = await fixtureRoot(t); const result = await collectReleaseStatus({