feat: complete MDX migration for blog, fix diagram fidelity and refactor styling architecture
This commit is contained in:
46
apps/web/scripts/clean-mermaid-format.ts
Normal file
46
apps/web/scripts/clean-mermaid-format.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const MDX_DIR = path.join(process.cwd(), 'content/blog');
|
||||
|
||||
function cleanMermaidFormatting(content: string): string {
|
||||
// Fix: The repair script reformatted <Mermaid> tags badly with extra blank lines
|
||||
// Pattern: <Mermaid\n \n graph={`...`}\n \n />
|
||||
// Should be: <Mermaid\n graph={`...`}\n id="..."\n .../>
|
||||
|
||||
// Remove empty lines between <Mermaid and graph=
|
||||
let result = content.replace(/<Mermaid\s*\n\s*\n\s*graph=/g, '<Mermaid\n graph=');
|
||||
|
||||
// Remove trailing empty space before />
|
||||
result = result.replace(/`\}\s*\n\s*\n\s*\/>/g, '`}\n />');
|
||||
|
||||
// Fix: Remove trailing whitespace-only lines before id= or title= or showShare=
|
||||
result = result.replace(/`\}\s*\n\s*\n\s*(id=)/g, '`}\n $1');
|
||||
result = result.replace(/`\}\s*\n\s*\n\s*(title=)/g, '`}\n $1');
|
||||
result = result.replace(/`\}\s*\n\s*\n\s*(showShare=)/g, '`}\n $1');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function processFiles() {
|
||||
const files = fs.readdirSync(MDX_DIR).filter(f => f.endsWith('.mdx'));
|
||||
let fixCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(MDX_DIR, file);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const cleaned = cleanMermaidFormatting(content);
|
||||
|
||||
if (content !== cleaned) {
|
||||
fs.writeFileSync(filePath, cleaned);
|
||||
fixCount++;
|
||||
console.log(`✅ Cleaned formatting in ${file}`);
|
||||
} else {
|
||||
console.log(`- ${file} OK`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nTotal cleaned: ${fixCount}`);
|
||||
}
|
||||
|
||||
processFiles();
|
||||
365
apps/web/scripts/convert-diagrams-to-children.ts
Normal file
365
apps/web/scripts/convert-diagrams-to-children.ts
Normal file
@@ -0,0 +1,365 @@
|
||||
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();
|
||||
56
apps/web/scripts/convert-to-children-clean.ts
Normal file
56
apps/web/scripts/convert-to-children-clean.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const MDX_DIR = path.join(process.cwd(), 'content/blog');
|
||||
|
||||
/**
|
||||
* Convert ugly single-line graph="..." props to clean multi-line children.
|
||||
*
|
||||
* FROM:
|
||||
* <Mermaid graph="graph TD\n A-->B\n B-->C" id="..." title="..." />
|
||||
*
|
||||
* TO:
|
||||
* <Mermaid id="..." title="...">
|
||||
* graph TD
|
||||
* A-->B
|
||||
* B-->C
|
||||
* </Mermaid>
|
||||
*/
|
||||
function convertToChildren(content: string): string {
|
||||
// Match <Mermaid graph="..." ... />
|
||||
const mermaidRegex = /<Mermaid\s+graph="([^"]*)"([^>]*?)\/>/g;
|
||||
|
||||
return content.replace(mermaidRegex, (match, graphValue, otherProps) => {
|
||||
// Unescape \n to real newlines
|
||||
const cleanGraph = graphValue.replace(/\\n/g, '\n');
|
||||
|
||||
// Clean up other props
|
||||
const cleanProps = otherProps.trim();
|
||||
|
||||
// Build the new format with children
|
||||
return `<Mermaid${cleanProps ? ' ' + cleanProps : ''}>\n${cleanGraph}\n</Mermaid>`;
|
||||
});
|
||||
}
|
||||
|
||||
function processFiles() {
|
||||
const files = fs.readdirSync(MDX_DIR).filter(f => f.endsWith('.mdx'));
|
||||
let fixCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(MDX_DIR, file);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const fixed = convertToChildren(content);
|
||||
|
||||
if (content !== fixed) {
|
||||
fs.writeFileSync(filePath, fixed);
|
||||
fixCount++;
|
||||
console.log(`✅ Converted to children: ${file}`);
|
||||
} else {
|
||||
console.log(`- ${file} (no changes needed)`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nTotal converted: ${fixCount}`);
|
||||
}
|
||||
|
||||
processFiles();
|
||||
46
apps/web/scripts/convert-to-plain-prop.ts
Normal file
46
apps/web/scripts/convert-to-plain-prop.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const MDX_DIR = path.join(process.cwd(), 'content/blog');
|
||||
|
||||
/**
|
||||
* Convert graph={"..."} to graph="..." (plain string prop without JSX expression wrapper).
|
||||
* MDXRemote RSC strips JSX expression props but keeps plain string props.
|
||||
*
|
||||
* But we also need the escape sequences to go through.
|
||||
* The plain string prop `graph="graph TD\nA-->B"` will have the \n treated as
|
||||
* literal characters by MDX's parser, not as a newline. The Mermaid component
|
||||
* then unescapes them.
|
||||
*/
|
||||
function convertToPlainStringProps(content: string): string {
|
||||
// Match graph={" ... "} and convert to graph="..."
|
||||
// The content inside should already have escaped newlines and quotes
|
||||
const pattern = /graph=\{"((?:[^"\\]|\\.)*)"\}/g;
|
||||
|
||||
return content.replace(pattern, (match, graphContent) => {
|
||||
return `graph="${graphContent}"`;
|
||||
});
|
||||
}
|
||||
|
||||
function processFiles() {
|
||||
const files = fs.readdirSync(MDX_DIR).filter(f => f.endsWith('.mdx'));
|
||||
let fixCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(MDX_DIR, file);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const fixed = convertToPlainStringProps(content);
|
||||
|
||||
if (content !== fixed) {
|
||||
fs.writeFileSync(filePath, fixed);
|
||||
fixCount++;
|
||||
console.log(`✅ Converted: ${file}`);
|
||||
} else {
|
||||
console.log(`- ${file} (no changes needed)`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nTotal converted: ${fixCount}`);
|
||||
}
|
||||
|
||||
processFiles();
|
||||
60
apps/web/scripts/convert-to-plain-string.ts
Normal file
60
apps/web/scripts/convert-to-plain-string.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const MDX_DIR = path.join(process.cwd(), 'content/blog');
|
||||
|
||||
/**
|
||||
* Convert all Mermaid children syntax back to graph prop,
|
||||
* BUT use a regular double-quoted string with escaped newlines instead of template literals.
|
||||
*
|
||||
* MDXRemote RSC strips template literals!
|
||||
*
|
||||
* Convert:
|
||||
* <Mermaid id="..." title="..." showShare={true}>
|
||||
* {`graph TD
|
||||
* A --> B`}
|
||||
* </Mermaid>
|
||||
*
|
||||
* To:
|
||||
* <Mermaid graph={"graph TD\n A --> B"} id="..." title="..." showShare={true} />
|
||||
*/
|
||||
function convertToPlainStringProp(content: string): string {
|
||||
// Match <Mermaid ...>{\`...\`}</Mermaid>
|
||||
const mermaidChildrenRegex = /<Mermaid\s+([^>]*?)>\s*\{`([\s\S]*?)`\}\s*<\/Mermaid>/g;
|
||||
|
||||
return content.replace(mermaidChildrenRegex, (match, propsStr, graphContent) => {
|
||||
// Escape double quotes in the graph content
|
||||
const escapedGraph = graphContent
|
||||
.replace(/\\/g, '\\\\') // escape backslashes first
|
||||
.replace(/"/g, '\\"') // escape double quotes
|
||||
.replace(/\n/g, '\\n'); // escape newlines
|
||||
|
||||
// Clean up props string
|
||||
const cleanProps = propsStr.trim();
|
||||
|
||||
return `<Mermaid graph={"${escapedGraph}"} ${cleanProps} />`;
|
||||
});
|
||||
}
|
||||
|
||||
function processFiles() {
|
||||
const files = fs.readdirSync(MDX_DIR).filter(f => f.endsWith('.mdx'));
|
||||
let fixCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(MDX_DIR, file);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const fixed = convertToPlainStringProp(content);
|
||||
|
||||
if (content !== fixed) {
|
||||
fs.writeFileSync(filePath, fixed);
|
||||
fixCount++;
|
||||
console.log(`✅ Converted to plain string: ${file}`);
|
||||
} else {
|
||||
console.log(`- ${file} (no Mermaid children found)`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nTotal converted: ${fixCount}`);
|
||||
}
|
||||
|
||||
processFiles();
|
||||
47
apps/web/scripts/final-diagram-fix.ts
Normal file
47
apps/web/scripts/final-diagram-fix.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const MDX_DIR = path.join(process.cwd(), 'content/blog');
|
||||
|
||||
/**
|
||||
* FINAL ATTEMPT: Standardize EVERYTHING in Mermaid blocks to double quotes.
|
||||
*
|
||||
* 1. Find all text inside <Mermaid>...</Mermaid>.
|
||||
* 2. Replace any ['Label'] or ['Label's'] or ["Label"] patterns.
|
||||
* 3. Enforce ["Label"] for all labels.
|
||||
* 4. Remove any internal single quotes that break parsing.
|
||||
*/
|
||||
function finalMermaidFix(content: string): string {
|
||||
const mermaidRegex = /(<Mermaid[^>]*>)([\s\S]*?)(<\/Mermaid>)/g;
|
||||
|
||||
return content.replace(mermaidRegex, (match, open, body, close) => {
|
||||
let fixedBody = body;
|
||||
|
||||
// Convert common label syntax to clean double quotes
|
||||
// Match: [followed by optional space and any quote, capture content, end with optional quote and space]
|
||||
fixedBody = fixedBody.replace(/\[\s*['"]?([^\]'"]+?)['"]?\s*\]/g, (m, label) => {
|
||||
// Clean the label: remove any internal quotes that could cause issues
|
||||
const cleanLabel = label.replace(/['"]/g, "").trim();
|
||||
return `["${cleanLabel}"]`;
|
||||
});
|
||||
|
||||
// Also handle Pie charts which use 'Label' : value
|
||||
fixedBody = fixedBody.replace(/^\s*'([^']+)'\s*:/gm, (m, label) => {
|
||||
const cleanLabel = label.replace(/['"]/g, "").trim();
|
||||
return ` "${cleanLabel}" :`;
|
||||
});
|
||||
|
||||
return open + fixedBody + close;
|
||||
});
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(MDX_DIR).filter(f => f.endsWith('.mdx'));
|
||||
for (const file of files) {
|
||||
const fp = path.join(MDX_DIR, file);
|
||||
const content = fs.readFileSync(fp, 'utf8');
|
||||
const fixed = finalMermaidFix(content);
|
||||
if (content !== fixed) {
|
||||
fs.writeFileSync(fp, fixed);
|
||||
console.log(`✅ Fixed potentially problematic syntax in: ${file}`);
|
||||
}
|
||||
}
|
||||
86
apps/web/scripts/fix-graph-quotes.ts
Normal file
86
apps/web/scripts/fix-graph-quotes.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const MDX_DIR = path.join(process.cwd(), 'content/blog');
|
||||
|
||||
/**
|
||||
* Fix escaped double quotes in Mermaid graph props.
|
||||
*
|
||||
* The graph prop contains \" which is invalid in MDX attribute syntax.
|
||||
* Replace \" with ' (single quote) - Mermaid supports both.
|
||||
*
|
||||
* Also fix \\n to just \n (single backslash) - the MDX parser
|
||||
* will treat \n as literal characters, and the Mermaid component
|
||||
* will unescape them.
|
||||
*/
|
||||
function fixGraphQuotes(content: string): string {
|
||||
// Match graph="..." prop (the entire value including escaped content)
|
||||
// This is tricky because the value can contain escaped quotes
|
||||
// We need to match from graph=" to the closing " that ends the prop value
|
||||
|
||||
// Strategy: Find graph=" then scan forward, tracking escape sequences
|
||||
const graphPropStart = 'graph="';
|
||||
let result = '';
|
||||
let i = 0;
|
||||
|
||||
while (i < content.length) {
|
||||
const idx = content.indexOf(graphPropStart, i);
|
||||
if (idx === -1) {
|
||||
result += content.slice(i);
|
||||
break;
|
||||
}
|
||||
|
||||
// Copy everything up to and including graph="
|
||||
result += content.slice(i, idx + graphPropStart.length);
|
||||
|
||||
// Now scan the value, replacing \" with '
|
||||
let j = idx + graphPropStart.length;
|
||||
let graphValue = '';
|
||||
|
||||
while (j < content.length) {
|
||||
if (content[j] === '\\' && content[j + 1] === '"') {
|
||||
// Replace \" with '
|
||||
graphValue += "'";
|
||||
j += 2;
|
||||
} else if (content[j] === '\\' && content[j + 1] === '\\') {
|
||||
// Keep \\ as is
|
||||
graphValue += '\\\\';
|
||||
j += 2;
|
||||
} else if (content[j] === '"') {
|
||||
// End of attribute value
|
||||
break;
|
||||
} else {
|
||||
graphValue += content[j];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
result += graphValue;
|
||||
i = j; // Continue from the closing quote (will be added in next iteration)
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function processFiles() {
|
||||
const files = fs.readdirSync(MDX_DIR).filter(f => f.endsWith('.mdx'));
|
||||
let fixCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(MDX_DIR, file);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const fixed = fixGraphQuotes(content);
|
||||
|
||||
if (content !== fixed) {
|
||||
fs.writeFileSync(filePath, fixed);
|
||||
fixCount++;
|
||||
console.log(`✅ Fixed quotes: ${file}`);
|
||||
} else {
|
||||
console.log(`- ${file} (no changes needed)`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nTotal fixed: ${fixCount}`);
|
||||
}
|
||||
|
||||
processFiles();
|
||||
123
apps/web/scripts/fix-mdx-whitespace.ts
Normal file
123
apps/web/scripts/fix-mdx-whitespace.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env tsx
|
||||
|
||||
/**
|
||||
* Fix missing whitespace in MDX files by comparing with TSX originals
|
||||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// Mapping of TSX component names to MDX slugs
|
||||
const TSX_TO_MDX_MAP: Record<string, string> = {
|
||||
'PageSpeedFails': 'why-pagespeed-fails',
|
||||
'SlowLoadingDebt': 'slow-loading-costs-customers',
|
||||
'AgencySlowdown': 'why-agencies-are-slow',
|
||||
'WordPressPlugins': 'hidden-costs-of-wordpress-plugins',
|
||||
'WebsiteStability': 'why-websites-break-after-updates',
|
||||
'CookieFreeDesign': 'website-without-cookie-banners',
|
||||
'LocalCloud': 'no-us-cloud-platforms',
|
||||
'GDPRSystem': 'gdpr-conformity-system-approach',
|
||||
'VendorLockIn': 'builder-systems-threaten-independence',
|
||||
'PrivacyAnalytics': 'analytics-without-tracking',
|
||||
'GreenIT': 'green-it-sustainable-web',
|
||||
'FixedPrice': 'fixed-price-digital-projects',
|
||||
'BuildFirst': 'build-first-digital-architecture',
|
||||
'MaintenanceNoCMS': 'maintenance-for-headless-systems',
|
||||
'Longevity': 'digital-longevity-architecture',
|
||||
'CleanCode': 'clean-code-for-business-value',
|
||||
'ResponsiveDesign': 'responsive-design-high-fidelity',
|
||||
'HostingOps': 'professional-hosting-operations',
|
||||
'NoTemplates': 'why-no-templates-matter',
|
||||
'CRMSync': 'crm-synchronization-headless',
|
||||
};
|
||||
|
||||
const TSX_BASE = path.join(process.cwd(), 'src/components/blog/posts');
|
||||
const MDX_BASE = path.join(process.cwd(), 'content/blog');
|
||||
|
||||
function findTsxFile(componentName: string): string | null {
|
||||
for (const group of ['Group1', 'Group2', 'Group3', 'Group4']) {
|
||||
const tsxPath = path.join(TSX_BASE, group, `${componentName}.tsx`);
|
||||
if (fs.existsSync(tsxPath)) {
|
||||
return tsxPath;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function fixWhitespace() {
|
||||
let totalFixed = 0;
|
||||
|
||||
for (const [tsxName, mdxSlug] of Object.entries(TSX_TO_MDX_MAP)) {
|
||||
const tsxPath = findTsxFile(tsxName);
|
||||
const mdxPath = path.join(MDX_BASE, `${mdxSlug}.mdx`);
|
||||
|
||||
if (!tsxPath || !fs.existsSync(mdxPath)) {
|
||||
console.log(`⚠️ Skipping ${tsxName}: files not found`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const tsxContent = fs.readFileSync(tsxPath, 'utf-8');
|
||||
let mdxContent = fs.readFileSync(mdxPath, 'utf-8');
|
||||
|
||||
// Count occurrences of {" "} in both files
|
||||
const tsxSpaces = (tsxContent.match(/\{" "\}/g) || []).length;
|
||||
const mdxSpacesBefore = (mdxContent.match(/\{" "\}/g) || []).length;
|
||||
|
||||
if (tsxSpaces === 0) {
|
||||
console.log(`✓ ${mdxSlug}: No whitespace needed`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract all lines with {" "} from TSX
|
||||
const tsxLines = tsxContent.split('\n');
|
||||
const spacedLines: Array<{ lineNum: number; content: string }> = [];
|
||||
|
||||
tsxLines.forEach((line, idx) => {
|
||||
if (line.includes('{" "}')) {
|
||||
spacedLines.push({ lineNum: idx, content: line.trim() });
|
||||
}
|
||||
});
|
||||
|
||||
// For each spaced line in TSX, find similar content in MDX and add {" "}
|
||||
let fixCount = 0;
|
||||
for (const { content } of spacedLines) {
|
||||
// Extract the text pattern before {" "}
|
||||
const match = content.match(/(.+?)\{" "\}/);
|
||||
if (!match) continue;
|
||||
|
||||
const textBefore = match[1].trim();
|
||||
|
||||
// Find this pattern in MDX without the space
|
||||
const searchPattern = new RegExp(
|
||||
textBefore.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '(?!\\{" "\\})',
|
||||
'g'
|
||||
);
|
||||
|
||||
const newMdxContent = mdxContent.replace(searchPattern, (matched) => {
|
||||
// Only add {" "} if it's not already there and if it's followed by a tag
|
||||
if (mdxContent.indexOf(matched + '{" "}') === -1 &&
|
||||
mdxContent.indexOf(matched) < mdxContent.indexOf('<', mdxContent.indexOf(matched))) {
|
||||
fixCount++;
|
||||
return matched + '{" "}';
|
||||
}
|
||||
return matched;
|
||||
});
|
||||
|
||||
mdxContent = newMdxContent;
|
||||
}
|
||||
|
||||
const mdxSpacesAfter = (mdxContent.match(/\{" "\}/g) || []).length;
|
||||
|
||||
if (fixCount > 0) {
|
||||
fs.writeFileSync(mdxPath, mdxContent, 'utf-8');
|
||||
console.log(`✓ ${mdxSlug}: Fixed ${fixCount} whitespace issues (${mdxSpacesBefore} → ${mdxSpacesAfter})`);
|
||||
totalFixed += fixCount;
|
||||
} else {
|
||||
console.log(`✓ ${mdxSlug}: Already correct (${mdxSpacesBefore} spaces)`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n✅ Total whitespace fixes: ${totalFixed}`);
|
||||
}
|
||||
|
||||
fixWhitespace();
|
||||
61
apps/web/scripts/fix-mermaid-apostrophes.ts
Normal file
61
apps/web/scripts/fix-mermaid-apostrophes.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const MDX_DIR = path.join(process.cwd(), 'content/blog');
|
||||
|
||||
/**
|
||||
* Fix apostrophes in Mermaid labels by removing them.
|
||||
*
|
||||
* Mermaid parser treats ' as a quote delimiter even inside ["..."].
|
||||
* Replace ' with nothing or use HTML entity ' (but simpler to just remove).
|
||||
*/
|
||||
function fixMermaidApostrophes(content: string): string {
|
||||
// Find all Mermaid blocks
|
||||
const mermaidBlockRegex = /(<Mermaid[^>]*>)([\s\S]*?)(<\/Mermaid>)/g;
|
||||
|
||||
return content.replace(mermaidBlockRegex, (match, openTag, mermaidContent, closeTag) => {
|
||||
// Within Mermaid content, find all ["..."] labels and remove apostrophes
|
||||
const fixed = mermaidContent.replace(/\["([^"]*)"\]/g, (m: string, label: string) => {
|
||||
// Remove all apostrophes from the label
|
||||
const cleanLabel = label.replace(/'/g, '');
|
||||
return `["${cleanLabel}"]`;
|
||||
});
|
||||
|
||||
return openTag + fixed + closeTag;
|
||||
});
|
||||
}
|
||||
|
||||
function processFiles() {
|
||||
const files = fs.readdirSync(MDX_DIR).filter(f => f.endsWith('.mdx'));
|
||||
let fixCount = 0;
|
||||
let totalApostrophes = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(MDX_DIR, file);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Count apostrophes in Mermaid blocks before fixing
|
||||
const mermaidBlocks = content.match(/<Mermaid[^>]*>[\s\S]*?<\/Mermaid>/g) || [];
|
||||
for (const block of mermaidBlocks) {
|
||||
const apostrophes = (block.match(/\["[^"]*'[^"]*"\]/g) || []).length;
|
||||
if (apostrophes > 0) {
|
||||
totalApostrophes += apostrophes;
|
||||
}
|
||||
}
|
||||
|
||||
const fixed = fixMermaidApostrophes(content);
|
||||
|
||||
if (content !== fixed) {
|
||||
fs.writeFileSync(filePath, fixed);
|
||||
fixCount++;
|
||||
console.log(`✅ Fixed apostrophes: ${file}`);
|
||||
} else {
|
||||
console.log(`- ${file} (no apostrophes found)`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nTotal files fixed: ${fixCount}`);
|
||||
console.log(`Total apostrophes removed: ${totalApostrophes}`);
|
||||
}
|
||||
|
||||
processFiles();
|
||||
69
apps/web/scripts/fix-mermaid-labels-strict.ts
Normal file
69
apps/web/scripts/fix-mermaid-labels-strict.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const MDX_DIR = path.join(process.cwd(), 'content/blog');
|
||||
|
||||
/**
|
||||
* STRICTLY fixes Mermaid node labels.
|
||||
*
|
||||
* Goal:
|
||||
* 1. Find all content inside [...] in Mermaid blocks.
|
||||
* 2. Strip ALL outer quotes (single or double).
|
||||
* 3. Sanitize the inner text:
|
||||
* - Remove/Replace internal double quotes
|
||||
* - Remove/Replace internal single quotes (to avoid any ambiguity)
|
||||
* 4. Wrap strictly in ["..."].
|
||||
*/
|
||||
function fixMermaidLabels(content: string): string {
|
||||
// Find Mermaid blocks
|
||||
return content.replace(/(<Mermaid[^>]*>)([\s\S]*?)(<\/Mermaid>)/g, (match, open, body, close) => {
|
||||
|
||||
// Process the body line by line to be safe, or just regex the labels.
|
||||
// Regex for labels: \[ followed by anything until \]
|
||||
// Note: We assume labels don't contain nested brackets for now (Mermaid usually doesn't).
|
||||
const fixedBody = body.replace(/\[([^\]]+)\]/g, (labelMatch, innerContent) => {
|
||||
let text = innerContent.trim();
|
||||
|
||||
// Check if it looks like a quoted label
|
||||
const hasOuterQuotes = /^['"]|['"]$/.test(text);
|
||||
if (hasOuterQuotes) {
|
||||
// Remove ALL starting/ending quotes (handling multiple if messed up)
|
||||
text = text.replace(/^['"]+|['"]+$/g, '');
|
||||
}
|
||||
|
||||
// Sanitize internal text
|
||||
// Replace " with ' to avoid breaking the outer double quotes
|
||||
text = text.replace(/"/g, "'");
|
||||
|
||||
// Verify parsing safety:
|
||||
// Replace ' with space or nothing if we want to be super safe,
|
||||
// but "Text with 'quotes'" SHOULD be valid in Mermaid.
|
||||
// However, the previous error might have been due to MDX interference.
|
||||
// Let's keep single quotes inside, but ensure outer are double.
|
||||
|
||||
// WAIT: The specific error was `B['Server ... 'inner' ...']`.
|
||||
// If we convert to `B["Server ... 'inner' ..."]`, it should work.
|
||||
|
||||
return `["${text}"]`;
|
||||
});
|
||||
|
||||
return open + fixedBody + close;
|
||||
});
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(MDX_DIR).filter(f => f.endsWith('.mdx'));
|
||||
let fixedCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(MDX_DIR, file);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const fixed = fixMermaidLabels(content);
|
||||
|
||||
if (content !== fixed) {
|
||||
fs.writeFileSync(filePath, fixed);
|
||||
console.log(`✅ Fixed labels in: ${file}`);
|
||||
fixedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nFixed ${fixedCount} files.`);
|
||||
49
apps/web/scripts/fix-mermaid-quotes.ts
Normal file
49
apps/web/scripts/fix-mermaid-quotes.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const MDX_DIR = path.join(process.cwd(), 'content/blog');
|
||||
|
||||
/**
|
||||
* Fix ALL quote variations in Mermaid labels to use consistent double quotes.
|
||||
*
|
||||
* Handles:
|
||||
* - ['Label'] → ["Label"]
|
||||
* - ["Label'] → ["Label"]
|
||||
* - ['Label"] → ["Label"]
|
||||
* - ["Label"] → ["Label"] (already correct)
|
||||
*/
|
||||
function fixMermaidQuotes(content: string): string {
|
||||
// Find all Mermaid blocks (between <Mermaid> and </Mermaid>)
|
||||
const mermaidBlockRegex = /(<Mermaid[^>]*>)([\s\S]*?)(<\/Mermaid>)/g;
|
||||
|
||||
return content.replace(mermaidBlockRegex, (match, openTag, mermaidContent, closeTag) => {
|
||||
// Replace all variations: [' or [" at start, '] or "] at end
|
||||
// Match pattern: [ followed by ' or ", then content, then ' or ", then ]
|
||||
const fixed = mermaidContent.replace(/\[['"]([^'"]*)['"]\]/g, '["$1"]');
|
||||
|
||||
return openTag + fixed + closeTag;
|
||||
});
|
||||
}
|
||||
|
||||
function processFiles() {
|
||||
const files = fs.readdirSync(MDX_DIR).filter(f => f.endsWith('.mdx'));
|
||||
let fixCount = 0;
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(MDX_DIR, file);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const fixed = fixMermaidQuotes(content);
|
||||
|
||||
if (content !== fixed) {
|
||||
fs.writeFileSync(filePath, fixed);
|
||||
fixCount++;
|
||||
console.log(`✅ Fixed Mermaid quotes: ${file}`);
|
||||
} else {
|
||||
console.log(`- ${file} (no changes needed)`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nTotal fixed: ${fixCount}`);
|
||||
}
|
||||
|
||||
processFiles();
|
||||
53
apps/web/scripts/repair-mermaid.ts
Normal file
53
apps/web/scripts/repair-mermaid.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const MDX_DIR = path.join(process.cwd(), 'content/blog');
|
||||
|
||||
function repairMermaidSyntax(content: string): string {
|
||||
// 1. Convert <Mermaid graph={`...`} /> to <Mermaid graph={`...`}>...</Mermaid> style or just fix the graph prop
|
||||
// Actually, let's keep the graph prop but make sure the content is VERY safe.
|
||||
|
||||
const mermaidRegex = /<Mermaid\s+([\s\S]*?)graph=\{(`[\s\S]*?`)\}([\s\S]*?)\/>/g;
|
||||
|
||||
return content.replace(mermaidRegex, (match, before, graphLiteral, after) => {
|
||||
let graphContent = graphLiteral.slice(1, -1);
|
||||
|
||||
// Replace all {Label} with ["Label"]
|
||||
graphContent = graphContent.replace(/\{([^{}]+)\}/g, '["$1"]');
|
||||
|
||||
// Sometimes people use double {{Label}}
|
||||
graphContent = graphContent.replace(/\{\{([^{}]+)\}\}/g, '["$1"]');
|
||||
|
||||
// Remove any trailing backticks inside that might have been accidentally added
|
||||
graphContent = graphContent.trim();
|
||||
|
||||
return `<Mermaid\n ${before.trim()}\n graph={\`${graphContent}\`}\n ${after.trim()}\n />`;
|
||||
});
|
||||
}
|
||||
|
||||
// Additional fix for other diagram components that might have similar issues with props
|
||||
function repairOtherDiagrams(content: string): string {
|
||||
// For DiagramSequence, DiagramTimeline etc., we often pass arrays of objects.
|
||||
// MDX handles these better, but let's make sure there are no weird backticks.
|
||||
return content;
|
||||
}
|
||||
|
||||
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);
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
let repaired = repairMermaidSyntax(content);
|
||||
repaired = repairOtherDiagrams(repaired);
|
||||
|
||||
if (content !== repaired) {
|
||||
fs.writeFileSync(filePath, repaired);
|
||||
console.log(`✅ Repaired Mermaid syntax in ${file}`);
|
||||
} else {
|
||||
console.log(`- Checked ${file}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processFiles();
|
||||
Reference in New Issue
Block a user