@liustack/briefpress 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/LICENSE +21 -0
- package/README.md +70 -0
- package/README.zh-CN.md +68 -0
- package/dist/main.js +136 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Leon Liu (liustack)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# BriefPress
|
|
2
|
+
|
|
3
|
+
Generate one-page visual reports, dashboards, and infographics from HTML — powered by Playwright + Chromium.
|
|
4
|
+
|
|
5
|
+
中文说明请见:[README.zh-CN.md](README.zh-CN.md)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
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
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @liustack/briefpress
|
|
19
|
+
npx playwright install chromium
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or run with `npx`:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @liustack/briefpress [options]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Generate infographic (default 1200×630 @2x)
|
|
32
|
+
briefpress -i report.html -o report.png
|
|
33
|
+
|
|
34
|
+
# Custom dimensions for a tall infographic
|
|
35
|
+
briefpress -i report.html -o report.png -w 1080 -h 1920
|
|
36
|
+
|
|
37
|
+
# High-DPI poster
|
|
38
|
+
briefpress -i poster.html -o poster.png -w 1200 -h 1500 --scale 3
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## HTML Requirements
|
|
42
|
+
|
|
43
|
+
Your HTML must include a `<div id="container">` — BriefPress clips the screenshot to this element's bounding box.
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<body>
|
|
47
|
+
<div id="container">
|
|
48
|
+
<!-- your content here -->
|
|
49
|
+
</div>
|
|
50
|
+
</body>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Options
|
|
54
|
+
|
|
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)
|
|
60
|
+
- `--wait-until <state>` `load | domcontentloaded | networkidle`
|
|
61
|
+
- `--timeout <ms>` timeout in milliseconds
|
|
62
|
+
- `--safe` disable external network requests and JavaScript execution
|
|
63
|
+
|
|
64
|
+
## AI Agent Skill
|
|
65
|
+
|
|
66
|
+
- [briefpress/SKILL.md](skills/briefpress/SKILL.md)
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
MIT
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# BriefPress
|
|
2
|
+
|
|
3
|
+
从 HTML 生成单页可视化报告、仪表盘和信息图 — 基于 Playwright + Chromium。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- HTML → PNG 截图管线
|
|
8
|
+
- 自定义视口尺寸(`--width`、`--height`)
|
|
9
|
+
- 高 DPI 输出,支持最高 4 倍缩放(`--scale`)
|
|
10
|
+
- 仅支持本地 HTML 文件输入(`.html`)
|
|
11
|
+
- 基于 Playwright + Chromium 的稳定渲染引擎
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g @liustack/briefpress
|
|
17
|
+
npx playwright install chromium
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
或使用 `npx`:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @liustack/briefpress [options]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 用法
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# 生成信息图(默认 1200×630 @2x)
|
|
30
|
+
briefpress -i report.html -o report.png
|
|
31
|
+
|
|
32
|
+
# 自定义尺寸的长信息图
|
|
33
|
+
briefpress -i report.html -o report.png -w 1080 -h 1920
|
|
34
|
+
|
|
35
|
+
# 高 DPI 海报
|
|
36
|
+
briefpress -i poster.html -o poster.png -w 1200 -h 1500 --scale 3
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## HTML 要求
|
|
40
|
+
|
|
41
|
+
HTML 中必须包含 `<div id="container">` — BriefPress 会根据该元素的边界框裁剪截图。
|
|
42
|
+
|
|
43
|
+
```html
|
|
44
|
+
<body>
|
|
45
|
+
<div id="container">
|
|
46
|
+
<!-- 你的内容 -->
|
|
47
|
+
</div>
|
|
48
|
+
</body>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 参数
|
|
52
|
+
|
|
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)
|
|
58
|
+
- `--wait-until <state>` `load | domcontentloaded | networkidle`
|
|
59
|
+
- `--timeout <ms>` 超时时间(毫秒)
|
|
60
|
+
- `--safe` 禁用外部网络请求和 JavaScript 执行
|
|
61
|
+
|
|
62
|
+
## AI Agent Skill
|
|
63
|
+
|
|
64
|
+
- [briefpress/SKILL.md](skills/briefpress/SKILL.md)
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
MIT
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { chromium } from "playwright";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { pathToFileURL } from "url";
|
|
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;
|
|
12
|
+
function normalizeWaitUntil(value) {
|
|
13
|
+
if (!value) return "networkidle";
|
|
14
|
+
if (WAIT_UNTIL_STATES.has(value)) {
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
throw new Error(`Invalid waitUntil: ${value}. Allowed: load, domcontentloaded, networkidle.`);
|
|
18
|
+
}
|
|
19
|
+
function normalizeTimeout(value) {
|
|
20
|
+
if (value === void 0) return 3e4;
|
|
21
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
22
|
+
throw new Error("Invalid timeout. Use a non-negative number of milliseconds.");
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
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}.`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function render(options) {
|
|
35
|
+
const width = options.width ?? DEFAULT_WIDTH;
|
|
36
|
+
const height = options.height ?? DEFAULT_HEIGHT;
|
|
37
|
+
const scale = options.scale ?? 2;
|
|
38
|
+
const safeMode = options.safe ?? false;
|
|
39
|
+
const waitUntil = normalizeWaitUntil(options.waitUntil);
|
|
40
|
+
const timeout = normalizeTimeout(options.timeout);
|
|
41
|
+
validateRange("Width", width, 1, 1e4);
|
|
42
|
+
validateRange("Height", height, 1, 1e4);
|
|
43
|
+
validateRange("Scale", scale, MIN_SCALE, MAX_SCALE);
|
|
44
|
+
if (options.input.startsWith("http://") || options.input.startsWith("https://")) {
|
|
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.`);
|
|
49
|
+
}
|
|
50
|
+
const inputPath = path.resolve(options.input);
|
|
51
|
+
const htmlContent = fs.readFileSync(inputPath, "utf-8");
|
|
52
|
+
let browser = null;
|
|
53
|
+
try {
|
|
54
|
+
browser = await chromium.launch({ headless: true });
|
|
55
|
+
const context = await browser.newContext({
|
|
56
|
+
viewport: { width, height },
|
|
57
|
+
deviceScaleFactor: scale,
|
|
58
|
+
javaScriptEnabled: !safeMode
|
|
59
|
+
});
|
|
60
|
+
if (safeMode) {
|
|
61
|
+
await context.route("**/*", (route) => {
|
|
62
|
+
const url = route.request().url();
|
|
63
|
+
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
64
|
+
return route.abort();
|
|
65
|
+
}
|
|
66
|
+
return route.continue();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const page = await context.newPage();
|
|
70
|
+
await page.goto(pathToFileURL(inputPath).href, { waitUntil: "domcontentloaded", timeout });
|
|
71
|
+
await page.setContent(htmlContent, { waitUntil, timeout });
|
|
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.');
|
|
87
|
+
}
|
|
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.");
|
|
91
|
+
}
|
|
92
|
+
const outputPath = path.resolve(options.output);
|
|
93
|
+
await page.screenshot({
|
|
94
|
+
path: outputPath,
|
|
95
|
+
type: "png",
|
|
96
|
+
clip: { x: box.x, y: box.y, width: box.width, height: box.height }
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
pngPath: outputPath,
|
|
100
|
+
meta: {
|
|
101
|
+
width,
|
|
102
|
+
height,
|
|
103
|
+
deviceScaleFactor: scale,
|
|
104
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
} finally {
|
|
108
|
+
if (browser) {
|
|
109
|
+
await browser.close();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const program = new Command();
|
|
114
|
+
program.name("briefpress").description("Generate one-page visual reports, dashboards, and infographics from HTML").version("3.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) => {
|
|
115
|
+
try {
|
|
116
|
+
const timeout = Number.parseInt(options.timeout, 10);
|
|
117
|
+
if (!Number.isFinite(timeout) || timeout < 0) {
|
|
118
|
+
throw new Error("Invalid --timeout value. Use a non-negative integer in milliseconds.");
|
|
119
|
+
}
|
|
120
|
+
const result = await render({
|
|
121
|
+
input: options.input,
|
|
122
|
+
output: options.output,
|
|
123
|
+
width: Number.parseInt(options.width, 10),
|
|
124
|
+
height: Number.parseInt(options.height, 10),
|
|
125
|
+
scale: Number.parseFloat(options.scale),
|
|
126
|
+
waitUntil: options.waitUntil,
|
|
127
|
+
timeout,
|
|
128
|
+
safe: options.safe
|
|
129
|
+
});
|
|
130
|
+
console.log(JSON.stringify(result, null, 2));
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@liustack/briefpress",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "CLI tool to generate one-page visual briefs, infographics, reports, and dashboards from HTML",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"briefpress": "./dist/main.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"infographic",
|
|
14
|
+
"html-to-png",
|
|
15
|
+
"visual-report",
|
|
16
|
+
"dashboard",
|
|
17
|
+
"screenshot",
|
|
18
|
+
"cli",
|
|
19
|
+
"playwright"
|
|
20
|
+
],
|
|
21
|
+
"author": "Leon Liu",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/liustack/briefpress.git"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/liustack/briefpress/issues"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/liustack/briefpress#readme",
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"commander": "^13.1.0",
|
|
36
|
+
"playwright": "^1.58.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^22.19.7",
|
|
40
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
41
|
+
"typescript": "^5.9.3",
|
|
42
|
+
"vite": "^6.4.1",
|
|
43
|
+
"vite-plugin-node": "^5.0.1",
|
|
44
|
+
"vitest": "^4.0.18"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"dev": "vite build --watch",
|
|
48
|
+
"build": "vite build",
|
|
49
|
+
"typecheck": "tsc --noEmit",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:watch": "vitest",
|
|
52
|
+
"test:coverage": "vitest run --coverage",
|
|
53
|
+
"docs:list": "node scripts/docs-list.js"
|
|
54
|
+
}
|
|
55
|
+
}
|