Files
claudeskill/demo_scripts/check-mermaid.js
Misaka_Company 1351371bd2 feat: Add mermaid-fixer skill for automatic Mermaid syntax error correction
- Add skill that uses check-mermaid.js to validate Mermaid diagrams
- Skill guides Claude to parse error reports and apply intelligent fixes
- Fixes common Mermaid parser bugs (parentheses, brackets, braces in labels)
- Include demo scripts with test Markdown file for validation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 13:32:55 +08:00

128 lines
4.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
const fs = require('fs');
const { execSync } = require('child_process');
const path = require('path');
const os = require('os');
// 获取命令行传入的 Markdown 文件路径
const fileArg = process.argv[2];
if (!fileArg) {
console.error('❌ 请提供 Markdown 文件路径。用法: node check-mermaid.js <file.md>');
process.exit(1);
}
const filePath = path.resolve(fileArg);
const fileName = path.basename(filePath);
if (!fs.existsSync(filePath)) {
console.error(`❌ 文件不存在: ${filePath}`);
process.exit(1);
}
const content = fs.readFileSync(filePath, 'utf-8');
// 正则匹配 ```mermaid ... ``` 代码块,兼容 Windows(\r\n) 和 Linux(\n)
const mermaidRegex = /```mermaid\r?\n([\s\S]*?)```/g;
let match;
let blockCount = 0;
let errorCount = 0;
// 用于收集所有格式化后的错误报告块
const errorReports = [];
while ((match = mermaidRegex.exec(content)) !== null) {
blockCount++;
const fullMatch = match[0];
const code = match[1];
// 1. 计算在 Markdown 文件中的绝对行号
const textBeforeMatch = content.substring(0, match.index);
const startLine = textBeforeMatch.split(/\r?\n/).length;
const endLine = startLine + fullMatch.split(/\r?\n/).length - 1;
// 创建临时文件存放单个 Mermaid 代码
const tmpFile = path.join(os.tmpdir(), `mermaid-check-${Date.now()}-${blockCount}.mmd`);
fs.writeFileSync(tmpFile, code.trim());
try {
// 调用 mmdc 进行静默渲染检查
execSync(`npx mmdc -i "${tmpFile}" -o "${tmpFile}.svg" -q`, { stdio: 'pipe' });
} catch (error) {
errorCount++;
// 2. 净化报错信息:剔除底层执行堆栈
const stderr = error.stderr ? error.stderr.toString() : error.message;
const errorLines = stderr.split(/\r?\n/);
const cleanErrorLines = [];
for (const line of errorLines) {
if (line.trim().startsWith('at ') ||
line.includes('Parser3.parseError') ||
line.includes('fromText')) {
break;
}
cleanErrorLines.push(line);
}
const cleanErrorOutput = cleanErrorLines.join('\n').trim();
// 3. 截断代码内容最少3行最多6行
const codeLines = code.split(/\r?\n/).filter(l => l.trim() !== '');
let displayCode = '';
if (codeLines.length <= 6) {
displayCode = codeLines.join('\n');
} else {
const head = codeLines.slice(0, 3).join('\n');
const tail = codeLines.slice(-3).join('\n');
displayCode = `${head}\n ...\n ... (中间省略 ${codeLines.length - 6} 行) ...\n ...\n${tail}`;
}
// 4. 组装单个错误的 Markdown 块
errorReports.push(`### ❌ 错误 #${errorCount} (代码块 #${blockCount})
- **文档位置:** 第 \`${startLine}\` 行至第 \`${endLine}\`
#### 核心错误详情
\`\`\`text
${cleanErrorOutput}
\`\`\`
#### 代码内容片段
\`\`\`text
${displayCode}
\`\`\``);
} finally {
// 清理临时文件
if (fs.existsSync(tmpFile)) fs.unlinkSync(tmpFile);
if (fs.existsSync(`${tmpFile}.svg`)) fs.unlinkSync(`${tmpFile}.svg`);
}
}
// ------------------------------------------------------------------
// 生成最终的 Markdown 报告输出
// ------------------------------------------------------------------
if (errorCount > 0) {
// 存在错误的情况
console.log(`## 🚨 Mermaid 语法检查报告\n`);
console.log(`**检查文件:** \`${fileName}\``);
console.log(`**检查结果:** ❌ 发现 ${errorCount} 处语法错误 (共检测到 ${blockCount} 个代码块)\n`);
console.log(`---\n`);
console.log(errorReports.join('\n\n---\n\n'));
// 返回非零状态码,确保在 CI/CD 或 Git Hook 中能够阻断流程
process.exit(1);
} else {
// 全部正确或没有代码块的情况
console.log(`## 🎉 Mermaid 语法检查报告\n`);
console.log(`**检查文件:** \`${fileName}\``);
if (blockCount === 0) {
console.log(`**检查结果:** ⚠️ 未检测到 Mermaid 代码块`);
} else {
console.log(`**检查结果:** ✅ 全部通过 (共检测到 ${blockCount} 个代码块)`);
}
process.exit(0);
}