#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const { marked } = require('marked'); // Custom script to handle Mermaid rendering and fix internal links async function convertMarkdownToPDF() { const inputFile = process.argv[2] || 'complete-documentation.md'; const outputFile = inputFile.replace('.md', '.pdf'); console.log(`Converting ${inputFile} to PDF with Mermaid support...`); try { // Read the markdown file let content = fs.readFileSync(`/app/docs/${inputFile}`, 'utf8'); // Remove YAML front matter if present content = content.replace(/^---\n[\s\S]*?\n---\n/, ''); // Remove emojis that don't render well in PDF content = content.replace(/📋|🛠️|🔧|📊|🏗️|🎉|✅|❌|🚀|📝|📚|🔍|⚠️|💡/g, ''); // Helper function to generate section ID from text function generateSectionId(text) { return text .toLowerCase() .replace(/[^\w\s-]/g, '') // Remove special chars .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/-+/g, '-') // Collapse multiple hyphens .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens } // Fix internal links to work within the same document // Convert [text](filename.md) to [text](#section-name) let linkCount = 0; content = content.replace(/\[([^\]]+)\]\((\d+-[^)]+\.md)\)/g, (match, linkText, filename) => { // Use the link text to generate the section ID (since that's what the header will be) const sectionId = generateSectionId(linkText); linkCount++; return `[${linkText}](#${sectionId})`; }); // Fix relative links ./filename.md content = content.replace(/\[([^\]]+)\]\(\.\/(\d+-[^)]+\.md)\)/g, (match, linkText, filename) => { const sectionId = generateSectionId(linkText); linkCount++; return `[${linkText}](#${sectionId})`; }); console.log(`✅ Processed ${linkCount} internal links`); // Process Mermaid diagrams let mermaidCounter = 0; content = content.replace(/```mermaid\n([\s\S]*?)\n```/g, (match, mermaidCode) => { mermaidCounter++; const diagramFile = `/app/docs/diagram-${mermaidCounter}.mmd`; const imageFile = `/app/docs/diagram-${mermaidCounter}.png`; // Write mermaid code to temporary file fs.writeFileSync(diagramFile, mermaidCode.trim()); try { // Generate PNG from mermaid execSync(`mmdc -i ${diagramFile} -o ${imageFile} -w 800 -H 600 --backgroundColor white`); // Replace with image reference return `![Diagram ${mermaidCounter}](diagram-${mermaidCounter}.png)`; } catch (error) { console.warn(`Warning: Failed to render Mermaid diagram ${mermaidCounter}:`, error.message); // Return original code block if mermaid fails return match; } }); // Configure marked to generate proper IDs for headers marked.use({ renderer: { heading(text, level) { // Generate ID from text content const id = text .toLowerCase() .replace(/[^\w\s-]/g, '') // Remove special chars .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/-+/g, '-') // Collapse multiple hyphens .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens return `${text}`; } } }); // Convert markdown to HTML const html = marked(content, { gfm: true, breaks: false, headerIds: true, mangle: false }); // Create complete HTML document with CSS const cssContent = fs.readFileSync('/app/docs/custom-styles.css', 'utf8'); const fullHtml = ` DoczyAI Documentation ${html} `; // Write HTML to temporary file const htmlFile = `/app/docs/temp-${inputFile.replace('.md', '.html')}`; fs.writeFileSync(htmlFile, fullHtml); // Use Chromium to generate PDF with clickable links (suppress stderr) execSync(`/usr/bin/chromium-browser --headless --disable-gpu --no-sandbox --run-all-compositor-stages-before-draw --virtual-time-budget=10000 --print-to-pdf=/app/docs/${outputFile} --print-to-pdf-no-header file://${htmlFile} 2>/dev/null`); console.log(`✅ PDF generated: ${outputFile}`); // Clean up temporary files try { fs.unlinkSync(htmlFile); for (let i = 1; i <= mermaidCounter; i++) { try { fs.unlinkSync(`/app/docs/diagram-${i}.mmd`); // Keep PNG files for debugging, remove in production // fs.unlinkSync(`/app/docs/diagram-${i}.png`); } catch (e) { // Ignore cleanup errors } } } catch (e) { // Ignore cleanup errors } } catch (error) { console.error('❌ Error converting to PDF:', error.message); console.error(error.stack); process.exit(1); } } convertMarkdownToPDF();