Skip to content

Merge pull request #946 from Raven062/fix/W2-B-027-yield-accounting-i… #986

Merge pull request #946 from Raven062/fix/W2-B-027-yield-accounting-i…

Merge pull request #946 from Raven062/fix/W2-B-027-yield-accounting-i… #986

Workflow file for this run

name: CI
on:
push:
branches: [main, dev, develop]
pull_request:
branches: [main, dev, develop]
jobs:
lint-and-format:
name: Lint, Format & Security Audit
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
with:
version: "10.33.1"
- uses: actions/setup-node@v7
with:
node-version: "22"
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run security audit
run: pnpm audit --audit-level high
- name: Run non-blocking audit report
if: always()
run: pnpm audit || true
- name: Run ESLint
run: pnpm lint
- name: Check Prettier formatting
run: pnpm format:check
test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 20
env:
# Minimal env vars so Jest can load modules without connecting to real infra.
# All external calls are mocked in tests.
DATABASE_URL: postgresql://test:test@localhost:5432/test
MONGODB_URI: mongodb://localhost:27017/test
RABBITMQ_URL: amqp://localhost:5672
JWT_SECRET: ci-test-secret-not-used-in-real-traffic
NODE_ENV: test
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
with:
version: "10.33.1"
- uses: actions/setup-node@v7
with:
node-version: "22"
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Verify bcrypt native build
run: node -e "require('bcrypt')"
- name: Run tests
run: pnpm exec jest --ci --coverage
- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v7
with:
name: coverage
path: coverage/
retention-days: 7
build:
name: Build
runs-on: ubuntu-latest
timeout-minutes: 20
needs: [lint-and-format, test]
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
with:
version: "10.33.1"
- uses: actions/setup-node@v7
with:
node-version: "22"
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Verify bcrypt native build
run: node -e "require('bcrypt')"
- name: Generate Prisma client
run: pnpm prisma:generate
- name: Build TypeScript
run: pnpm build
- name: Upload build artifact
uses: actions/upload-artifact@v7
with:
name: dist
path: dist/
retention-days: 7
license-check:
name: License Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6
with:
version: "10.33.1"
- uses: actions/setup-node@v7
with:
node-version: "20"
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Install license-checker
run: npm install -g license-checker
- name: Check licenses
run: license-checker --production --failOn "GPL;AGPL;LGPL;MPL;CPL;EPL;OSL" --summary
validate-migrations:
name: Validate Migrations
runs-on: ubuntu-latest
timeout-minutes: 10
needs: build
# Only run on pull requests — push to main/dev triggers deploy, not re-validation
if: github.event_name == 'pull_request'
# Fresh, ephemeral Postgres provisioned for THIS job run only.
# Migrations never touch shared/staging databases — GitHub destroys the
# service container as soon as the job finishes, so every run starts
# from an empty database and full isolation is guaranteed.
env:
DATABASE_URL: postgresql://acbu_ci:acbu_ci_pw@localhost:5432/acbu_ci
steps:
- uses: actions/checkout@v7
with:
# Fetch full history so the destructive-migration gate can diff
# schema.prisma against the pull request base commit
fetch-depth: 0
- name: Start local dev infrastructure
env:
POSTGRES_USER: acbu_ci
POSTGRES_PASSWORD: acbu_ci_pw
POSTGRES_DB: acbu_ci
MONGO_USER: acbu_ci
MONGO_PASSWORD: acbu_ci_pw
MONGO_DB: acbu_ci_cache
RABBITMQ_USER: acbu_ci
RABBITMQ_PASSWORD: acbu_ci_pw
run: docker compose up -d --wait
- uses: pnpm/action-setup@v6
with:
version: "10.33.1"
- uses: actions/setup-node@v7
with:
node-version: "22"
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Create shadow database for reproducibility check
# `prisma migrate diff --from-migrations` rebuilds the schema purely from the
# migration files into an empty shadow database, then compares it to the deployed
# database. The superuser provisioned by the service container can create it.
run: >
node -e "const{Pool}=require('pg');(async()=>{const p=new Pool({connectionString:process.env.DATABASE_URL});await p.query('CREATE DATABASE acbu_ci_shadow');await p.end();console.log('shadow database created');})().catch(e=>{console.error(e.message);process.exit(1);})"
- name: Block destructive migrations without label
# Shared, unit-tested implementation (see tests/destructiveMigrationGate.test.ts).
# Detects DROP TABLE / DROP COLUMN / TRUNCATE in changed migration files AND
# destructive changes implied by prisma/schema.prisma edits, then requires the
# 'allow-destructive-migration' label to proceed. This is the reversibility gate:
# irreversible schema changes must be acknowledged explicitly.
run: node scripts/ci/check-destructive-migrations.js
- name: Apply all migrations to the isolated database
# Proves every migration applies cleanly from an empty database on a fresh,
# ephemeral instance. A broken migration fails here in isolation instead of
# corrupting shared/staging databases or blocking unrelated PRs.
run: pnpm prisma:migrate:deploy
- name: Verify migration history is reproducible (reversibility check)
# Rebuild the schema from the migration files into the empty shadow database and
# confirm it matches the database produced by `migrate deploy`. This proves the
# migration chain is self-consistent and reproducible — a prerequisite for safe,
# reversible migrations — without depending on unrelated drift between
# prisma/schema.prisma and the live migration history.
run: >
pnpm exec prisma migrate diff
--from-migrations prisma/migrations
--to-url "$DATABASE_URL"
--shadow-database-url "postgresql://acbu_ci:acbu_ci_pw@localhost:5432/acbu_ci_shadow"
--exit-code
docker-build-publish:
name: Build and Publish Docker Image
runs-on: ubuntu-latest
needs: build
# Only publish images on pushes to the main/dev branches, not on PRs
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/develop')
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v7
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=sha
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Output image digest
run: echo "Image pushed with digest ${{ steps.build-and-push.outputs.digest }}"