1515 #
1616 # The two paths build different schemas (migrations add triggers, checks and NOT VALID
1717 # constraints that `db:push` does not), so each runs the whole suite. Vitest splits each
18- # suite's files across four shards; a shard runs its files one at a time against its own
19- # database. Files run serially, so a shard barely uses more than one core: 4 vCPU is enough.
18+ # suite's files across four shards by measured duration (DurationBalancedSequencer in vitest.shared.ts); a
19+ # shard runs its files one at a time against its own database. Files run serially, so a shard
20+ # barely uses more than one core: 4 vCPU is enough.
2021 name : integration (${{ matrix.provision }}, ${{ matrix.shard }}/4)
2122 runs-on : &runner-4vcpu ${{ (vars.CI_PROVIDER == '' || vars.CI_PROVIDER == 'blacksmith') && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-latest' }}
2223 timeout-minutes : 20
@@ -272,6 +273,23 @@ jobs:
272273 working-directory : packages/db
273274 run : bun run db:migrate
274275
276+ # Turbopack's dev cache turns the spec's route warm-up from a cold compile (~4 min) into a
277+ # restore. It is content-addressed, so a pull request's changed modules still recompile; the
278+ # key carries the installed Next version so an upgrade starts from an empty cache, and the
279+ # event and fork segments keep untrusted runs off the cache trusted runs read.
280+ - name : Resolve Turbopack dev cache key
281+ id : next-cache
282+ run : echo "key=${GITHUB_REPOSITORY}-next-dev-desktop-live-${GITHUB_EVENT_NAME}${FORK_SUFFIX}-$(jq -r .version node_modules/next/package.json)" >> "$GITHUB_OUTPUT"
283+ env :
284+ FORK_SUFFIX : ${{ github.event.pull_request.head.repo.fork && '-fork' || '' }}
285+
286+ - name : Mount Turbopack dev cache
287+ uses : ./.github/actions/cache
288+ with :
289+ provider : ${{ vars.CI_PROVIDER }}
290+ key : ${{ steps.next-cache.outputs.key }}
291+ path : ./apps/sim/.next/dev
292+
275293 # Chat switches, Stop, sign-out, approval and the flag-off foreground round trip. The spec
276294 # runs the recording proxy (the app's public origin) and the stand-in worker.
277295 - name : Verify desktop tools in the Electron app against a local app
@@ -292,39 +310,84 @@ jobs:
292310 READY_TIMEOUT_SECONDS : 300
293311 run : |
294312 report_dir="$RUNNER_TEMP/e2e"
313+ next_log="$report_dir/desktop-tools-next.log"
295314 mkdir -p "$report_dir"
296- sudo apt-get update -q
297- sudo apt-get install -yq xvfb libgtk-3-0t64 libnss3 libasound2t64 libgbm1 libxss1 \
298- libxtst6 libatk-bridge2.0-0t64 libxkbcommon0 > /dev/null
299- # Bundle only: `bun run build` also fetches the macOS node-pty prebuilds for packaging,
300- # which a Linux run does not use.
301- (cd apps/desktop && bun run scripts/build.ts)
302315 # Each app runs in its own session under an E2E_APP tag, and stop-session.sh returns once
303316 # every process it started has exited.
304317 realtime_tag="desktop-realtime-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$$"
305318 server_tag="desktop-tools-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT-$$"
319+ # Keeps the restored cache under the cap the dev scripts apply locally.
320+ (cd apps/sim && bun run dev:cache:cap)
321+ start_sim() {
322+ (cd apps/sim && E2E_APP="$server_tag" exec setsid node ../../node_modules/next/dist/bin/next dev --hostname 127.0.0.1 \
323+ --port 3021 >> "$next_log" 2>&1) &
324+ server_pid=$!
325+ }
326+ # A corrupted cache aborts Turbopack instead of falling back. Only then is the shared cache
327+ # dropped: a failing test must not cost every later run its warm cache.
328+ cache_broken() {
329+ grep -qiE 'cache corruption|turbopack.*panic|panicked' "$next_log" 2>/dev/null
330+ }
331+ # The cache directory is a mount point: empty it rather than remove it. Absolute, because
332+ # the EXIT trap runs after the step has moved into apps/desktop.
333+ clear_cache() {
334+ find "$GITHUB_WORKSPACE/apps/sim/.next/dev" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
335+ }
336+ # SIGINT first: `next dev` SIGKILLs its server 100ms after SIGTERM, which discards a cache
337+ # write in flight. stop-session.sh then removes anything still running.
338+ # SIGINT is best-effort; the cleanup always runs, since workers and the detached telemetry
339+ # flush can outlive a server that has already exited.
340+ stop_sim() {
341+ if kill -INT "$server_pid" 2>/dev/null; then
342+ for _ in $(seq 1 30); do kill -0 "$server_pid" 2>/dev/null || break; sleep 1; done
343+ fi
344+ bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$server_pid" "$server_tag"
345+ }
306346 (cd apps/realtime && PORT=3023 SIM_DB_ROLE=realtime ALLOWED_ORIGINS="$NEXT_PUBLIC_APP_URL" \
307347 E2E_APP="$realtime_tag" exec setsid bun src/index.ts > "$report_dir/desktop-tools-realtime.log" 2>&1) &
308348 realtime_pid=$!
309- (cd apps/sim && E2E_APP="$server_tag" exec setsid node ../../node_modules/next/dist/bin/next dev --hostname 127.0.0.1 \
310- --port 3021 > "$report_dir/desktop-tools-next.log" 2>&1) &
311- server_pid=$!
349+ start_sim
312350 finish() {
313351 status=$?
314- bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$server_pid" "$server_tag" || status=1
352+ stop_sim || status=1
315353 bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$realtime_pid" "$realtime_tag" || status=1
316354 wait "$server_pid" "$realtime_pid" 2>/dev/null || true
355+ if cache_broken; then
356+ echo "::warning::Turbopack reported a broken dev cache; clearing it for the next run."
357+ clear_cache
358+ fi
317359 exit "$status"
318360 }
319361 trap finish EXIT
362+ # The apps boot while the runner installs Electron's libraries and bundles the shell.
363+ sudo apt-get update -q
364+ sudo apt-get install -yq xvfb libgtk-3-0t64 libnss3 libasound2t64 libgbm1 libxss1 \
365+ libxtst6 libatk-bridge2.0-0t64 libxkbcommon0 > /dev/null
366+ # Bundle only: `bun run build` also fetches the macOS node-pty prebuilds for packaging,
367+ # which a Linux run does not use.
368+ (cd apps/desktop && bun run scripts/build.ts)
320369 started=$SECONDS
370+ retried=0
321371 until curl --fail --silent --max-time 10 http://127.0.0.1:3021/api/health > /dev/null &&
322372 curl --fail --silent --max-time 10 http://127.0.0.1:3023/health > /dev/null; do
323- kill -0 "$server_pid" 2>/dev/null || { tail -n 200 "$report_dir/desktop-tools-next.log"; exit 1; }
373+ if ! kill -0 "$server_pid" 2>/dev/null; then
374+ tail -n 200 "$next_log"
375+ if [ "$retried" = 0 ] && cache_broken; then
376+ echo "::warning::Turbopack rejected the restored dev cache; restarting from an empty cache."
377+ bash "$GITHUB_WORKSPACE/.github/scripts/stop-session.sh" "$server_pid" "$server_tag"
378+ clear_cache
379+ : > "$next_log"
380+ retried=1
381+ start_sim
382+ continue
383+ fi
384+ exit 1
385+ fi
324386 kill -0 "$realtime_pid" 2>/dev/null || { tail -n 200 "$report_dir/desktop-tools-realtime.log"; exit 1; }
325387 [ $((SECONDS - started)) -lt "$READY_TIMEOUT_SECONDS" ] || { echo '::error::Local app did not become ready'; exit 1; }
326388 sleep 2
327389 done
390+ echo "Local apps ready $((SECONDS - started))s after the Electron bundle"
328391 cd apps/desktop
329392 SIM_DESKTOP_E2E_SIM_URL=http://127.0.0.1:3021 \
330393 SIM_DESKTOP_E2E_PROXY_PORT=3020 \
0 commit comments