diff --git a/build.js b/build.js index aaf85f6..467acd7 100644 --- a/build.js +++ b/build.js @@ -1,20 +1,139 @@ -import fs from 'fs'; -import path from 'path'; -import { marked } from 'marked'; +// build.js — tiny static-site generator without the generator +// Scans /posts/*.md, extracts front-matter & first paragraph, +// writes posts/posts.json, rss.xml, and sitemap.xml -const postsDir = './posts'; -const outputFile = './posts.js'; +const fs = require("fs"); +const path = require("path"); -const files = fs.readdirSync(postsDir).filter(f => f.endsWith('.md')); +const POSTS_DIR = path.join(__dirname, "posts"); +const SITE_URL = "https://thefoldwithin.earth"; // change if needed + +function slugify(s) { + return s + .toLowerCase() + .normalize("NFKD").replace(/[^\w\s-]/g, "") + .trim().replace(/\s+/g, "-") + .replace(/-+/g, "-"); +} + +function parseFrontMatter(src) { + // very small YAML-ish parser + const fm = { title: "", date: "", excerpt: "", tags: [] }; + const m = src.match(/^---\n([\s\S]*?)\n---\n?/); + if (!m) return { fm, body: src }; + + const block = m[1]; + for (const line of block.split("\n")) { + const idx = line.indexOf(":"); + if (idx === -1) continue; + const key = line.slice(0, idx).trim(); + let val = line.slice(idx + 1).trim(); + + if (key === "tags") { + // tags: [a, b, c] OR tags: a, b, c + if (/^\[.*\]$/.test(val)) { + val = val.replace(/^\[|\]$/g, ""); + } + fm.tags = val.split(",").map(t => t.trim()).filter(Boolean); + } else if (key in fm) { + fm[key] = val; + } + } + const body = src.slice(m[0].length); + return { fm, body }; +} + +function firstParagraph(text) { + const body = text.replace(/\r/g, "").trim(); + const noFM = body.replace(/^---[\s\S]*?---/, "").trim(); + const para = noFM.split(/\n{2,}/).find(p => p.replace(/\s/g, "").length > 0) || ""; + return para.replace(/\n/g, " ").trim(); +} + +function toISODate(s, fallback) { + // try to parse; if fail, fallback (usually file mtime) + const d = s ? new Date(s) : null; + if (d && !isNaN(d.getTime())) return d.toISOString(); + return fallback.toISOString(); +} + +function escapeXML(s) { + return s + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +const files = fs.readdirSync(POSTS_DIR).filter(f => f.endsWith(".md")); const posts = files.map(file => { - const filepath = path.join(postsDir, file); - const raw = fs.readFileSync(filepath, 'utf-8'); - const html = marked.parse(raw); - return { file, content: html }; + const full = path.join(POSTS_DIR, file); + const raw = fs.readFileSync(full, "utf8"); + const stat = fs.statSync(full); + const { fm, body } = parseFrontMatter(raw); + + const fallbackTitle = file.replace(/\.md$/i, "").replace(/-/g, " ").replace(/\s+/g, " ").trim(); + const title = fm.title || fallbackTitle; + const excerpt = fm.excerpt || (firstParagraph(raw).slice(0, 200) + (firstParagraph(raw).length > 200 ? "…" : "")); + const slug = slugify(fm.title || fallbackTitle); + const dateISO = toISODate(fm.date, stat.mtime); + + return { + title, + date: dateISO, // ISO for reliable sort + excerpt, + tags: fm.tags || [], + slug, + file // actual filename + }; }); -const output = `document.getElementById("content").innerHTML = \`${posts.map(p => `
${p.content}
`).join("")}\`;`; +// newest first +posts.sort((a, b) => (a.date < b.date ? 1 : -1)); -fs.writeFileSync(outputFile, output); -console.log("✅ Blog built to posts.js"); +// write JSON index +fs.writeFileSync( + path.join(POSTS_DIR, "posts.json"), + JSON.stringify(posts, null, 2), + "utf8" +); +console.log(`✅ posts.json written (${posts.length} posts)`); + +// write RSS +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(path.join(__dirname, "rss.xml"), rss, "utf8"); +console.log("✅ rss.xml written"); + +// write sitemap +const sitemapUrls = posts.map(p => ` ${SITE_URL}/#/post/${p.slug}`).join("\n"); +const sitemap = ` + + ${SITE_URL} +${sitemapUrls} +`; +fs.writeFileSync(path.join(__dirname, "sitemap.xml"), sitemap, "utf8"); +console.log("✅ sitemap.xml written"); \ No newline at end of file