import { access, copyFile, mkdir, readdir, readFile, writeFile, } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; const blogs = [ { slug: "research", label: "Research", summary: "Notes from papers, algorithms, computational social choice, and mini-surveys of themes I am learning about.", }, { slug: "vibes", label: "Vibes", summary: "Loose experiments, AI conversations, and work-in-progress thoughts that do not need a formal frame.", }, { slug: "puzzles", label: "Puzzles", summary: "Problems, teaching prompts, contest-style curiosities, and small mathematical rabbit holes.", }, { slug: "reflections", label: "Reflections", summary: "Personal notes on teaching, academia, writing, attention, and the rhythms of intellectual work.", }, { slug: "poetry", label: "Poetry", summary: "Poems, tiny forms, and playful writing around mathematics, code, and ordinary life.", }, { slug: "reviews", label: "Reviews", summary: "Notes on tools, workflows, books, hardware, and objects that shape everyday work.", }, { slug: "art", label: "Art", summary: "Algorithmic sketches, generative art, visual experiments, and mathematical images.", }, ]; const socialLinks = [ { href: "https://x.com/home", label: "X", color: "#525252", svg: ``, }, { href: "https://bsky.app/profile/neeldhara.bsky.social", label: "Bluesky", color: "#4f83b8", svg: ``, }, { href: "https://www.linkedin.com/in/neeldhara-misra-a54b6920/", label: "LinkedIn", color: "#496f9e", svg: ``, }, { href: "https://mathstodon.xyz/@neeldhara", label: "Mastodon", color: "#7464a8", svg: ``, }, { href: "https://www.youtube.com/@neeldhara", label: "YouTube", color: "#b55252", svg: ``, }, { href: "https://www.instagram.com/neeldharamisra", label: "Instagram", color: "#9a6b8c", svg: ``, }, ]; const publicHostSuffix = process.env.PUBLIC_HOST_SUFFIX ?? "neeldhara.blog"; const allblogsRoot = new URL("../../allblogs/", import.meta.url); const contentIndex = new URL("../content/blog-index.json", import.meta.url); const outDir = new URL("../dist/", import.meta.url); const fontDir = new URL("../webfonts/", import.meta.url); const shouldWriteContentIndex = process.argv.includes("--write-content-index"); const contentSource = process.env.BLOG_HOME_CONTENT_SOURCE ?? "auto"; const fontFiles = [ "heliotrope_3_regular.woff2", "heliotrope_3_italic.woff2", "heliotrope_3_bold.woff2", "heliotrope_3_bold_italic.woff2", ]; function escapeHtml(value) { return value .toString() .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function parseScalar(value) { const trimmed = value.trim(); if (trimmed === "true") return true; if (trimmed === "false") return false; if ( (trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'")) ) { return trimmed.slice(1, -1); } if (trimmed.startsWith("[") && trimmed.endsWith("]")) { return trimmed .slice(1, -1) .split(",") .map((item) => parseScalar(item)) .filter(Boolean); } return trimmed; } function parseFrontmatter(markdown) { if (!markdown.startsWith("---")) return {}; const end = markdown.indexOf("\n---", 3); if (end === -1) return {}; const yaml = markdown.slice(3, end).trim(); const data = {}; for (const line of yaml.split(/\r?\n/)) { const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/); if (!match) continue; data[match[1]] = parseScalar(match[2]); } return data; } async function pathExists(filePath) { try { await access(filePath); return true; } catch { return false; } } async function findMarkdownFiles(dir) { const files = []; const entries = await readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { files.push(...(await findMarkdownFiles(fullPath))); continue; } if (entry.isFile() && /\.(md|mdx)$/i.test(entry.name)) { files.push(fullPath); } } return files; } function slugFromFile(filePath, contentDir) { const relative = path.relative(contentDir, filePath).replaceAll(path.sep, "/"); return relative.replace(/\/index\.mdx?$/i, "").replace(/\.mdx?$/i, ""); } async function getLocalPosts(blog) { const contentDir = fileURLToPath( new URL(`sites/${blog.slug}/src/content/${blog.slug}/`, allblogsRoot), ); if (!(await pathExists(contentDir))) { return null; } const files = await findMarkdownFiles(contentDir); const posts = []; for (const file of files) { const data = parseFrontmatter(await readFile(file, "utf8")); const pubDate = new Date(`${data.pubDate}T00:00:00Z`); if (!data.title || Number.isNaN(pubDate.valueOf())) { continue; } const slug = slugFromFile(file, contentDir); posts.push({ title: data.title, link: `https://${blog.slug}.${publicHostSuffix}/${slug}/`, date: pubDate.toISOString().slice(0, 10), featured: data["main-feature"] === true || data.mainFeature === true, }); } return posts.sort((a, b) => b.date.localeCompare(a.date)); } async function getIndexedPosts() { const index = JSON.parse(await readFile(contentIndex, "utf8")); return blogs.map((blog) => [blog, index[blog.slug] ?? []]); } async function getEntries() { if (contentSource === "index") { return getIndexedPosts(); } const localEntries = await Promise.all( blogs.map(async (blog) => [blog, await getLocalPosts(blog)]), ); if (localEntries.every(([, posts]) => posts !== null)) { return localEntries; } return getIndexedPosts(); } function renderPost(post) { const date = post.date ? new Date(`${post.date}T00:00:00Z`) : null; const dateLabel = date ? date.toLocaleDateString("en", { month: "short", day: "numeric", timeZone: "UTC" }) : ""; return `
  • ${escapeHtml(dateLabel)}${escapeHtml(post.title)}
  • `; } function renderFeaturedPost(post) { if (!post) return ""; return `

    Featured${escapeHtml(post.title)}

    `; } function renderRssIcon(blog) { const href = `https://${blog.slug}.${publicHostSuffix}/rss.xml`; return ` `; } function renderSiteIcon(blog) { const href = `https://${blog.slug}.${publicHostSuffix}/`; return ` `; } function renderSocialLinks() { return ``; } function renderBlog(blog, posts) { const featured = posts.find((post) => post.featured); const latest = posts.filter((post) => post !== featured).slice(0, 2); return `
    ${escapeHtml(blog.label)} ${renderSiteIcon(blog)} ${renderRssIcon(blog)}

    ${escapeHtml(blog.summary)}

    ${renderFeaturedPost(featured)}
    `; } const entries = await getEntries(); if (shouldWriteContentIndex) { await mkdir(new URL("../content/", import.meta.url), { recursive: true }); await writeFile( contentIndex, `${JSON.stringify( Object.fromEntries(entries.map(([blog, posts]) => [blog.slug, posts])), null, 2, )}\n`, ); } const html = ` neeldhara.blog
    ${entries.map(([blog, posts]) => renderBlog(blog, posts)).join("\n ")}
    `; await mkdir(outDir, { recursive: true }); await writeFile(new URL("index.html", outDir), html); await mkdir(new URL("webfonts/", outDir), { recursive: true }); await Promise.all( fontFiles.map((file) => copyFile(new URL(file, fontDir), new URL(`webfonts/${file}`, outDir))), );