@liustack/pagepress 0.6.1 → 0.8.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Leon
3
+ Copyright (c) 2026 Leon Liu (liustack)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,123 +1,59 @@
1
1
  # PagePress
2
2
 
3
- A web content rendering toolkit (CLI) designed for **AI Agents**, converting local HTML and Markdown files into high-quality **PDF** or **PNG** images.
3
+ A CLI toolkit for AI agents to render local Markdown and HTML files into high-quality PDF documents.
4
4
 
5
5
  中文说明请见:[README.zh-CN.md](README.zh-CN.md)
6
6
 
7
- ## Features
7
+ ## Features
8
8
 
9
- - **Unified CLI**: One tool (`pagepress`) for both PDF and image generation
10
- - **AI Agent friendly**: Includes a detailed SKILL.md guide with scenario routing
11
- - **Local files only**: Accepts local HTML and Markdown files (remote URLs are not supported for security)
12
- - **Rich templates**:
13
- - **PDF**: Apple style, GitHub style, magazine layout (Magazine)
14
- - **Image**: 8 presets — OG cards, infographics, posters, banners, Twitter/X cards, YouTube thumbnails, Xiaohongshu covers, WeChat covers
15
- - **Brand-aware visuals**: OG / poster / banner outputs are generated based on workspace visual & brand assets (commonly in `assets/`, `public/`, `src/assets/`)
16
- - **Consistency guarantees**: Deterministic rendering, font waiting, network idle detection, syntax highlighting
9
+ - PDF-only command surface (`pagepress`)
10
+ - Local file input only (`.md` / `.html`)
11
+ - Built-in Markdown templates: `default`, `github`, `magazine`
12
+ - Built-in Mermaid rendering for Markdown code blocks
13
+ - Deterministic output via Playwright + Chromium print engine
17
14
 
18
- ## 📦 Installation
15
+ ## Installation
19
16
 
20
17
  ```bash
21
- # Global install
22
18
  npm install -g @liustack/pagepress
23
-
24
- # Install browsers (Playwright)
25
19
  npx playwright install chromium
26
20
  ```
27
21
 
28
- Or use `npx` directly:
22
+ Or run with `npx`:
29
23
 
30
24
  ```bash
31
- npx @liustack/pagepress <command> [options]
25
+ npx @liustack/pagepress [options]
32
26
  ```
33
27
 
34
- ## 🚀 Usage
35
-
36
- ### 1. Generate PDF
37
-
38
- Convert HTML/Markdown into PDF documents. Supports syntax highlighting.
28
+ ## Usage
39
29
 
40
30
  ```bash
41
- # Markdown to PDF (Apple style template)
42
- pagepress pdf -i document.md -o output.pdf --template default
43
-
44
- # Local HTML file to PDF
45
- pagepress pdf -i page.html -o output.pdf
31
+ # Markdown to PDF
32
+ pagepress -i document.md -o output.pdf --template default
46
33
 
34
+ # Local HTML to PDF
35
+ pagepress -i page.html -o output.pdf
47
36
  ```
48
37
 
49
- **Supported templates**:
50
- - `default` - Apple style, clean and elegant
51
- - `github` - GitHub style
52
- - `magazine` - VOGUE/WIRED magazine layout
53
-
54
- ### 2. Generate Images
38
+ ## Templates
55
39
 
56
- Render local HTML into PNG images, suitable for social share cards, posters, and infographics.
57
-
58
- For OG cards, posters, and banners, the visuals should be generated based on workspace brand assets (logo, colors, fonts, imagery), commonly located in `assets/`, `public/`, or `src/assets/`.
59
-
60
- **Recommended brand assets layout**:
61
-
62
- ```text
63
- assets/
64
- brand/
65
- logo.svg
66
- palette.json
67
- fonts/
68
- YourBrand-Regular.woff2
69
- imagery/
70
- hero.jpg
71
- ```
72
-
73
- ```bash
74
- # Generate an OG card (1200x630)
75
- pagepress shot -i card.html -o og.png --preset og
76
-
77
- # Generate a long infographic
78
- pagepress shot -i stats.html -o infographic.png --preset infographic
79
-
80
- ```
81
-
82
- **Supported presets**:
83
- - `og` (1200x630) - Social card
84
- - `infographic` (1080x1350) - Infographic
85
- - `poster` (1200x1500) - Poster
86
- - `banner` (1600x900) - Banner
87
- - `twitter` (1200x675) - Twitter/X card
88
- - `youtube` (1280x720) - YouTube thumbnail
89
- - `xiaohongshu` (1080x1440) - Xiaohongshu / RedNote cover
90
- - `wechat` (900x383) - WeChat Official Account cover
91
-
92
- ## 🖼️ Examples
93
-
94
- ### Image Presets
95
-
96
- | OG Card (1200×630) | Infographic (1080×1350) |
97
- |:---:|:---:|
98
- | ![OG Card](examples/shot-og.png) | ![Infographic](examples/shot-infographic.png) |
99
-
100
- | Poster (1200×1500) | Banner (1600×900) |
101
- |:---:|:---:|
102
- | ![Poster](examples/shot-poster.png) | ![Banner](examples/shot-banner.png) |
103
-
104
- | Twitter/X Card (1200×675) | YouTube Thumbnail (1280×720) |
105
- |:---:|:---:|
106
- | ![Twitter Card](examples/shot-twitter.png) | ![YouTube Thumbnail](examples/shot-youtube.png) |
107
-
108
- | Xiaohongshu Cover (1080×1440) | WeChat Cover (900×383) |
109
- |:---:|:---:|
110
- | ![Xiaohongshu Cover](examples/shot-xiaohongshu.png) | ![WeChat Cover](examples/shot-wechat.png) |
40
+ - `default` - Clean minimalist style
41
+ - `github` - GitHub style
42
+ - `magazine` - magazine layout
111
43
 
112
- ## 🤖 AI Agent Integration
44
+ ## Options
113
45
 
114
- This project includes two skill guides for AI Agents (such as Claude, ChatGPT):
46
+ - `-i, --input <path>` input Markdown or HTML file path
47
+ - `-o, --output <path>` output PDF file path
48
+ - `-t, --template <name>` template for Markdown input (default: `default`)
49
+ - `--wait-until <state>` `load | domcontentloaded | networkidle`
50
+ - `--timeout <ms>` timeout in milliseconds
51
+ - `--safe` disable external network requests and JavaScript execution
115
52
 
116
- - [web-to-pdf/SKILL.md](skills/web-to-pdf/SKILL.md) PDF generation (Markdown beautification, templates, HTML printing)
117
- - [web-to-png/SKILL.md](skills/web-to-png/SKILL.md) — PNG generation (OG cards, infographics, posters, banners, social cards)
53
+ ## AI Agent Skill
118
54
 
119
- Agents can automatically choose the appropriate commands and parameters based on natural language instructions (for example, "generate a poster" or "convert this document to PDF").
55
+ - [pagepress/SKILL.md](skills/pagepress/SKILL.md)
120
56
 
121
- ## 📄 License
57
+ ## License
122
58
 
123
59
  MIT
package/README.zh-CN.md CHANGED
@@ -1,121 +1,57 @@
1
1
  # PagePress
2
2
 
3
- 一套专为 **AI Agent** 设计的 Web 内容渲染工具集(CLI),可将本地 HTML、Markdown 文件转换为高质量的 **PDF** **PNG** 图像。
3
+ 面向 AI Agent PDF 渲染 CLI,可将本地 Markdown HTML 文件输出为高质量 PDF。
4
4
 
5
- ## 特性
5
+ ## 特性
6
6
 
7
- - **统一 CLI**:一个工具 (`pagepress`) 搞定 PDF 和 Image 生成
8
- - **AI Agent 友好**:包含详细的 SKILL.md 指南,支持场景路由
9
- - **仅本地文件**:支持本地 HTML、Markdown 文件(出于安全考虑不支持远程 URL)
10
- - **丰富模板**:
11
- - **PDF**: Apple 风格、GitHub 风格、杂志排版 (Magazine)
12
- - **Image**: 8 种预设 — OG 卡片、信息图、海报、横幅、Twitter/X 卡片、YouTube 缩略图、小红书封面、微信公众号封面
13
- - **品牌一致性**:OG/海报/横幅会基于工作区的视觉与品牌资源生成(常见于 `assets/`、`public/`、`src/assets/`)
14
- - **一致性保障**:确定性渲染、字体等待、网络空闲检测、代码高亮
7
+ - 专注 PDF 输出(仅 `pagepress`)
8
+ - 仅支持本地文件输入(`.md` / `.html`)
9
+ - 内置 Markdown 模板:`default`、`github`、`magazine`
10
+ - 支持 Mermaid 代码块渲染
11
+ - 基于 Playwright + Chromium 的稳定打印引擎
15
12
 
