ci: Phase 1 low-risk CI wall-time optimizations#941
Conversation
Reduces CI time on every PR with no behavioural trade-offs. Each change is independent and can be reverted in isolation. Frontend / Vitest: - Consolidate test:unit into a single `vitest run` call (was 10 sequential invocations, each paying full Vite + TS-transform cold-start cost). - Switch Vitest to `pool: 'threads'` (Linux + `environment: 'node'`; no native addons, no thread-unsafe APIs in tests). - Cache `node_modules/.astro` (Astro content-layer) keyed on content + `astro.config.mjs`. - Add `--prefer-offline` to `pnpm install` to skip registry HEAD probes when the setup-node pnpm-store cache hits. - Set `compression-level: 0` on Playwright artifact uploads (HTML report, raw results) since the payloads are already compressed (png/webm/trace). .NET: - Set `DOTNET_NOLOGO=1` and `DOTNET_CLI_TELEMETRY_OPTOUT=1` on both `apphost-build.yml` and `tools-tests.yml`. - `tools-tests.yml`: add explicit `dotnet build --no-restore` then run `dotnet test --no-build --no-restore` so MSBuild does not implicitly rebuild during the test step. GitHub Actions infra: - Add `concurrency` group to `ci.yml` so superseded PR commits cancel in-flight runs while main/release pushes keep running. - `changes` job now uses `fetch-depth: 1` plus a targeted `git fetch --depth=1` for the PR base + head SHAs rather than a full-history clone. `frontend-build.yml` keeps `fetch-depth: 0` because Lunaria still requires the full git history. Verified locally: - `pnpm test:unit` passes all 159 unit tests across 17 files in 5.5s with the new consolidated invocation + threads pool. - YAML for all four workflows parses cleanly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
d0c124e to
6762e04
Compare
There was a problem hiding this comment.
Pull request overview
This PR optimizes CI wall time by reducing redundant test startup/build work and adding workflow-level caching and runner settings without changing product behavior.
Changes:
- Consolidates frontend unit tests into one Vitest invocation and switches Vitest to the threads pool.
- Adds frontend dependency/cache/artifact upload optimizations.
- Updates .NET workflows and CI orchestration to reduce repeated work and cancel superseded PR runs.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/frontend/vitest.config.ts |
Sets Vitest to use the threads worker pool. |
src/frontend/package.json |
Consolidates test:unit into one Vitest run. |
.github/workflows/tools-tests.yml |
Adds .NET CLI env flags and separates build from test with --no-build. |
.github/workflows/frontend-build.yml |
Adds pnpm --prefer-offline, Astro content cache, and uncompressed artifact uploads. |
.github/workflows/ci.yml |
Adds workflow concurrency and shallow PR diff fetches. |
.github/workflows/apphost-build.yml |
Adds .NET CLI env flags for AppHost build. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address PR review feedback: the previous `${{ github.workflow }}-${{ github.ref }}`
group was shared across consecutive pushes to the same branch. GitHub Actions
concurrency cancels older *pending* runs in a group even when
`cancel-in-progress: false`, so rapid pushes to `main`/`release/*` could
silently drop a required CI run.
New group key:
${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
- pull_request: group keyed on PR number — new commits cancel in-flight
runs for the same PR (intended behaviour).
- push: group includes `github.run_id` so every push gets a unique group;
pushes never cancel or queue against each other.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| # `cancel-in-progress: false`, which would silently drop required CI on | ||
| # `main`.) | ||
| concurrency: | ||
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} |
There was a problem hiding this comment.
Hmm, why isn't this the default behavior? I don't understand this change.
There was a problem hiding this comment.
Good question. Two things going on:
Why GHA doesn't do this by default: concurrency control isn't universally safe — release pipelines, audit jobs, and workflows with external side-effects need every commit to run to completion. So GHA's default is "run everything in parallel" and concurrency: is the opt-in switch.
What this specific block does:
- PRs — key includes
github.event.pull_request.number+cancel-in-progress: true. New commits to a PR cancel the older in-flight run for that same PR. Saves runner-minutes on rapid fixup / rebase pushes. - Pushes (
main/release/*) — key falls through togithub.run_id, which is unique per run, so each push effectively gets its own group. Combined withcancel-in-progress: false, push runs neither cancel each other nor queue against each other. Every commit tomainalways gets its own CI run.
The reason this is split rather than just using a single shared group with cancel-in-progress: false is the bug the bot caught above: when runs share a group, GHA cancels older pending runs regardless of cancel-in-progress. Giving push events a unique group sidesteps that entirely.
There was a problem hiding this comment.
New commits to a PR cancel the older in-flight run for that same PR. Saves runner-minutes on rapid fixup / rebase pushes.
I thought that is the normal "default" behavior...
Summary
Phase 1 of CI wall-time optimizations — all changes are low-risk with no behavioural trade-offs. Each item is independently revertable. Background research and tradeoffs are documented in the commit message.
What's in this PR
Frontend / Vitest
test:unit— was 10 sequentialvitest runcalls, each paying full Vite + TS-transform cold-start cost. Now a single invocation that picks up the same files viavitest.config.ts'sincludepattern. Individualtest:unit:*scripts kept for local granular runs.pool: 'threads'—worker_threadshas lower spawn overhead than the defaultforkspool on Linux. Tests areenvironment: 'node'with no native addons or thread-unsafe APIs.node_modules/.astrokeyed on content +astro.config.mjsso Astro's content layer doesn't re-hydrate from scratch when MDX hasn't changed.pnpm install --prefer-offline— skips registry HEAD probes when the setup-node pnpm-store cache hits. Falls back to network on misses.compression-level: 0on Playwright artifact uploads — reports contain PNGs, webm videos, and traces that are already compressed..NET
DOTNET_NOLOGO=1andDOTNET_CLI_TELEMETRY_OPTOUT=1on bothapphost-build.ymlandtools-tests.yml.tools-tests.ymlexplicit build +--no-build—dotnet testwas implicitly rebuilding each project. Now we restore once, build once, and rundotnet test --no-build --no-restore.GitHub Actions infra
concurrencygroup onci.ymlwithcancel-in-progress: ${{ github.event_name == 'pull_request' }}. Stacked PR runs cancel after rebases; pushes tomain/release/*are never cancelled.changesjob usesfetch-depth: 1plus a targetedgit fetch --depth=1of the PR base and head SHAs.frontend-build.ymlkeepsfetch-depth: 0because Lunaria still requires full history.Verified locally
pnpm test:unitruns all 159 unit tests across 17 files in 5.5 s with the new consolidated invocation + threads pool.Explicitly NOT recommended (researched and verified)
Listed here so reviewers don't ask:
NODE_OPTIONS=--max-old-space-size=*— Node 22+ auto-sizes heap to ~50% of RAM; manual flags are cargo cult on 16 GB runners.pnpm --reporter=silent— already silent in non-TTY CI.DOTNET_SKIP_FIRST_TIME_EXPERIENCE— deprecated since .NET Core 3.0.build:skip-search— Pagefind IS exercised bysite-search.spec.tsandts-api-search.spec.ts.isolate: false—api-markdown.vitest.test.tsusesvi.mock; state-bleed risk isn't worth the marginal win.~/.cache/ms-playwright— Playwright's own docs advise against it.Follow-ups (not in this PR)
Two higher-reward changes that trade off discipline or runner minutes; I'll open separate PRs once we agree:
packages.lock.json— adds dependency-bump discipline.desktop-chromium,tablet-chromium,mobile-chromium) — 3× runner minutes for the E2E phase in exchange for ~60% wall-time reduction.