From 04a0f73e0d4b45a90b0d517863aa8f5607dbfead Mon Sep 17 00:00:00 2001 From: Stuart Haigh Date: Tue, 25 Aug 2026 14:51:57 +0100 Subject: [PATCH] Add PR preview workflow via Cloudflare Worker versions Workers Builds deploys main to production but reports nothing back to GitHub, so PRs had no build check and no preview. This workflow builds every PR, uploads the result as a Worker version (production untouched) and posts a sticky comment with the preview URL. Without the Cloudflare secrets the build still runs as a check and the upload is skipped. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01HVg4d3Fg3MLk6MEyjeYowp --- .github/workflows/pr-preview.yml | 95 ++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 .github/workflows/pr-preview.yml diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml new file mode 100644 index 0000000..f6d841b --- /dev/null +++ b/.github/workflows/pr-preview.yml @@ -0,0 +1,95 @@ +# Builds every PR and uploads the result to Cloudflare as a Worker *version* — +# a preview that leaves production untouched — then comments the preview URL on +# the PR. Complements Workers Builds, which deploys `main` to production but +# reports nothing back to GitHub. +# +# Requires two repository secrets: +# CLOUDFLARE_API_TOKEN — API token with Workers Scripts: Edit on the account +# CLOUDFLARE_ACCOUNT_ID — the Cloudflare account id +# Until they are set (and on fork/Dependabot PRs, which cannot read secrets), +# the build still runs as a check and the preview upload is skipped with a note. +name: PR preview + +on: + pull_request: + branches: [main] + +concurrency: + group: pr-preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + +jobs: + preview: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version-file: .node-version + cache: npm + + - run: npm ci + + - run: npx astro build + + - name: Check for Cloudflare credentials + id: creds + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} + run: | + if [ -n "$CLOUDFLARE_API_TOKEN" ]; then + echo "available=true" >> "$GITHUB_OUTPUT" + else + echo "available=false" >> "$GITHUB_OUTPUT" + echo "CLOUDFLARE_API_TOKEN is not available (missing secret, fork PR, or Dependabot) — preview upload skipped." >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload preview version to Cloudflare + if: steps.creds.outputs.available == 'true' + id: upload + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: versions upload + + - name: Comment the preview URL on the PR + if: steps.creds.outputs.available == 'true' + uses: actions/github-script@v7 + env: + WRANGLER_OUTPUT: ${{ steps.upload.outputs.command-output }} + with: + script: | + const match = (process.env.WRANGLER_OUTPUT || '').match(/https:\/\/\S+\.workers\.dev/); + const marker = ''; + const sha = context.payload.pull_request.head.sha.slice(0, 7); + const body = match + ? `${marker}\n### 🔍 Preview\n\n**${match[0]}**\n\nBuilt from \`${sha}\`. Uploaded as a Worker version — production is unaffected until merge.` + : `${marker}\n### 🔍 Preview\n\nThe preview version uploaded, but no preview URL appeared in the wrangler output — check that the worker's Preview URLs setting is enabled. Built from \`${sha}\`.`; + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + per_page: 100, + }); + const existing = comments.find((c) => c.body && c.body.includes(marker)); + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + }