Skip to content

Commit f0a4d8c

Browse files
Lms24claude
andcommitted
ci: Add workflow to auto-bump size limits via PR label
Add an "Accept Bundlesize Increase" label-triggered workflow that automatically bumps failing size-limit entries in .size-limit.js with a min(10%, 1KB) margin. The workflow reuses build artifacts from the most recent CI run to avoid a full rebuild. Also improve the size-limit action failure message to list exceeded entries with details and instruct users about the label. Co-Authored-By: Claude <noreply@anthropic.com> Made-with: Cursor
1 parent 5e5487b commit f0a4d8c

3 files changed

Lines changed: 187 additions & 11 deletions

File tree

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: 'Automation: Accept Bundlesize Increase'
2+
on:
3+
pull_request:
4+
types: [labeled]
5+
6+
concurrency:
7+
group: accept-size-increase-${{ github.event.pull_request.number }}
8+
cancel-in-progress: true
9+
10+
jobs:
11+
bump-size-limits:
12+
if: github.event.label.name == 'Accept Bundlesize Increase'
13+
runs-on: ubuntu-24.04
14+
permissions:
15+
contents: write
16+
pull-requests: write
17+
actions: read
18+
steps:
19+
- uses: actions/checkout@v6
20+
with:
21+
ref: ${{ github.head_ref }}
22+
token: ${{ secrets.GITHUB_TOKEN }}
23+
24+
- uses: actions/setup-node@v6
25+
with:
26+
node-version-file: 'package.json'
27+
28+
- name: Install dependencies
29+
run: yarn install --ignore-engines --frozen-lockfile
30+
31+
- name: Find latest CI run for this PR
32+
id: find-run
33+
env:
34+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35+
run: |
36+
RUN_JSON=$(gh run list \
37+
--workflow "CI: Build & Test" \
38+
--branch "${{ github.head_ref }}" \
39+
--status completed \
40+
--limit 1 \
41+
--json databaseId,headSha)
42+
RUN_ID=$(echo "$RUN_JSON" | jq -r '.[0].databaseId')
43+
RUN_SHA=$(echo "$RUN_JSON" | jq -r '.[0].headSha')
44+
if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then
45+
echo "::error::No completed CI run found. Wait for CI to finish, then re-add the label."
46+
exit 1
47+
fi
48+
HEAD_SHA=$(git rev-parse HEAD)
49+
if [ "$RUN_SHA" != "$HEAD_SHA" ]; then
50+
echo "::error::CI run ($RUN_SHA) does not match current HEAD ($HEAD_SHA). Wait for the latest CI run to complete, then re-add the label."
51+
exit 1
52+
fi
53+
echo "run_id=$RUN_ID" >> "$GITHUB_OUTPUT"
54+
55+
- name: Download build artifacts
56+
uses: actions/download-artifact@v6
57+
with:
58+
name: build-output
59+
run-id: ${{ steps.find-run.outputs.run_id }}
60+
github-token: ${{ secrets.GITHUB_TOKEN }}
61+
62+
- name: Run size-limit and bump failing entries
63+
id: bump
64+
run: node scripts/bump-size-limits.mjs
65+
66+
- name: Format
67+
run: yarn format
68+
69+
- name: Commit and push
70+
run: |
71+
git config user.name "github-actions[bot]"
72+
git config user.email "github-actions[bot]@users.noreply.github.com"
73+
git add .size-limit.js
74+
git diff --cached --quiet && echo "No changes to commit" && exit 0
75+
git commit -m "chore: Bump size limits"
76+
git push
77+
78+
- name: Comment on PR
79+
if: steps.bump.outputs.summary != ''
80+
env:
81+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
82+
run: |
83+
gh pr comment "${{ github.event.pull_request.number }}" \
84+
--body "${{ steps.bump.outputs.summary }}"
85+
86+
- name: Remove label
87+
if: always()
88+
env:
89+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
90+
run: |
91+
gh pr edit "${{ github.event.pull_request.number }}" \
92+
--remove-label "Accept Bundlesize Increase"

