Files
documentation/scripts/sidebar-coverage.mjs
jarvis-at-skic 8a9363ccb2
Build and publish Eldorado documentation / build (pull_request) Successful in 42s
feat: embed canonical presentation on homepage
2026-10-08 18:33:31 +00:00

123 lines
7.0 KiB
JavaScript

import assert from 'node:assert/strict';
import {readFileSync, readdirSync, mkdirSync, writeFileSync} from 'node:fs';
import {join} from 'node:path';
const normalize = path => path.replace(/\/$/, '');
const read = path => JSON.parse(readFileSync(path, 'utf8'));
// Use Docusaurus's resolved IDs/routes and sidebar trees, not filename guesses.
export function auditSidebars(instances, entrySidebars) {
const docs = new Map();
const menus = new Map();
for (const {plugin, version, documents, sidebars} of instances) {
const ids = new Set();
for (const doc of documents) {
assert(!ids.has(doc.id), `Duplicate document ID: ${plugin}:${version}:${doc.id}`);
ids.add(doc.id);
const route = normalize(doc.path);
assert(!docs.has(route), `Duplicate document route: ${route}`);
docs.set(route, {...doc, plugin, version, menu: `${plugin}:${version}:${doc.sidebar}`});
}
for (const [name, items] of Object.entries(sidebars)) menus.set(`${plugin}:${version}:${name}`, items);
}
const links = new Map();
for (const [menu, items] of menus) {
const found = new Set();
const walk = items => {
for (const item of items) {
assert(['category', 'link', 'html'].includes(item.type), `Unresolved sidebar type: ${item.type}`);
assert(!item.unlisted, `Hidden sidebar item: ${item.label}`);
if (item.href && !/^(https?:|mailto:)/.test(item.href)) {
const route = normalize(item.href);
if (item.docId) assert.equal(docs.get(route)?.id, item.docId, `Sidebar ID/route mismatch: ${item.docId}`);
assert(!found.has(route), `Duplicate sidebar route in ${menu}: ${route}`);
found.add(route);
}
if (item.type === 'category') walk(item.items);
}
};
walk(items);
links.set(menu, found);
}
const reachable = new Set(entrySidebars);
for (const menu of reachable) {
assert(menus.has(menu), `Unknown navigation sidebar: ${menu}`);
for (const route of links.get(menu)) {
const doc = docs.get(route);
if (doc) reachable.add(doc.menu);
}
}
for (const [route, doc] of docs) {
assert(reachable.has(doc.menu), `Unreachable sidebar for ${doc.plugin}:${doc.id}`);
assert(links.get(doc.menu)?.has(route), `Missing sidebar document: ${doc.plugin}:${doc.id}`);
}
return [...docs.entries()].map(([route, doc]) => ({route, id:doc.id, plugin:doc.plugin, sidebar:doc.sidebar}));
}
export async function verifySidebarCoverage() {
const global = read('.docusaurus/globalData.json')['docusaurus-plugin-content-docs'];
assert(global && Object.keys(global).length, 'No generated document inventory');
const instances = [];
for (const [plugin, data] of Object.entries(global)) {
const folder = `.docusaurus/docusaurus-plugin-content-docs/${plugin}/p`;
const resolved = readdirSync(folder).filter(f => f.endsWith('.json')).map(f => read(join(folder, f)).version);
for (const version of data.versions) {
const matches = resolved.filter(v => v.version === version.name);
assert.equal(matches.length, 1, `Missing/duplicate resolved version: ${plugin}:${version.name}`);
instances.push({plugin, version:version.name, documents:version.docs, sidebars:matches[0].docsSidebars});
}
}
const {default:config} = await import('../.docusaurus/docusaurus.config.mjs');
const entries = config.themeConfig.navbar.items.filter(i => i.type === 'docSidebar').map(i => `${i.docsPluginId || 'default'}:current:${i.sidebarId}`);
const documents = auditSidebars(instances, entries);
const routes = new Set(documents.map(d => `${d.route.replace(config.baseUrl, '')}/index.html`));
const exclusions = [];
const html = readdirSync('build', {recursive:true}).filter(f => f.endsWith('.html')).sort();
for (const file of html) {
if (routes.delete(file)) continue;
let reason;
if (file === 'index.html') reason = 'Dedicated React homepage, not a documentation page';
else if (file === '404.html') reason = 'Docusaurus not-found system page';
else if (file === 'search/index.html') reason = 'Local-search results UI, not a documentation page';
else if (file === 'strategy/index.html') {
assert(readFileSync(join('build',file),'utf8').includes('http-equiv="refresh"'), 'Strategy alias must remain a redirect');
reason = 'Legacy Strategy redirect alias to Target';
} else if (file === 'strategy/reference/measurement-contracts/index.html') {
const body = readFileSync(join('build',file),'utf8');
assert(body.includes('window.location.replace') && body.includes('name="robots" content="noindex"') && !body.includes('<article'), 'Metric alias must remain a non-indexed redirect');
assert.equal(body, readFileSync(join('static',file),'utf8'));
reason = 'Legacy measurement-contracts hash-aware redirect to canonical Measurement pages';
} else if (file === 'presentations/less-prompting-more-shipping/index.html') {
const body = readFileSync(join('build',file),'utf8');
assert.equal(body, readFileSync(join('static',file),'utf8'));
assert(body.includes('class="reveal deck"') && body.includes('class="notes"'));
assert(readFileSync('build/index.html','utf8').includes('/presentations/less-prompting-more-shipping/?embed'));
reason = 'Reveal presentation linked from the homepage, not a competing documentation page';
} else if (file.startsWith('diagrams/') && readFileSync(join('build',file),'utf8').includes('<svg')) {
assert.equal(readFileSync(join('build',file),'utf8'), readFileSync(join('static',file),'utf8'), `Viewer must match generated static asset: ${file}`);
reason = 'Standalone diagram viewer asset, not documentation';
}
assert(reason, `Unclassified published HTML route: ${file}`);
exclusions.push({file, reason});
}
assert.equal(routes.size, 0, `Missing published docs: ${[...routes]}`);
// Check all local links and anchors on every document, not only selected Strategy pages.
for (const {route} of documents) {
const file = `build/${route.replace(config.baseUrl, '')}/index.html`;
const body = readFileSync(file, 'utf8');
for (const [, href] of body.matchAll(/href="([^" ]+)"/g)) {
if (!href.startsWith(config.baseUrl) && !href.startsWith('#')) continue;
const url = new URL(href, `https://docs.invalid${route}/`);
let target = url.pathname.slice(config.baseUrl.length);
if (!target || target.endsWith('/')) target += 'index.html';
else if (!target.split('/').at(-1).includes('.')) target += '/index.html';
const targetBody = readFileSync(join('build', decodeURIComponent(target)), 'utf8');
if (url.hash) assert(targetBody.includes(`id="${decodeURIComponent(url.hash.slice(1))}"`), `Broken anchor: ${route} -> ${href}`);
}
}
mkdirSync('.cache', {recursive:true});
writeFileSync('.cache/sidebar-coverage.json', JSON.stringify({documents, exclusions}, null, 2));
console.log(`PASS: ${documents.length} published docs reachable in resolved sidebars; ${exclusions.length} classified non-doc HTML routes; all doc links/anchors checked`);
return {documents, exclusions};
}