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 = `${escapeHtml(title)} · ${escapeHtml(images[index][1])}${escapeHtml(conclusion)};2026-10-12,业务基线 0.14,图集版本 0.17${escapeHtml(index ? `${title.slice(0, 3)} · ${images[index][1]}` : title)}2026-10-12 · 业务基线 0.14 · 图集 0.17 · ${index ? '展开图' : '主图'} · 技术选型未定${rendered.svg}`; 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.title)}

${escapeHtml(entry.conclusion)}

${entry.diagrams.map(graph => `

${escapeHtml(graph.label)}

${graph.svg}
`).join('')}
`).join(''); const html = `AI 软件交付平台 · G01–G05 会议图集
AI 软件交付与持续服务平台 · 会议图集

G01–G05 · 2026-10-12 · 业务基线 0.14 · 图集 0.17

${panels}
`; fs.writeFileSync(path.join(root, 'G01-G05-会议图集.html'), html); fs.writeFileSync(path.join(root, 'render-manifest.json'), JSON.stringify({ date: '2026-10-12', baseline: '0.14', version: '0.17', mermaid: '11.12.0', diagrams: manifest.map(({ file, title, diagrams }) => ({ file, title, diagrams: diagrams.map(({ svg, ...graph }) => graph) })) }, null, 2) + '\n'); await page.goto('file:///' + path.join(root, 'G01-G05-会议图集.html').replaceAll('\\', '/')); for (let index = 0; index < manifest.length; index++) { await page.locator(`button[data-index="${index}"]`).click(); await page.screenshot({ path: path.join(cache, `review-G0${index + 1}.png`), fullPage: true }); } await browser.close(); } main().catch(error => { console.error(error); process.exit(1); });