[docs] add "Patterns" tab with installable Workflow patterns #10018
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |