Repository navigation
356 lines (316 loc) · 14.1 KB
/
Copy pathci.yml
File metadata and controls
356 lines (316 loc) · 14.1 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
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# Runs on every push and pull request.
#
# The suite already had 8,296 tests when this was added, and 19 of them were
# failing because nothing ever ran them. Tests that no machine runs are
# documentation, and stale documentation at that. This is the gate that keeps
# a contributor's pull request honest without a maintainer having to check by
# hand.
name: CI
on:
push:
branches: [main, develop]
pull_request:
# Release calls this so a tag is gated on exactly what a pull request meets.
# It used to run the API unit suite alone, which meant a release could ship
# with drifted API docs, a lint error, or - worst - a module missing from the
# packaged app, because the check that catches that only ran here.
workflow_call:
# A second push to the same branch makes the first run irrelevant.
#
# The caller is part of the group, and has to be. This workflow runs both on
# its own and as a called workflow - release.yml and beta.yml both use it so a
# tag or a test build is gated on exactly what a pull request meets. A called
# workflow keeps the caller's ref, so with the ref alone the standalone run and
# the called one landed in the same group and cancelled each other: pushing to
# main killed CI four seconds in, every time, and the run that survived was
# whichever started second.
#
# github.workflow is the caller's name - "CI" when this runs on its own,
# "Test build (main)" when beta.yml calls it - so this separates them while
# still cancelling a superseded push within each.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# The clock the suite runs on, set before Node starts.
#
# A runner is in UTC and the people writing these tests are not, so an
# assertion like "14:05 UTC formats as 07:35 pm" passed for its author and
# failed here, saying nothing about the code. Pinned to the timezone this
# product assumes everywhere else - the documented default for a branch that
# has not set one.
#
# It has to be here rather than in a Jest setup file. Node caches the zone when
# a process starts in UTC and will not re-resolve it afterwards, so setting
# process.env.TZ from inside the suite works on a developer's machine and
# silently does nothing on the runner - which is the one place it was needed.
env:
TZ: Asia/Kolkata
# puppeteer arrives as a transitive dependency (whatsapp-web.js) and its
# postinstall downloads a whole Chrome from Google's CDN - which 403s
# often enough to fail unrelated commits at `npm ci`. No CI job launches
# a browser, so the download is pure risk with no benefit.
PUPPETEER_SKIP_DOWNLOAD: "1"
# Least privilege for GITHUB_TOKEN.
#
# Without this the token takes the repository default, which on a public repo
# is more than any of these jobs needs. None of them writes to GitHub - they
# build, test, upload an artifact or deploy outward - so read is the ceiling.
# Releases are created by release.yml, which asks for contents: write there.
permissions:
contents: read
jobs:
attribution:
name: Human attribution guard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Reject software tools credited as contributors
run: npm run check:attribution
api-tests:
name: API unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
cache-dependency-path: api/package-lock.json
- name: Install
working-directory: api
run: npm ci
# --forceExit because a few suites leave handles open; that is worth
# fixing, but it should not be what blocks the suite from running at all.
#
# jest.ci.config.js skips the suites listed in jest.known-failures.js,
# which are failing today and not yet fixed. Everything else still has to
# pass, so a new break still fails the build - the quarantine covers
# today's failures and nothing more. `npm run test:known-failures` shows
# where those stand.
- name: Unit tests
working-directory: api
run: |
npx jest -c jest.ci.config.js --ci --forceExit --coverageReporters=text-summary --coverage 2>&1 | tee ../.jest-api.log
exit ${PIPESTATUS[0]}
- name: Mobile POS integration
working-directory: api
env:
MOBILE_FULL_APP: "1"
run: node --test tests/mobile-pos.integration.cjs
- name: Captain pairing and delivery authority integration
working-directory: api
run: node --test tests/captain-access.integration.cjs
# The README carries a test count, a coverage figure and an endpoint
# count. All three are hardcoded and all three drift; the readiness audit
# found the test badge already stale. This checks them against what just
# ran, so a public number cannot quietly become untrue. The test claim is
# a floor, so it fails when a suite silently stops running rather than
# every time a test is added.
- name: README numbers are still true
run: |
api_passed=$(grep -oE 'Tests:.*[0-9]+ passed' .jest-api.log | grep -oE '[0-9]+ passed' | grep -oE '[0-9]+' | tail -1)
echo "API passed: ${api_passed}"
node scripts/check-badges.js --api="${api_passed}" --coverage-from=.jest-api.log
- name: Upload coverage
if: always()
uses: actions/upload-artifact@v7
with:
name: api-coverage
path: api/coverage
retention-days: 14
if-no-files-found: ignore
rest-api:
name: REST API against a real database
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
cache-dependency-path: api/package-lock.json
- name: Install
working-directory: api
run: npm ci
# The rest of the suite mocks the database, which is why a unique index
# could stop a shop creating its second branch without a single test
# noticing: a mocked collection has no indexes, so the collision it
# exists to cause cannot happen. These boot the real server against a
# real MongoDB and talk to it over HTTP.
#
# The URI is a secret and never appears in this file. Without it the
# tests skip and say why, rather than passing quietly - a green tick
# that means nothing is worse than a job that is not there.
#
# On a release tag that is not good enough. A skip is invisible in the
# summary, so the one job that talks to a real database could quietly not
# run on the only build that reaches shops. Releases fail instead, and
# say what to do about it.
#
# Checking the secret is non-empty is not enough either. A cluster whose
# allowlist excludes the runner, a user scoped to one database, or a free
# cluster that has auto-paused all pass that check and then fail as
# something that reads like a code problem. The preflight connects,
# writes, reads back and drops - the same things the tests do - and names
# the setting to change.
- name: A release must not skip this
if: startsWith(github.ref, 'refs/tags/v')
working-directory: api
env:
CI_MONGODB_URI: ${{ secrets.CI_MONGODB_URI }}
run: node scripts/check-ci-database.js
- name: REST smoke tests
working-directory: api
env:
CI_MONGODB_URI: ${{ secrets.CI_MONGODB_URI }}
run: npx jest tests/api --ci --forceExit --runInBand
api-docs:
name: API docs are current
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
cache-dependency-path: api/package-lock.json
- name: Install
working-directory: api
run: npm ci
# Generated documentation is only worth having if it cannot drift. This
# regenerates and fails if the result differs from what was committed, so
# a new endpoint cannot land undocumented.
- name: Regenerate and compare
working-directory: api
run: npm run docs:check
- name: Validate the OpenAPI spec
working-directory: api
run: npm run docs:lint
lint:
# NOT reporting only, whatever it used to be. Both steps below gate, and a
# red one here fails Full CI - which the desktop build workflow needs, so a
# stray trailing space stops an installer being produced. The old name said
# the opposite and cost a release: the failure was read as noise and left.
name: Lint and formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
cache: npm
cache-dependency-path: api/package-lock.json
- name: Install
working-directory: api
run: npm ci
# This reported without gating while roughly 400 errors sat in the tree -
# a check that is red on every pull request teaches people to ignore every
# check. The errors are at zero now, so it gates: a new one fails the
# build on the pull request that introduced it, which is the only moment
# it is cheap to fix.
#
# Warnings still do not fail. There are around 1,600, almost all
# no-console, and they are tracked in eslint.config.js rather than here.
- name: ESLint
working-directory: api
run: npx eslint src tests
# Formatting gates now that it is clean. It could not before: 348 files
# disagreed with the formatter, so a contributor who ran `npm run format`
# produced an unreviewable diff and one who did not produced drift. The
# one-off reformat is its own commit; from here the check keeps it clean.
- name: Prettier
working-directory: api
run: npm run format:check
desktop-tests:
name: Desktop tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
# These once needed no dependencies, which is why nothing was installed
# here. The lock screen and the item-entry tests drive real markup in
# jsdom, so they do - and without this the whole job failed in seconds on
# "Cannot find module" while every test passed locally.
#
# --omit=optional skips the Electron download, which none of these need.
- name: Install
run: npm ci --omit=optional --ignore-scripts
- name: Install API dependencies used by desktop contract tests
working-directory: api
run: npm ci
# The rollback drill releases the REAL artifact - the manifest signed
# over an actually-built frontend/public - so the suite needs the pages
# built first. Same steps as deploy-frontend.yml. Without this the drill
# fails on every run and the job has been red since it landed.
- name: Build the pages (the drill releases the real artifact)
working-directory: frontend
run: |
npm ci --no-audit --no-fund
npm run build:assets
- name: Startup, hardware, printing and lock tests
run: npm test
packaging:
name: Packaging checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
# Linux software centers and AppImage catalogs read this file before a
# user installs Posnic. Validate it with the reference implementation so
# a typo cannot silently remove the app identity, screenshot or links.
- name: AppStream metadata is valid
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends -y appstream
appstreamcli validate --pedantic builds/linux/com.posnic.app.metainfo.xml
# A module that is required but never packaged produces "Cannot find
# module" on a user's machine and nowhere else. This has happened, so it
# is checked here rather than after an installer ships.
- name: Every required module is packaged
run: node scripts/check-packaged-modules.js
- name: Install API dependencies
working-directory: api
run: npm ci
# The shipped archive leaves development packages out. This asserts that
# nothing the API requires at runtime is among them, because that failure
# only appears on a customer machine.
- name: No runtime require depends on a dev package
run: node scripts/check-runtime-deps.js
# And nothing depends on a package it never declared. `mongodb` was
# required by 56 API files and declared by none of them - it resolved only
# because mongoose hoists it, so a mongoose upgrade could have changed the
# driver underneath the API without a line of this repo changing.
- name: Every required package is declared
run: node scripts/check-declared-deps.js
# npm audit reports two moderate advisories that cannot be reached from
# any code path here, so the command is permanently non-zero - and a check
# that is always red is one nobody reads. This fails only on an advisory
# nobody has judged yet, and on an accepted one whose review date has
# passed, so an exception cannot become permanent by neglect.
- name: No unreviewed production advisories
run: node scripts/check-advisories.js
secrets:
name: No secrets in history
runs-on: ubuntu-latest
steps:
# The whole history, not the checked-out tip. Publishing a repository
# publishes every commit at once, and a password added in one commit and
# removed in the next is still in the pack every clone receives. It cannot
# be recalled afterwards - people fork and mirror within minutes.
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Scan every blob ever committed
run: node scripts/scan-git-history.js