-
Notifications
You must be signed in to change notification settings - Fork 85
293 lines (259 loc) · 10.7 KB
/
Copy pathci.yml
File metadata and controls
293 lines (259 loc) · 10.7 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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
issues: write
pull-requests: write
jobs:
# ----------------------------------------------------------------
# Per-workspace lint + typecheck + test, run in parallel. Each
# matrix entry runs on its own runner so the wall time drops from
# the previous ~15 min (6 sequential lints + 6 sequential
# typechecks + 4 sequential test:cov + lockfile checks) to roughly
# the slowest single workspace. The actions/setup-node `cache: npm`
# hint means each matrix entry's `npm ci` is near-instant on cache
# hits.
#
# The matrix drives WHICH scripts run (lint, typecheck, test:cov)
# AND whether the workspace needs the test Docker database. The
# postgres service in `services:` is therefore started on every
# matrix entry — a few seconds of unused-postgres overhead per entry
# in exchange for a single simplified workflow shape.
# ----------------------------------------------------------------
quality:
name: Quality (${{ matrix.workspace.label }})
runs-on: ubuntu-latest
# Cancel any in-flight job for the same branch / workspace on a new
# commit so force-pushes don't queue up.
concurrency:
group: ci-quality-${{ matrix.workspace.label }}-${{ github.ref }}
cancel-in-progress: true
strategy:
fail-fast: false
matrix:
workspace:
# id-only workspaces first so they fail fast; the integration
# suites are last so they get the longest wall-clock budget.
# `needs_tests: false` skips the `npm run test:cov` step and
# the Codecov upload — these workspaces don't ship Jest suites
# (types is compile-only; contracts is shared schema/zod
# definitions whose provider/consumer verification lives in
# the api and sdk matrices respectively).
- {
label: types,
path: packages/types,
needs_db: false,
needs_tests: false,
}
- {
label: contracts,
path: tests/contracts,
needs_db: false,
needs_tests: false,
}
- {
label: sdk,
path: xstreamroll-sdk,
needs_db: false,
needs_tests: true,
}
- {
label: processing,
path: xstreamroll-processing,
needs_db: false,
needs_tests: true,
}
- { label: app, path: app, needs_db: false, needs_tests: true }
- { label: api, path: api, needs_db: true, needs_tests: true }
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 2s
--health-timeout 2s
--health-retries 15
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
# Install ALL workspace dependencies in one shot via the root
# `npm ci`. npm itself does not support `npm ci` inside an
# individual workspace (the lockfile lives at the repo root for
# workspaces in this repo), so a single root install is the
# documented path (#375).
- name: Install workspace dependencies
run: npm ci
- name: Build types package
run: npm run build --workspace=packages/types
- name: Build contract tests package
run: npm run build --workspace=tests/contracts
- name: Lint — ${{ matrix.workspace.label }}
run: cd ${{ matrix.workspace.path }} && npm run lint
env:
# Database-bound lint config (api) reads DATABASE_URL; give
# it the same postgres service that the test step will use.
DATABASE_URL: postgresql://test:test@localhost:5432/test
JWT_SECRET: test-secret
STREAM_API_KEY: test-api-key
- name: Typecheck — ${{ matrix.workspace.label }}
run: cd ${{ matrix.workspace.path }} && npm run typecheck
- name: Start test database
if: matrix.workspace.needs_db
run: docker compose -f docker-compose.test.yml up -d --wait
# Issue #340 — exercise the migration runner against the freshly
# provisioned test DB before the api test suite runs. The test DB
# starts empty (no schema.sql autoload), so this also confirms the
# migration files together reproduce the schema end-to-end. Runs
# the idempotent "up" migration; the runner tracks applied
# migrations in `pgmigrations` so reruns against a polluted DB
# stay safe.
- name: Run database migrations (Issue #340)
if: matrix.workspace.needs_db
run: cd api && npm run migrate
env:
DATABASE_URL: postgresql://xstreamroll:xstreamroll@localhost:5433/xstreamroll_test
# Provider verification for tests/contracts/ runs as part of this
# step (api/src/contract-provider.spec.ts matches the api Jest
# config's `*.spec.ts` pattern) — a contract violation fails the
# build the same way any other api test failure would. Consumer
# contract verification (xstreamroll-sdk/__tests__/contract.consumer.test.ts)
# runs in the sdk matrix entry.
- name: Test — ${{ matrix.workspace.label }} (with coverage)
if: matrix.workspace.needs_tests
run: cd ${{ matrix.workspace.path }} && npm run test:cov
env:
DATABASE_URL: postgresql://xstreamroll:xstreamroll@localhost:5433/xstreamroll_test
JWT_SECRET: test-secret
STREAM_API_KEY: test-api-key
- name: Stop test database
if: always() && matrix.workspace.needs_db
run: docker compose -f docker-compose.test.yml down -v
- name: Upload ${{ matrix.workspace.label }} coverage to Codecov
uses: codecov/codecov-action@v4
if: always() && matrix.workspace.needs_tests
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ${{ matrix.workspace.path }}/coverage/lcov.info
flags: ${{ matrix.workspace.label }}
name: ${{ matrix.workspace.label }}-coverage
fail_ci_if_error: false
# ----------------------------------------------------------------
# Lockfile-drift guard (issue #375). Runs `npm ci` once at the repo
# root and confirms no `package-lock.json` was mutated by the
# install. Lives in its own job so it can run in parallel with the
# quality matrix — readers get the result faster — and so a failure
# is reported independently of any specific workspace's lint/test
# results. The samePaths list keeps the guard future-proof: any
# workspace that gains a workspace-local lockfile later MUST be
# added here.
# ----------------------------------------------------------------
lockfile:
name: Verify lockfile integrity
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install workspace dependencies
run: npm ci
- name: Verify lockfile integrity
run: bash .github/scripts/verify-lockfiles.sh
# ----------------------------------------------------------------
# k8s/70-network-policies.yaml validator (issue #357). Runs WITHOUT
# a cluster: parses the YAML against documented invariants (see
# scripts/validate-network-policies.js for the full list). The
# validator fails CI on a regression that would otherwise break
# production traffic — e.g., a deleted allow rule or a typo'd
# podSelector label — instead of waiting for the rollout.
# ----------------------------------------------------------------
validate-k8s:
name: Validate k8s NetworkPolicies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install workspace dependencies
run: npm ci
- name: Validate NetworkPolicies
run: npm run validate:network-policies
bundle-analysis:
runs-on: ubuntu-latest
needs: quality
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- name: Install workspace dependencies
run: npm ci
- name: Build types package
run: npm run build --workspace=packages/types
- name: Build app with analysis
run: cd app && npm run analyze
- name: List analyzer output
run: ls -la app/.next || true; ls -la app/.next/analyze || true
- name: Check bundle sizes
run: node .github/scripts/check-bundle-size.js
env:
BUNDLE_BUDGET_TOTAL: 5000000
BUNDLE_BUDGET_PER_LARGEST: 500000
- name: Upload analyzer artifacts
uses: actions/upload-artifact@v4
with:
name: app-bundle-analysis
path: app/.next/analyze
- name: Upload bundle summary
uses: actions/upload-artifact@v4
with:
name: app-bundle-summary
path: app/.next/analyze/summary.json
# Same lockfile-drift invariant as the `lockfile` job (#375). The
# bundle-analysis install path runs again here, so any accidental
# mutation to a package-lock.json must also fail this job rather
# than silently propagate.
- name: Verify lockfile integrity
run: bash .github/scripts/verify-lockfiles.sh
- name: Comment PR with summary
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
uses: actions/github-script@v6
continue-on-error: true
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');
const path = 'app/.next/analyze/summary.json';
if (fs.existsSync(path)) {
const summary = JSON.parse(fs.readFileSync(path, 'utf8'));
const body = `Bundle analysis summary:\n\n- Total JS size: ${summary.totalBytes} bytes\n- Largest file: ${summary.largestFile} (${summary.largestBytes} bytes)\n\nArtifacts: app-bundle-analysis`;
github.rest.issues.createComment({
issue_number: context.payload.pull_request.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
});
} else {
console.log('No summary available')
}