From 342a2eccd756eb86096cdc0f579c99844336f9a6 Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Thu, 26 Feb 2026 17:38:44 -0800 Subject: [PATCH] Refactor: Split page generator entrypoint --- scripts/generate-pages.mjs | 340 ++----------------------------------- 1 file changed, 15 insertions(+), 325 deletions(-) diff --git a/scripts/generate-pages.mjs b/scripts/generate-pages.mjs index 4b115b8..ac5c888 100644 --- a/scripts/generate-pages.mjs +++ b/scripts/generate-pages.mjs @@ -1,323 +1,14 @@ import fs from "node:fs/promises"; -import path from "node:path"; import fg from "fast-glob"; import matter from "gray-matter"; import { marked } from "marked"; - -const ROOT = process.cwd(); -const SRC = path.join(ROOT, "src"); -const PUBLIC = path.join(ROOT, "public"); -const ARTICLES_GLOB = "articles/**/index.md"; -const SITE_URL = (process.env.SITE_URL || "https://apophenia.news").replace(/\/+$/, ""); +import { ARTICLES_GLOB, AUTHOR_PAGES, ROOT, SRC } from "./lib/constants.mjs"; +import { fixInternalLinks } from "./lib/utils.mjs"; +import { renderArticle, renderHome, renderWritePage, renderAuthorPage } from "./lib/renderers.mjs"; +import { ensureCleanGenerated, writeDiscoveryFiles, writePage } from "./lib/io.mjs"; marked.setOptions({ breaks: true, gfm: true }); -const logo = ` - - - - - - - - - - - - - - -`; - -const shellHead = ({ title, desc, image, url, type = "website" }) => ` - - - - - ${title} - - - - - ${url ? `` : ""} - ${image ? `` : ""} - - ${url ? `` : ""} - - - - -`; - -const nav = ` -
- -
-`; - -const footer = ` - - -`; - -const fmtDate = (date) => - new Date(date).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" }); - -const escapeHtml = (s = "") => - s.replace(/[&<>"']/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch])); - -const escapeXml = (s = "") => - s.replace(/[<>&'"]/g, (ch) => ({ "<": "<", ">": ">", "&": "&", "'": "'", '"': """ }[ch])); - -const fixInternalLinks = (html) => - html - .replace(/href="\.\//g, 'href="/') - .replace(/href="([^"]+)"(?=[^>]*>)/g, (_, href) => - href.startsWith("http") || href.startsWith("#") || href.endsWith("/") || href.endsWith(".md") - ? `href="${href}"` - : `href="${href}/"` - ); - -const renderHome = (articles) => ` -${shellHead({ - title: "apophenia.news — The news outlet for pattern seekers", - desc: "Signals, anomalies, civilization trajectories, and deep pattern analysis.", - image: "https://direct-img.link/constellation+data+points+minimal+white+background", - url: `${SITE_URL}/`, - type: "website" -})} -${nav} -
-
-

Pattern Intelligence Journalism

-

The news outlet for pattern seekers

-

High-agency analysis at the intersection of AGI, consciousness, geopolitics, and first-contact logic.

-
- -
-
-

Latest Articles

- ${articles.length} published -
- -
- ${articles - .map( - (a) => ` - ` - ) - .join("")} -
-
-
-${footer} -`; - -const renderArticle = (article) => ` -${shellHead({ - title: `${article.title} — apophenia.news`, - desc: article.description, - image: article.header_image, - url: `${SITE_URL}/${article.slug}/`, - type: "article" -})} -${nav} -
-
- ${escapeHtml(article.title)} -
-

${fmtDate(article.date)} • ${escapeHtml(article.author || "Apophenia")}

-

${escapeHtml(article.title)}

-

${escapeHtml(article.description || "")}

-
- ${(article.tags || []).map((t) => `#${escapeHtml(t)}`).join("")} -
-
-
${article.html}
-
-
- - -
-${footer} -`; - -const renderWritePage = () => ` -${shellHead({ - title: "Become a writer for apophenia.news", - desc: "Pitch your pattern analysis. Email your story as a Markdown file for review and publication.", - image: "https://direct-img.link/writer+typing+cosmic+newsroom+editorial", - url: `${SITE_URL}/write/`, - type: "website" -})} -${nav} -
-
-

Contributor Program

-

Become a writer for apophenia.news

-

- Have a strong pattern-based story, analysis, or investigation? Send it to us. -

- -
-

How to submit

-
    -
  • Write your article in a .md (Markdown) file.
  • -
  • Email it to planetrenox@pm.me.
  • -
  • If approved, your story will be published on apophenia.news.
  • -
  • Your byline can use your real name or an alias.
  • -
- -

Submission tips

-
    -
  • Lead with a clear thesis and strong evidence.
  • -
  • Use links/citations when making factual claims.
  • -
  • Include a short author bio line if you want one shown.
  • -
  • Add a suggested title, slug, description, and tags at the top (frontmatter preferred).
  • -
- -

Frontmatter template (optional)

-
---
-title: "Your headline"
-slug: your-slug
-date: 2026-03-01
-author: Your Name or Alias
-description: "1-2 sentence summary"
-header_image: https://direct-img.link/your+image+query
-tags:
-  - your-tag
-  - another-tag
----
-
-
-
-${footer} -`; - -const ensureCleanGenerated = async () => { - await fs.mkdir(SRC, { recursive: true }); - const children = await fs.readdir(SRC, { withFileTypes: true }); - const keep = new Set(["assets"]); - await Promise.all( - children - .filter((d) => d.isDirectory() && !keep.has(d.name)) - .map((d) => fs.rm(path.join(SRC, d.name), { recursive: true, force: true })) - ); -}; - -const toISODate = (d) => new Date(d).toISOString().split("T")[0]; - -const renderRss = (articles) => { - const lastBuildDate = new Date().toUTCString(); - const items = articles - .map((a) => { - const link = `${SITE_URL}/${a.slug}/`; - return ` - ${escapeXml(a.title || "")} - ${escapeXml(link)} - ${escapeXml(link)} - ${new Date(a.date).toUTCString()} - ${escapeXml(a.description || "")} -`; - }) - .join("\n"); - - return ` - - - apophenia.news - ${SITE_URL}/ - Signals, anomalies, civilization trajectories, and deep pattern analysis. - en-us - ${lastBuildDate} - ${items} - - -`; -}; - -const renderSitemap = (articles) => { - const now = toISODate(new Date()); - const urls = [ - { loc: `${SITE_URL}/`, lastmod: now }, - { loc: `${SITE_URL}/write/`, lastmod: now }, - ...articles.map((a) => ({ loc: `${SITE_URL}/${a.slug}/`, lastmod: toISODate(a.date || new Date()) })) - ]; - - return ` - -${urls - .map( - (u) => ` - ${escapeXml(u.loc)} - ${u.lastmod} - ` - ) - .join("\n")} - -`; -}; - -const renderRobots = () => `User-agent: * -Allow: / - -Sitemap: ${SITE_URL}/sitemap.xml -`; - -const writeDiscoveryFiles = async (articles) => { - await fs.mkdir(PUBLIC, { recursive: true }); - await Promise.all([ - fs.writeFile(path.join(PUBLIC, "rss.xml"), renderRss(articles), "utf8"), - fs.writeFile(path.join(PUBLIC, "sitemap.xml"), renderSitemap(articles), "utf8"), - fs.writeFile(path.join(PUBLIC, "robots.txt"), renderRobots(), "utf8") - ]); -}; - const run = async () => { const files = await fg(ARTICLES_GLOB, { cwd: ROOT, absolute: true }); const articles = []; @@ -325,33 +16,32 @@ const run = async () => { for (const file of files) { const md = await fs.readFile(file, "utf8"); const { data, content } = matter(md); - const html = fixInternalLinks(marked.parse(content)); if (!data.slug) continue; articles.push({ ...data, - html + html: fixInternalLinks(marked.parse(content)) }); } articles.sort((a, b) => +new Date(b.date) - +new Date(a.date)); await ensureCleanGenerated(); - await fs.writeFile(path.join(SRC, "index.html"), renderHome(articles), "utf8"); - const writeDir = path.join(SRC, "write"); - await fs.mkdir(writeDir, { recursive: true }); - await fs.writeFile(path.join(writeDir, "index.html"), renderWritePage(), "utf8"); + await writePage([SRC, "index.html"], renderHome(articles)); + await writePage([SRC, "write", "index.html"], renderWritePage()); - for (const article of articles) { - const dir = path.join(SRC, article.slug); - await fs.mkdir(dir, { recursive: true }); - await fs.writeFile(path.join(dir, "index.html"), renderArticle(article), "utf8"); + for (const author of AUTHOR_PAGES) { + await writePage([SRC, "author", author.slug, "index.html"], renderAuthorPage(author)); } - await writeDiscoveryFiles(articles); + for (const article of articles) { + await writePage([SRC, article.slug, "index.html"], renderArticle(article)); + } - console.log(`Generated ${articles.length} article pages + home + write + rss/sitemap/robots.`); + await writeDiscoveryFiles(articles, AUTHOR_PAGES); + + console.log(`Generated ${articles.length} article pages + home + write + author pages + rss/sitemap/robots.`); }; run().catch((err) => {