// Server-side persistence for editor-authored chart templates. Each site owns // its dashboard at sites//chart-templates.json; the export build copies // that site's file into its staging dir so build:site composes it into the // bundle as /chart-templates.json for the static viewer to fetch. import path from "node:path"; import { readFileSync } from "node:fs"; import { writeJsonAtomicSync } from "./jsonFile-server"; import type { Paths } from "./paths"; import { siteChartTemplatesFile, siteIndexDir } from "./site"; import { defaultTemplates, defaultHomepageTemplates, type ChartTemplates, type Dashboard, } from "./chartConfig"; const EXPORT_TEMPLATES_FILE = "chart-templates.json"; // Staging path (NOT served). Composed into a site's bundle as // /chart-templates.json by build:site. export function siteTemplatesStagingPath(paths: Paths, siteId: string): string { return path.join(siteIndexDir(paths, siteId), EXPORT_TEMPLATES_FILE); } function coerce(raw: unknown): ChartTemplates { if (!raw || typeof raw !== "object") return defaultTemplates(); const r = raw as Partial; const base = defaultTemplates(); return { version: 1, defaultDashboard: r.defaultDashboard && Array.isArray(r.defaultDashboard.charts) ? (r.defaultDashboard as Dashboard) : base.defaultDashboard, }; } export function readTemplates(paths: Paths, siteId: string): ChartTemplates { try { return coerce( JSON.parse(readFileSync(siteChartTemplatesFile(paths, siteId), "utf8")), ); } catch { return defaultTemplates(); } } // Indented, no trailing newline — these files' historical bytes. function writeJsonAtomic(filePath: string, value: unknown): void { writeJsonAtomicSync(filePath, value, { newline: false, mkdir: true }); } export function writeTemplates( paths: Paths, siteId: string, templates: ChartTemplates, ): void { writeJsonAtomic(siteChartTemplatesFile(paths, siteId), templates); } // Copy a site's canonical templates into its staging dir. Called by the export // build (build:templates) for each site. export function syncTemplatesToExport(paths: Paths, siteId: string): void { const templates = readTemplates(paths, siteId); writeJsonAtomic(siteTemplatesStagingPath(paths, siteId), templates); } // ─── Archilyzer hub (homepage) chart templates ─── // The hub owns one cross-site dashboard at homepageChartTemplatesFile // (sites/_homepage/chart-templates.json). Falls back to the code-level hub // default (per-site acquisition charts) when unauthored. Read server-side by the // homepage app and the editor's hub charts authoring; no staging/composition // step — the homepage app reads it directly at build. function coerceHomepage(raw: unknown): ChartTemplates { if (!raw || typeof raw !== "object") return defaultHomepageTemplates(); const r = raw as Partial; const base = defaultHomepageTemplates(); return { version: 1, defaultDashboard: r.defaultDashboard && Array.isArray(r.defaultDashboard.charts) ? (r.defaultDashboard as Dashboard) : base.defaultDashboard, }; } export function readHomepageTemplates(paths: Paths): ChartTemplates { try { return coerceHomepage( JSON.parse(readFileSync(paths.homepageChartTemplatesFile, "utf8")), ); } catch { return defaultHomepageTemplates(); } } export function writeHomepageTemplates( paths: Paths, templates: ChartTemplates, ): void { writeJsonAtomic(paths.homepageChartTemplatesFile, templates); }