mirror of
https://github.com/vercel/workflow.git
synced 2026-09-14 19:59:43 +08:00
9da2d76260
* [world-vercel] Add /run-id sub-export with tagged ULID encode/decode Encodes a tag bit, 5-bit version, and 6-bit Vercel region ID into a ULID-shaped string used for workflow run IDs. Tagged values remain valid 26-char Crockford-Base32 ULIDs so they still sort and round-trip through any system that accepts ULIDs. * [world-vercel] Add string-value assertions to run-id tests Add exact-string expectations for encoded outputs at known inputs, covering the default region/version pair, numeric region IDs, version overrides, boundary values (all-zero, all-max), the dirty-input overwrite case, and the lexicographic-order checks. Also adds an explicit byte-array expectation for the canonical ULID-spec example string and an additional first-char-range coverage test for isTagged. * [world-vercel] Remove internal-repo reference from regions doc comment * [world-vercel] Address PR review feedback on run-id sub-export - isTaggedString now fully validates the input as a 26-char Crockford Base32 ULID (delegating to ulidToBytes) instead of only inspecting the first character. This fixes false positives on inputs like '4UUUU...' that have a valid tag-bit position but invalid chars later in the string. - isTagged() now accepts `unknown` to match its documented behavior of safely rejecting non-string inputs without requiring callers to cast. - Introduce `RegionKey` for the full set of keys including 'unknown', and narrow `RegionCode` to `Exclude<RegionKey, 'unknown'>` so the return type of `lookupRegion` and the `DecodedRunId.region` field accurately reflect that 'unknown' is never produced. Updates `encode` to reject 'unknown' as a region code string at runtime (callers wanting the unknown sentinel should pass numeric 0). * [world] [core] [world-vercel] Add World.createRunId() and region-aware queue routing - @workflow/world: add optional createRunId(input?) to the World interface so worlds can mint run IDs with embedded metadata, and add an optional 'region' field to QueueOptions for per-message routing hints. - @workflow/core: start() now delegates run ID generation to world.createRunId() when defined (falling back to a monotonic ULID otherwise), and accepts a new 'runIdInput' option that is forwarded verbatim to createRunId. When runIdInput.region is a string, it is also threaded onto the queue options so the initial workflow message is dispatched to the matching region. - @workflow/world-vercel: implement createRunId() to mint region-tagged ULIDs, preferring an explicit runIdInput.region and falling back to the VERCEL_REGION env var. The queue now resolves its destination region from (in order): an explicit opts.region, the region embedded in the payload's tagged run ID, the VERCEL_REGION env var, and finally a hardcoded 'iad1' default. This replaces the previous unconditional 'iad1' region passed to the @vercel/queue client. Monotonicity within a process is preserved by tracking the last emitted run ID and bumping the bit immediately above the 11-bit metadata window when a same-ms collision would otherwise occur, then re-stamping the requested region/version on top so metadata remains stable. * [core] [world] [world-vercel] Pass full StartOptions to World.createRunId Drop the dedicated 'runIdInput' field on StartOptions and forward the entire options bag to world.createRunId() instead. This keeps the public API surface smaller and lets each World pick the fields it recognises (e.g. world-vercel reads 'region'). The top-level 'region' option remains on StartOptionsBase and is also threaded onto the queue's per-call region opt when set. * Address review feedback: doc fixes and deterministic same-ms tests - Document the final iad1 fallback in QueueOptions.region (world) - Correct the World.createRunId doc: start() always passes an object - Fix the clientOptions comment: the handler client omits region and relies on SDK auto-detection + the ce-vqsregion header for acks - Fix a misleading QueueClient-construction comment in queue.test.ts - Freeze time in the same-ms monotonicity test so it deterministically exercises the intended path, and add a test covering the bump-above-metadata fallback when the region changes mid-millisecond Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: keep workflow-server override rewrite-compatible Export WORKFLOW_SERVER_URL_OVERRIDE while keeping the one-line const shape that workflow-server's cross-repo e2e test automation rewrites. Update world-vercel tests to import that exported value for mock origins and URL expectations instead of duplicating the temporary preview URL. * fix(world): clear region tag bit before ULID timestamp validation Region-tagged run IDs set the high bit of the ULID timestamp byte. The shared world timestamp validator used raw decodeTime(), so current tagged run IDs appeared thousands of years in the future and were rejected before reaching workflow-server. Clear the tag bit before decoding, matching the workflow-server behavior, and cover tagged IDs in tests. * fix(world-vercel): validate tagged runId timestamps via run-id decode Keep @workflow/world's ULID helpers generic; they should not know about world-vercel's region-tagged run ID layout. Instead, world-vercel decodes its tagged runId to the original ULID before using the shared timestamp validator for run_created events. Add a world-vercel regression test that a current sfo1-tagged runId passes validation. * fix(world-vercel): default run ID region to iad1 instead of unknown When neither an explicit region option nor VERCEL_REGION is available, createRunId minted a tagged ULID with the unknown (0) region sentinel, producing the tagged: true, region: null state. The server already resolves unknown/untagged runs to DEFAULT_VERCEL_REGION (iad1), so mint a concrete iad1 tag instead, keeping every run ID self-describing and routable. * test(e2e): use verbose reporter + per-test start heartbeat The default vitest reporter buffers per-file output, so a stalling e2e test produces no output until its timeout — making CI look like a silent 30-minute hang. Switch the e2e CI invocations to the verbose reporter (prints each test result as it completes) and emit a '[e2e] ▶ start:' heartbeat to stdout at the start of every test (bypassing vitest's console buffering) so a stuck test is immediately identifiable in the live CI log. * test(world-vercel): point WORKFLOW_SERVER_URL_OVERRIDE at combined 527+529 preview Temporarily target the workflow-server combined-527-529-preview deployment, which bundles platform-directed multi-region routing (vercel/workflow-server#527, incl. the iad1 hook pin) and durable stream state (vercel/workflow-server#529), so e2e can validate the full multi-region path end-to-end. Revert to empty on main. * fix(core): region-tag the health-check correlationId The health-check response is delivered over a Redis stream whose name (and synthetic run ID) embed the correlationId. Under platform-directed routing the responding endpoint and the polling reader can be served from different physical regions; Redis is physical-region-local, so the correlationId must carry the region for both sides to resolve the same backend. Generate the correlationId via world.createRunId() (a region-tagged ULID) when the world provides it, falling back to a plain ULID for worlds that don't tag IDs (e.g. local, single-region). The synthetic wrun_hc_<id> run ID then carries the region; workflow-server's region middleware decodes it. * Address review feedback: validate region overrides, reset server override - queue: validate opts.region and VERCEL_REGION against the known region table before routing, ignoring unrecognised codes so a bad override can't clobber the payload-derived region (Copilot) - add isKnownRegionCode() runtime guard to run-id/regions - reset WORKFLOW_SERVER_URL_OVERRIDE to '' (must be empty on main) - fold the within-PR iad1-default changeset into the main world-vercel changeset and delete it (review) - start.test: declare specVersion on createRunId mock worlds now that the merged world-compatibility check requires it - cover the new region-validation fall-through paths in queue.test Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(world-vercel): point WORKFLOW_SERVER_URL_OVERRIDE at wave-1 multi-region preview BRANCH-ONLY — revert the override to '' before merge (lint enforces). Points this PR's e2e/benchmark runs at the wave-1 multi-region workflow-server preview (vercel/workflow-server#590: iad1+sfo1+fra1 serving, staging data backends) so region-tagged runs are validated against real multi-region serving end-to-end. Also makes the unit-test mock origins in events-v4.test.ts and trace-propagation.test.ts override-aware (same pattern the rest of the file and utils.test.ts already use), so the suite passes whether or not the override is set — these two files were the only spots hardcoding https://vercel-workflow.com. * test(e2e): Vercel multi-region suite for start()'s region option Adds a dedicated e2e suite validating @workflow/world-vercel region routing end to end, run as its own CI job (e2e-vercel-multi-region) against the nextjs-turbopack workbench only — deliberately separate from e2e.test.ts, which runs as a matrix across all worlds/frameworks where Vercel-specific multi-region behavior doesn't apply. - workbench/nextjs-turbopack/vercel.json: deploy to iad1+sfo1+fra1 so region-routed flow messages have a function to land on in each region. - workflows/99_e2e.ts: regionProbeWorkflow returns the VERCEL_REGION observed by both the workflow and a step, so tests can assert the run EXECUTED in the intended region (not just that it was tagged). - packages/core/e2e/e2e-region.test.ts: per-region cases assert 1) start(..., { region }) mints a region-tagged run ID (decoded via @workflow/world-vercel/run-id), 2) the workflow + step both observed VERCEL_REGION === region, 3) the server reports the run completed; plus a concurrent all-regions case guarding against cross-region misrouting under simultaneous multi-region traffic. Skips on local deployments. - .github/workflows/tests.yml: new e2e-vercel-multi-region job mirroring e2e-vercel-prod's env/deployment-wait, running only the new suite. * test(e2e): start region probes in-function; fix getWorld await The first multi-region CI run surfaced two issues: 1. sfo1/fra1-tagged runs executed in iad1. The suite started runs from the external test process, which uses the api.vercel.com token proxy — and the proxy's queues path forwards every send to the region-less VQS host (the world's proxy-mode resolveBaseUrl ignores the region argument, and the proxy's x-vercel-vqs-api-url escape hatch only allowlists vqs-server-*.vercel.sh preview hosts). Production traffic publishes IN-FUNCTION (direct regional queue routing), so the suite now triggers start() through a new workbench route (/api/e2e-region-start) and rehydrates the run with getRun() — testing the path production actually takes. Proxy-mode regional queue routing is a known gap to address separately in api-workflow. 2. TypeError on world.runs.get: getWorld() is async and was called without await. * test(e2e): cover explicit and implicit region starts in the multi-region suite With regional VQS routing now working through the api.vercel.com proxy (vercel/api#79056 + #2789 + this branch's per-send region resolution), the suite covers both start configurations, asserting the same three properties for each (region-tagged run ID, execution in the intended region via VERCEL_REGION echoed in the return value, server-side completion): 1. EXPLICIT: start(..., { region }) called directly in the vitest runner — publishes through the token proxy, per-send region carried by x-vercel-queue-region. Restores the direct-start shape the suite had originally, plus the concurrent all-regions case. 2. IMPLICIT: dedicated per-region workbench routes (/api/e2e-region-implicit/{iad1,sfo1,fra1}), each pinned to a single region via a per-function 'regions' entry in the workbench vercel.json, calling start() with NO region option — createRunId derives the tag from the minting function's VERCEL_REGION. The test also asserts the route reported executing in its pinned region, so the implicit-tagging assertion can't pass vacuously. Replaces the interim /api/e2e-region-start route (explicit region via request body), which existed to work around the pre-#79056 proxy gap. * Revert WORKFLOW_SERVER_URL_OVERRIDE to '' — wave-1 multi-region serving is in production workflow-server#590 (iad1+sfo1+fra1 serving) merged and deployed to production and the e2e backend, so this branch's e2e/benchmark runs no longer need to target the wave-1 preview. Restores the empty override the No Test Overrides lint job enforces for merge. The override-aware unit-test origins (events-v4/trace-propagation) stay — they are correct under any override value. * test(e2e): cross-region stream visibility (iad1 writer, sfo1 reader) Regression coverage for a backend bug that made cross-region stream reads report zero chunks on IN-PROGRESS streams (completed streams were unaffected), which forced the multi-region serving rollback. The new case exercises exactly that geometry: - crossRegionStreamWorkflow (99_e2e.ts) writes N chunks to the default output stream, then holds the stream OPEN for 45s before closing — the in-progress window is the point, since completed streams are the easy case. - The e2e starts it with region iad1, waits (same-region, via the api.vercel.com proxy) until all chunks are written, asserts the run is still 'running', then reads through a new sfo1-pinned workbench route (/api/e2e-stream-read/sfo1) that returns getTailIndex() plus its VERCEL_REGION. The reader's region served none of the stream's writes, so the reported chunk count must come from the backend's cross-region stream metadata. The test fails loudly if the route isn't actually executing in sfo1. Also bumps the explicit-region test timeout to 120s: the first case in the file absorbs every cold start at once (fresh workbench instances in up to three regions plus a cold backend preview) and was observed just over the 60s default. BRANCH-ONLY (revert before merge, lint enforces): WORKFLOW_SERVER_URL_OVERRIDE points at a multi-region backend preview that includes the fix, so this validates cross-region stream visibility end-to-end before multi-region serving is re-enabled. * test(e2e): extend multi-region suite to all 19 provisioned regions Points the suite at an all-regions backend preview and widens coverage from the wave-1 trio to every provisioned region: - Explicit path: a single concurrent all-regions case starts one tagged run per region (one shared cold-start window instead of 19 sequential ones) and aggregates per-region failures so a single region's breakage reports alongside the full picture. The trio keeps its detailed per-region cases and the 9-way concurrent-isolation case. - Implicit path: workbench gains a region-pinned /api/e2e-region-implicit/<region> route per provisioned region (19 total, shared handler), the workbench itself now deploys to all of them, and the test.each covers the full set with per-case timeouts for regional cold starts. - Multi-region CI job timeout 20m -> 35m for the sequential implicit cases. BRANCH-ONLY (revert before merge, lint enforces): WORKFLOW_SERVER_URL_OVERRIDE now targets the all-regions backend preview instead of the previous (stale, since-merged) fix preview. * test(e2e): tolerate geo-adjacent execution of queue callbacks The first all-regions run surfaced a subtle execution-locality behavior: queue delivery is guaranteed to the tagged region's dataplane and the delivery callback egresses from that region, but the consumer invocation's execution region is chosen by where that callback enters Vercel's edge — and adjacent regions can geo-resolve to each other's functions. Observed live: kix1-tagged runs (callback egressing from Osaka) deterministically executing in hnd1/Tokyo on both the explicit and implicit paths, with tagging, data placement, and completion all still strictly kix1. expectRunInRegion now asserts execution lands in the tagged region OR one of its geographic neighbors (EXECUTION_ADJACENCY), while run-ID tagging and server-side completion remain strictly the requested region. Gross misrouting (e.g. kix1 -> iad1) still fails. * Revert WORKFLOW_SERVER_URL_OVERRIDE to '' — all-regions serving is in production The all-regions workflow-server rollout is deployed and serving production traffic from every Vercel region, so this branch's e2e no longer needs to target a branch preview. Restores the empty override the No Test Overrides lint enforces for merge. With this the PR is complete: region-tagged run IDs, region-aware queue routing, and the multi-region e2e suite (explicit + implicit + all-regions + cross-region streams) all validate against the production-default backends. * docs: fix three stale comments flagged in review - start.ts: StartOptionsBase.region fallback is iad1, not the unknown sentinel (createRunId always mints a concrete routable region) - queue.ts: example used a nonexistent start({ runIdInput }) API; the real option is start({ region }) - events.ts: decode() clears only the tag bit (top bit of the 48-bit timestamp field) — it does not restore the original untagged ULID; reword to say what actually matters for timestamp validation * test(e2e): cover hook resolve/resume for runs owned by non-iad1 regions Hooks are resolved by opaque token, which carries no region hint, so lookup and resume must work regardless of which region owns the run's data. Exercises the full follow-up-message path on sfo1- and fra1-tagged runs: create inside the workflow, resolve by token from the test process, resume twice sequentially, and assert payload order and completion. Regression coverage for the failure mode where the first message to a hook-driven app on a non-iad1 run worked but every follow-up failed with 'Hook not found'. --------- Co-authored-by: Peter Wielander <peter.wielander@vercel.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1210 lines
50 KiB
YAML
1210 lines
50 KiB
YAML
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:
|
|
ci-scope:
|
|
name: Detect CI Scope
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
fast-path: ${{ steps.scope.outputs.runtime-fast-path }}
|
|
validation-fast-path: ${{ steps.scope.outputs.validation-fast-path }}
|
|
steps:
|
|
- name: Checkout Repo
|
|
uses: actions/checkout@v4
|
|
with:
|
|
ref: ${{ github.event.pull_request.head.sha || github.sha }}
|
|
|
|
- name: Classify changed files
|
|
id: scope
|
|
uses: ./.github/actions/detect-ci-scope
|
|
with:
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
# Phase 0: Update PR comment to show tests are running
|
|
pr-comment-start:
|
|
name: Create PR Comment
|
|
runs-on: ubuntu-latest
|
|
needs: ci-scope
|
|
if: github.event_name == 'pull_request' && needs.ci-scope.outputs.fast-path != 'true'
|
|
timeout-minutes: 2
|
|
|
|
steps:
|
|
- name: Find existing test comment
|
|
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
|
|
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@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
|
|
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@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
|
|
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@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
|
|
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
|
|
needs: ci-scope
|
|
if: ${{ needs.ci-scope.outputs.validation-fast-path != 'true' && !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'
|
|
|
|
serializable-revivers:
|
|
name: Serializable Reviver Compatibility
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
needs: ci-scope
|
|
if: ${{ needs.ci-scope.outputs.validation-fast-path != 'true' && !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 environment
|
|
uses: ./.github/actions/setup-workflow-dev
|
|
with:
|
|
install-args: '--ignore-scripts'
|
|
build-packages: 'false'
|
|
|
|
- name: Build reviver packages
|
|
run: pnpm turbo run build --filter='@workflow/cli' --filter='@workflow/web-shared'
|
|
|
|
- name: Check web-shared and CLI revivers
|
|
run: pnpm vitest run packages/web-shared/test/serializable-revivers.test.ts
|
|
|
|
build-error-messages:
|
|
name: Node.js Module Build Errors Test
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
needs: ci-scope
|
|
if: ${{ needs.ci-scope.outputs.fast-path != 'true' && !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@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
|
|
|
- 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
|
|
needs: ci-scope
|
|
if: ${{ needs.ci-scope.outputs.fast-path != 'true' }}
|
|
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-package-build:
|
|
name: Build Shared E2E Packages
|
|
runs-on: ubuntu-latest
|
|
needs: ci-scope
|
|
if: ${{ needs.ci-scope.outputs.fast-path != 'true' && !contains(github.event.pull_request.labels.*.name, 'workflow-server-test') }}
|
|
timeout-minutes: 30
|
|
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 and build packages
|
|
uses: ./.github/actions/setup-workflow-dev
|
|
with:
|
|
build-packages: 'true'
|
|
|
|
- name: Upload package build artifacts
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: e2e-package-build-artifacts
|
|
# Keep this list aligned with package build outputs in turbo.json.
|
|
# upload-artifact strips the common packages/ root; consumers restore into packages/.
|
|
path: |
|
|
packages/*/dist
|
|
packages/*/.well-known
|
|
packages/*/src/version.ts
|
|
packages/swc-plugin-workflow/swc_plugin_workflow.wasm
|
|
packages/swc-plugin-workflow/build-hash.json
|
|
include-hidden-files: true
|
|
retention-days: 1
|
|
|
|
e2e-vercel-prod:
|
|
name: E2E Vercel Prod Tests (${{ matrix.app.name }})
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
needs: ci-scope
|
|
if: ${{ needs.ci-scope.outputs.fast-path != 'true' }}
|
|
permissions:
|
|
id-token: write
|
|
contents: read
|
|
deployments: read
|
|
statuses: 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'
|
|
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: vercel/wait-for-deployment-action@0e2b0c5c5cce31f1648108aeec56467187aca037
|
|
with:
|
|
project-slug: ${{ matrix.app.project-slug }}
|
|
timeout: 1000
|
|
check-interval: 15
|
|
environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }}
|
|
|
|
- name: Record E2E start time
|
|
id: e2eStart
|
|
run: echo "ms=$(($(date +%s) * 1000))" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Run E2E Tests
|
|
run: pnpm run test:e2e --reporter=verbose --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: Capture runtime logs on failure
|
|
if: failure()
|
|
env:
|
|
APP_NAME: ${{ matrix.app.name }}
|
|
WORKFLOW_VERCEL_AUTH_TOKEN: ${{ secrets.VERCEL_LABS_TOKEN }}
|
|
WORKFLOW_VERCEL_TEAM: "team_nO2mCG4W8IxPIeKoSsqwAxxB"
|
|
WORKFLOW_VERCEL_PROJECT: ${{ matrix.app.project-id }}
|
|
WORKFLOW_VERCEL_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }}
|
|
VERCEL_DEPLOYMENT_ID: ${{ steps.waitForDeployment.outputs.deployment-id }}
|
|
E2E_START_MS: ${{ steps.e2eStart.outputs.ms }}
|
|
run: node .github/scripts/fetch-e2e-runtime-logs.mjs
|
|
|
|
- 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
|
|
e2e-runtime-logs-${{ matrix.app.name }}-vercel.json
|
|
retention-days: 7
|
|
if-no-files-found: ignore
|
|
|
|
e2e-vercel-multi-region:
|
|
# Vercel-specific multi-region routing suite (start()'s `region`
|
|
# option). Deliberately separate from e2e-vercel-prod: that job runs
|
|
# the shared suite as a matrix across all worlds/frameworks, while
|
|
# this functionality only exists for @workflow/world-vercel and only
|
|
# the nextjs-turbopack workbench is deployed multi-region
|
|
# (workbench/nextjs-turbopack/vercel.json pins iad1+sfo1+fra1).
|
|
name: E2E Vercel Multi-Region Tests (nextjs-turbopack)
|
|
runs-on: ubuntu-latest
|
|
# 19 regions: the implicit-route cases pay one cold start each,
|
|
# sequentially.
|
|
timeout-minutes: 35
|
|
needs: ci-scope
|
|
if: ${{ needs.ci-scope.outputs.fast-path != 'true' }}
|
|
permissions:
|
|
id-token: write
|
|
contents: read
|
|
deployments: read
|
|
statuses: read
|
|
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 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: vercel/wait-for-deployment-action@0e2b0c5c5cce31f1648108aeec56467187aca037
|
|
with:
|
|
project-slug: "example-nextjs-workflow-turbopack"
|
|
timeout: 1000
|
|
check-interval: 15
|
|
environment: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }}
|
|
|
|
- name: Run Multi-Region E2E Tests
|
|
run: pnpm vitest run packages/core/e2e/e2e-region.test.ts --reporter=verbose --reporter=json --reporter=./packages/core/e2e/github-reporter.ts "--outputFile=e2e-vercel-multi-region-$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: "nextjs-turbopack"
|
|
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: "prj_yjkM7UdHliv8bfxZ1sMJQf1pMpdi"
|
|
WORKFLOW_VERCEL_PROJECT_SLUG: "example-nextjs-workflow-turbopack"
|
|
# See the note on e2e-vercel-prod: PRs point at the protected
|
|
# workflow-server preview; unset on main for production.
|
|
VERCEL_WORKFLOW_SERVER_URL: ${{ github.ref != 'refs/heads/main' && secrets.VERCEL_WORKFLOW_SERVER_URL || '' }}
|
|
|
|
- name: Capture runtime logs on failure
|
|
if: failure()
|
|
env:
|
|
APP_NAME: "nextjs-turbopack"
|
|
WORKFLOW_VERCEL_AUTH_TOKEN: ${{ secrets.VERCEL_LABS_TOKEN }}
|
|
WORKFLOW_VERCEL_TEAM: "team_nO2mCG4W8IxPIeKoSsqwAxxB"
|
|
WORKFLOW_VERCEL_PROJECT: "prj_yjkM7UdHliv8bfxZ1sMJQf1pMpdi"
|
|
WORKFLOW_VERCEL_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || 'preview' }}
|
|
VERCEL_DEPLOYMENT_ID: ${{ steps.waitForDeployment.outputs.deployment-id }}
|
|
run: node .github/scripts/fetch-e2e-runtime-logs.mjs
|
|
|
|
- name: Upload E2E results
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: e2e-results-vercel-multi-region-nextjs-turbopack
|
|
path: |
|
|
e2e-vercel-multi-region-nextjs-turbopack.json
|
|
e2e-metadata-nextjs-turbopack-vercel.json
|
|
e2e-failures-nextjs-turbopack-vercel.json
|
|
e2e-diagnostics-nextjs-turbopack-vercel.json
|
|
e2e-runtime-logs-nextjs-turbopack-vercel.json
|
|
retention-days: 7
|
|
if-no-files-found: ignore
|
|
|
|
getTestMatrix:
|
|
name: Get Test Matrix
|
|
runs-on: ubuntu-latest
|
|
needs: ci-scope
|
|
if: ${{ needs.ci-scope.outputs.fast-path != 'true' && !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: ${{ needs.ci-scope.outputs.fast-path != 'true' && !contains(github.event.pull_request.labels.*.name, 'workflow-server-test') }}
|
|
needs: [ci-scope, getTestMatrix, e2e-package-build]
|
|
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'
|
|
|
|
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: Download shared package builds
|
|
if: ${{ !matrix.app.canary }}
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: e2e-package-build-artifacts
|
|
path: packages
|
|
|
|
- name: Run Initial Build for Next.js canary
|
|
if: ${{ matrix.app.canary }}
|
|
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: |
|
|
export DEV_SERVER_LOG_PATH="$GITHUB_WORKSPACE/dev-server-${{ matrix.app.name }}-${{ matrix.app.artifactSuffix }}.log"
|
|
rm -f "$DEV_SERVER_LOG_PATH"
|
|
(cd "$WORKBENCH_APP_PATH" && pnpm dev 2>&1 | tee "$DEV_SERVER_LOG_PATH") &
|
|
echo "starting tests in 10 seconds" && sleep 10
|
|
pnpm vitest run packages/core/e2e/dev.test.ts; sleep 10
|
|
pnpm run test:e2e --reporter=verbose --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) }}
|
|
WORKFLOW_DEV_HMR_LOGS: "1"
|
|
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: ${{ needs.ci-scope.outputs.fast-path != 'true' && !contains(github.event.pull_request.labels.*.name, 'workflow-server-test') }}
|
|
needs: [ci-scope, getTestMatrix, e2e-package-build]
|
|
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'
|
|
|
|
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: Download shared package builds
|
|
if: ${{ !matrix.app.canary }}
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: e2e-package-build-artifacts
|
|
path: packages
|
|
|
|
- name: Run Initial Build for Next.js canary
|
|
if: ${{ matrix.app.canary }}
|
|
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: |
|
|
export PROD_SERVER_LOG_PATH="$GITHUB_WORKSPACE/prod-server-${{ matrix.app.name }}-${{ matrix.app.artifactSuffix }}.log"
|
|
(cd "$WORKBENCH_APP_PATH" && pnpm start 2>&1 | tee "$PROD_SERVER_LOG_PATH") &
|
|
echo "starting tests in 10 seconds" && sleep 10
|
|
pnpm run test:e2e --reporter=verbose --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: ${{ needs.ci-scope.outputs.fast-path != 'true' && !contains(github.event.pull_request.labels.*.name, 'workflow-server-test') }}
|
|
needs: [ci-scope, getTestMatrix, e2e-package-build]
|
|
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"
|
|
|
|
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: Download shared package builds
|
|
if: ${{ !matrix.app.canary }}
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: e2e-package-build-artifacts
|
|
path: packages
|
|
|
|
- name: Run Initial Build for Next.js canary
|
|
if: ${{ matrix.app.canary }}
|
|
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: |
|
|
export PROD_SERVER_LOG_PATH="$GITHUB_WORKSPACE/prod-server-postgres-${{ matrix.app.name }}-${{ matrix.app.artifactSuffix }}.log"
|
|
(cd "$WORKBENCH_APP_PATH" && pnpm start 2>&1 | tee "$PROD_SERVER_LOG_PATH") &
|
|
echo "starting tests in 10 seconds" && sleep 10
|
|
pnpm run test:e2e --reporter=verbose --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
|
|
needs: ci-scope
|
|
if: ${{ needs.ci-scope.outputs.fast-path != 'true' && !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@46268bd060767258de96ed93c1251119784f2ab6 # v1.16.1
|
|
with:
|
|
toolchain: stable
|
|
target: wasm32-unknown-unknown
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
|
|
|
- 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"
|
|
$env:DEV_SERVER_LOG_PATH = $logFile
|
|
$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=verbose --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"
|
|
# Use a real workflow file for the Windows HMR test. Most of this
|
|
# workbench's workflows are symlinks into workbench/example, and
|
|
# Windows/Turbopack can rebuild from the symlink path while still
|
|
# serving the previous module contents from the realpath cache.
|
|
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,"testWorkflowFile":"96_many_steps.ts"}'
|
|
DEV_SERVER_LOG_PATH: "${{ github.workspace }}/nextjs-server.log"
|
|
WORKFLOW_DEV_HMR_LOGS: "1"
|
|
|
|
- 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 }}
|
|
package-version: ${{ matrix.world.version }}
|
|
service-type: ${{ matrix.world.service-type }}
|
|
env-vars: ${{ matrix.world.env-vars }}
|
|
services: ${{ matrix.world.services }}
|
|
secrets: inherit
|
|
|
|
# Final job: Aggregate all E2E results and update PR comment
|
|
summary:
|
|
name: E2E Summary
|
|
runs-on: ubuntu-latest
|
|
needs: [ci-scope, e2e-vercel-prod, e2e-local-dev, e2e-local-prod, e2e-local-postgres, e2e-windows]
|
|
if: always() && !cancelled() && needs.ci-scope.outputs.fast-path != 'true'
|
|
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@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
|
|
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@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
|
|
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: [ci-scope, unit, e2e-package-build, 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 }}
|
|
BUILD_STATUS: ${{ needs.e2e-package-build.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 }}
|
|
FAST_PATH: ${{ needs.ci-scope.outputs.fast-path }}
|
|
VALIDATION_FAST_PATH: ${{ needs.ci-scope.outputs.validation-fast-path }}
|
|
HAS_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'workflow-server-test') }}
|
|
run: |
|
|
FAILED_JOBS=()
|
|
|
|
if [[ "$FAST_PATH" == "true" ]]; then
|
|
if [[ "$VALIDATION_FAST_PATH" != "true" && "$HAS_LABEL" != "true" && "$UNIT_STATUS" != "success" ]]; then
|
|
echo "UI-only fast path still requires unit tests; unit status was $UNIT_STATUS."
|
|
exit 1
|
|
fi
|
|
echo "Docs/UI-only change detected - no runtime E2E lanes are required."
|
|
exit 0
|
|
fi
|
|
|
|
# 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)")
|
|
[[ "$BUILD_STATUS" == "success" ]] || FAILED_JOBS+=("e2e-package-build ($BUILD_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: ./.github/actions/publish-to-gh-pages
|
|
with:
|
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
source-dir: ./docs-data
|
|
destination-dir: ci
|