1+ name : Server CI
2+
3+ on :
4+ push :
5+ branches : [main]
6+ pull_request :
7+ branches : [main]
8+
9+ concurrency :
10+ group : server-ci-${{ github.ref }}
11+ cancel-in-progress : true
12+
13+ jobs :
14+ server :
15+ name : Server quality gates
16+ runs-on : ubuntu-latest
17+ defaults :
18+ run :
19+ working-directory : server
20+
21+ steps :
22+ - name : Checkout
23+ uses : actions/checkout@v4
24+
25+ - name : Set up pnpm
26+ uses : pnpm/action-setup@v4
27+ with :
28+ version : 10.28.0
29+
30+ - name : Set up Node.js
31+ uses : actions/setup-node@v4
32+ with :
33+ node-version : 20
34+ cache : pnpm
35+ cache-dependency-path : pnpm-lock.yaml
36+
37+ - name : Cache server dependencies and TypeScript build cache
38+ uses : actions/cache@v4
39+ with :
40+ path : |
41+ server/node_modules
42+ server/tsconfig.tsbuildinfo
43+ key : server-ci-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'server/tsconfig.json') }}
44+ restore-keys : |
45+ server-ci-${{ runner.os }}-
46+
47+ - name : Install dependencies
48+ working-directory : .
49+ run : pnpm install --frozen-lockfile
50+
51+ - name : Prepare test environment
52+ run : cp .env.test .env
53+
54+ - name : Lint
55+ run : pnpm lint
56+
57+ - name : Type-check
58+ run : pnpm tsc --noEmit
59+
60+ - name : Test
61+ id : tests
62+ continue-on-error : true
63+ env :
64+ NODE_ENV : test
65+ DOTENV_CONFIG_PATH : .env.test
66+ NODE_OPTIONS : --require=dotenv/config
67+ run : pnpm test -- --run --json --outputFile=test-results.json
68+
69+ - name : Convert Jest results to JUnit XML
70+ if : always()
71+ run : |
72+ node - <<'NODE'
73+ const fs = require('fs');
74+ const results = JSON.parse(fs.readFileSync('test-results.json', 'utf8'));
75+ const escape = (value) => String(value)
76+ .replaceAll('&', '&')
77+ .replaceAll('<', '<')
78+ .replaceAll('>', '>')
79+ .replaceAll('"', '"');
80+ const cases = (results.testResults || []).map((test) => {
81+ const duration = test.endTime && test.startTime ? test.endTime - test.startTime : 0;
82+ const failure = test.status === 'failed'
83+ ? `<failure message="${escape((test.message || '').split('\n')[0])}">${escape(test.message || '')}</failure>`
84+ : '';
85+ return `<testcase classname="${escape(test.testFilePath || 'server')}" name="${escape(test.testFilePath || 'test')}" time="${(duration / 1000).toFixed(3)}">${failure}</testcase>`;
86+ }).join('');
87+ const failures = results.numFailedTestSuites || 0;
88+ const tests = results.numTotalTestSuites || cases.length;
89+ fs.writeFileSync('test-results.xml', `<testsuites tests="${tests}" failures="${failures}">${cases}</testsuites>`);
90+ NODE
91+
92+ - name : Upload JUnit test results
93+ if : always()
94+ uses : actions/upload-artifact@v4
95+ with :
96+ name : server-junit-results
97+ path : server/test-results.xml
98+ if-no-files-found : error
99+
100+ - name : Build
101+ run : pnpm build
102+
103+ - name : Fail when tests fail
104+ if : steps.tests.outcome == 'failure'
105+ run : exit 1
0 commit comments