diff --git a/.github/workflows/finalize-existing-release.yml b/.github/workflows/finalize-existing-release.yml new file mode 100644 index 00000000..8d20f575 --- /dev/null +++ b/.github/workflows/finalize-existing-release.yml @@ -0,0 +1,144 @@ +name: Finalize Existing Release + +on: + workflow_dispatch: + inputs: + version: + description: Existing release version without the v prefix + required: true + type: string + +permissions: + contents: write + +jobs: + finalize-release: + runs-on: ubuntu-latest + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + DISCORD_UPDATE_ROLE_ID: ${{ secrets.DISCORD_UPDATE_ROLE_ID }} + steps: + - name: Verify release inventory + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPOSITORY: ${{ github.repository }} + VERSION: ${{ inputs.version }} + run: | + node - <<'NODE' + (async () => { + const { GITHUB_TOKEN: token, REPOSITORY: repo, VERSION: version } = process.env; + const expected = [ + `OpenChamber-${version}-mac-arm64.dmg`, + `OpenChamber-${version}-mac-arm64.zip`, + `OpenChamber-${version}-mac-x64.dmg`, + `OpenChamber-${version}-mac-x64.zip`, + `OpenChamber-${version}-win-x64.exe`, + 'latest-mac.yml', + 'latest.yml', + ]; + const response = await fetch(`https://api.github.com/repos/${repo}/releases/tags/v${version}`, { + headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' }, + }); + if (!response.ok) throw new Error(`Failed to inspect release: ${response.status} ${await response.text()}`); + const release = await response.json(); + for (const name of expected) { + const matches = release.assets.filter((asset) => asset.name === name); + if (matches.length !== 1) throw new Error(`Expected exactly one ${name}, found ${matches.length}`); + if (!Number.isSafeInteger(matches[0].size) || matches[0].size <= 0) { + throw new Error(`Release asset ${name} has invalid size ${matches[0].size}`); + } + } + console.log(`Verified release v${version} inventory before finalization.`); + })().catch((error) => { + console.error(error); + process.exit(1); + }); + NODE + + - name: Publish release + uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2 + with: + tag_name: v${{ inputs.version }} + draft: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Send release to Discord + if: ${{ env.DISCORD_WEBHOOK_URL != '' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ inputs.version }} + REPOSITORY: ${{ github.repository }} + UPDATE_ROLE_ID: ${{ env.DISCORD_UPDATE_ROLE_ID }} + run: | + node - <<'NODE' + (async () => { + const tag = `v${process.env.VERSION}`; + const repo = process.env.REPOSITORY; + const rawRoleId = (process.env.UPDATE_ROLE_ID || '').trim(); + const updateRoleId = /^\d+$/.test(rawRoleId) ? rawRoleId : ''; + + const releaseRes = await fetch(`https://api.github.com/repos/${repo}/releases/tags/${tag}`, { + headers: { + Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, + Accept: 'application/vnd.github+json', + }, + }); + if (!releaseRes.ok) { + throw new Error(`Failed to fetch release ${tag}: ${releaseRes.status} ${await releaseRes.text()}`); + } + + const release = await releaseRes.json(); + const description = (release.body || `OpenChamber ${tag} released.`).slice(0, 4096); + const mention = updateRoleId ? `<@&${updateRoleId}>` : ''; + const payload = { + username: 'OpenChamber Releases', + ...(mention ? { content: mention } : {}), + ...(updateRoleId ? { allowed_mentions: { roles: [updateRoleId] } } : {}), + embeds: [{ + title: release.name || `OpenChamber ${tag}`, + url: release.html_url, + description, + color: 2105893, + footer: { text: 'OpenChamber Changelog' }, + }], + }; + + const discordRes = await fetch(process.env.DISCORD_WEBHOOK_URL, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify(payload), + }); + if (!discordRes.ok) { + throw new Error(`Failed to send Discord release: ${discordRes.status} ${await discordRes.text()}`); + } + })().catch((error) => { + console.error(error); + process.exit(1); + }); + NODE + + - name: Trigger openchamber-website site refresh + env: + WEBSITE_REPO: openchamber/openchamber-website + WEBSITE_TOKEN: ${{ secrets.OPENCHAMBER_WEBSITE_REPO_TOKEN }} + VERSION: ${{ inputs.version }} + run: | + if [ -z "$WEBSITE_TOKEN" ]; then + echo "OPENCHAMBER_WEBSITE_REPO_TOKEN not set; skip site refresh dispatch." + exit 0 + fi + + curl --fail-with-body -sS -X POST \ + -H "Authorization: Bearer $WEBSITE_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$WEBSITE_REPO/dispatches" \ + -d @- <