cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

cat/module-path
012 / website / scripts / check-mermaid.mjs
check-mermaid.mjs 2.6K
1// Parses every mermaid code block in docs/ with the Mermaid the site2// draws with, and fails on one it can't read: in the browser such a3// block is drawn as an error, not a diagram. `npm run build` (and so4// `make site`) runs it first.5//6// Mermaid's parser runs in Node, but it sanitizes labels with DOMPurify,7// which needs a DOM: jsdom gives it one, set up before Mermaid loads.89import {readFileSync, readdirSync} from 'node:fs';10import {join, relative} from 'node:path';11import {fileURLToPath} from 'node:url';12import {JSDOM} from 'jsdom';1314const docs = fileURLToPath(new URL('../../docs', import.meta.url));1516const {window} = new JSDOM('<!doctype html><html><body></body></html>');17globalThis.window = window;18globalThis.document = window.document;19globalThis.DOMParser = window.DOMParser;20globalThis.Element = window.Element;21const {default: mermaid} = await import('mermaid');22mermaid.initialize({startOnLoad: false});2324function markdownFiles(dir) {25 return readdirSync(dir, {withFileTypes: true}).flatMap((e) => {26 const path = join(dir, e.name);27 if (e.isDirectory()) {28 return markdownFiles(path);29 }30 return /\.mdx?$/.test(e.name) ? [path] : [];31 });32}3334// The mermaid blocks of a page, as CommonMark reads its fences: a block35// inside a longer fence (an example of Markdown) is text, not a diagram.36function mermaidBlocks(text) {37 const blocks = [];38 let fence = null;39 text.split('\n').forEach((line, i) => {40 const m = /^(\s*)(`{3,}|~{3,})\s*([^`\s]*)/.exec(line);41 if (fence === null) {42 if (m) {43 fence = {marker: m[2], indent: m[1].length, mermaid: m[3] === 'mermaid', line: i + 1, body: []};44 }45 return;46 }47 const close = /^\s*(`{3,}|~{3,})\s*$/.exec(line);48 if (close && close[1][0] === fence.marker[0] && close[1].length >= fence.marker.length) {49 if (fence.mermaid) {50 blocks.push({line: fence.line, text: fence.body.join('\n')});51 }52 fence = null;53 return;54 }55 fence.body.push(line.slice(Math.min(fence.indent, line.search(/\S|$/))));56 });57 return blocks;58}5960let count = 0;61const failures = [];62for (const file of markdownFiles(docs).sort()) {63 for (const block of mermaidBlocks(readFileSync(file, 'utf8'))) {64 count++;65 try {66 await mermaid.parse(block.text);67 } catch (err) {68 const first = block.text.trim().split('\n')[0];69 failures.push(`docs/${relative(docs, file)}:${block.line}: ${first}\n${String(err.message ?? err)}`);70 }71 }72}7374if (failures.length > 0) {75 console.error(`${failures.length} of ${count} mermaid diagrams don't parse:\n`);76 console.error(failures.join('\n\n'));77 process.exit(1);78}79console.log(`${count} mermaid diagrams parse`);