Skip to content

Commit cfb9394

Browse files
authored
ci(repo): extend break-check coverage to all SDK packages (#8691)
1 parent 35020c3 commit cfb9394

27 files changed

Lines changed: 276 additions & 73 deletions
File renamed without changes.

.github/workflows/api-changes.yml

Lines changed: 147 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,26 @@ on:
1313
- release/v4
1414
- release/core-2
1515
paths:
16+
- 'packages/astro/**'
1617
- 'packages/backend/**'
18+
- 'packages/chrome-extension/**'
1719
- 'packages/clerk-js/**'
20+
- 'packages/expo/**'
21+
- 'packages/expo-passkeys/**'
22+
- 'packages/express/**'
23+
- 'packages/fastify/**'
24+
- 'packages/hono/**'
25+
- 'packages/localizations/**'
1826
- 'packages/nextjs/**'
27+
- 'packages/nuxt/**'
1928
- 'packages/react/**'
29+
- 'packages/react-router/**'
2030
- 'packages/shared/**'
31+
- 'packages/tanstack-react-start/**'
32+
- 'packages/testing/**'
2133
- 'packages/ui/**'
22-
- 'snapi.config.json'
34+
- 'packages/vue/**'
35+
- 'break-check.config.json'
2336
- '.github/workflows/api-changes.yml'
2437

2538
permissions:
@@ -30,14 +43,30 @@ concurrency:
3043
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
3144

3245
env:
33-
SNAPI_PACKAGE: https://pkg.pr.new/clerk/snapi/@clerk/snapi@2479ffce1adb8ff5f1caf95509268774224c121d
34-
SNAPI_FILTERS: >-
46+
# The tool was renamed snapi -> break-check (package @clerk/break-check, binary
47+
# break-check, repo clerk/break-check). Pinned to a pkg.pr.new build of a
48+
# specific commit on main.
49+
BREAK_CHECK_PACKAGE: https://pkg.pr.new/clerk/break-check/@clerk/break-check@a4db93a68e43e45a71cc958cc4a722d91c5f62b2
50+
BREAK_CHECK_FILTERS: >-
51+
--filter=@clerk/astro
3552
--filter=@clerk/backend
53+
--filter=@clerk/chrome-extension
3654
--filter=@clerk/clerk-js
55+
--filter=@clerk/expo
56+
--filter=@clerk/expo-passkeys
57+
--filter=@clerk/express
58+
--filter=@clerk/fastify
59+
--filter=@clerk/hono
60+
--filter=@clerk/localizations
3761
--filter=@clerk/nextjs
62+
--filter=@clerk/nuxt
3863
--filter=@clerk/react
64+
--filter=@clerk/react-router
3965
--filter=@clerk/shared
66+
--filter=@clerk/tanstack-react-start
67+
--filter=@clerk/testing
4068
--filter=@clerk/ui
69+
--filter=@clerk/vue
4170
4271
jobs:
4372
publish-baseline:
@@ -66,18 +95,27 @@ jobs:
6695
turbo-token: ${{ secrets.TURBO_TOKEN }}
6796

6897
- name: Build declarations
69-
run: pnpm turbo build:declarations $TURBO_ARGS $SNAPI_FILTERS
98+
run: pnpm turbo build:declarations $TURBO_ARGS $BREAK_CHECK_FILTERS
7099

71100
- name: Generate API snapshot
72101
run: |
73-
pnpm dlx --package "$SNAPI_PACKAGE" snapi snapshot \
102+
pnpm dlx --package "$BREAK_CHECK_PACKAGE" break-check snapshot \
74103
--output "$GITHUB_WORKSPACE/.api-snapshots-baseline"
75104
105+
- name: Resolve break-check cache key
106+
id: break-check-key
107+
run: echo "ref=${BREAK_CHECK_PACKAGE##*@}" >> "$GITHUB_OUTPUT"
108+
76109
- name: Save baseline to cache
77110
uses: actions/cache/save@v4
78111
with:
79112
path: .api-snapshots-baseline
80-
key: snapi-baseline-${{ github.sha }}
113+
# Fold the break-check commit into the key: a snapshot produced by one
114+
# break-check version must not be reused by another, since discovery
115+
# changes (e.g. wildcard subpath expansion) make the surfaces
116+
# incomparable and the diff degenerates into thousands of phantom
117+
# additions.
118+
key: break-check-baseline-${{ steps.break-check-key.outputs.ref }}-${{ github.sha }}
81119

82120
check-api:
83121
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.draft == false }}
@@ -96,6 +134,13 @@ jobs:
96134
- name: Checkout Repo
97135
uses: actions/checkout@v4
98136
with:
137+
# Pin the "current" side of the diff to the PR head, not the
138+
# refs/pull/N/merge ref checkout resolves by default. The merge ref is
139+
# the head merged into the moving tip of the base branch, so once main
140+
# advances it absorbs unrelated changes and break-check reports them as
141+
# this PR's own (clerk/break-check#32). The baseline is already pinned
142+
# to base.sha (cache key + worktree fallback below).
143+
ref: ${{ github.event.pull_request.head.sha }}
99144
fetch-depth: 100
100145
fetch-tags: false
101146
filter: 'blob:none'
@@ -109,15 +154,22 @@ jobs:
109154
turbo-team: ${{ vars.TURBO_TEAM }}
110155
turbo-token: ${{ secrets.TURBO_TOKEN }}
111156

