diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e88384be..dad5ed36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,26 +4,67 @@ on: push: pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: - changes: + frontend-ci: runs-on: ubuntu-latest - outputs: - backend: ${{ steps.filter.outputs.backend }} + timeout-minutes: 20 steps: - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 id: filter with: filters: | - backend: - - 'backend/**' - - 'go.work' - - 'go.work.sum' + app: + - 'app/**' + - 'components/**' + - 'lib/**' + - 'middleware.ts' + - 'next.config.mjs' + - 'package.json' + - 'tsconfig.json' + - 'vitest.config.ts' + - '*.ts' + - '*.tsx' + + - uses: actions/setup-node@v4 + if: steps.filter.outputs.app == 'true' || github.ref == 'refs/heads/main' + with: + node-version: 20 + + - uses: actions/cache@v4 + if: steps.filter.outputs.app == 'true' || github.ref == 'refs/heads/main' + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + + - name: Install dependencies + if: steps.filter.outputs.app == 'true' || github.ref == 'refs/heads/main' + run: npm ci - backend: - needs: changes - if: needs.changes.outputs.backend == 'true' + - name: Lint + if: steps.filter.outputs.app == 'true' || github.ref == 'refs/heads/main' + run: npm run lint + + - name: Typecheck + if: steps.filter.outputs.app == 'true' || github.ref == 'refs/heads/main' + run: npx tsc --noEmit + + - name: Test + if: steps.filter.outputs.app == 'true' || github.ref == 'refs/heads/main' + run: npx vitest run + + - name: Build + if: steps.filter.outputs.app == 'true' || github.ref == 'refs/heads/main' + run: npm run build + + backend-ci: runs-on: ubuntu-latest + timeout-minutes: 20 defaults: run: working-directory: backend @@ -41,34 +82,66 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + backend: + - 'backend/**' + - '.golangci.yml' + - 'go.work' + - 'go.work.sum' + - uses: actions/setup-go@v5 + if: steps.filter.outputs.backend == 'true' || github.ref == 'refs/heads/main' with: - go-version: '1.25' + go-version: '1.24' + + - uses: actions/cache@v4 + if: steps.filter.outputs.backend == 'true' || github.ref == 'refs/heads/main' + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - name: Download dependencies + if: steps.filter.outputs.backend == 'true' || github.ref == 'refs/heads/main' run: go mod download - - name: Run SQLite unit tests - run: go test ./... -count=1 -timeout 60s - - - name: Run PostgreSQL integration tests - run: go test -tags integration ./internal/infrastructure/... -count=1 -timeout 120s - env: - DB_TYPE: postgres - DB_DSN: postgres://postgres:postgres@localhost:5432/testdb?sslmode=disable + - name: Vet + if: steps.filter.outputs.backend == 'true' || github.ref == 'refs/heads/main' + run: go vet ./... - name: golangci-lint + if: steps.filter.outputs.backend == 'true' || github.ref == 'refs/heads/main' uses: golangci/golangci-lint-action@v6 with: version: v2.1 working-directory: backend args: --timeout=5m - - name: Upload coverage artifact - uses: actions/upload-artifact@v4 - with: - name: coverage - path: backend/coverage.out + - name: Test with race detector + if: steps.filter.outputs.backend == 'true' || github.ref == 'refs/heads/main' + run: go test -race -coverprofile=coverage.out -covermode=atomic ./... + + - name: Run PostgreSQL integration tests + if: steps.filter.outputs.backend == 'true' || github.ref == 'refs/heads/main' + run: go test -tags integration ./internal/infrastructure/... -count=1 -timeout 120s + env: + DB_TYPE: postgres + DB_DSN: postgres://postgres:postgres@localhost:5432/testdb?sslmode=disable + + - name: Check coverage threshold + if: steps.filter.outputs.backend == 'true' || github.ref == 'refs/heads/main' + run: | + total=$(go tool cover -func=coverage.out | awk '/^total:/ {gsub(/%/,"",$3); print $3}') + awk -v total="$total" 'BEGIN { exit (total >= 60 ? 0 : 1) }' || { + echo "Coverage ${total}% below 60% threshold" + exit 1 + } + + - name: Build + if: steps.filter.outputs.backend == 'true' || github.ref == 'refs/heads/main' + run: go build ./cmd/server compat-tests: runs-on: ubuntu-latest @@ -77,7 +150,7 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: '1.25' + go-version: '1.24' - name: Run compatibility tests run: cd backend && go test ./internal/compat/... -v -count=1 -timeout 120s 2>&1 | tee compat-results.txt