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: 7cloudflare/kumo
Publicmirrored from https://github.com/cloudflare/kumoAvailable
.github/workflows/preview.yml
278lines · modepreview