366 lines
14 KiB
TypeScript
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();
|