From 358909084116296125d5d96a2048da28966d8951 Mon Sep 17 00:00:00 2001 From: Jess Date: Fri, 26 Jun 2026 13:37:13 +0100 Subject: [PATCH] feat(#204): implement preview deployments for pull requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two GitHub Actions workflows and a usage guide to automatically deploy an isolated frontend preview for every pull request. - .github/workflows/preview.yml Triggers on pull_request (opened / synchronize / reopened). Installs dependencies, builds the Vite dashboard with testnet env vars, and deploys to a Cloudflare Pages branch named pr- via cloudflare/wrangler-action. Posts a single bot comment to the PR containing the live preview URL and commit SHA; updates the same comment in place on every subsequent push (no duplicates). Uses a concurrency group so a fast-follow push cancels the in-flight deploy before starting a new one. - .github/workflows/preview-cleanup.yml Triggers on pull_request (closed) — covers both merge and manual close. Deletes the Cloudflare Pages branch deployment and edits the existing PR comment to show the preview has been removed. - PREVIEW_DEPLOYMENTS.md Documents how previews work, the required repository secrets (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, PREVIEW_EVENTS_API_URL), one-time Cloudflare Pages project setup, environment variables used in builds, and URL isolation between PRs. --- .github/workflows/preview-cleanup.yml | 57 ++++++++++++++++ .github/workflows/preview.yml | 94 ++++++++++++++++++++++++++ PREVIEW_DEPLOYMENTS.md | 96 +++++++++++++++++++++++++++ 3 files changed, 247 insertions(+) create mode 100644 .github/workflows/preview-cleanup.yml create mode 100644 .github/workflows/preview.yml create mode 100644 PREVIEW_DEPLOYMENTS.md diff --git a/.github/workflows/preview-cleanup.yml b/.github/workflows/preview-cleanup.yml new file mode 100644 index 0000000..28731a2 --- /dev/null +++ b/.github/workflows/preview-cleanup.yml @@ -0,0 +1,57 @@ +name: Preview Cleanup + +on: + pull_request: + types: [closed] + +jobs: + cleanup-preview: + name: Remove preview (PR #${{ github.event.pull_request.number }}) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + deployments: write + + steps: + - uses: actions/checkout@v4 + + - name: Delete Cloudflare Pages deployment + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deployment delete --project-name=notify-chain-dashboard --branch=pr-${{ github.event.pull_request.number }} --yes + + - name: Update PR comment to mark preview removed + uses: actions/github-script@v7 + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + with: + script: | + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: Number(process.env.PR_NUMBER), + }); + + const marker = ''; + const existing = comments.find(c => c.body.includes(marker)); + if (!existing) return; + + const reason = context.payload.pull_request.merged ? 'merged' : 'closed'; + const body = [ + marker, + '## 🚀 Preview Deployment', + '', + `| | |`, + `|---|---|`, + `| **Status** | 🗑️ Removed (PR ${reason}) |`, + ].join('\n'); + + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml new file mode 100644 index 0000000..84d9f4f --- /dev/null +++ b/.github/workflows/preview.yml @@ -0,0 +1,94 @@ +name: Preview Deployment + +on: + pull_request: + types: [opened, synchronize, reopened] + +concurrency: + group: preview-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + deploy-preview: + name: Deploy preview (PR #${{ github.event.pull_request.number }}) + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + deployments: write + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 18 + cache: npm + cache-dependency-path: dashboard/package-lock.json + + - name: Install dependencies + working-directory: dashboard + run: npm ci + + - name: Build dashboard + working-directory: dashboard + env: + VITE_EVENTS_API_URL: ${{ secrets.PREVIEW_EVENTS_API_URL }} + VITE_STELLAR_NETWORK: TESTNET + run: npm run build + + - name: Deploy to Cloudflare Pages + id: deploy + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} + command: pages deploy dashboard/dist --project-name=notify-chain-dashboard --branch=pr-${{ github.event.pull_request.number }} + + - name: Post preview URL comment + uses: actions/github-script@v7 + env: + DEPLOYMENT_URL: ${{ steps.deploy.outputs.deployment-url }} + PR_NUMBER: ${{ github.event.pull_request.number }} + COMMIT_SHA: ${{ github.event.pull_request.head.sha }} + with: + script: | + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: Number(process.env.PR_NUMBER), + }); + + const marker = ''; + const existing = comments.find(c => c.body.includes(marker)); + + const short = process.env.COMMIT_SHA.slice(0, 7); + const body = [ + marker, + '## 🚀 Preview Deployment', + '', + `| | |`, + `|---|---|`, + `| **URL** | ${process.env.DEPLOYMENT_URL} |`, + `| **Commit** | \`${short}\` |`, + `| **Status** | ✅ Ready |`, + '', + '_This comment is updated automatically on every push to the PR._', + ].join('\n'); + + 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: Number(process.env.PR_NUMBER), + body, + }); + } diff --git a/PREVIEW_DEPLOYMENTS.md b/PREVIEW_DEPLOYMENTS.md new file mode 100644 index 0000000..e918c80 --- /dev/null +++ b/PREVIEW_DEPLOYMENTS.md @@ -0,0 +1,96 @@ +# Preview Deployments + +Every pull request that touches the repository automatically gets a live preview +of the dashboard deployed to Cloudflare Pages. + +--- + +## How it works + +| Event | Action | +|---|---| +| PR opened / new commit pushed | Build the dashboard and deploy to a unique Cloudflare Pages branch URL | +| PR merged or closed | Delete the preview deployment and update the PR comment | + +The deployment URL is posted as a comment on the PR and updated on every +subsequent push. Only one comment is ever created per PR — it is edited in +place, not duplicated. + +--- + +## Accessing a preview + +1. Open the pull request on GitHub. +2. Find the comment from the `github-actions` bot titled **🚀 Preview + Deployment**. +3. Click the URL in the table. The preview reflects the exact commit at the + head of the PR branch. + +--- + +## Required repository secrets + +A repository administrator must configure the following secrets under +**Settings → Secrets and variables → Actions** before previews will work. + +| Secret | Where to find it | +|---|---| +| `CLOUDFLARE_API_TOKEN` | Cloudflare dashboard → **My Profile → API Tokens**. Create a token with the **Cloudflare Pages: Edit** permission scoped to your account. | +| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare dashboard → right-hand sidebar on the **Workers & Pages** overview page. | +| `PREVIEW_EVENTS_API_URL` | The URL of a shared testnet listener instance used by all previews, e.g. `https://listener.testnet.example.com/api/events`. If none is available, leave this secret unset and the dashboard falls back to mock data automatically. | + +--- + +## Cloudflare Pages project setup + +The workflow targets a project named **`notify-chain-dashboard`**. If this +project does not exist yet, create it once: + +```bash +# Install Wrangler if you don't already have it +npm install -g wrangler + +# Authenticate +wrangler login + +# Create the project (one-time setup — no need to link a Git repo) +wrangler pages project create notify-chain-dashboard +``` + +All subsequent deployments are handled entirely by the GitHub Actions workflows. + +--- + +## Workflow files + +| File | Purpose | +|---|---| +| `.github/workflows/preview.yml` | Triggered on `pull_request` (opened / synchronize / reopened). Builds the dashboard and deploys to Cloudflare Pages on a branch named `pr-`. Posts or updates a PR comment with the URL. | +| `.github/workflows/preview-cleanup.yml` | Triggered on `pull_request` (closed). Deletes the Cloudflare Pages deployment for the branch and updates the PR comment to indicate the preview has been removed. | + +--- + +## Environment variables in previews + +Previews are built with the following Vite environment variables: + +| Variable | Value | +|---|---| +| `VITE_EVENTS_API_URL` | Value of the `PREVIEW_EVENTS_API_URL` repository secret | +| `VITE_STELLAR_NETWORK` | `TESTNET` | + +If `PREVIEW_EVENTS_API_URL` is not set, the dashboard automatically falls back +to built-in mock event data so the preview is still usable for UI review. + +--- + +## Isolation + +Each PR gets its own Cloudflare Pages branch deployment at a URL in the form: + +``` +https://pr-..pages.dev +``` + +Deployments are fully isolated — they do not share state, cookies, or API +connections with each other or with the staging/production environment.