50 lines
2.3 KiB
JavaScript
50 lines
2.3 KiB
JavaScript
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import sharp from "sharp";
|
|
|
|
const root = fileURLToPath(new URL("../../../", import.meta.url));
|
|
const sources = path.join(root, "tools/figures/assets");
|
|
const output = path.join(root, "apps/docs/public/figures");
|
|
const check = process.argv.includes("--check");
|
|
const policy = JSON.parse(await readFile(path.join(root, "content/image-policy.json"), "utf8"));
|
|
|
|
async function files(directory) {
|
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
return (await Promise.all(entries.map((entry) => {
|
|
const name = path.join(directory, entry.name);
|
|
return entry.isDirectory() ? files(name) : [name];
|
|
}))).flat().sort();
|
|
}
|
|
|
|
// Originals stay outside public; both inline and lightbox images use the WebP.
|
|
for (const name of await files(path.join(root, "apps/docs/public"))) {
|
|
if (/\.(?:png|jpe?g|tiff?|bmp)$/i.test(name)) {
|
|
throw new Error(`Original raster exposed in public: ${path.relative(root, name)}. Move it to tools/figures/assets and run images.`);
|
|
}
|
|
}
|
|
|
|
await mkdir(output, { recursive: true });
|
|
for (const source of await files(sources)) {
|
|
if (!/\.(?:png|jpe?g|tiff?|bmp)$/i.test(source)) continue;
|
|
const relative = path.relative(sources, source).replace(/\.[^.]+$/, ".webp");
|
|
const destination = path.join(output, relative);
|
|
const original = await readFile(source);
|
|
const encoded = await sharp(original)
|
|
.rotate()
|
|
.resize({ width: policy.maxWidth, withoutEnlargement: true })
|
|
.webp({ quality: policy.quality, effort: 6 })
|
|
.toBuffer();
|
|
if (encoded.length > policy.maxBytes) {
|
|
throw new Error(`${relative}: ${encoded.length} bytes exceeds the ${policy.maxBytes}-byte image budget.`);
|
|
}
|
|
if (check) {
|
|
const generated = await readFile(destination);
|
|
if (!encoded.equals(generated)) throw new Error(`${relative} is stale. Regenerate browser images on the confirmed remote runner.`);
|
|
} else {
|
|
await mkdir(path.dirname(destination), { recursive: true });
|
|
await writeFile(destination, encoded);
|
|
}
|
|
console.log(`${check ? "Verified" : "Generated"} ${relative}: ${original.length} → ${encoded.length} bytes (${(100 * (1 - encoded.length / original.length)).toFixed(1)}% smaller)`);
|
|
}
|