125 lines
3.8 KiB
JavaScript
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;
|
|
}
|
|
}
|
|
}
|