157+
- name: Resolve break-check cache key
158+
id: break-check-key
159+
run: echo "ref=${BREAK_CHECK_PACKAGE##*@}" >> "$GITHUB_OUTPUT"
160+
112161
- name: Restore baseline from cache
113162
id: baseline-cache
114163
uses: actions/cache/restore@v4
115164
with:
116165
path: .api-snapshots-baseline
117-
key: snapi-baseline-${{ github.event.pull_request.base.sha }}
166+
# Keyed on the break-check commit too, so bumping break-check misses the
167+
# stale baseline and the worktree fallback below rebuilds it with the
168+
# same version the PR runs (see publish-baseline for the rationale).
169+
key: break-check-baseline-${{ steps.break-check-key.outputs.ref }}-${{ github.event.pull_request.base.sha }}
118170

119171
- name: Build current declarations
120-
run: pnpm turbo build:declarations $TURBO_ARGS $SNAPI_FILTERS
172+
run: pnpm turbo build:declarations $TURBO_ARGS $BREAK_CHECK_FILTERS
121173

122174
- name: Fetch base commit
123175
if: steps.baseline-cache.outputs.cache-matched-key == ''
@@ -127,31 +179,48 @@ jobs:
127179
if: steps.baseline-cache.outputs.cache-matched-key == ''
128180
run: |
129181
mkdir -p .worktrees
130-
git worktree add --detach .worktrees/snapi-baseline "${{ github.event.pull_request.base.sha }}"
131-
cp snapi.config.json .worktrees/snapi-baseline/snapi.config.json
182+
git worktree add --detach .worktrees/break-check-baseline "${{ github.event.pull_request.base.sha }}"
183+
# Snapshot the base ref with the coverage it actually had. Only seed the
184+
# config when the base tracks no coverage at all; otherwise packages
185+
# newly added to coverage in this PR get diffed against a baseline that
186+
# never tracked them (every export reads as a phantom change against the
187+
# base's bundled .d.ts), and the base ref may not even build their
188+
# declarations yet. A base from before this rename still names its config
189+
# snapi.config.json, so check both names. This reads the base's real
190+
# coverage; it is not rename-compat, and goes no-op once main carries
191+
# break-check.config.json.
192+
if [ ! -f .worktrees/break-check-baseline/break-check.config.json ] && [ ! -f .worktrees/break-check-baseline/snapi.config.json ]; then
193+
cp break-check.config.json .worktrees/break-check-baseline/break-check.config.json
194+
fi
132195
133196
- name: Install baseline dependencies
134197
if: steps.baseline-cache.outputs.cache-matched-key == ''
135-
working-directory: .worktrees/snapi-baseline
198+
working-directory: .worktrees/break-check-baseline
136199
run: pnpm install --frozen-lockfile
137200

138201
- name: Build baseline declarations
139202
if: steps.baseline-cache.outputs.cache-matched-key == ''
140-
working-directory: .worktrees/snapi-baseline
141-
run: pnpm turbo build:declarations $TURBO_ARGS $SNAPI_FILTERS
203+
working-directory: .worktrees/break-check-baseline
204+
# --continue past per-package failures and don't fail the step: the base
205+
# ref may not build declarations for packages that only gained
206+
# build:declarations support in this PR. break-check snapshots only the
207+
# base's own coverage below, so a partial build is fine; a needed package
208+
# that fails to build still surfaces when `break-check snapshot` finds no
209+
# .d.ts.
210+
run: pnpm turbo build:declarations $TURBO_ARGS $BREAK_CHECK_FILTERS --continue || true
142211

