Files
mintel.me/apps/web/scripts/convert-diagrams-to-children.ts

366 lines
14 KiB
TypeScript

import * as fs from 'fs';
import * as path from 'path';
const MDX_DIR = path.join(process.cwd(), 'content/blog');
/**
* Convert <Mermaid graph={`...`} id="..." ... /> to <Mermaid id="..." ...>{`...`}</Mermaid>
* This fixes the RSC serialization issue where template literal props are stripped.
*/
function convertMermaidToChildren(content: string): string {
// Match <Mermaid ... graph={`...`} ... /> (self-closing)
// We need a multi-pass approach since the graph prop can appear anywhere in the tag
const mermaidBlockRegex = /<Mermaid\s+([\s\S]*?)\/>/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</Mermaid>`;
});
}
/**
* Convert <DiagramPie data={[...]} ... /> to <Mermaid ...>{`pie\n "label": value\n...`}</Mermaid>
*/
function convertDiagramPie(content: string): string {
const pieRegex = /<DiagramPie\s+([\s\S]*?)\/>/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 = `<div className="my-12">\n <Mermaid`;
if (id) result += ` id="${id}"`;
if (title) result += ` title="${title}"`;
result += ` showShare={${showShare}}>`;
result += `\n{\`${pieGraph}\`}\n</Mermaid>`;
if (caption) {
result += `\n <div className="text-center text-xs text-slate-400 mt-4 italic">\n ${caption}\n </div>`;
}
result += `\n</div>`;
return result;
});
}
/**
* Convert <DiagramGantt tasks={[...]} ... /> to <Mermaid ...>{`gantt\n...`}</Mermaid>
*/
function convertDiagramGantt(content: string): string {
const ganttRegex = /<DiagramGantt\s+([\s\S]*?)\/>/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 = `<div className="my-12">\n <Mermaid`;
if (id) result += ` id="${id}"`;
if (title) result += ` title="${title}"`;
result += ` showShare={${showShare}}>`;
result += `\n{\`${ganttGraph}\`}\n</Mermaid>`;
if (caption) {
result += `\n <div className="text-center text-xs text-slate-400 mt-4 italic">\n ${caption}\n </div>`;
}
result += `\n</div>`;
return result;
});
}
/**
* Convert <DiagramSequence participants={[...]} messages={[...]} ... /> to <Mermaid ...>{`sequenceDiagram\n...`}</Mermaid>
*/
function convertDiagramSequence(content: string): string {
const seqRegex = /<DiagramSequence\s+([\s\S]*?)\/>/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 = `<div className="my-12">\n <Mermaid`;
if (id) result += ` id="${id}"`;
if (title) result += ` title="${title}"`;
result += ` showShare={${showShare}}>`;
result += `\n{\`${seqGraph}\`}\n</Mermaid>`;
if (caption) {
result += `\n <div className="text-center text-xs text-slate-400 mt-4 italic">\n ${caption}\n </div>`;
}
result += `\n</div>`;
return result;
});
}
/**
* Convert <DiagramTimeline events={[...]} ... /> to <Mermaid ...>{`timeline\n...`}</Mermaid>
*/
function convertDiagramTimeline(content: string): string {
const timelineRegex = /<DiagramTimeline\s+([\s\S]*?)\/>/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 = `<div className="my-12">\n <Mermaid`;
if (id) result += ` id="${id}"`;
if (title) result += ` title="${title}"`;
result += ` showShare={${showShare}}>`;
result += `\n{\`${timelineGraph}\`}\n</Mermaid>`;
if (caption) {
result += `\n <div className="text-center text-xs text-slate-400 mt-4 italic">\n ${caption}\n </div>`;
}
result += `\n</div>`;
return result;
});
}
/**
* Convert <DiagramState states={[...]} transitions={[...]} ... /> to <Mermaid ...>{`stateDiagram-v2\n...`}</Mermaid>
*/
function convertDiagramState(content: string): string {
const stateRegex = /<DiagramState\s+([\s\S]*?)\/>/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 = `<div className="my-12">\n <Mermaid`;
if (id) result += ` id="${id}"`;
if (title) result += ` title="${title}"`;
result += ` showShare={${showShare}}>`;
result += `\n{\`${stateLines}\`}\n</Mermaid>`;
if (caption) {
result += `\n <div className="text-center text-xs text-slate-400 mt-4 italic">\n ${caption}\n </div>`;
}
result += `\n</div>`;
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();