-
Notifications
You must be signed in to change notification settings - Fork 199
268 lines (219 loc) · 8.28 KB
/
Copy pathci.yml
File metadata and controls
268 lines (219 loc) · 8.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
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 }}"