cloudflare/kumo

Public

mirrored from https://github.com/cloudflare/kumoAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
main

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

.github/workflows/preview.yml

278lines · modepreview

name: Preview

on:
  pull_request:
  # Also trigger on push to PR branches (for bots that push via API)
  push:
    branches:
      - "opencode/**"
      - "changeset-release/main"

permissions:
  contents: write
  pull-requests: write

concurrency:
  group: preview-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

jobs:
  # Resolve PR number for push events (bots pushing via API)
  resolve-pr:
    name: Resolve PR
    if: ${{ github.event_name == 'push' }}
    runs-on: ubuntu-latest
    outputs:
      pr_number: ${{ steps.find-pr.outputs.pr_number }}
      is_fork: ${{ steps.find-pr.outputs.is_fork }}
    steps:
      - name: Find PR for branch
        id: find-pr
        uses: actions/github-script@v7
        with:
          script: |
            const branch = context.ref.replace('refs/heads/', '');
            const { data: prs } = await github.rest.pulls.list({
              owner: context.repo.owner,
              repo: context.repo.repo,
              head: `${context.repo.owner}:${branch}`,
              state: 'open',
            });

            if (prs.length === 0) {
              core.warning(`No open PR found for branch ${branch}; skipping PR-scoped preview steps.`);
              core.setOutput('pr_number', '');
              core.setOutput('is_fork', '');
              return;
            }

            const pr = prs[0];
            core.setOutput('pr_number', pr.number.toString());
            core.setOutput('is_fork', (pr.head.repo.full_name !== pr.base.repo.full_name).toString());

  pkg-preview:
    name: Package Preview
    if: ${{ github.repository_owner == 'cloudflare' }}
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Install Dependencies
        uses: ./.github/actions/install-dependencies
        with:
          filter: "@cloudflare/kumo..."

      - name: Build @cloudflare/kumo
        run: pnpm --filter @cloudflare/kumo build

      - name: Publish preview package
        run: pnpm dlx pkg-pr-new publish --pnpm --compact --no-template ./packages/kumo

  # Stage 1: Build docs (runs for ALL PRs including forks).
  # Produces an artifact consumed by either docs-deploy (internal) or
  # the preview-deploy.yml workflow_run workflow (forks).
  docs-build:
    name: Docs Build
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Install Dependencies
        uses: ./.github/actions/install-dependencies
        with:
          filter: "@cloudflare/kumo-docs-astro..."

      - name: Build @cloudflare/kumo
        run: pnpm --filter @cloudflare/kumo build

      - name: Build docs
        run: pnpm --filter @cloudflare/kumo-docs-astro build

      - name: Test docs
        run: pnpm --filter @cloudflare/kumo-docs-astro test

      - name: Upload docs artifact
        uses: actions/upload-artifact@v4
        with:
          name: docs-preview-dist
          path: packages/kumo-docs-astro/dist/
          include-hidden-files: true
          retention-days: 1

  # Stage 2 (internal path): Deploy docs preview for non-fork PRs.
  # Runs in the same workflow with secrets available.
  # Fork PRs are handled by preview-deploy.yml (workflow_run).
  docs-deploy:
    name: Docs Deploy
    needs: [docs-build, resolve-pr]
    # Run for pull_request (non-fork) OR push events with resolved PR (non-fork)
    if: |
      always() &&
      needs.docs-build.result == 'success' &&
      (
        (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) ||
        (github.event_name == 'push' && needs.resolve-pr.result == 'success' && needs.resolve-pr.outputs.is_fork == 'false')
      )
    runs-on: ubuntu-latest
    timeout-minutes: 10
    outputs:
      preview_url: ${{ steps.deploy.outputs.preview_url }}
      pr_number: ${{ steps.set-pr.outputs.pr_number }}
    steps:
      - name: Set PR number
        id: set-pr
        run: |
          if [ "${{ github.event_name }}" == "pull_request" ]; then
            echo "pr_number=${{ github.event.pull_request.number }}" >> "$GITHUB_OUTPUT"
          else
            echo "pr_number=${{ needs.resolve-pr.outputs.pr_number }}" >> "$GITHUB_OUTPUT"
          fi

      - name: Checkout
        uses: actions/checkout@v4
        with:
          sparse-checkout: packages/kumo-docs-astro/wrangler.jsonc

      - name: Download docs artifact
        uses: actions/download-artifact@v4
        with:
          name: docs-preview-dist
          path: packages/kumo-docs-astro/dist/

      - name: Deploy docs preview
        id: deploy
        working-directory: packages/kumo-docs-astro
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          PR_NUMBER: ${{ steps.set-pr.outputs.pr_number }}
        run: |
          COMMIT_SHORT="${GITHUB_SHA:0:7}"

          # "wrangler versions upload" creates a preview version without
          # routing production traffic. Safe to run on every PR.
          VERSION_OUTPUT=$(npx wrangler versions upload --env="" --message "PR #${PR_NUMBER} (${COMMIT_SHORT})" 2>&1) || true
          echo "$VERSION_OUTPUT"

          # Verify upload succeeded
          if ! echo "$VERSION_OUTPUT" | grep -q "Worker Version ID:"; then
            echo "::error::wrangler versions upload failed -- no Worker Version ID in output"
            exit 1
          fi

          # Extract preview URL from wrangler output
          PREVIEW_URL=$(echo "$VERSION_OUTPUT" | grep -oE 'Version Preview URL: https://[^ ]+' | sed 's/Version Preview URL: //')

          # Fallback: construct URL from version ID
          if [ -z "$PREVIEW_URL" ]; then
            VERSION_ID=$(echo "$VERSION_OUTPUT" | grep -oE 'Worker Version ID: [a-f0-9-]+' | sed 's/Worker Version ID: //')
            if [ -n "$VERSION_ID" ]; then
              VERSION_PREFIX=$(echo "$VERSION_ID" | cut -c1-8)
              PREVIEW_URL="https://${VERSION_PREFIX}-kumo-docs.design-engineering.workers.dev"
            fi
          fi

          if [ -z "$PREVIEW_URL" ]; then
            echo "::error::Failed to determine docs preview URL"
            exit 1
          fi

          echo "preview_url=$PREVIEW_URL" >> "$GITHUB_OUTPUT"

      - name: Comment on PR
        uses: actions/github-script@v7
        env:
          PREVIEW_URL: ${{ steps.deploy.outputs.preview_url }}
          PR_NUMBER: ${{ steps.set-pr.outputs.pr_number }}
        with:
          script: |
            const marker = '<!-- kumo-docs-preview -->';
            const previewUrl = process.env.PREVIEW_URL;
            const prNumber = parseInt(process.env.PR_NUMBER, 10);
            const commitSha = context.sha.substring(0, 7);
            const body = [
              marker,
              '### Docs Preview',
              '',
              `[View docs preview](${previewUrl})`,
              '',
              `Commit: \`${commitSha}\``,
            ].join('\n');

            const { data: comments } = await github.rest.issues.listComments({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: prNumber,
            });

            const existing = comments.find(c => c.body?.startsWith(marker));

            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: prNumber,
                body,
              });
            }

  visual-regression:
    name: Visual Regression
    needs: docs-deploy
    if: always() && needs.docs-deploy.result == 'success' && needs.docs-deploy.outputs.preview_url != ''
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      # Pin to main so that a PR modifying run-visual-regression.ts cannot
      # execute its own version of the script with secrets in env.
      - name: Checkout VR script from main
        uses: actions/checkout@v4
        with:
          ref: main
          sparse-checkout: |
            ci/visual-regression
            .github/actions/install-dependencies

      # Fetch the PR's HEAD so git diff can detect changed files.
      # (HEAD points to main after checkout, so we need the explicit SHA)
      - name: Fetch PR head for git diff
        run: git fetch --depth=1 origin ${{ github.event.pull_request.head.sha }}

      - name: Install Dependencies
        uses: ./.github/actions/install-dependencies
        with:
          filter: "@cloudflare/kumo..."

      - name: Run Visual Regression
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_PR_NUMBER: ${{ needs.docs-deploy.outputs.pr_number }}
          GITHUB_REPOSITORY: ${{ github.repository }}
          PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
          BEFORE_URL: "https://kumo-ui.com"
          AFTER_URL: ${{ needs.docs-deploy.outputs.preview_url }}
          SCREENSHOT_API_KEY: ${{ secrets.SCREENSHOT_API_KEY }}
        run: pnpm tsx ci/visual-regression/run-visual-regression.ts

      - name: Upload Screenshots
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: visual-regression-screenshots
          path: ci/visual-regression/screenshots/
          retention-days: 7