Ctrl K
ComunidadGuías…

scripts/check-image-budget.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env node
// Fails a pull request that adds a bitmap over IMAGE_BUDGET_BYTES, or grows an
// existing one past it, unless the PR description carries an exemption line.
//
// MUL-6352 took the repo from 21.7MB of PNG/JPG to ~4MB of WebP; this keeps it
// there. Every committed bitmap is paid for on every clone, every Docker build
// context, and every deploy upload — costs a runtime image optimizer like
// `next/image` never touches, because it optimizes what visitors download, not
// what the repo carries.
//
// Shrinking a file is always allowed: the check compares against the size at
// `--base`, so a PR that recompresses an oversized image passes even while the
// result is still over budget.
//
// Usage:
//   node scripts/check-image-budget.mjs --base origin/main
//
// Reads PR_BODY from the environment for the exemption escape hatch, and emits
// GitHub Actions annotations when GITHUB_ACTIONS is set.

import { execFileSync } from "node:child_process";
import { statSync } from "node:fs";

const IMAGE_BUDGET_BYTES = 300 * 1024;
const BITMAP_EXTENSIONS = /\.(png|jpe?g|gif|bmp|tiff?|webp|avif)$/i;
const EXEMPTION_PATTERN = /^\s*Oversized image exemption:\s*\S/im;

const baseRef = readBaseRef();
const offenders = [];

for (const { status, file } of changedFiles(baseRef)) {
  if (!BITMAP_EXTENSIONS.test(file)) continue;
  const bytes = statSync(file).size;
  if (bytes <= IMAGE_BUDGET_BYTES) continue;
  const was = status === "A" ? 0 : blobSize(baseRef, file);
  if (bytes <= was) continue; // recompressed downward — an improvement
  offenders.push({ file, bytes, was });
}

if (offenders.length === 0) {
  console.log(`No bitmap added or grown past ${kb(IMAGE_BUDGET_BYTES)} against ${baseRef}.`);
  process.exit(0);
}

for (const { file, bytes, was } of offenders) {
  const from = was === 0 ? "new file" : `up from ${kb(was)}`;
  const message = `${file} is ${kb(bytes)} (${from}), over the ${kb(IMAGE_BUDGET_BYTES)} image budget`;
  console.log(process.env.GITHUB_ACTIONS ? `::error file=${file}::${message}` : `  ${message}`);
}

if (EXEMPTION_PATTERN.test(process.env.PR_BODY ?? "")) {
  console.log("\nPR description carries an exemption line — allowing.");
  process.exit(0);
}

console.error(
  [
    "",
    `${offenders.length} bitmap(s) exceed the ${kb(IMAGE_BUDGET_BYTES)} budget.`,
    "",
    "Shrink them first. Lossy WebP capped at 1920px for decorative art and",
    "1600px for screenshots strips most of the weight with no visible loss:",
    "",
    "  cwebp -q 78 -m 6 -sharp_yuv -metadata none -resize 1600 0 in.png -o out.webp",
    "",
    "Flat, palettized UI screenshots sometimes encode smaller losslessly",
    "(`cwebp -lossless -z 9`) — compare both and keep the smaller file.",
    "",
    "If the size is genuinely required, add a line to the PR description:",
    "",
    "  Oversized image exemption: <why this file has to ship at this size>",
  ].join("\n"),
);
process.exit(1);

// Added and modified paths between `base` and the working tree. Deletions
// cannot regress the budget, so the filter drops them.
function changedFiles(base) {
  const out = execFileSync("git", ["diff", "--name-status", "--diff-filter=AM", base, "--"], {
    encoding: "utf8",
  });
  return out
    .split("\n")
    .filter(Boolean)
    .map((line) => {
      const [status, file] = line.split("\t");
      return { status, file };
    });
}

// Byte size of `file` as of `ref`, or 0 when it did not exist there.
function blobSize(ref, file) {
  try {
    return Number(execFileSync("git", ["cat-file", "-s", `${ref}:${file}`], { encoding: "utf8" }));
  } catch {
    return 0;
  }
}

function readBaseRef() {
  const i = process.argv.indexOf("--base");
  if (i !== -1 && process.argv[i + 1]) return process.argv[i + 1];
  if (process.env.GITHUB_BASE_REF) return `origin/${process.env.GITHUB_BASE_REF}`;
  throw new Error("no base ref: pass --base <ref> or set GITHUB_BASE_REF");
}

function kb(bytes) {
  return `${(bytes / 1024).toFixed(1)}KB`;
}