Skip to content

ci: purge the Cloudflare cache after each deploy goes live #17

ci: purge the Cloudflare cache after each deploy goes live

ci: purge the Cloudflare cache after each deploy goes live #17

Workflow file for this run

name: CI
on:
push:
branches: [master]
pull_request:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: yarn
- name: Install packages
run: yarn install --frozen-lockfile
- name: Build
run: yarn build
- name: Check the static files survived the build
# CNAME keeps the custom domain, 404.html is the SPA shell that serves
# unprerendered deep paths, and api/ is the gpu.js API reference served
# at https://gpu.rocks/api/
run: |
for file in index.html CNAME 404.html api/index.html manifest.json service-worker.js; do
if [ ! -f "dist/$file" ]; then
echo "::error::dist/$file is missing from the build"
exit 1
fi
echo "ok dist/$file"
done
- name: Serve the build
run: |
yarn preview --port 4173 &
for _ in $(seq 1 30); do
curl -sf http://localhost:4173/ >/dev/null && exit 0
sleep 1
done
echo "::error::preview server never came up"
exit 1
- name: Smoke test every route in a browser
run: yarn test:smoke http://localhost:4173
- name: Keep the tested build for the deploy job
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
retention-days: 7
deploy:
# publishes the exact build the smoke test passed against, rather than
# rebuilding, so what ships is what was verified
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
concurrency:
group: deploy-gh-pages
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Publish to the gh-pages branch
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
# gh-pages publishes from its own clone, which does not inherit the
# credentials actions/checkout writes into this working copy
npx --yes gh-pages@6 --dist dist --dotfiles \
--repo "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \
--message "Deploy ${GITHUB_SHA::7}: $(git log -1 --pretty=%s)"
- name: Wait for the deploy to go live
# compares the served page against what was just published rather than
# looking for an asset name, so an HTML-only change is verified too
run: |
for _ in $(seq 1 30); do
if curl -sf "https://gpu.rocks/?cachebust=$RANDOM" -o /tmp/live.html \
&& diff -q dist/index.html /tmp/live.html >/dev/null; then
curl -sf -o /dev/null "https://gpu.rocks/api/" \
&& echo "live, and /api/ is still served" && exit 0
echo "::error::the site is live but /api/ is not being served"
exit 1
fi
sleep 10
done
echo "::error::gpu.rocks is not serving the page that was just published"
diff dist/index.html /tmp/live.html || true
exit 1
- name: Purge the Cloudflare cache
# the origin is now serving the new build (previous step), but Cloudflare
# caches HTML for max-age=600 — purge so visitors see the deploy at once.
# Assets are content-hashed, so purge_everything is safe.
env:
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
run: |
if [ -z "$CLOUDFLARE_ZONE_ID" ] || [ -z "$CLOUDFLARE_API_TOKEN" ]; then
echo "Cloudflare secrets not configured — skipping purge (stale HTML expires within ~10 min)"
exit 0
fi
response=$(curl -s -X POST \
"https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/purge_cache" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}')
if echo "$response" | grep -q '"success": *true'; then
echo "Cloudflare cache purged"
else
echo "::error::Cloudflare purge failed: $response"
exit 1
fi