@liustack/pagepress 2.0.0 → 3.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 +44 -26
- package/README.zh-CN.md +44 -26
- package/dist/main.js +383 -61
- package/package.json +27 -20
- package/src/templates/default.html +262 -0
- package/src/templates/github.html +326 -0
- package/src/templates/magazine.html +462 -0
package/dist/main.js
CHANGED
|
@@ -3,12 +3,78 @@ 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 { pathToFileURL } from "url";
|
|
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
|
+
import { execFile } from "child_process";
|
|
12
|
+
const pdfTemplates = {
|
|
13
|
+
default: {
|
|
14
|
+
name: "default",
|
|
15
|
+
description: "Clean minimalist design",
|
|
16
|
+
file: "default.html"
|
|
17
|
+
},
|
|
18
|
+
github: {
|
|
19
|
+
name: "github",
|
|
20
|
+
description: "GitHub-flavored markdown style",
|
|
21
|
+
file: "github.html"
|
|
22
|
+
},
|
|
23
|
+
magazine: {
|
|
24
|
+
name: "magazine",
|
|
25
|
+
description: "VOGUE/WIRED premium magazine style",
|
|
26
|
+
file: "magazine.html"
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
function getTemplate(name) {
|
|
30
|
+
const template = pdfTemplates[name];
|
|
31
|
+
if (!template) {
|
|
32
|
+
throw new Error(`Unknown PDF template: ${name}. Available: ${Object.keys(pdfTemplates).join(", ")}`);
|
|
33
|
+
}
|
|
34
|
+
return template;
|
|
35
|
+
}
|
|
36
|
+
const require$2 = createRequire(import.meta.url);
|
|
37
|
+
const MAGAZINE_FONTS = [
|
|
38
|
+
"@fontsource/bebas-neue/400.css",
|
|
39
|
+
"@fontsource/cormorant-garamond/400.css",
|
|
40
|
+
"@fontsource/cormorant-garamond/400-italic.css",
|
|
41
|
+
"@fontsource/cormorant-garamond/600.css",
|
|
42
|
+
"@fontsource/inter/300.css",
|
|
43
|
+
"@fontsource/inter/400.css",
|
|
44
|
+
"@fontsource/inter/500.css"
|
|
45
|
+
];
|
|
46
|
+
function getFontCSS(template) {
|
|
47
|
+
if (template !== "magazine") {
|
|
48
|
+
return "";
|
|
49
|
+
}
|
|
50
|
+
const cssChunks = [];
|
|
51
|
+
for (const fontPath of MAGAZINE_FONTS) {
|
|
52
|
+
try {
|
|
53
|
+
const cssFilePath = require$2.resolve(fontPath);
|
|
54
|
+
let css = fs.readFileSync(cssFilePath, "utf-8");
|
|
55
|
+
const cssDir = path.dirname(cssFilePath);
|
|
56
|
+
css = css.replace(/url\(\.\/files\/([^)]+)\)/g, (match, filename) => {
|
|
57
|
+
const fontFilePath = path.join(cssDir, "files", filename);
|
|
58
|
+
if (fs.existsSync(fontFilePath)) {
|
|
59
|
+
const fontData = fs.readFileSync(fontFilePath);
|
|
60
|
+
const base64 = fontData.toString("base64");
|
|
61
|
+
const ext = path.extname(filename).slice(1);
|
|
62
|
+
const mimeType = ext === "woff2" ? "font/woff2" : `font/${ext}`;
|
|
63
|
+
return `url(data:${mimeType};base64,${base64})`;
|
|
64
|
+
}
|
|
65
|
+
return match;
|
|
66
|
+
});
|
|
67
|
+
cssChunks.push(css);
|
|
68
|
+
} catch (e) {
|
|
69
|
+
console.warn(`Warning: Could not load font ${fontPath}:`, e);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return cssChunks.join("\n");
|
|
73
|
+
}
|
|
74
|
+
const __filename$1 = fileURLToPath(import.meta.url);
|
|
75
|
+
const __dirname$1 = path.dirname(__filename$1);
|
|
76
|
+
const require$1 = createRequire(import.meta.url);
|
|
7
77
|
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;
|
|
12
78
|
function normalizeWaitUntil(value) {
|
|
13
79
|
if (!value) return "networkidle";
|
|
14
80
|
if (WAIT_UNTIL_STATES.has(value)) {
|
|
@@ -23,40 +89,92 @@ function normalizeTimeout(value) {
|
|
|
23
89
|
}
|
|
24
90
|
return value;
|
|
25
91
|
}
|
|
26
|
-
function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
92
|
+
function findTemplatesDir() {
|
|
93
|
+
const candidates = [
|
|
94
|
+
// When running from project root (dev/installed)
|
|
95
|
+
path.resolve(process.cwd(), "src/templates"),
|
|
96
|
+
// Relative to dist/main.js
|
|
97
|
+
path.resolve(__dirname$1, "..", "src", "templates"),
|
|
98
|
+
// Relative to source file location
|
|
99
|
+
path.resolve(__dirname$1, "templates")
|
|
100
|
+
];
|
|
101
|
+
for (const dir of candidates) {
|
|
102
|
+
if (fs.existsSync(dir)) return dir;
|
|
32
103
|
}
|
|
104
|
+
throw new Error(`Templates directory not found. Searched: ${candidates.join(", ")}`);
|
|
33
105
|
}
|
|
34
106
|
async function render(options) {
|
|
35
|
-
const
|
|
36
|
-
const height = options.height ?? DEFAULT_HEIGHT;
|
|
37
|
-
const scale = options.scale ?? 2;
|
|
107
|
+
const template = getTemplate(options.template ?? "default");
|
|
38
108
|
const safeMode = options.safe ?? false;
|
|
39
109
|
const waitUntil = normalizeWaitUntil(options.waitUntil);
|
|
40
110
|
const timeout = normalizeTimeout(options.timeout);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
111
|
+
const templatesDir = findTemplatesDir();
|
|
112
|
+
const templatePath = path.join(templatesDir, template.file);
|
|
113
|
+
if (!fs.existsSync(templatePath)) {
|
|
114
|
+
throw new Error(`Template not found: ${templatePath}`);
|
|
115
|
+
}
|
|
116
|
+
const inputPath = path.resolve(options.input);
|
|
44
117
|
if (options.input.startsWith("http://") || options.input.startsWith("https://")) {
|
|
45
|
-
throw new Error("Remote URL inputs are not supported. Please provide a local
|
|
118
|
+
throw new Error("Remote URL inputs are not supported. Please provide a local Markdown file path.");
|
|
46
119
|
}
|
|
47
|
-
if (!options.input.endsWith(".
|
|
48
|
-
throw new Error(`Unsupported input format: ${options.input}. Only
|
|
120
|
+
if (!options.input.endsWith(".md")) {
|
|
121
|
+
throw new Error(`Unsupported input format: ${options.input}. Only Markdown (.md) files are supported.`);
|
|
49
122
|
}
|
|
50
|
-
const
|
|
51
|
-
const
|
|
123
|
+
const raw = fs.readFileSync(inputPath, "utf-8");
|
|
124
|
+
const { data: frontmatter, content } = matter(raw);
|
|
125
|
+
const hasMermaid = /```mermaid/i.test(content);
|
|
126
|
+
const renderer = new Renderer();
|
|
127
|
+
renderer.code = function({ text, lang }) {
|
|
128
|
+
if (lang === "mermaid") {
|
|
129
|
+
return `<pre class="mermaid">${text}</pre>`;
|
|
130
|
+
}
|
|
131
|
+
const highlighted = lang && hljs.getLanguage(lang) ? hljs.highlight(text, { language: lang }).value : hljs.highlightAuto(text).value;
|
|
132
|
+
const langClass = lang ? `hljs language-${lang}` : "hljs";
|
|
133
|
+
return `<pre><code class="${langClass}">${highlighted}</code></pre>`;
|
|
134
|
+
};
|
|
135
|
+
marked.use({ renderer });
|
|
136
|
+
const bodyHtml = await marked.parse(content);
|
|
137
|
+
const title = frontmatter.title || "Document";
|
|
138
|
+
const isDarkTheme = template.name === "magazine";
|
|
139
|
+
const hljsLightStyles = `
|
|
140
|
+
<style>
|
|
141
|
+
/* highlight.js GitHub Light Theme */
|
|
142
|
+
.hljs { color: #24292e; }
|
|
143
|
+
.hljs-comment, .hljs-quote { color: #6a737d; font-style: italic; }
|
|
144
|
+
.hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-type { color: #d73a49; font-weight: 600; }
|
|
145
|
+
.hljs-string, .hljs-attr, .hljs-symbol, .hljs-bullet, .hljs-addition { color: #032f62; }
|
|
146
|
+
.hljs-number { color: #005cc5; }
|
|
147
|
+
.hljs-title, .hljs-section, .hljs-function .hljs-title { color: #6f42c1; }
|
|
148
|
+
.hljs-built_in, .hljs-name { color: #005cc5; }
|
|
149
|
+
.hljs-class .hljs-title { color: #e36209; }
|
|
150
|
+
.hljs-meta { color: #6f42c1; }
|
|
151
|
+
.hljs-variable, .hljs-template-variable { color: #e36209; }
|
|
152
|
+
.hljs-params { color: #24292e; }
|
|
153
|
+
</style>
|
|
154
|
+
`;
|
|
155
|
+
const hljsDarkStyles = `
|
|
156
|
+
<style>
|
|
157
|
+
/* highlight.js Dark Theme for Magazine */
|
|
158
|
+
.hljs { color: #e8e8e8; }
|
|
159
|
+
.hljs-comment, .hljs-quote { color: #6b7280; font-style: italic; }
|
|
160
|
+
.hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-type { color: #f0abfc; }
|
|
161
|
+
.hljs-string, .hljs-attr, .hljs-symbol, .hljs-bullet, .hljs-addition { color: #86efac; }
|
|
162
|
+
.hljs-number { color: #fcd34d; }
|
|
163
|
+
.hljs-title, .hljs-section, .hljs-function .hljs-title { color: #93c5fd; }
|
|
164
|
+
.hljs-built_in, .hljs-name { color: #93c5fd; }
|
|
165
|
+
.hljs-class .hljs-title { color: #67e8f9; }
|
|
166
|
+
.hljs-meta { color: #f0abfc; }
|
|
167
|
+
.hljs-variable, .hljs-template-variable { color: #fcd34d; }
|
|
168
|
+
.hljs-params { color: #e8e8e8; }
|
|
169
|
+
</style>
|
|
170
|
+
`;
|
|
171
|
+
const hljsStyles = isDarkTheme ? hljsDarkStyles : hljsLightStyles;
|
|
172
|
+
let templateHtml = fs.readFileSync(templatePath, "utf-8");
|
|
173
|
+
const htmlContent = templateHtml.replace(/\{\{title\}\}/g, title).replace(/\{\{body\}\}/g, bodyHtml).replace(/\{\{styles\}\}/g, hljsStyles);
|
|
52
174
|
let browser = null;
|
|
53
175
|
try {
|
|
54
176
|
browser = await chromium.launch({ headless: true });
|
|
55
|
-
const context = await browser.newContext({
|
|
56
|
-
viewport: { width, height },
|
|
57
|
-
deviceScaleFactor: scale,
|
|
58
|
-
javaScriptEnabled: !safeMode
|
|
59
|
-
});
|
|
177
|
+
const context = await browser.newContext({ javaScriptEnabled: !safeMode });
|
|
60
178
|
if (safeMode) {
|
|
61
179
|
await context.route("**/*", (route) => {
|
|
62
180
|
const url = route.request().url();
|
|
@@ -69,38 +187,124 @@ async function render(options) {
|
|
|
69
187
|
const page = await context.newPage();
|
|
70
188
|
await page.goto(pathToFileURL(inputPath).href, { waitUntil: "domcontentloaded", timeout });
|
|
71
189
|
await page.setContent(htmlContent, { waitUntil, timeout });
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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.');
|
|
190
|
+
const fontCSS = getFontCSS(template.name);
|
|
191
|
+
if (fontCSS) {
|
|
192
|
+
await page.addStyleTag({ content: fontCSS });
|
|
87
193
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
194
|
+
if (hasMermaid && !safeMode) {
|
|
195
|
+
const mermaidPath = require$1.resolve("mermaid/dist/mermaid.min.js");
|
|
196
|
+
await page.addScriptTag({ path: mermaidPath });
|
|
197
|
+
const templateName = template.name;
|
|
198
|
+
await page.evaluate((tpl) => {
|
|
199
|
+
const defaultTheme = {
|
|
200
|
+
primaryColor: "#f5f5f7",
|
|
201
|
+
primaryTextColor: "#1d1d1f",
|
|
202
|
+
primaryBorderColor: "#d2d2d7",
|
|
203
|
+
lineColor: "#86868b",
|
|
204
|
+
secondaryColor: "#f5f5f7",
|
|
205
|
+
tertiaryColor: "#ffffff",
|
|
206
|
+
background: "#ffffff",
|
|
207
|
+
mainBkg: "#f5f5f7",
|
|
208
|
+
nodeBorder: "#d2d2d7",
|
|
209
|
+
nodeTextColor: "#1d1d1f",
|
|
210
|
+
clusterBkg: "rgba(0, 0, 0, 0.02)",
|
|
211
|
+
clusterBorder: "rgba(0, 0, 0, 0.1)",
|
|
212
|
+
titleColor: "#1d1d1f"
|
|
213
|
+
};
|
|
214
|
+
const githubTheme = {
|
|
215
|
+
primaryColor: "#f6f8fa",
|
|
216
|
+
primaryTextColor: "#1f2328",
|
|
217
|
+
primaryBorderColor: "#d0d7de",
|
|
218
|
+
lineColor: "#656d76",
|
|
219
|
+
secondaryColor: "#f6f8fa",
|
|
220
|
+
tertiaryColor: "#ffffff",
|
|
221
|
+
background: "#ffffff",
|
|
222
|
+
mainBkg: "#f6f8fa",
|
|
223
|
+
nodeBorder: "#d0d7de",
|
|
224
|
+
nodeTextColor: "#1f2328",
|
|
225
|
+
clusterBkg: "rgba(208, 215, 222, 0.2)",
|
|
226
|
+
clusterBorder: "#d0d7de",
|
|
227
|
+
titleColor: "#1f2328"
|
|
228
|
+
};
|
|
229
|
+
const magazineTheme = {
|
|
230
|
+
primaryColor: "#2a2a2a",
|
|
231
|
+
primaryTextColor: "#ffffff",
|
|
232
|
+
primaryBorderColor: "#c41e3a",
|
|
233
|
+
lineColor: "#666666",
|
|
234
|
+
secondaryColor: "#1a1a1a",
|
|
235
|
+
tertiaryColor: "#0f0f0f",
|
|
236
|
+
background: "#0f0f0f",
|
|
237
|
+
mainBkg: "#2a2a2a",
|
|
238
|
+
nodeBorder: "#c41e3a",
|
|
239
|
+
nodeTextColor: "#ffffff",
|
|
240
|
+
clusterBkg: "rgba(196, 30, 58, 0.1)",
|
|
241
|
+
clusterBorder: "rgba(196, 30, 58, 0.4)",
|
|
242
|
+
titleColor: "#ffffff"
|
|
243
|
+
};
|
|
244
|
+
const themes = {
|
|
245
|
+
default: defaultTheme,
|
|
246
|
+
github: githubTheme,
|
|
247
|
+
magazine: magazineTheme
|
|
248
|
+
};
|
|
249
|
+
const activeTheme = themes[tpl] || defaultTheme;
|
|
250
|
+
window.mermaid.initialize({
|
|
251
|
+
startOnLoad: false,
|
|
252
|
+
theme: "base",
|
|
253
|
+
themeVariables: {
|
|
254
|
+
...activeTheme,
|
|
255
|
+
fontFamily: '"SF Mono", "Fira Code", Menlo, monospace',
|
|
256
|
+
fontSize: "13px"
|
|
257
|
+
},
|
|
258
|
+
flowchart: {
|
|
259
|
+
curve: "basis",
|
|
260
|
+
nodeSpacing: 40,
|
|
261
|
+
rankSpacing: 50,
|
|
262
|
+
htmlLabels: true,
|
|
263
|
+
useMaxWidth: true,
|
|
264
|
+
subGraphTitleMargin: { top: 15, bottom: 15 },
|
|
265
|
+
padding: 20
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
}, templateName);
|
|
269
|
+
await page.evaluate(() => {
|
|
270
|
+
return window.mermaid.run();
|
|
271
|
+
});
|
|
272
|
+
await page.waitForSelector(".mermaid svg", { timeout: 5e3 });
|
|
273
|
+
await page.waitForTimeout(300);
|
|
274
|
+
await page.evaluate(() => {
|
|
275
|
+
document.querySelectorAll(".node rect").forEach((rect) => {
|
|
276
|
+
rect.setAttribute("rx", "12");
|
|
277
|
+
rect.setAttribute("ry", "12");
|
|
278
|
+
});
|
|
279
|
+
document.querySelectorAll(".cluster rect").forEach((rect) => {
|
|
280
|
+
rect.setAttribute("rx", "16");
|
|
281
|
+
rect.setAttribute("ry", "16");
|
|
282
|
+
});
|
|
283
|
+
document.querySelectorAll(".cluster-label").forEach((label) => {
|
|
284
|
+
const transform = label.getAttribute("transform");
|
|
285
|
+
if (transform) {
|
|
286
|
+
const match = transform.match(/translate\(([\d.]+),\s*([\d.]+)\)/);
|
|
287
|
+
if (match) {
|
|
288
|
+
const x = parseFloat(match[1]);
|
|
289
|
+
const y = parseFloat(match[2]) + 8;
|
|
290
|
+
label.setAttribute("transform", `translate(${x}, ${y})`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
});
|
|
91
295
|
}
|
|
296
|
+
await page.waitForTimeout(200);
|
|
92
297
|
const outputPath = path.resolve(options.output);
|
|
93
|
-
await page.
|
|
298
|
+
await page.pdf({
|
|
94
299
|
path: outputPath,
|
|
95
|
-
|
|
96
|
-
|
|
300
|
+
format: "A4",
|
|
301
|
+
margin: { top: "0", right: "0", bottom: "0", left: "0" },
|
|
302
|
+
printBackground: true
|
|
97
303
|
});
|
|
98
304
|
return {
|
|
99
|
-
|
|
305
|
+
pdfPath: outputPath,
|
|
100
306
|
meta: {
|
|
101
|
-
|
|
102
|
-
height,
|
|
103
|
-
deviceScaleFactor: scale,
|
|
307
|
+
template: template.name,
|
|
104
308
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
105
309
|
}
|
|
106
310
|
};
|
|
@@ -110,9 +314,107 @@ async function render(options) {
|
|
|
110
314
|
}
|
|
111
315
|
}
|
|
112
316
|
}
|
|
113
|
-
const
|
|
114
|
-
|
|
317
|
+
const PACKAGE_NAME = "@liustack/pagepress";
|
|
318
|
+
function normalizeVersion(version) {
|
|
319
|
+
const normalized = version.trim().replace(/^v/i, "").split("-")[0];
|
|
320
|
+
if (!normalized) {
|
|
321
|
+
throw new Error(`Invalid version: ${version}`);
|
|
322
|
+
}
|
|
323
|
+
return normalized;
|
|
324
|
+
}
|
|
325
|
+
function parseVersion(version) {
|
|
326
|
+
return normalizeVersion(version).split(".").map((segment) => {
|
|
327
|
+
const value = Number.parseInt(segment, 10);
|
|
328
|
+
if (!Number.isFinite(value)) {
|
|
329
|
+
throw new Error(`Invalid version segment: ${segment}`);
|
|
330
|
+
}
|
|
331
|
+
return value;
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
function compareVersions(left, right) {
|
|
335
|
+
const leftParts = parseVersion(left);
|
|
336
|
+
const rightParts = parseVersion(right);
|
|
337
|
+
const length = Math.max(leftParts.length, rightParts.length);
|
|
338
|
+
for (let index = 0; index < length; index += 1) {
|
|
339
|
+
const leftValue = leftParts[index] ?? 0;
|
|
340
|
+
const rightValue = rightParts[index] ?? 0;
|
|
341
|
+
if (leftValue < rightValue) return -1;
|
|
342
|
+
if (leftValue > rightValue) return 1;
|
|
343
|
+
}
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
346
|
+
const runCommand = (command, args) => new Promise((resolve, reject) => {
|
|
347
|
+
execFile(command, args, { encoding: "utf-8" }, (error, stdout, stderr) => {
|
|
348
|
+
if (error) {
|
|
349
|
+
const message = stderr.trim() || error.message;
|
|
350
|
+
reject(new Error(message));
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
resolve(stdout.trim());
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
const resolvePlaywrightCliPath = () => {
|
|
357
|
+
const require2 = createRequire(import.meta.url);
|
|
358
|
+
const playwrightPackagePath = require2.resolve("playwright/package.json");
|
|
359
|
+
const playwrightPackage = JSON.parse(fs.readFileSync(playwrightPackagePath, "utf-8"));
|
|
360
|
+
const cliRelativePath = playwrightPackage.bin?.playwright;
|
|
361
|
+
if (!cliRelativePath) {
|
|
362
|
+
throw new Error("Unable to resolve Playwright CLI.");
|
|
363
|
+
}
|
|
364
|
+
return path.resolve(path.dirname(playwrightPackagePath), cliRelativePath);
|
|
365
|
+
};
|
|
366
|
+
async function checkForUpdate({
|
|
367
|
+
currentVersion,
|
|
368
|
+
packageName = PACKAGE_NAME,
|
|
369
|
+
run = runCommand
|
|
370
|
+
}) {
|
|
371
|
+
const normalizedCurrentVersion = normalizeVersion(currentVersion);
|
|
115
372
|
try {
|
|
373
|
+
const latestVersion = normalizeVersion(await run("npm", ["view", packageName, "version"]));
|
|
374
|
+
return {
|
|
375
|
+
packageName,
|
|
376
|
+
currentVersion: normalizedCurrentVersion,
|
|
377
|
+
latestVersion,
|
|
378
|
+
updateAvailable: compareVersions(normalizedCurrentVersion, latestVersion) < 0,
|
|
379
|
+
checked: true
|
|
380
|
+
};
|
|
381
|
+
} catch (error) {
|
|
382
|
+
return {
|
|
383
|
+
packageName,
|
|
384
|
+
currentVersion: normalizedCurrentVersion,
|
|
385
|
+
latestVersion: null,
|
|
386
|
+
updateAvailable: false,
|
|
387
|
+
checked: false,
|
|
388
|
+
error: error instanceof Error ? error.message : String(error)
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function createSelfUpdater(run = runCommand, resolvePlaywrightCli = resolvePlaywrightCliPath) {
|
|
393
|
+
return async ({
|
|
394
|
+
currentVersion,
|
|
395
|
+
packageName = PACKAGE_NAME
|
|
396
|
+
}) => {
|
|
397
|
+
const updateInfo = await checkForUpdate({ currentVersion, packageName, run });
|
|
398
|
+
if (!updateInfo.checked) {
|
|
399
|
+
throw new Error(`Unable to check for updates: ${updateInfo.error ?? "unknown error"}`);
|
|
400
|
+
}
|
|
401
|
+
if (!updateInfo.updateAvailable) {
|
|
402
|
+
return {
|
|
403
|
+
...updateInfo,
|
|
404
|
+
updated: false
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
await run("npm", ["install", "-g", `${packageName}@latest`]);
|
|
408
|
+
await run(process.execPath, [resolvePlaywrightCli(), "install", "chromium"]);
|
|
409
|
+
return {
|
|
410
|
+
...updateInfo,
|
|
411
|
+
updated: true
|
|
412
|
+
};
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
async function runRenderCommand() {
|
|
416
|
+
const program = new Command();
|
|
417
|
+
program.name("pagepress").description("Render local Markdown files to PDF").version("3.0.0").requiredOption("-i, --input <path>", "Input Markdown 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) => {
|
|
116
418
|
const timeout = Number.parseInt(options.timeout, 10);
|
|
117
419
|
if (!Number.isFinite(timeout) || timeout < 0) {
|
|
118
420
|
throw new Error("Invalid --timeout value. Use a non-negative integer in milliseconds.");
|
|
@@ -120,17 +422,37 @@ program.name("pagepress").description("Generate one-page visual reports, dashboa
|
|
|
120
422
|
const result = await render({
|
|
121
423
|
input: options.input,
|
|
122
424
|
output: options.output,
|
|
123
|
-
|
|
124
|
-
height: Number.parseInt(options.height, 10),
|
|
125
|
-
scale: Number.parseFloat(options.scale),
|
|
425
|
+
template: options.template,
|
|
126
426
|
waitUntil: options.waitUntil,
|
|
127
427
|
timeout,
|
|
128
428
|
safe: options.safe
|
|
129
429
|
});
|
|
130
430
|
console.log(JSON.stringify(result, null, 2));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
431
|
+
});
|
|
432
|
+
await program.parseAsync(process.argv);
|
|
433
|
+
}
|
|
434
|
+
async function runCheckUpdateCommand() {
|
|
435
|
+
const result = await checkForUpdate({ currentVersion: "3.0.0" });
|
|
436
|
+
console.log(JSON.stringify(result, null, 2));
|
|
437
|
+
}
|
|
438
|
+
async function runSelfUpdateCommand() {
|
|
439
|
+
const selfUpdate = createSelfUpdater();
|
|
440
|
+
const result = await selfUpdate({ currentVersion: "3.0.0" });
|
|
441
|
+
console.log(JSON.stringify(result, null, 2));
|
|
442
|
+
}
|
|
443
|
+
async function main() {
|
|
444
|
+
const command = process.argv[2];
|
|
445
|
+
if (command === "check-update") {
|
|
446
|
+
await runCheckUpdateCommand();
|
|
447
|
+
return;
|
|
134
448
|
}
|
|
449
|
+
if (command === "self-update") {
|
|
450
|
+
await runSelfUpdateCommand();
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
await runRenderCommand();
|
|
454
|
+
}
|
|
455
|
+
main().catch((error) => {
|
|
456
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
457
|
+
process.exit(1);
|
|
135
458
|
});
|
|
136
|
-
program.parse();
|
package/package.json
CHANGED
|
@@ -1,32 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liustack/pagepress",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "CLI tool to
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "CLI tool to render local Markdown files into polished PDF documents",
|
|
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
|
-
},
|
|
19
9
|
"files": [
|
|
20
|
-
"dist"
|
|
10
|
+
"dist",
|
|
11
|
+
"src/templates"
|
|
21
12
|
],
|
|
22
13
|
"keywords": [
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"dashboard",
|
|
27
|
-
"screenshot",
|
|
14
|
+
"pdf",
|
|
15
|
+
"markdown-to-pdf",
|
|
16
|
+
"pdf-report",
|
|
28
17
|
"cli",
|
|
29
|
-
"playwright"
|
|
18
|
+
"playwright",
|
|
19
|
+
"mermaid"
|
|
30
20
|
],
|
|
31
21
|
"author": "Leon Liu",
|
|
32
22
|
"license": "MIT",
|
|
@@ -42,7 +32,15 @@
|
|
|
42
32
|
"node": ">=18"
|
|
43
33
|
},
|
|
44
34
|
"dependencies": {
|
|
35
|
+
"@fontsource/bebas-neue": "^5.2.7",
|
|
36
|
+
"@fontsource/cormorant-garamond": "^5.2.11",
|
|
37
|
+
"@fontsource/inter": "^5.2.8",
|
|
45
38
|
"commander": "^13.1.0",
|
|
39
|
+
"gray-matter": "^4.0.3",
|
|
40
|
+
"highlight.js": "^11.11.1",
|
|
41
|
+
"marked": "^15.0.12",
|
|
42
|
+
"marked-highlight": "^2.2.3",
|
|
43
|
+
"mermaid": "^11.12.2",
|
|
46
44
|
"playwright": "^1.58.1"
|
|
47
45
|
},
|
|
48
46
|
"devDependencies": {
|
|
@@ -52,5 +50,14 @@
|
|
|
52
50
|
"vite": "^6.4.1",
|
|
53
51
|
"vite-plugin-node": "^5.0.1",
|
|
54
52
|
"vitest": "^4.0.18"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"dev": "vite build --watch",
|
|
56
|
+
"build": "vite build",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest",
|
|
60
|
+
"test:coverage": "vitest run --coverage",
|
|
61
|
+
"docs:list": "node scripts/docs-list.js"
|
|
55
62
|
}
|
|
56
|
-
}
|
|
63
|
+
}
|