From 6b8581ee06b81eb20a51ef4a48a818e7ffc20e95 Mon Sep 17 00:00:00 2001 From: McCal-Codes <175259256+McCal-Codes@users.noreply.github.com> Date: Fri, 2 Oct 2026 17:46:40 -0400 Subject: [PATCH 1/2] A manual re-post of a release announcement works --- .github/workflows/announce.yml | 7 ++++--- tools/announce-release.mjs | 8 ++++++-- tools/announce-release.test.mjs | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.github/workflows/announce.yml b/.github/workflows/announce.yml index 3af2c32c..4b7bd756 100644 --- a/.github/workflows/announce.yml +++ b/.github/workflows/announce.yml @@ -32,7 +32,9 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - # Both triggers end up with the same file, so the manual run and the automatic one cannot drift apart. + # Both triggers end up with the same file, so the manual run and the automatic one cannot drift apart. A step cannot + # change GITHUB_EVENT_PATH, which is why a manual run used to fail with "The event carries no release"; the script + # reads this file by its own name instead. - name: Gather the release env: GH_TOKEN: ${{ github.token }} @@ -40,8 +42,7 @@ jobs: run: | set -euo pipefail gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${TAG}" > release.json - jq '{release: .}' release.json > event.json - echo "GITHUB_EVENT_PATH=$PWD/event.json" >> "$GITHUB_ENV" + echo "RELEASE_FILE=$PWD/release.json" >> "$GITHUB_ENV" - name: Post it env: diff --git a/tools/announce-release.mjs b/tools/announce-release.mjs index aafa9d23..329e8ce1 100644 --- a/tools/announce-release.mjs +++ b/tools/announce-release.mjs @@ -220,9 +220,13 @@ async function post(webhook, message, loaded) { async function main() { const dryRun = process.argv.includes('--dry-run') + // A manual run has no release in GitHub's own event file, and a step cannot change GITHUB_EVENT_PATH, so the + // workflow fetches the release itself and names the file in RELEASE_FILE. An automatic run reads the event. + const releaseFile = process.env.RELEASE_FILE const eventPath = process.env.GITHUB_EVENT_PATH - if (!eventPath) throw new Error('No GITHUB_EVENT_PATH; run this from the workflow, which writes one either way') - const { release } = JSON.parse(readFileSync(eventPath, 'utf8')) + if (!releaseFile && !eventPath) throw new Error('No RELEASE_FILE or GITHUB_EVENT_PATH; run this from the workflow') + const parsed = JSON.parse(readFileSync(releaseFile || eventPath, 'utf8')) + const release = releaseFile ? parsed : parsed.release if (!release) throw new Error('The event carries no release') if (release.draft) return console.log('Draft release, nothing posted.') diff --git a/tools/announce-release.test.mjs b/tools/announce-release.test.mjs index d4c8b26b..1a21800f 100644 --- a/tools/announce-release.test.mjs +++ b/tools/announce-release.test.mjs @@ -198,3 +198,21 @@ test('two servers each receive their own ping, and the wall is downloaded once', assert.doesNotMatch(mmd.message.content, /1553093586705449062/) assert.ok(mmd.hasFile) }) + +test('a manual re-post reads the release from RELEASE_FILE, which is the release itself and not an event around it', async () => { + const dir = mkdtempSync(join(tmpdir(), 'announce-')) + const file = join(dir, 'release.json') + writeFileSync(file, JSON.stringify(release)) + const out = await new Promise((resolve) => { + let text = '' + const child = spawn(process.execPath, [new URL('./announce-release.mjs', import.meta.url).pathname, '--dry-run'], { + // GITHUB_EVENT_PATH points at an event with no release, as GitHub's own file does for a manual run. + env: { ...process.env, RELEASE_FILE: file, GITHUB_EVENT_PATH: join(dir, 'missing.json') }, + }) + child.stdout.on('data', (d) => (text += d)) + child.on('exit', (code) => resolve({ code, text })) + }) + assert.equal(out.code, 0) + assert.match(out.text, /Dry run\. This is the message:/) + assert.match(out.text, /v0\.6\.6|0\.6\.6/) +}) From c96f328a07e632d59040c2018c6c828dc8a94249 Mon Sep 17 00:00:00 2001 From: McCal-Codes <175259256+McCal-Codes@users.noreply.github.com> Date: Fri, 2 Oct 2026 17:46:59 -0400 Subject: [PATCH 2/2] The test for a manual re-post imports what it uses --- tools/announce-release.test.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/announce-release.test.mjs b/tools/announce-release.test.mjs index 1a21800f..4a05e178 100644 --- a/tools/announce-release.test.mjs +++ b/tools/announce-release.test.mjs @@ -200,6 +200,10 @@ test('two servers each receive their own ping, and the wall is downloaded once', }) test('a manual re-post reads the release from RELEASE_FILE, which is the release itself and not an event around it', async () => { + const { spawn } = await import('node:child_process') + const { mkdtempSync, writeFileSync } = await import('node:fs') + const { tmpdir } = await import('node:os') + const { join } = await import('node:path') const dir = mkdtempSync(join(tmpdir(), 'announce-')) const file = join(dir, 'release.json') writeFileSync(file, JSON.stringify(release))