import { execFile } from "node:child_process"; import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { promisify } from "node:util"; import type { Plugin } from "vite"; const run = promisify(execFile); /** * Dev-only PDF compiler. Uses the same local TeX Live toolchain as the * repository Makefile, so dev output is byte-identical to `make`. * In production the client falls back to the Worker's /api/pdf route. */ export function latexDev(): Plugin { return { name: "latex-dev-compiler", apply: "serve", configureServer(server) { server.middlewares.use("/local/compile", (req, res) => { if (req.method !== "POST") { res.statusCode = 405; res.end(); return; } const chunks: Buffer[] = []; req.on("data", (c: Buffer) => chunks.push(c)); req.on("end", () => { void (async () => { let dir: string | undefined; try { const { latex } = JSON.parse(Buffer.concat(chunks).toString("utf8")) as { latex: string; }; dir = await mkdtemp(join(tmpdir(), "cvtex-")); await writeFile(join(dir, "main.tex"), latex, "utf8"); await run("pdflatex", ["-interaction=nonstopmode", "-halt-on-error", "main.tex"], { cwd: dir, timeout: 30_000, }); const pdf = await readFile(join(dir, "main.pdf")); res.setHeader("Content-Type", "application/pdf"); res.end(pdf); } catch (err) { let log = err instanceof Error ? err.message : String(err); if (dir) { try { log = (await readFile(join(dir, "main.log"), "utf8")).slice(-4000); } catch { /* no log written */ } } res.statusCode = 422; res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify({ error: "pdflatex failed", log })); } finally { if (dir) void rm(dir, { recursive: true, force: true }); } })(); }); }); }, }; }