build(deps): Bump the ui-minor-patch group across 1 directory with 17 updates #871
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Coverage | |
| # Coverage gate (CONTRIBUTING.md "Coverage gate"). Reports Rust + UI line | |
| # coverage on every PR, compares it to the latest `main` baseline, and FAILS | |
| # the check if either number regresses below `main` (minus a small jitter | |
| # epsilon). Mark this "coverage" check as Required in branch protection to | |
| # block merges on a regression. | |
| # | |
| # Baseline model (bootstrapping-safe): every push to `main` recomputes | |
| # coverage and stores it in the Actions cache. PRs restore that baseline and | |
| # diff against it. Before the first `main` build that carries this workflow | |
| # there is no baseline, so the gate is informational (fail-open) for that one | |
| # run, then enforces from the next PR onward. | |
| # | |
| # Scope: the library crates (`--exclude src-tauri --exclude driven-chaos | |
| # --exclude driven-bench --exclude driven-e2e`). src-tauri is a thin IPC layer over driven-core; | |
| # driven-chaos is the stress harness; driven-e2e is the app-level WebDriver | |
| # harness (runs only inside its container); driven-bench is the benchmark harness, | |
| # which mostly spawns child processes and talks to a real Google account and so | |
| # is largely unreachable without credentials. All three are excluded from the | |
| # measured/report set (their tests are not run for coverage), but | |
| # `--workspace --exclude` still auto-includes any NEW | |
| # crate in the gate, which a hand-maintained `-p` list would silently miss. The | |
| # Vue/TS app (`ui/`) is measured in full. (telemetry-worker is its own toolchain | |
| # and is out of scope for this gate.) | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| # Only cancel a SUPERSEDED PR run (a newer commit on the same PR). NEVER | |
| # cancel a push-to-main run: those compute and SAVE the baseline cache, and a | |
| # rapid run of merges would otherwise cancel each baseline run before it | |
| # saved, leaving the gate permanently without a baseline to compare against. | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| env: | |
| CARGO_TERM_COLOR: always | |
| SQLX_OFFLINE: "true" | |
| # Percentage-point slack so float jitter in line counts never flips the gate. | |
| COVERAGE_EPSILON: "0.1" | |
| BASELINE_FILE: coverage-baseline.json | |
| jobs: | |
| coverage: | |
| name: coverage | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Install Linux Tauri deps | |
| # Some workspace crates pull in the GTK/webkit stack transitively; the | |
| # coverage build needs the same system libs the rust-test job installs. | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libwebkit2gtk-4.1-dev libxdo-dev libssl-dev \ | |
| libayatana-appindicator3-dev librsvg2-dev libsoup-3.0-dev javascriptcoregtk-4.1 | |
| # The S3 backend's integration suite spawns a local MinIO and exercises the | |
| # store's whole I/O surface (upload, multipart, download, listing, audit, | |
| # delete). Without the binary that suite gate-skips and every one of those | |
| # lines reads as uncovered - so this is what makes the destination's | |
| # coverage number reflect code that is actually verified, rather than | |
| # forcing the alternative of a hand-written fake S3 server that would drift | |
| # from the real protocol. Mirrors the same step in ci.yml's rust-test job. | |
| - name: Install MinIO (S3 backend integration tests) | |
| run: | | |
| curl -fsSL https://dl.min.io/server/minio/release/linux-amd64/minio -o /tmp/minio | |
| sudo install -m 0755 /tmp/minio /usr/local/bin/minio | |
| minio --version | |
| - uses: dtolnay/rust-toolchain@stable | |
| with: { components: llvm-tools-preview } | |
| - uses: taiki-e/install-action@v2.85.13 | |
| with: { tool: cargo-llvm-cov } | |
| # This is an instrumented (coverage) build with its own RUSTFLAGS, so it | |
| # gets its own cache key and deliberately does NOT share the `workspace` | |
| # key. Restore-only on PRs; only main writes it (main coverage runs never | |
| # cancel, so the cache stays warm). | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| - uses: pnpm/action-setup@v6 | |
| with: { version: 10 } | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: 22 | |
| cache: pnpm | |
| cache-dependency-path: ui/pnpm-lock.yaml | |
| - name: pnpm install | |
| working-directory: ui | |
| run: pnpm install --frozen-lockfile | |
| # --- Compute this commit's coverage ------------------------------------- | |
| - name: Rust coverage (library crates) | |
| run: | | |
| cargo llvm-cov --workspace --exclude src-tauri --exclude driven-chaos \ | |
| --exclude driven-bench --exclude driven-e2e \ | |
| --summary-only --json --output-path coverage-rust.json | |
| RUST_PCT=$(jq '.data[0].totals.lines.percent' coverage-rust.json) | |
| echo "HEAD_RUST=$RUST_PCT" >> "$GITHUB_ENV" | |
| echo "Rust line coverage: ${RUST_PCT}%" | |
| - name: UI coverage (vue/ts) | |
| working-directory: ui | |
| run: pnpm run test:coverage | |
| - name: Extract UI coverage | |
| run: | | |
| UI_PCT=$(jq '.total.lines.pct' ui/coverage/coverage-summary.json) | |
| echo "HEAD_UI=$UI_PCT" >> "$GITHUB_ENV" | |
| echo "UI line coverage: ${UI_PCT}%" | |
| # --- main: publish the baseline ----------------------------------------- | |
| - name: Write baseline file | |
| if: github.event_name == 'push' | |
| run: | | |
| jq -n --argjson rust "$HEAD_RUST" --argjson ui "$HEAD_UI" \ | |
| '{rust: $rust, ui: $ui}' > "$BASELINE_FILE" | |
| cat "$BASELINE_FILE" | |
| - name: Save baseline to cache | |
| if: github.event_name == 'push' | |
| uses: actions/cache/save@v6 | |
| with: | |
| path: ${{ env.BASELINE_FILE }} | |
| # New key per commit; PRs restore the most recent via restore-keys. | |
| key: driven-cov-baseline-${{ github.sha }} | |
| # --- PR: restore baseline, comment the delta, gate ---------------------- | |
| - name: Restore baseline from cache | |
| if: github.event_name == 'pull_request' | |
| uses: actions/cache/restore@v6 | |
| with: | |
| path: ${{ env.BASELINE_FILE }} | |
| key: driven-cov-baseline-none | |
| restore-keys: | | |
| driven-cov-baseline- | |
| - name: Comment coverage + enforce gate | |
| if: github.event_name == 'pull_request' | |
| uses: actions/github-script@v9 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const eps = parseFloat(process.env.COVERAGE_EPSILON); | |
| const head = { rust: parseFloat(process.env.HEAD_RUST), ui: parseFloat(process.env.HEAD_UI) }; | |
| let base = null; | |
| try { base = JSON.parse(fs.readFileSync(process.env.BASELINE_FILE, 'utf8')); } | |
| catch (e) { core.info('No coverage baseline on main yet; gate is informational this run.'); } | |
| const fmt = (n) => (n === null || n === undefined ? 'n/a' : `${n.toFixed(2)}%`); | |
| const row = (label, b, h) => { | |
| if (b === null || b === undefined) return `| ${label} | n/a | ${fmt(h)} | n/a |`; | |
| const d = h - b; | |
| const sign = d >= 0 ? '+' : ''; | |
| const ok = d >= -eps ? 'OK' : 'REGRESSED'; | |
| return `| ${label} | ${fmt(b)} | ${fmt(h)} | ${sign}${d.toFixed(2)} (${ok}) |`; | |
| }; | |
| const regressed = []; | |
| if (base) { | |
| if (head.rust < base.rust - eps) regressed.push(`Rust ${fmt(head.rust)} < main ${fmt(base.rust)}`); | |
| if (head.ui < base.ui - eps) regressed.push(`UI ${fmt(head.ui)} < main ${fmt(base.ui)}`); | |
| } | |
| const lines = [ | |
| '<!-- coverage-gate -->', | |
| '## Coverage', | |
| '', | |
| '| Area | main | this PR | delta |', | |
| '| --- | --- | --- | --- |', | |
| row('Rust (lib crates)', base?.rust, head.rust), | |
| row('UI (vue/ts)', base?.ui, head.ui), | |
| '', | |
| base | |
| ? (regressed.length | |
| ? `**Gate: FAILED** - coverage regressed vs main:\n- ${regressed.join('\n- ')}` | |
| : `**Gate: passed** - no coverage regression (epsilon ${eps} pp).`) | |
| : '**Gate: informational** - no `main` baseline cached yet; will enforce from the next PR.', | |
| ]; | |
| const body = lines.join('\n'); | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.issue.number; | |
| const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number }); | |
| const existing = comments.find(c => c.body && c.body.includes('<!-- coverage-gate -->')); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); | |
| } else { | |
| await github.rest.issues.createComment({ owner, repo, issue_number, body }); | |
| } | |
| if (regressed.length) { | |
| core.setFailed(`Coverage regressed vs main: ${regressed.join('; ')}`); | |
| } |