mirror of
https://github.com/twentyhq/twenty.git
synced 2026-06-11 17:37:18 -04:00
## Summary Adds review apps for the marketing site. Every PR that touches `packages/twenty-website/**` or `packages/twenty-shared/**` gets a per-version Worker preview URL, sticky-commented on the PR, auto-cleaned up when the PR closes. Same Cloudflare machinery skew protection rides on, just used for previews — no extra plan, no extra services. Cleaner than the GitHub-Actions-runner + Cloudflare-tunnel pattern: previews persist for the life of the version, accessible from anywhere, no warm-up. ## Files - **`.github/workflows/website-pr-preview.yaml`** — on PR open/sync/reopen: builds the Worker with a per-PR `DEPLOYMENT_ID`, runs `wrangler versions upload --tag pr-<N>` (no production traffic), sticky-comments the preview URL. Skipped on fork PRs because GitHub doesn't pass secrets to forks anyway. - **`.github/workflows/website-pr-preview-cleanup.yaml`** — on PR close: walks the Worker version list via the CF API, deletes anything tagged `pr-<N>` (with message-based fallback if the annotation key changes), updates the sticky comment. - **`open-next.config.ts`** — `maxNumberOfVersions: 10 → 50` to leave room for PR previews on top of skew protection's prod-version retention. ## How it looks on a PR The bot leaves a sticky comment like: > 🔍 **Website preview** is up at **https://abc12345-twenty-website-dev.twentyhq.workers.dev** > > | | | > |---|---| > | Version | `abc12345-...` | > | Commit | `<sha>` | > | Bindings | shared with the `dev` Worker (R2 cache + secrets) | > > Updates on every push. Auto-deleted when the PR closes. On close it becomes: > 🧹 Website preview for this PR was cleaned up after close. ## Twenty repo credentials already provisioned - `secret CLOUDFLARE_API_TOKEN` — same scoped token the `twenty-infra` workflow uses - `var CLOUDFLARE_ACCOUNT_ID` = `67b2bbe4381006564d2b0aa6ce6177be` - `var CF_PREVIEW_DOMAIN` = `twentyhq` (no `.workers.dev` suffix — OpenNext appends it; [opennextjs-cloudflare#811](https://github.com/opennextjs/opennextjs-cloudflare/issues/811)) ## Known limitations - **Shared dev bindings**: PR previews use the dev Worker's R2 bucket + secrets (Stripe test key, JWT private key). Fine for a read-mostly marketing site; if two simultaneous PRs ever fight over ISR cache state we can prefix R2 keys per-PR later. - **Fork PRs don't get previews**. GitHub Actions doesn't pass `secrets.*` to fork-PR runs (security), and the wrangler upload requires the CF token. To enable forks, would need to switch to `pull_request_target` and gate on a maintainer label — not done here because the security tradeoff isn't worth it for a marketing-site preview. - **Version cap**: 50 versions is the new ceiling, and `maxVersionAgeDays: 14` auto-prunes anything older. Cleanup-on-close should keep us well under in steady state. ## Test plan - [ ] CI on this PR triggers the preview workflow itself; check that the sticky comment appears with a working URL - [ ] Hit the URL, click around — should look like a fresh marketing-site build with this PR's changes - [ ] Close (don't merge) → cleanup workflow should run; sticky comment switches to the "cleaned up" message; the version is gone from `wrangler versions list --name twenty-website-dev`
70 lines
2.8 KiB
YAML
70 lines
2.8 KiB
YAML
name: 'Website Preview Dispatch'
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
on:
|
|
pull_request:
|
|
types: [opened, synchronize, reopened, closed, labeled]
|
|
paths:
|
|
- packages/twenty-website/**
|
|
- .github/workflows/website-preview-dispatch.yaml
|
|
|
|
concurrency:
|
|
# Keyed on PR number so independent PRs don't cancel each other. `github.ref`
|
|
# would resolve to the base branch under pull_request and collide.
|
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
trigger-build:
|
|
# Same fork PRs from outside the org don't have `secrets.*` so the dispatch
|
|
# call would fail anyway — skip explicitly to avoid noise.
|
|
if: |
|
|
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
github.event.action != 'closed' && (
|
|
(github.event.action == 'labeled' && github.event.label.name == 'preview-website') ||
|
|
(
|
|
(
|
|
github.event.pull_request.author_association == 'MEMBER' ||
|
|
github.event.pull_request.author_association == 'OWNER' ||
|
|
github.event.pull_request.author_association == 'COLLABORATOR'
|
|
) && contains(fromJSON('["opened","synchronize","reopened"]'), github.event.action)
|
|
)
|
|
)
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Dispatch website-preview-build to ci-privileged
|
|
env:
|
|
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
|
run: |
|
|
gh api repos/twentyhq/ci-privileged/dispatches \
|
|
-f event_type=website-preview-build \
|
|
-f "client_payload[pr_number]=$PR_NUMBER" \
|
|
-f "client_payload[pr_head_sha]=$PR_HEAD_SHA" \
|
|
-f "client_payload[pr_head_ref]=$PR_HEAD_REF"
|
|
|
|
trigger-cleanup:
|
|
# Covers both merge and close-without-merge — pull_request `closed` fires
|
|
# for both. PRs left open forever are covered by OpenNext's
|
|
# `maxVersionAgeDays: 14` + `maxNumberOfVersions: 50` auto-pruning in
|
|
# open-next.config.ts, so nothing leaks even if cleanup never runs.
|
|
if: |
|
|
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
github.event.action == 'closed'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Dispatch website-preview-cleanup to ci-privileged
|
|
env:
|
|
GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
run: |
|
|
gh api repos/twentyhq/ci-privileged/dispatches \
|
|
-f event_type=website-preview-cleanup \
|
|
-f "client_payload[pr_number]=$PR_NUMBER"
|