16
- ## 📦 安装
13
+ ## 安装
17
14
 
18
15
  ```bash
19
- # 全局安装
20
16
  npm install -g @liustack/pagepress
21
-
22
- # 安装浏览器(Playwright)
23
17
  npx playwright install chromium
24
18
  ```
25
19
 
26
- 或者直接使用 `npx`:
20
+ 或使用 `npx`:
27
21
 
28
22
  ```bash
29
- npx @liustack/pagepress <command> [options]
23
+ npx @liustack/pagepress [options]
30
24
  ```
31
25
 
32
- ## 🚀 使用指南
33
-
34
- ### 1. 生成 PDF
35
-
36
- 将 HTML/Markdown 转换为 PDF 文档。支持代码高亮。
26
+ ## 用法
37
27
 
38
28
  ```bash
39
- # Markdown 转 PDF(使用 Apple 风格模板)
40
- pagepress pdf -i document.md -o output.pdf --template default
41
-
42
- # 本地 HTML 文件转 PDF
43
- pagepress pdf -i page.html -o output.pdf
29
+ # Markdown 转 PDF
30
+ pagepress -i document.md -o output.pdf --template default
44
31
 
32
+ # 本地 HTML 转 PDF
33
+ pagepress -i page.html -o output.pdf
45
34
  ```
46
35
 
47
- **支持的模板**:
48
- - `default` - Apple 风格,简洁优雅
49
- - `github` - GitHub 风格
50
- - `magazine` - VOGUE/WIRED 杂志排版
51
-
52
- ### 2. 生成图像
36
+ ## 模板
53
37
 
54
- 将本地 HTML 渲染为 PNG 图像,适合社交分享卡片、海报、信息图。
55
-
56
- 对于 OG 卡片、海报、横幅,视觉应基于工作区的品牌资源(logo、颜色、字体、素材)生成,常见目录包括 `assets/`、`public/`、`src/assets/`。
57
-
58
- **品牌资源目录建议**:
59
-
60
- ```text
61
- assets/
62
- brand/
63
- logo.svg
64
- palette.json
65
- fonts/
66
- YourBrand-Regular.woff2
67
- imagery/
68
- hero.jpg
69
- ```
70
-
71
- ```bash
72
- # 生成 OG 卡片(1200×630)
73
- pagepress shot -i card.html -o og.png --preset og
74
-
75
- # 生成信息长图
76
- pagepress shot -i stats.html -o infographic.png --preset infographic
77
-
78
- ```
79
-
80
- **支持的预设**:
81
- - `og` (1200×630) - 社交卡片
82
- - `infographic` (1080×1350) - 信息图
83
- - `poster` (1200×1500) - 海报
84
- - `banner` (1600×900) - 横幅
85
- - `twitter` (1200×675) - Twitter/X 卡片
86
- - `youtube` (1280×720) - YouTube 缩略图
87
- - `xiaohongshu` (1080×1440) - 小红书封面
88
- - `wechat` (900×383) - 微信公众号封面
89
-
90
- ## 🖼️ 示例
91
-
92
- ### 图像预设
93
-
94
- | OG 卡片 (1200×630) | 信息图 (1080×1350) |
95
- |:---:|:---:|
96
- | ![OG 卡片](examples/shot-og.png) | ![信息图](examples/shot-infographic.png) |
97
-
98
- | 海报 (1200×1500) | 横幅 (1600×900) |
99
- |:---:|:---:|
100
- | ![海报](examples/shot-poster.png) | ![横幅](examples/shot-banner.png) |
101
-
102
- | Twitter/X 卡片 (1200×675) | YouTube 缩略图 (1280×720) |
103
- |:---:|:---:|
104
- | ![Twitter 卡片](examples/shot-twitter.png) | ![YouTube 缩略图](examples/shot-youtube.png) |
105
-
106
- | 小红书封面 (1080×1440) | 微信公众号封面 (900×383) |
107
- |:---:|:---:|
108
- | ![小红书封面](examples/shot-xiaohongshu.png) | ![微信公众号封面](examples/shot-wechat.png) |
38
+ - `default` - 简洁风格
39
+ - `github` - GitHub 风格
40
+ - `magazine` - 杂志排版
109
41
 
