// Client documents: render markdown for the in-app viewer, export/serve branded PDFs. import fs from "node:fs"; import path from "node:path"; import { marked } from "marked"; import { safeSlug } from "./clients.js"; import { toPdf } from "../../tools/report-pdf/build.mjs"; import { reportOptions } from "./workspace.js"; /** Resolve a document path inside one client's folder (blocks ../ escapes). */ export function resolveDoc(slug, rel) { const dir = safeSlug(slug); const full = path.resolve(dir, String(rel || "")); if (!full.startsWith(dir + path.sep) || !/\.(md|pdf)$/.test(full) || !fs.existsSync(full)) { throw Object.assign(new Error("Document not found."), { status: 404 }); } return full; } function splitFrontMatter(md) { const m = md.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/); if (!m) return { meta: {}, body: md }; const meta = {}; const kpis = []; for (const line of m[1].split(/\r?\n/)) { const kpi = line.match(/^\s+-\s+(.*)$/); if (kpi) { const [value, label = "", note = ""] = kpi[1].split("|").map((s) => s.trim()); kpis.push({ value, label, note }); continue; } const kv = line.match(/^(\w+):\s*(.*)$/); if (kv && kv[1] !== "kpis") meta[kv[1]] = kv[2].trim(); } return { meta: { ...meta, kpis }, body: md.slice(m[0].length) }; } export function renderDoc(slug, rel) { const full = resolveDoc(slug, rel); if (!full.endsWith(".md")) throw Object.assign(new Error("Only markdown documents can be previewed."), { status: 400 }); const { meta, body } = splitFrontMatter(fs.readFileSync(full, "utf8")); // Content is written by our own team/AI runs; links open in a new tab. const html = marked.parse(body, { gfm: true, breaks: true }).replace(/