chore(main): release 0.22.3 #1813
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: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| # CI only reads code — no releases or package pushes. | |
| permissions: | |
| contents: read | |
| jobs: | |
| # ── Job 1: All linters ────────────────────────────────────────────────── | |
| lint: | |
| runs-on: ubuntu-latest | |
| # Extra scopes are needed by EnricoMi/publish-unit-test-result-action so | |
| # it can post the sticky comment + checks summary on pull requests. The | |
| # workflow-level default is contents:read; per-job permissions replace | |
| # rather than merge, so contents:read is re-listed below. | |
| permissions: | |
| contents: read | |
| checks: write | |
| pull-requests: write | |
| issues: read | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| # mise provisions this job's slice of the toolchain from mise.toml — | |
| # go, node, task, golangci-lint (built from source with this Go so it | |
| # accepts the project's go directive — a prebuilt binary on an older Go | |
| # would reject it), shfmt, shellcheck, ruff, jq, go-junit-report — and | |
| # loads mise.toml [env] (SPECTRAL_VERSION, …, GOTOOLCHAIN=local) into | |
| # $GITHUB_ENV regardless of install_args. Replaces setup-go + the | |
| # tool-versions.env grep + every per-tool `go install`/pipx step. | |
| - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 | |
| with: | |
| install_args: >- | |
| go node task ruff shfmt shellcheck jq | |
| go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint | |
| go:github.com/jstemmer/go-junit-report/v2 | |
| # Wails v3's desktop code imports a CGo WebKit2GTK webview, so the default | |
| # build tags need these system libs to typecheck on the Ubuntu runner. | |
| - uses: ./.github/actions/linux-webview-deps | |
| # Frontend — set up Node and build frontend/dist before Go linting so the | |
| # //go:embed all:frontend/dist directive in assets.go passes type-checking, | |
| # AND so the bundle-size budget step below can measure the real artifacts. | |
| - name: Prepare frontend/dist (real Vite build) | |
| uses: ./.github/actions/prepare-frontend-dist | |
| with: | |
| real-assets: "true" | |
| # Bundle-size budget. Catches accidental regressions (e.g. someone | |
| # imports a 200KB library by mistake). Two budgets: | |
| # - Initial chunk (index-*.{js,css}): what every page-load pays. | |
| # The four view components are lazy-loaded via | |
| # defineAsyncComponent so each becomes a separate chunk that | |
| # isn't counted here — only the masthead/modal/router-shell | |
| # code lands in index-*. | |
| # - Total assets/: catches the case where lazy-splitting masks a | |
| # real regression by smearing it across chunks. | |
| # Bump deliberately when a real feature needs the room. | |
| # Bundle-size budget. Logic lives in scripts/ci/check-bundle-size.sh | |
| # so the lefthook pre-push hook can reuse the same assertion — | |
| # contributors fail locally before push instead of finding out | |
| # from a red CI badge. Per-chunk + total caps live in the script | |
| # as defaults; override via env var if a one-off needs room. | |
| - name: Enforce bundle-size budget | |
| run: bash scripts/ci/check-bundle-size.sh | |
| - name: Lint Go (default build tags) | |
| run: golangci-lint run ./... | |
| - name: Lint Go (serveronly build tag) | |
| run: golangci-lint run --build-tags serveronly ./... | |
| # GOOS=windows lints the //go:build windows files (HideWindow, | |
| # probe_windows, exec_windows) the Linux-runner GOOS skips. Pure-Go, | |
| # so cross-GOOS analysis just swaps the stdlib view — no compilation. | |
| - name: Lint Go (windows GOOS, default build tags) | |
| run: GOOS=windows golangci-lint run ./... | |
| - name: Lint Go (windows GOOS, serveronly build tag) | |
| run: GOOS=windows golangci-lint run --build-tags serveronly ./... | |
| # Frontend linters — deps already installed above | |
| - name: Lint JavaScript/Vue (ESLint) | |
| run: cd frontend && npm run lint:js | |
| - name: Lint CSS (Stylelint) | |
| run: cd frontend && npm run lint:css | |
| - name: Lint HTML (HTMLHint) | |
| run: cd frontend && npm run lint:html | |
| # Python — ruff lint + format-check. ruff comes from mise (pinned in | |
| # mise.toml [tools]); no pipx install needed. | |
| - name: Lint Python (ruff) | |
| run: task lint-py | |
| # Shell scripts in scripts/ — shellcheck for correctness, shfmt for | |
| # style. Both come from mise (pinned in mise.toml), so no install step. | |
| - name: Lint shell scripts (shellcheck + shfmt) | |
| run: task lint-shell | |
| # Dockerfile | |
| - name: Lint Dockerfile (Hadolint) | |
| uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 | |
| with: | |
| dockerfile: Dockerfile.build | |
| # OpenAPI spec — Spectral with the spectral:oas ruleset + project | |
| # overrides in .spectral.yaml. SPECTRAL_VERSION comes from mise.toml | |
| # [env] (loaded into $GITHUB_ENV by mise-action). | |
| - name: Lint OpenAPI (Spectral) | |
| run: task lint-openapi | |
| # Spell-check code, docs, and identifiers. The action ships the | |
| # typos binary so the runner doesn't need a separate install | |
| # step; config lives in _typos.toml at the repo root. | |
| - name: Spell-check (typos) | |
| uses: crate-ci/typos@37bb98842b0d8c4ffebdb75301a13db0267cef89 # v1.47.2 | |
| # Markdown lint — mirrors `task lint-md`. Config in | |
| # .markdownlint-cli2.yaml at the repo root; npx pulls the | |
| # version on demand so no global install is required. | |
| - name: Lint Markdown (markdownlint-cli2) | |
| run: npx --yes markdownlint-cli2 | |
| # GitHub Actions workflow lint — catches deprecated inputs, | |
| # syntax errors, and shellcheck issues in `run:` blocks. The | |
| # action ships the actionlint binary so no install step. | |
| - name: Lint workflows (actionlint) | |
| uses: raven-actions/actionlint@3d39aea434753780c3b3d4a1a31c854b4dbf49d7 # v2 | |
| # Enforce the project's SHA-pinning policy for third-party | |
| # GitHub Actions. A tag-pinned action could be silently | |
| # re-pointed to a malicious commit; see CONTRIBUTING.md | |
| # → "Pinning GitHub Actions". | |
| - name: Enforce SHA-pinned actions | |
| run: bash scripts/ci/check-action-pins.sh | |
| # Go unit tests (merge logic, inference helpers, screenshotType | |
| # classification). Parser golden-file tests are skipped by -short. | |
| # Tee through go-junit-report so the XML is always produced — the | |
| # final pass/fail decision is delegated to the EnricoMi step below | |
| # via `fail_on: test failures` so a fork PR without comment perms | |
| # still surfaces failures via the workflow conclusion. | |
| # Test-skip inventory gate. Diffs every `t.Skip*` line under | |
| # pkg/ against `scripts/ci/test-skips-allow.txt`. A new skip | |
| # without an allow-list entry fails CI — flake-suppression | |
| # skips are forbidden; the allow-list is for documented | |
| # environment gates (OS-conditional probe tests, `-short`-mode | |
| # tesseract integration). Mirrors the lefthook pre-push | |
| # `test-skips` hook so the gate fires both locally and in CI. | |
| - name: Inventory test skips | |
| run: bash scripts/ci/check-test-skips.sh | |
| - name: Run Go unit tests | |
| id: gotest | |
| # continue-on-error so BOTH test steps run and the JUnit XML always | |
| # uploads + EnricoMi always posts the "Unit test results" check. The | |
| # real pass/fail is enforced by the "Fail the job on any unit-test | |
| # failure" gate at the end of this job (reads `.outcome`) — so a | |
| # failure, including a -race-only one, reds the workflow run, not just | |
| # the check-run. | |
| continue-on-error: true | |
| run: | | |
| set -o pipefail | |
| go test -race -short -v ./... 2>&1 \ | |
| | tee go-test.log \ | |
| | go-junit-report -set-exit-code > go-junit.xml | |
| # Frontend unit tests (Vitest) — pure-helper coverage for the | |
| # extracted match-helpers.ts module. `--reporter=default` keeps the | |
| # log readable in the Actions run; `--reporter=junit` adds the XML | |
| # consumed by the EnricoMi step. The `--outputFile.junit=` form | |
| # scopes the path to the junit reporter only, so the default | |
| # reporter still writes to stdout. | |
| - name: Run frontend unit tests | |
| id: fetest | |
| continue-on-error: true | |
| run: | | |
| cd frontend | |
| npx vitest run \ | |
| --reporter=default \ | |
| --reporter=junit \ | |
| --outputFile.junit=../frontend-junit.xml | |
| # Upload the JUnit XML so the coverage-comment job can fold its | |
| # numbers + per-failure details into the single combined PR | |
| # comment. `if: always()` so the artifact still uploads when a | |
| # test step exited non-zero (which is the normal path on a | |
| # failing test — `continue-on-error: true` makes the step succeed, | |
| # but EnricoMi below is what flips the workflow to red). | |
| - name: Upload unit-test JUnit XML | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: unit-test-results | |
| path: | | |
| go-junit.xml | |
| frontend-junit.xml | |
| if-no-files-found: warn | |
| retention-days: 7 | |
| # EnricoMi posts the "Unit test results" check (red via `fail_on: | |
| # test failures` when the JUnit XML carries a failure) — that's the | |
| # per-PR/commit status + the detailed breakdown. It does NOT fail the | |
| # workflow run itself, so the explicit `.outcome` gate at the end of | |
| # this job is what reds CI. Its own commenter is OFF — the | |
| # coverage-comment job renders one combined sticky comment. | |
| - name: Publish unit-test results check | |
| if: always() | |
| uses: EnricoMi/publish-unit-test-result-action@d0a4676d0e0b938bc201470d88276b7c74c712b3 # v2 | |
| with: | |
| check_name: Unit test results | |
| comment_mode: off | |
| files: | | |
| go-junit.xml | |
| frontend-junit.xml | |
| fail_on: test failures | |
| # TypeScript type-check — confirms api.ts agrees with the | |
| # OpenAPI-generated types in api.gen.d.ts. | |
| - name: TypeScript type-check | |
| run: cd frontend && npm run typecheck | |
| # Verify the committed types match the current OpenAPI spec. | |
| # Catches "you forgot to run `task gen-types` after editing | |
| # api/openapi.yaml" before it lands on main. | |
| - name: Verify generated types are in sync with OpenAPI spec | |
| run: | | |
| task gen-types | |
| if ! git diff --quiet frontend/src/api.gen.d.ts; then | |
| echo "::error::frontend/src/api.gen.d.ts is out of sync with api/openapi.yaml — run 'task gen-types' and commit" | |
| git diff frontend/src/api.gen.d.ts | head -50 | |
| exit 1 | |
| fi | |
| # Hard-fail CI on any unit-test failure. The two test steps are | |
| # continue-on-error (so JUnit always uploads + EnricoMi always posts its | |
| # check), which means their failure alone only reds the "Unit test | |
| # results" check — the workflow run stays green. Gate on their real | |
| # `.outcome` here so a failing test, including one that only fails under | |
| # `-race`, turns this job (and the CI run) red directly. Last step in the | |
| # job so every other check still runs first. | |
| - name: Fail the job on any unit-test failure | |
| if: always() && (steps.gotest.outcome == 'failure' || steps.fetest.outcome == 'failure') | |
| env: | |
| GO_OUTCOME: ${{ steps.gotest.outcome }} | |
| FE_OUTCOME: ${{ steps.fetest.outcome }} | |
| run: | | |
| echo "::error::Unit tests failed (Go=${GO_OUTCOME}, frontend=${FE_OUTCOME}). See the 'Unit test results' check and the test-run step logs." | |
| exit 1 | |
| # ── Job 2: Linux/amd64 Wails app ──────────────────────────────────────── | |
| build-linux: | |
| runs-on: ubuntu-latest | |
| env: | |
| DOCKER_BUILDKIT: "1" | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 | |
| # Docker does the heavy build; the host only needs task + jq (the | |
| # build-* tasks compute BUILD_VERSION via jq), so install just those. | |
| - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 | |
| with: | |
| install_args: task jq | |
| - name: Build Linux/amd64 Wails app | |
| run: task build-linux | |
| # ── Job 3: Windows/amd64 Wails app ────────────────────────────────────── | |
| build-windows: | |
| # Native cross-compile (no Docker) — v3's Windows WebView2 loader is pure Go, | |
| # so this needs wails3 + node + makensis, not buildx. | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| # Building the wails3 CLI itself (mise/go install) pulls in v3's CGo GTK4 | |
| # webview, so the Ubuntu runner needs the WebKit dev libs — even though the | |
| # Windows target is CGO_ENABLED=0. | |
| - uses: ./.github/actions/linux-webview-deps | |
| - uses: ./.github/actions/wails-build-env | |
| - name: Install makensis | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y --no-install-recommends nsis | |
| - name: Build Windows/amd64 Wails app + NSIS installer | |
| run: task build-windows | |
| # ── Job 4: Server binaries + container image ───────────────────────────── | |
| build-server: | |
| runs-on: ubuntu-latest | |
| env: | |
| DOCKER_BUILDKIT: "1" | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4 | |
| - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 | |
| with: | |
| install_args: task jq | |
| - name: Build all server binaries (Linux, Windows, macOS arm64+amd64) | |
| run: task build-server-all | |
| - name: Build Linux server container image (with Tesseract) | |
| run: task build-server-container | |
| # ── Job 5: macOS Wails build (requires Apple SDK / Xcode CLT) ──────────── | |
| build-mac: | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| # Go + Node + Wails CLI (+ task, jq) from mise.toml's pins. | |
| - uses: ./.github/actions/wails-build-env | |
| - name: Build macOS universal Wails app | |
| run: task build-mac | |
| # ── Job 6: Vulnerability scan ──────────────────────────────────────────── | |
| # Mirrors `task trivy` but runs in CI and uploads results to the GitHub | |
| # Security tab (Alerts → Code scanning) as well as failing the build. | |
| trivy: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - name: Run Trivy vulnerability scan | |
| uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 | |
| with: | |
| scan-type: fs | |
| scan-ref: . | |
| scanners: vuln | |
| severity: HIGH,CRITICAL | |
| exit-code: '1' | |
| format: sarif | |
| output: trivy.sarif | |
| - name: Upload Trivy results to GitHub Security tab | |
| if: always() | |
| uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4 | |
| with: | |
| sarif_file: trivy.sarif | |
| # ── Job 7: Go-specific vulnerability scan ──────────────────────────────── | |
| # govulncheck is call-graph-aware: it only flags CVEs in code paths Recall | |
| # actually invokes. Complements Trivy (which scans all module versions | |
| # regardless of reachability) and typically produces fewer false positives. | |
| govulncheck: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| # go + govulncheck from mise.toml (govulncheck built with this Go). | |
| - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 | |
| with: | |
| install_args: go go:golang.org/x/vuln/cmd/govulncheck | |
| # Wails v3's desktop code imports a CGo WebKit2GTK webview — the default | |
| # build tags need these system libs to build on the Ubuntu runner. | |
| - uses: ./.github/actions/linux-webview-deps | |
| # Wrapped in a retry that fires ONLY on a transient vuln.go.dev fetch | |
| # error (the DB download occasionally times out on the runner) — a real | |
| # CVE finding still fails immediately. See scripts/ci/govulncheck-retry.sh. | |
| - name: Scan Go modules (default build tag) | |
| run: bash scripts/ci/govulncheck-retry.sh ./... | |
| - name: Scan Go modules (serveronly build tag) | |
| run: bash scripts/ci/govulncheck-retry.sh -tags serveronly ./... | |
| # gosec (Go SAST) is rolled into Job 1's golangci-lint (the `gosec` linter, | |
| # run on both build tags). It no longer needs its own job / tool install. | |
| # govulncheck stays separate: it's call-graph + CVE-database driven, which | |
| # golangci-lint has no equivalent for. | |
| # JS/TS SAST in CI is handled by CodeQL's javascript-typescript | |
| # language matrix in .github/workflows/codeql.yml. Semgrep runs | |
| # locally only — via `task lint-semgrep` and the pre-push lefthook | |
| # hook — so contributors get fast feedback before push without | |
| # duplicating coverage with CodeQL. | |
| # ── Job 8: Cyclomatic complexity (REPORT ONLY) ─────────────────────────── | |
| # gocyclo (Go) + ESLint's `complexity` rule (frontend) at threshold 10. | |
| # `continue-on-error: true` keeps the job NON-BLOCKING — a high number | |
| # never fails the build, it just shows up in the job log. We watch the | |
| # trend across PRs to know when to refactor; tightening into a hard | |
| # gate is its own deliberate decision, not a side-effect of this job. | |
| # Threshold + tooling pins both live in scripts/ci/check-complexity.sh + | |
| # mise.toml (gocyclo). | |
| complexity: | |
| runs-on: ubuntu-latest | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| # go (gocyclo) + node (eslint pass) from mise.toml. | |
| - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 | |
| with: | |
| install_args: go node go:github.com/fzipp/gocyclo/cmd/gocyclo | |
| # gocyclo doesn't need real frontend assets — Go source only. | |
| - name: Prepare frontend/dist (stub for //go:embed) | |
| uses: ./.github/actions/prepare-frontend-dist | |
| with: | |
| real-assets: "false" | |
| - name: Install frontend deps (for the eslint pass) | |
| run: npm --prefix frontend ci | |
| - name: Run complexity sweep (Go + frontend, threshold 10) | |
| run: bash scripts/ci/check-complexity.sh | |
| # Diff the current Go scores against the checked-in top-20 | |
| # baseline. Same threshold, same exclusions; emits a summary | |
| # line + per-jump/newcomer record. continue-on-error keeps the | |
| # job non-blocking; a future PR-comment step can hook into | |
| # this output once the baseline shape stabilises. | |
| - name: Diff against baseline (report-only) | |
| run: bash scripts/ci/check-complexity-baseline.sh | |
| # ── Job 9: Dead code analysis ──────────────────────────────────────────── | |
| # deadcode does whole-program call-graph analysis for Go (serveronly variant | |
| # only — the Wails variant registers App methods via reflection so deadcode | |
| # would report false positives; golangci-lint `unused` covers that variant). | |
| # knip scans the frontend for unused TypeScript exports and stale deps. | |
| dead-code: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| # go (+ deadcode) and node (+ knip) from mise.toml. | |
| - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 | |
| with: | |
| install_args: go node go:golang.org/x/tools/cmd/deadcode | |
| - name: Install frontend dependencies | |
| run: cd frontend && npm ci | |
| - name: Build frontend (required for Go embed typecheck) | |
| run: cd frontend && npm run build | |
| - name: Dead Go code (serveronly build tag) | |
| # Delegates to scripts/ci/deadcode-check.sh; the allow-list of | |
| # intentional unreachables lives in scripts/ci/deadcode-allow.txt | |
| # and is shared with Make + lefthook so all three call sites | |
| # stay in sync. | |
| run: bash scripts/ci/deadcode-check.sh | |
| - name: Dead TypeScript code (knip) | |
| run: cd frontend && npm run dead:ts | |
| # ── Job 10: Frontend coverage report ───────────────────────────────────── | |
| # Informational only — no thresholds enforced. The HTML report and lcov | |
| # file are uploaded as a workflow artifact so they can be downloaded from | |
| # the Actions run page for any push or PR. | |
| coverage-frontend: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 | |
| with: | |
| install_args: node | |
| - name: Install frontend dependencies | |
| run: cd frontend && npm ci | |
| - name: Generate coverage report | |
| run: cd frontend && npm run test:coverage | |
| # Surface line + branch coverage (V8 reports real branch coverage) in the | |
| # job summary from the Cobertura rates. Informational; the gate is | |
| # vitest.config.ts coverage.thresholds. | |
| - name: Coverage summary (line + branch) | |
| run: | | |
| xml=frontend/coverage/cobertura-coverage.xml | |
| line=$(grep -oE 'line-rate="[0-9.]+"' "$xml" | head -1 | grep -oE '[0-9.]+') | |
| branch=$(grep -oE 'branch-rate="[0-9.]+"' "$xml" | head -1 | grep -oE '[0-9.]+') | |
| { | |
| echo "### Frontend coverage (unit)" | |
| echo "" | |
| echo "| Metric | Coverage |" | |
| echo "|---|---:|" | |
| awk -v l="$line" -v b="$branch" 'BEGIN { printf "| Line | %.1f%% |\n| Branch | %.1f%% |\n", l*100, b*100 }' | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Upload coverage report | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: frontend-coverage | |
| path: frontend/coverage/ | |
| retention-days: 30 | |
| # ── Job 11: Go coverage report ─────────────────────────────────────────── | |
| # Informational only — no thresholds enforced. The HTML report and func | |
| # summary are uploaded as a workflow artifact so they can be downloaded | |
| # from the Actions run page for any push or PR. | |
| coverage-go: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| # go + gocover-cobertura (Cobertura XML converter) from mise.toml. | |
| - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 | |
| with: | |
| install_args: go task go:github.com/boumenot/gocover-cobertura | |
| # Wails v3's desktop code imports a CGo WebKit2GTK webview — the default | |
| # build tags need these system libs to build on the Ubuntu runner. | |
| - uses: ./.github/actions/linux-webview-deps | |
| # Frontend build is required because assets.go uses | |
| # //go:embed all:frontend/dist — compiling the root `recall` | |
| # package (via `go test ./...`) fails if dist/ is missing. | |
| - name: Prepare frontend/dist (real Vite build) | |
| uses: ./.github/actions/prepare-frontend-dist | |
| with: | |
| real-assets: "true" | |
| - name: Generate Go coverage report | |
| run: task cover-go | |
| # gocover-cobertura converts Go's native coverprofile into the | |
| # Cobertura XML format that irongut/CodeCoverageSummary parses. | |
| # Bundled into the artifact so the coverage-comment job downloads | |
| # both formats in a single fetch. | |
| - name: Convert coverage profile to Cobertura XML | |
| run: gocover-cobertura < coverage/go/coverage.out > coverage/go/cobertura.xml | |
| # Surface line + branch coverage in the job summary. Go has no native | |
| # branch coverage, so "branch" is basic-block coverage (covered/total | |
| # blocks in the coverprofile) — its branch-equivalent. Mirrors e2e.yml's | |
| # coverage summary; informational (the func total above is what gates). | |
| - name: Coverage summary (line + branch) | |
| run: | | |
| line=$(awk '/^total:/ { gsub(/%/,"",$NF); print $NF }' coverage/go/coverage.txt) | |
| block=$(awk 'NR>1 { t++; if ($NF+0>0) c++ } END { if (t) printf "%.1f", 100*c/t }' coverage/go/coverage.out) | |
| { | |
| echo "### Go coverage (unit)" | |
| echo "" | |
| echo "| Metric | Coverage |" | |
| echo "|---|---:|" | |
| echo "| Line (statement) | ${line}% |" | |
| echo "| Branch (basic-block) | ${block}% |" | |
| echo "" | |
| echo "_Go has no native branch coverage; basic-block coverage is its branch-equivalent._" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Upload coverage report | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: go-coverage | |
| path: coverage/go/ | |
| retention-days: 30 | |
| # ── Job 12: OpenAPI ↔ implementation drift check ───────────────────────── | |
| # Spectral checks the spec is well-formed; schemathesis checks the spec | |
| # matches the running server. Fuzzes spec-derived requests against a | |
| # freshly-built serveronly binary and verifies every response conforms to | |
| # the declared schema. Catches the "I added a route but forgot to update | |
| # api/openapi.yaml" class of bug that Spectral can't see. | |
| schemathesis: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| # go (build serveronly) + schemathesis (pipx) from mise.toml; also loads | |
| # TESSERACT_VERSION + SCHEMATHESIS_VERSION from [env] into $GITHUB_ENV. | |
| - uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0 | |
| with: | |
| install_args: go task pipx:schemathesis | |
| - name: Prepare frontend/dist (real Vite build) | |
| uses: ./.github/actions/prepare-frontend-dist | |
| with: | |
| real-assets: "true" | |
| - name: Install Tesseract (no-op for API surface but Startup checks for it) | |
| # Assert installed major.minor matches TESSERACT_VERSION from mise.toml | |
| # [env] (loaded into $GITHUB_ENV by mise-action) so OCR drift can't | |
| # silently land between what dev baselined the goldens against and what | |
| # CI runs. Loud-fail when Ubuntu bumps the package version — that's the | |
| # signal to re-baseline goldens + bump the pin. | |
| run: | | |
| sudo apt-get update -y | |
| sudo apt-get install -y --no-install-recommends tesseract-ocr | |
| installed=$(tesseract --version 2>&1 | head -1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) | |
| expected_mm=$(printf '%s' "$TESSERACT_VERSION" | cut -d. -f1-2) | |
| installed_mm=$(printf '%s' "$installed" | cut -d. -f1-2) | |
| if [ "$installed_mm" != "$expected_mm" ]; then | |
| echo "::error::Tesseract major.minor mismatch: installed=$installed_mm expected=$expected_mm (TESSERACT_VERSION=$TESSERACT_VERSION in mise.toml). Bump the pin + re-baseline testdata/*.golden.json." | |
| exit 1 | |
| fi | |
| echo "Tesseract $installed matches pinned major.minor $expected_mm" | |
| - name: Build + boot + fuzz + teardown via shared script | |
| # scripts/ci/check-api-drift.sh handles the build-serveronly → | |
| # boot-on-isolated-tempdir → schemathesis-run → teardown | |
| # sequence. Same script lefthook's pre-push schemathesis hook | |
| # invokes so the local + CI signals stay 1:1. The script | |
| # short-circuits with an actionable error if schemathesis or | |
| # frontend/dist are missing. | |
| run: SKIP_FRONTEND_BUILD=1 bash scripts/ci/check-api-drift.sh |