110
- ## 🤖 AI Agent 集成
42
+ ## 参数
111
43
 
112
- 本项目包含两份 AI Agent 技能指南(适用于 Claude、ChatGPT 等):
44
+ - `-i, --input <path>` 输入 Markdown 或 HTML 路径
45
+ - `-o, --output <path>` 输出 PDF 路径
46
+ - `-t, --template <name>` Markdown 模板(默认 `default`)
47
+ - `--wait-until <state>` `load | domcontentloaded | networkidle`
48
+ - `--timeout <ms>` 超时时间(毫秒)
49
+ - `--safe` 禁用外部网络请求和 JavaScript 执行
113
50
 
114
- - [web-to-pdf/SKILL.md](skills/web-to-pdf/SKILL.md) PDF 生成(Markdown 美化、模板、HTML 打印)
115
- - [web-to-png/SKILL.md](skills/web-to-png/SKILL.md) — PNG 生成(OG 卡片、信息图、海报、横幅、社交卡片)
51
+ ## AI Agent Skill
116
52
 
117
- Agent 可以根据用户的自然语言指令(如"生成一张海报"、"把这个文档转成 PDF")自动选择合适的命令和参数。
53
+ - [pagepress/SKILL.md](skills/pagepress/SKILL.md)
118
54
 
119
- ## 📄 License
55
+ ## License
120
56
 
121
57
  MIT
