docs/scripts/docs-site/smoke.mjs
2026-05-07 20:06:20 -07:00

137 lines
6.4 KiB
JavaScript

#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
const root = process.cwd();
const site = path.join(root, "dist", "docs-site");
const required = [
"index.html",
"tools/reactions/index.html",
"it/channels/index.html",
"zh-CN/tools/reactions/index.html",
"concepts/models.md",
"llm.txt",
"llms.txt",
".well-known/llms.txt",
"robots.txt",
"sitemap.xml",
"de/tools/reactions/index.html",
"de/gateway/heartbeat/index.html",
"pagefind/pagefind.js",
"assets/docs-site.css",
"assets/docs-site.js"
];
const poison = [
/\banalysis\s+to=functions\./iu,
/\b(?:commentary|final)\s+to=functions\./iu,
/\bfunctions\.(?:read|write|exec|search|run)\b/iu,
/OPENCLAW_DOCS_MARKER/u,
/<\/?openclaw_docs_i18n_input>/iu,
/\/home\/runner\/work\//u,
/彩神马争霸/u
];
for (const rel of required) {
const file = path.join(site, rel);
if (!fs.existsSync(file)) throw new Error(`missing ${rel}`);
if (!rel.endsWith(".html")) continue;
const html = fs.readFileSync(file, "utf8");
if (!/<title>[^<]+<\/title>/i.test(html)) throw new Error(`${rel}: missing title`);
for (const pattern of poison) {
if (pattern.test(html)) throw new Error(`${rel}: poison matched ${pattern}`);
}
}
for (const rel of ["llms-full.txt", ".well-known/llms-full.txt"]) {
if (fs.existsSync(path.join(site, rel))) throw new Error(`${rel}: full-site LLM corpus should not be emitted`);
}
const llms = fs.readFileSync(path.join(site, "llms.txt"), "utf8");
if (/llms-full\.txt/.test(llms)) throw new Error("llms.txt: should not advertise llms-full.txt");
if (!/Accept: text\/markdown|\.md/.test(llms)) throw new Error("llms.txt: should advertise page-level Markdown");
const wellKnownLlms = fs.readFileSync(path.join(site, ".well-known/llms.txt"), "utf8");
if (wellKnownLlms !== llms) throw new Error(".well-known/llms.txt: does not match root llms.txt");
const robots = fs.readFileSync(path.join(site, "robots.txt"), "utf8");
if (!/Sitemap: https:\/\/documentation\.openclaw\.ai\/sitemap\.xml/.test(robots)) {
throw new Error("robots.txt: sitemap directive missing");
}
if (!/Disallow: \/llms-full\.txt/.test(robots) || !/LLMS: https:\/\/documentation\.openclaw\.ai\/llms\.txt/.test(robots)) {
throw new Error("robots.txt: LLM directives missing");
}
const zhReactions = fs.readFileSync(path.join(site, "zh-CN/tools/reactions/index.html"), "utf8");
if (!/href="(?:\/docs)?\/zh-CN\/tools\/reactions"/.test(zhReactions)) {
throw new Error("zh-CN reactions: language picker does not preserve current page");
}
if (!/href="(?:\/docs)?\/zh-CN\/tools\/agent-send/.test(zhReactions)) {
throw new Error("zh-CN reactions: article links do not stay in locale");
}
const itChannels = fs.readFileSync(path.join(site, "it/channels/index.html"), "utf8");
if (!/class="tab-link active" href="(?:\/docs)?\/it\/channels"/.test(itChannels)) {
throw new Error("it channels: localized tabs are missing active Channels tab");
}
if (!/<section class="nav-section"><h2>Overview<\/h2>/.test(itChannels)) {
throw new Error("it channels: localized sidebar is missing");
}
const index = fs.readFileSync(path.join(site, "index.html"), "utf8");
if (!/data-language-picker/.test(index) || !/class="language-option active"[^>]*aria-selected="true"/.test(index)) {
throw new Error("index: custom language picker is missing active state");
}
if (!/Português \(BR\)/.test(index)) {
throw new Error("index: language picker labels were not rendered");
}
if (!/data-docs-chat/.test(index) || !/OPENCLAW_DOCS_CHAT_API/.test(index)) {
throw new Error("index: docs chat widget was not rendered");
}
if (!/class="tok-key">channels<\/span>/.test(index)
|| !/class="tok-string">&quot;\+15555550123&quot;<\/span>/.test(index)
|| !/class="tok-literal">true<\/span>/.test(index)) {
throw new Error("index: json5 config example was not syntax-highlighted");
}
const modelsMarkdown = fs.readFileSync(path.join(site, "concepts/models.md"), "utf8");
if (!/^---\nsummary: /m.test(modelsMarkdown) || !/title: "Models CLI"/m.test(modelsMarkdown)) {
throw new Error("concepts/models.md: source markdown was not emitted");
}
if (process.env.DOCS_SITE_BASE_PATH && (/src="\/assets\//.test(index) || /href="\/assets\//.test(index))) {
throw new Error("index: absolute asset paths were not base-path rewritten");
}
if (!process.env.DOCS_SITE_BASE_PATH && !/href="\/assets\/docs-site\.css\?v=[^"]+"/.test(index)) {
throw new Error("index: custom-domain build did not emit root asset paths");
}
if (process.env.DOCS_SITE_CNAME) {
const cnamePath = path.join(site, "CNAME");
if (!fs.existsSync(cnamePath) || fs.readFileSync(cnamePath, "utf8").trim() !== process.env.DOCS_SITE_CNAME) {
throw new Error("CNAME: custom domain file missing or wrong");
}
}
const siteJs = fs.readFileSync(path.join(site, "assets/docs-site.js"), "utf8");
if (!/function syncSidebar/.test(siteJs) || !/async function navigateTo/.test(siteJs)) {
throw new Error("assets: docs PJAX navigation is missing");
}
if (/data-locale/.test(siteJs)) {
throw new Error("assets: stale native language select handler is still present");
}
if (!/function initChat/.test(siteJs) || !/data-chat-form/.test(siteJs)) {
throw new Error("assets: docs chat behavior is missing");
}
if (!/function runSearch/.test(siteJs) || !/setTimeout\(\(\)=>runSearch\(q,id\),140\)/.test(siteJs)) {
throw new Error("assets: search input is not debounced");
}
const platformsIndex = fs.readFileSync(path.join(site, "platforms/index.html"), "utf8");
if (/VPS &amp;amp; hosting/.test(platformsIndex)) {
throw new Error("platforms index: TOC double-escaped ampersand");
}
const dateTime = fs.readFileSync(path.join(site, "date-time/index.html"), "utf8");
if (/Current Date &amp;amp; Time/.test(dateTime)) {
throw new Error("date-time: TOC double-escaped ampersand");
}
const legacyDigitalOcean = path.join(site, "docs/platforms/digitalocean/index.html");
if (!fs.existsSync(legacyDigitalOcean)) {
throw new Error("legacy DigitalOcean redirect: missing /docs/platforms/digitalocean compatibility file");
}
if (!/url=\/(?:docs\/)?install\/digitalocean/.test(fs.readFileSync(legacyDigitalOcean, "utf8"))) {
throw new Error("legacy DigitalOcean redirect: wrong destination");
}
const showcase = fs.readFileSync(path.join(site, "start/showcase/index.html"), "utf8");
if (!/href="https:\/\/www\.youtube\.com\/watch\?v=SaWSPZoPX34"/.test(showcase)) {
throw new Error("showcase: external card href was not rendered");
}
console.log(`docs site smoke ok: ${required.length} checks`);