Files
cv/app/plugins/latex-dev.ts
T

65 lines
2.2 KiB
TypeScript

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 });
}
})();
});
});
},
};
}