the home site for me: also iteration 3 or 4 of my site
1#!/usr/bin/env bun 2 3import fs from 'fs'; 4import path from 'path'; 5import { glob } from 'glob'; 6 7const contentDir = process.argv[2] || 'content'; 8 9function splitByCodeBlocks(content: string): { text: string; isCode: boolean }[] { 10 const parts: { text: string; isCode: boolean }[] = []; 11 const codeBlockRegex = /^(```|~~~)/gm; 12 13 let lastIndex = 0; 14 let inCodeBlock = false; 15 let match; 16 17 codeBlockRegex.lastIndex = 0; 18 19 while ((match = codeBlockRegex.exec(content)) !== null) { 20 const segment = content.slice(lastIndex, match.index); 21 if (segment) { 22 parts.push({ text: segment, isCode: inCodeBlock }); 23 } 24 inCodeBlock = !inCodeBlock; 25 lastIndex = match.index; 26 } 27 28 // Add remaining content 29 if (lastIndex < content.length) { 30 parts.push({ text: content.slice(lastIndex), isCode: inCodeBlock }); 31 } 32 33 return parts; 34} 35 36function transformCallouts(content: string): string { 37 return content.replace( 38 /^> \[!(INFO|WARNING|WARN|DANGER|ERROR|TIP|HINT|NOTE)\]\n((?:> .*\n?)*)/gm, 39 (match, type, body) => { 40 const cleanBody = body.replace(/^> /gm, '').trim(); 41 const normalizedType = type.toLowerCase() === 'warn' ? 'warning' : 42 type.toLowerCase() === 'error' ? 'danger' : 43 type.toLowerCase() === 'hint' ? 'tip' : 44 type.toLowerCase(); 45 return `{% callout(type="${normalizedType}") %}\n${cleanBody}\n{% end %}\n`; 46 } 47 ); 48} 49 50function transformImages(content: string): string { 51 return content.replace( 52 /!\[([^\]]*)\]\(([^)]+)\)\{([^}]+)\}/g, 53 (match, alt, url, attrs) => { 54 const params: string[] = [`id="${url}"`]; 55 56 if (alt) { 57 params.push(`alt="${alt}"`); 58 } 59 60 const classes = attrs.match(/\.([a-zA-Z0-9_-]+)/g)?.map(c => c.slice(1)) || []; 61 if (classes.length) { 62 params.push(`class="${classes.join(' ')}"`); 63 } 64 65 const keyValueMatches = attrs.matchAll(/([a-zA-Z]+)=["']?([^"'\s}]+)["']?/g); 66 for (const [, key, value] of keyValueMatches) { 67 if (key !== 'class') { 68 params.push(`${key}="${value.replace(/["']/g, '')}"`); 69 } 70 } 71 72 return `{{ img(${params.join(', ')}) }}`; 73 } 74 ); 75} 76 77function processFile(filePath: string): void { 78 let content = fs.readFileSync(filePath, 'utf8'); 79 const originalContent = content; 80 81 // Split by code blocks and only transform non-code parts 82 const parts = splitByCodeBlocks(content); 83 content = parts.map(part => { 84 if (part.isCode) { 85 return part.text; // Don't transform code blocks 86 } 87 let text = part.text; 88 text = transformCallouts(text); 89 text = transformImages(text); 90 return text; 91 }).join(''); 92 93 if (content !== originalContent) { 94 fs.writeFileSync(filePath, content); 95 } 96} 97 98const files = glob.sync(`${contentDir}/**/*.md`); 99files.forEach(processFile); 100