@fulgur-rs/cli 0.5.12 → 0.5.14
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 +197 -0
- package/bin/fulgur +51 -0
- package/package.json +8 -12
- package/install.js +0 -89
package/README.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# fulgur
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@fulgur-rs/cli)
|
|
4
|
+
[](https://github.com/fulgur-rs/fulgur/blob/main/LICENSE-MIT)
|
|
5
|
+
[](https://github.com/fulgur-rs/fulgur/actions/workflows/ci.yml)
|
|
6
|
+
[](https://codecov.io/gh/fulgur-rs/fulgur)
|
|
7
|
+
|
|
8
|
+
A modern, lightweight successor to wkhtmltopdf. Converts HTML/CSS to PDF without
|
|
9
|
+
a browser engine. Single binary, instant cold start, deterministic output.
|
|
10
|
+
|
|
11
|
+
## Why fulgur?
|
|
12
|
+
|
|
13
|
+
- **No browser required.** No Chromium, no WebKit. Just a single native binary.
|
|
14
|
+
- **Low memory footprint.** Designed for server-side batch generation.
|
|
15
|
+
- **Deterministic output.** Identical input produces byte-identical PDFs — safe
|
|
16
|
+
for CI/CD and reproducible pipelines.
|
|
17
|
+
- **Template + JSON data.** Feed an HTML template and a JSON file to generate
|
|
18
|
+
PDFs at scale. Built-in [MiniJinja](https://github.com/mitsuhiko/minijinja)
|
|
19
|
+
engine.
|
|
20
|
+
- **Offline by design.** No network access. Fonts, images, and CSS must be
|
|
21
|
+
explicitly bundled.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Global install
|
|
27
|
+
npm i -g @fulgur-rs/cli
|
|
28
|
+
|
|
29
|
+
# Or run ad-hoc without installing
|
|
30
|
+
npx @fulgur-rs/cli render input.html -o output.pdf
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
fulgur render input.html -o output.pdf
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Commands
|
|
40
|
+
|
|
41
|
+
- [`fulgur render`](#fulgur-render) — convert HTML/CSS to PDF
|
|
42
|
+
- [`fulgur template schema`](#fulgur-template-schema) — extract JSON Schema from
|
|
43
|
+
a template (for AI-driven data generation)
|
|
44
|
+
|
|
45
|
+
### `fulgur render`
|
|
46
|
+
|
|
47
|
+
Render an HTML document (optionally a MiniJinja template with `--data`) to PDF.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Basic
|
|
51
|
+
fulgur render input.html -o output.pdf
|
|
52
|
+
|
|
53
|
+
# Read HTML from stdin, write PDF to stdout
|
|
54
|
+
cat input.html | fulgur render --stdin -o -
|
|
55
|
+
|
|
56
|
+
# Page size and margins
|
|
57
|
+
fulgur render input.html -o output.pdf --size A4 --landscape --margin "20 30"
|
|
58
|
+
|
|
59
|
+
# Bundle fonts, CSS, and images
|
|
60
|
+
fulgur render input.html -o output.pdf \
|
|
61
|
+
-f fonts/NotoSansJP.ttf \
|
|
62
|
+
--css style.css \
|
|
63
|
+
-i logo.png=assets/logo.png
|
|
64
|
+
|
|
65
|
+
# PDF metadata and bookmarks
|
|
66
|
+
fulgur render input.html -o output.pdf \
|
|
67
|
+
--title "Quarterly Report" \
|
|
68
|
+
--author "Acme Corp" \
|
|
69
|
+
--language en \
|
|
70
|
+
--bookmarks
|
|
71
|
+
|
|
72
|
+
# Template + JSON data
|
|
73
|
+
fulgur render template.html -o invoice.pdf -d data.json
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
| Option | Description |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `-o, --output <PATH>` | Output PDF path (required; use `-` for stdout) |
|
|
79
|
+
| `--stdin` | Read HTML from stdin |
|
|
80
|
+
| `-s, --size <SIZE>` | Page size: `A4`, `Letter`, `A3` (default: `A4`) |
|
|
81
|
+
| `-l, --landscape` | Landscape orientation |
|
|
82
|
+
| `--margin <SPEC>` | Margins in mm (CSS shorthand: `"20"`, `"20 30"`, `"10 20 30"`, `"10 20 30 40"`) |
|
|
83
|
+
| `-f, --font <FILE>` | Font file to bundle (repeatable; TTF / OTF / TTC / WOFF2) |
|
|
84
|
+
| `--css <FILE>` | External CSS file (repeatable) |
|
|
85
|
+
| `-i, --image <NAME=PATH>` | Image file to bundle (repeatable) |
|
|
86
|
+
| `-d, --data <FILE>` | JSON data for template rendering (use `-` for stdin) |
|
|
87
|
+
| `--bookmarks` | Generate PDF bookmarks from `h1`–`h6` |
|
|
88
|
+
| `--title`, `--author`, `--description`, `--keyword`, `--language`, `--creator`, `--producer`, `--creation-date` | PDF metadata |
|
|
89
|
+
|
|
90
|
+
### `fulgur template schema`
|
|
91
|
+
|
|
92
|
+
Extract a JSON Schema from a MiniJinja HTML template. The CLI analyzes template
|
|
93
|
+
syntax to infer variable names and types; with `--data`, it uses a sample JSON
|
|
94
|
+
file for more precise inference.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Infer schema from template syntax alone
|
|
98
|
+
fulgur template schema template.html -o schema.json
|
|
99
|
+
|
|
100
|
+
# Sharpen inference with a sample data file
|
|
101
|
+
fulgur template schema template.html -d sample.json -o schema.json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This is primarily intended for AI agents and tooling that need to know the
|
|
105
|
+
shape of the JSON data a template expects — see [Template engine](#template-engine)
|
|
106
|
+
below.
|
|
107
|
+
|
|
108
|
+
## Template engine
|
|
109
|
+
|
|
110
|
+
fulgur ships with [MiniJinja](https://github.com/mitsuhiko/minijinja). Supply an
|
|
111
|
+
HTML template and a JSON data file to `fulgur render -d`, and the template is
|
|
112
|
+
expanded before rendering.
|
|
113
|
+
|
|
114
|
+
`template.html`:
|
|
115
|
+
|
|
116
|
+
```html
|
|
117
|
+
<h1>Invoice #{{ invoice_number }}</h1>
|
|
118
|
+
<p>Bill to: {{ customer.name }}</p>
|
|
119
|
+
<table>
|
|
120
|
+
<thead><tr><th>Item</th><th>Price</th></tr></thead>
|
|
121
|
+
<tbody>
|
|
122
|
+
{% for item in items %}
|
|
123
|
+
<tr><td>{{ item.name }}</td><td>{{ item.price }}</td></tr>
|
|
124
|
+
{% endfor %}
|
|
125
|
+
</tbody>
|
|
126
|
+
</table>
|
|
127
|
+
<p>Total: {{ items | map(attribute="price") | sum }}</p>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`data.json`:
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"invoice_number": "2026-001",
|
|
135
|
+
"customer": { "name": "Acme Corp" },
|
|
136
|
+
"items": [
|
|
137
|
+
{ "name": "Widget", "price": 10 },
|
|
138
|
+
{ "name": "Gadget", "price": 25 }
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
fulgur render template.html -o invoice.pdf -d data.json
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Designer + agent workflow
|
|
148
|
+
|
|
149
|
+
The template / data split is a deliberate separation of concerns that works
|
|
150
|
+
well for AI-agent-driven document generation:
|
|
151
|
+
|
|
152
|
+
- **Humans (or designers) author the template.** They control layout,
|
|
153
|
+
typography, and branding in familiar HTML/CSS.
|
|
154
|
+
- **AI agents produce the data.** Given a JSON Schema — which
|
|
155
|
+
`fulgur template schema` can extract directly from the template — an agent
|
|
156
|
+
can populate the structured payload without touching the presentation layer.
|
|
157
|
+
|
|
158
|
+
The result: agents never produce malformed HTML, and designers never need to
|
|
159
|
+
review generated markup. Each side stays on its own contract.
|
|
160
|
+
|
|
161
|
+
## Supported platforms
|
|
162
|
+
|
|
163
|
+
| OS | Arch | Package |
|
|
164
|
+
|---|---|---|
|
|
165
|
+
| Linux (glibc) | x64 | `@fulgur-rs/cli-linux-x64` |
|
|
166
|
+
| Linux (musl / Alpine) | x64 | `@fulgur-rs/cli-linux-x64-musl` |
|
|
167
|
+
| Linux | arm64 | `@fulgur-rs/cli-linux-arm64` |
|
|
168
|
+
| macOS | arm64 (Apple Silicon) | `@fulgur-rs/cli-darwin-arm64` |
|
|
169
|
+
| macOS | x64 (Intel) | `@fulgur-rs/cli-darwin-x64` |
|
|
170
|
+
| Windows | x64 | `@fulgur-rs/cli-win32-x64` |
|
|
171
|
+
|
|
172
|
+
## How it works (distribution)
|
|
173
|
+
|
|
174
|
+
`@fulgur-rs/cli` is a thin meta package. The actual native binary lives in one
|
|
175
|
+
of the `@fulgur-rs/cli-<platform>` packages above, all declared as
|
|
176
|
+
[`optionalDependencies`](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#optionaldependencies).
|
|
177
|
+
npm only installs the one that matches the current `os` / `cpu` / libc, so
|
|
178
|
+
there is no per-platform bloat and no cross-compilation at install time.
|
|
179
|
+
|
|
180
|
+
The `bin/fulgur` entry is a small JavaScript shim that, at run time, resolves
|
|
181
|
+
the platform package via `require.resolve` and execs its native binary. There
|
|
182
|
+
is no `postinstall` step, so `npx @fulgur-rs/cli` works on the very first run.
|
|
183
|
+
If you install with `--ignore-optional` or on an unsupported platform, the
|
|
184
|
+
shim exits with a clear error message.
|
|
185
|
+
|
|
186
|
+
## Links
|
|
187
|
+
|
|
188
|
+
- [GitHub repository](https://github.com/fulgur-rs/fulgur)
|
|
189
|
+
- [Issue tracker](https://github.com/fulgur-rs/fulgur/issues)
|
|
190
|
+
- [Full documentation (README)](https://github.com/fulgur-rs/fulgur#readme)
|
|
191
|
+
- [CSS property support reference](https://github.com/fulgur-rs/fulgur/blob/main/docs/css-support.md)
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
Dual-licensed under either of [MIT](https://github.com/fulgur-rs/fulgur/blob/main/LICENSE-MIT)
|
|
196
|
+
or [Apache-2.0](https://github.com/fulgur-rs/fulgur/blob/main/LICENSE-APACHE)
|
|
197
|
+
at your option.
|
package/bin/fulgur
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawnSync } = require('child_process');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
|
|
8
|
+
const PLATFORMS = {
|
|
9
|
+
'linux-x64': { pkg: '@fulgur-rs/cli-linux-x64', bin: 'fulgur' },
|
|
10
|
+
'linux-x64-musl': { pkg: '@fulgur-rs/cli-linux-x64-musl', bin: 'fulgur' },
|
|
11
|
+
'linux-arm64': { pkg: '@fulgur-rs/cli-linux-arm64', bin: 'fulgur' },
|
|
12
|
+
'darwin-arm64': { pkg: '@fulgur-rs/cli-darwin-arm64', bin: 'fulgur' },
|
|
13
|
+
'darwin-x64': { pkg: '@fulgur-rs/cli-darwin-x64', bin: 'fulgur' },
|
|
14
|
+
'win32-x64': { pkg: '@fulgur-rs/cli-win32-x64', bin: 'fulgur.exe' },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function isMusl() {
|
|
18
|
+
try { return fs.readFileSync('/proc/self/maps', 'utf8').includes('musl'); }
|
|
19
|
+
catch { return false; }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function detectPlatformKey() {
|
|
23
|
+
const { platform, arch } = process;
|
|
24
|
+
if (platform === 'linux' && arch === 'x64') return isMusl() ? 'linux-x64-musl' : 'linux-x64';
|
|
25
|
+
if (platform === 'linux' && arch === 'arm64') return 'linux-arm64';
|
|
26
|
+
if (platform === 'darwin' && arch === 'arm64') return 'darwin-arm64';
|
|
27
|
+
if (platform === 'darwin' && arch === 'x64') return 'darwin-x64';
|
|
28
|
+
if (platform === 'win32' && arch === 'x64') return 'win32-x64';
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const key = detectPlatformKey();
|
|
33
|
+
if (!key) {
|
|
34
|
+
process.stderr.write(`@fulgur-rs/cli: unsupported platform ${process.platform}/${process.arch}\n`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { pkg, bin } = PLATFORMS[key];
|
|
39
|
+
let pkgDir;
|
|
40
|
+
try {
|
|
41
|
+
pkgDir = path.dirname(require.resolve(`${pkg}/package.json`));
|
|
42
|
+
} catch {
|
|
43
|
+
process.stderr.write(
|
|
44
|
+
`@fulgur-rs/cli: platform package ${pkg} not found.\n` +
|
|
45
|
+
`This usually means it was not installed (e.g. --ignore-optional was used).\n`
|
|
46
|
+
);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const r = spawnSync(path.join(pkgDir, 'bin', bin), process.argv.slice(2), { stdio: 'inherit' });
|
|
51
|
+
process.exit(r.status ?? 1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fulgur-rs/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.14",
|
|
4
4
|
"description": "HTML to PDF conversion CLI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"html",
|
|
@@ -17,18 +17,14 @@
|
|
|
17
17
|
"fulgur": "bin/fulgur"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"bin/"
|
|
21
|
-
"install.js"
|
|
20
|
+
"bin/"
|
|
22
21
|
],
|
|
23
|
-
"scripts": {
|
|
24
|
-
"postinstall": "node install.js"
|
|
25
|
-
},
|
|
26
22
|
"optionalDependencies": {
|
|
27
|
-
"@fulgur-rs/cli-linux-x64": "0.5.
|
|
28
|
-
"@fulgur-rs/cli-linux-x64-musl": "0.5.
|
|
29
|
-
"@fulgur-rs/cli-linux-arm64": "0.5.
|
|
30
|
-
"@fulgur-rs/cli-darwin-arm64": "0.5.
|
|
31
|
-
"@fulgur-rs/cli-darwin-x64": "0.5.
|
|
32
|
-
"@fulgur-rs/cli-win32-x64": "0.5.
|
|
23
|
+
"@fulgur-rs/cli-linux-x64": "0.5.14",
|
|
24
|
+
"@fulgur-rs/cli-linux-x64-musl": "0.5.14",
|
|
25
|
+
"@fulgur-rs/cli-linux-arm64": "0.5.14",
|
|
26
|
+
"@fulgur-rs/cli-darwin-arm64": "0.5.14",
|
|
27
|
+
"@fulgur-rs/cli-darwin-x64": "0.5.14",
|
|
28
|
+
"@fulgur-rs/cli-win32-x64": "0.5.14"
|
|
33
29
|
}
|
|
34
30
|
}
|
package/install.js
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
|
|
7
|
-
const PLATFORMS = {
|
|
8
|
-
'linux-x64': { os: 'linux', cpu: 'x64', pkg: '@fulgur-rs/cli-linux-x64', bin: 'fulgur' },
|
|
9
|
-
'linux-x64-musl': { os: 'linux', cpu: 'x64', pkg: '@fulgur-rs/cli-linux-x64-musl', bin: 'fulgur' },
|
|
10
|
-
'linux-arm64': { os: 'linux', cpu: 'arm64', pkg: '@fulgur-rs/cli-linux-arm64', bin: 'fulgur' },
|
|
11
|
-
'darwin-arm64': { os: 'darwin', cpu: 'arm64', pkg: '@fulgur-rs/cli-darwin-arm64', bin: 'fulgur' },
|
|
12
|
-
'darwin-x64': { os: 'darwin', cpu: 'x64', pkg: '@fulgur-rs/cli-darwin-x64', bin: 'fulgur' },
|
|
13
|
-
'win32-x64': { os: 'win32', cpu: 'x64', pkg: '@fulgur-rs/cli-win32-x64', bin: 'fulgur.exe' },
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
function isMusl() {
|
|
17
|
-
try {
|
|
18
|
-
const maps = fs.readFileSync('/proc/self/maps', 'utf8');
|
|
19
|
-
return maps.includes('musl');
|
|
20
|
-
} catch {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function detectPlatformKey() {
|
|
26
|
-
const os = process.platform;
|
|
27
|
-
const cpu = process.arch;
|
|
28
|
-
|
|
29
|
-
if (os === 'linux' && cpu === 'x64') {
|
|
30
|
-
return isMusl() ? 'linux-x64-musl' : 'linux-x64';
|
|
31
|
-
}
|
|
32
|
-
if (os === 'linux' && cpu === 'arm64') return 'linux-arm64';
|
|
33
|
-
if (os === 'darwin' && cpu === 'arm64') return 'darwin-arm64';
|
|
34
|
-
if (os === 'darwin' && cpu === 'x64') return 'darwin-x64';
|
|
35
|
-
if (os === 'win32' && cpu === 'x64') return 'win32-x64';
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const platformKey = detectPlatformKey();
|
|
40
|
-
if (!platformKey) {
|
|
41
|
-
process.stderr.write(
|
|
42
|
-
`@fulgur-rs/cli: unsupported platform ${process.platform}/${process.arch}\n`
|
|
43
|
-
);
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const platform = PLATFORMS[platformKey];
|
|
48
|
-
let pkgDir;
|
|
49
|
-
try {
|
|
50
|
-
pkgDir = path.dirname(require.resolve(`${platform.pkg}/package.json`));
|
|
51
|
-
} catch {
|
|
52
|
-
process.stderr.write(
|
|
53
|
-
`@fulgur-rs/cli: platform package ${platform.pkg} not found.\n` +
|
|
54
|
-
`This usually means it was not installed (e.g., --ignore-optional was used).\n`
|
|
55
|
-
);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const src = path.join(pkgDir, 'bin', platform.bin);
|
|
60
|
-
const destDir = path.join(__dirname, 'bin');
|
|
61
|
-
const dest = path.join(destDir, platform.bin);
|
|
62
|
-
|
|
63
|
-
if (!fs.existsSync(src)) {
|
|
64
|
-
process.stderr.write(
|
|
65
|
-
`@fulgur-rs/cli: binary not found at ${src}\n` +
|
|
66
|
-
`The platform package ${platform.pkg} may be corrupted or incomplete.\n`
|
|
67
|
-
);
|
|
68
|
-
process.exit(1);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
72
|
-
fs.copyFileSync(src, dest);
|
|
73
|
-
if (process.platform !== 'win32') {
|
|
74
|
-
fs.chmodSync(dest, 0o755);
|
|
75
|
-
} else {
|
|
76
|
-
// Windows: npm の bin フィールドは拡張子なし "bin/fulgur" を参照するため、
|
|
77
|
-
// .exe を起動する JS シムを書き込む
|
|
78
|
-
const shimPath = path.join(destDir, 'fulgur');
|
|
79
|
-
const shimContent = [
|
|
80
|
-
'#!/usr/bin/env node',
|
|
81
|
-
"'use strict';",
|
|
82
|
-
"const { spawnSync } = require('child_process');",
|
|
83
|
-
"const path = require('path');",
|
|
84
|
-
"const r = spawnSync(path.join(__dirname, 'fulgur.exe'), process.argv.slice(2), { stdio: 'inherit' });",
|
|
85
|
-
'process.exit(r.status ?? 1);',
|
|
86
|
-
'',
|
|
87
|
-
].join('\n');
|
|
88
|
-
fs.writeFileSync(shimPath, shimContent, { encoding: 'utf8' });
|
|
89
|
-
}
|