Skip to content

Docs Preview (Post-Build) #589

Docs Preview (Post-Build)

Docs Preview (Post-Build) #589

# Handles privileged work after docs previews are ready.
#
# Fork PRs use workflow_run. Fork code runs in the untrusted pull_request
# context and produces a static HTML artifact. This workflow deploys that
# artifact, but never checks out or executes fork code with secrets.
#
# Internal PRs use Worker Builds. The trusted Cloudflare check_run event starts
# visual regression after the Worker preview is ready.
#
# IMPORTANT: ALL checkouts in this workflow use ref: main (or no ref, which
# also defaults to main via the workflow_run base context). The wrangler.jsonc
# was previously checked out from the fork's SHA, but wrangler supports a
# build.command field that executes arbitrary shell commands — meaning a
# malicious wrangler.jsonc from a fork could exfiltrate CLOUDFLARE_API_TOKEN.
# We always deploy using the wrangler config from main.
#
# The visual-regression job always checks out ci/visual-regression from main
# (never the fork's SHA) before running it with secrets. The fork's component
# changes are captured via the deployed preview URL (AFTER_URL), not via
# executing the fork's copy of the script.
#
name: Docs Preview (Post-Build)
on:
workflow_run:
workflows: ["Docs Checks (PR)"]
types: [completed]
check_run:
types: [completed]
permissions:
actions: read
contents: read
pull-requests: write
jobs:
deploy:
name: Docs Deploy (Fork)
# Only run for:
# 1. Successful workflow runs
# 2. Pull request events (not push)
# 3. Fork PRs only (Worker Builds deploy internal PRs)
if: >-
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.head_repository.full_name != github.repository
runs-on: ubuntu-latest
timeout-minutes: 10
concurrency:
group: fork-preview-${{ github.event.workflow_run.head_repository.id }}-${{ github.event.workflow_run.head_branch }}
cancel-in-progress: true
outputs:
preview_url: ${{ steps.deploy.outputs.preview_url }}
pr_number: ${{ steps.metadata.outputs.pr_number }}
head_sha: ${{ steps.metadata.outputs.head_sha }}
preview_commit: ${{ steps.preview.outputs.preview_commit }}
steps:
# Always check out wrangler config from main — never from the fork's SHA.
# wrangler.jsonc supports a build.command field that executes arbitrary
# shell commands; checking out the fork's version with CLOUDFLARE_API_TOKEN
# in env would allow secret exfiltration via a malicious config.
- name: Checkout wrangler config from main
uses: actions/checkout@v4
with:
sparse-checkout: |
packages/kumo-docs-astro/wrangler.jsonc
ci/preview
.github/actions/install-dependencies
ref: main
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
with:
filter: kumo-workspace
- name: Resolve PR metadata
id: metadata
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: vp exec tsx ci/preview/preview.ts resolve-fork
- name: Download docs artifact
uses: actions/download-artifact@v4
with:
name: docs-preview-dist
path: packages/kumo-docs-astro/dist/
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Read preview commit
id: preview
run: vp exec tsx ci/preview/preview.ts read-artifact
- name: Install wrangler
run: npm install -g wrangler
- name: Deploy docs preview
id: deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
PR_NUMBER: ${{ steps.metadata.outputs.pr_number }}
HEAD_SHA: ${{ steps.metadata.outputs.head_sha }}
run: vp exec tsx ci/preview/preview.ts deploy
- name: Comment on PR
env:
PREVIEW_URL: ${{ steps.deploy.outputs.preview_url }}
HEAD_SHA: ${{ steps.metadata.outputs.head_sha }}
PR_NUMBER: ${{ steps.metadata.outputs.pr_number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: vp exec tsx ci/preview/preview.ts comment
visual-regression-fork:
name: Visual Regression (Fork)
needs: deploy
if: needs.deploy.outputs.preview_url != ''
concurrency:
group: visual-regression-${{ needs.deploy.outputs.pr_number }}
cancel-in-progress: true
uses: ./.github/workflows/visual-regression.yml
with:
preview_url: ${{ needs.deploy.outputs.preview_url }}
preview_commit: ${{ needs.deploy.outputs.preview_commit }}
pr_number: ${{ needs.deploy.outputs.pr_number }}
head_sha: ${{ needs.deploy.outputs.head_sha }}
artifact_name: visual-regression-screenshots-fork
secrets: inherit
resolve_internal_preview:
name: Resolve Internal Preview
if: >-
github.event_name == 'check_run' &&
github.event.check_run.name == 'Workers Builds: kumo-docs' &&
github.event.check_run.app.slug == 'cloudflare-workers-and-pages' &&
github.event.check_run.conclusion == 'success' &&
github.event.check_run.pull_requests[0].number != null
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
current: ${{ steps.metadata.outputs.current }}
preview_url: ${{ steps.metadata.outputs.preview_url }}
pr_number: ${{ steps.metadata.outputs.pr_number }}
head_sha: ${{ steps.metadata.outputs.head_sha }}
steps:
- name: Checkout preview tooling from main
uses: actions/checkout@v4
with:
ref: main
sparse-checkout: |
ci/preview
.github/actions/install-dependencies
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
with:
filter: kumo-workspace
- name: Resolve preview
id: metadata
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: vp exec tsx ci/preview/preview.ts resolve-internal
visual-regression-internal:
name: Visual Regression
needs: resolve_internal_preview
if: needs.resolve_internal_preview.outputs.current == 'true'
concurrency:
group: visual-regression-${{ needs.resolve_internal_preview.outputs.pr_number }}
cancel-in-progress: true
uses: ./.github/workflows/visual-regression.yml
with:
preview_url: ${{ needs.resolve_internal_preview.outputs.preview_url }}
preview_commit: ${{ needs.resolve_internal_preview.outputs.head_sha }}
pr_number: ${{ needs.resolve_internal_preview.outputs.pr_number }}
head_sha: ${{ needs.resolve_internal_preview.outputs.head_sha }}
artifact_name: visual-regression-screenshots
secrets: inherit