package/dist/main.js CHANGED
@@ -11,7 +11,7 @@ import hljs from "highlight.js";
11
11
  const pdfTemplates = {
12
12
  default: {
13
13
  name: "default",
14
- description: "Apple-inspired clean design",
14
+ description: "Clean minimalist design",
15
15
  file: "default.html"
16
16
  },
17
17
  github: {
@@ -73,15 +73,15 @@ function getFontCSS(template) {
73
73
  const __filename$1 = fileURLToPath(import.meta.url);
74
74
  const __dirname$1 = path.dirname(__filename$1);
75
75
  const require$1 = createRequire(import.meta.url);
76
- const WAIT_UNTIL_STATES$1 = /* @__PURE__ */ new Set(["load", "domcontentloaded", "networkidle"]);
77
- function normalizeWaitUntil$1(value) {
76
+ const WAIT_UNTIL_STATES = /* @__PURE__ */ new Set(["load", "domcontentloaded", "networkidle"]);
77
+ function normalizeWaitUntil(value) {
78
78
  if (!value) return "networkidle";
79
- if (WAIT_UNTIL_STATES$1.has(value)) {
79
+ if (WAIT_UNTIL_STATES.has(value)) {
80
80
  return value;
81
81
  }
82
82
  throw new Error(`Invalid waitUntil: ${value}. Allowed: load, domcontentloaded, networkidle.`);
83
83
  }
84
- function normalizeTimeout$1(value) {
84
+ function normalizeTimeout(value) {
85
85
  if (value === void 0) return 3e4;
86
86
  if (!Number.isFinite(value) || value < 0) {
87
87
  throw new Error("Invalid timeout. Use a non-negative number of milliseconds.");
@@ -91,9 +91,9 @@ function normalizeTimeout$1(value) {
91
91
  function findTemplatesDir() {
92
92
  const candidates = [
93
93
  // When running from project root (dev/installed)
94
- path.resolve(process.cwd(), "src/pdf/templates"),
94
+ path.resolve(process.cwd(), "src/templates"),
95
95
  // Relative to dist/main.js
96
- path.resolve(__dirname$1, "..", "src", "pdf", "templates"),
96
+ path.resolve(__dirname$1, "..", "src", "templates"),
97
97
  // Relative to source file location
98
98
  path.resolve(__dirname$1, "templates")
99
99
  ];
@@ -102,11 +102,11 @@ function findTemplatesDir() {
102
102
  }
103
103
  throw new Error(`Templates directory not found. Searched: ${candidates.join(", ")}`);
104
104
  }
105
- async function render$1(options) {
105
+ async function render(options) {
106
106
  const template = getTemplate(options.template ?? "default");
107
107
  const safeMode = options.safe ?? false;
108
- const waitUntil = normalizeWaitUntil$1(options.waitUntil);
109
- const timeout = normalizeTimeout$1(options.timeout);
108
+ const waitUntil = normalizeWaitUntil(options.waitUntil);
109
+ const timeout = normalizeTimeout(options.timeout);
110
110
  const templatesDir = findTemplatesDir();
111
111
  const templatePath = path.join(templatesDir, template.file);
112
112
  if (!fs.existsSync(templatePath)) {
@@ -316,191 +316,8 @@ async function render$1(options) {
316
316
  }
317
317
  }
318
318
  }
319
- const command$1 = new Command("pdf").description("Render Markdown or HTML to PDF").requiredOption("-i, --input <path>", "Input Markdown or HTML file path").requiredOption("-o, --output <path>", "Output PDF file path").option("-t, --template <name>", "PDF template (default, github, magazine)", "default").option("--wait-until <state>", "Navigation waitUntil (load, domcontentloaded, networkidle)", "networkidle").option("--timeout <ms>", "Navigation timeout in milliseconds", "30000").option("--safe", "Disable external network requests and JavaScript execution").action(async (options) => {
320
- try {
321
- const timeout = Number.parseInt(options.timeout, 10);
322
- if (!Number.isFinite(timeout) || timeout < 0) {
323
- throw new Error("Invalid --timeout value. Use a non-negative integer in milliseconds.");
324
- }
325
- const result = await render$1({
326
- input: options.input,
327
- output: options.output,
328
- template: options.template,
329
- waitUntil: options.waitUntil,
330
- timeout,
331
- safe: options.safe
332
- });
333
- console.log(JSON.stringify(result, null, 2));
334
- } catch (error) {
335
- console.error("Error:", error instanceof Error ? error.message : error);
336
- process.exit(1);
337
- }
338
- });
339
- const imagePresets = {
340
- og: {
341
- name: "og",
342
- width: 1200,
343
- height: 630,
344
- description: "Open Graph / Social Card (1200×630)"
345
- },
346
- infographic: {
347
- name: "infographic",
348
- width: 1080,
349
- height: 1350,
350
- description: "Infographic (1080×1350)"
351
- },
352
- poster: {
353
- name: "poster",
354
- width: 1200,
355
- height: 1500,
356
- description: "Poster (1200×1500)"
357
- },
358
- banner: {
359
- name: "banner",
360
- width: 1600,
361
- height: 900,
362
- description: "Banner (1600×900)"
363
- },
364
- twitter: {
365
- name: "twitter",
366
- width: 1200,
367
- height: 675,
368
- description: "Twitter Card (1200×675)"
369
- },
370
- youtube: {
371
- name: "youtube",
372
- width: 1280,
373
- height: 720,
374
- description: "YouTube Thumbnail (1280×720)"
375
- },
376
- xiaohongshu: {
377
- name: "xiaohongshu",
378
- width: 1080,
379
- height: 1440,
380
- description: "Xiaohongshu / RedNote Cover (1080×1440)"
381
- },
382
- wechat: {
383
- name: "wechat",
384
- width: 900,
385
- height: 383,
386
- description: "WeChat Official Account Cover (900×383)"
387
- }
388
- };
389
- function getPreset(name) {
390
- const preset = imagePresets[name];
391
- if (!preset) {
392
- throw new Error(`Unknown image preset: ${name}. Available: ${Object.keys(imagePresets).join(", ")}`);
393
- }
394
- return preset;
395
- }
396
- const WAIT_UNTIL_STATES = /* @__PURE__ */ new Set(["load", "domcontentloaded", "networkidle"]);
397
- const MIN_DIMENSION = 100;
398
- const MAX_DIMENSION = 5e3;
399
- const MIN_SCALE = 1;
400
- const MAX_SCALE = 4;
401
- function normalizeWaitUntil(value) {
402
- if (!value) return "networkidle";
403
- if (WAIT_UNTIL_STATES.has(value)) {
404
- return value;
405
- }
406
- throw new Error(`Invalid waitUntil: ${value}. Allowed: load, domcontentloaded, networkidle.`);
407
- }
408
- function normalizeTimeout(value) {
409
- if (value === void 0) return 3e4;
410
- if (!Number.isFinite(value) || value < 0) {
411
- throw new Error("Invalid timeout. Use a non-negative number of milliseconds.");
412
- }
413
- return value;
414
- }
415
- function validateRange(name, value, min, max) {
416
- if (!Number.isFinite(value)) {
417
- throw new Error(`${name} must be a finite number.`);
418
- }
419
- if (value < min || value > max) {
420
- throw new Error(`${name} must be between ${min} and ${max}. Received: ${value}.`);
421
- }
422
- }
423
- async function render(options) {
424
- const preset = options.preset ? getPreset(options.preset) : getPreset("og");
425
- const width = options.width ?? preset.width;
426
- const height = options.height ?? preset.height;
427
- const scale = options.scale ?? 2;
428
- const safeMode = options.safe ?? false;
429
- const waitUntil = normalizeWaitUntil(options.waitUntil);
430
- const timeout = normalizeTimeout(options.timeout);
431
- validateRange("Width", width, MIN_DIMENSION, MAX_DIMENSION);
432
- validateRange("Height", height, MIN_DIMENSION, MAX_DIMENSION);
433
- validateRange("Scale", scale, MIN_SCALE, MAX_SCALE);
434
- let browser = null;
435
- try {
436
- browser = await chromium.launch({ headless: true });
437
- const context = await browser.newContext({
438
- viewport: { width, height },
439
- deviceScaleFactor: scale,
440
- javaScriptEnabled: !safeMode
441
- });
442
- if (safeMode) {
443
- await context.route("**/*", (route) => {
444
- const url = route.request().url();
445
- if (url.startsWith("http://") || url.startsWith("https://")) {
446
- return route.abort();
447
- }
448
- return route.continue();
449
- });
450
- }
451
- const page = await context.newPage();
452
- const inputPath = path.resolve(options.input);
453
- if (options.input.startsWith("http://") || options.input.startsWith("https://")) {
454
- throw new Error("Remote URL inputs are not supported. Please provide a local HTML file path.");
455
- } else if (fs.existsSync(inputPath)) {
456
- const content = fs.readFileSync(inputPath, "utf-8");
457
- await page.setContent(content, { waitUntil, timeout });
458
- } else {
459
- throw new Error(`Input not found: ${options.input}`);
460
- }
461
- await page.addStyleTag({
462
- content: `
463
- html, body {
464
- width: ${width}px;
465
- height: ${height}px;
466
- margin: 0;
467
- padding: 0;
468
- overflow: hidden;
469
- }
470
- `
471
- });
472
- await page.waitForTimeout(500);
473
- const cardContainer = await page.$("#container");
474
- if (!cardContainer) {
475
- throw new Error("Missing #container element. PNG output requires a #container.");
476
- }
477
- const box = await cardContainer.boundingBox();
478
- if (!box || box.width <= 0 || box.height <= 0) {
479
- throw new Error("Invalid #container size. Ensure it has a positive width and height.");
480
- }
481
- const outputPath = path.resolve(options.output);
482
- await page.screenshot({
483
- path: outputPath,
484
- type: "png",
485
- clip: { x: box.x, y: box.y, width: box.width, height: box.height }
486
- });
487
- return {
488
- pngPath: outputPath,
489
- meta: {
490
- preset: preset.name,
491
- width,
492
- height,
493
- deviceScaleFactor: scale,
494
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
495
- }
496
- };
497
- } finally {
498
- if (browser) {
499
- await browser.close();
500
- }
501
- }
502
- }
503
- const command = new Command("shot").description("Capture HTML to PNG image").requiredOption("-i, --input <path>", "Input HTML file path").requiredOption("-o, --output <path>", "Output PNG file path").option("-p, --preset <name>", "Image preset (og, infographic, poster, banner, twitter, youtube, xiaohongshu, wechat)", "og").option("--width <number>", "Custom width in pixels").option("--height <number>", "Custom height in pixels").option("--scale <number>", "Device scale factor", "2").option("--wait-until <state>", "Navigation waitUntil (load, domcontentloaded, networkidle)", "networkidle").option("--timeout <ms>", "Navigation timeout in milliseconds", "30000").option("--safe", "Disable external network requests and JavaScript execution").action(async (options) => {
319
+ const program = new Command();
320
+ program.name("pagepress").description("Render local Markdown and HTML files to PDF").version("0.8.0").requiredOption("-i, --input <path>", "Input Markdown or HTML file path").requiredOption("-o, --output <path>", "Output PDF file path").option("-t, --template <name>", "PDF template (default, github, magazine)", "default").option("--wait-until <state>", "Navigation waitUntil (load, domcontentloaded, networkidle)", "networkidle").option("--timeout <ms>", "Navigation timeout in milliseconds", "30000").option("--safe", "Disable external network requests and JavaScript execution").action(async (options) => {
504
321
  try {
505
322
  const timeout = Number.parseInt(options.timeout, 10);
506
323
  if (!Number.isFinite(timeout) || timeout < 0) {
@@ -509,10 +326,7 @@ const command = new Command("shot").description("Capture HTML to PNG image").req
509
326
  const result = await render({
510
327
  input: options.input,
511
328
  output: options.output,
512
- preset: options.preset,
513
- width: options.width ? parseInt(options.width) : void 0,
514
- height: options.height ? parseInt(options.height) : void 0,
515
- scale: parseFloat(options.scale),
329
+ template: options.template,
516
330
  waitUntil: options.waitUntil,
517
331
  timeout,
518
332
  safe: options.safe
@@ -523,8 +337,4 @@ const command = new Command("shot").description("Capture HTML to PNG image").req
523
337
  process.exit(1);
524
338
  }
525
339
  });
526
- const program = new Command();
527
- program.name("pagepress").description("Convert web pages and Markdown to PDF and images").version("0.6.1");
528
- program.addCommand(command$1);
529
- program.addCommand(command);
530
340
  program.parse();
package/package.json CHANGED
@@ -1,28 +1,19 @@
1
1
  {
2
2
  "name": "@liustack/pagepress",
3
- "version": "0.6.1",
4
- "description": "CLI tool to convert web pages and Markdown to PDF and images",
3
+ "version": "0.8.0",
4
+ "description": "CLI tool to render local Markdown and HTML files into PDF",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "pagepress": "./dist/main.js"
8
8
  },
9
9
  "files": [
10
10
  "dist",
11
- "src/pdf/templates"
11
+ "src/templates"
12
12
  ],
13
13
  "keywords": [
14
14
  "pdf",
15
- "png",
16
- "screenshot",
17
- "og-image",
18
- "social-card",
19
- "banner",
20
- "poster",
21
- "infographic",
22
15
  "markdown-to-pdf",
23
16
  "html-to-pdf",
24
- "html-to-image",
25
- "web-to-png",
26
17
  "pdf-report",
27
18
  "cli",
28
19
  "playwright",
@@ -55,13 +46,19 @@
55
46
  },
56
47
  "devDependencies": {
57
48
  "@types/node": "^22.19.7",
49
+ "@vitest/coverage-v8": "^4.0.18",
58
50
  "typescript": "^5.9.3",
59
51
  "vite": "^6.4.1",
60
- "vite-plugin-node": "^5.0.1"
52
+ "vite-plugin-node": "^5.0.1",
53
+ "vitest": "^4.0.18"
61
54
  },
62
55
  "scripts": {
63
56
  "dev": "vite build --watch",
64
57
  "build": "vite build",
65
- "typecheck": "tsc --noEmit"
58
+ "typecheck": "tsc --noEmit",
59
+ "test": "vitest run",
60
+ "test:watch": "vitest",
61
+ "test:coverage": "vitest run --coverage",
62
+ "docs:list": "node scripts/docs-list.js"
66
63
  }
67
64
  }