setup.ts
113 lines · make-pdf/src/setup.ts at 3f79d81
1/**
2 * `$P setup` — guided smoke test.
3 *
4 * Flow:
5 * 1. Find a browser: Aside (primary) or gstack's own headless browser (fallback)
6 * 2. Render a tiny HTML page through it
7 * 3. Verify pdftotext is installed (warn, don't fail)
8 * 4. Generate a smoke-test PDF from an inline 2-paragraph fixture
9 * 5. Print a 3-command cheatsheet
10 */
12import * as path from "node:path";
13import * as fs from "node:fs";
15import { NO_BROWSER_HELP, pickEngine, render, renderTmpDir } from "../../lib/aside-render";
16import { ExitCode } from "./types";
17import { resolvePdftotext, PdftotextUnavailableError } from "./pdftotext";
18import { OUTPUT_TMP_DIR, generate } from "./orchestrator";
20export async function runSetup(): Promise<void> {
21 process.stderr.write("make-pdf setup — verifying install\n\n");
23 // 1. A browser: Aside when it answers, else gstack's own
24 process.stderr.write(" [1/5] Checking for a browser...");
25 const engine = pickEngine();
26 if (!engine.engine) {
27 process.stderr.write(" FAIL\n");
28 process.stderr.write(`\n${engine.error}\n`);
29 process.exit(ExitCode.BrowserUnavailable);
30 }
31 const via = engine.engine === "aside" ? `Aside ${engine.version}` : "gstack browser";
32 process.stderr.write(engine.engine === "aside"
33 ? ` Aside OK (${engine.version})\n`
34 : ` gstack browser OK (fallback: ${engine.bin}; Aside is not running)\n`);
36 // 2. Render smoke: open a tiny page and read it back
37 process.stderr.write(` [2/5] Rendering through ${via}...`);
38 const smokeDir = fs.mkdtempSync(path.join(renderTmpDir(), "make-pdf-setup-"));
39 try {
40 const file = path.join(smokeDir, "smoke.html");
41 fs.writeFileSync(file, "<!doctype html><title>make-pdf smoke</title><p id=t>browser-ok</p>", "utf8");
42 const r = await render({ file, steps: [{ kind: "eval", expression: "document.getElementById('t').textContent" }] });
43 if (!r.ok || r.evals[0] !== "browser-ok") {
44 throw new Error(r.error ?? `unexpected page text: ${r.evals[0]}`);
45 }
46 process.stderr.write(" OK\n");
47 } catch (err: any) {
48 process.stderr.write(" FAIL\n");
49 process.stderr.write(`\n${via} could not render a page: ${err.message}\n`);
50 process.stderr.write(`To fix: ${NO_BROWSER_HELP}\n`);
51 process.exit(ExitCode.BrowserUnavailable);
52 } finally {
53 fs.rmSync(smokeDir, { recursive: true, force: true });
54 }
56 // 3. pdftotext (optional — CI gate only)
57 process.stderr.write(" [3/5] Checking pdftotext (optional)...");
58 try {
59 const info = resolvePdftotext();
60 process.stderr.write(` OK (${info.flavor}, ${info.version.split(" ").slice(-1)[0] || "version unknown"})\n`);
61 } catch (err) {
62 process.stderr.write(" SKIP\n");
63 if (err instanceof PdftotextUnavailableError) {
64 process.stderr.write(
65 " pdftotext not installed. This is optional — only the CI\n" +
66 " copy-paste gate needs it. To enable:\n" +
67 " macOS: brew install poppler\n" +
68 " Ubuntu: sudo apt-get install poppler-utils\n",
69 );
70 }
71 }
73 // 4. Render smoke-test PDF
74 process.stderr.write(" [4/5] Generating smoke-test PDF...\n");
75 const fixture = [
76 "# Hello from make-pdf",
77 "",
78 "This is a two-paragraph smoke test. If you can read this sentence in the PDF that just opened, the pipeline works end-to-end.",
79 "",
80 "The second paragraph contains curly quotes (\"hello\"), an em dash -- like this, and an ellipsis... all of which should render correctly.",
81 "",
82 ].join("\n");
83 const fixturePath = path.join(OUTPUT_TMP_DIR, `make-pdf-smoke-${process.pid}.md`);
84 const outPath = path.join(OUTPUT_TMP_DIR, `make-pdf-smoke-${process.pid}.pdf`);
85 fs.writeFileSync(fixturePath, fixture, "utf8");
87 try {
88 await generate({
89 input: fixturePath,
90 output: outPath,
91 quiet: true,
92 pageNumbers: true,
93 });
94 process.stderr.write(` PASSED. Smoke test saved to ${outPath}\n`);
95 } catch (err: any) {
96 process.stderr.write(` FAILED: ${err.message}\n`);
97 process.exit(2);
98 } finally {
99 try { fs.unlinkSync(fixturePath); } catch { /* ignore */ }
100 }
102 // 5. Cheatsheet
103 process.stderr.write(" [5/5] All checks passed.\n\n");
104 process.stderr.write([
105 "make-pdf is ready. Try:",
106 " $P generate letter.md # default memo mode",
107 " $P generate --cover --toc essay.md # full publication",
108 " $P generate --watermark DRAFT memo.md # diagonal watermark",
109 "",
110 `Smoke-test PDF: ${outPath}`,
111 "",
112 ].join("\n"));
113}