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, + }); + }