docs/scripts/docs-site/cloudflare-prune.mjs
2026-05-07 01:22:46 +01:00

125 lines
3.8 KiB
JavaScript

#!/usr/bin/env node
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import { localeLabels } from "./config.mjs";
const root = process.cwd();
const site = path.join(root, "dist", "docs-site");
const pagefindDir = path.join(site, "pagefind");
const tempSearchSite = path.join(root, "dist", "pagefind-en-site");
const cloudflareFreeFileLimit = 20_000;
const nonEnglishLocales = Object.keys(localeLabels).filter((locale) => locale !== "en");
if (!fs.existsSync(site)) throw new Error("dist/docs-site does not exist; run docs:build first");
removeJunkFiles(site);
removeLocalizedMarkdown(site);
rebuildEnglishSearch();
writeStaticAssetHeaders();
const fileCount = countFiles(site);
if (fileCount > cloudflareFreeFileLimit) {
throw new Error(`Cloudflare static assets file count is ${fileCount}, above ${cloudflareFreeFileLimit}`);
}
console.log(`cloudflare prune ok: ${fileCount} files`);
function removeJunkFiles(dir) {
for (const file of walk(dir)) {
if (path.basename(file) === ".DS_Store") fs.rmSync(file, { force: true });
}
}
function removeLocalizedMarkdown(dir) {
for (const locale of nonEnglishLocales) {
fs.rmSync(path.join(dir, `${locale}.md`), { force: true });
const localeDir = path.join(dir, locale);
if (!fs.existsSync(localeDir)) continue;
for (const file of walk(localeDir)) {
if (file.endsWith(".md")) fs.rmSync(file, { force: true });
}
}
}
function rebuildEnglishSearch() {
fs.rmSync(pagefindDir, { recursive: true, force: true });
fs.rmSync(tempSearchSite, { recursive: true, force: true });
fs.mkdirSync(tempSearchSite, { recursive: true });
const localeSet = new Set(nonEnglishLocales);
for (const file of walk(site)) {
if (!file.endsWith(".html")) continue;
const rel = path.relative(site, file);
const firstSegment = rel.split(path.sep)[0];
if (localeSet.has(firstSegment)) continue;
linkOrCopy(file, path.join(tempSearchSite, rel));
}
const pagefindBin = path.join(root, "node_modules", ".bin", process.platform === "win32" ? "pagefind.cmd" : "pagefind");
const result = spawnSync(pagefindBin, [
"--site",
tempSearchSite,
"--output-path",
pagefindDir,
"--quiet",
], { stdio: "inherit" });
fs.rmSync(tempSearchSite, { recursive: true, force: true });
if (result.status !== 0) throw new Error(`pagefind failed with status ${result.status ?? "unknown"}`);
}
function writeStaticAssetHeaders() {
fs.writeFileSync(path.join(site, "_headers"), [
"/assets/*",
" Cache-Control: public, max-age=31536000, immutable",
"",
"/pagefind/*",
" Cache-Control: public, max-age=31536000, immutable",
"",
"/*.html",
" Cache-Control: public, max-age=60, s-maxage=86400, stale-while-revalidate=604800",
"",
"/*.md",
" Cache-Control: public, max-age=300, s-maxage=3600, stale-while-revalidate=86400",
"",
"/*.txt",
" Cache-Control: public, max-age=300, s-maxage=3600, stale-while-revalidate=86400",
"",
"/*.json",
" Cache-Control: public, max-age=300, s-maxage=3600, stale-while-revalidate=86400",
"",
"/*.jsonl",
" Cache-Control: public, max-age=300, s-maxage=3600, stale-while-revalidate=86400",
"",
].join("\n"));
}
function linkOrCopy(from, to) {
fs.mkdirSync(path.dirname(to), { recursive: true });
try {
fs.linkSync(from, to);
} catch {
fs.copyFileSync(from, to);
}
}
function countFiles(dir) {
let count = 0;
for (const file of walk(dir)) {
if (fs.statSync(file).isFile()) count += 1;
}
return count;
}
function* walk(dir) {
if (!fs.existsSync(dir)) return;
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
yield* walk(fullPath);
} else if (entry.isFile()) {
yield fullPath;
}
}
}