@@ -167,11 +167,10 @@ jobs:
167167 # The suites exercise HTTP behavior rather than a provisioning path, so they run once, against
168168 # the production (migrate) path. Each group's app environment lives in http-e2e.sh.
169169 #
170- # SCIM's hosted suites use 8 vCPU; the other self-hosted suites fit on 4. The mobile browser
171- # suite runs in e2e-production below.
170+ # All groups reuse the production build; compiling routes is not part of HTTP acceptance.
172171 e2e :
173172 name : e2e (${{ matrix.group }})
174- needs : changes
173+ needs : [ changes, e2e-build]
175174 if : needs.changes.outputs.full == 'true'
176175 runs-on : ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && matrix.runner || 'ubuntu-latest' }}
177176 timeout-minutes : 20
@@ -180,13 +179,15 @@ jobs:
180179 matrix :
181180 include :
182181 - group : scim
183- runner : blacksmith-8vcpu -ubuntu-2404
182+ runner : blacksmith-4vcpu -ubuntu-2404
184183 - group : cli
185184 runner : blacksmith-4vcpu-ubuntu-2404
186185 - group : stop-after
187186 runner : blacksmith-4vcpu-ubuntu-2404
188187 - group : desktop-inbox
189188 runner : blacksmith-4vcpu-ubuntu-2404
189+ - group : seo
190+ runner : blacksmith-4vcpu-ubuntu-2404
190191 services :
191192 postgres :
192193 image : pgvector/pgvector:pg17
@@ -214,6 +215,7 @@ jobs:
214215 env :
215216 DATABASE_URL : postgresql://postgres:postgres@127.0.0.1:5432/sim_test
216217 BETTER_AUTH_SECRET : http-e2e-ci-secret-at-least-32-characters
218+ E2E_APP_SERVER : start
217219 ENCRYPTION_KEY : ' 0000000000000000000000000000000000000000000000000000000000000000'
218220
219221 steps :
@@ -241,12 +243,39 @@ jobs:
241243 working-directory : packages/db
242244 run : bun run db:migrate
243245
246+ - &download-e2e-build
247+ name : Download the production build
248+ uses : actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
249+ with :
250+ name : next-build
251+ path : ${{ runner.temp }}
252+
253+ # A hosted build (NEXT_PUBLIC_APP_URL on sim.ai) prerenders every post; the build production
254+ # images use prerenders none. The URL itself is no marker: next.config's redirects name
255+ # www.sim.ai in every build.
256+ - &extract-e2e-build
257+ name : Extract the production build
258+ run : |
259+ tar -I zstd -xf "$RUNNER_TEMP/next-build.tar.zst" -C apps/sim
260+ jq -e '[.routes[].srcRoute] | any(. == "/blog/[slug]" or . == "/library/[slug]") | not' \
261+ apps/sim/.next/prerender-manifest.json > /dev/null ||
262+ { echo "::error::The build prerendered posts (a hosted build) or has no prerender manifest."; exit 1; }
263+
244264 # No step timeout: the job's bound covers a hang without cutting a slow but healthy suite
245265 # short of writing its report.
246266 - name : Run end-to-end suites
247267 working-directory : apps/sim
248268 run : bash ../../.github/scripts/http-e2e.sh "${{ matrix.group }}"
249269
270+ - name : Upload acceptance timing and coverage reports
271+ if : always()
272+ uses : actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
273+ with :
274+ name : e2e-reports-${{ matrix.group }}
275+ path : ${{ runner.temp }}/e2e/**/*.json
276+ if-no-files-found : ignore
277+ retention-days : 7
278+
250279 - name : Upload end-to-end reports and server logs
251280 if : failure()
252281 uses : actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
@@ -340,21 +369,8 @@ jobs:
340369 working-directory : packages/db
341370 run : bun run db:migrate
342371
343- - name : Download the production build
344- uses : actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
345- with :
346- name : next-build
347- path : ${{ runner.temp }}
348-
349- # A hosted build (NEXT_PUBLIC_APP_URL on sim.ai) prerenders every post; the build production
350- # images use prerenders none. The URL itself is no marker: next.config's redirects name
351- # www.sim.ai in every build.
352- - name : Extract the production build
353- run : |
354- tar -I zstd -xf "$RUNNER_TEMP/next-build.tar.zst" -C apps/sim
355- jq -e '[.routes[].srcRoute] | any(. == "/blog/[slug]" or . == "/library/[slug]") | not' \
356- apps/sim/.next/prerender-manifest.json > /dev/null ||
357- { echo "::error::The build prerendered posts (a hosted build) or has no prerender manifest."; exit 1; }
372+ - *download-e2e-build
373+ - *extract-e2e-build
358374
359375 # JOBS=4 caps node-gyp parallelism as docker/app.Dockerfile does (laverdet/isolated-vm#428).
360376 - name : Build native isolate addon for Project file rendering
@@ -393,15 +409,14 @@ jobs:
393409 if-no-files-found : ignore
394410 retention-days : 7
395411
396- # Desktop tools in the real Electron app against a local app, on its own runner: the
397- # Electron app, the dev app and its realtime server together outgrow the e2e runner.
412+ # Desktop tools exercise the real Electron app against the shared production build.
398413 # Two runners: the lease-lapse test sleeps through a long import and then a lease (~3 min), as
399414 # long as the rest of the spec together, so on one runner it set the time for every run.
400415 desktop-live :
401416 name : desktop-live (${{ matrix.tests }})
402- needs : changes
417+ needs : [ changes, e2e-build]
403418 if : needs.changes.outputs.desktop_live == 'true'
404- runs-on : ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-16vcpu -ubuntu-2404' || 'ubuntu-latest' }}
419+ runs-on : ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-8vcpu -ubuntu-2404' || 'ubuntu-latest' }}
405420 timeout-minutes : 30
406421 strategy :
407422 fail-fast : false
@@ -452,22 +467,8 @@ jobs:
452467 working-directory : packages/db
453468 run : bun run db:migrate
454469
455- # Turbopack's dev cache turns the spec's route warm-up from a cold compile (~4 min) into a
456- # restore. It is content-addressed, so a pull request's changed modules still recompile; the
457- # key carries the installed Next version so an upgrade starts from an empty cache, and the
458- # event and fork segments keep untrusted runs off the cache trusted runs read.
459- - name : Resolve Turbopack dev cache key
460- id : next-cache
461- run : echo "key=${GITHUB_REPOSITORY}-next-dev-desktop-live-${GITHUB_EVENT_NAME}${FORK_SUFFIX}-$(jq -r .version node_modules/next/package.json)" >> "$GITHUB_OUTPUT"
462- env :
463- FORK_SUFFIX : ${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
464-
465- - name : Mount Turbopack dev cache
466- uses : ./.github/actions/cache
467- with :
468- provider : ${{ vars.CI_PROVIDER }}
469- key : ${{ steps.next-cache.outputs.key }}
470- path : ./apps/sim/.next/dev
470+ - *download-e2e-build
471+ - *extract-e2e-build
471472
472473 # Chat switches, Stop, sign-out, approval, the dormant-executor round trip, and background
473474 # runs across a chat switch and a network cut. The spec runs the recording proxy (the app's
@@ -496,37 +497,12 @@ jobs:
496497 # every process it started has exited.
497498 realtime_tag="desktop-realtime-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$$"
498499 server_tag="desktop-tools-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$$"
499- # Keeps the restored cache under the cap the dev scripts apply locally.
500- (cd apps/sim && bun run dev:cache:cap)
501500 start_sim() {
502- (cd apps/sim && E2E_APP="$server_tag" exec setsid node ../../node_modules/next/dist/bin/next dev --hostname 127.0.0.1 \
501+ (cd apps/sim && E2E_APP="$server_tag" LOG_LEVEL=INFO exec setsid node ../../node_modules/next/dist/bin/next start --hostname 127.0.0.1 \
503502 --port 3021 >> "$next_log" 2>&1) &
504503 server_pid=$!
505504 }
506- # A corrupted cache aborts Turbopack instead of falling back. Only then is the shared cache
507- # dropped: a failing test must not cost every later run its warm cache.
508- cache_broken() {
509- grep -qiE 'cache corruption|turbopack.*panic|panicked' "$next_log" 2>/dev/null
510- }
511- # The cache directory is a mount point: empty it rather than remove it. Absolute, because
512- # the EXIT trap runs after the step has moved into apps/desktop.
513- clear_cache() {
514- find "$GITHUB_WORKSPACE/apps/sim/.next/dev" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
515- }
516- # A sticky disk keeps nothing from a failed job, so a clear here sticks only when the job
517- # passes. The marker names the cache for desktop-live-cache-reset, which empties it after
518- # a failed job; written on each panic, it survives the restart truncating the log.
519- mark_cache_broken() {
520- jq -r .version "$GITHUB_WORKSPACE/node_modules/next/package.json" > "$report_dir/turbopack-cache-broken"
521- }
522- # SIGINT first: `next dev` SIGKILLs its server 100ms after SIGTERM, which discards a cache
523- # write in flight. stop-session.sh then removes anything still running.
524- # SIGINT is best-effort; the cleanup always runs, since workers and the detached telemetry
525- # flush can outlive a server that has already exited.
526505 stop_sim() {
527- if kill -INT "$server_pid" 2>/dev/null; then
528- for _ in $(seq 1 30); do kill -0 "$server_pid" 2>/dev/null || break; sleep 1; done
529- fi
530506 bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$server_pid" "$server_tag"
531507 }
532508 (cd apps/realtime && PORT=3023 SIM_DB_ROLE=realtime ALLOWED_ORIGINS="$NEXT_PUBLIC_APP_URL" \
@@ -538,11 +514,6 @@ jobs:
538514 stop_sim || status=1
539515 bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$realtime_pid" "$realtime_tag" || status=1
540516 wait "$server_pid" "$realtime_pid" 2>/dev/null || true
541- if cache_broken; then
542- echo "::warning::Turbopack reported a broken dev cache; clearing it for the next run."
543- mark_cache_broken
544- clear_cache
545- fi
546517 exit "$status"
547518 }
548519 trap finish EXIT
@@ -554,21 +525,10 @@ jobs:
554525 # which a Linux run does not use.
555526 (cd apps/desktop && bun run scripts/build.ts)
556527 started=$SECONDS
557- retried=0
558528 until curl --fail --silent --max-time 10 http://127.0.0.1:3021/api/health > /dev/null &&
559529 curl --fail --silent --max-time 10 http://127.0.0.1:3023/health > /dev/null; do
560530 if ! kill -0 "$server_pid" 2>/dev/null; then
561531 tail -n 200 "$next_log"
562- if [ "$retried" = 0 ] && cache_broken; then
563- echo "::warning::Turbopack rejected the restored dev cache; restarting from an empty cache."
564- bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$server_pid" "$server_tag"
565- mark_cache_broken
566- clear_cache
567- : > "$next_log"
568- retried=1
569- start_sim
570- continue
571- fi
572532 exit 1
573533 fi
574534 kill -0 "$realtime_pid" 2>/dev/null || { tail -n 200 "$report_dir/desktop-tools-realtime.log"; exit 1; }
@@ -595,63 +555,6 @@ jobs:
595555 if-no-files-found : ignore
596556 retention-days : 7
597557
598- # Empties a dev cache Turbopack panicked on in a failed desktop-live run. The run's own clear is
599- # discarded with the rest of its failed job, so every later run would restore the same broken
600- # cache and fail; emptied in this passing job, the empty cache is what the next run mounts. A
601- # failure the cache did not cause leaves no marker, and the warm cache stays.
602- desktop-live-cache-reset :
603- name : desktop-live cache reset
604- needs : desktop-live
605- if : ${{ always() && needs.desktop-live.result == 'failure' }}
606- runs-on : *runner-2vcpu
607- timeout-minutes : 5
608- steps :
609- - name : Download desktop-live results
610- uses : actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
611- with :
612- pattern : desktop-live-e2e-results-*
613- path : ${{ runner.temp }}/desktop-live
614- merge-multiple : true
615-
616- # The marker carries only the Next version; the rest of the key comes from this run's own
617- # context, as in desktop-live, so a fork's marker can name no cache but its own.
618- - name : Resolve the broken cache's key
619- id : broken
620- env :
621- MARKER : ${{ runner.temp }}/desktop-live/turbopack-cache-broken
622- FORK_SUFFIX : ${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
623- run : |
624- if [ ! -s "$MARKER" ]; then
625- echo "No broken dev cache reported; keeping the warm cache."
626- exit 0
627- fi
628- next_version="$(head -n 1 "$MARKER")"
629- if ! [[ "$next_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+[0-9A-Za-z.-]*$ ]]; then
630- echo "::error::The broken cache marker holds no Next version: '$next_version'"
631- exit 1
632- fi
633- echo "key=${GITHUB_REPOSITORY}-next-dev-desktop-live-${GITHUB_EVENT_NAME}${FORK_SUFFIX}-${next_version}" >> "$GITHUB_OUTPUT"
634-
635- - *checkout-mirror
636- - *checkout-plain
637-
638- # A desktop-live leg that passed may have saved this run's actions/cache entry already; the
639- # suffix gives the emptied cache its own, newer entry for the next run's prefix restore.
640- - name : Mount the broken dev cache
641- if : steps.broken.outputs.key != ''
642- uses : ./.github/actions/cache
643- with :
644- provider : ${{ vars.CI_PROVIDER }}
645- key : ${{ steps.broken.outputs.key }}
646- path : ./apps/sim/.next/dev
647- save-suffix : -reset
648-
649- - name : Empty the dev cache
650- if : steps.broken.outputs.key != ''
651- run : |
652- mkdir -p apps/sim/.next/dev
653- find apps/sim/.next/dev -mindepth 1 -maxdepth 1 -exec rm -rf {} +
654-
655558 # Lint, audits, type-check and the schema sync check: about two minutes of mostly cached work,
656559 # kept off the test shards so neither waits on the other.
657560 lint :
@@ -985,7 +888,7 @@ jobs:
985888 e2e-build :
986889 name : build (e2e)
987890 needs : changes
988- if : needs.changes.outputs.full == 'true'
891+ if : needs.changes.outputs.full == 'true' || needs.changes.outputs.desktop_live == 'true'
989892 runs-on : ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-16vcpu-ubuntu-2404' || 'linux-x64-8-core' }}
990893 timeout-minutes : 25
991894
0 commit comments