const fs = require('node:fs'); const path = require('node:path'); // 文档是唯一编辑源;本脚本只生成图集和展示文件。 const root = __dirname; const bundlePackages = process.env.DIAGRAM_NODE_MODULES || 'C:/Users/mk/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules'; const { chromium } = require(path.join(bundlePackages, 'playwright')); const escapeHtml = text => text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"'); const files = fs.readdirSync(root).filter(name => /^G0[1-5]-.+\.md$/.test(name)).sort(); async function main() { const cache = path.join(root, '.render-cache'); fs.mkdirSync(cache, { recursive: true }); const library = path.join(cache, 'mermaid-11.12.0.min.js'); if (!fs.existsSync(library)) { const response = await fetch('https://cdn.jsdelivr.net/npm/mermaid@11.12.0/dist/mermaid.min.js'); if (!response.ok) throw new Error(`Mermaid download: ${response.status}`); fs.writeFileSync(library, await response.text()); } const executablePath = process.env.DIAGRAM_BROWSER || 'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe'; const browser = await chromium.launch({ executablePath, headless: true }); const page = await browser.newPage({ viewport: { width: 1800, height: 1200 }, deviceScaleFactor: 1.5 }); await page.setContent('
'); await page.addScriptTag({ path: library }); await page.evaluate(() => mermaid.initialize({ startOnLoad: false, securityLevel: 'strict', theme: 'base', fontFamily: 'Microsoft YaHei, Arial, sans-serif', themeVariables: { fontSize: '20px', primaryColor: '#eef5ff', primaryTextColor: '#17233a', primaryBorderColor: '#8198bb', lineColor: '#60718a', secondaryColor: '#f3f8f6', tertiaryColor: '#ffffff', clusterBkg: '#f8fafc', clusterBorder: '#cbd5e1', actorBkg: '#edf3fb', actorBorder: '#8198bb', actorTextColor: '#17233a', signalColor: '#334155', signalTextColor: '#17233a', labelBoxBkgColor: '#f3f6fa', labelBoxBorderColor: '#9aabc1', labelTextColor: '#17233a', loopTextColor: '#17233a', noteBkgColor: '#fffbea', noteBorderColor: '#d4c79f', noteTextColor: '#17233a' }, flowchart: { htmlLabels: false, useMaxWidth: false, nodeSpacing: 32, rankSpacing: 58, padding: 18, wrappingWidth: 310, curve: 'basis' }, sequence: { useMaxWidth: false, wrap: true, width: 240, actorMargin: 62, diagramMarginX: 30, diagramMarginY: 20, messageMargin: 40, boxMargin: 14, mirrorActors: false } })); const manifest = []; for (const file of files) { const md = fs.readFileSync(path.join(root, file), 'utf8'); const title = md.match(/^# (.+)$/m)[1]; const conclusion = md.match(/^\*\*(.+)\*\*$/m)[1]; const sources = [...md.matchAll(/```mermaid\r?\n([\s\S]+?)\r?\n```/g)]; const images = [...md.matchAll(/!\[([^\]]+)\]\(([^)]+)\.svg\)/g)]; if (sources.length !== images.length) throw new Error(`${file}: sources and images differ`); const diagrams = []; for (let index = 0; index < sources.length; index++) { const name = images[index][2]; const source = sources[index][1]; fs.writeFileSync(path.join(root, `${name}.mmd`), source + '\n'); const rendered = await page.evaluate(async ({ source, id }) => { const { svg } = await mermaid.render(id, source); document.getElementById('canvas').innerHTML = svg; const element = document.querySelector('#canvas svg'); const view = element.viewBox.baseVal; return { svg, width: view.width, height: view.height }; }, { source, id: `diagram${file.slice(1, 3)}x${index}` }); if (!rendered.width || !rendered.height || /Syntax error/.test(rendered.svg)) throw new Error(`${name}: invalid render`); const header = 124; const width = Math.max(1000, Math.ceil(rendered.width + 64)); const height = Math.ceil(rendered.height + header + 48); const wrapped = ``; fs.writeFileSync(path.join(root, `${name}.svg`), wrapped); await page.evaluate(svg => { document.getElementById('canvas').innerHTML = svg; }, wrapped); const scale = Math.min(1, 2200 / width); await page.evaluate(({ width, height }) => { const svg = document.querySelector('#canvas > svg'); svg.style.width = `${width}px`; svg.style.height = `${height}px`; document.getElementById('canvas').style.width = `${width}px`; }, { width: Math.ceil(width * scale), height: Math.ceil(height * scale) }); await page.locator('#canvas').screenshot({ path: path.join(root, `${name}.png`) }); diagrams.push({ name, label: images[index][1], width, height, svg: wrapped }); console.log(`${name}: ${width}x${height}`); } manifest.push({ file, title, conclusion, diagrams }); } const menu = manifest.map((entry, index) => ``).join(''); const panels = manifest.map((entry, index) => `${escapeHtml(entry.conclusion)}
阅读 Markdown 与编辑源${entry.diagrams.map(graph => ` · ${escapeHtml(graph.label)} SVG · PNG`).join('')}
${entry.diagrams.map(graph => `G01–G05 · 2026-10-12 · 业务基线 0.14 · 图集 0.17