# PR-gated continuous integration — runs backend pytest + frontend node:test # + TypeScript typecheck on every pull request and push to main. Keeps the # heavy 4-platform Tauri bundle off this path (that's release.yml on tag # push) so PRs turn around in a few minutes instead of ~40. name: CI on: pull_request: branches: [main] push: branches: [main] workflow_dispatch: permissions: contents: read env: # Run all JavaScript actions on Node 24 (GH deprecates Node 20 in Sep 2026). FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: test: name: Tests (backend + frontend) runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - name: Setup Python 3.11 uses: actions/setup-python@v5 with: python-version: "3.11" # enable-cache persists ~/.cache/uv across runs, keyed on uv.lock — # turns `uv sync` from ~45 s cold to ~5 s warm. - name: Install uv uses: astral-sh/setup-uv@v3 with: enable-cache: true cache-dependency-glob: "uv.lock" # Node 22 is needed for --experimental-strip-types so node:test can # import .ts files directly from frontend/src/api/*. - name: Setup Node 22 uses: actions/setup-node@v4 with: node-version: '22' - name: Setup Bun uses: oven-sh/setup-bun@v1 # apt install ffmpeg is ~30 s every run; cache the resolved .debs. - name: System deps (ffmpeg) uses: awalsh128/cache-apt-pkgs-action@latest with: packages: ffmpeg version: 1.0 - name: Install Python deps # `--all-extras` installs optional engine deps (e.g. `supertonic`) # so their tests can exercise the real import path, not the # "package not installed" fallback. Smoke job below stays on bare # `uv sync` because smoke only hits /health + fixture profiles. run: uv sync --all-extras - name: Run pytest run: uv run pytest tests/ -q --tb=short # Docs-drift CI gate (Phase 1 INST-06). The validator extracts code # blocks tagged `` from docs/install/*.md and asserts # each line appears in scripts/desktop-prod.sh after normalisation. # Its own correctness is enforced by tests/scripts/test_validate_install_docs.py # (checker B-5) — those tests run in the previous step. - name: Validate install docs against desktop-prod.sh run: python scripts/validate-install-docs.py # `backend/tests/` stubs core.config in sys.modules to avoid the heavy # main app import chain — that pollutes import state for other modules, # so it runs in its own pytest session to stay isolated from tests/. - name: Run pytest (backend/tests, isolated) run: uv run pytest backend/tests/ -q --tb=short # Cache ~/.bun/install/cache keyed on bun.lock — `bun install` drops # from ~15 s cold to near-instant on warm cache. - name: Cache bun deps uses: actions/cache@v4 with: path: ~/.bun/install/cache key: ${{ runner.os }}-bun-${{ hashFiles('frontend/bun.lock', 'bun.lock') }} restore-keys: | ${{ runner.os }}-bun- - name: Install frontend deps working-directory: frontend run: bun install # checkJs is true in tsconfig for IDE feedback, but 947 pre-existing # JS errors remain. Override to false in CI so only .ts files block. # Sourced from `typecheck:ci` in frontend/package.json so the release # workflow runs an identical command — drift broke v0.3.x release runs. - name: Frontend typecheck working-directory: frontend run: bun run typecheck:ci - name: Run Vitest (frontend) working-directory: frontend run: bunx vitest run # Legacy node:test runner for tests/frontend/*.test.mjs - name: Run frontend node:test (legacy) working-directory: frontend run: node --experimental-strip-types --no-warnings --test ../tests/frontend/*.test.mjs # ── Cross-platform Tauri shell check ──────────────────────────────────── # Catches platform-specific Rust regressions on PR (cfg(target_os=...) # gates, missing Windows/macOS deps, etc.) without spending the 15+ min # per-platform that a full `tauri build` takes. `cargo check` is the # lightest gate that exercises type-checking + linking for each target. # Full bundling stays in release.yml on tag push. tauri-cross-platform: name: Tauri shell check (${{ matrix.label }}) needs: test strategy: fail-fast: false matrix: include: - os: macos-14 label: macOS rust_target: aarch64-apple-darwin - os: windows-2022 label: Windows rust_target: x86_64-pc-windows-msvc - os: ubuntu-22.04 label: Linux rust_target: x86_64-unknown-linux-gnu runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - name: Setup Rust (stable) uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.rust_target }} # Per-target cache key so we don't conflict with the release matrix. - name: Rust cache uses: Swatinem/rust-cache@v2 with: workspaces: frontend/src-tauri -> target key: ${{ matrix.rust_target }}-check - name: Setup Bun uses: oven-sh/setup-bun@v1 # Linux is the only host with non-trivial Tauri build deps — # webkit2gtk + libayatana-appindicator + xdo. Mirror release.yml. - name: Linux system deps if: runner.os == 'Linux' run: | sudo apt-get update sudo apt-get install -y \ libwebkit2gtk-4.1-dev \ build-essential curl wget file libxdo-dev libssl-dev \ libayatana-appindicator3-dev librsvg2-dev \ libasound2-dev - name: Cache bun deps uses: actions/cache@v4 with: path: ~/.bun/install/cache key: ${{ runner.os }}-bun-${{ hashFiles('frontend/bun.lock', 'bun.lock') }} restore-keys: | ${{ runner.os }}-bun- - name: Install frontend deps working-directory: frontend run: bun install # tauri-build's setup hook reads tauri.conf.json's `frontendDist` # ("../dist"), which only exists after a frontend build. Without this, # `cargo check` would fail on a fresh checkout because the embedded # asset map can't resolve. - name: Build frontend (for tauri.conf.json frontendDist) working-directory: frontend run: bun run build - name: Cargo check (Tauri shell) working-directory: frontend/src-tauri run: cargo check --target ${{ matrix.rust_target }} --message-format=short # ── Cross-platform Python runtime smoke (Phase 0 GATE-02) ─────────────── # Loads the frozen tests/fixtures/omnivoice_data/ fixture and boots the # FastAPI app in-process via TestClient on macOS/Windows/Linux. Catches # platform-specific Python import / path bugs that the Linux-only `test` # job above misses. Narrow scope (tests/smoke/ only) — full pytest stays # on Linux until Phase 1's INST-01 lands setuptools for WhisperX. smoke-matrix: name: Smoke (${{ matrix.label }}) needs: test strategy: fail-fast: false matrix: include: - os: macos-14 label: macOS - os: windows-2022 label: Windows - os: ubuntu-22.04 label: Linux runs-on: ${{ matrix.os }} timeout-minutes: 10 env: # Restricted-network resilience (RESEARCH Pitfall #6) — keeps uv from # giving up on the first slow PyPI / python-build-standalone fetch. UV_HTTP_TIMEOUT: "120" UV_HTTP_RETRIES: "5" steps: - uses: actions/checkout@v4 - name: Setup Python 3.11 uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install uv uses: astral-sh/setup-uv@v3 with: enable-cache: true cache-dependency-glob: "uv.lock" # ffmpeg + libsndfile are needed by soundfile / audio fixtures even # though the silence WAV doesn't decode anything heavy — keeps test # collection from import-erroring on optional audio modules. - name: System deps (macOS) if: runner.os == 'macOS' run: brew install ffmpeg libsndfile || true - name: System deps (Windows) if: runner.os == 'Windows' shell: bash run: | choco install ffmpeg -y --no-progress ffmpeg -version - name: System deps (Linux) if: runner.os == 'Linux' uses: awalsh128/cache-apt-pkgs-action@latest with: packages: ffmpeg libsndfile1 version: 1.0 - name: Install Python deps run: uv sync - name: Run smoke tests run: uv run pytest tests/smoke/ -q --tb=short