dev-packages/size-limit-gh-action/index.mjs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -182,24 +182,25 @@ async function run() {
182182

183183
if (status > 0) {
184184
try {
185-
const results = limit.parseResults(output);
186-
const failedResults = results
187-
.filter(result => result.passed || false)
188-
.map(result => ({
189-
name: result.name,
190-
size: +result.size,
191-
sizeLimit: +result.sizeLimit,
192-
}));
185+
const results = JSON.parse(output);
186+
const failedResults = results.filter(result => !result.passed);
193187

194188
if (failedResults.length > 0) {
195-
// eslint-disable-next-line no-console
196-
console.log('Exceeded size-limits:', failedResults);
189+
const lines = failedResults.map(r => {
190+
const size = (r.size / 1024).toFixed(1);
191+
const max = (r.sizeLimit / 1024).toFixed(1);
192+
const over = ((r.size - r.sizeLimit) / 1024).toFixed(1);
193+
return ` ${r.name}: ${size} KB (limit: ${max} KB, +${over} KB over)`;
194+
});
195+
core.error(`Size limit exceeded:\n${lines.join('\n')}`);
197196
}
198197
} catch {
199198
// noop
200199
}
201200

202-
setFailed('Size limit has been exceeded.');
201+
setFailed(
202+
'Size limit has been exceeded. To accept this increase, add the "Accept Bundlesize Increase" label to this PR or update `.size-limit.js` manually.',
203+
);
203204
}
204205
} catch (error) {
205206
core.error(error);

scripts/bump-size-limits.mjs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { execSync } from 'node:child_process';
2+
import { readFileSync, writeFileSync, appendFileSync } from 'node:fs';
3+
4+
const SIZE_LIMIT_CONFIG = '.size-limit.js';
5+
6+
function roundUpToHalfKB(bytes) {
7+
return Math.ceil((bytes / 1024) * 2) / 2;
8+
}
9+
10+
function run() {
11+
let output;
12+
try {
13+
output = execSync('yarn run --silent size-limit --json', {
14+
encoding: 'utf-8',
15+
maxBuffer: 10 * 1024 * 1024,
16+
// size-limit exits with non-zero when limits are exceeded, which is expected
17+
stdio: ['pipe', 'pipe', 'pipe'],
18+
});
19+
} catch (error) {
20+
// size-limit exits with code 1 when limits are exceeded, but still writes JSON to stdout
21+
output = error.stdout;
22+
if (!output) {
23+
console.error('size-limit produced no output.');
24+
process.exit(1);
25+
}
26+
}
27+
28+
let results;
29+
try {
30+
results = JSON.parse(output);
31+
} catch {
32+
console.error('Failed to parse size-limit JSON output.');
33+
console.error('Raw output:', output.slice(0, 500));
34+
process.exit(1);
35+
}
36+
37+
const failedEntries = results.filter(r => !r.passed);
38+
39+
if (failedEntries.length === 0) {
40+
console.log('All size-limit checks passed. Nothing to bump.');
41+
return;
42+
}
43+
44+
console.log(`Found ${failedEntries.length} failing size-limit entries:`);
45+
46+
let config = readFileSync(SIZE_LIMIT_CONFIG, 'utf-8');
47+
const summaryLines = [];
48+
49+
for (const entry of failedEntries) {
50+
const actualSize = entry.size;
51+
const margin = Math.min(actualSize * 0.1, 1024);
52+
const newLimitKB = roundUpToHalfKB(actualSize + margin);
53+
const newLimitStr = `${newLimitKB} KB`;
54+
55+
const nameEscaped = entry.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
56+
const regex = new RegExp(`(name:\\s*'${nameEscaped}'[\\s\\S]*?limit:\\s*')([^']+)(')`);
57+
58+
const match = config.match(regex);
59+
if (!match) {
60+
console.error(` WARNING: Could not find limit entry for "${entry.name}" in ${SIZE_LIMIT_CONFIG}`);
61+
continue;
62+
}
63+
64+
const oldLimitStr = match[2];
65+
console.log(` ${entry.name}: ${oldLimitStr} -> ${newLimitStr}`);
66+
summaryLines.push(`- \`${entry.name}\`: ${oldLimitStr} -> ${newLimitStr}`);
67+
68+
config = config.replace(regex, `$1${newLimitStr}$3`);
69+
}
70+
71+
writeFileSync(SIZE_LIMIT_CONFIG, config, 'utf-8');
72+
console.log(`\nUpdated ${SIZE_LIMIT_CONFIG}`);
73+
74+
// Write summary to $GITHUB_OUTPUT for the PR comment step
75+
if (process.env.GITHUB_OUTPUT) {
76+
const summary = `Bumped size limits:\n${summaryLines.join('\n')}`;
77+
// Multi-line output requires delimiter syntax
78+
const delimiter = `EOF_${Date.now()}`;
79+
appendFileSync(process.env.GITHUB_OUTPUT, `summary<<${delimiter}\n${summary}\n${delimiter}\n`);
80+
}
81+
}
82+
83+
run();

0 commit comments

Comments
 (0)