mirror of
https://github.com/CopilotKit/CopilotKit.git
synced 2026-09-14 16:26:20 +08:00
5c0150392f
Every Playwright install in CI passed `--with-deps`, which runs `apt-get update` before downloading the browser. apt on the runners cannot always reach azure.archive.ubuntu.com; when it can't it retries for many minutes, which is long enough to burn a job's whole `timeout-minutes` budget before a single test runs. GitHub renders that kill as "The operation was canceled", so it reads as a test failure rather than an infrastructure hang. Chromium's system libraries are already present on the Ubuntu runner images, and every one of these steps installs chromium only, so the browser download is all they need. Six jobs lose their apt dependency: test_unit, test_e2e-legacy-v1, test_e2e-showcase-on-demand, test_showcase-frontend-matrix, showcase_eval and showcase_capture-previews. Ports CopilotKit/website#529 to this repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
286 lines
12 KiB
YAML
286 lines
12 KiB
YAML
name: "Showcase: Capture Previews"
|
|
|
|
on:
|
|
workflow_run:
|
|
workflows: ["Showcase: Build & Push"]
|
|
types: [completed]
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
inputs:
|
|
slug:
|
|
description: "Integration slug to capture (leave empty for all)"
|
|
type: string
|
|
default: ""
|
|
demo:
|
|
description: "Demo ID to capture (leave empty for all)"
|
|
type: string
|
|
default: ""
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- "showcase/integrations/*/src/app/demos/**"
|
|
- "showcase/integrations/*/manifest.yaml"
|
|
|
|
concurrency:
|
|
group: showcase-capture-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: write
|
|
|
|
jobs:
|
|
capture:
|
|
name: Capture Preview MP4s
|
|
# Hoist the Slack webhook into an env var so step-level `if:`
|
|
# expressions can reference it — `secrets.*` is not a valid
|
|
# named-value inside `if:` and causes a workflow startup failure.
|
|
env:
|
|
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }}
|
|
runs-on: ubuntu-latest
|
|
# Captures demo previews as MP4 and uploads them to a GitHub release.
|
|
# Loop prevention: capture commits registry.json with the message below,
|
|
# which could re-trigger via push or the completed-deploy workflow_run.
|
|
# Skip when the triggering commit came from this workflow itself.
|
|
if: >-
|
|
(github.event_name != 'workflow_run' ||
|
|
!startsWith(github.event.workflow_run.head_commit.message, 'Update preview URLs in registry')) &&
|
|
(github.event_name != 'push' ||
|
|
!startsWith(github.event.head_commit.message, 'Update preview URLs in registry'))
|
|
timeout-minutes: 30
|
|
steps:
|
|
- name: Mint devops-bot token
|
|
# PROTECT_OUR_MAIN requires a PR for pushes to main; the default
|
|
# GITHUB_TOKEN fails with GH013 on the `Commit registry updates`
|
|
# step below. devops-bot (app-id 1108748) is a configured bypass
|
|
# actor on that ruleset — mint its installation token here and use
|
|
# it for checkout + push. Mirrors the pattern in
|
|
# CopilotKit/internal-skills's sync-versions.yml.
|
|
id: app-token
|
|
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
|
|
with:
|
|
app-id: "1108748"
|
|
private-key: ${{ secrets.DEVOPS_BOT_PRIVATE_KEY }}
|
|
permission-contents: write
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
|
with:
|
|
lfs: true
|
|
ref: ${{ github.event.workflow_run.head_branch || github.ref }}
|
|
token: ${{ steps.app-token.outputs.token }}
|
|
persist-credentials: false
|
|
|
|
- name: Ensure preview release exists
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
gh release view showcase-previews --repo ${{ github.repository }} >/dev/null 2>&1 || \
|
|
gh release create showcase-previews --repo ${{ github.repository }} \
|
|
--title "Showcase Preview Videos" \
|
|
--notes "Auto-generated demo preview videos for the showcase platform. Updated by CI." \
|
|
--latest=false
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: 20
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
|
|
|
|
- name: Install ffmpeg
|
|
run: sudo apt-get update && sudo apt-get install -y ffmpeg
|
|
|
|
# No --with-deps: it shells out to apt, which on the runners cannot always
|
|
# reach azure.archive.ubuntu.com and retries for many minutes — long enough to
|
|
# burn this job's whole timeout before a test runs. Chromium's system libraries
|
|
# are already present on the Ubuntu runner image, so downloading the browser is
|
|
# all this step needs.
|
|
- name: Install Playwright
|
|
run: npx playwright install chromium
|
|
|
|
- name: Install script dependencies
|
|
working-directory: showcase/scripts
|
|
run: pnpm install --ignore-scripts || true
|
|
|
|
- name: Detect changed packages
|
|
id: detect
|
|
if: github.event_name == 'push'
|
|
run: |
|
|
# Find which package slugs had demo changes
|
|
CHANGED=$(git diff --name-only HEAD~1 HEAD -- 'showcase/integrations/*/src/app/demos/' 'showcase/integrations/*/manifest.yaml' | \
|
|
sed -n 's|showcase/integrations/\([^/]*\)/.*|\1|p' | sort -u | tr '\n' ',' | sed 's/,$//')
|
|
echo "slugs=$CHANGED" >> $GITHUB_OUTPUT
|
|
echo "Changed packages: $CHANGED"
|
|
|
|
- name: Generate registry
|
|
run: |
|
|
cd showcase/scripts
|
|
npm ci --silent
|
|
npx tsx generate-registry.ts
|
|
|
|
- name: Capture previews
|
|
env:
|
|
INPUT_SLUG: ${{ inputs.slug }}
|
|
INPUT_DEMO: ${{ inputs.demo }}
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
DETECTED_SLUGS: ${{ steps.detect.outputs.slugs }}
|
|
run: |
|
|
# Build the args as an array (not a space-joined string) so a
|
|
# slug or demo value containing whitespace or shell metacharacters
|
|
# stays a single argument rather than being re-tokenized by the shell.
|
|
ARGS=()
|
|
# Use input slug/demo if provided (manual dispatch)
|
|
if [ -n "$INPUT_SLUG" ]; then
|
|
ARGS+=(--slug "$INPUT_SLUG")
|
|
fi
|
|
if [ -n "$INPUT_DEMO" ]; then
|
|
ARGS+=(--demo "$INPUT_DEMO")
|
|
fi
|
|
# Use detected slugs for push events (capture only changed)
|
|
if [ "$EVENT_NAME" = "push" ] && [ -n "$DETECTED_SLUGS" ]; then
|
|
# Capture each changed slug
|
|
IFS=',' read -ra SLUGS <<< "$DETECTED_SLUGS"
|
|
for slug in "${SLUGS[@]}"; do
|
|
npx tsx showcase/scripts/capture-previews.ts --slug "$slug" || true
|
|
done
|
|
else
|
|
npx tsx showcase/scripts/capture-previews.ts "${ARGS[@]}" || true
|
|
fi
|
|
|
|
- name: Configure git for push
|
|
run: |
|
|
git config user.name "devops-bot[bot]"
|
|
git config user.email "1108748+devops-bot[bot]@users.noreply.github.com"
|
|
git config --local url."https://x-access-token:${TOKEN}@github.com/".insteadOf "https://github.com/"
|
|
env:
|
|
TOKEN: ${{ steps.app-token.outputs.token }}
|
|
|
|
- name: Commit registry updates
|
|
run: |
|
|
cd showcase/shell/src/data
|
|
if git diff --quiet registry.json; then
|
|
echo "No registry changes"
|
|
exit 0
|
|
fi
|
|
git add -f registry.json
|
|
git commit -m "Update preview URLs in registry"
|
|
# Rebase-and-retry loop: the capture job can take ~30 minutes,
|
|
# during which other commits routinely land on main. Without
|
|
# this loop, `git push` loses the race and fails with "fetch
|
|
# first" (observed in runs 24799601181 and 24809331709 on
|
|
# 2026-04-22). Rebase our single registry commit onto the
|
|
# latest main and retry; bounded attempts so a persistent
|
|
# failure still surfaces rather than looping forever.
|
|
attempts=0
|
|
max_attempts=5
|
|
until git push; do
|
|
attempts=$((attempts + 1))
|
|
if [ "$attempts" -ge "$max_attempts" ]; then
|
|
echo "::error::push failed after $max_attempts rebase attempts"
|
|
exit 1
|
|
fi
|
|
echo "push rejected — rebasing onto latest main (attempt $attempts/$max_attempts)"
|
|
git pull --rebase origin "$(git rev-parse --abbrev-ref HEAD)"
|
|
done
|
|
|
|
# Slack failure alert. This workflow only runs on main-branch pushes,
|
|
# workflow_run completions, and manual dispatch — all of which
|
|
# constitute "production" events where silent failures (e.g. the
|
|
# GH013 PROTECT_OUR_MAIN regression that motivated PR #4159) must
|
|
# surface in #oss-alerts. Extracts failed step name + first
|
|
# meaningful error line so the payload is triage-ready rather than
|
|
# forcing a click-through, per the oss-alerts detail policy.
|
|
#
|
|
# Must NEVER fail the job (runs on failure() already; a crash here
|
|
# would compound the original failure and could block the notify
|
|
# step). All extraction uses best-effort fallbacks so a malformed
|
|
# jobs response or truncated log still yields sane defaults.
|
|
- name: Extract failure details for Slack
|
|
id: extract
|
|
if: failure() && env.SLACK_WEBHOOK != ''
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
GH_REPO: ${{ github.repository }}
|
|
RUN_ID: ${{ github.run_id }}
|
|
run: |
|
|
set +e # best-effort: never block the notify step below
|
|
|
|
# --- Find the currently-running job and its first failed step ---
|
|
# The jobs API returns every job in the run. Match by job name
|
|
# first (mirrors `jobs.capture.name`); fall back to first job
|
|
# with a failed step if name match misses (e.g. future rename).
|
|
jobs_json=$(gh api "/repos/${GH_REPO}/actions/runs/${RUN_ID}/jobs" --paginate 2>/dev/null)
|
|
job_id=$(printf '%s' "$jobs_json" | jq -r '
|
|
.jobs // []
|
|
| map(select(.name == "Capture Preview MP4s"))
|
|
| (.[0].id // empty)
|
|
' 2>/dev/null)
|
|
if [ -z "$job_id" ]; then
|
|
job_id=$(printf '%s' "$jobs_json" | jq -r '
|
|
.jobs // []
|
|
| map(select(.steps // [] | map(.conclusion) | index("failure")))
|
|
| (.[0].id // empty)
|
|
' 2>/dev/null)
|
|
fi
|
|
failed_step=$(printf '%s' "$jobs_json" | jq -r --arg id "$job_id" '
|
|
.jobs // []
|
|
| map(select((.id|tostring) == $id))
|
|
| (.[0].steps // [])
|
|
| map(select(.conclusion == "failure"))
|
|
| (.[0].name // "unknown step")
|
|
' 2>/dev/null)
|
|
[ -z "$failed_step" ] && failed_step="unknown step"
|
|
|
|
# --- Pull log and extract first meaningful error line ------------
|
|
# `gh run view --log-failed` output is TSV: job\tstep\ttimestamp +
|
|
# content. Strip the three leading columns, strip ANSI escape
|
|
# codes + BOM, skip runner/group/env header noise, grab first
|
|
# line matching a recognised error marker. Truncate to ~300
|
|
# chars so the Slack payload stays under the 800-char budget.
|
|
error_excerpt="see workflow run for details"
|
|
if [ -n "$job_id" ]; then
|
|
log_excerpt=$(gh run view "$RUN_ID" --repo "$GH_REPO" --log-failed --job="$job_id" 2>/dev/null \
|
|
| awk -F'\t' 'NF>=3 { sub(/^[\xEF\xBB\xBF]?[0-9T:.\-Z ]+/, "", $3); print $3 }' \
|
|
| sed 's/\x1b\[[0-9;]*[a-zA-Z]//g' \
|
|
| grep -vE '^(##\[|shell: |env: |Run |[[:space:]]*$)' \
|
|
| grep -m1 -E '^\[(FAIL|ERROR)\]|^Error:|^error:|^::error' \
|
|
| head -c 300)
|
|
if [ -n "$log_excerpt" ]; then
|
|
error_excerpt="$log_excerpt"
|
|
fi
|
|
fi
|
|
|
|
# --- Emit to $GITHUB_ENV using heredoc delimiter -----------------
|
|
# Heredoc delimiter protects against values with `=` or newlines
|
|
# breaking the KEY=VALUE format.
|
|
{
|
|
echo "failed_step<<EOF_FAILED_STEP_b3f2"
|
|
printf '%s\n' "$failed_step"
|
|
echo "EOF_FAILED_STEP_b3f2"
|
|
echo "error_excerpt<<EOF_ERROR_EXCERPT_b3f2"
|
|
printf '%s\n' "$error_excerpt"
|
|
echo "EOF_ERROR_EXCERPT_b3f2"
|
|
} >> "$GITHUB_ENV"
|
|
|
|
exit 0 # belt-and-suspenders: never propagate a failure
|
|
|
|
- name: Notify Slack (failure)
|
|
if: failure() && env.SLACK_WEBHOOK != ''
|
|
uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0
|
|
with:
|
|
webhook: ${{ secrets.SLACK_WEBHOOK_OSS_ALERTS }}
|
|
webhook-type: incoming-webhook
|
|
# Defensive: wrap dynamic values via toJSON(format(...)) so that
|
|
# if github.repository or extracted failed_step / error_excerpt
|
|
# contain characters that would break the JSON payload (quotes,
|
|
# backslashes, newlines), the value is safely JSON-encoded.
|
|
# Matches the pattern used in showcase_validate.yml.
|
|
payload: |
|
|
{ "text": ${{ toJSON(format(':x: *Showcase: Capture Previews*: failed — {0}: {1} | <https://github.com/{2}/actions/runs/{3}|View run>', env.failed_step, env.error_excerpt, github.repository, github.run_id)) }} }
|
|
|
|
- name: Log (no Slack — webhook unset)
|
|
if: failure() && env.SLACK_WEBHOOK == ''
|
|
run: |
|
|
echo "::warning::showcase_capture-previews failed but SLACK_WEBHOOK_OSS_ALERTS is not set; no Slack notification sent."
|