Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/scripts/go-test-summary.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# Runs the Go suite once, appends pass/fail/skip counts and total statement
# coverage to the GitHub Actions job summary, and exits with the suite's own
# status so a red test still fails the job. Arg $1: optional label (e.g. the OS)
# appended to the heading. Falls back to stdout when run outside Actions.
set -uo pipefail

label="${1:+ — $1}"
json="$(mktemp)"
cover="$(mktemp)"

go test -json -coverprofile="$cover" ./... >"$json"
code=$?

read -r pass fail skip < <(jq -rs '
map(select(.Test != null and (.Action == "pass" or .Action == "fail" or .Action == "skip")))
| [ (map(select(.Action == "pass")) | length),
(map(select(.Action == "fail")) | length),
(map(select(.Action == "skip")) | length) ] | @tsv
' "$json")

total="$(go tool cover -func="$cover" | awk '/^total:/ {print $3}')"

summary="${GITHUB_STEP_SUMMARY:-/dev/stdout}"
{
echo "### Backend (Go)${label}"
echo ""
echo "| Passed | Failed | Skipped | Coverage |"
echo "|-------:|-------:|--------:|---------:|"
echo "| ${pass:-0} | ${fail:-0} | ${skip:-0} | ${total:-n/a} |"
echo ""
if [ "${fail:-0}" != "0" ]; then
echo "<details><summary>Failed tests</summary>"
echo ""
jq -rs 'map(select(.Action == "fail" and .Test != null)) | .[] | "- `\(.Package) \(.Test)`"' "$json" | sort -u
echo ""
echo "</details>"
fi
} >>"$summary"

exit "$code"
31 changes: 31 additions & 0 deletions .github/scripts/web-test-summary.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Runs the frontend (Vitest) suite with coverage, appends file/pass/fail counts
# and line coverage to the GitHub Actions job summary, and exits with the
# suite's own status. node_modules must already be installed. Falls back to
# stdout when run outside Actions.
set -uo pipefail

cd "$(dirname "$0")/../../frontend"
out="$(mktemp)"

pnpm exec vitest run --coverage --reporter=default --reporter=json --outputFile.json="$out"
code=$?

read -r files passed failed < <(jq -r '
[ (.testResults | length), .numPassedTests, .numFailedTests ] | @tsv
' "$out")

cov="$(jq -r '.total.lines.pct' coverage/coverage-summary.json 2>/dev/null || echo n/a)"
[ "$cov" != "n/a" ] && cov="${cov}%"

summary="${GITHUB_STEP_SUMMARY:-/dev/stdout}"
{
echo "### Frontend (Vitest)"
echo ""
echo "| Files | Passed | Failed | Coverage (lines) |"
echo "|------:|-------:|-------:|-----------------:|"
echo "| ${files:-0} | ${passed:-0} | ${failed:-0} | ${cov:-n/a} |"
echo ""
} >>"$summary"

exit "$code"
61 changes: 61 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: ci

# Fast per-PR gate: format/vet + both suites, with pass/fail counts and
# coverage rendered into the job summary. Windows backend is validated at
# release time (release.yml); a PR runs the linux path only for quick feedback.
on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod

- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
package_json_file: frontend/package.json

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
cache-dependency-path: frontend/pnpm-lock.yaml

# dist must exist before any Go step: the backend go:embeds frontend/dist,
# so it will not even compile without it. build also runs tsc (typecheck).
- name: Frontend install + build
working-directory: frontend
run: |
pnpm install --frozen-lockfile
pnpm build

- name: gofmt
run: |
unformatted="$(gofmt -l .)"
if [ -n "$unformatted" ]; then
echo "::error::gofmt: these files need formatting:"
echo "$unformatted"
exit 1
fi

- name: go vet
run: go vet ./...

- name: Frontend tests
run: bash .github/scripts/web-test-summary.sh

- name: Backend tests
run: bash .github/scripts/go-test-summary.sh linux
12 changes: 8 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,19 @@ jobs:

# Frontend first: the backend embeds frontend/dist (go:embed), so Go
# tests only compile once the frontend has been built.
- name: Frontend install + tests + build
- name: Frontend install + build
working-directory: frontend
run: |
pnpm install --frozen-lockfile
pnpm test
pnpm build

# Both suites emit pass/fail counts + coverage into the job summary and
# still fail the job on a red test (shared with ci.yml).
- name: Frontend tests
run: bash .github/scripts/web-test-summary.sh

- name: Backend tests
run: go test ./...
run: bash .github/scripts/go-test-summary.sh linux

- name: Build Linux packages
run: task package
Expand Down Expand Up @@ -120,7 +124,7 @@ jobs:
run: task package:windows

- name: Backend tests (windows)
run: go test ./...
run: bash .github/scripts/go-test-summary.sh windows

- name: Assemble Windows assets
run: |
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ bin
.idea/
frontend/dist
frontend/node_modules
frontend/coverage
16 changes: 16 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ task package:windows # bin/lich-setup.exe installer (needs Inno Setup 6 — CI/

Frontend in isolation: `cd frontend && pnpm run build` (runs `tsc` + `vite build`).

## Local Gate (before every commit / PR)

Run the whole check locally before pushing a commit or opening a PR — never lean
on CI to find what a local run catches in seconds:

- `gofmt -l .` clean (fix with `gofmt -w .`) and `go vet ./...` clean.
- `go test ./...` (backend) and `cd frontend && pnpm test` (frontend) green —
or `task test` for both at once.
- `cd frontend && pnpm build` succeeds (tsc typecheck + vite).

CI mirrors exactly these: `ci.yml` runs them on every PR and push to `main`,
`release.yml` on a tag. Both render pass/fail counts and coverage into the
Actions job summary (`.github/scripts/*-test-summary.sh`), so the numbers are
visible per run rather than buried in the log. A red test fails the job — the
summary is transparency, never a substitute for the gate.

---

## Hard Invariants
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^6.0.0",
"@vitest/coverage-v8": "4.1.10",
"tailwindcss": "^4.3.2",
"typescript": "^5.2.2",
"vite": "^8.0.5",
Expand Down
Loading
Loading