import { readFileSync, writeFileSync } from "node:fs"; import { readdir } from "node:fs/promises"; import { join, extname, relative } from "node:path"; const DOCS_DIR = join(import.meta.dirname, "..", "src", "content", "docs"); /** * Recursively collect all .mdx files, excluding files starting with "_" or "index". */ async function collectMdxFiles(dir) { const entries = await readdir(dir, { withFileTypes: true }); const files = []; for (const entry of entries) { if (entry.name.startsWith("_")) continue; const fullPath = join(dir, entry.name); if (entry.isDirectory()) { const subFiles = await collectMdxFiles(fullPath); files.push(...subFiles); } else if (extname(entry.name) === ".mdx" && entry.name !== "index.mdx") { files.push(fullPath); } } return files; } /** * Extract Q&A rows from the summary table in the MDX content. * Table format: * | 質問 | 答弁概要(クリックで詳細) | * |---|---| * | ① question text | [answer text](#anchor) | */ function extractQATable(content) { // Find the summary table: starts with "| 質問 | 答弁概要" const tableStartRegex = /\| 質問 \| 答弁概要(クリックで詳細) \|\r?\n\|[-| ]+\|\r?\n/; const tableStartMatch = content.match(tableStartRegex); if (!tableStartMatch) return null; const tableStart = tableStartMatch.index; const afterHeader = content.slice(tableStart + tableStartMatch[0].length); // Collect table rows const rows = []; const lines = afterHeader.split(/\r?\n/); for (const line of lines) { const rowMatch = line.match(/^\| (.+?) \| \[(.+?)\]\((#.+?)\)\s*\|$/); if (!rowMatch) break; rows.push({ question: rowMatch[1].trim(), answer: rowMatch[2].trim(), anchor: rowMatch[3].replace(/^#/, "").trim(), }); } if (rows.length === 0) return null; // Calculate the end of the table (header + separator + all rows) const tableText = content.slice(tableStart); const tableEndMatch = tableText.match( new RegExp( `(?:^\\| .+? \\| \\[.+?\\]\\(#.+?\\)\\s*\\|$(?:\\r?\\n)?){${rows.length}}`, "m", ), ); if (!tableEndMatch) return null; const tableEnd = tableStart + tableStartMatch[0].length + tableEndMatch[0].length; return { rows, tableStart, tableEnd, }; } /** * Extract frontmatter fields from MDX content. */ function extractFrontmatter(content) { const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); if (!match) return {}; const fm = match[1]; const titleMatch = fm.match(/^title:\s*["']?(.+?)["']?\s*$/m); const dateMatch = fm.match( /^(?:first|date):\s*["']?(\d{4}-\d{2}-\d{2})["']?\s*$/m, ); return { title: titleMatch ? titleMatch[1].trim() : null, date: dateMatch ? dateMatch[1].trim() : null, }; } /** * Ensure the import line for QuestionSummary exists in the import block. */ function addImport(content, importPath) { const importLine = `import QuestionSummary from '${importPath}';`; if (content.includes("QuestionSummary")) { return content; // Already imported } // Find the last import statement and insert after it const importRegex = /^(import .+)$/gm; const imports = [...content.matchAll(importRegex)]; if (imports.length > 0) { const lastImport = imports[imports.length - 1]; const insertPos = lastImport.index + lastImport[0].length; return ( content.slice(0, insertPos) + "\n" + importLine + content.slice(insertPos) ); } return content; // No existing imports } /** * Generate the QuestionSummary component JSX string. */ function generateQuestionSummary(qaRows, headline, datePublished) { const qaEntries = qaRows .map((row) => { return ` { question: ${JSON.stringify(row.question)}, answer: ${JSON.stringify(row.answer)}, anchor: ${JSON.stringify(row.anchor)} },`; }) .join("\n"); const props = []; if (headline) props.push(` headline=${JSON.stringify(headline)}`); if (datePublished) props.push(` datePublished=${JSON.stringify(datePublished)}`); return ``; } async function main() { const files = await collectMdxFiles(DOCS_DIR); console.log(`Found ${files.length} MDX files to check.`); let migrated = 0; let skipped = 0; for (const file of files) { const content = readFileSync(file, "utf-8"); const tableData = extractQATable(content); if (!tableData) { skipped++; continue; } const fm = extractFrontmatter(content); // Generate the replacement component const component = generateQuestionSummary( tableData.rows, fm.title, fm.date, ); // Replace the table with the component let newContent = content.slice(0, tableData.tableStart) + component + content.slice(tableData.tableEnd); // Ensure the import exists newContent = addImport(newContent, "@/components/QuestionSummary.astro"); writeFileSync(file, newContent, "utf-8"); const relPath = relative(DOCS_DIR, file); console.log(` Migrated: ${relPath} (${tableData.rows.length} QA pairs)`); migrated++; } console.log(`\nDone: ${migrated} migrated, ${skipped} skipped.`); } main().catch((err) => { console.error("Error:", err); process.exit(1); });