md-to-pdf-engine 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,671 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli.ts
27
+ var import_path = require("path");
28
+ var import_fs = require("fs");
29
+
30
+ // src/pdf-engine.ts
31
+ var import_marked = require("marked");
32
+ var import_highlight = __toESM(require("highlight.js"));
33
+ var import_puppeteer = __toESM(require("puppeteer"));
34
+
35
+ // src/pdf-styles.ts
36
+ function generatePDFStyles(template) {
37
+ const { colors, fonts } = template;
38
+ return `
39
+ /* Reset and base */
40
+ *, *::before, *::after {
41
+ box-sizing: border-box;
42
+ margin: 0;
43
+ padding: 0;
44
+ }
45
+
46
+ body {
47
+ font-family: ${fonts.body}, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
48
+ font-size: 12pt;
49
+ line-height: 1.6;
50
+ color: ${colors.text};
51
+ background-color: ${colors.background};
52
+ }
53
+
54
+ /* Headings */
55
+ h1, h2, h3, h4, h5, h6 {
56
+ font-family: ${fonts.heading}, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
57
+ line-height: 1.3;
58
+ margin-top: 1.5em;
59
+ margin-bottom: 0.5em;
60
+ page-break-after: avoid;
61
+ break-after: avoid;
62
+ orphans: 2;
63
+ widows: 2;
64
+ }
65
+
66
+ h1 {
67
+ font-size: 28pt;
68
+ color: ${colors.primary};
69
+ border-bottom: 2px solid ${colors.primary};
70
+ padding-bottom: 0.3em;
71
+ margin-top: 0;
72
+ }
73
+
74
+ h2 {
75
+ font-size: 22pt;
76
+ color: ${colors.primary};
77
+ border-bottom: 1px solid ${colors.secondary};
78
+ padding-bottom: 0.2em;
79
+ }
80
+
81
+ h3 { font-size: 18pt; color: ${colors.secondary}; }
82
+ h4 { font-size: 15pt; color: ${colors.secondary}; }
83
+ h5 { font-size: 13pt; color: ${colors.secondary}; }
84
+ h6 { font-size: 12pt; color: ${colors.secondary}; font-style: italic; }
85
+
86
+ /* Paragraphs and text */
87
+ p {
88
+ margin-bottom: 0.8em;
89
+ orphans: 2;
90
+ widows: 2;
91
+ break-inside: avoid;
92
+ }
93
+
94
+ a { color: ${colors.accent}; text-decoration: underline; }
95
+ strong { font-weight: 700; }
96
+ em { font-style: italic; }
97
+
98
+ /* Lists */
99
+ ul, ol { margin-bottom: 1em; padding-left: 2em; }
100
+ ul ul, ol ol, ul ol, ol ul { margin-bottom: 0; margin-top: 0.3em; }
101
+ li { margin-bottom: 0.3em; page-break-inside: avoid; break-inside: avoid; }
102
+ ul > li { list-style-type: disc; }
103
+ ul > li > ul > li { list-style-type: circle; }
104
+ ul > li > ul > li > ul > li { list-style-type: square; }
105
+ li::marker { color: ${colors.accent}; }
106
+
107
+ /* Tables */
108
+ table { width: 100%; border-collapse: collapse; margin-bottom: 1.5em; page-break-inside: auto; }
109
+ thead { background-color: ${colors.primary}; color: ${colors.background}; }
110
+ th { font-weight: 700; text-align: left; padding: 10px 12px; border: 1px solid ${colors.secondary}; }
111
+ td { padding: 8px 12px; border: 1px solid ${colors.secondary}; }
112
+ tbody tr:nth-child(even) { background-color: rgba(0, 0, 0, 0.03); }
113
+ tr { page-break-inside: avoid; }
114
+
115
+ /* Code blocks */
116
+ .code-block-wrapper { position: relative; margin-bottom: 1em; page-break-inside: avoid; }
117
+ .code-lang-label {
118
+ position: absolute; top: 0; right: 0;
119
+ background-color: ${colors.secondary}20; color: ${colors.secondary};
120
+ font-family: 'SFMono-Regular', 'Consolas', 'Liberation Mono', 'Menlo', monospace;
121
+ font-size: 8pt; padding: 2px 8px; border-radius: 0 6px 0 4px;
122
+ text-transform: uppercase; letter-spacing: 0.03em; z-index: 1;
123
+ }
124
+ pre {
125
+ background-color: ${colors.secondary}1a; border: 1px solid ${colors.secondary}40;
126
+ border-radius: 6px; padding: 1em; margin-bottom: 0;
127
+ overflow-x: visible; white-space: pre-wrap; word-wrap: break-word;
128
+ overflow-wrap: break-word; font-size: 10pt; page-break-inside: avoid;
129
+ }
130
+ pre code { background: none; border: none; padding: 0; font-size: inherit; white-space: pre-wrap; word-wrap: break-word; overflow-wrap: break-word; }
131
+ code {
132
+ font-family: 'SFMono-Regular', 'Consolas', 'Liberation Mono', 'Menlo', monospace;
133
+ background-color: #f0f0f0; border-radius: 3px; padding: 0.15em 0.3em; font-size: 0.9em;
134
+ }
135
+
136
+ /* Blockquotes */
137
+ blockquote { border-left: 4px solid ${colors.primary}; background-color: rgba(0, 0, 0, 0.02); padding: 0.8em 1.2em; margin: 0 0 1em 0; color: ${colors.text}; }
138
+ blockquote p { margin-bottom: 0.4em; }
139
+ blockquote p:last-child { margin-bottom: 0; }
140
+
141
+ /* Horizontal rule */
142
+ hr { border: none; border-top: 2px solid ${colors.secondary}; margin: 2em 0; }
143
+
144
+ /* Images */
145
+ img { max-width: 100%; height: auto; }
146
+
147
+ /* Pagination */
148
+ .page-break { page-break-after: always; }
149
+ .cover-page { page-break-after: always; break-after: page; }
150
+ * { orphans: 2; widows: 2; }
151
+
152
+ @media print {
153
+ h1, h2, h3, h4, h5, h6 { page-break-after: avoid; break-after: avoid; orphans: 2; widows: 2; }
154
+ p { page-break-inside: avoid; break-inside: avoid; }
155
+ li { page-break-inside: avoid; break-inside: avoid; }
156
+ pre, blockquote, figure { page-break-inside: avoid; break-inside: avoid; }
157
+ table { page-break-inside: auto; }
158
+ tr { page-break-inside: avoid; break-inside: avoid; }
159
+ thead { display: table-header-group; }
160
+ img { page-break-inside: avoid; break-inside: avoid; }
161
+ }
162
+ `;
163
+ }
164
+ function generateHeaderHTML(template) {
165
+ if (!template.logoUrl) {
166
+ return "";
167
+ }
168
+ const alignMap = {
169
+ left: "flex-start",
170
+ center: "center",
171
+ right: "flex-end"
172
+ };
173
+ const justify = alignMap[template.logoPosition] ?? "flex-start";
174
+ return `
175
+ <div style="display: flex; justify-content: ${justify}; align-items: center; padding: 0 0 10px 0; border-bottom: 1px solid #e0e0e0; margin-bottom: 10px;">
176
+ <img src="${template.logoUrl}" style="max-height: ${template.logoMaxSize}px; max-width: 200px;" alt="Logo" />
177
+ </div>
178
+ `;
179
+ }
180
+ function generateFooterHTML(pageNumberFormat) {
181
+ let content;
182
+ switch (pageNumberFormat) {
183
+ case "X/Y":
184
+ content = '<span class="pageNumber"></span>/<span class="totalPages"></span>';
185
+ break;
186
+ case "X":
187
+ content = '<span class="pageNumber"></span>';
188
+ break;
189
+ case "Page X of Y":
190
+ content = 'Page <span class="pageNumber"></span> of <span class="totalPages"></span>';
191
+ break;
192
+ case "P\xE1gina X de Y":
193
+ default:
194
+ content = 'P\xE1gina <span class="pageNumber"></span> de <span class="totalPages"></span>';
195
+ break;
196
+ }
197
+ return `
198
+ <div style="font-size: 9pt; color: #666; text-align: center; width: 100%; padding-top: 5px; border-top: 1px solid #e0e0e0;">
199
+ ${content}
200
+ </div>
201
+ `;
202
+ }
203
+
204
+ // src/pdf-engine.ts
205
+ var PAGE_SIZE_MAP = {
206
+ A4: { width: "210mm", height: "297mm" },
207
+ Letter: { width: "8.5in", height: "11in" },
208
+ Legal: { width: "8.5in", height: "14in" }
209
+ };
210
+ function getHighlightCSS() {
211
+ return `
212
+ .hljs { background: #f5f5f5; color: #24292e; }
213
+ .hljs-comment, .hljs-quote { color: #6a737d; font-style: italic; }
214
+ .hljs-keyword, .hljs-selector-tag, .hljs-addition { color: #d73a49; }
215
+ .hljs-string, .hljs-meta .hljs-string, .hljs-regexp { color: #032f62; }
216
+ .hljs-number, .hljs-literal, .hljs-variable, .hljs-template-variable, .hljs-tag .hljs-attr { color: #005cc5; }
217
+ .hljs-type, .hljs-title, .hljs-section, .hljs-name, .hljs-selector-id, .hljs-selector-class { color: #6f42c1; }
218
+ .hljs-attribute, .hljs-symbol, .hljs-bullet, .hljs-built_in, .hljs-link { color: #e36209; }
219
+ .hljs-subst { color: #24292e; }
220
+ .hljs-deletion { color: #b31d28; background-color: #ffeef0; }
221
+ .hljs-emphasis { font-style: italic; }
222
+ .hljs-strong { font-weight: bold; }
223
+ `;
224
+ }
225
+ function createConfiguredMarked() {
226
+ const marked = new import_marked.Marked();
227
+ const renderer = {
228
+ code({ text, lang }) {
229
+ const language = lang && import_highlight.default.getLanguage(lang) ? lang : void 0;
230
+ let highlighted;
231
+ if (language) {
232
+ highlighted = import_highlight.default.highlight(text, { language }).value;
233
+ } else {
234
+ highlighted = import_highlight.default.highlightAuto(text).value;
235
+ }
236
+ const langClass = language ? ` language-${language}` : "";
237
+ const langLabel = language ? `<div class="code-lang-label">${language}</div>` : "";
238
+ return `<div class="code-block-wrapper">${langLabel}<pre><code class="hljs${langClass}">${highlighted}</code></pre></div>
239
+ `;
240
+ }
241
+ };
242
+ marked.setOptions({ gfm: true, breaks: false });
243
+ marked.use({ renderer });
244
+ return marked;
245
+ }
246
+ function wrapCoverPage(htmlContent) {
247
+ const h1Regex = /(<h1[\s>])/i;
248
+ const match = h1Regex.exec(htmlContent);
249
+ if (!match || match.index === void 0) {
250
+ return htmlContent;
251
+ }
252
+ const h1Index = match.index;
253
+ const h1CloseIndex = htmlContent.indexOf("</h1>", h1Index);
254
+ if (h1CloseIndex === -1) {
255
+ return htmlContent;
256
+ }
257
+ const coverEnd = h1CloseIndex + "</h1>".length;
258
+ const coverContent = htmlContent.slice(0, coverEnd);
259
+ const restContent = htmlContent.slice(coverEnd);
260
+ return `<div class="cover-page">${coverContent}</div>${restContent}`;
261
+ }
262
+ function markdownToHTML(markdown, template, options) {
263
+ const marked = createConfiguredMarked();
264
+ let htmlContent = marked.parse(markdown);
265
+ const css = generatePDFStyles(template);
266
+ const highlightCSS = getHighlightCSS();
267
+ const headerHTML = generateHeaderHTML(template);
268
+ if (options?.includeCoverPage) {
269
+ htmlContent = wrapCoverPage(htmlContent);
270
+ }
271
+ return `<!DOCTYPE html>
272
+ <html lang="es">
273
+ <head>
274
+ <meta charset="UTF-8" />
275
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
276
+ <style>${css}</style>
277
+ <style>${highlightCSS}</style>
278
+ </head>
279
+ <body>
280
+ ${headerHTML}
281
+ <main>${htmlContent}</main>
282
+ </body>
283
+ </html>`;
284
+ }
285
+ function buildFooterTextScript(format) {
286
+ switch (format) {
287
+ case "X/Y":
288
+ return `textEl.textContent = adjustedPage + '/' + adjustedTotal;`;
289
+ case "X":
290
+ return `textEl.textContent = String(adjustedPage);`;
291
+ case "Page X of Y":
292
+ return `textEl.textContent = 'Page ' + adjustedPage + ' of ' + adjustedTotal;`;
293
+ case "P\xE1gina X de Y":
294
+ default:
295
+ return `textEl.textContent = 'P\xE1gina ' + adjustedPage + ' de ' + adjustedTotal;`;
296
+ }
297
+ }
298
+ function buildFooterTemplate(options, margins) {
299
+ const baseFooter = generateFooterHTML(options.pageNumberFormat);
300
+ if (options.includeCoverPage) {
301
+ return `<div style="width: 100%; padding: 0 ${margins.left}mm 0 ${margins.right}mm;">
302
+ <div id="footer-content" style="font-size: 9pt; color: #666; text-align: center; width: 100%; padding-top: 5px; border-top: 1px solid #e0e0e0;">
303
+ <span id="footer-text"></span>
304
+ </div>
305
+ <script>
306
+ try {
307
+ var pageNum = parseInt(document.querySelector('.pageNumber')?.textContent || '0', 10);
308
+ var totalPages = parseInt(document.querySelector('.totalPages')?.textContent || '0', 10);
309
+ var footerEl = document.getElementById('footer-content');
310
+ var textEl = document.getElementById('footer-text');
311
+ if (pageNum <= 1) {
312
+ footerEl.style.display = 'none';
313
+ } else {
314
+ var adjustedPage = pageNum - 1;
315
+ var adjustedTotal = totalPages - 1;
316
+ ${buildFooterTextScript(options.pageNumberFormat)}
317
+ }
318
+ } catch(e) {}
319
+ </script>
320
+ <span class="pageNumber" style="display:none;"></span>
321
+ <span class="totalPages" style="display:none;"></span>
322
+ </div>`;
323
+ }
324
+ return `<div style="width: 100%; padding: 0 ${margins.left}mm 0 ${margins.right}mm;">
325
+ ${baseFooter}
326
+ </div>`;
327
+ }
328
+ async function generatePDFFromMarkdown(markdown, template, options) {
329
+ let browser = null;
330
+ try {
331
+ const fullHTML = markdownToHTML(markdown, template, {
332
+ includeCoverPage: options.includeCoverPage
333
+ });
334
+ const margins = options.margins ?? template.margins;
335
+ browser = await import_puppeteer.default.launch({
336
+ headless: true,
337
+ args: ["--no-sandbox", "--disable-setuid-sandbox"]
338
+ });
339
+ const page = await browser.newPage();
340
+ await page.setContent(fullHTML, { waitUntil: "networkidle0" });
341
+ const pageSize = PAGE_SIZE_MAP[options.pageSize] ?? PAGE_SIZE_MAP["A4"];
342
+ const isLandscape = options.orientation === "landscape";
343
+ const logoJustify = template.logoPosition === "right" ? "flex-end" : template.logoPosition === "center" ? "center" : "flex-start";
344
+ const logoMaxHeight = Math.min(template.logoMaxSize, 50);
345
+ let headerTemplate;
346
+ if (!template.logoUrl) {
347
+ headerTemplate = '<div style="font-size: 1px;"></div>';
348
+ } else if (template.logoDisplay === "first-page") {
349
+ headerTemplate = `<div style="width: 100%; padding: 0 ${margins.left}mm 0 ${margins.right}mm;">
350
+ <div id="header-logo" style="display: flex; justify-content: ${logoJustify}; padding: 5px 0;">
351
+ <img src="${template.logoUrl}" style="max-height: ${logoMaxHeight}px;" />
352
+ </div>
353
+ <span class="pageNumber" style="display:none;"></span>
354
+ <script>
355
+ try {
356
+ var pageNum = parseInt(document.querySelector('.pageNumber').textContent || '0', 10);
357
+ if (pageNum > 1) {
358
+ document.getElementById('header-logo').style.display = 'none';
359
+ }
360
+ } catch(e) {}
361
+ </script>
362
+ </div>`;
363
+ } else {
364
+ headerTemplate = `<div style="width: 100%; padding: 0 ${margins.left}mm 0 ${margins.right}mm;">
365
+ <div style="display: flex; justify-content: ${logoJustify}; padding: 5px 0;">
366
+ <img src="${template.logoUrl}" style="max-height: ${logoMaxHeight}px;" />
367
+ </div>
368
+ </div>`;
369
+ }
370
+ const footerTemplate = options.includePageNumbers ? buildFooterTemplate(options, margins) : '<div style="font-size: 1px;"></div>';
371
+ const pdfBuffer = await page.pdf({
372
+ width: pageSize.width,
373
+ height: pageSize.height,
374
+ landscape: isLandscape,
375
+ printBackground: true,
376
+ displayHeaderFooter: options.includePageNumbers || !!template.logoUrl,
377
+ headerTemplate,
378
+ footerTemplate,
379
+ margin: {
380
+ top: `${margins.top}mm`,
381
+ bottom: `${margins.bottom}mm`,
382
+ left: `${margins.left}mm`,
383
+ right: `${margins.right}mm`
384
+ }
385
+ });
386
+ await browser.close();
387
+ browser = null;
388
+ return Buffer.from(pdfBuffer);
389
+ } finally {
390
+ if (browser) {
391
+ try {
392
+ await browser.close();
393
+ } catch {
394
+ }
395
+ }
396
+ }
397
+ }
398
+
399
+ // src/templates/corporativa-clasica.ts
400
+ var corporativaClasica = {
401
+ id: "corporativa-clasica",
402
+ name: "Corporativa Clasica",
403
+ logoUrl: "",
404
+ logoPosition: "center",
405
+ logoDisplay: "all-pages",
406
+ logoMaxSize: 100,
407
+ colors: {
408
+ primary: "#1B3A5C",
409
+ secondary: "#C4A035",
410
+ accent: "#2E5C8A",
411
+ background: "#FFFFFF",
412
+ text: "#333333"
413
+ },
414
+ fonts: {
415
+ heading: "Georgia, serif",
416
+ body: "Garamond, serif"
417
+ },
418
+ margins: {
419
+ top: 25,
420
+ bottom: 25,
421
+ left: 20,
422
+ right: 20
423
+ }
424
+ };
425
+
426
+ // src/templates/moderna-minimalista.ts
427
+ var modernaMinimalista = {
428
+ id: "moderna-minimalista",
429
+ name: "Moderna Minimalista",
430
+ logoUrl: "",
431
+ logoPosition: "left",
432
+ logoDisplay: "all-pages",
433
+ logoMaxSize: 80,
434
+ colors: {
435
+ primary: "#2D2D2D",
436
+ secondary: "#6B7280",
437
+ accent: "#06B6D4",
438
+ background: "#FAFAFA",
439
+ text: "#1A1A1A"
440
+ },
441
+ fonts: {
442
+ heading: "Inter, sans-serif",
443
+ body: "system-ui, sans-serif"
444
+ },
445
+ margins: {
446
+ top: 30,
447
+ bottom: 30,
448
+ left: 25,
449
+ right: 25
450
+ }
451
+ };
452
+
453
+ // src/templates/tecnica-documentacion.ts
454
+ var tecnicaDocumentacion = {
455
+ id: "tecnica-documentacion",
456
+ name: "Tecnica/Documentacion",
457
+ logoUrl: "",
458
+ logoPosition: "left",
459
+ logoDisplay: "all-pages",
460
+ logoMaxSize: 80,
461
+ colors: {
462
+ primary: "#374151",
463
+ secondary: "#6B7280",
464
+ accent: "#10B981",
465
+ background: "#FFFFFF",
466
+ text: "#1F2937"
467
+ },
468
+ fonts: {
469
+ heading: "Fira Code, monospace",
470
+ body: "system-ui, sans-serif"
471
+ },
472
+ margins: {
473
+ top: 20,
474
+ bottom: 20,
475
+ left: 20,
476
+ right: 20
477
+ }
478
+ };
479
+
480
+ // src/templates/moderna-azul.ts
481
+ var modernaAzul = {
482
+ id: "moderna-azul",
483
+ name: "Moderna Azul",
484
+ logoUrl: "",
485
+ logoPosition: "left",
486
+ logoDisplay: "first-page",
487
+ logoMaxSize: 80,
488
+ colors: {
489
+ primary: "#0080ff",
490
+ secondary: "#0f2033",
491
+ accent: "#00d4ff",
492
+ background: "#ffffff",
493
+ text: "#1a1a2e"
494
+ },
495
+ fonts: {
496
+ heading: "Inter, sans-serif",
497
+ body: "Inter, sans-serif"
498
+ },
499
+ margins: {
500
+ top: 25,
501
+ bottom: 25,
502
+ left: 20,
503
+ right: 20
504
+ }
505
+ };
506
+
507
+ // src/templates/profesional-moderna.ts
508
+ var profesionalModerna = {
509
+ id: "profesional-moderna",
510
+ name: "Profesional Moderna",
511
+ logoUrl: "",
512
+ logoPosition: "left",
513
+ logoDisplay: "first-page",
514
+ logoMaxSize: 80,
515
+ colors: {
516
+ primary: "#1E293B",
517
+ secondary: "#475569",
518
+ accent: "#7C3AED",
519
+ background: "#FFFFFF",
520
+ text: "#334155"
521
+ },
522
+ fonts: {
523
+ heading: "Helvetica Neue, Helvetica, Arial, sans-serif",
524
+ body: "Helvetica Neue, Helvetica, Arial, sans-serif"
525
+ },
526
+ margins: {
527
+ top: 25,
528
+ bottom: 25,
529
+ left: 25,
530
+ right: 25
531
+ }
532
+ };
533
+
534
+ // src/templates/index.ts
535
+ var DEFAULT_TEMPLATE = corporativaClasica;
536
+ var templatesMap = {
537
+ [corporativaClasica.id]: corporativaClasica,
538
+ [modernaMinimalista.id]: modernaMinimalista,
539
+ [tecnicaDocumentacion.id]: tecnicaDocumentacion,
540
+ [modernaAzul.id]: modernaAzul,
541
+ [profesionalModerna.id]: profesionalModerna
542
+ };
543
+ function getTemplateById(id) {
544
+ return templatesMap[id];
545
+ }
546
+ function getAllTemplates() {
547
+ return [
548
+ corporativaClasica,
549
+ modernaMinimalista,
550
+ tecnicaDocumentacion,
551
+ modernaAzul,
552
+ profesionalModerna
553
+ ];
554
+ }
555
+
556
+ // src/cli.ts
557
+ function printUsage() {
558
+ console.log(`
559
+ md-to-pdf \u2014 Professional PDF generation from Markdown
560
+
561
+ Usage:
562
+ md-to-pdf <file.md> [options]
563
+
564
+ Options:
565
+ --template <id> Template to use (default: corporativa-clasica)
566
+ --output <path> Output PDF path
567
+ --size <size> A4 | Letter | Legal (default: A4)
568
+ --orientation <o> portrait | landscape (default: portrait)
569
+ --cover Include cover page
570
+ --no-page-numbers Disable page numbers
571
+ --list-templates List available templates
572
+
573
+ Templates:
574
+ ${getAllTemplates().map((t) => ` - ${t.id} (${t.name})`).join("\n")}
575
+ `);
576
+ }
577
+ function parseArgs(args) {
578
+ if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
579
+ printUsage();
580
+ process.exit(0);
581
+ }
582
+ if (args.includes("--list-templates")) {
583
+ console.log("Available templates:");
584
+ for (const t of getAllTemplates()) {
585
+ console.log(` ${t.id} - ${t.name}`);
586
+ }
587
+ process.exit(0);
588
+ }
589
+ const inputFile = args[0];
590
+ let templateId = "corporativa-clasica";
591
+ let outputFile = "";
592
+ let pageSize = "A4";
593
+ let orientation = "portrait";
594
+ let includeCover = false;
595
+ let includePageNumbers = true;
596
+ for (let i = 1; i < args.length; i++) {
597
+ switch (args[i]) {
598
+ case "--template":
599
+ templateId = args[++i];
600
+ break;
601
+ case "--output":
602
+ outputFile = args[++i];
603
+ break;
604
+ case "--size":
605
+ pageSize = args[++i];
606
+ break;
607
+ case "--orientation":
608
+ orientation = args[++i];
609
+ break;
610
+ case "--cover":
611
+ includeCover = true;
612
+ break;
613
+ case "--no-page-numbers":
614
+ includePageNumbers = false;
615
+ break;
616
+ }
617
+ }
618
+ if (!outputFile) {
619
+ const dir = (0, import_path.dirname)(inputFile);
620
+ const name = (0, import_path.basename)(inputFile, (0, import_path.extname)(inputFile));
621
+ outputFile = (0, import_path.join)(dir, `${name}.pdf`);
622
+ }
623
+ return {
624
+ inputFile: (0, import_path.resolve)(inputFile),
625
+ templateId,
626
+ outputFile: (0, import_path.resolve)(outputFile),
627
+ pageSize,
628
+ orientation,
629
+ includeCover,
630
+ includePageNumbers
631
+ };
632
+ }
633
+ async function main() {
634
+ const args = process.argv.slice(2);
635
+ const config = parseArgs(args);
636
+ if (!(0, import_fs.existsSync)(config.inputFile)) {
637
+ console.error(`Error: file not found: ${config.inputFile}`);
638
+ process.exit(1);
639
+ }
640
+ const template = getTemplateById(config.templateId) ?? DEFAULT_TEMPLATE;
641
+ if (config.templateId && !getTemplateById(config.templateId)) {
642
+ console.warn(
643
+ `Warning: template "${config.templateId}" not found, using "${template.id}"`
644
+ );
645
+ }
646
+ const markdown = (0, import_fs.readFileSync)(config.inputFile, "utf-8");
647
+ const pdfOptions = {
648
+ pageSize: config.pageSize,
649
+ orientation: config.orientation,
650
+ margins: template.margins,
651
+ includeCoverPage: config.includeCover,
652
+ includePageNumbers: config.includePageNumbers,
653
+ pageNumberFormat: "P\xE1gina X de Y"
654
+ };
655
+ console.log(`Generating PDF...`);
656
+ console.log(` Input: ${config.inputFile}`);
657
+ console.log(` Template: ${template.name} (${template.id})`);
658
+ console.log(` Size: ${config.pageSize} ${config.orientation}`);
659
+ console.log(` Cover page: ${config.includeCover ? "Yes" : "No"}`);
660
+ console.log(` Page numbers: ${config.includePageNumbers ? "Yes" : "No"}`);
661
+ const pdfBuffer = await generatePDFFromMarkdown(markdown, template, pdfOptions);
662
+ (0, import_fs.writeFileSync)(config.outputFile, pdfBuffer);
663
+ const sizeMB = (pdfBuffer.length / 1024 / 1024).toFixed(2);
664
+ console.log(`
665
+ PDF generated: ${config.outputFile} (${sizeMB} MB)`);
666
+ }
667
+ main().catch((err) => {
668
+ console.error("Error generating PDF:", err);
669
+ process.exit(1);
670
+ });
671
+ //# sourceMappingURL=cli.js.map