--pdf [output.pdf]");
process.exit(0);
}
const pdfMode = args.includes("--pdf");
const filteredArgs = args.filter((a) => a !== "--pdf");
const inputPath = path.resolve(filteredArgs[0]);
const defaultExt = pdfMode ? ".pdf" : ".html";
const outputPath = filteredArgs[1]
? path.resolve(filteredArgs[1])
: inputPath.replace(/\.md$/i, defaultExt);
if (!fs.existsSync(inputPath)) {
console.error(`Error: file not found — ${inputPath}`);
process.exit(1);
}
const md = fs.readFileSync(inputPath, "utf-8");
// ── Marked setup ──────────────────────────────────────────────────────────────
// Custom renderer: leave mermaid code blocks as for client‑side rendering
const renderer = new marked.Renderer();
const defaultCode = renderer.code.bind(renderer);
renderer.code = function ({ text, lang }) {
if (lang === "mermaid") {
return `${escapeHtml(text)}\n`;
}
return defaultCode({ text, lang });
};
// Collect headings for TOC while rendering
const tocItems = [];
renderer.heading = function ({ text, depth }) {
const slug = text
.toLowerCase()
.replace(/<[^>]*>/g, "")
.replace(/[^\w\s-]/g, "")
.replace(/\s+/g, "-")
.replace(/-+/g, "-")
.trim();
tocItems.push({ text: stripHtml(text), depth, slug });
return `${text}\n`;
};
function escapeHtml(s) {
return s
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """);
}
function stripHtml(s) {
return s.replace(/<[^>]*>/g, "");
}
marked.setOptions({ renderer, gfm: true, breaks: false });
const bodyHtml = marked.parse(md);
// ── Build TOC HTML ────────────────────────────────────────────────────────────
function buildToc(items) {
if (items.length === 0) return "";
let html = "";
let prevDepth = 0;
for (const item of items) {
if (item.depth > prevDepth) {
for (let i = prevDepth; i < item.depth; i++) html += "";
} else if (item.depth < prevDepth) {
for (let i = item.depth; i < prevDepth; i++) html += "
";
}
html += `${escapeHtml(item.text)}\n`;
prevDepth = item.depth;
}
for (let i = 0; i < prevDepth; i++) html += "";
return html;
}
const tocHtml = buildToc(tocItems);
const docTitle = tocItems.length > 0 ? tocItems[0].text : "Document";
// ── Full HTML template ────────────────────────────────────────────────────────
const html = `
${escapeHtml(docTitle)}
${bodyHtml}
`;
if (pdfMode) {
(async () => {
const puppeteer = require("puppeteer-core");
const browser = await puppeteer.launch({
executablePath:
process.env.CHROME_PATH || "/usr/bin/google-chrome-stable",
args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
const page = await browser.newPage();
// Capture console for debugging
page.on("console", (msg) => {
if (msg.type() === "error") console.error(" [browser]", msg.text());
});
// Load the HTML content directly (no CDN scripts — we inject mermaid below)
// Strip the CDN mermaid script so we can inject it ourselves via puppeteer
const htmlForPdf = html.replace(
/