import * as fs from 'fs'; import * as path from 'path'; const MDX_DIR = path.join(process.cwd(), 'content/blog'); /** * Convert to {`...`} * This fixes the RSC serialization issue where template literal props are stripped. */ function convertMermaidToChildren(content: string): string { // Match (self-closing) // We need a multi-pass approach since the graph prop can appear anywhere in the tag const mermaidBlockRegex = //g; return content.replace(mermaidBlockRegex, (match) => { // Extract the graph prop value (template literal) const graphMatch = match.match(/graph=\{`([\s\S]*?)`\}/); if (!graphMatch) return match; // No graph prop, skip const graphContent = graphMatch[1]; // Remove the graph prop from the tag let cleanedTag = match.replace(/\s*graph=\{`[\s\S]*?`\}\s*/g, ' '); // Remove the self-closing /> and add children cleanedTag = cleanedTag.replace(/\s*\/>$/, ''); // Clean up extra whitespace cleanedTag = cleanedTag.replace(/\s+/g, ' ').trim(); return `${cleanedTag}>\n{\`${graphContent}\`}\n`; }); } /** * Convert to {`pie\n "label": value\n...`} */ function convertDiagramPie(content: string): string { const pieRegex = //g; return content.replace(pieRegex, (match) => { // Extract data array const dataMatch = match.match(/data=\{\[([\s\S]*?)\]\}/); if (!dataMatch) return match; const dataStr = dataMatch[1]; // Parse the array items: { label: "...", value: ... } const items: { label: string; value: number }[] = []; const itemRegex = /\{\s*label:\s*"([^"]+)"\s*,\s*value:\s*(\d+)\s*\}/g; let itemMatch; while ((itemMatch = itemRegex.exec(dataStr)) !== null) { items.push({ label: itemMatch[1], value: parseInt(itemMatch[2]) }); } if (items.length === 0) return match; // Build pie chart Mermaid syntax const pieLines = items.map(item => ` "${item.label}" : ${item.value}`).join('\n'); const pieGraph = `pie\n${pieLines}`; // Extract other props const titleMatch = match.match(/title="([^"]+)"/); const captionMatch = match.match(/caption="([^"]+)"/); const idMatch = match.match(/id="([^"]+)"/); const showShareMatch = match.match(/showShare=\{(true|false)\}/); const title = titleMatch ? titleMatch[1] : ''; const caption = captionMatch ? captionMatch[1] : ''; const id = idMatch ? idMatch[1] : ''; const showShare = showShareMatch ? showShareMatch[1] : 'true'; // Build replacement with Mermaid component wrapped in div let result = `
\n `; result += `\n{\`${pieGraph}\`}\n`; if (caption) { result += `\n
\n ${caption}\n
`; } result += `\n
`; return result; }); } /** * Convert to {`gantt\n...`} */ function convertDiagramGantt(content: string): string { const ganttRegex = //g; return content.replace(ganttRegex, (match) => { // Extract tasks array const tasksMatch = match.match(/tasks=\{\[([\s\S]*?)\]\}/); if (!tasksMatch) return match; const tasksStr = tasksMatch[1]; // Parse the task items const taskRegex = /\{\s*id:\s*"([^"]+)"\s*,\s*name:\s*"([^"]+)"\s*,\s*start:\s*"([^"]+)"\s*,\s*duration:\s*"([^"]+)"\s*(?:,\s*dependencies:\s*\[([^\]]*)\])?\s*\}/g; const tasks: { id: string; name: string; start: string; duration: string; deps?: string }[] = []; let taskMatch; while ((taskMatch = taskRegex.exec(tasksStr)) !== null) { tasks.push({ id: taskMatch[1], name: taskMatch[2], start: taskMatch[3], duration: taskMatch[4], deps: taskMatch[5] || '', }); } if (tasks.length === 0) return match; // Build gantt chart Mermaid syntax const ganttLines = tasks.map(task => { const deps = task.deps ? `, after ${task.deps.replace(/"/g, '').trim()}` : ''; return ` ${task.name} :${task.id}, ${task.start}, ${task.duration}${deps}`; }).join('\n'); const ganttGraph = `gantt\n dateFormat YYYY-MM-DD\n${ganttLines}`; // Extract other props const titleMatch = match.match(/title="([^"]+)"/); const captionMatch = match.match(/caption="([^"]+)"/); const idMatch = match.match(/id="([^"]+)"/); const showShareMatch = match.match(/showShare=\{(true|false)\}/); const title = titleMatch ? titleMatch[1] : ''; const caption = captionMatch ? captionMatch[1] : ''; const id = idMatch ? idMatch[1] : ''; const showShare = showShareMatch ? showShareMatch[1] : 'true'; let result = `
\n `; result += `\n{\`${ganttGraph}\`}\n`; if (caption) { result += `\n
\n ${caption}\n
`; } result += `\n
`; return result; }); } /** * Convert to {`sequenceDiagram\n...`} */ function convertDiagramSequence(content: string): string { const seqRegex = //g; return content.replace(seqRegex, (match) => { // Extract participants array const participantsMatch = match.match(/participants=\{\[([\s\S]*?)\]\}/); if (!participantsMatch) return match; // Extract messages array const messagesMatch = match.match(/messages=\{\[([\s\S]*?)\]\}/); if (!messagesMatch) return match; const participantsStr = participantsMatch[1]; const messagesStr = messagesMatch[1]; // Parse participants const participants = participantsStr.match(/"([^"]+)"/g)?.map(s => s.replace(/"/g, '')) || []; // Parse messages const msgRegex = /\{\s*from:\s*"([^"]+)"\s*,\s*to:\s*"([^"]+)"\s*,\s*message:\s*"([^"]+)"(?:\s*,\s*type:\s*"([^"]+)")?\s*\}/g; const messages: { from: string; to: string; message: string; type?: string }[] = []; let msgMatch; while ((msgMatch = msgRegex.exec(messagesStr)) !== null) { messages.push({ from: msgMatch[1], to: msgMatch[2], message: msgMatch[3], type: msgMatch[4], }); } if (participants.length === 0 || messages.length === 0) return match; // Build sequence diagram Mermaid syntax const getArrow = (type?: string) => { switch (type) { case 'dotted': return '-->>'; case 'async': return '->>'; default: return '->>'; } }; const participantLines = participants.map(p => ` participant ${p}`).join('\n'); const messageLines = messages.map(m => ` ${m.from}${getArrow(m.type)}${m.to}: ${m.message}`).join('\n'); const seqGraph = `sequenceDiagram\n${participantLines}\n${messageLines}`; // Extract other props const titleMatch = match.match(/title="([^"]+)"/); const captionMatch = match.match(/caption="([^"]+)"/); const idMatch = match.match(/id="([^"]+)"/); const showShareMatch = match.match(/showShare=\{(true|false)\}/); const title = titleMatch ? titleMatch[1] : ''; const caption = captionMatch ? captionMatch[1] : ''; const id = idMatch ? idMatch[1] : ''; const showShare = showShareMatch ? showShareMatch[1] : 'true'; let result = `
\n `; result += `\n{\`${seqGraph}\`}\n`; if (caption) { result += `\n
\n ${caption}\n
`; } result += `\n
`; return result; }); } /** * Convert to {`timeline\n...`} */ function convertDiagramTimeline(content: string): string { const timelineRegex = //g; return content.replace(timelineRegex, (match) => { const eventsMatch = match.match(/events=\{\[([\s\S]*?)\]\}/); if (!eventsMatch) return match; const eventsStr = eventsMatch[1]; const eventRegex = /\{\s*year:\s*"([^"]+)"\s*,\s*title:\s*"([^"]+)"\s*\}/g; const events: { year: string; title: string }[] = []; let eventMatch; while ((eventMatch = eventRegex.exec(eventsStr)) !== null) { events.push({ year: eventMatch[1], title: eventMatch[2] }); } if (events.length === 0) return match; const titleMatch = match.match(/title="([^"]+)"/); const captionMatch = match.match(/caption="([^"]+)"/); const idMatch = match.match(/id="([^"]+)"/); const showShareMatch = match.match(/showShare=\{(true|false)\}/); const title = titleMatch ? titleMatch[1] : ''; const caption = captionMatch ? captionMatch[1] : ''; const id = idMatch ? idMatch[1] : ''; const showShare = showShareMatch ? showShareMatch[1] : 'true'; const eventLines = events.map(e => ` ${e.year} : ${e.title}`).join('\n'); const timelineGraph = `timeline\n title ${title || 'Timeline'}\n${eventLines}`; let result = `
\n `; result += `\n{\`${timelineGraph}\`}\n`; if (caption) { result += `\n
\n ${caption}\n
`; } result += `\n
`; return result; }); } /** * Convert to {`stateDiagram-v2\n...`} */ function convertDiagramState(content: string): string { const stateRegex = //g; return content.replace(stateRegex, (match) => { // Extract transitions const transitionsMatch = match.match(/transitions=\{\[([\s\S]*?)\]\}/); if (!transitionsMatch) return match; const transitionsStr = transitionsMatch[1]; const transRegex = /\{\s*from:\s*"([^"]+)"\s*,\s*to:\s*"([^"]+)"(?:\s*,\s*label:\s*"([^"]+)")?\s*\}/g; const transitions: { from: string; to: string; label?: string }[] = []; let transMatch; while ((transMatch = transRegex.exec(transitionsStr)) !== null) { transitions.push({ from: transMatch[1], to: transMatch[2], label: transMatch[3], }); } if (transitions.length === 0) return match; // Extract initialState const initialStateMatch = match.match(/initialState="([^"]+)"/); const initialState = initialStateMatch ? initialStateMatch[1] : ''; // Extract finalStates const finalStatesMatch = match.match(/finalStates=\{\[([^\]]*)\]\}/); const finalStatesStr = finalStatesMatch ? finalStatesMatch[1] : ''; const finalStates = finalStatesStr.match(/"([^"]+)"/g)?.map(s => s.replace(/"/g, '')) || []; const titleMatch = match.match(/title="([^"]+)"/); const captionMatch = match.match(/caption="([^"]+)"/); const idMatch = match.match(/id="([^"]+)"/); const showShareMatch = match.match(/showShare=\{(true|false)\}/); const title = titleMatch ? titleMatch[1] : ''; const caption = captionMatch ? captionMatch[1] : ''; const id = idMatch ? idMatch[1] : ''; const showShare = showShareMatch ? showShareMatch[1] : 'true'; let stateLines = 'stateDiagram-v2'; if (initialState) { stateLines += `\n [*] --> ${initialState}`; } stateLines += '\n' + transitions.map(t => { const label = t.label ? ` : ${t.label}` : ''; return ` ${t.from} --> ${t.to}${label}`; }).join('\n'); stateLines += '\n' + finalStates.map(s => ` ${s} --> [*]`).join('\n'); let result = `
\n `; result += `\n{\`${stateLines}\`}\n`; if (caption) { result += `\n
\n ${caption}\n
`; } result += `\n
`; return result; }); } function processFiles() { const files = fs.readdirSync(MDX_DIR).filter(f => f.endsWith('.mdx')); for (const file of files) { const filePath = path.join(MDX_DIR, file); let content = fs.readFileSync(filePath, 'utf8'); const original = content; content = convertMermaidToChildren(content); content = convertDiagramPie(content); content = convertDiagramGantt(content); content = convertDiagramSequence(content); content = convertDiagramTimeline(content); content = convertDiagramState(content); if (content !== original) { fs.writeFileSync(filePath, content); console.log(`✅ Converted diagrams in ${file}`); } else { console.log(`- ${file} (no changes needed)`); } } } processFiles();