@liustack/pagepress 0.9.0 → 2.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/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # PagePress
2
2
 
3
- A CLI toolkit for AI agents to render local Markdown and HTML files into high-quality PDF documents.
3
+ Generate one-page visual reports, dashboards, and infographics from HTML powered by Playwright + Chromium.
4
4
 
5
5
  中文说明请见:[README.zh-CN.md](README.zh-CN.md)
6
6
 
7
7
  ## Features
8
8
 
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
9
+ - HTML PNG screenshot pipeline
10
+ - Custom viewport dimensions (`--width`, `--height`)
11
+ - High-DPI output via `--scale` (up to 4x)
12
+ - Local HTML file input only (`.html`)
13
+ - Deterministic output via Playwright + Chromium
14
14
 
15
15
  ## Installation
16
16
 
@@ -28,24 +28,35 @@ npx @liustack/pagepress [options]
28
28
  ## Usage
29
29
 
30
30
  ```bash
31
- # Markdown to PDF
32
- pagepress -i document.md -o output.pdf --template default
31
+ # Generate infographic (default 1200×630 @2x)
32
+ pagepress -i report.html -o report.png
33
33
 
34
- # Local HTML to PDF
35
- pagepress -i page.html -o output.pdf
34
+ # Custom dimensions for a tall infographic
35
+ pagepress -i report.html -o report.png -w 1080 -h 1920
36
+
37
+ # High-DPI poster
38
+ pagepress -i poster.html -o poster.png -w 1200 -h 1500 --scale 3
36
39
  ```
37
40
 
38
- ## Templates
41
+ ## HTML Requirements
42
+
43
+ Your HTML must include a `<div id="container">` — PagePress clips the screenshot to this element's bounding box.
39
44
 
40
- - `default` - Clean minimalist style
41
- - `github` - GitHub style
42
- - `magazine` - magazine layout
45
+ ```html
46
+ <body>
47
+ <div id="container">
48
+ <!-- your content here -->
49
+ </div>
50
+ </body>
51
+ ```
43
52
 
44
53
  ## Options
45
54
 
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`)
55
+ - `-i, --input <path>` input HTML file path
56
+ - `-o, --output <path>` output PNG file path
57
+ - `-w, --width <pixels>` viewport width (default: 1200)
58
+ - `-h, --height <pixels>` viewport height (default: 630)
59
+ - `--scale <number>` device scale factor, 1-4 (default: 2)
49
60
  - `--wait-until <state>` `load | domcontentloaded | networkidle`
50
61
  - `--timeout <ms>` timeout in milliseconds
51
62
  - `--safe` disable external network requests and JavaScript execution
package/README.zh-CN.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # PagePress
2
2
 
3
- 面向 AI Agent PDF 渲染 CLI,可将本地 Markdown 和 HTML 文件输出为高质量 PDF
3
+ HTML 生成单页可视化报告、仪表盘和信息图 基于 Playwright + Chromium
4
4
 
5
5
  ## 特性
6
6
 
7
- - 专注 PDF 输出(仅 `pagepress`)
8
- - 仅支持本地文件输入(`.md` / `.html`)
9
- - 内置 Markdown 模板:`default`、`github`、`magazine`
10
- - 支持 Mermaid 代码块渲染
11
- - 基于 Playwright + Chromium 的稳定打印引擎
7
+ - HTML PNG 截图管线
8
+ - 自定义视口尺寸(`--width`、`--height`)
9
+ - DPI 输出,支持最高 4 倍缩放(`--scale`)
10
+ - 仅支持本地 HTML 文件输入(`.html`)
11
+ - 基于 Playwright + Chromium 的稳定渲染引擎
12
12
 
13
13
  ## 安装
14
14
 
@@ -26,24 +26,35 @@ npx @liustack/pagepress [options]
26
26
  ## 用法
27
27
 
28
28
  ```bash
29
- # Markdown PDF
30
- pagepress -i document.md -o output.pdf --template default
29
+ # 生成信息图(默认 1200×630 @2x)
30
+ pagepress -i report.html -o report.png
31
31
 
