133 lines
3.9 KiB
JavaScript
133 lines
3.9 KiB
JavaScript
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const { spawnSync, execFileSync } = require("node:child_process");
|
|
|
|
function parseArgs(argv) {
|
|
const out = { fixtures: "/fixtures", outDir: "/tmp/pdf-fixtures-out" };
|
|
for (let i = 2; i < argv.length; i++) {
|
|
const a = argv[i];
|
|
if (a === "--fixtures") out.fixtures = argv[++i];
|
|
else if (a === "--out") out.outDir = argv[++i];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
function readManifestSHA() {
|
|
const p = "/opt/forgejo-pdf/manifest.json";
|
|
const b = fs.readFileSync(p, "utf8");
|
|
const j = JSON.parse(b);
|
|
if (!j.manifest_sha || typeof j.manifest_sha !== "string") {
|
|
throw new Error("manifest.json missing manifest_sha");
|
|
}
|
|
return j.manifest_sha;
|
|
}
|
|
|
|
function extractKnownUniqueString(md) {
|
|
const m = md.match(/KNOWN_UNIQUE_STRING:\s*([A-Za-z0-9_\-\.]+)/);
|
|
return m ? m[1] : null;
|
|
}
|
|
|
|
function containsMermaid(md) {
|
|
return /```mermaid[\s\S]*?```/m.test(md);
|
|
}
|
|
|
|
function run(cmd, args, opts = {}) {
|
|
const res = spawnSync(cmd, args, { encoding: "utf8", ...opts });
|
|
return res;
|
|
}
|
|
|
|
function main() {
|
|
const { fixtures, outDir } = parseArgs(process.argv);
|
|
fs.mkdirSync(outDir, { recursive: true });
|
|
|
|
const manifestSHA = readManifestSHA();
|
|
|
|
const pdfConfig = {
|
|
pdf: {
|
|
determinism: "strict",
|
|
timestamp: "commit_time",
|
|
typography: "professional",
|
|
mermaid: { strategy: "balanced", caption: false },
|
|
orphansWidows: { enforce: true },
|
|
footer: { enabled: true }
|
|
}
|
|
};
|
|
|
|
const files = fs
|
|
.readdirSync(fixtures)
|
|
.filter((f) => f.endsWith(".md"))
|
|
.sort();
|
|
|
|
if (files.length === 0) {
|
|
throw new Error("no fixture markdown files found");
|
|
}
|
|
|
|
for (const f of files) {
|
|
const mdPath = path.join(fixtures, f);
|
|
const md = fs.readFileSync(mdPath, "utf8");
|
|
const expected = extractKnownUniqueString(md);
|
|
if (!expected) throw new Error(`fixture missing KNOWN_UNIQUE_STRING: ${f}`);
|
|
|
|
const input = {
|
|
markdown: md,
|
|
repoMeta: {
|
|
owner: "fixture",
|
|
repo: "forgejo-pdf",
|
|
path: f,
|
|
repoID: 1,
|
|
commitSHA: "0123456789abcdef0123456789abcdef01234567",
|
|
commitTimeRFC3339: "2020-01-02T03:04:05Z"
|
|
},
|
|
config: pdfConfig,
|
|
manifestSHA
|
|
};
|
|
|
|
const jobDir = fs.mkdtempSync(path.join(outDir, "job-"));
|
|
const inPath = path.join(jobDir, "input.json");
|
|
const outPath = path.join(jobDir, "output.pdf");
|
|
fs.writeFileSync(inPath, JSON.stringify(input), "utf8");
|
|
|
|
const res = run("node", ["src/index.js", "--in", inPath, "--out", outPath], {
|
|
cwd: "/opt/forgejo-pdf"
|
|
});
|
|
if (res.status !== 0) {
|
|
throw new Error(`worker failed for ${f}: ${res.stderr.trim() || res.stdout.trim()}`);
|
|
}
|
|
|
|
const logs = res.stderr
|
|
.split("\n")
|
|
.map((l) => l.trim())
|
|
.filter(Boolean)
|
|
.map((l) => {
|
|
try {
|
|
return JSON.parse(l);
|
|
} catch {
|
|
return null;
|
|
}
|
|
})
|
|
.filter(Boolean);
|
|
|
|
const done = logs.findLast ? logs.findLast((l) => l.event === "done") : logs.reverse().find((l) => l.event === "done");
|
|
if (!done) throw new Error(`missing done log for ${f}`);
|
|
if (done.blocked_requests !== 0) throw new Error(`blocked_requests != 0 for ${f}`);
|
|
|
|
const hasMermaid = containsMermaid(md);
|
|
if (hasMermaid && (!Number.isFinite(done.mermaid_count) || done.mermaid_count < 1)) {
|
|
throw new Error(`expected mermaid_count >= 1 for ${f}`);
|
|
}
|
|
if (!hasMermaid && done.mermaid_count !== 0) {
|
|
throw new Error(`expected mermaid_count == 0 for ${f}`);
|
|
}
|
|
|
|
execFileSync("qpdf", ["--check", outPath], { stdio: "inherit" });
|
|
|
|
const text = execFileSync("pdftotext", [outPath, "-"], { encoding: "utf8" });
|
|
const normalized = text.replace(/\s+/g, "");
|
|
const expectedNorm = expected.replace(/\s+/g, "");
|
|
if (!normalized.includes(expectedNorm)) {
|
|
throw new Error(`pdftotext missing expected marker for ${f}: ${expected}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
main();
|