emo-social-insta-dm-agent/tools/verify_mermaid.mjs

109 lines
2.9 KiB
JavaScript

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();