32
- # 本地 HTML 转 PDF
33
- pagepress -i page.html -o output.pdf
32
+ # 自定义尺寸的长信息图
33
+ pagepress -i report.html -o report.png -w 1080 -h 1920
34
+
35
+ # 高 DPI 海报
36
+ pagepress -i poster.html -o poster.png -w 1200 -h 1500 --scale 3
34
37
  ```
35
38
 
36
- ## 模板
39
+ ## HTML 要求
40
+
41
+ HTML 中必须包含 `<div id="container">` — PagePress 会根据该元素的边界框裁剪截图。
37
42
 
38
- - `default` - 简洁风格
39
- - `github` - GitHub 风格
40
- - `magazine` - 杂志排版
43
+ ```html
44
+ <body>
45
+ <div id="container">
46
+ <!-- 你的内容 -->
47
+ </div>
48
+ </body>
49
+ ```
41
50
 
42
51
  ## 参数
43
52
 
44
- - `-i, --input <path>` 输入 Markdown 或 HTML 路径
45
- - `-o, --output <path>` 输出 PDF 路径
46
- - `-t, --template <name>` Markdown 模板(默认 `default`)
53
+ - `-i, --input <path>` 输入 HTML 路径
54
+ - `-o, --output <path>` 输出 PNG 路径
55
+ - `-w, --width <pixels>` 视口宽度(默认: 1200)
56
+ - `-h, --height <pixels>` 视口高度(默认: 630)
57
+ - `--scale <number>` 设备缩放因子,1-4(默认: 2)
47
58
  - `--wait-until <state>` `load | domcontentloaded | networkidle`
48
59
  - `--timeout <ms>` 超时时间(毫秒)
49
60
  - `--safe` 禁用外部网络请求和 JavaScript 执行
package/dist/main.js CHANGED
@@ -3,77 +3,12 @@ import { Command } from "commander";
3
3
  import { chromium } from "playwright";
4
4
  import * as fs from "fs";
5
5
  import * as path from "path";
6
- import { fileURLToPath, pathToFileURL } from "url";
7
- import { createRequire } from "module";
8
- import { Renderer, marked } from "marked";
9
- import matter from "gray-matter";
10
- import hljs from "highlight.js";
11
- const pdfTemplates = {
12
- default: {
13
- name: "default",
14
- description: "Clean minimalist design",
15
- file: "default.html"
16
- },
17
- github: {
18
- name: "github",
19
- description: "GitHub-flavored markdown style",
20
- file: "github.html"
21
- },
22
- magazine: {
23
- name: "magazine",
24
- description: "VOGUE/WIRED premium magazine style",
25
- file: "magazine.html"
26
- }
27
- };
28
- function getTemplate(name) {
29
- const template = pdfTemplates[name];
30
- if (!template) {
31
- throw new Error(`Unknown PDF template: ${name}. Available: ${Object.keys(pdfTemplates).join(", ")}`);
32
- }
33
- return template;
34
- }
35
- const require$2 = createRequire(import.meta.url);
36
- const MAGAZINE_FONTS = [
37
- "@fontsource/bebas-neue/400.css",
38
- "@fontsource/cormorant-garamond/400.css",
39
- "@fontsource/cormorant-garamond/400-italic.css",
40
- "@fontsource/cormorant-garamond/600.css",
41
- "@fontsource/inter/300.css",
42
- "@fontsource/inter/400.css",
43
- "@fontsource/inter/500.css"
44
- ];
45
- function getFontCSS(template) {
46
- if (template !== "magazine") {
47
- return "";
48
- }
49
- const cssChunks = [];
50
- for (const fontPath of MAGAZINE_FONTS) {
51
- try {
52
- const cssFilePath = require$2.resolve(fontPath);
53
- let css = fs.readFileSync(cssFilePath, "utf-8");
54
- const cssDir = path.dirname(cssFilePath);
55
- css = css.replace(/url\(\.\/files\/([^)]+)\)/g, (match, filename) => {
56
- const fontFilePath = path.join(cssDir, "files", filename);
57
- if (fs.existsSync(fontFilePath)) {
58
- const fontData = fs.readFileSync(fontFilePath);
59
- const base64 = fontData.toString("base64");
60
- const ext = path.extname(filename).slice(1);
61
- const mimeType = ext === "woff2" ? "font/woff2" : `font/${ext}`;
62
- return `url(data:${mimeType};base64,${base64})`;
63
- }
64
- return match;
65
- });
66
- cssChunks.push(css);
67
- } catch (e) {
68
- console.warn(`Warning: Could not load font ${fontPath}:`, e);
69
- }
70
- }
71
- return cssChunks.join("\n");
72
- }
73
- const __filename$1 = fileURLToPath(import.meta.url);
74
- const __dirname$1 = path.dirname(__filename$1);
75
- const require$1 = createRequire(import.meta.url);
6
+ import { pathToFileURL } from "url";
76
7
  const WAIT_UNTIL_STATES = /* @__PURE__ */ new Set(["load", "domcontentloaded", "networkidle"]);
8
+ const MIN_SCALE = 1;
9
+ const MAX_SCALE = 4;
10
+ const DEFAULT_WIDTH = 1200;
11
+ const DEFAULT_HEIGHT = 630;
77
12
  function normalizeWaitUntil(value) {
78
13
  if (!value) return "networkidle";
79
14
  if (WAIT_UNTIL_STATES.has(value)) {
@@ -88,96 +23,40 @@ function normalizeTimeout(value) {
88
23
  }
89
24
  return value;
90
25
  }
91
- function findTemplatesDir() {
92
- const candidates = [
93
- // When running from project root (dev/installed)
94
- path.resolve(process.cwd(), "src/templates"),
95
- // Relative to dist/main.js
96
- path.resolve(__dirname$1, "..", "src", "templates"),
97
- // Relative to source file location
98
- path.resolve(__dirname$1, "templates")
99
- ];
100
- for (const dir of candidates) {
101
- if (fs.existsSync(dir)) return dir;
26
+ function validateRange(name, value, min, max) {
27
+ if (!Number.isFinite(value)) {
28
+ throw new Error(`${name} must be a finite number.`);
29
+ }
30
+ if (value < min || value > max) {
31
+ throw new Error(`${name} must be between ${min} and ${max}. Received: ${value}.`);
102
32
  }
103
- throw new Error(`Templates directory not found. Searched: ${candidates.join(", ")}`);
104
33
  }
105
34
  async function render(options) {
106
- const template = getTemplate(options.template ?? "default");
35
+ const width = options.width ?? DEFAULT_WIDTH;
36
+ const height = options.height ?? DEFAULT_HEIGHT;
37
+ const scale = options.scale ?? 2;
107
38
  const safeMode = options.safe ?? false;
108
39
  const waitUntil = normalizeWaitUntil(options.waitUntil);
109
40
  const timeout = normalizeTimeout(options.timeout);
110
- const templatesDir = findTemplatesDir();
111
- const templatePath = path.join(templatesDir, template.file);
112
- if (!fs.existsSync(templatePath)) {
113
- throw new Error(`Template not found: ${templatePath}`);
114
- }
115
- const inputPath = path.resolve(options.input);
116
- let htmlContent;
117
- let hasMermaid = false;
41
+ validateRange("Width", width, 1, 1e4);
42
+ validateRange("Height", height, 1, 1e4);
43
+ validateRange("Scale", scale, MIN_SCALE, MAX_SCALE);
118
44
  if (options.input.startsWith("http://") || options.input.startsWith("https://")) {
119
- throw new Error("Remote URL inputs are not supported. Please provide a local Markdown or HTML file path.");
120
- } else if (options.input.endsWith(".md")) {
121
- const raw = fs.readFileSync(inputPath, "utf-8");
122
- const { data: frontmatter, content } = matter(raw);
123
- hasMermaid = /```mermaid/i.test(content);
124
- const renderer = new Renderer();
125
- renderer.code = function({ text, lang }) {
126
- if (lang === "mermaid") {
127
- return `<pre class="mermaid">${text}</pre>`;
128
- }
129
- const highlighted = lang && hljs.getLanguage(lang) ? hljs.highlight(text, { language: lang }).value : hljs.highlightAuto(text).value;
130
- const langClass = lang ? `hljs language-${lang}` : "hljs";
131
- return `<pre><code class="${langClass}">${highlighted}</code></pre>`;
132
- };
133
- marked.use({ renderer });
134
- const bodyHtml = await marked.parse(content);
135
- const title = frontmatter.title || "Document";
136
- const isDarkTheme = template.name === "magazine";
137
- const hljsLightStyles = `
138
- <style>
139
- /* highlight.js GitHub Light Theme */
140
- .hljs { color: #24292e; }
141
- .hljs-comment, .hljs-quote { color: #6a737d; font-style: italic; }
142
- .hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-type { color: #d73a49; font-weight: 600; }
143
- .hljs-string, .hljs-attr, .hljs-symbol, .hljs-bullet, .hljs-addition { color: #032f62; }
144
- .hljs-number { color: #005cc5; }
145
- .hljs-title, .hljs-section, .hljs-function .hljs-title { color: #6f42c1; }
146
- .hljs-built_in, .hljs-name { color: #005cc5; }
147
- .hljs-class .hljs-title { color: #e36209; }
148
- .hljs-meta { color: #6f42c1; }
149
- .hljs-variable, .hljs-template-variable { color: #e36209; }
150
- .hljs-params { color: #24292e; }
151
- </style>
152
- `;
153
- const hljsDarkStyles = `
154
- <style>
155
- /* highlight.js Dark Theme for Magazine */
156
- .hljs { color: #e8e8e8; }
157
- .hljs-comment, .hljs-quote { color: #6b7280; font-style: italic; }
158
- .hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-type { color: #f0abfc; }
159
- .hljs-string, .hljs-attr, .hljs-symbol, .hljs-bullet, .hljs-addition { color: #86efac; }
160
- .hljs-number { color: #fcd34d; }
161
- .hljs-title, .hljs-section, .hljs-function .hljs-title { color: #93c5fd; }
162
- .hljs-built_in, .hljs-name { color: #93c5fd; }
163
- .hljs-class .hljs-title { color: #67e8f9; }
164
- .hljs-meta { color: #f0abfc; }
165
- .hljs-variable, .hljs-template-variable { color: #fcd34d; }
166
- .hljs-params { color: #e8e8e8; }
167
- </style>
168
- `;
169
- const hljsStyles = isDarkTheme ? hljsDarkStyles : hljsLightStyles;
170
- let templateHtml = fs.readFileSync(templatePath, "utf-8");
171
- htmlContent = templateHtml.replace(/\{\{title\}\}/g, title).replace(/\{\{body\}\}/g, bodyHtml).replace(/\{\{styles\}\}/g, hljsStyles);
172
- } else if (options.input.endsWith(".html")) {
173
- htmlContent = fs.readFileSync(inputPath, "utf-8");
174
- } else {
175
- throw new Error(`Unsupported input format: ${options.input}`);
45
+ throw new Error("Remote URL inputs are not supported. Please provide a local HTML file path.");
46
+ }
47
+ if (!options.input.endsWith(".html")) {
48
+ throw new Error(`Unsupported input format: ${options.input}. Only HTML (.html) files are supported.`);
176
49
  }
