-
-
Notifications
You must be signed in to change notification settings - Fork 123
196 lines (168 loc) · 8.3 KB
/
Copy pathcoverage.yml
File metadata and controls
196 lines (168 loc) · 8.3 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
name: coverage
# Coverage + full Vitest suite for code changes. This workflow always reports a
# stable `coverage` PR check so branch protection can require it safely; pure
# content/docs/markdown PRs skip inside the job instead of skipping the workflow.
# Codecov consumes the generated lcov/JUnit reports for patch/project coverage
# and Test Analytics, while this job remains the single test-producing lane.
on:
pull_request:
push:
branches:
- main
paths-ignore:
- "content/**"
- "README.md"
- "docs/**"
- "**/*.md"
workflow_dispatch:
permissions:
contents: read
pull-requests: read
concurrency:
group: coverage-${{ github.ref }}
cancel-in-progress: true
jobs:
coverage:
name: coverage
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Determine coverage scope
id: coverage-scope
if: ${{ github.event_name == 'pull_request' }}
env:
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
node --input-type=module <<'NODE'
import { appendFileSync } from "node:fs";
function normalizePath(filename) {
return String(filename || "").replace(/\\/g, "/").replace(/^\.\//, "");
}
function isCoverageIgnoredPath(filename) {
const normalized = normalizePath(filename);
return (
normalized === "README.md" ||
normalized.startsWith("content/") ||
normalized.startsWith("docs/") ||
normalized.endsWith(".md")
);
}
async function listChangedFiles() {
const repo = process.env.GITHUB_REPOSITORY;
const prNumber = process.env.PR_NUMBER;
const token = process.env.GITHUB_TOKEN;
const files = [];
for (let page = 1; ; page += 1) {
const url = new URL(`https://api.github.com/repos/${repo}/pulls/${prNumber}/files`);
url.searchParams.set("per_page", "100");
url.searchParams.set("page", String(page));
const response = await fetch(url, {
headers: {
accept: "application/vnd.github+json",
authorization: `Bearer ${token}`,
"x-github-api-version": "2022-11-28",
},
});
if (!response.ok) {
throw new Error(`GitHub pull files API returned ${response.status} ${response.statusText}`);
}
const pageFiles = await response.json();
if (!Array.isArray(pageFiles) || pageFiles.length === 0) break;
files.push(...pageFiles.map((file) => normalizePath(file.filename)).filter(Boolean));
if (pageFiles.length < 100) break;
}
return files;
}
const files = await listChangedFiles();
const runCoverage = files.length === 0 || files.some((file) => !isCoverageIgnoredPath(file));
appendFileSync(process.env.GITHUB_OUTPUT, `run_coverage=${runCoverage ? "true" : "false"}\n`);
if (runCoverage) {
console.log(`Coverage required for ${files.length} changed file(s).`);
} else {
console.log("Coverage skipped for content/docs/markdown-only PR.");
}
NODE
- name: Skip coverage for content/docs/markdown-only PR
if: ${{ github.event_name == 'pull_request' && steps.coverage-scope.outputs.run_coverage != 'true' }}
run: echo "No code files changed; coverage check passes without running the test suite."
- name: Checkout
if: ${{ github.event_name != 'pull_request' || steps.coverage-scope.outputs.run_coverage == 'true' }}
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0
persist-credentials: false
- name: Setup pnpm
if: ${{ github.event_name != 'pull_request' || steps.coverage-scope.outputs.run_coverage == 'true' }}
uses: pnpm/action-setup@4f4305e1fe60ad818b8ae6fc4a697cc47eab0b2d
- name: Setup Node.js
if: ${{ github.event_name != 'pull_request' || steps.coverage-scope.outputs.run_coverage == 'true' }}
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f
with:
node-version: 24.17.0
cache: pnpm
- name: Install dependencies
if: ${{ github.event_name != 'pull_request' || steps.coverage-scope.outputs.run_coverage == 'true' }}
run: pnpm install --frozen-lockfile
- name: Prepare test reports dir
if: ${{ github.event_name != 'pull_request' || steps.coverage-scope.outputs.run_coverage == 'true' }}
run: rm -rf reports/junit && mkdir -p reports/junit
# EXPERIMENT: cache the generated prebuild artifacts. Key hashes every
# input to `prebuild` (content, the build script, registry builder source,
# routes, prettier via the lockfile). A hit restores the artifacts and the
# build step below is skipped. Because content/** changes on nearly every
# merge, this only hits across runs that share an identical content
# snapshot — in practice, re-pushes of the same code PR (the iterate loop).
# Note: prebuild also derives entry `updatedAt` from `git log -- content`;
# a hit requires byte-identical content, so within a non-rebased PR the git
# history (and thus updatedAt) is identical too. The display timestamp can
# only go stale in the rare case of byte-identical content with a different
# commit touch-history, which is acceptable for a non-required lane.
- name: Restore prebuild artifacts
if: ${{ github.event_name != 'pull_request' || steps.coverage-scope.outputs.run_coverage == 'true' }}
id: prebuild-cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: |
apps/web/public/data
apps/web/src/generated
apps/web/src/routeTree.gen.ts
key: prebuild-v1-${{ runner.os }}-${{ hashFiles('content/**', 'scripts/build-content-index.mjs', 'packages/registry/src/**', 'packages/registry/package.json', 'apps/web/src/routes/**', 'pnpm-lock.yaml') }}
- name: Build registry artifacts
if: ${{ (github.event_name != 'pull_request' || steps.coverage-scope.outputs.run_coverage == 'true') && steps.prebuild-cache.outputs.cache-hit != 'true' }}
run: pnpm --filter web run prebuild
- name: Apply local D1 migrations
if: ${{ github.event_name != 'pull_request' || steps.coverage-scope.outputs.run_coverage == 'true' }}
run: pnpm --filter web db:migrate:local
- name: Run Vitest suite with coverage
if: ${{ github.event_name != 'pull_request' || steps.coverage-scope.outputs.run_coverage == 'true' }}
run: pnpm test:coverage
- name: Upload coverage to Codecov (pull request)
if: ${{ github.event_name == 'pull_request' && steps.coverage-scope.outputs.run_coverage == 'true' }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
files: ./coverage/lcov.info
fail_ci_if_error: false
- name: Upload coverage to Codecov (trusted)
if: ${{ github.event_name != 'pull_request' }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/lcov.info
fail_ci_if_error: false
- name: Upload Vitest results to Codecov (pull request)
if: ${{ !cancelled() && github.event_name == 'pull_request' && steps.coverage-scope.outputs.run_coverage == 'true' }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
files: ./reports/junit/vitest.xml
report_type: test_results
fail_ci_if_error: false
- name: Upload Vitest results to Codecov (trusted)
if: ${{ !cancelled() && github.event_name != 'pull_request' }}
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./reports/junit/vitest.xml
report_type: test_results
fail_ci_if_error: false