#!/usr/bin/env node
/**
* Mermaid 语法检测脚本
* 用法: node scripts/check-mermaid.js
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 查找所有 md 文件
function findMdFiles(dir, files = []) {
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules') {
findMdFiles(fullPath, files);
} else if (item.endsWith('.md')) {
files.push(fullPath);
}
}
return files;
}
// 提取 mermaid 代码块
function extractMermaidBlocks(content, filePath) {
const blocks = [];
const regex = /```mermaid\n([\s\S]*?)```/g;
let match;
let lineNum = 1;
let lastIndex = 0;
while ((match = regex.exec(content)) !== null) {
// 计算行号
const beforeMatch = content.substring(lastIndex, match.index);
lineNum += (beforeMatch.match(/\n/g) || []).length;
blocks.push({
content: match[1],
file: filePath,
line: lineNum
});
lastIndex = match.index;
}
return blocks;
}
// 检查常见的 mermaid 语法问题
function checkMermaidSyntax(block) {
const errors = [];
const content = block.content;
const lines = content.split('\n');
lines.forEach((line, idx) => {
// 检查 @ 符号(mermaid 中的特殊字符)
if (line.includes('@') && !line.includes('"')) {
errors.push({
...block,
lineInBlock: idx + 1,
issue: `包含未转义的 @ 符号: "${line.trim()}"`,
suggestion: '用引号包裹含 @ 的文本,如 ["@xxx"]'
});
}
// 检查 < > 符号
if ((line.includes('<') || line.includes('>')) && !line.includes('-->') && !line.includes('---') && !line.match(/\[.*<.*>.*\]/)) {
if (!line.includes('"') && line.match(/\[.*[<>].*\]/)) {
errors.push({
...block,
lineInBlock: idx + 1,
issue: `包含未转义的 < 或 > 符号: "${line.trim()}"`,
suggestion: '用引号包裹含 <> 的文本'
});
}
}
// 检查括号不匹配
const openBrackets = (line.match(/\[/g) || []).length;
const closeBrackets = (line.match(/\]/g) || []).length;
if (openBrackets !== closeBrackets) {
errors.push({
...block,
lineInBlock: idx + 1,
issue: `括号不匹配: "${line.trim()}"`,
suggestion: '检查 [ ] 是否成对'
});
}
// 检查圆括号不匹配
const openParens = (line.match(/\(/g) || []).length;
const closeParens = (line.match(/\)/g) || []).length;
if (openParens !== closeParens && !line.includes('-->')) {
errors.push({
...block,
lineInBlock: idx + 1,
issue: `圆括号不匹配: "${line.trim()}"`,
suggestion: '检查 ( ) 是否成对'
});
}
// 检查引号不匹配
const quotes = (line.match(/"/g) || []).length;
if (quotes % 2 !== 0) {
errors.push({
...block,
lineInBlock: idx + 1,
issue: `引号不匹配: "${line.trim()}"`,
suggestion: '检查 " 是否成对'
});
}
// 检查特殊字符 # { } & 等
if (line.match(/\[[^\]]*[#{}|&][^\]]*\]/) && !line.includes('"')) {
errors.push({
...block,
lineInBlock: idx + 1,
issue: `包含特殊字符 #{}|&: "${line.trim()}"`,
suggestion: '用引号包裹含特殊字符的文本'
});
}
});
return errors;
}
// 主函数
function main() {
const rootDir = process.cwd();
console.log('🔍 扫描 Mermaid 语法问题...\n');
const mdFiles = findMdFiles(rootDir);
console.log(`📁 找到 ${mdFiles.length} 个 Markdown 文件\n`);
let totalBlocks = 0;
let totalErrors = 0;
const allErrors = [];
for (const file of mdFiles) {
const content = fs.readFileSync(file, 'utf-8');
const blocks = extractMermaidBlocks(content, file);
totalBlocks += blocks.length;
for (const block of blocks) {
const errors = checkMermaidSyntax(block);
if (errors.length > 0) {
allErrors.push(...errors);
totalErrors += errors.length;
}
}
}
console.log(`📊 共扫描 ${totalBlocks} 个 Mermaid 代码块\n`);
if (allErrors.length === 0) {
console.log('✅ 未发现明显的语法问题!\n');
} else {
console.log(`⚠️ 发现 ${totalErrors} 个潜在问题:\n`);
console.log('─'.repeat(80));
for (const error of allErrors) {
const relPath = path.relative(rootDir, error.file);
console.log(`\n📄 ${relPath}:${error.line}`);
console.log(` ❌ ${error.issue}`);
console.log(` 💡 ${error.suggestion}`);
}
console.log('\n' + '─'.repeat(80));
console.log(`\n📋 总结: ${allErrors.length} 个问题需要修复\n`);
}
}
main();
#!/usr/bin/env node
/**
* Mermaid 语法自动修复脚本
* 用法: node scripts/fix-mermaid.js
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// 查找所有 md 文件
function findMdFiles(dir, files = []) {
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory() && !item.startsWith('.') && item !== 'node_modules') {
findMdFiles(fullPath, files);
} else if (item.endsWith('.md')) {
files.push(fullPath);
}
}
return files;
}
// 修复 mermaid 代码块中的语法问题
function fixMermaidBlock(content) {
const lines = content.split('\n');
const fixedLines = lines.map(line => {
// 跳过空行和图表类型声明行
if (!line.trim() || line.match(/^(graph|flowchart|sequenceDiagram|classDiagram|stateDiagram|erDiagram|gantt|pie|journey|gitGraph|mindmap|timeline|quadrantChart|sankey|xychart)/)) {
return line;
}
let fixed = line;
// 1. 修复时序图消息中的 @ 符号: A->>B : 调用@xxx -> A->>B : "调用@xxx"
fixed = fixed.replace(/(->>|-->>|->|-->)\s*([^:]+)\s*:\s*([^"\n]*@[^"\n]*)/g, (match, arrow, target, message) => {
return `${arrow} ${target.trim()} : "${message.trim()}"`;
});
// 2. 修复类图中的属性/方法 @ 符号: +@observable xxx -> +"@observable xxx"
fixed = fixed.replace(/^(\s*)([+\-#~])(@[a-zA-Z]+.*)/gm, (match, indent, modifier, rest) => {
if (rest.includes('"')) return match;
return `${indent}${modifier}"${rest}"`;
});
// 3. 修复节点标签中的 @ 符号: A[@xxx] -> A["@xxx"]
fixed = fixed.replace(/\[([^\]"]*@[^\]"]*)](?!")/g, (match, inner) => {
return `["${inner}"]`;
});
// 4. 修复节点标签中的 # { } | & 特殊字符
fixed = fixed.replace(/\[([^\]"]*[#{}|&][^\]"]*)](?!")/g, (match, inner) => {
return `["${inner}"]`;
});
// 5. 修复边标签中的 @ 符号: |@Action注解| -> |"@Action注解"|
fixed = fixed.replace(/\|([^|"]*@[^|"]*)]\|/g, (match, inner) => {
return `|"${inner}"|`;
});
// 6. 修复菱形节点: {A@xxx} -> {"A@xxx"}
fixed = fixed.replace(/\{([^{}"]*@[^{}"]*)}(?!")/g, (match, inner) => {
return `{"${inner}"}`;
});
// 7. 修复类图中单独一行的注解: @BillPlugin -> "@BillPlugin"
if (fixed.trim().match(/^@[a-zA-Z]+/) && !fixed.includes('"')) {
fixed = fixed.replace(/^(\s*)(@[^\s"]+.*)$/gm, (match, indent, annotation) => {
return `${indent}"${annotation}"`;
});
}
return fixed;
});
return fixedLines.join('\n');
}
// 处理单个文件
function processFile(filePath) {
const content = fs.readFileSync(filePath, 'utf-8');
// 匹配并替换所有 mermaid 代码块
const mermaidRegex = /(```mermaid\n)([\s\S]*?)(```)/g;
let modified = false;
const newContent = content.replace(mermaidRegex, (match, start, mermaidContent, end) => {
const fixed = fixMermaidBlock(mermaidContent);
if (fixed !== mermaidContent) {
modified = true;
}
return start + fixed + end;
});
if (modified) {
fs.writeFileSync(filePath, newContent, 'utf-8');
return true;
}
return false;
}
// 主函数
function main() {
const rootDir = process.cwd();
console.log('🔧 自动修复 Mermaid 语法问题...\n');
const mdFiles = findMdFiles(rootDir);
console.log(`📁 找到 ${mdFiles.length} 个 Markdown 文件\n`);
let fixedCount = 0;
const fixedFiles = [];
for (const file of mdFiles) {
if (processFile(file)) {
fixedCount++;
fixedFiles.push(path.relative(rootDir, file));
}
}
if (fixedCount === 0) {
console.log('✅ 没有需要修复的文件\n');
} else {
console.log(`✅ 已修复 ${fixedCount} 个文件:\n`);
for (const file of fixedFiles) {
console.log(` 📄 ${file}`);
}
console.log('\n💡 建议运行 npm run check:mermaid 验证修复结果\n');
}
}
{
"name": "xyz-docs",
"version": "1.0.0",
"description": "xyz-docs",
"type": "module",
"scripts": {
"dev": "vitepress dev",
"build": "vitepress build",
"preview": "vitepress preview",
"check:mermaid": "node scripts/check-mermaid.js"
},
"devDependencies": {
"markdown-it-container": "^4.0.0",
"mermaid": "^11.12.2",
"vitepress": "^1.0.0",
"vitepress-sidebar": "^1.33.1"
}
}