Deploy to GitHub Pages #89
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: Deploy to GitHub Pages | |
| on: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| # SCHEDULED REBUILD — powers content scheduling. A static build freezes "now" at build | |
| # time, so a post with a future `pubDate` (src/lib/content.ts) or a video with a future | |
| # `publishedAt` (src/data/videos.ts) stays hidden until a build runs past that instant. | |
| # This cron re-runs the SAME build+deploy every 15 min so scheduled content reveals itself | |
| # AUTOMATICALLY — no manual step at publish time. Set the gate to your YouTube publish time | |
| # (or a few minutes after) and the site reveals the post + video together, after the video | |
| # is live. Caveat: GitHub cron is UTC and often runs several minutes LATE (never early), so | |
| # the gate guarantees "at or after" your time — exactly the "after YouTube" requirement. | |
| schedule: | |
| - cron: '*/15 * * * *' | |
| permissions: | |
| contents: read | |
| pages: write | |
| id-token: write | |
| # Serialize deploys: let an in-progress one FINISH, queue the next. Do NOT cancel — | |
| # cancelling a Pages deploy mid-flight wedges the `github-pages` environment queue, and the | |
| # next run then polls `deployment_queued` until it TIMES OUT (~10m). This bit us when a commit | |
| # + its submodule-pointer bump landed minutes apart. GitHub's own Pages starter uses `false`. | |
| concurrency: | |
| group: pages | |
| cancel-in-progress: false | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| # Gate for the deploy job. 'true' on every push / manual run; on a SCHEDULED run only | |
| # when the freshly-built site actually differs from what's live (see the guard step). | |
| should_deploy: ${{ steps.guard.outputs.should_deploy }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install, build, and upload | |
| uses: withastro/action@v4 | |
| # withastro/action runs `npm ci && npm run build` (which also runs | |
| # `pagefind --site dist`) and uploads ./dist as the Pages artifact. It leaves | |
| # ./dist in the workspace, so the guard below can inspect the built sitemap. | |
| - name: Decide whether the scheduled build is worth deploying | |
| id: guard | |
| # The cron fires every 15 min, but a scheduled reveal only ever ADDS a page (a due | |
| # post's /post/<slug>/ + its video's /video/<slug>/). So on a cron run we build, | |
| # then compare THIS build's sitemap URL set to the LIVE one: identical => nothing is | |
| # due => skip the redeploy (keeps the Pages deployment history clean). Any push or | |
| # manual run always deploys. FAIL-OPEN: if the live sitemap can't be read (network, | |
| # first deploy, format change) we deploy — a missed reveal is worse than a no-op deploy. | |
| # Only reads our own sitemap as DATA (piped to grep); no untrusted input hits the shell. | |
| run: | | |
| if [ "${{ github.event_name }}" != "schedule" ]; then | |
| echo "Not a scheduled run — always deploy." | |
| echo "should_deploy=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| built=$(grep -o '<loc>[^<]*</loc>' dist/sitemap-0.xml 2>/dev/null | sort -u) | |
| if [ -z "$built" ]; then | |
| echo "No built sitemap found — deploy to be safe." | |
| echo "should_deploy=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| live=$(curl -sf --max-time 30 https://prostdev.com/sitemap-0.xml | grep -o '<loc>[^<]*</loc>' | sort -u) | |
| if [ -z "$live" ]; then | |
| echo "Could not read live sitemap — deploy to be safe." | |
| echo "should_deploy=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| if [ "$built" = "$live" ]; then | |
| echo "Built site matches live — nothing due, skipping deploy." | |
| echo "should_deploy=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "Built site differs from live — deploying." | |
| echo "should_deploy=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| deploy: | |
| needs: build | |
| if: needs.build.outputs.should_deploy == 'true' | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| steps: | |
| - name: Deploy to GitHub Pages | |
| id: deployment | |
| uses: actions/deploy-pages@v4 |