// Check generated HTML, so Markdown links and MDX components are both covered. import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'; import { join, relative, sep } from 'node:path'; import { fileURLToPath } from 'node:url'; const root = fileURLToPath(new URL('../dist/', import.meta.url)); if (!existsSync(root)) throw new Error('Missing dist/. Run npm run build first.'); const origin = 'https://docs.simba-robotics.com'; const files = []; function walk(dir) { for (const entry of readdirSync(dir, { withFileTypes: true })) { const path = join(dir, entry.name); if (entry.isDirectory()) walk(path); else files.push(path); } } walk(root); const pages = new Map(files.filter((path) => path.endsWith('.html')) .map((path) => [path, readFileSync(path, 'utf8')])); const errors = []; let checked = 0; let diagrams = 0; for (const [path, html] of pages) { const page = relative(root, path).split(sep).join('/'); const base = new URL(page.replace(/index\.html$/, ''), origin); if (html.includes('class="katex-error"')) errors.push(`${page}: invalid math`); if (/]*\sset:html=/.test(html)) errors.push(`${page}: MDX emitted inert SVG styles`); const diagramCount = [...html.matchAll(/class="mermaid-diagram"/g)].length; if (diagramCount) { diagrams += diagramCount; const staticCount = [...html.matchAll(/data-rendered="static"/g)].length; const lightCount = [...html.matchAll(/class="mermaid-variant mermaid-light"/g)].length; const darkCount = [...html.matchAll(/class="mermaid-variant mermaid-dark"/g)].length; const svgCount = [...html.matchAll(/]*\bid="mermaid-[^"]+"/g)].length; if ([staticCount, lightCount, darkCount].some((count) => count !== diagramCount) || svgCount !== diagramCount * 2) { errors.push(`${page}: Mermaid must contain both pre-rendered SVG themes`); } const ids = [...html.matchAll(/\sid="([^"]+)"/g)].map((match) => match[1]); if (ids.length !== new Set(ids).size) errors.push(`${page}: duplicate IDs may break inline SVG references`); } // Astro emits quoted, escaped attributes. Code examples escape '<', so they // are not mistaken for real links here. External URLs are not fetched. for (const tag of html.matchAll(/<(?:a|link|script|img)\b[^>]*>/g)) { // Canonical/hreflang are SEO metadata, not navigation or loaded assets. if (/^