// build.js — auto-index Markdown posts for The Fold Within // Parses front-matter, removes it from body, and generates clean summaries. import fs from "fs"; import path from "path"; const POSTS_DIR = path.join(".", "posts"); const SITE_URL = "https://thefoldwithin.earth"; // Update if needed function slugify(s) { return s .toLowerCase() .normalize("NFKD") .replace(/[^\w\s-]/g, "") .trim() .replace(/\s+/g, "-") .replace(/-+/g, "-"); } // --- Extract YAML-style front matter --- function parseFrontMatter(src) { const fm = { title: "", date: "", excerpt: "", tags: [] }; const match = src.match(/^---\n([\s\S]*?)\n---\n?/); if (!match) return { fm, body: src }; const block = match[1]; for (const line of block.split("\n")) { const [key, ...rest] = line.split(":"); const value = rest.join(":").trim(); if (key.trim() === "title") fm.title = value; if (key.trim() === "date") fm.date = value; if (key.trim() === "excerpt") fm.excerpt = value; if (key.trim() === "tags") { fm.tags = value .replace(/[\[\]]/g, "") .split(",") .map((v) => v.trim()) .filter(Boolean); } } const body = src.slice(match[0].length).trim(); return { fm, body }; } function firstParagraph(text) { const para = text .replace(/\r/g, "") .split(/\n{2,}/) .find((p) => p.replace(/\s/g, "").length > 0); return para ? para.replace(/\n/g, " ").trim() : ""; } function toISODate(s, fallback) { const d = s ? new Date(s) : null; if (d && !isNaN(d.getTime())) return d; return fallback; } function escapeXML(s) { return s .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } const files = fs .readdirSync(POSTS_DIR) .filter((f) => f.endsWith(".md") && !f.startsWith("_")); const posts = files.map((file) => { const raw = fs.readFileSync(path.join(POSTS_DIR, file), "utf8"); const stat = fs.statSync(path.join(POSTS_DIR, file)); const { fm, body } = parseFrontMatter(raw); const fallbackTitle = file.replace(/\.md$/, "").replace(/-/g, " "); const title = fm.title || fallbackTitle; const slug = slugify(title); const excerpt = fm.excerpt || (firstParagraph(body).slice(0, 200) + (firstParagraph(body).length > 200 ? "…" : "")); const dateISO = toISODate(fm.date, stat.mtime); return { title, date: dateISO.toISOString().split("T")[0], // human-readable YYYY-MM-DD excerpt, tags: fm.tags || [], slug, file, }; }); // newest first posts.sort((a, b) => (a.date < b.date ? 1 : -1)); // write posts.json fs.writeFileSync( path.join(POSTS_DIR, "posts.json"), JSON.stringify(posts, null, 2), "utf8" ); console.log(`✅ Generated posts.json (${posts.length} posts)`); // write rss.xml const rssItems = posts .map((p) => { const url = `${SITE_URL}/#/post/${p.slug}`; return ` ${escapeXML(p.title)} ${url} ${url} ${new Date(p.date).toUTCString()} ${escapeXML(p.excerpt)} `; }) .join(""); const rss = ` The Fold Within ${SITE_URL} Uncovering the Recursive Real. ${new Date().toUTCString()} ${rssItems} `; fs.writeFileSync("rss.xml", rss, "utf8"); console.log("✅ rss.xml written"); // write sitemap.xml const sitemapUrls = posts .map((p) => ` ${SITE_URL}/#/post/${p.slug}`) .join("\n"); const sitemap = ` ${SITE_URL} ${sitemapUrls} `; fs.writeFileSync("sitemap.xml", sitemap, "utf8"); console.log("✅ sitemap.xml written");