#!/usr/bin/env node import { promises as fs } from "fs"; import path from "path"; import { glob } from "glob"; import MarkdownIt from "markdown-it"; import { mdToPdf } from "md-to-pdf"; const PUBLIC_DIR = path.resolve(process.cwd(), "public/cv"); async function ensureDir(dir) { await fs.mkdir(dir, { recursive: true }).catch(() => {}); } const STYLE_CSS = ` /* Default styles */ pre { background: #2d2d2d; border-radius: 4px; margin: 0.5em 0; } code { font-family: 'Fira Code', Consolas, Monaco, monospace; } /* Custom CSS */ /* ====== Markdown PDF Pro Theme ====== */ @page { margin: 25mm 20mm; margin-top: 0mm; } body { font-family: "Segoe UI", "Helvetica Neue", Arial, sans-serif; font-size: 11pt; line-height: 1.6; color: #333; background: white; max-width: 800px; margin: auto; padding: 60px; } /* Headings */ h1, h2, h3, h4, h5, h6 { font-family: "Segoe UI Semibold", "Helvetica Neue", Arial, sans-serif; font-weight: 600; margin-top: 2em; margin-bottom: 0.6em; line-height: 1.3; color: #222; } h1 { font-size: 24pt; border-bottom: 2px solid #000; /* accent color */ padding-bottom: 0.3em; margin-top: 0; } h2 { font-size: 18pt; border-left: 4px solid #000; padding-left: 0.5em; } h3 { font-size: 14pt; color: #444; } h4, h5, h6 { font-size: 12pt; color: #555; } /* Paragraphs */ p { margin: 0.5em 0; text-align: justify; } /* Links */ a { color: #1e7fce; text-decoration: none; } a:hover { text-decoration: underline; } /* Lists */ ul, ol { margin: 0.5em 0 0.5em 2em; } li { margin-bottom: 0.3em; } /* Blockquote */ blockquote { border-left: 4px solid #0078D7; margin: 1em 0; padding: 0.5em 1em; color: #555; background: #f9f9f9; font-style: italic; } /* Code */ code { font-family: "Fira Code", "Consolas", monospace; background: #f4f4f4; padding: 0.2em 0.4em; border-radius: 4px; font-size: 0.95em; } pre { background: #1e1e1e; color: #dcdcdc; padding: 1em; border-radius: 6px; overflow-x: auto; font-size: 0.9em; line-height: 1.4; } /* Tables */ table { border-collapse: collapse; margin: 1em 0; width: 100%; font-size: 0.95em; } th, td { border: 1px solid #ccc; padding: 0.6em 0.8em; text-align: left; } th { background: #f0f0f0; font-weight: 600; } tr:nth-child(even) td { background: #fafafa; } /* Horizontal rule */ hr { border: none; border-top: 2px solid #eee; margin: 2em 0; }`; function wrapHtml(title, body) { return ` ${body} `; } async function convertFile(mdPath) { const base = path.basename(mdPath, ".md"); const dir = path.dirname(mdPath); const rel = path.relative(PUBLIC_DIR, mdPath); // Créer les dossiers de destination s'ils n'existent pas const htmlDir = path.join(PUBLIC_DIR, 'html'); const pdfDir = path.join(PUBLIC_DIR, 'pdf'); await ensureDir(htmlDir); await ensureDir(pdfDir); let mdContent = await fs.readFile(mdPath, "utf8"); // Remplacer les guillemets courbes par des guillemets droits mdContent = mdContent .replace(/’/g, "'"); // Remplace les apostrophes courbes par des droites // HTML const md = new MarkdownIt({ html: true, linkify: true, typographer: true }); const htmlBody = md.render(mdContent); const html = wrapHtml(base, htmlBody); const htmlPath = path.join(htmlDir, `${base}.html`); await fs.writeFile(htmlPath, html, "utf8"); // PDF const pdfPath = path.join(pdfDir, `${base}.pdf`); const pdf = await mdToPdf( { content: mdContent, path: mdPath }, { basedir: dir, launch_options: { args: ["--no-sandbox", "--disable-setuid-sandbox"] }, pdf_options: { format: "A4", printBackground: true }, css: STYLE_CSS, } ); if (pdf && pdf.content) { await fs.writeFile(pdfPath, pdf.content); } console.log( `Converted ${rel} -> ${path.relative( PUBLIC_DIR, htmlPath )}, ${path.relative(PUBLIC_DIR, pdfPath)}` ); } async function main() { await ensureDir(PUBLIC_DIR); const mdFiles = await glob("public/**/*.md", { nodir: true }); if (!mdFiles.length) { console.log("No Markdown files found in public/."); return; } for (const file of mdFiles) { try { await convertFile(path.resolve(process.cwd(), file)); } catch (err) { console.error(`Failed to convert ${file}:`, err.message || err); process.exitCode = 1; } } } main();