Skip to content

feat(logo): replace old logo with new INVOICESCANLOGO2.png and update… #46

feat(logo): replace old logo with new INVOICESCANLOGO2.png and update…

feat(logo): replace old logo with new INVOICESCANLOGO2.png and update… #46

Workflow file for this run

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