import fs from "node:fs/promises"; import path from "node:path"; import { ReferenceClient } from "@permaweb/references"; const ROOT = process.cwd(); const DIST_DIR = path.join(ROOT, "dist"); const DIST_INDEX_HTML = path.join(DIST_DIR, "index.html"); const SRC_CONFIG = path.join(ROOT, "src", "config.ts"); const DEFAULTS = { BLOG_NAME: "hyperzine", BLOG_SITE_URL: "https://hyperzine.xyz", BLOG_DEFAULT_DESCRIPTION: "hyperzine", ARWEAVE_GATEWAY: "https://arweave.net", MANIFEST_REFERENCE_NAME: "", MANIFEST_TX_ID: "" }; const asObject = (value) => (typeof value === "object" && value !== null ? value : null); const asString = (value) => (typeof value === "string" && value.trim().length > 0 ? value : ""); const htmlEscape = (value) => value .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """); const normalizeSlashPath = (input) => { const cleaned = input.trim().replace(/^\/+|\/+$/g, ""); return cleaned ? `/${cleaned}` : "/"; }; const toAbsoluteUrl = (siteUrl, inputPath) => new URL(normalizeSlashPath(inputPath), siteUrl).toString(); const parseConfig = async () => { let source = ""; try { source = await fs.readFile(SRC_CONFIG, "utf8"); } catch { return DEFAULTS; } const pick = (key) => { const match = source.match( new RegExp(`export\\s+const\\s+${key}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, "m") ); const value = match?.[1] || match?.[2]; return value ?? DEFAULTS[key]; }; return { BLOG_NAME: pick("BLOG_NAME"), BLOG_SITE_URL: pick("BLOG_SITE_URL"), BLOG_DEFAULT_DESCRIPTION: pick("BLOG_DEFAULT_DESCRIPTION"), ARWEAVE_GATEWAY: pick("ARWEAVE_GATEWAY"), MANIFEST_REFERENCE_NAME: pick("MANIFEST_REFERENCE_NAME"), MANIFEST_TX_ID: pick("MANIFEST_TX_ID") }; }; const parseFrontmatter = (input) => { const obj = asObject(input); if (!obj) return {}; const bannerValue = obj.banner; const bannerObj = asObject(bannerValue); const banner = asString(bannerValue) || asString(bannerObj?.txId) || asString(bannerObj?.id) || asString(bannerObj?.src) || asString(bannerObj?.url); return { title: asString(obj.title), desc: asString(obj.desc), description: asString(obj.description), excerpt: asString(obj.excerpt), slug: asString(obj.slug), banner, date: asString(obj.date), updated: asString(obj.updated) }; }; const parseManifest = (input) => { const root = asObject(input); const posts = Array.isArray(root?.posts) ? root.posts : []; return posts .map((entry) => { const post = asObject(entry); if (!post) return null; const frontmatter = parseFrontmatter(post.frontmatter); const bannerTxIdValue = post.bannerTxId; const bannerTxIdObject = asObject(bannerTxIdValue); const bannerTxId = asString(bannerTxIdValue) || asString(bannerTxIdObject?.txId) || asString(bannerTxIdObject?.id) || asString(bannerTxIdObject?.src) || asString(bannerTxIdObject?.url); const slug = asString(post.slug) || frontmatter.slug; const postTxId = asString(post.postTxId); if (!slug || !postTxId) return null; return { slug, title: asString(post.title) || frontmatter.title || "Untitled", description: asString(post.description) || frontmatter.desc || frontmatter.description || "", excerpt: asString(post.excerpt) || frontmatter.excerpt || "", bannerTxId: bannerTxId || frontmatter.banner || "", publishedAt: asString(post.publishedAt) || frontmatter.date || "", updated: asString(post.updated) || frontmatter.updated || "" }; }) .filter(Boolean) .sort((a, b) => { const aDate = a.publishedAt || ""; const bDate = b.publishedAt || ""; return aDate < bDate ? 1 : -1; }); }; const fetchManifestPosts = async (manifestTxId) => { if (!manifestTxId) return []; const url = `https://arweave.net/${manifestTxId}`; const response = await fetch(url, { cache: "no-store" }); if (!response.ok) throw new Error(`Manifest fetch failed (${response.status})`); const payload = await response.json(); const posts = parseManifest(payload); return { txId: manifestTxId, posts }; }; const resolveManifestTxId = async (config) => { if (config.MANIFEST_REFERENCE_NAME) { try { const names = new ReferenceClient({ gateway: config.ARWEAVE_GATEWAY || DEFAULTS.ARWEAVE_GATEWAY }); const value = await names.resolveName(config.MANIFEST_REFERENCE_NAME); if (typeof value === "string" && value.length > 0) return value; throw new Error(`Reference ${config.MANIFEST_REFERENCE_NAME} did not resolve to a manifest id`); } catch (error) { console.warn( `[prerender-meta] Falling back to configured manifest; reference ${config.MANIFEST_REFERENCE_NAME} failed: ${error.message}` ); } } return config.MANIFEST_TX_ID; }; const fetchCurrentManifestPosts = async (config) => { const manifestTxId = await resolveManifestTxId(config); if (!manifestTxId) return []; try { return (await fetchManifestPosts(manifestTxId)).posts; } catch (error) { if (!config.MANIFEST_REFERENCE_NAME || manifestTxId === config.MANIFEST_TX_ID) throw error; console.warn( `[prerender-meta] Falling back to configured manifest; reference manifest ${manifestTxId} failed: ${error.message}` ); } return config.MANIFEST_TX_ID ? (await fetchManifestPosts(config.MANIFEST_TX_ID)).posts : []; }; const buildMetaTags = ({ title, description, canonicalUrl, siteName, imageUrl, type = "website", publishedTime, modifiedTime }) => { const tags = [ `