77
88permissions :
99 contents : read
10+ pull-requests : write
1011
1112concurrency :
1213 group : ci-${{ github.ref }}
1314 cancel-in-progress : true
1415
1516jobs :
16- test :
17- name : Test (Node ${{ matrix.node }})
17+ ci :
18+ name : Test & Coverage
1819 runs-on : ubuntu-latest
19- strategy :
20- fail-fast : false
21- matrix :
22- node : [18.x, 20.x, 22.x]
2320 steps :
2421 - uses : actions/checkout@v4
2522
@@ -30,41 +27,21 @@ jobs:
3027
3128 - uses : actions/setup-node@v4
3229 with :
33- node-version : ${{ matrix.node }}
30+ node-version : 20.x
3431 cache : pnpm
3532
3633 - name : Install dependencies
37- run : pnpm install --frozen-lockfile
34+ run : pnpm install
3835
3936 - name : Build and test
4037 run : pnpm test:spec
4138
42- coverage :
43- name : Coverage (domain suite)
44- runs-on : ubuntu-latest
45- needs : test
46- steps :
47- - uses : actions/checkout@v4
48-
49- - uses : pnpm/action-setup@v4
50- with :
51- version : 9
52- run_install : false
53-
54- - uses : actions/setup-node@v4
55- with :
56- node-version : 20.x
57- cache : pnpm
58-
59- - name : Install dependencies
60- run : pnpm install --frozen-lockfile
61-
62- - name : Run coverage
39+ - name : Run coverage (domain suite)
6340 run : |
6441 set -o pipefail
6542 pnpm test:domain:cov | tee coverage/typecheck/coverage.log
6643
67- - name : Coverage summary
44+ - name : Coverage summary (job)
6845 run : |
6946 {
7047 echo "## Coverage (domain suite)";
7754 with :
7855 name : coverage-typecheck
7956 path : coverage/typecheck
57+
58+ - name : Comment coverage on PR
59+ if : github.event_name == 'pull_request'
60+ uses : actions/github-script@v7
61+ with :
62+ github-token : ${{ secrets.GITHUB_TOKEN }}
63+ script : |
64+ const fs = require('fs');
65+ const path = 'coverage/typecheck/coverage-final.json';
66+ const coverage = JSON.parse(fs.readFileSync(path, 'utf8'));
67+
68+ const keys = ['lines', 'statements', 'functions', 'branches'];
69+ const totals = Object.values(coverage).reduce((acc, file) => {
70+ for (const key of keys) {
71+ acc[key].total += file[key].total;
72+ acc[key].covered += file[key].covered;
73+ }
74+ return acc;
75+ }, {
76+ lines: { total: 0, covered: 0 },
77+ statements: { total: 0, covered: 0 },
78+ functions: { total: 0, covered: 0 },
79+ branches: { total: 0, covered: 0 },
80+ });
81+
82+ const fmt = ({ covered, total }) =>
83+ total === 0 ? 'n/a' : `${((covered / total) * 100).toFixed(2)}% (${covered}/${total})`;
84+
85+ const header = '## Coverage (domain suite)';
86+ const body = [
87+ header,
88+ '',
89+ `- Lines: ${fmt(totals.lines)}`,
90+ `- Statements: ${fmt(totals.statements)}`,
91+ `- Functions: ${fmt(totals.functions)}`,
92+ `- Branches: ${fmt(totals.branches)}`,
93+ '',
94+ '_Generated by CI_',
95+ ].join('\n');
96+
97+ const { data: comments } = await github.rest.issues.listComments({
98+ issue_number: context.issue.number,
99+ owner: context.repo.owner,
100+ repo: context.repo.repo,
101+ });
102+ const existing = comments.find(
103+ (c) => c.user?.login === 'github-actions[bot]' && c.body?.startsWith(header)
104+ );
105+
106+ if (existing) {
107+ await github.rest.issues.updateComment({
108+ comment_id: existing.id,
109+ owner: context.repo.owner,
110+ repo: context.repo.repo,
111+ body,
112+ });
113+ } else {
114+ await github.rest.issues.createComment({
115+ issue_number: context.issue.number,
116+ owner: context.repo.owner,
117+ repo: context.repo.repo,
118+ body,
119+ });
120+ }
0 commit comments