52 lines
2.6 KiB
JavaScript
52 lines
2.6 KiB
JavaScript
import { access, readFile, readdir } from "node:fs/promises";
|
|
import { fileURLToPath } from "node:url";
|
|
import { join } from "node:path";
|
|
|
|
const root = new URL("../", import.meta.url);
|
|
const contentRoot = new URL("../content/", import.meta.url);
|
|
const required = ["title", "summary", "date", "updated", "topic", "kind", "status", "visibility", "canonicalUrl"];
|
|
const forbidden = [
|
|
[/\/(?:Users|home|mnt)\/[^\s)\]>'"]+/, "private absolute path"],
|
|
[/\b(?:10\.(?:\d{1,3}\.){2}\d{1,3}|192\.168\.(?:\d{1,3}\.)\d{1,3}|100\.(?:6[4-9]|[7-9]\d|1[01]\d|12[0-7])\.(?:\d{1,3}\.)\d{1,3})\b/, "private IPv4"],
|
|
[/(?:git\.n\.xiaomi\.com|coro\.doc|\.codex\/sessions|<codex_internal_context)/, "internal material"],
|
|
[/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/, "private key"],
|
|
[/"?sourcePaths"?\s*:/, "Knowledge Hub private metadata"]
|
|
];
|
|
|
|
async function files(dir) {
|
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
const base = fileURLToPath(dir);
|
|
return entries.flatMap((entry) => entry.isFile() && entry.name.endsWith(".md") ? [join(base, entry.name)] : []);
|
|
}
|
|
|
|
function parse(text, path) {
|
|
const match = text.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
if (!match) throw new Error(`${path}: invalid frontmatter delimiters`);
|
|
return { data: JSON.parse(match[1]), body: match[2].trim() };
|
|
}
|
|
|
|
const paths = [...await files(new URL("posts/", contentRoot)), ...await files(new URL("sites/", contentRoot))];
|
|
const errors = [];
|
|
const urls = new Set();
|
|
for (const path of paths) {
|
|
const text = await readFile(path, "utf8");
|
|
let parsed;
|
|
try { parsed = parse(text, path); } catch (error) { errors.push(error.message); continue; }
|
|
for (const key of required) if (!parsed.data[key]) errors.push(`${path}: missing ${key}`);
|
|
if (parsed.data.visibility !== "public") errors.push(`${path}: visibility must be public`);
|
|
if (!/^https:\/\//.test(parsed.data.canonicalUrl ?? "")) errors.push(`${path}: canonicalUrl must use HTTPS`);
|
|
if (urls.has(parsed.data.canonicalUrl)) errors.push(`${path}: duplicate canonicalUrl`);
|
|
urls.add(parsed.data.canonicalUrl);
|
|
if (parsed.data.kind === "article" && !parsed.body) errors.push(`${path}: empty article body`);
|
|
if (parsed.data.preview?.startsWith("/")) {
|
|
try { await access(new URL(`../public${parsed.data.preview}`, import.meta.url)); }
|
|
catch { errors.push(`${path}: missing preview asset ${parsed.data.preview}`); }
|
|
}
|
|
for (const [pattern, label] of forbidden) if (pattern.test(text)) errors.push(`${path}: forbidden ${label}`);
|
|
}
|
|
if (errors.length) {
|
|
console.error(errors.join("\n"));
|
|
process.exit(1);
|
|
}
|
|
console.log(`valid: ${paths.length} public content entries at ${root.pathname}`);
|