CI/CD Pipeline
Forja uses GitHub Actions for continuous integration. The pipeline runs on every push to main and on every pull request targeting main.
Pipeline Overview
The CI configuration lives at .github/workflows/ci.yml and defines two parallel jobs. A separate security scan workflow (.github/workflows/security-scan.yml) runs vulnerability scanning -- see Security Scanning below.
┌─────────────────────────────────────────┐
│ CI Pipeline │
├─────────────────┬───────────────────────┤
│ Backend (Rust) │ Admin (React) │
│ │ │
│ 1. Checkout │ 1. Checkout │
│ 2. Rust setup │ 2. Node.js 20 setup │
│ 3. Cache deps │ 3. npm install │
│ 4. Format check│ 4. Type check │
│ 5. Clippy lint │ 5. ESLint │
│ 6. Init DB │ 6. Tests │
│ 7. Unit tests │ │
│ 8. Integ tests │ │
└─────────────────┴───────────────────────┘
Both jobs run in parallel. The pipeline passes only when both jobs succeed.
Backend Job
The backend job runs on ubuntu-latest with a PostgreSQL 16 service container.
Service Container
A PostgreSQL 16 Alpine container starts automatically with health checks:
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: forja
POSTGRES_PASSWORD: forja
POSTGRES_DB: forja
options: >-
--health-cmd "pg_isready -U forja"
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432
Steps
-
Checkout -- Uses
actions/checkout@v4. -
Install Rust toolchain -- Uses
dtolnay/rust-toolchain@stablewithrustfmtandclippycomponents. -
Cache cargo registry and build -- Uses
actions/cache@v4to cache~/.cargo/registry,~/.cargo/git, andbackend/target. The cache key is based onCargo.lock. -
Check formatting --
cargo fmt --checkfails the build if any file is not properly formatted. -
Lint with Clippy --
cargo clippy -- -D warningstreats all warnings as errors. -
Initialize databases -- Creates the required PostgreSQL extensions on both the main and test databases:
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";CREATE EXTENSION IF NOT EXISTS "citext";CREATE EXTENSION IF NOT EXISTS "pg_trgm";Also creates a separate
forja_testdatabase for integration tests. -
Run unit tests --
cargo test --libruns all unit tests (no database required). -
Run integration tests --
cargo test --test integration_testsruns tests against the test database usingTEST_DATABASE_URL.
Environment Variables
env:
DATABASE_URL: postgres://forja:forja@localhost:5432/forja
TEST_DATABASE_URL: postgres://forja:forja@localhost:5432/forja_test
Admin Job
The admin job runs on ubuntu-latest with Node.js 20.
Steps
-
Checkout -- Uses
actions/checkout@v4. -
Setup Node.js -- Uses
actions/setup-node@v4with Node.js 20. -
Install dependencies --
npm installin theadmin/directory. -
Type check --
npm run typecheckruns the TypeScript compiler in check mode. -
Lint --
npm run lintruns ESLint. -
Run tests --
npm testruns the Vitest test suite.
Caching Strategy
The backend job caches three directories:
| Path | Purpose |
|---|---|
~/.cargo/registry | Downloaded crate sources |
~/.cargo/git | Git-based dependencies |
backend/target | Compiled artifacts |
The cache key is ${{ runner.os }}-cargo-${{ hashFiles('backend/Cargo.lock') }}, so the cache is invalidated whenever dependencies change. A restore key (${{ runner.os }}-cargo-) provides a fallback to the most recent cache.
Triggers
on:
push:
branches: [main]
pull_request:
branches: [main]
The pipeline runs on:
- Every push to
main(direct pushes and merged pull requests). - Every pull request targeting
main(opened, synchronized, reopened).
Adding New Checks
To add a new CI step, edit .github/workflows/ci.yml. For example, to add an admin build check:
- name: Build admin
run: npm run build
For security-related checks, add them to .github/workflows/security-scan.yml instead.
Security Scanning
A separate workflow (.github/workflows/security-scan.yml) runs automated vulnerability scanning across the full stack. It triggers on pushes to main, pull requests, and on a weekly schedule (Mondays at 06:00 UTC).
┌──────────────────────────────────────────────────┐
│ Security Scan Pipeline │
├────────────────┬────────────────┬────────────────┤
│ cargo-audit │ npm audit │ Trivy │
│ │ │ │
│ Rust dep CVEs │ Node.js dep │ Filesystem │
│ via RustSec │ CVEs (high+) │ HIGH+CRITICAL │
│ advisory DB │ admin/ + │ full repo │
│ │ libs/* │ │
└────────────────┴────────────────┴────────────────┘
cargo-audit
Scans Rust dependencies against the RustSec Advisory Database. Runs in the backend/ directory using the configuration at backend/.cargo/audit.toml, which can suppress known advisories with justification.
npm audit
Runs npm audit --omit=dev --audit-level=high in admin/ and all libs/ packages. Only production dependencies are checked; dev-only vulnerabilities are excluded.
Trivy
Trivy performs a filesystem scan of the entire repository, catching vulnerabilities across languages as well as secrets or misconfigurations. Only HIGH and CRITICAL severity issues with available fixes cause the pipeline to fail.
Path Filtering
The security scan only runs when relevant files change (e.g., backend/**, admin/**, libs/**, docs/**), keeping CI fast for documentation-only PRs.
React Doctor (PR only)
React Doctor runs as an additional check on pull requests to enforce frontend component health. It checks for unnecessary re-renders, missing memoization, unstable references, and component complexity.
The CI step uses the millionco/react-doctor@main GitHub Action and runs only on pull requests targeting main. The score must be 100 — any score below is a blocking failure.
Local equivalent:
cd admin && npm run react-doctor:online
Run this before submitting a PR if you changed any files in admin/src/. See Quality Gates for common fixes and scoring details.
Running CI Checks Locally
Use the dev-test.sh script to run the same checks locally before pushing:
# Run all checks (matches CI)
./scripts/dev-test.sh
# Include integration tests (requires running database)
./scripts/dev-test.sh --integration