name: '🏗️ Build and Test JellyTau' on: push: branches: - master paths-ignore: - '**/*.md' pull_request: branches: - master paths-ignore: - '**/*.md' workflow_dispatch: env: # Incremental state is never reused between CI runs -- pure disk cost. CARGO_INCREMENTAL: 0 jobs: test: name: Run Tests # A release push triggers build-release.yml on the tag, which runs this exact # test suite itself — and on a single-slot runner the two ~1h workflows would # otherwise serialize/contend. Skip the duplicate for chore(release) commits. # (head_commit is absent on pull_request/workflow_dispatch; startsWith(null,…) # is false there, so those events still run.) if: "!startsWith(github.event.head_commit.message, 'chore(release)')" runs-on: linux/amd64 container: image: gitea.tourolle.paris/dtourolle/jellytau-builder:latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Cache Rust dependencies uses: actions/cache@v3 with: # Registry only -- never src-tauri/target. That directory is ~16 GB and # was cached under five separate keys, which filled the runner's 74 GB # disk at ~1.15 GB/day (23 GB in 20 days, measured Aug 2026). # registry/src is omitted too: cargo re-extracts it for free from # registry/cache (155 MB of .crate tarballs vs 1.1 GB extracted). path: | ~/.cargo/registry/index ~/.cargo/registry/cache ~/.cargo/git/db # One shared key across every job. The old per-job keys existed to stop # debug/release target artifacts clobbering each other; with target no # longer cached, registry contents are target-independent, so all jobs # want the same crates. First job to finish saves; the rest restore. key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo-registry- - name: Cache Node dependencies uses: actions/cache@v3 with: path: | ~/.bun/install/cache node_modules key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} restore-keys: | ${{ runner.os }}-bun- - name: Install dependencies run: | bun install # Tripwire for domain-taxonomy leaks into the presentation layer (a # multi-type includeItemTypes query defining a category in the frontend). # See scripts/check-frontend-boundary.sh and # docs/specs/scoped-search-boundary.md. - name: Check frontend/backend boundary run: bash scripts/check-frontend-boundary.sh # The docs are the maintained source of truth for architecture and # process, and they cross-reference each other heavily. A rename that # misses a link turns a doc into a dead end silently. Pure shell + git — # no tool is installed at job time. - name: Check documentation links run: bash scripts/check-doc-links.sh # Formatting, linting and type-checking were all configured in this repo # and enforced by nothing: .prettierrc described a tree where 199 files did # not match it, eslint.config.js ran in no workflow and in no hook, and # `bun run check` ran only in build-release.yml — i.e. a type error could # sit on master until somebody cut a tag. These three steps are what make # those configs load-bearing. All are project deps installed by # `bun install`; nothing is fetched at job time. - name: Check formatting run: bun run format:check # RATCHET — this number only ever goes DOWN. Same policy as MIN_THRESHOLD # in traceability-check.yml and the coverage thresholds in # vitest.config.ts. 159 is what the tree carried when the gate went in; the # backlog is real findings (dead bindings, unkeyed {#each}, `any` at the # IPC boundary) that eslint.config.js documents rule by rule, each parked # at "warn" until its class is cleared and it can be promoted to "error". # Lower this as you clear them. Never raise it to make a build pass. - name: Lint run: bun run lint -- --max-warnings=159 - name: Check TypeScript run: | bunx svelte-kit sync bun run check # Coverage rather than a bare `bun run test`: same suite, plus the # thresholds in vitest.config.ts, so a large untested module or a deleted # test fails here instead of being noticed months later. - name: Run frontend tests run: | bunx svelte-kit sync bun run test:coverage # CLAUDE.md has required `cargo fmt` + `cargo clippy` before every commit # for as long as the rule has existed, but nothing in CI checked either, # so the requirement rested entirely on memory. Both components are baked # into the builder image (Dockerfile.builder: `rustup component add # rustfmt clippy`) — nothing is installed at job time. - name: Check Rust formatting run: | cd src-tauri cargo fmt --all -- --check # Clippy is a hard gate. It was advisory while the tree carried a warning # backlog; that backlog is gone (0 warnings on 1.97.1, the pinned # toolchain), so a warning here is now new breakage rather than old noise. # # This only means anything because src-tauri/rust-toolchain.toml pins the # compiler: clippy's lint set moves between releases, so an unpinned gate # would fail on whatever the runner happened to install. The pin and this # flag stand or fall together — if you unpin, drop this back to advisory. - name: Run clippy run: | cd src-tauri cargo clippy --all-targets -- -D warnings - name: Run Rust tests run: | cd src-tauri cargo test cd .. # Fast per-commit Android compile check. This does NOT build a shippable APK: # the full signed release APK is built only on tag pushes by build-release.yml # (which runs sync-android-sources.sh + signing). Running the full bundle here # too would duplicate a ~15min build and, without the sync step, produced an # unsigned APK missing our custom sources/icons/proguard rules anyway. # `cargo check` for the Android target (~1min) catches Android-specific Rust # breakage without linking, bundling, or signing. android-check: name: Android Compile Check runs-on: linux/amd64 needs: test container: image: gitea.tourolle.paris/dtourolle/jellytau-builder:latest env: ANDROID_HOME: /opt/android-sdk ANDROID_SDK_ROOT: /opt/android-sdk NDK_HOME: /opt/android-sdk/ndk/27.0.11902837 ANDROID_NDK_HOME: /opt/android-sdk/ndk/27.0.11902837 steps: - name: Checkout repository uses: actions/checkout@v4 - name: Cache Rust dependencies uses: actions/cache@v3 with: # Registry only -- never src-tauri/target. That directory is ~16 GB and # was cached under five separate keys, which filled the runner's 74 GB # disk at ~1.15 GB/day (23 GB in 20 days, measured Aug 2026). # registry/src is omitted too: cargo re-extracts it for free from # registry/cache (155 MB of .crate tarballs vs 1.1 GB extracted). path: | ~/.cargo/registry/index ~/.cargo/registry/cache ~/.cargo/git/db # One shared key across every job. The old per-job keys existed to stop # debug/release target artifacts clobbering each other; with target no # longer cached, registry contents are target-independent, so all jobs # want the same crates. First job to finish saves; the rest restore. key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ runner.os }}-cargo-registry- - name: Cache Node dependencies uses: actions/cache@v3 with: path: | ~/.bun/install/cache node_modules key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} restore-keys: | ${{ runner.os }}-bun- - name: Install dependencies run: bun install - name: Cargo check (aarch64-linux-android) run: | TC="$NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin" export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$TC/aarch64-linux-android24-clang" export CC_aarch64_linux_android="$TC/aarch64-linux-android24-clang" export AR_aarch64_linux_android="$TC/llvm-ar" cd src-tauri cargo check --target aarch64-linux-android --lib