Skip to content

Raise CSS gzip budget to 36 KiB to accommodate react-markdown #8

Raise CSS gzip budget to 36 KiB to accommodate react-markdown

Raise CSS gzip budget to 36 KiB to accommodate react-markdown #8

Triggered via push March 17, 2026 15:38
Status Failure
Total duration 44s
Artifacts

release.yml

on: push
Web UI Gates
39s
Web UI Gates
Matrix: build
Publish GitHub release (tags only)
0s
Publish GitHub release (tags only)
Fit to window
Zoom out
Zoom in

Annotations

2 errors and 1 warning
Web UI Gates
Process completed with exit code 1.
src/app/build-performance.test.ts > build performance baselines > wires deterministic gzip budget checks for embedded entry assets: crates/orchestrator-web-server/web-ui/src/app/build-performance.test.ts#L37
AssertionError: expected '#!/usr/bin/env node\n\nimport { readF…' to contain 'CSS_GZIP_BUDGET_BYTES = 24 * 1024' - Expected + Received - CSS_GZIP_BUDGET_BYTES = 24 * 1024 + #!/usr/bin/env node + + import { readFileSync, statSync } from "node:fs"; + import { basename, relative, resolve } from "node:path"; + import { gzipSync } from "node:zlib"; + + const JS_GZIP_BUDGET_BYTES = 110 * 1024; + const CSS_GZIP_BUDGET_BYTES = 36 * 1024; + + const embeddedDirPath = resolve(import.meta.dirname, "..", "..", "embedded"); + const embeddedIndexPath = resolve(embeddedDirPath, "index.html"); + const failures = []; + + const embeddedIndexSource = readEmbeddedIndexSource(embeddedIndexPath, failures); + const scriptAssetPaths = embeddedIndexSource + ? extractReferencedAssets( + embeddedIndexSource, + /<script\b[^>]*\bsrc=["']([^"']+\.js(?:[?#][^"']*)?)["'][^>]*><\/script>/g, + ) + : []; + const stylesheetAssetPaths = embeddedIndexSource + ? extractReferencedAssets( + embeddedIndexSource, + /<link\b[^>]*\brel=["']stylesheet["'][^>]*\bhref=["']([^"']+\.css(?:[?#][^"']*)?)["'][^>]*>/g, + ) + : []; + + const jsEntryAsset = pickEntryAsset(scriptAssetPaths, ".js"); + const cssEntryAsset = pickEntryAsset(stylesheetAssetPaths, ".css"); + + if (!jsEntryAsset) { + failures.push("Missing referenced JS entry asset in embedded/index.html"); + } + + if (!cssEntryAsset) { + failures.push("Missing referenced CSS entry asset in embedded/index.html"); + } + + if (jsEntryAsset) { + const jsResult = buildAssetResult(jsEntryAsset, JS_GZIP_BUDGET_BYTES); + if (jsResult.kind === "error") { + failures.push(jsResult.message); + } else { + reportAssetResult("JS", jsResult.result); + if (jsResult.result.isOverBudget) { + failures.push( + `JS entry asset is over budget (${formatBytes(jsResult.result.gzipBytes)} > ${formatBytes(JS_GZIP_BUDGET_BYTES)})`, + ); + } + } + } + + if (cssEntryAsset) { + const cssResult = buildAssetResult(cssEntryAsset, CSS_GZIP_BUDGET_BYTES); + if (cssResult.kind === "error") { + failures.push(cssResult.message); + } else { + reportAssetResult("CSS", cssResult.result); + if (cssResult.result.isOverBudget) { + failures.push( + `CSS entry asset is over budget (${formatBytes(cssResult.result.gzipBytes)} > ${formatBytes(CSS_GZIP_BUDGET_BYTES)})`, + ); + } + } + } + + if (failures.length > 0) { + for (const failure of failures) { + console.error(`[budget:fail] ${failure}`); + } + process.exit(1); + } + + console.log("[budget:ok] Embedded entry assets meet gzip budgets"); + + function readEmbeddedIndexSource(indexPath, failures) { + try { + return readFileSync(indexPath, "utf8"); + } catch (error) { + failures.push( + `Unable to read embedded/index.html at ${indexPath}. Run \`npm run build\` before budget checks.`, + ); + if (error instanceof Error && error.message) { + failures.push(`embedded/index.html read error: ${error.message}`); + } + return null; + } + } + + function extractReferencedAssets(source, pattern) { + const referencedAssets = []; + let match = pattern.exec(source); + + while (match) { + referencedAssets.push(match[1]); + match = pattern.exec(source); + } + + return Array.from(new Set(referencedAssets)); + } + + function pickEntryAsset(assetPaths, extension) { + if (assetPaths.length === 0) { + return null; + } + + const namedEntry = assetPaths.find((assetPath) => { + const fileName = basename(assetPath); + return fileName.startsWith("index-") && fileName.endsWith(extension); + }); + + return namedEntry ?? assetPaths[0]; + } + + function buildAssetResult(assetPath, budgetBytes) { + const relativeAssetPath = normalizeAssetPath(assetPath); + const absoluteAssetPath = resolve(embeddedDirPath, relativeAssetPath); + if (!isInsideDirectory(embeddedDirPath, absoluteAssetPath)) { + return { + kind: "error", + message: `Referenced asset resolves outside embedded directory: ${assetPath}`, +
Web UI Gates
Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: actions/checkout@v4, actions/setup-node@v4, actions/upload-artifact@v4. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2026. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/