import fs from "node:fs"; import path from "node:path"; import process from "node:process"; import { parse } from "@mermaid-js/parser"; function isMarkdownFile(filePath) { const lower = filePath.toLowerCase(); return lower.endsWith(".md") || lower.endsWith(".markdown"); } function* walkFiles(rootPath) { const stat = fs.statSync(rootPath); if (stat.isFile()) { yield rootPath; return; } const entries = fs.readdirSync(rootPath, { withFileTypes: true }); for (const ent of entries) { const full = path.join(rootPath, ent.name); if (ent.isDirectory()) { yield* walkFiles(full); } else if (ent.isFile()) { yield full; } } } function extractMermaidBlocks(markdownText) { const blocks = []; const re = /```mermaid\s*([\s\S]*?)```/g; let m; while ((m = re.exec(markdownText)) !== null) { blocks.push(m[1] || ""); } return blocks; } function detectDiagramType(code) { const lines = String(code || "") .replace(/\r\n?/g, "\n") .split("\n") .map((l) => l.trim()) .filter((l) => l && !l.startsWith("%%")); if (!lines.length) return null; const head = lines[0]; if (head.startsWith("pie")) return "pie"; if (head.startsWith("gitGraph")) return "gitGraph"; if (head.startsWith("architecture")) return "architecture"; if (head.startsWith("packet")) return "packet"; if (head.startsWith("info")) return "info"; if (head.startsWith("radar")) return "radar"; if (head.startsWith("treemap")) return "treemap"; // Not supported by @mermaid-js/parser yet (example: flowchart/sequence/class). return null; } async function main() { const args = process.argv.slice(2); const roots = args.length ? args : ["reports"]; let ok = true; let total = 0; let failures = 0; let skipped = 0; for (const root of roots) { for (const filePath of walkFiles(root)) { if (!isMarkdownFile(filePath)) continue; const text = fs.readFileSync(filePath, "utf8"); const blocks = extractMermaidBlocks(text); if (!blocks.length) continue; for (let i = 0; i < blocks.length; i++) { const code = String(blocks[i] || "").trim(); total += 1; const diagramType = detectDiagramType(code); if (!diagramType) { skipped += 1; continue; } try { await parse(diagramType, code); } catch (err) { ok = false; failures += 1; const msg = err && typeof err === "object" && "message" in err ? String(err.message) : String(err); console.error(`[mermaid] ${filePath} block=${i + 1} type=${diagramType}: ${msg}`); } } } } if (ok) { console.log(JSON.stringify({ ok: true, diagrams: total, skipped }, null, 2)); return 0; } console.error(JSON.stringify({ ok: false, diagrams: total, failures, skipped }, null, 2)); return 1; } process.exitCode = await main();