@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.
Files changed (4) hide show
  1. package/README.md +197 -0
  2. package/bin/fulgur +51 -0
  3. package/package.json +8 -12
  4. package/install.js +0 -89
package/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # fulgur
2
+
3
+ [![npm version](https://img.shields.io/npm/v/%40fulgur-rs%2Fcli.svg)](https://www.npmjs.com/package/@fulgur-rs/cli)
4
+ [![License](https://img.shields.io/npm/l/%40fulgur-rs%2Fcli.svg)](https://github.com/fulgur-rs/fulgur/blob/main/LICENSE-MIT)
5
+ [![CI](https://github.com/fulgur-rs/fulgur/actions/workflows/ci.yml/badge.svg)](https://github.com/fulgur-rs/fulgur/actions/workflows/ci.yml)
6
+ [![codecov](https://codecov.io/gh/fulgur-rs/fulgur/graph/badge.svg)](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.12",
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.12",
28
- "@fulgur-rs/cli-linux-x64-musl": "0.5.12",
29
- "@fulgur-rs/cli-linux-arm64": "0.5.12",
30
- "@fulgur-rs/cli-darwin-arm64": "0.5.12",
31
- "@fulgur-rs/cli-darwin-x64": "0.5.12",
32
- "@fulgur-rs/cli-win32-x64": "0.5.12"
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
- }