feat(logo): replace old logo with new INVOICESCANLOGO2.png and update… #46
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, develop] | |
| pull_request: | |
| branches: [main, develop] | |
| # Cancel in-progress runs for the same branch to save CI minutes | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| # ─── Reusable step fragment: pnpm setup ──────────────────────────────────────── | |
| # Defined once here as an anchor; referenced in each job. | |
| # GitHub Actions does not support YAML anchors natively, so the setup steps | |
| # are duplicated across jobs — this is intentional and normal practice. | |
| jobs: | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| # JOB 1 — Backend quality (lint, typecheck, unit tests, build, security) | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| quality: | |
| name: Backend — quality | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: latest | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT | |
| - name: Cache pnpm store | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Lint | |
| run: pnpm --filter backend lint | |
| - name: Typecheck | |
| run: pnpm --filter backend typecheck | |
| - name: Unit tests with coverage | |
| # Minimal env vars so NestJS config validation passes during test bootstrap. | |
| # No real DB/Redis needed — unit tests mock all external dependencies. | |
| env: | |
| NODE_ENV: test | |
| DATABASE_URL: postgresql://user:pass@localhost:5432/invoicescan_unit | |
| REDIS_URL: redis://localhost:6379 | |
| JWT_SECRET: test-jwt-secret-at-least-32-characters-long | |
| JWT_REFRESH_SECRET: test-refresh-secret-at-least-32-characters-x | |
| FRONTEND_URL: http://localhost:3001 | |
| run: pnpm --filter backend test:cov | |
| - name: Build | |
| run: pnpm --filter backend build | |
| - name: Secret scan | |
| uses: gitleaks/gitleaks-action@v2 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Audit dependencies | |
| run: pnpm audit --audit-level=high | |
| working-directory: packages/backend | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| # JOB 2 — Frontend quality (lint, typecheck, build, security) | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| frontend: | |
| name: Frontend — quality | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: latest | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT | |
| - name: Cache pnpm store | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Lint | |
| run: pnpm --filter frontend lint | |
| - name: Typecheck | |
| run: pnpm --filter frontend typecheck | |
| - name: Build | |
| env: | |
| # Baked into the Next.js bundle at build time | |
| NEXT_PUBLIC_API_URL: http://localhost:3000/api/v1 | |
| run: pnpm --filter frontend build | |
| - name: Audit dependencies | |
| run: pnpm audit --audit-level=high | |
| working-directory: packages/frontend | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| # JOB 3 — Integration tests (TypeORM repos against real Postgres + Redis) | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| integration: | |
| name: Backend — integration tests | |
| runs-on: ubuntu-latest | |
| needs: quality | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| env: | |
| POSTGRES_DB: invoicescan_test | |
| POSTGRES_USER: user | |
| POSTGRES_PASSWORD: pass | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd "pg_isready -U user -d invoicescan_test" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| redis: | |
| image: redis:7-alpine | |
| ports: | |
| - 6379:6379 | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| env: | |
| NODE_ENV: test | |
| DATABASE_URL: postgresql://user:pass@localhost:5432/invoicescan_test | |
| REDIS_URL: redis://localhost:6379 | |
| JWT_SECRET: ${{ secrets.CI_JWT_SECRET }} | |
| JWT_REFRESH_SECRET: ${{ secrets.CI_JWT_REFRESH_SECRET }} | |
| FRONTEND_URL: http://localhost:3001 | |
| PORT: 3000 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: latest | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT | |
| - name: Cache pnpm store | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build backend (required for migration:run) | |
| run: pnpm --filter backend build | |
| - name: Run migrations | |
| run: pnpm --filter backend migration:run | |
| - name: Run integration tests | |
| run: pnpm --filter backend test:integration | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| # JOB 4 — Backend E2E tests (Vitest + Supertest against full NestJS app) | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| e2e: | |
| name: Backend — E2E tests | |
| runs-on: ubuntu-latest | |
| needs: quality | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| env: | |
| POSTGRES_DB: invoicescan_e2e | |
| POSTGRES_USER: user | |
| POSTGRES_PASSWORD: pass | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd "pg_isready -U user -d invoicescan_e2e" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| redis: | |
| image: redis:7-alpine | |
| ports: | |
| - 6379:6379 | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| env: | |
| NODE_ENV: test | |
| DATABASE_URL: postgresql://user:pass@localhost:5432/invoicescan_e2e | |
| REDIS_URL: redis://localhost:6379 | |
| JWT_SECRET: ${{ secrets.CI_JWT_SECRET }} | |
| JWT_REFRESH_SECRET: ${{ secrets.CI_JWT_REFRESH_SECRET }} | |
| FRONTEND_URL: http://localhost:3001 | |
| PORT: 3000 | |
| # All Supertest requests share the same loopback IP, so the 5/min | |
| # production limit is exhausted immediately. Raise it for E2E. | |
| LOGIN_RATE_LIMIT: '100' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: latest | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT | |
| - name: Cache pnpm store | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Create uploads and exports directories | |
| run: mkdir -p packages/backend/uploads packages/backend/exports | |
| - name: Run E2E tests | |
| run: pnpm --filter backend test:e2e | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| # JOB 5 — Playwright UI E2E tests (real browser against full stack) | |
| # Runs after both quality and frontend jobs pass. | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| playwright: | |
| name: Playwright — UI E2E tests | |
| runs-on: ubuntu-latest | |
| needs: [quality, frontend] | |
| services: | |
| postgres: | |
| image: postgres:16-alpine | |
| env: | |
| POSTGRES_DB: invoicescan_playwright | |
| POSTGRES_USER: user | |
| POSTGRES_PASSWORD: pass | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd "pg_isready -U user -d invoicescan_playwright" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| redis: | |
| image: redis:7-alpine | |
| ports: | |
| - 6379:6379 | |
| options: >- | |
| --health-cmd "redis-cli ping" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| env: | |
| NODE_ENV: production | |
| DATABASE_URL: postgresql://user:pass@localhost:5432/invoicescan_playwright | |
| DATABASE_SSL: 'false' | |
| REDIS_URL: redis://localhost:6379 | |
| JWT_SECRET: ${{ secrets.CI_JWT_SECRET }} | |
| JWT_REFRESH_SECRET: ${{ secrets.CI_JWT_REFRESH_SECRET }} | |
| FRONTEND_URL: http://localhost:3001 | |
| # NEXT_PUBLIC_API_URL is baked at build time via --build-arg in the frontend step | |
| PORT: 3000 | |
| # BACKEND_URL used by Playwright global-setup to call the API directly | |
| # (without /api/v1 — the helper appends that path itself) | |
| BACKEND_URL: http://localhost:3000 | |
| # Playwright runs many tests that each need a login; raise the cap so | |
| # the test suite does not hit the 5-per-minute production limit. | |
| LOGIN_RATE_LIMIT: '100' | |
| # Use the deterministic LLM stub so invoices reach EXTRACTED status | |
| # without a real AISTUDIO_API_KEY. Workflow tests depend on this. | |
| AISTUDIO_STUB: 'true' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| version: latest | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT | |
| - name: Cache pnpm store | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Install Playwright browsers | |
| run: pnpm exec playwright install --with-deps chromium | |
| working-directory: packages/frontend | |
| - name: Create uploads and exports directories | |
| run: mkdir -p packages/backend/uploads packages/backend/exports | |
| - name: Build backend | |
| run: pnpm --filter backend build | |
| - name: Build frontend | |
| env: | |
| NEXT_PUBLIC_API_URL: http://localhost:3000/api/v1 | |
| run: pnpm --filter frontend build | |
| - name: Run database migrations | |
| # Run migrations before starting the backend so the DB schema is ready. | |
| # Uses the compiled dist/run-migrations.js produced by the build step. | |
| run: node packages/backend/dist/run-migrations.js | |
| - name: Seed demo users (required by Playwright global-setup) | |
| # The seed script creates admin@invoicescan.com and the three other | |
| # demo accounts. global-setup uses admin@invoicescan.com to bootstrap | |
| # Playwright-specific test users via the API. | |
| run: pnpm --filter backend seed | |
| - name: Start backend in background | |
| run: nohup pnpm --filter backend start:prod > /tmp/backend.log 2>&1 & | |
| - name: Start frontend in background | |
| env: | |
| NEXT_PUBLIC_API_URL: http://localhost:3000/api/v1 | |
| run: nohup pnpm --filter frontend start > /tmp/frontend.log 2>&1 & | |
| - name: Wait for backend to be healthy | |
| run: | | |
| for i in $(seq 1 30); do | |
| curl -sf http://localhost:3000/api/v1/health && echo "" && break || true | |
| echo "Waiting for backend ($i/30)..." | |
| sleep 3 | |
| done | |
| curl -sf http://localhost:3000/api/v1/health || (echo "Backend failed to start"; cat /tmp/backend.log; exit 1) | |
| - name: Wait for frontend to be ready | |
| run: | | |
| for i in $(seq 1 30); do | |
| curl -sf http://localhost:3001 && echo "" && break || true | |
| echo "Waiting for frontend ($i/30)..." | |
| sleep 3 | |
| done | |
| curl -sf http://localhost:3001 || (echo "Frontend failed to start"; cat /tmp/frontend.log; exit 1) | |
| - name: Run Playwright tests | |
| env: | |
| FRONTEND_URL: http://localhost:3001 | |
| BACKEND_URL: http://localhost:3000 | |
| # Demo admin credentials used by global-setup to bootstrap test users. | |
| # These match the values seeded by `pnpm --filter backend seed`. | |
| DEMO_ADMIN_EMAIL: admin@invoicescan.com | |
| DEMO_ADMIN_PASSWORD: Admin1234! | |
| run: pnpm --filter frontend test:e2e | |
| - name: Print backend logs on failure | |
| if: failure() | |
| run: cat /tmp/backend.log || true | |
| - name: Print frontend logs on failure | |
| if: failure() | |
| run: cat /tmp/frontend.log || true | |
| - name: Upload Playwright report | |
| uses: actions/upload-artifact@v4 | |
| if: always() | |
| with: | |
| name: playwright-report | |
| path: packages/frontend/playwright-report/ | |
| retention-days: 7 | |
| - name: Upload Playwright results on failure | |
| uses: actions/upload-artifact@v4 | |
| if: failure() | |
| with: | |
| name: playwright-results | |
| path: packages/frontend/playwright-results/ | |
| retention-days: 7 |