import fs from "node:fs"; import path from "node:path"; const repoRoot = process.cwd(); const websiteRoot = path.join(repoRoot, "website"); function listTopLevelHtml() { return fs .readdirSync(websiteRoot, { withFileTypes: true }) .filter((entry) => entry.isFile() && entry.name.endsWith(".html")) .map((entry) => entry.name); } function destinationFor(filename) { if (filename === "archive-speeches.html") { return "speeches/index.html"; } let match = filename.match(/^archive-speeches-rev-dr-sun-myung-moon-(.+)\.html$/); if (match) { return `speeches/rev-dr-sun-myung-moon/${match[1]}.html`; } match = filename.match(/^blog-category-speeches-of-rev-sun-myung-moon(?:-(.+))?\.html$/); if (match) { return `speeches/categories/rev-sun-myung-moon${match[1] ? `-${match[1]}` : ""}.html`; } if (filename === "blog-category-speeches-of-mrs-hak-ja-han-moon.html") { return "speeches/categories/mrs-hak-ja-han-moon.html"; } match = filename.match(/^blog-category-(.+)\.html$/); if (match) { return `blog/categories/${match[1]}.html`; } match = filename.match(/^blog-tag-(.+)\.html$/); if (match) { return `blog/tags/${match[1]}.html`; } match = filename.match(/^blog-(\d{4})-(\d{2})-(\d{2})-(.+)\.html$/); if (match) { return `blog/${match[1]}/${match[2]}/${match[3]}/${match[4]}.html`; } return null; } function splitUrl(value) { const match = value.match(/^([^?#]*)([?#].*)?$/); return { pathname: match?.[1] ?? value, suffix: match?.[2] ?? "" }; } function stripLeadingParentDirs(value) { return value.replace(/^(\.\/)?/, "").replace(/^(\.\.\/)+/, ""); } function shouldSkipUrl(value) { return ( value === "" || value.startsWith("#") || value.startsWith("/") || /^[a-z][a-z0-9+.-]*:/i.test(value) ); } function rewriteUrl(value, movedPaths) { if (shouldSkipUrl(value)) { return value; } const { pathname, suffix } = splitUrl(value); const normalized = stripLeadingParentDirs(pathname); if (normalized.startsWith("assets/") || normalized.startsWith("css/") || normalized.startsWith("js/")) { return `/${normalized}${suffix}`; } if (movedPaths.has(normalized)) { return `/${movedPaths.get(normalized)}${suffix}`; } if (normalized.endsWith(".html")) { return `/${normalized}${suffix}`; } return value; } function rewriteHtml(html, movedPaths) { let output = html.replace( /\b(href|src|action)=(["'])([^"']+)\2/g, (_match, attr, quote, value) => `${attr}=${quote}${rewriteUrl(value, movedPaths)}${quote}` ); output = output.replace(/\burl\((["']?)(\.\.\/(?:assets|css|js)\/[^"')]+)\1\)/g, (_match, quote, value) => { const rewritten = rewriteUrl(value, movedPaths); return `url(${quote}${rewritten}${quote})`; }); return output; } const movedPaths = new Map(); for (const filename of listTopLevelHtml()) { const destination = destinationFor(filename); if (destination) { movedPaths.set(filename, destination); } } for (const [filename, destination] of movedPaths) { const sourcePath = path.join(websiteRoot, filename); const destinationPath = path.join(websiteRoot, destination); fs.mkdirSync(path.dirname(destinationPath), { recursive: true }); fs.renameSync(sourcePath, destinationPath); } function walk(dir) { return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { return walk(fullPath); } return fullPath.endsWith(".html") || fullPath.endsWith(".md") ? [fullPath] : []; }); } let rewritten = 0; for (const filePath of [...walk(websiteRoot), path.join(repoRoot, "README.md"), path.join(repoRoot, "docs", "SITE-STRUCTURE.md")]) { if (!fs.existsSync(filePath)) { continue; } const original = fs.readFileSync(filePath, "utf8"); const updated = rewriteHtml(original, movedPaths); if (updated !== original) { fs.writeFileSync(filePath, updated); rewritten += 1; } } console.log(`Moved ${movedPaths.size} HTML files into blog/ and speeches/.`); console.log(`Rewrote links in ${rewritten} files.`);