@liustack/pagepress 0.9.0 → 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/README.md +3 -15
- package/README.zh-CN.md +3 -15
- package/dist/main.js +8 -257
- package/package.json +4 -15
- package/src/templates/default.html +0 -253
- package/src/templates/github.html +0 -316
- package/src/templates/magazine.html +0 -452
package/README.md
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
# PagePress
|
|
2
2
|
|
|
3
|
-
A CLI toolkit for AI agents to render local
|
|
3
|
+
A CLI toolkit for AI agents to render local HTML files into high-quality PDF documents.
|
|
4
4
|
|
|
5
5
|
中文说明请见:[README.zh-CN.md](README.zh-CN.md)
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- PDF-only command surface (`pagepress`)
|
|
10
|
-
- Local file input only (`.
|
|
11
|
-
- Built-in Markdown templates: `default`, `github`, `magazine`
|
|
12
|
-
- Built-in Mermaid rendering for Markdown code blocks
|
|
10
|
+
- Local HTML file input only (`.html`)
|
|
13
11
|
- Deterministic output via Playwright + Chromium print engine
|
|
14
12
|
|
|
15
13
|
## Installation
|
|
@@ -28,24 +26,14 @@ npx @liustack/pagepress [options]
|
|
|
28
26
|
## Usage
|
|
29
27
|
|
|
30
28
|
```bash
|
|
31
|
-
# Markdown to PDF
|
|
32
|
-
pagepress -i document.md -o output.pdf --template default
|
|
33
|
-
|
|
34
29
|
# Local HTML to PDF
|
|
35
30
|
pagepress -i page.html -o output.pdf
|
|
36
31
|
```
|
|
37
32
|
|
|
38
|
-
## Templates
|
|
39
|
-
|
|
40
|
-
- `default` - Clean minimalist style
|
|
41
|
-
- `github` - GitHub style
|
|
42
|
-
- `magazine` - magazine layout
|
|
43
|
-
|
|
44
33
|
## Options
|
|
45
34
|
|
|
46
|
-
- `-i, --input <path>` input
|
|
35
|
+
- `-i, --input <path>` input HTML file path
|
|
47
36
|
- `-o, --output <path>` output PDF file path
|
|
48
|
-
- `-t, --template <name>` template for Markdown input (default: `default`)
|
|
49
37
|
- `--wait-until <state>` `load | domcontentloaded | networkidle`
|
|
50
38
|
- `--timeout <ms>` timeout in milliseconds
|
|
51
39
|
- `--safe` disable external network requests and JavaScript execution
|
package/README.zh-CN.md
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# PagePress
|
|
2
2
|
|
|
3
|
-
面向 AI Agent 的 PDF 渲染 CLI,可将本地
|
|
3
|
+
面向 AI Agent 的 PDF 渲染 CLI,可将本地 HTML 文件输出为高质量 PDF。
|
|
4
4
|
|
|
5
5
|
## 特性
|
|
6
6
|
|
|
7
7
|
- 专注 PDF 输出(仅 `pagepress`)
|
|
8
|
-
-
|
|
9
|
-
- 内置 Markdown 模板:`default`、`github`、`magazine`
|
|
10
|
-
- 支持 Mermaid 代码块渲染
|
|
8
|
+
- 仅支持本地 HTML 文件输入(`.html`)
|
|
11
9
|
- 基于 Playwright + Chromium 的稳定打印引擎
|
|
12
10
|
|
|
13
11
|
## 安装
|
|
@@ -26,24 +24,14 @@ npx @liustack/pagepress [options]
|
|
|
26
24
|
## 用法
|
|
27
25
|
|
|
28
26
|
```bash
|
|
29
|
-
# Markdown 转 PDF
|
|
30
|
-
pagepress -i document.md -o output.pdf --template default
|
|
31
|
-
|
|
32
27
|
# 本地 HTML 转 PDF
|
|
33
28
|
pagepress -i page.html -o output.pdf
|
|
34
29
|
```
|
|
35
30
|
|
|
36
|
-
## 模板
|
|
37
|
-
|
|
38
|
-
- `default` - 简洁风格
|
|
39
|
-
- `github` - GitHub 风格
|
|
40
|
-
- `magazine` - 杂志排版
|
|
41
|
-
|
|
42
31
|
## 参数
|
|
43
32
|
|
|
44
|
-
- `-i, --input <path>` 输入
|
|
33
|
+
- `-i, --input <path>` 输入 HTML 路径
|
|
45
34
|
- `-o, --output <path>` 输出 PDF 路径
|
|
46
|
-
- `-t, --template <name>` Markdown 模板(默认 `default`)
|
|
47
35
|
- `--wait-until <state>` `load | domcontentloaded | networkidle`
|
|
48
36
|
- `--timeout <ms>` 超时时间(毫秒)
|
|
49
37
|
- `--safe` 禁用外部网络请求和 JavaScript 执行
|
package/dist/main.js
CHANGED
|
@@ -4,75 +4,8 @@ import { chromium } from "playwright";
|
|
|
4
4
|
import * as fs from "fs";
|
|
5
5
|
import * as path from "path";
|
|
6
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
7
|
const __filename$1 = fileURLToPath(import.meta.url);
|
|
74
|
-
|
|
75
|
-
const require$1 = createRequire(import.meta.url);
|
|
8
|
+
path.dirname(__filename$1);
|
|
76
9
|
const WAIT_UNTIL_STATES = /* @__PURE__ */ new Set(["load", "domcontentloaded", "networkidle"]);
|
|
77
10
|
function normalizeWaitUntil(value) {
|
|
78
11
|
if (!value) return "networkidle";
|
|
@@ -88,92 +21,18 @@ function normalizeTimeout(value) {
|
|
|
88
21
|
}
|
|
89
22
|
return value;
|
|
90
23
|
}
|
|
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;
|
|
102
|
-
}
|
|
103
|
-
throw new Error(`Templates directory not found. Searched: ${candidates.join(", ")}`);
|
|
104
|
-
}
|
|
105
24
|
async function render(options) {
|
|
106
|
-
const template = getTemplate(options.template ?? "default");
|
|
107
25
|
const safeMode = options.safe ?? false;
|
|
108
26
|
const waitUntil = normalizeWaitUntil(options.waitUntil);
|
|
109
27
|
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;
|
|
118
28
|
if (options.input.startsWith("http://") || options.input.startsWith("https://")) {
|
|
119
|
-
throw new Error("Remote URL inputs are not supported. Please provide a local
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
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}`);
|
|
29
|
+
throw new Error("Remote URL inputs are not supported. Please provide a local HTML file path.");
|
|
30
|
+
}
|
|
31
|
+
if (!options.input.endsWith(".html")) {
|
|
32
|
+
throw new Error(`Unsupported input format: ${options.input}. Only HTML (.html) files are supported.`);
|
|
176
33
|
}
|
|
34
|
+
const inputPath = path.resolve(options.input);
|
|
35
|
+
const htmlContent = fs.readFileSync(inputPath, "utf-8");
|
|
177
36
|
let browser = null;
|
|
178
37
|
try {
|
|
179
38
|
browser = await chromium.launch({ headless: true });
|
|
@@ -190,112 +49,6 @@ async function render(options) {
|
|
|
190
49
|
const page = await context.newPage();
|
|
191
50
|
await page.goto(pathToFileURL(inputPath).href, { waitUntil: "domcontentloaded", timeout });
|
|
192
51
|
await page.setContent(htmlContent, { waitUntil, timeout });
|
|
193
|
-
const fontCSS = getFontCSS(template.name);
|
|
194
|
-
if (fontCSS) {
|
|
195
|
-
await page.addStyleTag({ content: fontCSS });
|
|
196
|
-
}
|
|
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
|
-
});
|
|
298
|
-
}
|
|
299
52
|
await page.waitForTimeout(200);
|
|
300
53
|
const outputPath = path.resolve(options.output);
|
|
301
54
|
await page.pdf({
|
|
@@ -307,7 +60,6 @@ async function render(options) {
|
|
|
307
60
|
return {
|
|
308
61
|
pdfPath: outputPath,
|
|
309
62
|
meta: {
|
|
310
|
-
template: template.name,
|
|
311
63
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
312
64
|
}
|
|
313
65
|
};
|
|
@@ -318,7 +70,7 @@ async function render(options) {
|
|
|
318
70
|
}
|
|
319
71
|
}
|
|
320
72
|
const program = new Command();
|
|
321
|
-
program.name("pagepress").description("Render local
|
|
73
|
+
program.name("pagepress").description("Render local HTML files to PDF").version("1.0.0").requiredOption("-i, --input <path>", "Input HTML file path").requiredOption("-o, --output <path>", "Output PDF file path").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
74
|
try {
|
|
323
75
|
const timeout = Number.parseInt(options.timeout, 10);
|
|
324
76
|
if (!Number.isFinite(timeout) || timeout < 0) {
|
|
@@ -327,7 +79,6 @@ program.name("pagepress").description("Render local Markdown and HTML files to P
|
|
|
327
79
|
const result = await render({
|
|
328
80
|
input: options.input,
|
|
329
81
|
output: options.output,
|
|
330
|
-
template: options.template,
|
|
331
82
|
waitUntil: options.waitUntil,
|
|
332
83
|
timeout,
|
|
333
84
|
safe: options.safe
|
package/package.json
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liustack/pagepress",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI tool to render local
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool to render local HTML files into PDF",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"pagepress": "./dist/main.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
|
-
"dist"
|
|
11
|
-
"src/templates"
|
|
10
|
+
"dist"
|
|
12
11
|
],
|
|
13
12
|
"keywords": [
|
|
14
13
|
"pdf",
|
|
15
|
-
"markdown-to-pdf",
|
|
16
14
|
"html-to-pdf",
|
|
17
15
|
"pdf-report",
|
|
18
16
|
"cli",
|
|
19
|
-
"playwright"
|
|
20
|
-
"mermaid"
|
|
17
|
+
"playwright"
|
|
21
18
|
],
|
|
22
19
|
"author": "Leon Liu",
|
|
23
20
|
"license": "MIT",
|
|
@@ -33,15 +30,7 @@
|
|
|
33
30
|
"node": ">=18"
|
|
34
31
|
},
|
|
35
32
|
"dependencies": {
|
|
36
|
-
"@fontsource/bebas-neue": "^5.2.7",
|
|
37
|
-
"@fontsource/cormorant-garamond": "^5.2.11",
|
|
38
|
-
"@fontsource/inter": "^5.2.8",
|
|
39
33
|
"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
34
|
"playwright": "^1.58.1"
|
|
46
35
|
},
|
|
47
36
|
"devDependencies": {
|
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
|
|
4
|
-
<head>
|
|
5
|
-
<meta charset="UTF-8" />
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<title>{{title}}</title>
|
|
8
|
-
<style>
|
|
9
|
-
/**
|
|
10
|
-
* Default Theme — Clean minimalist design
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
:root {
|
|
14
|
-
--text-primary: #1d1d1f;
|
|
15
|
-
--text-secondary: #6e6e73;
|
|
16
|
-
--text-tertiary: #86868b;
|
|
17
|
-
--bg-primary: #ffffff;
|
|
18
|
-
--border-color: #d2d2d7;
|
|
19
|
-
--border-light: #e5e5ea;
|
|
20
|
-
--accent: #0071e3;
|
|
21
|
-
--code-bg: #f5f5f7;
|
|
22
|
-
--code-block-bg: #f6f8fa;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
* {
|
|
26
|
-
box-sizing: border-box;
|
|
27
|
-
margin: 0;
|
|
28
|
-
padding: 0;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
body {
|
|
32
|
-
font-family: system-ui, -apple-system, BlinkMacSystemFont,
|
|
33
|
-
"PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", sans-serif;
|
|
34
|
-
font-size: 16px;
|
|
35
|
-
line-height: 1.75;
|
|
36
|
-
color: var(--text-primary);
|
|
37
|
-
background: var(--bg-primary);
|
|
38
|
-
-webkit-font-smoothing: antialiased;
|
|
39
|
-
-moz-osx-font-smoothing: grayscale;
|
|
40
|
-
-webkit-print-color-adjust: exact;
|
|
41
|
-
print-color-adjust: exact;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.container {
|
|
45
|
-
max-width: 900px;
|
|
46
|
-
margin: 0 auto;
|
|
47
|
-
padding: 64px 56px 120px;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/* Heading System */
|
|
51
|
-
h1, h2, h3, h4, h5, h6 {
|
|
52
|
-
font-weight: 600;
|
|
53
|
-
line-height: 1.25;
|
|
54
|
-
margin-top: 1.5em;
|
|
55
|
-
margin-bottom: 0.5em;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
h1 {
|
|
59
|
-
font-size: 32px;
|
|
60
|
-
line-height: 1.15;
|
|
61
|
-
margin-top: 0;
|
|
62
|
-
margin-bottom: 20px;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
h2 {
|
|
66
|
-
font-size: 24px;
|
|
67
|
-
margin-top: 40px;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
h3 {
|
|
71
|
-
font-size: 18px;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
h4 {
|
|
75
|
-
font-size: 16px;
|
|
76
|
-
color: var(--text-secondary);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/* Paragraph */
|
|
80
|
-
p {
|
|
81
|
-
margin-bottom: 1.25em;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
strong {
|
|
85
|
-
font-weight: 600;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
a {
|
|
89
|
-
color: var(--accent);
|
|
90
|
-
text-decoration: none;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
a:hover {
|
|
94
|
-
text-decoration: underline;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/* Blockquote */
|
|
98
|
-
blockquote {
|
|
99
|
-
margin: 1.5em 0;
|
|
100
|
-
padding: 0 0 0 1em;
|
|
101
|
-
border-left: 3px solid var(--border-color);
|
|
102
|
-
color: var(--text-secondary);
|
|
103
|
-
font-style: italic;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
blockquote p:last-child {
|
|
107
|
-
margin-bottom: 0;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/* Code */
|
|
111
|
-
code {
|
|
112
|
-
font-family: ui-monospace, Menlo, Consolas, monospace;
|
|
113
|
-
font-size: 0.875em;
|
|
114
|
-
padding: 0.15em 0.4em;
|
|
115
|
-
background: var(--code-bg);
|
|
116
|
-
border-radius: 4px;
|
|
117
|
-
color: var(--text-primary);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
pre {
|
|
121
|
-
margin: 1.5em 0;
|
|
122
|
-
padding: 20px 24px;
|
|
123
|
-
background: var(--code-block-bg);
|
|
124
|
-
border: 1px solid var(--border-light);
|
|
125
|
-
border-radius: 8px;
|
|
126
|
-
overflow-x: auto;
|
|
127
|
-
font-family: ui-monospace, Menlo, Consolas, monospace;
|
|
128
|
-
font-size: 13px;
|
|
129
|
-
line-height: 1.6;
|
|
130
|
-
color: var(--text-primary);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
pre code {
|
|
134
|
-
display: block;
|
|
135
|
-
padding: 0;
|
|
136
|
-
background: transparent;
|
|
137
|
-
color: inherit;
|
|
138
|
-
border-radius: 0;
|
|
139
|
-
font-size: inherit;
|
|
140
|
-
font-family: inherit;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/* Syntax highlighting — light theme */
|
|
144
|
-
pre .hljs { color: #24292e; }
|
|
145
|
-
pre .hljs-comment, pre .hljs-quote { color: #6a737d; font-style: italic; }
|
|
146
|
-
pre .hljs-keyword, pre .hljs-selector-tag, pre .hljs-literal, pre .hljs-type { color: #d73a49; font-weight: 600; }
|
|
147
|
-
pre .hljs-string, pre .hljs-attr, pre .hljs-symbol, pre .hljs-bullet, pre .hljs-addition { color: #032f62; }
|
|
148
|
-
pre .hljs-number { color: #005cc5; }
|
|
149
|
-
pre .hljs-title, pre .hljs-section { color: #6f42c1; }
|
|
150
|
-
pre .hljs-built_in, pre .hljs-name { color: #005cc5; }
|
|
151
|
-
pre .hljs-variable, pre .hljs-template-variable { color: #e36209; }
|
|
152
|
-
pre .hljs-meta { color: #6f42c1; }
|
|
153
|
-
|
|
154
|
-
/* codehilite compatibility */
|
|
155
|
-
pre .c, pre .ch, pre .cm, pre .c1, pre .cs { color: #6a737d; font-style: italic; }
|
|
156
|
-
pre .k, pre .kc, pre .kd, pre .kn, pre .kp, pre .kr, pre .kt { color: #d73a49; font-weight: 600; }
|
|
157
|
-
pre .s, pre .s1, pre .s2, pre .sa, pre .sb, pre .sc, pre .sd, pre .se, pre .sh, pre .si, pre .sr, pre .ss, pre .sx { color: #032f62; }
|
|
158
|
-
pre .nf, pre .fm { color: #6f42c1; }
|
|
159
|
-
pre .mi, pre .mf, pre .mh, pre .mo, pre .mb, pre .il { color: #005cc5; }
|
|
160
|
-
pre .bp { color: #005cc5; }
|
|
161
|
-
|
|
162
|
-
/* Table */
|
|
163
|
-
table {
|
|
164
|
-
width: 100%;
|
|
165
|
-
margin: 1.5em 0;
|
|
166
|
-
border-collapse: collapse;
|
|
167
|
-
font-size: 15px;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
th, td {
|
|
171
|
-
padding: 10px 14px;
|
|
172
|
-
text-align: left;
|
|
173
|
-
border-bottom: 1px solid var(--border-light);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
th {
|
|
177
|
-
font-weight: 600;
|
|
178
|
-
color: var(--text-primary);
|
|
179
|
-
border-bottom-color: var(--border-color);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
tr:last-child td {
|
|
183
|
-
border-bottom: none;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/* Lists */
|
|
187
|
-
ul, ol {
|
|
188
|
-
margin: 1em 0;
|
|
189
|
-
padding-left: 1.5em;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
li {
|
|
193
|
-
margin: 0.4em 0;
|
|
194
|
-
line-height: 1.75;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
li::marker {
|
|
198
|
-
color: var(--text-secondary);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/* Images */
|
|
202
|
-
img {
|
|
203
|
-
max-width: 100%;
|
|
204
|
-
height: auto;
|
|
205
|
-
display: block;
|
|
206
|
-
margin: 1.5em auto;
|
|
207
|
-
border-radius: 8px;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/* Divider */
|
|
211
|
-
hr {
|
|
212
|
-
border: none;
|
|
213
|
-
height: 1px;
|
|
214
|
-
background: var(--border-color);
|
|
215
|
-
margin: 2em 0;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/* Print */
|
|
219
|
-
@page {
|
|
220
|
-
size: A4;
|
|
221
|
-
margin: 24mm 20mm;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
@media print {
|
|
225
|
-
.container {
|
|
226
|
-
padding: 0;
|
|
227
|
-
max-width: none;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
h1 { font-size: 24px; }
|
|
231
|
-
h2 { font-size: 18px; }
|
|
232
|
-
|
|
233
|
-
pre {
|
|
234
|
-
background: var(--code-block-bg) !important;
|
|
235
|
-
color: var(--text-primary) !important;
|
|
236
|
-
border: 1px solid var(--border-light);
|
|
237
|
-
-webkit-print-color-adjust: exact;
|
|
238
|
-
print-color-adjust: exact;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
pre code { color: inherit !important; }
|
|
242
|
-
}
|
|
243
|
-
</style>
|
|
244
|
-
{{styles}}
|
|
245
|
-
</head>
|
|
246
|
-
|
|
247
|
-
<body>
|
|
248
|
-
<article class="container">
|
|
249
|
-
{{body}}
|
|
250
|
-
</article>
|
|
251
|
-
</body>
|
|
252
|
-
|
|
253
|
-
</html>
|