Merge remote-tracking branch 'origin/release' #193
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
| # CI workflow: build + unit tests on every push/PR. See deploy.yml for production deployment. | |
| name: CI – Build & Unit Tests | |
| on: | |
| push: | |
| branches: | |
| - main | |
| - develop | |
| - 'release' | |
| - 'bugfix/**' | |
| - 'feature/**' | |
| - 'hotfix/**' | |
| pull_request: | |
| branches: | |
| - main | |
| - develop | |
| - 'release' | |
| - 'bugfix/**' | |
| - 'feature/**' | |
| - 'hotfix/**' | |
| env: | |
| DOTNET_VERSION: '10.0.x' | |
| NODE_VERSION: '20.x' | |
| jobs: | |
| build-and-test-backend: | |
| name: Build & Test Backend (.NET) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up .NET | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: ${{ env.DOTNET_VERSION }} | |
| - name: Restore dependencies | |
| run: dotnet restore services/api/ServiceHub.sln | |
| - name: Build solution | |
| run: dotnet build services/api/ServiceHub.sln --no-restore --configuration Release | |
| - name: Run unit tests with coverage | |
| run: | | |
| dotnet test services/api/tests/ServiceHub.UnitTests \ | |
| --no-build \ | |
| --configuration Release \ | |
| --settings services/api/coverlet.runsettings \ | |
| --results-directory services/api/TestResults \ | |
| --logger "console;verbosity=normal" \ | |
| --logger "trx;LogFileName=results.trx" | |
| - name: Install report generator | |
| run: | | |
| dotnet tool install --global dotnet-reportgenerator-globaltool \ | |
| --version 5.4.3 2>/dev/null || true | |
| - name: Generate coverage report and enforce 60% threshold | |
| run: | | |
| reportgenerator \ | |
| -reports:"services/api/TestResults/**/coverage.cobertura.xml" \ | |
| -targetdir:"services/api/TestResults/CoverageReport" \ | |
| -reporttypes:"TextSummary" | |
| echo "=== Coverage Summary ===" | |
| cat services/api/TestResults/CoverageReport/Summary.txt | |
| COVERAGE=$(grep -oP 'Line coverage: \K[\d.]+' \ | |
| services/api/TestResults/CoverageReport/Summary.txt || echo "0") | |
| echo "Line coverage: ${COVERAGE}%" | |
| if awk "BEGIN {exit !($COVERAGE < 60)}"; then | |
| echo "❌ Coverage ${COVERAGE}% is below the required 60% threshold" | |
| exit 1 | |
| fi | |
| echo "✅ Coverage ${COVERAGE}% meets the 60% threshold" | |
| - name: Upload coverage report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-backend-${{ github.run_number }} | |
| path: services/api/TestResults/CoverageReport/ | |
| retention-days: 14 | |
| build-and-test-frontend: | |
| name: Build & Test Frontend (React) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: ${{ env.NODE_VERSION }} | |
| cache: 'npm' | |
| cache-dependency-path: apps/web/package-lock.json | |
| - name: Install dependencies | |
| working-directory: apps/web | |
| run: npm ci --include=optional | |
| - name: Audit npm packages for vulnerabilities | |
| working-directory: apps/web | |
| run: | | |
| echo "=== npm audit — checking for known vulnerabilities ===" | |
| # --audit-level=high: fail only on HIGH or CRITICAL severity | |
| # --omit=dev: skip dev dependencies (they don't run in production) | |
| npm audit --audit-level=high --omit=dev || { | |
| echo "" | |
| echo "❌ High/critical severity vulnerabilities found in production npm dependencies." | |
| echo " Run 'npm audit' locally to see details and 'npm audit fix' to fix automatically." | |
| exit 1 | |
| } | |
| echo "✅ No high/critical npm vulnerabilities in production dependencies" | |
| - name: Run unit tests with coverage | |
| working-directory: apps/web | |
| run: npm run test:coverage | |
| - name: Check frontend coverage threshold (60%) | |
| working-directory: apps/web | |
| run: | | |
| if [ ! -f "coverage/coverage-summary.json" ]; then | |
| echo "⚠ Coverage file not found — skipping threshold check" | |
| exit 0 | |
| fi | |
| # Extract total line coverage from vitest coverage JSON | |
| COVERAGE=$(node -e " | |
| const c = require('./coverage/coverage-summary.json'); | |
| const total = c.total || c; | |
| const pct = total.lines?.pct ?? total.lines?.percent ?? 0; | |
| console.log(pct); | |
| " 2>/dev/null || echo "0") | |
| echo "Frontend line coverage: ${COVERAGE}%" | |
| if awk "BEGIN {exit !($COVERAGE < 60)}"; then | |
| echo "❌ Frontend coverage ${COVERAGE}% is below 60%" | |
| exit 1 | |
| fi | |
| echo "✅ Frontend coverage ${COVERAGE}% meets the 60% threshold" | |
| - name: Upload frontend coverage | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-frontend-${{ github.run_number }} | |
| path: apps/web/coverage/ | |
| retention-days: 14 | |
| - name: Type check with TypeScript | |
| working-directory: apps/web | |
| run: npx tsc -b | |
| - name: Verify production env file exists | |
| run: | | |
| if [ ! -f apps/web/.env.production ]; then | |
| echo "ERROR: apps/web/.env.production is missing. Create it with VITE_API_BASE_URL=/api/v1" | |
| exit 1 | |
| fi | |
| - name: Build application | |
| working-directory: apps/web | |
| run: npm run build | |
| ci-status: | |
| name: CI Status | |
| runs-on: ubuntu-latest | |
| needs: [build-and-test-backend, build-and-test-frontend] | |
| if: always() | |
| steps: | |
| - name: Check build and test status | |
| run: | | |
| if [ "${{ needs.build-and-test-backend.result }}" != "success" ]; then | |
| echo "❌ Backend build/tests failed" | |
| exit 1 | |
| fi | |
| if [ "${{ needs.build-and-test-frontend.result }}" != "success" ]; then | |
| echo "❌ Frontend build/tests failed" | |
| exit 1 | |
| fi | |
| echo "✅ All builds and unit tests passed!" |