143212
- name: Generate baseline API snapshots
144213
if: steps.baseline-cache.outputs.cache-matched-key == ''
145-
working-directory: .worktrees/snapi-baseline
214+
working-directory: .worktrees/break-check-baseline
146215
run: |
147-
pnpm dlx --package "$SNAPI_PACKAGE" snapi snapshot \
216+
pnpm dlx --package "$BREAK_CHECK_PACKAGE" break-check snapshot \
148217
--output "$GITHUB_WORKSPACE/.api-snapshots-baseline"
149218
150219
- name: Detect API changes
151220
env:
152-
SNAPI_ANTHROPIC_API_KEY: ${{ secrets.SNAPI_ANTHROPIC_API_KEY }}
221+
BREAK_CHECK_ANTHROPIC_API_KEY: ${{ secrets.BREAK_CHECK_ANTHROPIC_API_KEY }}
153222
run: |
154-
pnpm dlx --package "$SNAPI_PACKAGE" snapi detect \
223+
pnpm dlx --package "$BREAK_CHECK_PACKAGE" break-check detect \
155224
--baseline .api-snapshots-baseline \
156225
--output api-changes-report.md \
157226
--fail-on-breaking
@@ -165,38 +234,67 @@ jobs:
165234
if-no-files-found: ignore
166235
retention-days: 5
167236

168-
- name: Build snapi comment body
237+
- name: Post break-check report
169238
if: always()
170-
id: report
171-
run: |
172-
if [ ! -f api-changes-report.md ]; then
173-
exit 0
174-
fi
175-
{
176-
echo 'body<<SNAPI_REPORT_EOF'
177-
echo '<!-- snapi-report -->'
178-
if grep -q '## No API Changes Detected' api-changes-report.md; then
179-
echo '**Snapi**: no API changes detected in `@clerk/backend`, `@clerk/clerk-js`, `@clerk/nextjs`, `@clerk/react`, `@clerk/shared`, `@clerk/ui`.'
180-
else
181-
cat api-changes-report.md
182-
fi
183-
echo 'SNAPI_REPORT_EOF'
184-
} >> "$GITHUB_OUTPUT"
185-
186-
- name: Find existing snapi comment
187-
if: always() && steps.report.outputs.body != ''
188-
id: find-comment
189-
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
239+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
240+
env:
241+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
190242
with:
191-
issue-number: ${{ github.event.pull_request.number }}
192-
comment-author: 'github-actions[bot]'
193-
body-includes: '<!-- snapi-report -->'
243+
script: |
244+
const fs = require('fs');
245+
const reportPath = 'api-changes-report.md';
246+
if (!fs.existsSync(reportPath)) {
247+
core.info('No break-check report found; skipping comment.');
248+
return;
249+
}
194250
195-
- name: Post snapi report
196-
if: always() && steps.report.outputs.body != ''
197-
uses: peter-evans/create-or-update-comment@23ff15729ef2fc348714a3bb66d2f655ca9066f2 # v3.1.0
198-
with:
199-
comment-id: ${{ steps.find-comment.outputs.comment-id }}
200-
issue-number: ${{ github.event.pull_request.number }}
201-
body: ${{ steps.report.outputs.body }}
202-
edit-mode: replace
251+
const marker = '<!-- break-check-report -->';
252+
const report = fs.readFileSync(reportPath, 'utf-8');
253+
254+
let body;
255+
if (report.includes('## No API Changes Detected')) {
256+
body = `${marker}\n**Break Check**: no API changes detected across the tracked packages.`;
257+
} else {
258+
// GitHub rejects comment bodies over 65536 chars. Read the report
259+
// from disk and post via the API so we never hit an arg-length
260+
// limit, and truncate with a pointer to the full artifact when a
261+
// genuinely large diff would overflow the comment.
262+
const LIMIT = 64000;
263+
const head = `${marker}\n`;
264+
if (head.length + report.length <= LIMIT) {
265+
body = head + report;
266+
} else {
267+
const notice =
268+
`\n\n> **Note**\n> Report truncated to fit GitHub's comment limit. ` +
269+
`The full report is attached as the \`api-changes-report\` artifact on ` +
270+
`[this run](${process.env.RUN_URL}).\n`;
271+
const budget = LIMIT - head.length - notice.length;
272+
body = head + report.slice(0, budget) + notice;
273+
}
274+
}
275+
276+
const comments = await github.paginate(github.rest.issues.listComments, {
277+
owner: context.repo.owner,
278+
repo: context.repo.repo,
279+
issue_number: context.issue.number,
280+
per_page: 100,
281+
});
282+
const existing = comments.find(
283+
(c) => c.user?.type === 'Bot' && c.body && c.body.includes(marker),
284+
);
285+
286+
if (existing) {
287+
await github.rest.issues.updateComment({
288+
owner: context.repo.owner,
289+
repo: context.repo.repo,
290+
comment_id: existing.id,
291+
body,
292+
});
293+
} else {
294+
await github.rest.issues.createComment({
295+
owner: context.repo.owner,
296+
repo: context.repo.repo,
297+
issue_number: context.issue.number,
298+
body,
299+
});
300+
}

