From 70e6b9635d824aef9365768d0aa815bffa081fef Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Thu, 21 May 2026 16:41:28 -0700 Subject: [PATCH 1/8] fix cloudflare pages(?) --- wrangler.jsonc | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 wrangler.jsonc diff --git a/wrangler.jsonc b/wrangler.jsonc new file mode 100644 index 0000000..d8d69ff --- /dev/null +++ b/wrangler.jsonc @@ -0,0 +1,7 @@ +{ + "name": "gaps", + "compatibility_date": "2026-05-21", + "assets": { + "directory": "./_site" + } +} From e6f6f974c43517c6d0c79eb142576975e83b4cdf Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Fri, 22 May 2026 19:53:20 -0500 Subject: [PATCH 2/8] Update compatibility date in wrangler.jsonc --- wrangler.jsonc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wrangler.jsonc b/wrangler.jsonc index d8d69ff..7ffd0bb 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -1,6 +1,7 @@ { + "$schema": "node_modules/wrangler/config-schema.json", "name": "gaps", - "compatibility_date": "2026-05-21", + "compatibility_date": "2026-05-23", "assets": { "directory": "./_site" } From a2189949e490df7835adafc9a413b24d5a4d318b Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Mon, 25 May 2026 01:27:45 -0500 Subject: [PATCH 3/8] Add unprivileged build workflow for PR deploy previews First half of a secure fork-safe deploy preview setup. This workflow runs on all PRs (including forks), builds the site with no access to secrets, and uploads the artifact. A separate workflow_run-triggered workflow will handle the privileged Cloudflare deploy. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/preview-build.yml | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/preview-build.yml diff --git a/.github/workflows/preview-build.yml b/.github/workflows/preview-build.yml new file mode 100644 index 0000000..7e6c838 --- /dev/null +++ b/.github/workflows/preview-build.yml @@ -0,0 +1,36 @@ +name: Build Preview + +on: + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + build: + name: Build site + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "24" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Build GAP website + run: npm run build + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: preview-build + path: _site + retention-days: 1 From 0f391138b78335140159057dc8cf16e9ff31d13a Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Mon, 25 May 2026 23:17:22 -0500 Subject: [PATCH 4/8] Add deploy preview workflow (workflow_run triggered) Second half of the secure fork-safe deploy preview setup. Downloads the build artifact from the unprivileged build workflow and deploys to Cloudflare Pages. Has access to secrets but never executes fork code. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/preview-deploy.yml | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 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..a0eb56b --- /dev/null +++ b/.github/workflows/preview-deploy.yml @@ -0,0 +1,32 @@ +name: Deploy Preview + +on: + workflow_run: + workflows: ["Build Preview"] + types: [completed] + +permissions: + contents: read + deployments: write + +jobs: + deploy: + name: Deploy preview to Cloudflare + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' }} + + steps: + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: preview-build + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + path: _site + + - name: Deploy to Cloudflare Pages + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy _site --project-name=gaps --branch=pr-${{ github.event.workflow_run.pull_requests[0].number }} From 59c21913acd118421f6259e39c8db56c80bd2249 Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Mon, 25 May 2026 23:25:08 -0500 Subject: [PATCH 5/8] Fix deploy preview workflows: fork PR support, action versions, permissions - Fix critical bug: pull_requests[] is empty for fork PRs in workflow_run events. Save PR number as artifact metadata instead. - Add actions: read permission so download-artifact can fetch cross-workflow - Update action versions to latest: checkout@v6, upload-artifact@v7, download-artifact@v8, wrangler-action@v4 Co-Authored-By: Claude Opus 4.7 --- .github/workflows/preview-build.yml | 7 +++++-- .github/workflows/preview-deploy.yml | 11 ++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/preview-build.yml b/.github/workflows/preview-build.yml index 7e6c838..c928dbb 100644 --- a/.github/workflows/preview-build.yml +++ b/.github/workflows/preview-build.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v6 @@ -28,8 +28,11 @@ jobs: - name: Build GAP website run: npm run build + - name: Save PR metadata + run: echo "${{ github.event.pull_request.number }}" > _site/.pr-number + - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: preview-build path: _site diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index a0eb56b..e08bf15 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -7,6 +7,7 @@ on: permissions: contents: read + actions: read deployments: write jobs: @@ -17,16 +18,20 @@ jobs: steps: - name: Download build artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: preview-build run-id: ${{ github.event.workflow_run.id }} github-token: ${{ secrets.GITHUB_TOKEN }} path: _site + - name: Read PR number + id: pr + run: echo "number=$(cat _site/.pr-number)" >> "$GITHUB_OUTPUT" + - name: Deploy to Cloudflare Pages - uses: cloudflare/wrangler-action@v3 + uses: cloudflare/wrangler-action@v4 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - command: pages deploy _site --project-name=gaps --branch=pr-${{ github.event.workflow_run.pull_requests[0].number }} + command: pages deploy _site --project-name=gaps --branch=pr-${{ steps.pr.outputs.number }} From 939c1d47f8282b98d10eb3a149248f481ad914a7 Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Wed, 27 May 2026 01:41:47 -0500 Subject: [PATCH 6/8] Update preview workflows from gaps-website-test-1, remove pages.yml Co-Authored-By: Claude Opus 4.7 --- .github/workflows/pages.yml | 57 ---------------------- .github/workflows/preview-build.yml | 9 +++- .github/workflows/preview-deploy.yml | 72 ++++++++++++++++++++++++++-- 3 files changed, 75 insertions(+), 63 deletions(-) delete mode 100644 .github/workflows/pages.yml diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml deleted file mode 100644 index 64a86b9..0000000 --- a/.github/workflows/pages.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Deploy Pages - -on: - push: - branches: [main] - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: pages - cancel-in-progress: false - -jobs: - build: - name: Build site - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v5 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: "24" - cache: "npm" - - - name: Install dependencies - run: npm ci - - - name: Build GAP website - run: npm run build - - - name: Configure Pages - uses: actions/configure-pages@v6 - - - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v5 - with: - path: _site - - deploy: - name: Deploy site - needs: build - 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@v5 diff --git a/.github/workflows/preview-build.yml b/.github/workflows/preview-build.yml index c928dbb..6410bd9 100644 --- a/.github/workflows/preview-build.yml +++ b/.github/workflows/preview-build.yml @@ -7,6 +7,10 @@ on: permissions: contents: read +concurrency: + group: preview-build-${{ github.event.pull_request.number }} + cancel-in-progress: true + jobs: build: name: Build site @@ -29,11 +33,12 @@ jobs: run: npm run build - name: Save PR metadata - run: echo "${{ github.event.pull_request.number }}" > _site/.pr-number + run: echo "${{ github.event.pull_request.number }}" > _site/pr-number - name: Upload build artifact uses: actions/upload-artifact@v7 with: name: preview-build path: _site - retention-days: 1 + include-hidden-files: true + retention-days: 3 diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index e08bf15..00badac 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -8,7 +8,7 @@ on: permissions: contents: read actions: read - deployments: write + pull-requests: write jobs: deploy: @@ -17,6 +17,12 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request' }} steps: + - name: Checkout wrangler config + uses: actions/checkout@v6 + with: + sparse-checkout: | + wrangler.jsonc + - name: Download build artifact uses: actions/download-artifact@v8 with: @@ -27,11 +33,69 @@ jobs: - name: Read PR number id: pr - run: echo "number=$(cat _site/.pr-number)" >> "$GITHUB_OUTPUT" + run: | + PR_NUM=$(cat _site/pr-number 2>/dev/null) + # Guard against injection (this value flows into API calls and shell commands) + if ! [[ "$PR_NUM" =~ ^[0-9]+$ ]]; then + echo "::error::Invalid or missing .pr-number from artifact" + exit 1 + fi + echo "number=$PR_NUM" >> "$GITHUB_OUTPUT" - - name: Deploy to Cloudflare Pages + - name: Deploy preview to Cloudflare Workers + id: deploy uses: cloudflare/wrangler-action@v4 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} - command: pages deploy _site --project-name=gaps --branch=pr-${{ steps.pr.outputs.number }} + command: versions upload --preview-alias "pr-${{ steps.pr.outputs.number }}" --message "PR #${{ steps.pr.outputs.number }} preview" + + - name: Hide old deploy preview comments + uses: actions/github-script@v7 + with: + script: | + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: ${{ steps.pr.outputs.number }} + }); + await Promise.all( + comments + .filter(c => c.body.includes('')) + .map(c => github.graphql(` + mutation($id: ID!) { + minimizeComment(input: { subjectId: $id, classifier: OUTDATED }) { + minimizedComment { isMinimized } + } + } + `, { id: c.node_id })) + ); + + - name: Comment on PR (success) + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: ${{ steps.pr.outputs.number }}, + body: [ + '', + '### 🚀 Deploy Preview', + '', + '- **This commit:** ${{ steps.deploy.outputs.deployment-url }}', + '- **This PR** (kept up to date): https://pr-${{ steps.pr.outputs.number }}-gaps-website-test-1.markl.workers.dev' + ].join('\n') + }); + + - name: Comment on PR (failure) + if: failure() + uses: actions/github-script@v7 + with: + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: ${{ steps.pr.outputs.number }}, + body: 'Deploy preview failed.\n\n```\ngh run view ${{ github.run_id }} --repo ${{ github.repository }} --log-failed\n```' + }); From fff39a1eb4f91b9d423ab84b74551117ec9c7041 Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Wed, 27 May 2026 01:45:19 -0500 Subject: [PATCH 7/8] Update preview-deploy.yml --- .github/workflows/preview-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index 00badac..0d5927b 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -84,7 +84,7 @@ jobs: '### 🚀 Deploy Preview', '', '- **This commit:** ${{ steps.deploy.outputs.deployment-url }}', - '- **This PR** (kept up to date): https://pr-${{ steps.pr.outputs.number }}-gaps-website-test-1.markl.workers.dev' + '- **This PR** (kept up to date): https://pr-${{ steps.pr.outputs.number }}-gaps.graphql-foundation.workers.dev' ].join('\n') }); From e84156054a6e501878dc98f738134bcdddeae0d6 Mon Sep 17 00:00:00 2001 From: Mark Larah Date: Wed, 27 May 2026 03:29:49 -0500 Subject: [PATCH 8/8] Harden deploy preview workflows - Pin all actions to commit SHAs (supply-chain protection) - Replace artifact-based PR number with gh pr list --head lookup (prevents PR number spoofing from untrusted artifacts) - Remove "Save PR metadata" step from build (no longer needed) Co-Authored-By: Claude Opus 4.7 --- .github/workflows/preview-build.yml | 9 +++------ .github/workflows/preview-deploy.yml | 23 +++++++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/preview-build.yml b/.github/workflows/preview-build.yml index 6410bd9..995bf90 100644 --- a/.github/workflows/preview-build.yml +++ b/.github/workflows/preview-build.yml @@ -18,10 +18,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Setup Node.js - uses: actions/setup-node@v6 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 with: node-version: "24" cache: "npm" @@ -32,11 +32,8 @@ jobs: - name: Build GAP website run: npm run build - - name: Save PR metadata - run: echo "${{ github.event.pull_request.number }}" > _site/pr-number - - name: Upload build artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: preview-build path: _site diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index 0d5927b..10263f5 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -18,40 +18,43 @@ jobs: steps: - name: Checkout wrangler config - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: sparse-checkout: | wrangler.jsonc - name: Download build artifact - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: name: preview-build run-id: ${{ github.event.workflow_run.id }} github-token: ${{ secrets.GITHUB_TOKEN }} path: _site - - name: Read PR number + - name: Get PR number id: pr + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - PR_NUM=$(cat _site/pr-number 2>/dev/null) - # Guard against injection (this value flows into API calls and shell commands) + PR_NUM=$(gh pr list --repo "${{ github.repository }}" \ + --head "${{ github.event.workflow_run.head_branch }}" \ + --json number --jq '.[0].number') if ! [[ "$PR_NUM" =~ ^[0-9]+$ ]]; then - echo "::error::Invalid or missing .pr-number from artifact" + echo "::error::Could not determine PR number" exit 1 fi echo "number=$PR_NUM" >> "$GITHUB_OUTPUT" - name: Deploy preview to Cloudflare Workers id: deploy - uses: cloudflare/wrangler-action@v4 + uses: cloudflare/wrangler-action@ebbaa1584979971c8614a24965b4405ff95890e0 # v4 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} command: versions upload --preview-alias "pr-${{ steps.pr.outputs.number }}" --message "PR #${{ steps.pr.outputs.number }} preview" - name: Hide old deploy preview comments - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const { data: comments } = await github.rest.issues.listComments({ @@ -72,7 +75,7 @@ jobs: ); - name: Comment on PR (success) - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | await github.rest.issues.createComment({ @@ -90,7 +93,7 @@ jobs: - name: Comment on PR (failure) if: failure() - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | await github.rest.issues.createComment({