learn: resize the four payoff tasks so the better algorithm actually … #45
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 (HTML only) | |
| # HTML must be purged: it names the new content-hashed chunks, and a | |
| # stale page would ask for assets this deploy replaced. The assets | |
| # themselves must NOT be — their names change whenever their bytes do, | |
| # so they are immutable, and purge_everything just forces every visitor | |
| # to re-pull ~2 MB from GitHub Pages cold. Doing that on back-to-back | |
| # deploys is what turned this site into 8-17 s origin fetches and edge | |
| # 520s. Cloudflare takes at most 30 URLs per call, hence the batching. | |
| 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 | |
| # Every prerendered page, in both the slashless and trailing-slash | |
| # forms GitHub Pages serves — Cloudflare keys the cache on the exact | |
| # URL, so both need purging. Only index.html files name real URLs: | |
| # prerender also writes a <route>.html twin so GitHub Pages serves the | |
| # slashless path without a redirect, but nobody requests that name — | |
| # purging it would be 95 wasted origin fetches. dist/api is the | |
| # vendored gpu.js reference: it changes only on a version bump, is not | |
| # coupled to our hashed chunks, and can expire on its own max-age. | |
| find dist -name 'index.html' -not -path 'dist/api/*' | while read -r file; do | |
| rel=${file#dist/} | |
| if [ "$rel" = "index.html" ]; then | |
| echo "https://gpu.rocks/" | |
| else | |
| dir=${rel%/index.html} | |
| echo "https://gpu.rocks/$dir" | |
| echo "https://gpu.rocks/$dir/" | |
| fi | |
| done | sort -u > /tmp/purge-urls.txt | |
| printf '%s\n' \ | |
| "https://gpu.rocks/404.html" \ | |
| "https://gpu.rocks/sitemap.xml" \ | |
| "https://gpu.rocks/robots.txt" >> /tmp/purge-urls.txt | |
| echo "purging $(wc -l < /tmp/purge-urls.txt) HTML URLs (assets left warm)" | |
| split -l 30 /tmp/purge-urls.txt /tmp/purge-batch- | |
| failed=0 | |
| for batch in /tmp/purge-batch-*; do | |
| payload=$(jq -R -s -c 'split("\n") | map(select(length > 0)) | {files: .}' < "$batch") | |
| 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 "$payload") | |
| if echo "$response" | grep -q '"success": *true'; then | |
| echo " purged $(wc -l < "$batch") URLs" | |
| else | |
| echo "::error::Cloudflare purge failed: $response" | |
| failed=1 | |
| fi | |
| done | |
| [ "$failed" -eq 0 ] || exit 1 | |
| echo "Cloudflare HTML cache purged" |