Files
gitlab-foss/scripts/lint/check_mermaid.mjs
2024-04-27 03:13:20 +00:00

78 lines
2.0 KiB
JavaScript
Executable File

#!/usr/bin/env node
// Lint mermaid code in markdown files.
// Usage: scripts/lint/check_mermaid.mjs [files ...]
import fs from 'node:fs';
import glob from 'glob';
import mermaid from 'mermaid';
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
const jsdom = new JSDOM('...', {
pretendToBeVisual: true,
});
global.document = jsdom;
global.window = jsdom.window;
global.Option = window.Option;
// Workaround to make DOMPurify not fail.
// See https://github.com/mermaid-js/mermaid/issues/5204
DOMPurify.addHook = () => {};
DOMPurify.sanitize = (x) => x;
const defaultGlob = "doc/**/*.md";
const mermaidMatch = /```mermaid(.*?)```/gms;
const argv = process.argv.length > 2 ? process.argv.slice(2) : [defaultGlob];
const mdFiles = argv.flatMap((arg) => glob.sync(arg))
console.log(`Checking ${mdFiles.length} markdown files...`);
// Mimicking app/assets/javascripts/lib/mermaid.js
mermaid.initialize({
// mermaid core options
mermaid: {
startOnLoad: false,
},
// mermaidAPI options
theme: 'neutral',
flowchart: {
useMaxWidth: true,
htmlLabels: true,
},
secure: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize', 'htmlLabels'],
securityLevel: 'strict',
});
let errors = 0;
await Promise.all(
mdFiles.map((path) => {
const data = fs.readFileSync(path, 'utf8');
const matched = [...data.matchAll(mermaidMatch)];
return Promise.all(
matched.map((match) => {
const matchIndex = match.index;
const mermaidText = match[1];
return mermaid.parse(mermaidText).catch((error) => {
const lineNumber = data.slice(0, matchIndex).split('\n').length;
console.log(`${path}:${lineNumber}: Mermaid syntax error\nError: ${error}\n`);
errors += 1;
});
}),
);
}),
);
if (errors > 0) {
console.log(`Total errors: ${errors}`);
// eslint-disable-next-line no-restricted-syntax
console.log(`To fix these errors, see https://docs.gitlab.com/ee/development/documentation/testing/#mermaid-chart-linting.`);
process.exit(1);
}