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(/\/+$/, ""); marked.setOptions({ breaks: true, gfm: true }); const logo = ` `; const shellHead = ({ title, desc, image, url, type = "website" }) => ` ${title} ${url ? `` : ""} ${image ? `` : ""} ${url ? `` : ""} `; const nav = `
${logo}

apophenia.news

for pattern seekers

`; 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}
Back to Home
${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 = []; 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 }); } 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"); 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"); } await writeDiscoveryFiles(articles); console.log(`Generated ${articles.length} article pages + home + write + rss/sitemap/robots.`); }; run().catch((err) => { console.error(err); process.exit(1); });