Skip to content

Announce

Announce #7

Workflow file for this run

# Announce: posts an already-published release to the Discord #releases channel. Manually
# dispatched, never automatic — the dispatch IS the maintainer's approval, given only after
# they have personally verified the published release (the workflow succeeded, repo.json
# serves the new version, the asset URL answers, the in-game update works). Running this is
# the release process's very last step, so a broken or half-published release can never
# announce itself.
#
# Deliberately a separate workflow rather than an approval-gated job inside release.yml: a
# waiting job sits on the release run inviting an approve-to-clear-the-queue click, while a
# dispatch requires the maintainer to come back and actively say "announce it".
#
# Re-running posts a SECOND Discord message (webhooks are not idempotent) — re-run only when
# the previous run failed without posting anything.
name: Announce
on:
workflow_dispatch:
inputs:
tag:
description: "Published release tag to announce (vX.Y.Z)"
required: true
type: string
jobs:
announce:
runs-on: ubuntu-latest
# Reading the release needs only the default read scope; this workflow never writes to
# the repository.
permissions:
contents: read
defaults:
run:
shell: pwsh
steps:
# Checked out at the dispatched ref (main, normally), NOT the release's tag: release
# tags are immutable, so a tag checkout would forever run whatever announcement script
# existed at release time — a script fix landed on main could never reach it. The
# screenshots still match the release, because per-release folders
# (images/releases/<tag>/) are committed on main and stay there.
- name: Checkout
uses: actions/checkout@v5
# The interlock this workflow exists for: only a genuinely published release can be
# announced. A missing or draft release fails here, loudly, and nothing reaches
# Discord.
- name: Verify the release is published
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ inputs.tag }}
run: |
if ($env:TAG -notmatch '^v\d+\.\d+\.\d+$') {
throw "Input '$($env:TAG)' is not a release tag (expected vMAJOR.MINOR.PATCH)."
}
# Captured before parsing so a missing release reports itself rather than
# surfacing as a JSON parse error on empty output.
$json = gh release view $env:TAG --json isDraft 2>$null
if ($LASTEXITCODE -ne 0) {
throw "No GitHub Release exists for $($env:TAG); publish it (push the tag) first."
}
$release = $json | ConvertFrom-Json
if ($release.isDraft) {
throw "The $($env:TAG) release is still a draft; publish it before announcing."
}
# The release's fields are read back from the GitHub API and cross into the Node
# script as environment variables — the body is arbitrary changelog markdown and must
# never be interpolated into script text. No continue-on-error: this run's only job
# is the announcement, so a Discord failure should fail the run visibly.
- name: Announce the release on Discord
env:
GH_TOKEN: ${{ github.token }}
DISCORD_RELEASES_WEBHOOK_URL: ${{ secrets.DISCORD_RELEASES_WEBHOOK_URL }}
TAG: ${{ inputs.tag }}
run: |
$release = gh release view $env:TAG --json name,body,url | ConvertFrom-Json
$env:RELEASE_NAME = $release.name
$env:RELEASE_BODY = $release.body
$env:RELEASE_URL = $release.url
$env:RELEASE_TAG = $env:TAG
node .github/scripts/post-discord-release.mjs