50
+ const inputPath = path.resolve(options.input);
51
+ const htmlContent = fs.readFileSync(inputPath, "utf-8");
177
52
  let browser = null;
178
53
  try {
179
54
  browser = await chromium.launch({ headless: true });
180
- const context = await browser.newContext({ javaScriptEnabled: !safeMode });
55
+ const context = await browser.newContext({
56
+ viewport: { width, height },
57
+ deviceScaleFactor: scale,
58
+ javaScriptEnabled: !safeMode
59
+ });
181
60
  if (safeMode) {
182
61
  await context.route("**/*", (route) => {
183
62
  const url = route.request().url();
@@ -190,124 +69,38 @@ async function render(options) {
190
69
  const page = await context.newPage();
191
70
  await page.goto(pathToFileURL(inputPath).href, { waitUntil: "domcontentloaded", timeout });
192
71
  await page.setContent(htmlContent, { waitUntil, timeout });
193
- const fontCSS = getFontCSS(template.name);
194
- if (fontCSS) {
195
- await page.addStyleTag({ content: fontCSS });
72
+ await page.addStyleTag({
73
+ content: `
74
+ html, body {
75
+ width: ${width}px;
76
+ height: ${height}px;
77
+ margin: 0;
78
+ padding: 0;
79
+ overflow: hidden;
80
+ }
81
+ `
82
+ });
83
+ await page.waitForTimeout(500);
84
+ const cardContainer = await page.$("#container");
85
+ if (!cardContainer) {
86
+ throw new Error('Missing #container element. PNG output requires a <div id="container"> wrapper.');
196
87
  }
197
- if (hasMermaid && !safeMode) {
198
- const mermaidPath = require$1.resolve("mermaid/dist/mermaid.min.js");
199
- await page.addScriptTag({ path: mermaidPath });
200
- const templateName = template.name;
201
- await page.evaluate((tpl) => {
202
- const defaultTheme = {
203
- primaryColor: "#f5f5f7",
204
- primaryTextColor: "#1d1d1f",
205
- primaryBorderColor: "#d2d2d7",
206
- lineColor: "#86868b",
207
- secondaryColor: "#f5f5f7",
208
- tertiaryColor: "#ffffff",
209
- background: "#ffffff",
210
- mainBkg: "#f5f5f7",
211
- nodeBorder: "#d2d2d7",
212
- nodeTextColor: "#1d1d1f",
213
- clusterBkg: "rgba(0, 0, 0, 0.02)",
214
- clusterBorder: "rgba(0, 0, 0, 0.1)",
215
- titleColor: "#1d1d1f"
216
- };
217
- const githubTheme = {
218
- primaryColor: "#f6f8fa",
219
- primaryTextColor: "#1f2328",
220
- primaryBorderColor: "#d0d7de",
221
- lineColor: "#656d76",
222
- secondaryColor: "#f6f8fa",
223
- tertiaryColor: "#ffffff",
224
- background: "#ffffff",
225
- mainBkg: "#f6f8fa",
226
- nodeBorder: "#d0d7de",
227
- nodeTextColor: "#1f2328",
228
- clusterBkg: "rgba(208, 215, 222, 0.2)",
229
- clusterBorder: "#d0d7de",
230
- titleColor: "#1f2328"
231
- };
232
- const magazineTheme = {
233
- primaryColor: "#2a2a2a",
234
- primaryTextColor: "#ffffff",
235
- primaryBorderColor: "#c41e3a",
236
- lineColor: "#666666",
237
- secondaryColor: "#1a1a1a",
238
- tertiaryColor: "#0f0f0f",
239
- background: "#0f0f0f",
240
- mainBkg: "#2a2a2a",
241
- nodeBorder: "#c41e3a",
242
- nodeTextColor: "#ffffff",
243
- clusterBkg: "rgba(196, 30, 58, 0.1)",
244
- clusterBorder: "rgba(196, 30, 58, 0.4)",
245
- titleColor: "#ffffff"
246
- };
247
- const themes = {
248
- default: defaultTheme,
249
- github: githubTheme,
250
- magazine: magazineTheme
251
- };
252
- const activeTheme = themes[tpl] || defaultTheme;
253
- window.mermaid.initialize({
254
- startOnLoad: false,
255
- theme: "base",
256
- themeVariables: {
257
- ...activeTheme,
258
- fontFamily: '"SF Mono", "Fira Code", Menlo, monospace',
259
- fontSize: "13px"
260
- },
261
- flowchart: {
262
- curve: "basis",
263
- nodeSpacing: 40,
264
- rankSpacing: 50,
265
- htmlLabels: true,
266
- useMaxWidth: true,
267
- subGraphTitleMargin: { top: 15, bottom: 15 },
268
- padding: 20
269
- }
270
- });
271
- }, templateName);
272
- await page.evaluate(() => {
273
- return window.mermaid.run();
274
- });
275
- await page.waitForSelector(".mermaid svg", { timeout: 5e3 });
276
- await page.waitForTimeout(300);
277
- await page.evaluate(() => {
278
- document.querySelectorAll(".node rect").forEach((rect) => {
279
- rect.setAttribute("rx", "12");
280
- rect.setAttribute("ry", "12");
281
- });
282
- document.querySelectorAll(".cluster rect").forEach((rect) => {
283
- rect.setAttribute("rx", "16");
284
- rect.setAttribute("ry", "16");
285
- });
286
- document.querySelectorAll(".cluster-label").forEach((label) => {
287
- const transform = label.getAttribute("transform");
288
- if (transform) {
289
- const match = transform.match(/translate\(([\d.]+),\s*([\d.]+)\)/);
290
- if (match) {
291
- const x = parseFloat(match[1]);
292
- const y = parseFloat(match[2]) + 8;
293
- label.setAttribute("transform", `translate(${x}, ${y})`);
294
- }
295
- }
296
- });
297
- });
88
+ const box = await cardContainer.boundingBox();
89
+ if (!box || box.width <= 0 || box.height <= 0) {
90
+ throw new Error("Invalid #container size. Ensure it has a positive width and height.");
298
91
  }
299
- await page.waitForTimeout(200);
300
92
  const outputPath = path.resolve(options.output);
301
- await page.pdf({
93
+ await page.screenshot({
302
94
  path: outputPath,
303
- format: "A4",
304
- margin: { top: "0", right: "0", bottom: "0", left: "0" },
305
- printBackground: true
95
+ type: "png",
96
+ clip: { x: box.x, y: box.y, width: box.width, height: box.height }
306
97
  });
307
98
  return {
308
- pdfPath: outputPath,
99
+ pngPath: outputPath,
309
100
  meta: {
310
- template: template.name,
101
+ width,
102
+ height,
103
+ deviceScaleFactor: scale,
311
104
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
312
105
  }
313
106
  };
@@ -318,7 +111,7 @@ async function render(options) {
318
111
  }
319
112
  }
320
113
  const program = new Command();
321
- program.name("pagepress").description("Render local Markdown and HTML files to PDF").version("0.9.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) => {
114
+ program.name("pagepress").description("Generate one-page visual reports, dashboards, and infographics from HTML").version("2.0.0").requiredOption("-i, --input <path>", "Input HTML file path").requiredOption("-o, --output <path>", "Output PNG file path").option("-w, --width <pixels>", "Viewport width in pixels", "1200").option("-h, --height <pixels>", "Viewport height in pixels", "630").option("--scale <number>", "Device scale factor (1-4)", "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) => {
322
115
  try {
323
116
  const timeout = Number.parseInt(options.timeout, 10);
324
117
  if (!Number.isFinite(timeout) || timeout < 0) {
@@ -327,7 +120,9 @@ program.name("pagepress").description("Render local Markdown and HTML files to P
327
120
  const result = await render({
328
121
  input: options.input,
329
122
  output: options.output,
330
- template: options.template,
123
+ width: Number.parseInt(options.width, 10),
124
+ height: Number.parseInt(options.height, 10),
125
+ scale: Number.parseFloat(options.scale),
331
126
  waitUntil: options.waitUntil,
332
127
  timeout,
333
128
  safe: options.safe
package/package.json CHANGED
@@ -1,23 +1,32 @@
1
1
  {
2
2
  "name": "@liustack/pagepress",
3
- "version": "0.9.0",
4
- "description": "CLI tool to render local Markdown and HTML files into PDF",
3
+ "version": "2.0.0",
4
+ "description": "CLI tool to generate one-page visual reports, dashboards, and infographics from HTML",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "pagepress": "./dist/main.js"
8
8
  },
9
+ "scripts": {
10
+ "dev": "vite build --watch",
11
+ "build": "vite build",
12
+ "typecheck": "tsc --noEmit",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest",
15
+ "test:coverage": "vitest run --coverage",
16
+ "prepublishOnly": "pnpm build",
17
+ "docs:list": "node scripts/docs-list.js"
18
+ },
9
19
  "files": [
10
- "dist",
11
- "src/templates"
20
+ "dist"
12
21
  ],
13
22
  "keywords": [
14
- "pdf",
15
- "markdown-to-pdf",
16
- "html-to-pdf",
17
- "pdf-report",
23
+ "infographic",
24
+ "html-to-png",
25
+ "visual-report",
26
+ "dashboard",
27
+ "screenshot",
18
28
  "cli",
19
- "playwright",
20
- "mermaid"
29
+ "playwright"
21
30
  ],
22
31
  "author": "Leon Liu",
23
32
  "license": "MIT",
@@ -33,15 +42,7 @@
33
42
  "node": ">=18"
34
43
  },
35
44
  "dependencies": {
36
- "@fontsource/bebas-neue": "^5.2.7",
37
- "@fontsource/cormorant-garamond": "^5.2.11",
38
- "@fontsource/inter": "^5.2.8",
39
45
  "commander": "^13.1.0",
40
- "gray-matter": "^4.0.3",
41
- "highlight.js": "^11.11.1",
42
- "marked": "^15.0.12",
43
- "marked-highlight": "^2.2.3",
44
- "mermaid": "^11.12.2",
45
46
  "playwright": "^1.58.1"
46
47
  },
47
48
  "devDependencies": {
@@ -51,14 +52,5 @@
51
52
  "vite": "^6.4.1",
52
53
  "vite-plugin-node": "^5.0.1",
53
54
  "vitest": "^4.0.18"
54
- },
55
- "scripts": {
56
- "dev": "vite build --watch",
57
- "build": "vite build",
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"
63
55
  }
64
- }
56
+ }