#!/usr/bin/env node import { existsSync, readFileSync, readdirSync, writeFileSync } from "fs"; import { join } from "path"; const PAPERS_DIR = new URL(".", import.meta.url).pathname; const REPO_ROOT = join(PAPERS_DIR, ".."); const PLATTER_PATH = join( REPO_ROOT, "system/public/papers.aesthetic.computer/platter.html", ); const INDEX_PATH = join( REPO_ROOT, "system/public/papers.aesthetic.computer/index.html", ); function escapeJsString(text) { return String(text).replaceAll("\\", "\\\\").replaceAll('"', '\\"'); } function titleFromFilename(file) { const withoutExt = file.replace(/\.md$/i, ""); const withoutDate = withoutExt.replace(/^\d{4}-\d{2}-\d{2}-/, ""); const words = withoutDate.replaceAll(/[-_]+/g, " ").trim(); if (!words) return file; return words.replace(/\b[a-z]/g, (letter) => letter.toUpperCase()); } function countFiles(dirPath, ext) { if (!existsSync(dirPath)) return 0; return readdirSync(dirPath).filter((name) => name.endsWith(ext)).length; } function getMarkdownFiles(repoDir) { const fullPath = join(REPO_ROOT, repoDir); if (!existsSync(fullPath)) return []; return readdirSync(fullPath) .filter((name) => name.endsWith(".md")) .sort((a, b) => b.localeCompare(a)); } function parseArrayEntries(source) { const entries = []; const entryRe = /\["([^"]+)",\s*"([^"]+)"\],?/g; let match; while ((match = entryRe.exec(source)) !== null) { entries.push([match[1], match[2]]); } return entries; } function syncArray(html, arrayName, repoDir) { const arrRe = new RegExp(`const ${arrayName} = \\[([\\s\\S]*?)\\n\\s*\\];`, "m"); const match = html.match(arrRe); if (!match) throw new Error(`Could not find ${arrayName} array in platter.html`); const existingEntries = parseArrayEntries(match[1]); const existingMap = new Map(existingEntries); const existingOrder = existingEntries.map(([file]) => file); const actualFiles = getMarkdownFiles(repoDir); const kept = existingOrder.filter((file) => actualFiles.includes(file)); const added = actualFiles.filter((file) => !existingMap.has(file)); const ordered = [...kept, ...added]; const rendered = ordered .map((file) => { const title = existingMap.get(file) || titleFromFilename(file); return ` ["${escapeJsString(file)}","${escapeJsString(title)}"],`; }) .join("\n"); const nextBlock = `const ${arrayName} = [\n${rendered}\n ];`; const nextHtml = html.replace(arrRe, nextBlock); return { html: nextHtml, count: ordered.length, added: added.length, removed: existingOrder.length - kept.length, }; } function replaceOrThrow(html, pattern, replacement, label) { if (!pattern.test(html)) { throw new Error(`Could not update ${label}`); } return html.replace(pattern, replacement); } function main() { if (!existsSync(PLATTER_PATH)) { throw new Error(`Missing platter file: ${PLATTER_PATH}`); } let html = readFileSync(PLATTER_PATH, "utf8"); const reports = syncArray(html, "reports", "reports"); html = reports.html; const plans = syncArray(html, "plans", "plans"); html = plans.html; const studies = syncArray(html, "studies", "studies"); html = studies.html; const piecesMjs = countFiles( join(REPO_ROOT, "system/public/aesthetic.computer/disks"), ".mjs", ); const piecesLisp = countFiles( join(REPO_ROOT, "system/public/aesthetic.computer/disks"), ".lisp", ); const piecesTotal = piecesMjs + piecesLisp; const libModules = countFiles( join(REPO_ROOT, "system/public/aesthetic.computer/lib"), ".mjs", ); const functionCount = countFiles(join(REPO_ROOT, "system/netlify/functions"), ".mjs"); const sessionModules = countFiles(join(REPO_ROOT, "session-server"), ".mjs"); const papersCount = existsSync(INDEX_PATH) ? (readFileSync(INDEX_PATH, "utf8").match(/class="p"\s+data-paper-id=/g) || []) .length : 0; const readingsCount = countFiles( join(REPO_ROOT, "system/public/assets/papers/readings/text"), ".txt", ); html = replaceOrThrow( html, /