import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; const repoRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); const pagePath = join(repoRoot, "content", "pages", "home.json"); const templateRoot = join(repoRoot, "templates", "pages", "home"); const shellPath = join(templateRoot, "shell.html"); const blockRoot = join(templateRoot, "blocks"); const REGION_TOKEN = { beforeCViz: "{{NODEDC_BLOCKS_BEFORE_CVIZ}}", cViz: "{{NODEDC_BLOCKS_CVIZ}}", sections: "{{NODEDC_BLOCKS_SECTIONS}}", }; const page = JSON.parse(await readFile(pagePath, "utf8")); const shell = await readFile(shellPath, "utf8"); const templateCache = new Map(); async function readTemplate(templateName) { if (!templateName || templateName.includes("..") || templateName.includes("/")) { throw new Error(`Unsafe template name: ${templateName}`); } if (!templateCache.has(templateName)) { templateCache.set(templateName, await readFile(join(blockRoot, templateName), "utf8")); } return templateCache.get(templateName); } function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function getByPath(source, path) { return path.split(".").reduce((value, key) => value?.[key], source); } function isTruthy(value) { if (Array.isArray(value)) return value.length > 0; return Boolean(value); } function cloneValue(value) { return value == null ? value : JSON.parse(JSON.stringify(value)); } function allBlocks(model) { return (model.regions ?? []).flatMap((region) => region.blocks ?? []); } function applySharedDemoRequest(model) { const blocks = allBlocks(model); const source = blocks.find((block) => block.id === "hero-waitlist"); if (!source?.content) return; for (const block of blocks) { if (block.id === source.id || block.type !== "waitlistFiles") continue; block.content ??= {}; block.content.accessCard = cloneValue(source.content.accessCard); block.content.form = cloneValue(source.content.form); block.content.legal ??= { enabled: true, html: "" }; block.content.legal.html = source.content.legalHtml ?? block.content.legal.html ?? ""; } } const LINK_WINDOW_TARGETS = new Set(["_blank", "_self", "_parent", "_top"]); function isWindowTarget(value) { return LINK_WINDOW_TARGETS.has(String(value || "").trim()); } function isExternalHref(value) { return /^(https?:)?\/\//i.test(String(value || "")) || /^(mailto:|tel:)/i.test(String(value || "")); } function isInertHref(value) { const href = String(value || "").trim(); return !href || href === "#" || href === "#hero" || href.toLowerCase().startsWith("javascript:"); } function normalizeLinkForRender(item) { if (!item || typeof item !== "object") return; let href = item.href ?? ""; let target = item.target ?? ""; let linkTarget = item.linkTarget ?? ""; if (isWindowTarget(target)) { linkTarget ||= target; target = ""; } const mode = item.linkMode === "href" || item.linkMode === "target" ? item.linkMode : isExternalHref(href) ? "href" : "target"; if (mode === "target" && !target && href && !isExternalHref(href)) { target = href; href = ""; } const primaryHref = mode === "href" ? href : target; const fallbackHref = mode === "href" ? target : href; item.resolvedHref = primaryHref || fallbackHref || "#"; item.resolvedTarget = mode === "href" ? linkTarget : ""; } function normalizeDockItemForRender(item) { if (!item || typeof item !== "object") return; const href = String(item.href || "").trim(); const opensWindow = !isInertHref(href); item.resolvedDockHref = href || "#"; item.resolvedDockTarget = opensWindow ? "_blank" : ""; item.resolvedDockRel = opensWindow ? "noopener noreferrer" : ""; } function applyRenderableLinks(model) { const staticLinks = model.staticElements?.content?.navigation?.links; if (Array.isArray(staticLinks)) { staticLinks.forEach(normalizeLinkForRender); } const dockItems = model.staticElements?.content?.dock?.items; if (Array.isArray(dockItems)) { dockItems.forEach(normalizeDockItemForRender); } for (const block of allBlocks(model)) { const footer = block.content?.footer; if (!footer) continue; if (Array.isArray(footer.navLinks)) footer.navLinks.forEach(normalizeLinkForRender); if (Array.isArray(footer.contactLinks)) footer.contactLinks.forEach(normalizeLinkForRender); } } function escapeHtml(value) { return String(value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">"); } function escapeAttr(value) { return escapeHtml(value).replaceAll('"', """).replaceAll("'", "'"); } function escapeJsString(value) { return String(value) .replaceAll("\\", "\\\\") .replaceAll('"', '\\"') .replaceAll("\n", "\\n") .replaceAll("\r", "\\r") .replaceAll("\u2028", "\\u2028") .replaceAll("\u2029", "\\u2029") .replaceAll(" { const value = getByPath(block, path); if (value === undefined) { throw new Error(`Missing token value "${path}" in block "${block.id}"`); } if (mode === "html") { return value == null ? "" : String(value); } if (mode === "attr") { return value == null ? "" : escapeAttr(value); } if (mode === "js") { return value == null ? "" : escapeJsString(value); } return value == null ? "" : escapeHtml(value); }); } function renderIfBlocks(html, block) { let rendered = html; let previous = null; while (rendered !== previous) { previous = rendered; rendered = rendered.replace( /\{\{#if\s+([a-zA-Z0-9_.-]+)\s*\}\}((?:(?!\{\{#if|\{\{\/if\}\})[\s\S])*)\{\{\/if\}\}/g, (_match, path, innerHtml) => { const value = getByPath(block, path); return isTruthy(value) ? renderTemplate(innerHtml, block) : ""; }, ); } return rendered; } function renderEachBlocks(html, block) { return html.replace(/\{\{#each\s+([a-zA-Z0-9_.-]+)\s*\}\}([\s\S]*?)\{\{\/each\}\}/g, (_match, path, innerHtml) => { const items = getByPath(block, path); if (!Array.isArray(items)) { throw new Error(`Each token "${path}" in block "${block.id}" must point to an array`); } return items .map((item) => { const itemScope = item && typeof item === "object" && !Array.isArray(item) ? item : { value: item }; return renderTemplate(innerHtml, { ...block, ...itemScope }); }) .join(""); }); } function renderTemplate(html, block) { return renderTemplateTokens(renderIfBlocks(renderEachBlocks(html, block), block), block); } function scopeDuplicateIds(html, scope) { const ids = [...html.matchAll(/\sid="([^"]+)"/g)].map((match) => match[1]); if (!ids.length) { return html; } let scoped = html.replace(/\sid="([^"]+)"/g, (_match, id) => ` id="${scope}-${id}"`); for (const id of ids) { const escaped = escapeRegExp(id); scoped = scoped.replace( new RegExp(`\\s(data-target|for|aria-controls|aria-labelledby|aria-describedby)="${escaped}"`, "g"), (_match, attr) => ` ${attr}="${scope}-${id}"`, ); scoped = scoped.replace(new RegExp(`(href="[^"]*)#${escaped}(")`, "g"), `$1#${scope}-${id}$2`); } return scoped; } async function renderBlock(block, renderCounts) { if (block.enabled === false) { return ""; } const templateName = block.template; const renderCount = renderCounts.get(templateName) ?? 0; renderCounts.set(templateName, renderCount + 1); const html = renderTemplate(await readTemplate(templateName), block); if (renderCount === 0 && block.scopeIds !== true) { return html; } return scopeDuplicateIds(html, block.id); } async function renderRegion(region) { const renderCounts = new Map(); const blocks = []; for (const block of region.blocks ?? []) { blocks.push(await renderBlock(block, renderCounts)); } return blocks.join(""); } async function renderSections() { const renderCounts = new Map(); const output = []; let cVizRun = []; const flushCVizRun = () => { if (!cVizRun.length) return; output.push(`