Skip to content

[docs] add "Patterns" tab with installable Workflow patterns #10018

[docs] add "Patterns" tab with installable Workflow patterns

[docs] add "Patterns" tab with installable Workflow patterns #10018

Workflow file for this run

name: Tests
on:
push:
branches:
- main
tags:
- "!*"
pull_request:
types:
- opened
- reopened
- synchronize
concurrency:
# Unique group for this workflow and branch
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
# Phase 0: Update PR comment to show tests are running
pr-comment-start:
name: Create PR Comment
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
timeout-minutes: 2
steps:
- name: Find existing test comment
uses: peter-evans/find-comment@v3
id: find-comment
with:
issue-number: ${{ github.event.pull_request.number }}
comment-author: 'github-actions[bot]'
body-includes: '<!-- e2e-test-results -->'
- name: Get existing comment body
if: steps.find-comment.outputs.comment-id != ''
id: get-comment
uses: actions/github-script@v7
env:
STARTED_AT: ${{ github.run_started_at }}
with:
script: |
const fs = require('fs');
const comment = await github.rest.issues.getComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: ${{ steps.find-comment.outputs.comment-id }}
});
const body = comment.data.body;
// Check if there are actual results (tables)
if (body.includes('|') && body.includes('Passed')) {
// Extract results section (everything after header)
const resultsSection = body
.replace(/<!-- e2e-test-results -->\n## 🧪 E2E Test Results\n\n> ⚠️ \*\*Results below are stale\*\*[^\n]*\n\n/g, '')
.replace(/<!-- e2e-test-results -->\n## 🧪 E2E Test Results\n\n/g, '')
.replace(/⏳ \*\*Tests are running\.\.\.\*\*\n\n---\n_Started at:[^_]*_\n\n---\n\n/g, '')
.replace(/⏳ \*\*Tests are running\.\.\.\*\*\n\n---\n_Started at:[^_]*_/g, '')
.trim();
if (resultsSection && resultsSection.includes('|')) {
// Write the full stale-banner message to disk and pass the
// path to the sticky-pull-request-comment action below.
// Inlining the previous results via `message:` blew past
// ARG_MAX once the matrix doubled (snapshot + replay).
const startedAt = process.env.STARTED_AT;
const message =
'<!-- e2e-test-results -->\n' +
'## 🧪 E2E Test Results\n\n' +
'> ⚠️ **Results below are stale** and not from the latest commit. This comment will be updated when CI completes on the latest run.\n\n' +
'⏳ **Tests are running...**\n\n' +
'---\n' +
`_Started at: ${startedAt}_\n\n` +
'---\n\n' +
resultsSection +
'\n';
const path = `${process.env.RUNNER_TEMP}/stale-comment.md`;
fs.writeFileSync(path, message);
core.setOutput('has-results', 'true');
core.setOutput('stale-comment-path', path);
} else {
core.setOutput('has-results', 'false');
}
} else {
core.setOutput('has-results', 'false');
}
- name: Create new test comment
if: steps.find-comment.outputs.comment-id == ''
uses: marocchino/sticky-pull-request-comment@v2
with:
header: e2e-test-results
message: |
<!-- e2e-test-results -->
## 🧪 E2E Test Results
⏳ **Tests are running...**
This comment will be updated with the results when the tests complete.
---
_Started at: ${{ github.run_started_at }}_
- name: Update existing test comment with stale warning
if: steps.find-comment.outputs.comment-id != '' && steps.get-comment.outputs.has-results == 'true'
uses: marocchino/sticky-pull-request-comment@v2
with:
header: e2e-test-results
path: ${{ steps.get-comment.outputs.stale-comment-path }}
- name: Update existing test comment without results
if: steps.find-comment.outputs.comment-id != '' && steps.get-comment.outputs.has-results != 'true'
uses: marocchino/sticky-pull-request-comment@v2
with:
header: e2e-test-results
message: |
<!-- e2e-test-results -->
## 🧪 E2E Test Results
⏳ **Tests are running...**
This comment will be updated with the results when the tests complete.
---
_Started at: ${{ github.run_started_at }}_
unit:
name: Unit Tests (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 30
if: ${{ !contains(github.event.pull_request.labels.*.name, 'workflow-server-test') }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup environment
uses: ./.github/actions/setup-workflow-dev
with:
setup-rust: 'true'
install-args: '--ignore-scripts'
build-packages: 'false'
- name: Run Unit Tests
run: pnpm test --filter='!./docs' --filter='!./workbench/vitest'
build-error-messages:
name: Node.js Module Build Errors Test
runs-on: ubuntu-latest
timeout-minutes: 30
if: ${{ !contains(github.event.pull_request.labels.*.name, 'workflow-server-test') }}
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup pnpm
uses: pnpm/action-setup@v5
- name: Setup Node.js 22.x
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: "pnpm"
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Run Initial Build
run: pnpm turbo run build --filter='!./workbench/*'
- name: Generate Workflows Registry
run: node workbench/scripts/generate-workflows-registry.js workbench/nextjs-turbopack/workflows workbench/nextjs-turbopack/_workflows.ts
- name: Run Build Error Tests
run: pnpm vitest run packages/core/e2e/build-errors.test.ts
env:
APP_NAME: "nextjs-turbopack"
vitest-plugin:
name: Vitest Plugin Tests
runs-on: ubuntu-latest
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Setup environment
uses: ./.github/actions/setup-workflow-dev
with:
setup-rust: 'true'
install-args: '--ignore-scripts'
build-packages: 'false'
- name: Build packages
run: pnpm turbo run build --filter='!./workbench/*'
- name: Run Vitest Plugin Tests
run: pnpm test
working-directory: workbench/vitest
e2e-vercel-prod:
name: E2E Vercel Prod Tests (${{ matrix.app.name }})
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
id-token: write
contents: read
deployments: read
strategy:
fail-fast: false
matrix:
app:
- name: "example"
project-id: "prj_xWq20Dd860HHAfzMjK2Mb6TPVxMa"
project-slug: "example-workflow"
- name: "nextjs-turbopack"
project-id: "prj_yjkM7UdHliv8bfxZ1sMJQf1pMpdi"
project-slug: "example-nextjs-workflow-turbopack"
- name: "nextjs-webpack"
project-id: "prj_avRPBF3eWjh6iDNQgmhH4VOg27h0"
project-slug: "example-nextjs-workflow-webpack"
- name: "nitro"
project-id: "prj_e7DZirYdLrQKXNrlxg7KmA6ABx8r"
project-slug: "workbench-nitro-workflow"
- name: "vite"
project-id: "prj_uLIcNZNDmETulAvj5h0IcDHi5432"
project-slug: "workbench-vite-workflow"
- name: "nuxt"
project-id: "prj_oTgiz3SGX2fpZuM6E0P38Ts8de6d"
project-slug: "workbench-nuxt-workflow"
- name: "sveltekit"
project-id: "prj_MqnBLm71ceXGSnm3Fs8i8gBnI23G"
project-slug: "workbench-sveltekit-workflow"
- name: "hono"
project-id: "prj_p0GIEsfl53L7IwVbosPvi9rPSOYW"
project-slug: "workbench-hono-workflow"
- name: "express"
project-id: "prj_cCZjpBy92VRbKHHbarDMhOHtkuIr"
project-slug: "workbench-express-workflow"
- name: "fastify"
project-id: "prj_5Yap0VDQ633v998iqQ3L3aQ25Cck"
project-slug: "workbench-fastify-workflow"
- name: "astro"
project-id: "prj_YDAXj3K8LM0hgejuIMhioz2yLgTI"
project-slug: "workbench-astro-workflow"
- name: "tanstack-start"
project-id: "prj_643jeVugTMq5ivsOFQHcbLG1qcnu"
project-slug: "workbench-tanstack-start-workflow"
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
WORKFLOW_PUBLIC_MANIFEST: '1'
WORKFLOW_NEXT_LAZY_DISCOVERY: ${{ matrix.app.canary != true && (matrix.app.name == 'nextjs-turbopack' || matrix.app.name == 'nextjs-webpack') && '0' || '' }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup environment
uses: ./.github/actions/setup-workflow-dev
with:
build-packages: 'false'
- name: Build CLI
run: pnpm turbo run build --filter='@workflow/cli'
- name: Waiting for the Vercel deployment
id: waitForDeployment
uses: ./.github/actions/wait-for-vercel-project
with:
project-slug: ${{ matrix.app.project-slug }}
timeout: 1000
check-interval: 15
environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }}
- name: Run E2E Tests
run: pnpm run test:e2e --reporter=default --reporter=json --reporter=./packages/core/e2e/github-reporter.ts "--outputFile=e2e-vercel-prod-$APP_NAME.json"
env:
NODE_OPTIONS: "--enable-source-maps"
DEPLOYMENT_URL: ${{ steps.waitForDeployment.outputs.deployment-url }}
VERCEL_DEPLOYMENT_ID: ${{ steps.waitForDeployment.outputs.deployment-id }}
APP_NAME: ${{ matrix.app.name }}
WORKFLOW_VERCEL_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }}
WORKFLOW_VERCEL_AUTH_TOKEN: ${{ secrets.VERCEL_LABS_TOKEN }}
WORKFLOW_VERCEL_TEAM: "team_nO2mCG4W8IxPIeKoSsqwAxxB"
WORKFLOW_VERCEL_PROJECT: ${{ matrix.app.project-id }}
WORKFLOW_VERCEL_PROJECT_SLUG: ${{ matrix.app.project-slug }}
# The trusted-sources OIDC token (x-vercel-trusted-oidc-idp-token)
# is minted on demand inside `scripts/trusted-sources-headers.mjs`
# via the runner's ACTIONS_ID_TOKEN_REQUEST_URL/_TOKEN env vars
# (exposed when `permissions: id-token: write` is set on the job),
# and re-minted before its 5-minute expiry. This lets long e2e
# suites keep a fresh token throughout the run instead of using
# a single pre-minted token that would expire mid-suite.
# See: https://vercel.com/docs/deployment-protection/methods-to-bypass-deployment-protection/trusted-sources
# Point PRs at the protected workflow-server preview; unset on main
# so production runs hit the public vercel-workflow.com URL.
VERCEL_WORKFLOW_SERVER_URL: ${{ github.ref != 'refs/heads/main' && secrets.VERCEL_WORKFLOW_SERVER_URL || '' }}
- name: Generate E2E summary
if: always()
env:
APP_NAME: ${{ matrix.app.name }}
run: node .github/scripts/aggregate-e2e-results.js . --job-name "E2E Vercel Prod ($APP_NAME)" >> $GITHUB_STEP_SUMMARY || true
- name: Upload E2E results
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-results-vercel-prod-${{ matrix.app.name }}
path: |
e2e-vercel-prod-${{ matrix.app.name }}.json
e2e-metadata-${{ matrix.app.name }}-vercel.json
e2e-failures-${{ matrix.app.name }}-vercel.json
e2e-diagnostics-${{ matrix.app.name }}-vercel.json
retention-days: 7
if-no-files-found: ignore
getTestMatrix:
name: Get Test Matrix
runs-on: ubuntu-latest
if: ${{ !contains(github.event.pull_request.labels.*.name, 'workflow-server-test') }}
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup environment
uses: ./.github/actions/setup-workflow-dev
with:
install-dependencies: 'false'
build-packages: 'false'
cache-pnpm: 'false'
- id: set-matrix
run: |
MATRIX_JSON="$(node ./scripts/create-test-matrix.mjs | jq -c .)"
echo "matrix=$MATRIX_JSON" >> "$GITHUB_OUTPUT"
e2e-local-dev:
name: E2E Local Dev Tests (${{ matrix.app.name }} - ${{ matrix.app.runLabel }})
runs-on: ubuntu-latest
timeout-minutes: 30
if: ${{ !contains(github.event.pull_request.labels.*.name, 'workflow-server-test') }}
needs: getTestMatrix
strategy:
fail-fast: false
matrix: ${{fromJson(needs.getTestMatrix.outputs.matrix)}}
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
WORKFLOW_PUBLIC_MANIFEST: '1'
WORKFLOW_NEXT_LAZY_DISCOVERY: ${{ matrix.app.lazyDiscovery == false && '0' || matrix.app.lazyDiscovery == true && '1' || '' }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup environment
uses: ./.github/actions/setup-workflow-dev
with:
install-dependencies: 'false'
build-packages: 'false'
- name: Setup canary
if: ${{ matrix.app.canary }}
env:
APP_NAME: ${{ matrix.app.name }}
run: |
cat packages/next/package.json | jq '.dependencies.next|="16.3.0-canary.2"' > packages/next/package.json.new && mv packages/next/package.json.new packages/next/package.json
cat "workbench/$APP_NAME/package.json" | jq '.dependencies.next|="16.3.0-canary.2"' > "workbench/$APP_NAME/package.json.new" && mv "workbench/$APP_NAME/package.json.new" "workbench/$APP_NAME/package.json"
pnpm install --no-frozen-lockfile
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Run Initial Build
run: pnpm turbo run build --filter='!./workbench/*'
- name: Prepare workbench path
id: prepare-workbench
uses: ./.github/actions/prepare-workbench-path
with:
app-name: ${{ matrix.app.name }}
- name: Run E2E Tests
run: |
cd "$WORKBENCH_APP_PATH" && pnpm dev &
echo "starting tests in 10 seconds" && sleep 10
pnpm vitest run packages/core/e2e/dev.test.ts; sleep 10
pnpm run test:e2e --reporter=default --reporter=json --reporter=./packages/core/e2e/github-reporter.ts --outputFile=e2e-local-dev-${{ matrix.app.name }}-${{ matrix.app.artifactSuffix }}.json
env:
NODE_OPTIONS: "--enable-source-maps"
APP_NAME: ${{ matrix.app.name }}
WORKBENCH_APP_PATH: ${{ steps.prepare-workbench.outputs.workbench_app_path }}
DEPLOYMENT_URL: "http://localhost:${{ matrix.app.name == 'sveltekit' && '5173' || (matrix.app.name == 'astro' && '4321' || '3000') }}"
DEV_TEST_CONFIG: ${{ toJSON(matrix.app) }}
NEXT_CANARY: ${{ matrix.app.canary && '1' || '' }}
- name: Generate E2E summary
if: always()
run: node .github/scripts/aggregate-e2e-results.js . --job-name "E2E Local Dev (${{ matrix.app.name }} - ${{ matrix.app.runLabel }})" >> $GITHUB_STEP_SUMMARY || true
- name: Upload E2E results
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-results-local-dev-${{ matrix.app.name }}-${{ matrix.app.artifactSuffix }}
path: e2e-local-dev-${{ matrix.app.name }}-${{ matrix.app.artifactSuffix }}.json
retention-days: 7
if-no-files-found: ignore
e2e-local-prod:
name: E2E Local Prod Tests (${{ matrix.app.name }} - ${{ matrix.app.runLabel }})
runs-on: ubuntu-latest
timeout-minutes: 30
if: ${{ !contains(github.event.pull_request.labels.*.name, 'workflow-server-test') }}
needs: getTestMatrix
strategy:
fail-fast: false
matrix: ${{fromJson(needs.getTestMatrix.outputs.matrix)}}
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
WORKFLOW_PUBLIC_MANIFEST: '1'
WORKFLOW_NEXT_LAZY_DISCOVERY: ${{ matrix.app.lazyDiscovery == false && '0' || matrix.app.lazyDiscovery == true && '1' || '' }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup environment
uses: ./.github/actions/setup-workflow-dev
with:
install-dependencies: 'false'
build-packages: 'false'
- name: Setup canary
if: ${{ matrix.app.canary }}
env:
APP_NAME: ${{ matrix.app.name }}
run: |
cat packages/next/package.json | jq '.dependencies.next|="16.3.0-canary.2"' > packages/next/package.json.new && mv packages/next/package.json.new packages/next/package.json
cat "workbench/$APP_NAME/package.json" | jq '.dependencies.next|="16.3.0-canary.2"' > "workbench/$APP_NAME/package.json.new" && mv "workbench/$APP_NAME/package.json.new" "workbench/$APP_NAME/package.json"
pnpm install --no-frozen-lockfile
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Run Initial Build
run: pnpm turbo run build --filter='!./workbench/*'
- name: Prepare workbench path
id: prepare-workbench
uses: ./.github/actions/prepare-workbench-path
with:
app-name: ${{ matrix.app.name }}
- name: Run Build Tests
run: pnpm vitest run packages/core/e2e/local-build.test.ts
env:
APP_NAME: ${{ matrix.app.name }}
WORKBENCH_APP_PATH: ${{ steps.prepare-workbench.outputs.workbench_app_path }}
- name: Run E2E Tests
run: |
cd "$WORKBENCH_APP_PATH" && pnpm start &
echo "starting tests in 10 seconds" && sleep 10
pnpm run test:e2e --reporter=default --reporter=json --reporter=./packages/core/e2e/github-reporter.ts --outputFile=e2e-local-prod-${{ matrix.app.name }}-${{ matrix.app.artifactSuffix }}.json
env:
NODE_OPTIONS: "--enable-source-maps"
APP_NAME: ${{ matrix.app.name }}
WORKBENCH_APP_PATH: ${{ steps.prepare-workbench.outputs.workbench_app_path }}
DEPLOYMENT_URL: "http://localhost:${{ matrix.app.name == 'sveltekit' && '4173' || (matrix.app.name == 'astro' && '4321' || '3000') }}"
NEXT_CANARY: ${{ matrix.app.canary && '1' || '' }}
- name: Generate E2E summary
if: always()
run: node .github/scripts/aggregate-e2e-results.js . --job-name "E2E Local Prod (${{ matrix.app.name }} - ${{ matrix.app.runLabel }})" >> $GITHUB_STEP_SUMMARY || true
- name: Upload E2E results
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-results-local-prod-${{ matrix.app.name }}-${{ matrix.app.artifactSuffix }}
path: e2e-local-prod-${{ matrix.app.name }}-${{ matrix.app.artifactSuffix }}.json
retention-days: 7
if-no-files-found: ignore
e2e-local-postgres:
name: E2E Local Postgres Tests (${{ matrix.app.name }} - ${{ matrix.app.runLabel }})
runs-on: ubuntu-latest
timeout-minutes: 30
if: ${{ !contains(github.event.pull_request.labels.*.name, 'workflow-server-test') }}
needs: getTestMatrix
strategy:
fail-fast: false
matrix: ${{fromJson(needs.getTestMatrix.outputs.matrix)}}
services:
postgres:
image: postgres:18-alpine
env:
POSTGRES_USER: world
POSTGRES_PASSWORD: world
POSTGRES_DB: world
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
WORKFLOW_PUBLIC_MANIFEST: '1'
WORKFLOW_TARGET_WORLD: "@workflow/world-postgres"
WORKFLOW_POSTGRES_URL: "postgres://world:world@localhost:5432/world"
WORKFLOW_NEXT_LAZY_DISCOVERY: ${{ matrix.app.lazyDiscovery == false && '0' || matrix.app.lazyDiscovery == true && '1' || '' }}
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup environment
uses: ./.github/actions/setup-workflow-dev
with:
install-dependencies: 'false'
build-packages: 'false'
- name: Setup canary
if: ${{ matrix.app.canary }}
env:
APP_NAME: ${{ matrix.app.name }}
run: |
cat packages/next/package.json | jq '.dependencies.next|="16.3.0-canary.2"' > packages/next/package.json.new && mv packages/next/package.json.new packages/next/package.json
cat "workbench/$APP_NAME/package.json" | jq '.dependencies.next|="16.3.0-canary.2"' > "workbench/$APP_NAME/package.json.new" && mv "workbench/$APP_NAME/package.json.new" "workbench/$APP_NAME/package.json"
pnpm install --no-frozen-lockfile
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Run Initial Build
run: pnpm turbo run build --filter='!./workbench/*'
- name: Setup PostgreSQL Database
run: ./packages/world-postgres/bin/setup.js
- name: Prepare workbench path
id: prepare-workbench
uses: ./.github/actions/prepare-workbench-path
with:
app-name: ${{ matrix.app.name }}
- name: Run Build Tests
run: pnpm vitest run packages/core/e2e/local-build.test.ts
env:
APP_NAME: ${{ matrix.app.name }}
WORKBENCH_APP_PATH: ${{ steps.prepare-workbench.outputs.workbench_app_path }}
- name: Run E2E Tests
run: |
cd "$WORKBENCH_APP_PATH" && pnpm start &
echo "starting tests in 10 seconds" && sleep 10
pnpm run test:e2e --reporter=default --reporter=json --reporter=./packages/core/e2e/github-reporter.ts --outputFile=e2e-local-postgres-${{ matrix.app.name }}-${{ matrix.app.artifactSuffix }}.json
env:
NODE_OPTIONS: "--enable-source-maps"
APP_NAME: ${{ matrix.app.name }}
WORKBENCH_APP_PATH: ${{ steps.prepare-workbench.outputs.workbench_app_path }}
DEPLOYMENT_URL: "http://localhost:${{ matrix.app.name == 'sveltekit' && '4173' || (matrix.app.name == 'astro' && '4321' || '3000') }}"
NEXT_CANARY: ${{ matrix.app.canary && '1' || '' }}
- name: Generate E2E summary
if: always()
run: node .github/scripts/aggregate-e2e-results.js . --job-name "E2E Local Postgres (${{ matrix.app.name }} - ${{ matrix.app.runLabel }})" >> $GITHUB_STEP_SUMMARY || true
- name: Upload E2E results
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-results-local-postgres-${{ matrix.app.name }}-${{ matrix.app.artifactSuffix }}
path: e2e-local-postgres-${{ matrix.app.name }}-${{ matrix.app.artifactSuffix }}.json
retention-days: 7
if-no-files-found: ignore
e2e-windows:
name: E2E Windows Tests
runs-on: windows-latest
timeout-minutes: 30
if: ${{ !contains(github.event.pull_request.labels.*.name, 'workflow-server-test') }}
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
WORKFLOW_PUBLIC_MANIFEST: '1'
steps:
- name: Checkout Repo
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
- name: Setup pnpm
uses: pnpm/action-setup@v5
- name: Setup Node.js 22.x
uses: actions/setup-node@v4
with:
node-version: 22.x
cache: "pnpm"
- name: Install Dependencies
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Run Initial Build
run: pnpm turbo run build --filter='!./workbench/*' --filter='!./docs'
- name: Run E2E Tests (Next.js)
run: |
cd workbench/nextjs-turbopack
$logFile = "$env:GITHUB_WORKSPACE/nextjs-server.log"
$job = Start-Job -ScriptBlock { Set-Location $using:PWD; pnpm dev *>&1 | Tee-Object -FilePath $using:logFile }
Start-Sleep -Seconds 15
cd ../..
# Fail-fast guards: if the Next.js dev server gets into a corrupted
# state during HMR (a recurring Turbopack-on-Windows issue where
# `packages/core/dist/*.js` becomes "MODULE_UNPARSABLE" and every
# subsequent request returns 500), the e2e suite would otherwise burn
# the full 30-minute job window polling stuck workflow runs before
# finally being cancelled. Below we (1) bail out if `dev.test.ts`
# fails, since that's the first thing to start hitting 500s, and
# (2) probe the server before the long e2e run to catch any other
# silent breakage.
#
# Get-DevServerStatus returns the HTTP status code of GET /api/chat,
# or 0 if the request never produced a response. /api/chat is a
# POST-only endpoint, so a healthy dev server responds with 405
# Method Not Allowed; we only treat 5xx (and no-response) as
# unhealthy.
function Get-DevServerStatus {
try {
$resp = Invoke-WebRequest -Uri "http://localhost:3000/api/chat" -UseBasicParsing -TimeoutSec 15 -ErrorAction Stop
return [int]$resp.StatusCode
} catch {
if ($_.Exception.Response) {
try { return [int]$_.Exception.Response.StatusCode } catch { return 0 }
}
return 0
}
}
$devExit = 0
pnpm vitest run packages/core/e2e/dev.test.ts
$devExit = $LASTEXITCODE
if ($devExit -ne 0) {
Write-Host "::warning title=dev.test.ts failed::dev.test.ts exited with code $devExit. Skipping the remainder of the e2e suite to avoid waiting for the 30-minute job timeout."
Stop-Job $job -ErrorAction SilentlyContinue
exit $devExit
}
# Sanity-check the server before kicking off the long e2e suite. This
# catches the case where Turbopack quietly went sideways (e.g. lost
# track of files in `packages/core/dist` after HMR on Windows) and is
# returning 500 for every request. Without this check the e2e suite
# would burn the full 30-minute job window polling stuck workflow
# runs before getting cancelled.
$status = Get-DevServerStatus
Write-Host "[health-check:pre-e2e] GET /api/chat -> $status"
if ($status -eq 0 -or $status -ge 500) {
Write-Host "::error title=Next.js dev server unhealthy::Dev server returned $status before e2e tests. Aborting to avoid the 30-minute job timeout. See the 'Print Next.js server logs' step for the underlying Turbopack error."
Stop-Job $job -ErrorAction SilentlyContinue
exit 1
}
pnpm run test:e2e --reporter=default --reporter=json --reporter=./packages/core/e2e/github-reporter.ts --outputFile=e2e-windows-nextjs-turbopack.json
$e2eExit = $LASTEXITCODE
Stop-Job $job -ErrorAction SilentlyContinue
exit $e2eExit
shell: powershell
env:
NODE_OPTIONS: "--enable-source-maps"
APP_NAME: "nextjs-turbopack"
DEPLOYMENT_URL: "http://localhost:3000"
DEV_TEST_CONFIG: '{"generatedStepPath":"app/.well-known/workflow/v1/flow/__step_registrations.js","generatedWorkflowPath":"app/.well-known/workflow/v1/flow/route.js","apiFilePath":"app/api/chat/route.ts","apiFileImportPath":"../../..","port":3000}'
- name: Print Next.js server logs
if: always()
shell: powershell
run: |
$logFile = "$env:GITHUB_WORKSPACE/nextjs-server.log"
if (Test-Path $logFile) {
Write-Host "=== Next.js Server Logs ===" -ForegroundColor Cyan
Get-Content $logFile
Write-Host "=== End of Server Logs ===" -ForegroundColor Cyan
} else {
Write-Host "No server log file found at $logFile" -ForegroundColor Yellow
}
- name: Generate E2E summary
if: always()
shell: bash
run: node .github/scripts/aggregate-e2e-results.js . --job-name "E2E Windows (nextjs-turbopack)" >> $GITHUB_STEP_SUMMARY || true
- name: Upload E2E results
if: always()
uses: actions/upload-artifact@v4
with:
name: e2e-results-windows-nextjs-turbopack
path: e2e-windows-nextjs-turbopack.json
retention-days: 7
if-no-files-found: ignore
- name: Upload Next.js server logs
if: always()
uses: actions/upload-artifact@v4
with:
name: nextjs-server-logs-windows
path: nextjs-server.log
retention-days: 7
if-no-files-found: ignore
# Community World E2E Tests (dynamically generated from worlds-manifest.json)
# Disabled on main: community worlds target spec v2 and do not yet support the
# CBOR queue transport used by world-vercel (see #1627 / #1658). They continue
# to run on `stable`, where spec v2 is still the primary compat target. Once
# the community worlds (mizzle-dev/workflow-worlds) ship CBOR support we can
# re-enable these jobs here.
getCommunityWorldsMatrix:
name: Get Community Worlds Matrix
runs-on: ubuntu-latest
if: false
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup environment
uses: ./.github/actions/setup-workflow-dev
with:
install-dependencies: 'false'
build-packages: 'false'
cache-pnpm: 'false'
- id: set-matrix
run: |
MATRIX_JSON="$(node ./scripts/create-community-worlds-matrix.mjs | jq -c .)"
echo "matrix=$MATRIX_JSON" >> "$GITHUB_OUTPUT"
e2e-community:
name: E2E Community World (${{ matrix.world.name }})
if: false
needs: getCommunityWorldsMatrix
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.getCommunityWorldsMatrix.outputs.matrix) }}
uses: ./.github/workflows/e2e-community-world.yml
with:
world-id: ${{ matrix.world.id }}
world-name: ${{ matrix.world.name }}
world-package: ${{ matrix.world.package }}
service-type: ${{ matrix.world.service-type }}
env-vars: ${{ matrix.world.env-vars }}
setup-command: ${{ matrix.world.setup-command }}
secrets: inherit
# Final job: Aggregate all E2E results and update PR comment
summary:
name: E2E Summary
runs-on: ubuntu-latest
needs: [e2e-vercel-prod, e2e-local-dev, e2e-local-prod, e2e-local-postgres, e2e-windows]
if: always() && !cancelled()
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Download all E2E artifacts
uses: actions/download-artifact@v4
with:
pattern: e2e-results-*
path: e2e-results
merge-multiple: true
- name: List downloaded files
run: find e2e-results -type f -name "*.json" | sort || echo "No files found"
- name: Aggregate E2E results
id: aggregate
run: |
node .github/scripts/aggregate-e2e-results.js e2e-results --mode aggregate --run-url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | tee e2e-summary.md >> $GITHUB_STEP_SUMMARY
- name: Check E2E job statuses
id: check-status
run: |
VERCEL_STATUS="${{ needs.e2e-vercel-prod.result }}"
LOCAL_DEV_STATUS="${{ needs.e2e-local-dev.result }}"
LOCAL_PROD_STATUS="${{ needs.e2e-local-prod.result }}"
POSTGRES_STATUS="${{ needs.e2e-local-postgres.result }}"
WINDOWS_STATUS="${{ needs.e2e-windows.result }}"
echo "vercel=$VERCEL_STATUS" >> $GITHUB_OUTPUT
echo "local-dev=$LOCAL_DEV_STATUS" >> $GITHUB_OUTPUT
echo "local-prod=$LOCAL_PROD_STATUS" >> $GITHUB_OUTPUT
echo "postgres=$POSTGRES_STATUS" >> $GITHUB_OUTPUT
echo "windows=$WINDOWS_STATUS" >> $GITHUB_OUTPUT
if [[ "$VERCEL_STATUS" == "failure" || "$LOCAL_DEV_STATUS" == "failure" || "$LOCAL_PROD_STATUS" == "failure" || "$POSTGRES_STATUS" == "failure" || "$WINDOWS_STATUS" == "failure" ]]; then
echo "has_failures=true" >> $GITHUB_OUTPUT
else
echo "has_failures=false" >> $GITHUB_OUTPUT
fi
- name: Update PR comment with results
if: github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@v2
with:
header: e2e-test-results
path: e2e-summary.md
- name: Append failure notice to PR comment
if: github.event_name == 'pull_request' && steps.check-status.outputs.has_failures == 'true'
uses: marocchino/sticky-pull-request-comment@v2
with:
header: e2e-test-results
append: true
message: |
---
❌ **Some E2E test jobs failed:**
- Vercel Prod: ${{ needs.e2e-vercel-prod.result }}
- Local Dev: ${{ needs.e2e-local-dev.result }}
- Local Prod: ${{ needs.e2e-local-prod.result }}
- Local Postgres: ${{ needs.e2e-local-postgres.result }}
- Windows: ${{ needs.e2e-windows.result }}
Check the [workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) for details.
# Final required check: passes only when unit + all E2E jobs succeed.
# Community worlds are intentionally excluded — they are disabled on main.
e2e-required-check:
name: E2E Required Check
runs-on: ubuntu-latest
needs: [unit, e2e-vercel-prod, e2e-local-dev, e2e-local-prod, e2e-local-postgres, e2e-windows]
if: always()
timeout-minutes: 5
steps:
- name: Validate E2E job statuses
env:
UNIT_STATUS: ${{ needs.unit.result }}
VERCEL_STATUS: ${{ needs.e2e-vercel-prod.result }}
LOCAL_DEV_STATUS: ${{ needs.e2e-local-dev.result }}
LOCAL_PROD_STATUS: ${{ needs.e2e-local-prod.result }}
POSTGRES_STATUS: ${{ needs.e2e-local-postgres.result }}
WINDOWS_STATUS: ${{ needs.e2e-windows.result }}
HAS_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'workflow-server-test') }}
run: |
FAILED_JOBS=()
# When workflow-server-test label is present, only check vercel-prod (others should be skipped)
# Otherwise, all jobs must succeed
if [[ "$HAS_LABEL" == "true" ]]; then
echo "workflow-server-test label detected - only checking required jobs"
[[ "$VERCEL_STATUS" == "success" ]] || FAILED_JOBS+=("e2e-vercel-prod ($VERCEL_STATUS)")
# Verify other jobs were actually skipped
[[ "$UNIT_STATUS" == "skipped" ]] || echo "Warning: unit was not skipped ($UNIT_STATUS)"
[[ "$LOCAL_DEV_STATUS" == "skipped" ]] || echo "Warning: e2e-local-dev was not skipped ($LOCAL_DEV_STATUS)"
[[ "$LOCAL_PROD_STATUS" == "skipped" ]] || echo "Warning: e2e-local-prod was not skipped ($LOCAL_PROD_STATUS)"
[[ "$POSTGRES_STATUS" == "skipped" ]] || echo "Warning: e2e-local-postgres was not skipped ($POSTGRES_STATUS)"
[[ "$WINDOWS_STATUS" == "skipped" ]] || echo "Warning: e2e-windows was not skipped ($WINDOWS_STATUS)"
else
echo "Standard PR - checking all jobs"
[[ "$UNIT_STATUS" == "success" ]] || FAILED_JOBS+=("unit ($UNIT_STATUS)")
[[ "$VERCEL_STATUS" == "success" ]] || FAILED_JOBS+=("e2e-vercel-prod ($VERCEL_STATUS)")
[[ "$LOCAL_DEV_STATUS" == "success" ]] || FAILED_JOBS+=("e2e-local-dev ($LOCAL_DEV_STATUS)")
[[ "$LOCAL_PROD_STATUS" == "success" ]] || FAILED_JOBS+=("e2e-local-prod ($LOCAL_PROD_STATUS)")
[[ "$POSTGRES_STATUS" == "success" ]] || FAILED_JOBS+=("e2e-local-postgres ($POSTGRES_STATUS)")
[[ "$WINDOWS_STATUS" == "success" ]] || FAILED_JOBS+=("e2e-windows ($WINDOWS_STATUS)")
fi
if [[ ${#FAILED_JOBS[@]} -gt 0 ]]; then
echo "The following E2E jobs did not succeed:"
printf '%s\n' "${FAILED_JOBS[@]}"
exit 1
fi
echo "All required E2E jobs succeeded."
# Publish E2E results to GitHub Pages (main branch only)
publish-results:
name: Publish E2E Results
runs-on: ubuntu-latest
needs: [summary]
# Use always() because summary uses always() - without it this job gets skipped when any E2E test fails
if: always() && !cancelled() && github.event_name == 'push' && github.ref == 'refs/heads/main'
timeout-minutes: 5
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Download all E2E artifacts
uses: actions/download-artifact@v4
with:
pattern: e2e-results-*
path: e2e-results
merge-multiple: true
- name: Generate docs data
run: |
mkdir -p docs-data
node .github/scripts/generate-docs-data.js \
--type e2e \
--results-dir e2e-results \
--output docs-data/e2e-results.json \
--commit "${{ github.sha }}" \
--branch "${{ github.ref_name }}"
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs-data
destination_dir: ci
keep_files: true