chartjs2img 0.3.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 +607 -0
- package/dist/cache.d.ts +16 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +60 -0
- package/dist/cache.js.map +1 -0
- package/dist/chromium.d.ts +10 -0
- package/dist/chromium.d.ts.map +1 -0
- package/dist/chromium.js +226 -0
- package/dist/chromium.js.map +1 -0
- package/dist/lib.d.ts +40 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +48 -0
- package/dist/lib.js.map +1 -0
- package/dist/renderer.d.ts +76 -0
- package/dist/renderer.d.ts.map +1 -0
- package/dist/renderer.js +283 -0
- package/dist/renderer.js.map +1 -0
- package/dist/semaphore.d.ts +12 -0
- package/dist/semaphore.d.ts.map +1 -0
- package/dist/semaphore.js +42 -0
- package/dist/semaphore.js.map +1 -0
- package/dist/template.d.ts +85 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +120 -0
- package/dist/template.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/package.json +75 -0
- package/src/index.ts +312 -0
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chartjs2img",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Render Chart.js charts to images via a TypeScript API, CLI, or HTTP server — headless Chromium under the hood. Chart.js 4.4 + 12 plugins bundled.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/lib.js",
|
|
7
|
+
"module": "./dist/lib.js",
|
|
8
|
+
"types": "./dist/lib.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/lib.d.ts",
|
|
12
|
+
"import": "./dist/lib.js"
|
|
13
|
+
},
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"bin": {
|
|
23
|
+
"chartjs2img": "src/index.ts"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"chart",
|
|
27
|
+
"chartjs",
|
|
28
|
+
"chart.js",
|
|
29
|
+
"image",
|
|
30
|
+
"png",
|
|
31
|
+
"jpeg",
|
|
32
|
+
"webp",
|
|
33
|
+
"puppeteer",
|
|
34
|
+
"headless-chromium",
|
|
35
|
+
"visualization",
|
|
36
|
+
"llm"
|
|
37
|
+
],
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/ideamans/chartjs2img.git"
|
|
41
|
+
},
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"scripts": {
|
|
44
|
+
"dev": "bun run src/index.ts serve",
|
|
45
|
+
"build": "bun build src/index.ts --compile --outfile chartjs2img",
|
|
46
|
+
"start": "bun run src/index.ts serve",
|
|
47
|
+
"cli": "bun run src/index.ts",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"clean:dist": "rm -rf dist",
|
|
50
|
+
"build:lib": "bun run clean:dist && tsc -p tsconfig.build.json",
|
|
51
|
+
"prepublishOnly": "bun run build:lib",
|
|
52
|
+
"build-llms-txt": "bun scripts/build-llms-txt.ts",
|
|
53
|
+
"validate-plugin-skills": "bun scripts/validate-plugin-skills.ts",
|
|
54
|
+
"ai:regen": "bun scripts/build-llms-txt.ts",
|
|
55
|
+
"docs:examples": "bun scripts/build-docs-examples.ts",
|
|
56
|
+
"docs:diagrams": "bun scripts/build-docs-diagrams.ts",
|
|
57
|
+
"docs:dev": "bun run build-llms-txt && bun run docs:diagrams && vitepress dev docs",
|
|
58
|
+
"docs:build": "bun run build-llms-txt && bun run docs:diagrams && bun run docs:examples && vitepress build docs",
|
|
59
|
+
"docs:preview": "vitepress preview docs",
|
|
60
|
+
"docs:deploy": "bun run docs:build && rsync -av docs/.vitepress/dist/ web-g6:web/vhosts/chartjs2img.ideamans.com/html/"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"puppeteer-core": "^24.40.0"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@tailwindcss/vite": "^4.2.2",
|
|
67
|
+
"@types/bun": "latest",
|
|
68
|
+
"daisyui": "^5.5.19",
|
|
69
|
+
"tailwindcss": "^4.2.2",
|
|
70
|
+
"typescript": "^5.7.0",
|
|
71
|
+
"vitepress": "^2.0.0-alpha.17",
|
|
72
|
+
"vitepress-daisyui-theme": "^0.2.1",
|
|
73
|
+
"vue": "^3.5.0"
|
|
74
|
+
}
|
|
75
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { startServer } from './server'
|
|
3
|
+
import { cliRender, cliExamples } from './cli'
|
|
4
|
+
import { closeBrowser } from './renderer'
|
|
5
|
+
import { VERSION } from './version'
|
|
6
|
+
import { getLlmDocs } from './llm-docs'
|
|
7
|
+
import { parseArgs, CliArgError } from './cli-args'
|
|
8
|
+
|
|
9
|
+
function printUsage(): void {
|
|
10
|
+
console.log(`chartjs2img v${VERSION} - Render Chart.js charts to images using Playwright (headless Chromium)
|
|
11
|
+
|
|
12
|
+
Converts a Chart.js configuration JSON into a PNG or JPEG image. Works as an
|
|
13
|
+
HTTP server or a one-shot CLI. Chromium is installed automatically on first run.
|
|
14
|
+
|
|
15
|
+
COMMANDS
|
|
16
|
+
chartjs2img serve [options] Start HTTP server
|
|
17
|
+
chartjs2img render [options] Render a single chart image
|
|
18
|
+
chartjs2img examples [options] Generate all built-in example images
|
|
19
|
+
chartjs2img llm Print extended help for LLMs (Chart.js + plugin reference)
|
|
20
|
+
chartjs2img help Show this help
|
|
21
|
+
|
|
22
|
+
SERVE OPTIONS
|
|
23
|
+
--port, -p <port> Listen port (default: 3000, env: PORT)
|
|
24
|
+
--host <host> Bind address (default: 0.0.0.0, env: HOST)
|
|
25
|
+
--api-key <key> Require API key auth (env: API_KEY)
|
|
26
|
+
|
|
27
|
+
RENDER OPTIONS
|
|
28
|
+
--input, -i <file> Input JSON file, or "-" for stdin (default: stdin)
|
|
29
|
+
--output, -o <file> Output image file, or "-" for stdout (default: stdout)
|
|
30
|
+
--width, -w <px> Canvas width in pixels (default: 800)
|
|
31
|
+
--height, -h <px> Canvas height in pixels (default: 600)
|
|
32
|
+
--device-pixel-ratio <n> Retina scale factor (default: 2)
|
|
33
|
+
--background-color <color> CSS color or "transparent" (default: white)
|
|
34
|
+
--format, -f <fmt> png | jpeg (default: png)
|
|
35
|
+
--quality, -q <0-100> JPEG quality (default: 90)
|
|
36
|
+
|
|
37
|
+
EXAMPLES OPTIONS
|
|
38
|
+
--outdir, -o <dir> Output directory (default: ./examples)
|
|
39
|
+
--format, -f <fmt> png | jpeg (default: png)
|
|
40
|
+
--quality, -q <0-100> JPEG quality (default: 90)
|
|
41
|
+
|
|
42
|
+
ENVIRONMENT VARIABLES
|
|
43
|
+
PORT Server listen port (default: 3000)
|
|
44
|
+
HOST Server bind address (default: 0.0.0.0)
|
|
45
|
+
API_KEY API key for authentication (optional)
|
|
46
|
+
CONCURRENCY Max simultaneous renders (default: 8)
|
|
47
|
+
CACHE_MAX_ENTRIES Max cached images in memory (default: 1000)
|
|
48
|
+
CACHE_TTL_SECONDS Cache entry TTL in seconds (default: 3600)
|
|
49
|
+
MAX_RENDER_TIME_SECONDS Per-render upper bound for goto/waitForFunction (default: 30)
|
|
50
|
+
PAGE_TIMEOUT_SECONDS Override the safety-net force-close timer (default: MAX_RENDER_TIME * 2 + 10)
|
|
51
|
+
|
|
52
|
+
HTTP ENDPOINTS (serve mode)
|
|
53
|
+
POST /render Render chart. Body: JSON (see schema below). Returns image.
|
|
54
|
+
GET /render Same, but params via query string: ?chart={...}&width=800
|
|
55
|
+
GET /cache/<hash> Retrieve a previously rendered image by its cache hash.
|
|
56
|
+
GET /examples Interactive gallery page showing all built-in examples.
|
|
57
|
+
GET /health JSON status: browser, concurrency, cache stats.
|
|
58
|
+
|
|
59
|
+
Response headers from /render:
|
|
60
|
+
X-Cache-Hash SHA-256 hash (first 16 hex chars) of the render input
|
|
61
|
+
X-Cache-Url Full URL to GET this image from /cache/<hash>
|
|
62
|
+
X-Cache-Hit "true" if served from cache, "false" if freshly rendered
|
|
63
|
+
X-Chart-Messages JSON array of {level, message} from Chart.js (if any)
|
|
64
|
+
|
|
65
|
+
AUTHENTICATION (when API_KEY is set)
|
|
66
|
+
Requests to /render and /cache must include the key in one of:
|
|
67
|
+
- Header: Authorization: Bearer <key>
|
|
68
|
+
- Header: X-API-Key: <key>
|
|
69
|
+
- Query: ?api_key=<key>
|
|
70
|
+
/health is accessible without authentication.
|
|
71
|
+
/examples requires the API key as well (otherwise the page would leak
|
|
72
|
+
the key in its HTML since it calls /render from the browser).
|
|
73
|
+
|
|
74
|
+
INPUT JSON SCHEMA (for "render" CLI and POST /render)
|
|
75
|
+
The input is a JSON object. For CLI "render", the top-level object IS the
|
|
76
|
+
Chart.js config. For HTTP POST /render, wrap it in a "chart" field alongside
|
|
77
|
+
optional render settings.
|
|
78
|
+
|
|
79
|
+
JSON only — function / callback values (e.g. tooltip.callbacks, scale.ticks
|
|
80
|
+
callback, treemap labels.formatter) are silently dropped by JSON
|
|
81
|
+
serialization and never reach the browser. Use static values instead.
|
|
82
|
+
|
|
83
|
+
CLI stdin/file (Chart.js config directly):
|
|
84
|
+
{
|
|
85
|
+
"type": "bar",
|
|
86
|
+
"data": {
|
|
87
|
+
"labels": ["A", "B", "C"],
|
|
88
|
+
"datasets": [{ "label": "Series", "data": [10, 20, 30] }]
|
|
89
|
+
},
|
|
90
|
+
"options": { ... }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
HTTP POST /render body:
|
|
94
|
+
{
|
|
95
|
+
"chart": { <Chart.js config as above> },
|
|
96
|
+
"width": 800, // optional, default 800
|
|
97
|
+
"height": 600, // optional, default 600
|
|
98
|
+
"devicePixelRatio": 2, // optional, default 2
|
|
99
|
+
"backgroundColor": "white",// optional, default "white"
|
|
100
|
+
"format": "png", // optional, "png" | "jpeg"
|
|
101
|
+
"quality": 90 // optional, 0-100 for jpeg
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
CHART.JS CONFIGURATION REFERENCE
|
|
105
|
+
The "chart" (or CLI input) follows the standard Chart.js config:
|
|
106
|
+
{
|
|
107
|
+
"type": "<chart-type>",
|
|
108
|
+
"data": {
|
|
109
|
+
"labels": [...],
|
|
110
|
+
"datasets": [{
|
|
111
|
+
"label": "...",
|
|
112
|
+
"data": [...],
|
|
113
|
+
"backgroundColor": "...",
|
|
114
|
+
"borderColor": "...",
|
|
115
|
+
...dataset options
|
|
116
|
+
}]
|
|
117
|
+
},
|
|
118
|
+
"options": {
|
|
119
|
+
"responsive": true, // forced true internally
|
|
120
|
+
"plugins": {
|
|
121
|
+
"title": { "display": true, "text": "Title" },
|
|
122
|
+
"legend": { "position": "top" },
|
|
123
|
+
"datalabels": { "display": true, "color": "#333" },
|
|
124
|
+
"annotation": { "annotations": { ... } }
|
|
125
|
+
},
|
|
126
|
+
"scales": {
|
|
127
|
+
"x": { "type": "category", "title": { "display": true, "text": "X" } },
|
|
128
|
+
"y": { "type": "linear", "beginAtZero": true }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
Supported "type" values (built-in):
|
|
134
|
+
bar, line, pie, doughnut, radar, polarArea, scatter, bubble
|
|
135
|
+
|
|
136
|
+
Additional types from plugins:
|
|
137
|
+
treemap, matrix, sankey, wordcloud, choropleth, bubbleMap,
|
|
138
|
+
graph, forceDirectedGraph, dendrogram, tree, venn, euler
|
|
139
|
+
|
|
140
|
+
BUILT-IN PLUGINS (all pre-loaded, no configuration needed)
|
|
141
|
+
chart.js 4.4.9 Core charting library
|
|
142
|
+
chartjs-plugin-datalabels 2.2.0 Show values on chart elements
|
|
143
|
+
chartjs-plugin-annotation 3.1.0 Lines, boxes, labels as overlays
|
|
144
|
+
chartjs-plugin-zoom 2.2.0 Pan & zoom (initial range for static)
|
|
145
|
+
chartjs-plugin-gradient 0.6.1 Gradient fills via simple config
|
|
146
|
+
chartjs-chart-matrix 2.0.1 Heatmap / matrix charts
|
|
147
|
+
chartjs-chart-sankey 0.12.1 Sankey flow diagrams
|
|
148
|
+
chartjs-chart-treemap 2.3.1 Treemap charts
|
|
149
|
+
chartjs-chart-wordcloud 4.4.3 Word cloud charts
|
|
150
|
+
chartjs-chart-geo 4.3.3 Choropleth & bubble map charts
|
|
151
|
+
chartjs-chart-graph 4.3.3 Network / force-directed graphs
|
|
152
|
+
chartjs-chart-venn 4.3.3 Venn & Euler diagrams
|
|
153
|
+
chartjs-adapter-date-fns 3.0.0 date-fns date adapter for time axes
|
|
154
|
+
|
|
155
|
+
Note: animation is always forced OFF for deterministic rendering.
|
|
156
|
+
|
|
157
|
+
ERROR FEEDBACK
|
|
158
|
+
Chart.js errors and warnings are captured from the browser console during
|
|
159
|
+
rendering and returned to the caller. This helps diagnose invalid configs.
|
|
160
|
+
|
|
161
|
+
CLI:
|
|
162
|
+
Errors/warnings are printed to stderr:
|
|
163
|
+
[chart ERROR] "nonexistent" is not a registered controller.
|
|
164
|
+
[chart WARN] Some warning message from Chart.js
|
|
165
|
+
|
|
166
|
+
HTTP API:
|
|
167
|
+
When messages exist, the response includes an X-Chart-Messages header:
|
|
168
|
+
X-Chart-Messages: [{"level":"error","message":"..."},{"level":"warn","message":"..."}]
|
|
169
|
+
|
|
170
|
+
Parse it as JSON to inspect issues:
|
|
171
|
+
curl -s -D- -X POST http://localhost:3000/render \\
|
|
172
|
+
-H 'Content-Type: application/json' \\
|
|
173
|
+
-d '{"chart":{"type":"invalid",...}}' -o /dev/null \\
|
|
174
|
+
| grep X-Chart-Messages
|
|
175
|
+
|
|
176
|
+
Note: rendering still completes even when errors occur (the image may be
|
|
177
|
+
blank or partial). Check messages to determine if the chart was valid.
|
|
178
|
+
|
|
179
|
+
USAGE EXAMPLES
|
|
180
|
+
|
|
181
|
+
# Start server on port 3000
|
|
182
|
+
chartjs2img serve
|
|
183
|
+
|
|
184
|
+
# Start with auth and custom port
|
|
185
|
+
chartjs2img serve --port 8080 --api-key s3cret
|
|
186
|
+
|
|
187
|
+
# Render bar chart from stdin to file
|
|
188
|
+
echo '{"type":"bar","data":{"labels":["A","B","C"],"datasets":[{"data":[1,2,3]}]}}' \\
|
|
189
|
+
| chartjs2img render -o chart.png
|
|
190
|
+
|
|
191
|
+
# Render from JSON file with custom size
|
|
192
|
+
chartjs2img render -i config.json -o wide.png -w 1200 -h 400
|
|
193
|
+
|
|
194
|
+
# Render as JPEG with quality setting
|
|
195
|
+
chartjs2img render -i config.json -o chart.jpg -f jpeg -q 85
|
|
196
|
+
|
|
197
|
+
# Generate all built-in examples as PNG
|
|
198
|
+
chartjs2img examples -o ./gallery
|
|
199
|
+
|
|
200
|
+
# Generate examples as JPEG
|
|
201
|
+
chartjs2img examples -o ./gallery -f jpeg -q 80
|
|
202
|
+
|
|
203
|
+
# HTTP: render via POST and save
|
|
204
|
+
curl -X POST http://localhost:3000/render \\
|
|
205
|
+
-H 'Content-Type: application/json' \\
|
|
206
|
+
-d '{"chart":{"type":"bar","data":{"labels":["A","B"],"datasets":[{"data":[1,2]}]}}}' \\
|
|
207
|
+
-o chart.png
|
|
208
|
+
|
|
209
|
+
# HTTP: render via POST with auth and options
|
|
210
|
+
curl -X POST http://localhost:3000/render \\
|
|
211
|
+
-H 'Content-Type: application/json' \\
|
|
212
|
+
-H 'X-API-Key: s3cret' \\
|
|
213
|
+
-d '{"chart":{"type":"line","data":{"labels":["Mon","Tue","Wed"],"datasets":[{"label":"Views","data":[100,200,150],"borderColor":"#36a2eb"}]}},"width":1200,"height":400,"format":"jpeg","quality":85}' \\
|
|
214
|
+
-o chart.jpg
|
|
215
|
+
|
|
216
|
+
# HTTP: use cached image (hash from X-Cache-Hash response header)
|
|
217
|
+
curl -o cached.png http://localhost:3000/cache/6b4cc4e8940fd921
|
|
218
|
+
|
|
219
|
+
# HTTP: embed in <img> tag via GET (URL-encode the chart JSON)
|
|
220
|
+
<img src="http://localhost:3000/render?chart=%7B%22type%22%3A%22pie%22%2C...%7D&width=400&height=400">
|
|
221
|
+
|
|
222
|
+
# Check server health
|
|
223
|
+
curl http://localhost:3000/health
|
|
224
|
+
`)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function main(): Promise<void> {
|
|
228
|
+
const command = process.argv[2]
|
|
229
|
+
let args: Record<string, string | boolean>
|
|
230
|
+
try {
|
|
231
|
+
args = parseArgs(process.argv.slice(3))
|
|
232
|
+
} catch (err) {
|
|
233
|
+
if (err instanceof CliArgError) {
|
|
234
|
+
console.error(err.message)
|
|
235
|
+
process.exit(2)
|
|
236
|
+
}
|
|
237
|
+
throw err
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (command === '--version' || command === 'version') {
|
|
241
|
+
console.log(`chartjs2img v${VERSION}`)
|
|
242
|
+
process.exit(0)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!command || command === '--help' || command === '-h' || command === '-?' || command === 'help') {
|
|
246
|
+
printUsage()
|
|
247
|
+
process.exit(0)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (command === 'llm') {
|
|
251
|
+
console.log(getLlmDocs())
|
|
252
|
+
process.exit(0)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (command === 'serve') {
|
|
256
|
+
const port = Number(args['port'] ?? args['p'] ?? process.env.PORT ?? '3000')
|
|
257
|
+
const host = String(args['host'] ?? process.env.HOST ?? '0.0.0.0')
|
|
258
|
+
const apiKey = String(args['api-key'] ?? process.env.API_KEY ?? '') || undefined
|
|
259
|
+
|
|
260
|
+
const handle = await startServer({ port, host, apiKey })
|
|
261
|
+
|
|
262
|
+
// Signal handlers belong to the CLI (process entrypoint), not the
|
|
263
|
+
// server library. `once` avoids accumulating handlers if startServer
|
|
264
|
+
// is ever called multiple times in the same process.
|
|
265
|
+
let shuttingDown = false
|
|
266
|
+
const shutdown = async () => {
|
|
267
|
+
if (shuttingDown) return
|
|
268
|
+
shuttingDown = true
|
|
269
|
+
console.log('\nShutting down...')
|
|
270
|
+
try {
|
|
271
|
+
await handle.stop()
|
|
272
|
+
} catch (err) {
|
|
273
|
+
console.error('[cli] server stop failed:', err)
|
|
274
|
+
}
|
|
275
|
+
await closeBrowser()
|
|
276
|
+
process.exit(0)
|
|
277
|
+
}
|
|
278
|
+
process.once('SIGINT', shutdown)
|
|
279
|
+
process.once('SIGTERM', shutdown)
|
|
280
|
+
// Bun.serve keeps the event loop alive; main() returning is fine.
|
|
281
|
+
} else if (command === 'examples') {
|
|
282
|
+
const outdir = (args['outdir'] as string) ?? (args['o'] as string) ?? './examples'
|
|
283
|
+
const format = ((args['format'] ?? args['f']) as string) ?? 'png'
|
|
284
|
+
const quality = args['quality'] ? Number(args['quality']) : args['q'] ? Number(args['q']) : undefined
|
|
285
|
+
|
|
286
|
+
await cliExamples({
|
|
287
|
+
outdir,
|
|
288
|
+
format: format as 'png' | 'jpeg',
|
|
289
|
+
quality,
|
|
290
|
+
})
|
|
291
|
+
} else if (command === 'render') {
|
|
292
|
+
await cliRender({
|
|
293
|
+
input: args['input'] as string ?? args['i'] as string,
|
|
294
|
+
output: args['output'] as string ?? args['o'] as string,
|
|
295
|
+
width: args['width'] ? Number(args['width']) : args['w'] ? Number(args['w']) : undefined,
|
|
296
|
+
height: args['height'] ? Number(args['height']) : args['h'] ? Number(args['h']) : undefined,
|
|
297
|
+
devicePixelRatio: args['device-pixel-ratio'] ? Number(args['device-pixel-ratio']) : undefined,
|
|
298
|
+
backgroundColor: args['background-color'] as string,
|
|
299
|
+
format: (args['format'] ?? args['f']) as 'png' | 'jpeg' | undefined,
|
|
300
|
+
quality: args['quality'] ? Number(args['quality']) : args['q'] ? Number(args['q']) : undefined,
|
|
301
|
+
})
|
|
302
|
+
} else {
|
|
303
|
+
console.error(`Unknown command: ${command}`)
|
|
304
|
+
printUsage()
|
|
305
|
+
process.exit(1)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
main().catch((err) => {
|
|
310
|
+
console.error(err)
|
|
311
|
+
process.exit(1)
|
|
312
|
+
})
|