diff --git a/build.js b/build.js
index 467acd7..4e4eb6f 100644
--- a/build.js
+++ b/build.js
@@ -1,60 +1,59 @@
-// 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
+// build.js — auto-index Markdown posts for The Fold Within
+// Parses front-matter, removes it from body, and generates clean summaries.
-const fs = require("fs");
-const path = require("path");
+import fs from "fs";
+import path from "path";
-const POSTS_DIR = path.join(__dirname, "posts");
-const SITE_URL = "https://thefoldwithin.earth"; // change if needed
+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, "-")
+ .normalize("NFKD")
+ .replace(/[^\w\s-]/g, "")
+ .trim()
+ .replace(/\s+/g, "-")
.replace(/-+/g, "-");
}
+// --- Extract YAML-style front matter ---
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 match = src.match(/^---\n([\s\S]*?)\n---\n?/);
+ if (!match) return { fm, body: src };
- const block = m[1];
+ const block = match[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 [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(m[0].length);
+ const body = src.slice(match[0].length).trim();
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();
+ 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) {
- // 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();
+ if (d && !isNaN(d.getTime())) return d;
+ return fallback;
}
function escapeXML(s) {
@@ -66,45 +65,50 @@ function escapeXML(s) {
.replace(/'/g, "'");
}
-const files = fs.readdirSync(POSTS_DIR).filter(f => f.endsWith(".md"));
+const files = fs
+ .readdirSync(POSTS_DIR)
+ .filter((f) => f.endsWith(".md") && !f.startsWith("_"));
-const posts = files.map(file => {
- const full = path.join(POSTS_DIR, file);
- const raw = fs.readFileSync(full, "utf8");
- const stat = fs.statSync(full);
+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$/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 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, // ISO for reliable sort
+ date: dateISO.toISOString().split("T")[0], // human-readable YYYY-MM-DD
excerpt,
tags: fm.tags || [],
slug,
- file // actual filename
+ file,
};
});
// newest first
posts.sort((a, b) => (a.date < b.date ? 1 : -1));
-// write JSON index
+// write posts.json
fs.writeFileSync(
path.join(POSTS_DIR, "posts.json"),
JSON.stringify(posts, null, 2),
"utf8"
);
-console.log(`✅ posts.json written (${posts.length} posts)`);
+console.log(`✅ Generated posts.json (${posts.length} posts)`);
-// write RSS
-const rssItems = posts.map(p => {
- const url = `${SITE_URL}/#/post/${p.slug}`;
- return `
+// write rss.xml
+const rssItems = posts
+ .map((p) => {
+ const url = `${SITE_URL}/#/post/${p.slug}`;
+ return `
-
${escapeXML(p.title)}
${url}
@@ -112,28 +116,31 @@ const rssItems = posts.map(p => {
${new Date(p.date).toUTCString()}
${escapeXML(p.excerpt)}
`;
-}).join("");
+ })
+ .join("");
const rss = `
The Fold Within
${SITE_URL}
- Uncovering the recursive real.
+ Uncovering the Recursive Real.
${new Date().toUTCString()}
${rssItems}
`;
-fs.writeFileSync(path.join(__dirname, "rss.xml"), rss, "utf8");
+fs.writeFileSync("rss.xml", rss, "utf8");
console.log("✅ rss.xml written");
-// write sitemap
-const sitemapUrls = posts.map(p => ` ${SITE_URL}/#/post/${p.slug}`).join("\n");
+// write sitemap.xml
+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");
+fs.writeFileSync("sitemap.xml", sitemap, "utf8");
console.log("✅ sitemap.xml written");
\ No newline at end of file