From 0783fcaeeb8f965a24a31bafe3a809a57fa76ee9 Mon Sep 17 00:00:00 2001 From: Jad wauthier Date: Wed, 12 Aug 2026 20:12:45 -0500 Subject: [PATCH] Add Cloudflare preview deployments for pull requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a CI job that uploads a new Worker version (wrangler versions upload, not a full deploy — never touches the production/Active deployment) on every pull request, tagged with a stable per-PR preview alias, and posts/updates the resulting preview URL as a PR comment. Also provisions the SESSION KV namespace that @astrojs/cloudflare's sessions feature auto-injects into the generated wrangler.json — it previously had no namespace ID, which would have hard-failed any real deploy regardless of this change (KV namespace IDs are not secret). Closes #4 Co-Authored-By: Claude Sonnet 5 --- .github/workflows/preview-deploy.yml | 66 ++++++++++++++++++++++++++++ wrangler.toml | 8 ++++ 2 files changed, 74 insertions(+) create mode 100644 .github/workflows/preview-deploy.yml diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml new file mode 100644 index 0000000..fe6cb25 --- /dev/null +++ b/.github/workflows/preview-deploy.yml @@ -0,0 +1,66 @@ +name: Preview Deploy + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: [main] + +permissions: + contents: read + pull-requests: write + +jobs: + preview-deploy: + name: Deploy Cloudflare Preview + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run build + + # Uploads a new Worker version (not a full deploy — never touches the + # production/Active deployment) with a preview alias tied to this PR + # number. The alias is stable across pushes to the same PR, so the + # posted URL never changes even as new commits land. Missing runtime + # secrets (e.g. RECAPTCHA_SECRET) don't block this step — Workers + # secrets are resolved at request time, not at version-upload time — + # so routes that don't depend on them (every page but the contact + # form's POST handler) preview correctly regardless. + - name: Upload preview version + env: + CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_KEY }} + CLOUDFLARE_ACCOUNT_ID: ${{ vars.CLOUDFLARE_ACCOUNT_ID }} + run: | + set -euo pipefail + npx wrangler versions upload \ + --config dist/server/wrangler.json \ + --preview-alias "pr-${{ github.event.number }}" \ + | tee wrangler-preview.log + + PREVIEW_URL=$(grep -oE 'https://[a-zA-Z0-9.-]*\.workers\.dev' wrangler-preview.log | head -n1) + if [ -z "$PREVIEW_URL" ]; then + echo "Could not find a preview URL in wrangler's output." >&2 + exit 1 + fi + echo "PREVIEW_URL=$PREVIEW_URL" >> "$GITHUB_ENV" + + - name: Post or update preview URL comment + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + MARKER="" + BODY="$(printf '%s\n🔍 **Preview deployment:** %s' "$MARKER" "$PREVIEW_URL")" + + EXISTING_ID=$(gh api "repos/${{ github.repository }}/issues/${{ github.event.number }}/comments" --paginate \ + --jq ".[] | select(.body | startswith(\"$MARKER\")) | .id" | head -n1) + + if [ -n "$EXISTING_ID" ]; then + gh api -X PATCH "repos/${{ github.repository }}/issues/comments/$EXISTING_ID" -f body="$BODY" + else + gh api -X POST "repos/${{ github.repository }}/issues/${{ github.event.number }}/comments" -f body="$BODY" + fi diff --git a/wrangler.toml b/wrangler.toml index 0eb9863..21a6522 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -4,6 +4,14 @@ compatibility_date = "2026-01-01" # the nodejs_compat flag. compatibility_flags = ["nodejs_compat"] +# Required by @astrojs/cloudflare's built-in sessions feature (auto-injects +# a SESSION KV binding regardless of whether this project uses sessions +# directly). KV namespace IDs are not secret — see +# https://developers.cloudflare.com/kv/concepts/kv-namespaces/. +[[kv_namespaces]] +binding = "SESSION" +id = "cf7dafbef60049ea95d4982cbe2504f9" + # Contact form outbound email, via the classic Email Routing `send_email` # binding (legacy EmailMessage/mimetext API) rather than the newer Email # Sending product, which requires a Workers Paid plan this account does not