diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 67118dcd..76ba6cc3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -234,6 +234,8 @@ jobs: runs-on: ubuntu-latest # Every write here is the app token's. permissions: {} + outputs: + prerelease: ${{ steps.tag.outputs.prerelease }} steps: # The app token, not GITHUB_TOKEN: a release published with the built-in # token starts no workflow run, and the Community Applications template @@ -283,3 +285,31 @@ jobs: --notes-file release-notes.md \ --verify-tag \ "$FLAG" + + # The documentation site generates its changelog, its development page and its + # release badge from this repository at build time. Its host builds only on a + # push to its own repository, so without this it keeps serving the previous + # release until something unrelated is pushed there. + docs-rebuild: + needs: release-page + if: needs.release-page.outputs.prerelease == 'false' + runs-on: ubuntu-latest + permissions: {} + steps: + - name: Request a documentation site rebuild + env: + HOOK: ${{ secrets.DOCS_DEPLOY_HOOK_URL }} + run: | + set -euo pipefail + if [ -z "$HOOK" ]; then + echo 'DOCS_DEPLOY_HOOK_URL is not set. Nothing requested.' + exit 0 + fi + # Never print the hook. The URL is the credential. + code=$(curl --silent --output /dev/null --write-out '%{http_code}' \ + --request POST --max-time 30 "$HOOK") + if [ "$code" -lt 200 ] || [ "$code" -ge 300 ]; then + echo "The deploy hook answered HTTP $code." + exit 1 + fi + echo 'Documentation site rebuild requested.' diff --git a/api/test/release-automation.test.js b/api/test/release-automation.test.js index 70089f34..0eca758f 100644 --- a/api/test/release-automation.test.js +++ b/api/test/release-automation.test.js @@ -61,6 +61,19 @@ test('the release workflows grant the built-in token nothing', () => { assert.deepEqual(release.jobs['release-page'].permissions, {}); }); +test('a stable release asks the documentation site to rebuild', () => { + const doc = yaml.load(wf('release.yml')); + const job = doc.jobs['docs-rebuild']; + assert.ok(job, 'release.yml has no docs-rebuild job'); + assert.equal(job.needs, 'release-page', 'the site must not describe a release page that failed'); + assert.deepEqual(job.permissions, {}, 'the rebuild needs no scope on this repository'); + assert.match(String(job.if), /prerelease == 'false'/, 'an rc tag must not move the site'); + assert.equal(doc.jobs['release-page'].outputs?.prerelease, '${{ steps.tag.outputs.prerelease }}'); + const script = job.steps.map(s => s.run || '').join('\n'); + assert.match(script, /DOCS_DEPLOY_HOOK_URL is not set/, 'a missing hook must not fail the release'); + assert.doesNotMatch(script, /echo\s+"?\$HOOK/, 'the hook URL is a credential and must not be printed'); +}); + test('the release runs one at a time', () => { for (const f of ['release-prep.yml', 'release-tag.yml']) { const doc = yaml.load(wf(f)); @@ -70,10 +83,14 @@ test('the release runs one at a time', () => { test('docs/releasing.md documents the app secrets the workflows read', () => { const doc = fs.readFileSync(path.join(ROOT, 'docs', 'releasing.md'), 'utf8'); - for (const secret of ['RELEASE_APP_CLIENT_ID', 'RELEASE_APP_PRIVATE_KEY']) { + for (const [secret, file] of [ + ['RELEASE_APP_CLIENT_ID', 'release-prep.yml'], + ['RELEASE_APP_PRIVATE_KEY', 'release-prep.yml'], + ['DOCS_DEPLOY_HOOK_URL', 'release.yml'], + ]) { assert.ok(doc.includes(secret), `${secret} is read by a workflow and not documented`); assert.ok( - wf('release-prep.yml').includes(secret), + wf(file).includes(secret), `${secret} is documented but no longer read; the doc would send someone to create a secret nothing uses`, ); } diff --git a/docs/releasing.md b/docs/releasing.md index bd20accb..ef56cc26 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -56,6 +56,24 @@ App** on the account that owns the repository: The app needs no ruleset bypass. It opens pull requests rather than pushing to the default branch, and the ruleset covers branches, not tags. +## The documentation site + +The site at https://stackyard.sandobserver.com builds its changelog, its +development page and its release badge from this repository. Its host builds +only on a push to its own repository, so a release here does not update it on +its own. + +The `docs-rebuild` job asks the host to build after a stable release. It reads +one secret, `DOCS_DEPLOY_HOOK_URL`, added under **Settings > Secrets and +variables > Actions**. Create the hook in the Cloudflare Pages project under +**Settings > Builds and deployments > Deploy hooks**, pointed at `main`. + +The URL is the credential. Anyone holding it can trigger a build. It is never +printed in a log. + +Without the secret the job reports that nothing was requested and the release +still succeeds. An rc tag never triggers a rebuild. + ## When a release build fails The tag stays and nothing publishes. Fix `main`, delete the tag locally and on