break-check.config.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"packages": [
3+
"packages/astro",
4+
"packages/backend",
5+
"packages/chrome-extension",
6+
"packages/clerk-js",
7+
"packages/expo",
8+
"packages/expo-passkeys",
9+
"packages/express",
10+
"packages/fastify",
11+
"packages/hono",
12+
"packages/localizations",
13+
"packages/nextjs",
14+
"packages/nuxt",
15+
"packages/react",
16+
"packages/react-router",
17+
"packages/shared",
18+
"packages/tanstack-react-start",
19+
"packages/testing",
20+
"packages/ui",
21+
"packages/vue"
22+
],
23+
"snapshotDir": ".api-snapshots",
24+
"mainBranch": "main",
25+
"checkVersionBump": true,
26+
"outputFormat": "markdown"
27+
}

packages/astro/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@
7777
"types.ts"
7878
],
7979
"scripts": {
80-
"build": "tsup --onSuccess \"pnpm build:dts\" && pnpm copy:components",
81-
"build:dts": "tsc --emitDeclarationOnly --declaration",
80+
"build": "tsup --onSuccess \"pnpm build:declarations\" && pnpm copy:components",
81+
"build:declarations": "tsc -p tsconfig.declarations.json",
8282
"copy:components": "rm -rf ./components && mkdir -p ./components/ && cp -r ./src/astro-components/* ./components/ && cp ./src/types.ts ./",
8383
"dev": "tsup --watch",
8484
"dev:pub": "pnpm dev -- --env.publish",
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"declaration": true,
5+
"declarationMap": true,
6+
"emitDeclarationOnly": true,
7+
"outDir": "./dist",
8+
"declarationDir": "./dist/types",
9+
"skipLibCheck": true
10+
},
11+
"exclude": ["node_modules", "dist", "build", "src/astro-components/**/*.ts", "**/__tests__/**/*"]
12+
}

packages/astro/tsup.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default defineConfig(overrideOptions => {
2121
],
2222
dts: true,
2323
minify: false,
24-
onSuccess: shouldPublish ? 'pnpm build:dts && pkglab pub --ping' : 'pnpm build:dts',
24+
onSuccess: shouldPublish ? 'pnpm build:declarations && pkglab pub --ping' : 'pnpm build:declarations',
2525
define: {
2626
PACKAGE_NAME: `"${name}"`,
2727
PACKAGE_VERSION: `"${version}"`,

packages/express/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
],
6363
"scripts": {
6464
"build": "pnpm clean && tsup",
65+
"build:declarations": "tsc -p tsconfig.declarations.json",
6566
"clean": "rimraf ./dist",
6667
"dev": "tsup --watch",
6768
"dev:pub": "pnpm dev -- --env.publish",
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"emitDeclarationOnly": true,
5+
"skipLibCheck": true
6+
},
7+
"exclude": ["node_modules", "dist", "**/__tests__/**/*"]
8+
}

packages/fastify/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
],
6262
"scripts": {
6363
"build": "tsup --env.NODE_ENV production",
64+
"build:declarations": "tsc -p tsconfig.declarations.json",
6465
"clean": "rimraf ./dist",
6566
"dev": "tsup --watch",
6667
"dev:pub": "pnpm dev -- --env.publish",
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"emitDeclarationOnly": true,
5+
"skipLibCheck": true
6+
},
7+
"exclude": ["node_modules", "dist", "**/__tests__/**/*"]
8+
}

0 commit comments

Comments
 (0)