#!/usr/bin/env node /** * Normalisiert quote und youtube_video: _slug und Dateiname = prefix + lesbarer Teil. * - quote: quote-{slugify(author)} * - youtube_video: youtube-video-{slugify(title)|slugify(id ohne Präfix)|youtubeId} * Aktualisiert alle Referenzen in content/de. */ import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const CONTENT_DE = path.join(__dirname, "..", "content", "de"); function slugify(value) { if (value == null || value === "") return ""; return String(value) .replace(/_/g, "-") .replace(/\s+/g, "-") .replace(/[^a-zA-Z0-9-]+/g, "-") .replace(/-+/g, "-") .replace(/^-|-$/g, "") .toLowerCase() || "untitled"; } function stripPrefix(s, prefixes) { let t = s || ""; for (const p of prefixes) { if (t.toLowerCase().startsWith(p.toLowerCase())) { t = t.slice(p.length).replace(/^-+/, ""); break; } } return t; } function parseJson5(str) { try { return JSON.parse(str); } catch { return null; } } // ─── Quote: prefix + slugify(author) ───────────────────────────────────── function processQuote(quoteDir) { if (!fs.existsSync(quoteDir)) return new Map(); const files = fs.readdirSync(quoteDir).filter((f) => f.endsWith(".json5")).sort(); const oldToNew = new Map(); const used = new Set(); for (const file of files) { const raw = fs.readFileSync(path.join(quoteDir, file), "utf8"); const data = parseJson5(raw); const oldSlug = data?._slug || file.replace(/\.json5$/, ""); const author = data?.author ?? ""; const quoteText = data?.quote ?? ""; let rest = slugify(author) || slugify(quoteText.slice(0, 50)) || "zitat"; let newSlug = "quote-" + rest; let n = 0; while (used.has(newSlug)) { n++; newSlug = "quote-" + rest + (n === 1 ? "-1" : "-" + n); } used.add(newSlug); oldToNew.set(oldSlug, newSlug); } for (const file of files) { const filePath = path.join(quoteDir, file); const data = parseJson5(fs.readFileSync(filePath, "utf8")); const oldSlug = data?._slug || file.replace(/\.json5$/, ""); const newSlug = oldToNew.get(oldSlug); if (!newSlug) continue; data._slug = newSlug; const newPath = path.join(quoteDir, newSlug + ".json5"); fs.writeFileSync(newPath, JSON.stringify(data, null, 2) + "\n", "utf8"); if (path.basename(newPath) !== file) fs.unlinkSync(filePath); } return oldToNew; } // ─── Youtube_video: prefix + title oder id (ohne Präfix) oder youtubeId ─── const YOUTUBE_ID_PREFIXES = [ "youtube-video - ", "component-youtube-", "component-video-", "https-www-youtube-com-watch-v-", "https-youtu-be-", ]; function processYoutubeVideo(ytDir) { if (!fs.existsSync(ytDir)) return new Map(); const files = fs.readdirSync(ytDir).filter((f) => f.endsWith(".json5")).sort(); const oldToNew = new Map(); const used = new Set(); for (const file of files) { const raw = fs.readFileSync(path.join(ytDir, file), "utf8"); const data = parseJson5(raw); const oldSlug = data?._slug || file.replace(/\.json5$/, ""); const id = data?.id ?? ""; const title = data?.title ?? ""; const youtubeId = data?.youtubeId ?? ""; let rest = slugify(title) || slugify(stripPrefix(id, YOUTUBE_ID_PREFIXES)) || slugify(stripPrefix(oldSlug, YOUTUBE_ID_PREFIXES)) || youtubeId.toLowerCase() || "untitled"; let newSlug = "youtube-video-" + rest; let n = 0; while (used.has(newSlug)) { n++; newSlug = "youtube-video-" + rest + (n === 1 ? "-1" : "-" + n); } used.add(newSlug); oldToNew.set(oldSlug, newSlug); } for (const file of files) { const filePath = path.join(ytDir, file); const data = parseJson5(fs.readFileSync(filePath, "utf8")); const oldSlug = data?._slug || file.replace(/\.json5$/, ""); const newSlug = oldToNew.get(oldSlug); if (!newSlug) continue; data._slug = newSlug; if (data.id && data.id === oldSlug) data.id = newSlug; const newPath = path.join(ytDir, newSlug + ".json5"); fs.writeFileSync(newPath, JSON.stringify(data, null, 2) + "\n", "utf8"); if (path.basename(newPath) !== file) fs.unlinkSync(filePath); } return oldToNew; } // ─── Main ──────────────────────────────────────────────────────────────── const quoteDir = path.join(CONTENT_DE, "quote"); const ytDir = path.join(CONTENT_DE, "youtube_video"); const mapQuote = processQuote(quoteDir); const mapYoutube = processYoutubeVideo(ytDir); const allMaps = [mapQuote, mapYoutube]; console.log("quote:", Object.fromEntries(mapQuote)); console.log("youtube_video:", Object.fromEntries(mapYoutube)); function walkDir(dir, fn) { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const e of entries) { const full = path.join(dir, e.name); if (e.isDirectory()) walkDir(full, fn); else if (e.name.endsWith(".json5")) fn(full); } } walkDir(CONTENT_DE, (filePath) => { let content = fs.readFileSync(filePath, "utf8"); let changed = false; for (const oldToNew of allMaps) { for (const [oldSlug, newSlug] of oldToNew) { if (oldSlug === newSlug) continue; const escaped = oldSlug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); const newContent = content.replace(new RegExp('"' + escaped + '"', "g"), '"' + newSlug + '"'); if (newContent !== content) { content = newContent; changed = true; } } } if (changed) fs.writeFileSync(filePath, content, "utf8"); }); console.log("Fertig. Quote- und youtube-video-Slugs normalisiert und Referenzen aktualisiert.");