Update build.js
This commit is contained in:
parent
2c6c783a26
commit
9b6154fbda
1 changed files with 64 additions and 57 deletions
115
build.js
115
build.js
|
|
@ -1,60 +1,59 @@
|
||||||
// build.js — tiny static-site generator without the generator
|
// build.js — auto-index Markdown posts for The Fold Within
|
||||||
// Scans /posts/*.md, extracts front-matter & first paragraph,
|
// Parses front-matter, removes it from body, and generates clean summaries.
|
||||||
// writes posts/posts.json, rss.xml, and sitemap.xml
|
|
||||||
|
|
||||||
const fs = require("fs");
|
import fs from "fs";
|
||||||
const path = require("path");
|
import path from "path";
|
||||||
|
|
||||||
const POSTS_DIR = path.join(__dirname, "posts");
|
const POSTS_DIR = path.join(".", "posts");
|
||||||
const SITE_URL = "https://thefoldwithin.earth"; // change if needed
|
const SITE_URL = "https://thefoldwithin.earth"; // Update if needed
|
||||||
|
|
||||||
function slugify(s) {
|
function slugify(s) {
|
||||||
return s
|
return s
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.normalize("NFKD").replace(/[^\w\s-]/g, "")
|
.normalize("NFKD")
|
||||||
.trim().replace(/\s+/g, "-")
|
.replace(/[^\w\s-]/g, "")
|
||||||
|
.trim()
|
||||||
|
.replace(/\s+/g, "-")
|
||||||
.replace(/-+/g, "-");
|
.replace(/-+/g, "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Extract YAML-style front matter ---
|
||||||
function parseFrontMatter(src) {
|
function parseFrontMatter(src) {
|
||||||
// very small YAML-ish parser
|
|
||||||
const fm = { title: "", date: "", excerpt: "", tags: [] };
|
const fm = { title: "", date: "", excerpt: "", tags: [] };
|
||||||
const m = src.match(/^---\n([\s\S]*?)\n---\n?/);
|
const match = src.match(/^---\n([\s\S]*?)\n---\n?/);
|
||||||
if (!m) return { fm, body: src };
|
if (!match) return { fm, body: src };
|
||||||
|
|
||||||
const block = m[1];
|
const block = match[1];
|
||||||
for (const line of block.split("\n")) {
|
for (const line of block.split("\n")) {
|
||||||
const idx = line.indexOf(":");
|
const [key, ...rest] = line.split(":");
|
||||||
if (idx === -1) continue;
|
const value = rest.join(":").trim();
|
||||||
const key = line.slice(0, idx).trim();
|
if (key.trim() === "title") fm.title = value;
|
||||||
let val = line.slice(idx + 1).trim();
|
if (key.trim() === "date") fm.date = value;
|
||||||
|
if (key.trim() === "excerpt") fm.excerpt = value;
|
||||||
if (key === "tags") {
|
if (key.trim() === "tags") {
|
||||||
// tags: [a, b, c] OR tags: a, b, c
|
fm.tags = value
|
||||||
if (/^\[.*\]$/.test(val)) {
|
.replace(/[\[\]]/g, "")
|
||||||
val = val.replace(/^\[|\]$/g, "");
|
.split(",")
|
||||||
}
|
.map((v) => v.trim())
|
||||||
fm.tags = val.split(",").map(t => t.trim()).filter(Boolean);
|
.filter(Boolean);
|
||||||
} else if (key in fm) {
|
|
||||||
fm[key] = val;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const body = src.slice(m[0].length);
|
const body = src.slice(match[0].length).trim();
|
||||||
return { fm, body };
|
return { fm, body };
|
||||||
}
|
}
|
||||||
|
|
||||||
function firstParagraph(text) {
|
function firstParagraph(text) {
|
||||||
const body = text.replace(/\r/g, "").trim();
|
const para = text
|
||||||
const noFM = body.replace(/^---[\s\S]*?---/, "").trim();
|
.replace(/\r/g, "")
|
||||||
const para = noFM.split(/\n{2,}/).find(p => p.replace(/\s/g, "").length > 0) || "";
|
.split(/\n{2,}/)
|
||||||
return para.replace(/\n/g, " ").trim();
|
.find((p) => p.replace(/\s/g, "").length > 0);
|
||||||
|
return para ? para.replace(/\n/g, " ").trim() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function toISODate(s, fallback) {
|
function toISODate(s, fallback) {
|
||||||
// try to parse; if fail, fallback (usually file mtime)
|
|
||||||
const d = s ? new Date(s) : null;
|
const d = s ? new Date(s) : null;
|
||||||
if (d && !isNaN(d.getTime())) return d.toISOString();
|
if (d && !isNaN(d.getTime())) return d;
|
||||||
return fallback.toISOString();
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeXML(s) {
|
function escapeXML(s) {
|
||||||
|
|
@ -66,43 +65,48 @@ function escapeXML(s) {
|
||||||
.replace(/'/g, "'");
|
.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 posts = files.map((file) => {
|
||||||
const full = path.join(POSTS_DIR, file);
|
const raw = fs.readFileSync(path.join(POSTS_DIR, file), "utf8");
|
||||||
const raw = fs.readFileSync(full, "utf8");
|
const stat = fs.statSync(path.join(POSTS_DIR, file));
|
||||||
const stat = fs.statSync(full);
|
|
||||||
const { fm, body } = parseFrontMatter(raw);
|
const { fm, body } = parseFrontMatter(raw);
|
||||||
|
|
||||||
const fallbackTitle = file.replace(/\.md$/i, "").replace(/-/g, " ").replace(/\s+/g, " ").trim();
|
const fallbackTitle = file.replace(/\.md$/, "").replace(/-/g, " ");
|
||||||
const title = fm.title || fallbackTitle;
|
const title = fm.title || fallbackTitle;
|
||||||
const excerpt = fm.excerpt || (firstParagraph(raw).slice(0, 200) + (firstParagraph(raw).length > 200 ? "…" : ""));
|
const slug = slugify(title);
|
||||||
const slug = slugify(fm.title || fallbackTitle);
|
const excerpt =
|
||||||
|
fm.excerpt ||
|
||||||
|
(firstParagraph(body).slice(0, 200) +
|
||||||
|
(firstParagraph(body).length > 200 ? "…" : ""));
|
||||||
const dateISO = toISODate(fm.date, stat.mtime);
|
const dateISO = toISODate(fm.date, stat.mtime);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title,
|
title,
|
||||||
date: dateISO, // ISO for reliable sort
|
date: dateISO.toISOString().split("T")[0], // human-readable YYYY-MM-DD
|
||||||
excerpt,
|
excerpt,
|
||||||
tags: fm.tags || [],
|
tags: fm.tags || [],
|
||||||
slug,
|
slug,
|
||||||
file // actual filename
|
file,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// newest first
|
// newest first
|
||||||
posts.sort((a, b) => (a.date < b.date ? 1 : -1));
|
posts.sort((a, b) => (a.date < b.date ? 1 : -1));
|
||||||
|
|
||||||
// write JSON index
|
// write posts.json
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
path.join(POSTS_DIR, "posts.json"),
|
path.join(POSTS_DIR, "posts.json"),
|
||||||
JSON.stringify(posts, null, 2),
|
JSON.stringify(posts, null, 2),
|
||||||
"utf8"
|
"utf8"
|
||||||
);
|
);
|
||||||
console.log(`✅ posts.json written (${posts.length} posts)`);
|
console.log(`✅ Generated posts.json (${posts.length} posts)`);
|
||||||
|
|
||||||
// write RSS
|
// write rss.xml
|
||||||
const rssItems = posts.map(p => {
|
const rssItems = posts
|
||||||
|
.map((p) => {
|
||||||
const url = `${SITE_URL}/#/post/${p.slug}`;
|
const url = `${SITE_URL}/#/post/${p.slug}`;
|
||||||
return `
|
return `
|
||||||
<item>
|
<item>
|
||||||
|
|
@ -112,28 +116,31 @@ const rssItems = posts.map(p => {
|
||||||
<pubDate>${new Date(p.date).toUTCString()}</pubDate>
|
<pubDate>${new Date(p.date).toUTCString()}</pubDate>
|
||||||
<description>${escapeXML(p.excerpt)}</description>
|
<description>${escapeXML(p.excerpt)}</description>
|
||||||
</item>`;
|
</item>`;
|
||||||
}).join("");
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
const rss = `<?xml version="1.0" encoding="UTF-8"?>
|
const rss = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<rss version="2.0">
|
<rss version="2.0">
|
||||||
<channel>
|
<channel>
|
||||||
<title>The Fold Within</title>
|
<title>The Fold Within</title>
|
||||||
<link>${SITE_URL}</link>
|
<link>${SITE_URL}</link>
|
||||||
<description>Uncovering the recursive real.</description>
|
<description>Uncovering the Recursive Real.</description>
|
||||||
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
|
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
|
||||||
${rssItems}
|
${rssItems}
|
||||||
</channel>
|
</channel>
|
||||||
</rss>`;
|
</rss>`;
|
||||||
|
|
||||||
fs.writeFileSync(path.join(__dirname, "rss.xml"), rss, "utf8");
|
fs.writeFileSync("rss.xml", rss, "utf8");
|
||||||
console.log("✅ rss.xml written");
|
console.log("✅ rss.xml written");
|
||||||
|
|
||||||
// write sitemap
|
// write sitemap.xml
|
||||||
const sitemapUrls = posts.map(p => ` <url><loc>${SITE_URL}/#/post/${p.slug}</loc></url>`).join("\n");
|
const sitemapUrls = posts
|
||||||
|
.map((p) => ` <url><loc>${SITE_URL}/#/post/${p.slug}</loc></url>`)
|
||||||
|
.join("\n");
|
||||||
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
<url><loc>${SITE_URL}</loc></url>
|
<url><loc>${SITE_URL}</loc></url>
|
||||||
${sitemapUrls}
|
${sitemapUrls}
|
||||||
</urlset>`;
|
</urlset>`;
|
||||||
fs.writeFileSync(path.join(__dirname, "sitemap.xml"), sitemap, "utf8");
|
fs.writeFileSync("sitemap.xml", sitemap, "utf8");
|
||||||
console.log("✅ sitemap.xml written");
|
console.log("✅ sitemap.xml written");
|
||||||
Loading…
Add table
Add a link
Reference in a new issue