Files
cinny/scripts/check-bundle-size.mjs
jaredandClaude Opus 5 19eded89c1 ci: engines >=20 + .nvmrc; hard audit gate; boot check; bundle budget; eslint ratchet
- engines.node >=20.0.0 and .nvmrc mirroring .node-version (#54)
- npm audit --audit-level=high is a hard gate (tree is at 0) (#91)
- scripts/boot-check.mjs serves dist/ with vite preview and asserts /,
  config.json, the entry chunk and the Element Call bundle all load (#92)
- scripts/check-bundle-size.mjs enforces gzip budgets from
  scripts/bundle-budget.json (seeded +10%); fails PRs, warns on push (#96)
- check:eslint runs with --max-warnings 68 so the count can only go down;
  7 unused eslint-disable directives removed to get there (#97)

Fixes #54
Fixes #91
Fixes #92
Fixes #96
Fixes #97

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
2026-09-12 20:28:42 -04:00

98 lines
3.4 KiB
JavaScript

#!/usr/bin/env node
// Bundle size budget (Gitea #96). The CI "Report bundle sizes" step printed
// numbers with nothing to compare them against, so a bundle could balloon
// silently. This script compares dist/assets gzip sizes against a small
// checked-in budget (scripts/bundle-budget.json) and prints the same report.
//
// Mode is passed via argv: `pull_request` fails the build over budget,
// anything else (e.g. `push`) only warns — a push has already merged, so
// blocking it can't prevent the regression, only delay the deploy of an
// otherwise-good commit; the pull_request gate is where this should be caught.
import { appendFileSync, readFileSync, readdirSync, statSync } from 'node:fs';
import { gzipSync } from 'node:zlib';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const mode = process.argv[2] || 'push';
const distAssetsDir = path.join(__dirname, '..', 'dist', 'assets');
const budgetPath = path.join(__dirname, 'bundle-budget.json');
function formatKb(bytes) {
return `${(bytes / 1024).toFixed(1)} kB`;
}
function main() {
const budget = JSON.parse(readFileSync(budgetPath, 'utf-8'));
const jsFiles = readdirSync(distAssetsDir)
.filter((f) => f.endsWith('.js') && !f.endsWith('.map'))
.sort();
if (jsFiles.length === 0) {
console.error(
`[check-bundle-size] No .js files found in ${distAssetsDir} — did the build run?`,
);
process.exitCode = 1;
return;
}
const rows = jsFiles.map((name) => {
const filePath = path.join(distAssetsDir, name);
const size = statSync(filePath).size;
const gzipSize = gzipSync(readFileSync(filePath)).length;
return { name, size, gzipSize };
});
const totalGzipBytes = rows.reduce((sum, r) => sum + r.gzipSize, 0);
const largest = rows.reduce((max, r) => (r.gzipSize > max.gzipSize ? r : max), rows[0]);
const summaryLines = [
'### Bundle sizes',
'',
'| File | Size | Gzip |',
'|------|------|------|',
...rows.map((r) => `| ${r.name} | ${formatKb(r.size)} | ${formatKb(r.gzipSize)} |`),
'',
`**Total gzip:** ${formatKb(totalGzipBytes)} (budget ${formatKb(budget.totalGzipBytes)})`,
`**Largest chunk gzip:** ${largest.name}${formatKb(largest.gzipSize)} (budget ${formatKb(
budget.largestChunkGzipBytes,
)})`,
];
const summaryText = summaryLines.join('\n');
console.log(summaryText);
if (process.env.GITHUB_STEP_SUMMARY) {
// Gitea Actions/act_runner honors the same GITHUB_STEP_SUMMARY convention.
appendFileSync(process.env.GITHUB_STEP_SUMMARY, `${summaryText}\n`);
}
const overBudget = [];
if (totalGzipBytes > budget.totalGzipBytes) {
overBudget.push(
`total gzip ${formatKb(totalGzipBytes)} exceeds budget ${formatKb(budget.totalGzipBytes)}`,
);
}
if (largest.gzipSize > budget.largestChunkGzipBytes) {
overBudget.push(
`largest chunk (${largest.name}) gzip ${formatKb(largest.gzipSize)} exceeds budget ${formatKb(
budget.largestChunkGzipBytes,
)}`,
);
}
if (overBudget.length > 0) {
const message = `[check-bundle-size] Over budget: ${overBudget.join('; ')}`;
if (mode === 'pull_request') {
console.error(message);
process.exitCode = 1;
} else {
console.warn(`${message} (warning only on "${mode}")`);
}
} else {
console.log('[check-bundle-size] Within budget.');
}
}
main();