// server/templates/render.ts import Mustache from "mustache"; import { readFileSync, existsSync } from "node:fs"; import { resolve } from "node:path"; const TEMPLATES_ROOT = resolve(process.cwd(), "templates"); export interface TemplateVars { aptProxy?: string | null; aptProxyUrl?: string | null; // proxy persistant (apt_proxy_persistent) // Docker template vars composeRoots?: string | number | null; composeScanDepth?: string | number | null; stackDir?: string | null; } export function renderTemplate( relPath: string, vars: TemplateVars, opts?: { tags?: [string, string] }, ): string { const tpl = readFileSync(resolve(TEMPLATES_ROOT, relPath), "utf8"); // Les templates Docker contiennent des Go-templates {{...}} : on bascule les // délimiteurs Mustache sur <% %> pour ne pas les interpréter. const tags = opts?.tags ?? (relPath.startsWith("docker/") ? (["<%", "%>"] as [string, string]) : undefined); // eslint-disable-next-line @typescript-eslint/no-explicit-any return Mustache.render(tpl, vars, {}, { escape: (s: any) => s, ...(tags ? { tags } : {}) } as any); } /** Existence par défaut d'un template relatif à templates/. */ function defaultExists(rel: string): boolean { return existsSync(resolve(TEMPLATES_ROOT, rel)); } /** * Résout le chemin de template le plus spécifique pour (action, OS) : * `/.sh.tpl` s'il existe, sinon fallback base `apt/.sh.tpl`. * `exists` est injectable pour les tests. */ export function resolveTemplate( action: string, osFamily: string, exists: (rel: string) => boolean = defaultExists, ): string { const specific = `${osFamily}/${action}.sh.tpl`; if (osFamily !== "unknown" && osFamily !== "apt" && exists(specific)) return specific; return `apt/${action}.sh.tpl`; }