@qrdx/cli 0.0.6 → 0.0.8
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 +0 -3
- package/dist/index.js +15 -42
- package/package.json +16 -15
package/README.md
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
QR code generator CLI for developers. Generate styled, scannable QR codes as SVG, PNG, WebP or vector EPS. It runs non-interactively in scripts and CI, and guides you with prompts when run in a terminal. It renders with the same [`qrdx`](https://www.npmjs.com/package/qrdx) library as the [QRdx](https://qrdx.dev) web app.
|
|
4
4
|
|
|
5
|
-
> [!NOTE]
|
|
6
|
-
> This README documents `@qrdx/cli` 0.0.6, the version in this repository. WebP output is new in 0.0.6; 0.0.5 has every other option here. Run `npm view @qrdx/cli version` to see what npm installs. Until it is 0.0.6, get WebP from the [image API](https://qrdx.dev/docs/developers/image-api): `curl -o qr.webp "https://api.qrdx.dev/v1/qr?data=https://example.com&format=webp"`.
|
|
7
|
-
|
|
8
5
|
## Install
|
|
9
6
|
|
|
10
7
|
Run without installing:
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync, mkdirSync, writeFileSync, createReadStream } from 'fs';
|
|
|
3
3
|
import { join, resolve, extname } from 'path';
|
|
4
4
|
import { readFile } from 'fs/promises';
|
|
5
5
|
import { BODY_PATTERN, CORNER_EYE_PATTERNS, CORNER_EYE_DOT_PATTERNS, getTemplateIds, FRAME_STYLES, DEFAULT_FRAME_TEXT, FRAME_TEXT_MAX_LENGTH, DEFAULT_MARGIN, getFrameDimensions, colorConfigSchema, errorLevelSchema, QRCodeSVG, svgToEPS } from 'qrdx';
|
|
6
|
+
import { getQRAsPNG } from 'qrdx/node';
|
|
6
7
|
import { createElement } from 'react';
|
|
7
8
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
8
9
|
import * as readline from 'readline';
|
|
@@ -138,7 +139,6 @@ var OUTPUT_FORMATS = [
|
|
|
138
139
|
"webp",
|
|
139
140
|
"eps"
|
|
140
141
|
];
|
|
141
|
-
var encodeRaster = (image, format) => format === "webp" ? image.webp({ lossless: true }) : image.png();
|
|
142
142
|
function buildSVGString(props) {
|
|
143
143
|
const element = createElement(QRCodeSVG, {
|
|
144
144
|
size: props.size ?? 512,
|
|
@@ -198,24 +198,6 @@ async function buildEPSString(props) {
|
|
|
198
198
|
}
|
|
199
199
|
return await svgToEPS(buildSVGString(epsProps));
|
|
200
200
|
}
|
|
201
|
-
async function findLogoBox(sharp, svgString) {
|
|
202
|
-
const imageTag = svgString.match(/<image\b[^>]*?(?:\/>|><\/image>)/)?.[0];
|
|
203
|
-
if (!imageTag) {
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
const markerTag = imageTag.replace(/^<image\b/, '<rect class="qrdx-logo-box"').replace(/\shref="[^"]*"/, "").replace(/(?:\/>|><\/image>)$/, ' fill="#000"/>');
|
|
207
|
-
const markerSvg = svgString.replace(imageTag, markerTag).replace(
|
|
208
|
-
/<svg\b[^>]*>/,
|
|
209
|
-
(open) => `${open}<style>*{visibility:hidden}.qrdx-logo-box{visibility:visible}</style>`
|
|
210
|
-
);
|
|
211
|
-
const { info } = await sharp(Buffer.from(markerSvg, "utf-8")).png().trim().toBuffer({ resolveWithObject: true });
|
|
212
|
-
return {
|
|
213
|
-
left: -(info.trimOffsetLeft ?? 0),
|
|
214
|
-
top: -(info.trimOffsetTop ?? 0),
|
|
215
|
-
width: info.width,
|
|
216
|
-
height: info.height
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
201
|
async function saveOutput(options) {
|
|
220
202
|
const { outputPath, format, ...qrProps } = options;
|
|
221
203
|
const svgString = buildSVGString(qrProps);
|
|
@@ -229,33 +211,24 @@ ${svgString}`;
|
|
|
229
211
|
writeFileSync(outputPath, await buildEPSString(qrProps), "utf-8");
|
|
230
212
|
return;
|
|
231
213
|
}
|
|
214
|
+
const pngBytes = await getQRAsPNG(qrProps, {
|
|
215
|
+
size: qrProps.size ?? 512,
|
|
216
|
+
loadExternalLogos: true
|
|
217
|
+
});
|
|
218
|
+
if (format === "png") {
|
|
219
|
+
writeFileSync(outputPath, pngBytes);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
232
222
|
let sharp;
|
|
233
223
|
try {
|
|
234
224
|
const mod = await import('sharp');
|
|
235
225
|
sharp = mod.default;
|
|
236
226
|
} catch {
|
|
237
227
|
throw new Error(
|
|
238
|
-
|
|
228
|
+
"WebP output needs the sharp package (native binaries). It is optional and may not have installed; use SVG or reinstall: https://sharp.pixelplumbing.com/install"
|
|
239
229
|
);
|
|
240
230
|
}
|
|
241
|
-
|
|
242
|
-
const size = qrProps.size ?? 512;
|
|
243
|
-
const { width, height } = getFrameDimensions(size, qrProps.frame);
|
|
244
|
-
const qrImage = sharp(svgBuffer).resize(
|
|
245
|
-
Math.round(width),
|
|
246
|
-
Math.round(height)
|
|
247
|
-
);
|
|
248
|
-
const logoSrc = qrProps.imageSettings?.src;
|
|
249
|
-
const box = logoSrc ? await findLogoBox(sharp, svgString) : null;
|
|
250
|
-
if (!(logoSrc && box)) {
|
|
251
|
-
await encodeRaster(qrImage, format).toFile(outputPath);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
const logo = await sharp(await loadLogo(logoSrc)).resize(box.width, box.height, { fit: "fill" }).png().toBuffer();
|
|
255
|
-
const composited = sharp(await qrImage.png().toBuffer()).composite([
|
|
256
|
-
{ input: logo, left: box.left, top: box.top }
|
|
257
|
-
]);
|
|
258
|
-
await encodeRaster(composited, format).toFile(outputPath);
|
|
231
|
+
await sharp(Buffer.from(pngBytes)).webp({ lossless: true }).toFile(outputPath);
|
|
259
232
|
}
|
|
260
233
|
var RESET = "\x1B[0m";
|
|
261
234
|
var TEXT = "\x1B[38;5;145m";
|
|
@@ -563,7 +536,7 @@ function isCancel2(value) {
|
|
|
563
536
|
function handleCancel() {
|
|
564
537
|
p.cancel("Cancelled.");
|
|
565
538
|
console.error(
|
|
566
|
-
`${DIM}Tip:${RESET} ${TEXT}qrdx generate "https://\u2026" -o qr.svg${RESET} \u2014 local dev: ${TEXT}
|
|
539
|
+
`${DIM}Tip:${RESET} ${TEXT}qrdx generate "https://\u2026" -o qr.svg${RESET} \u2014 local dev: ${TEXT}bun run qrdx generate \u2026${RESET}`
|
|
567
540
|
);
|
|
568
541
|
process.exit(0);
|
|
569
542
|
}
|
|
@@ -1262,7 +1235,7 @@ ${BOLD}Generate Options:${RESET3}
|
|
|
1262
1235
|
--dot Corner dot pattern, e.g.:
|
|
1263
1236
|
${DIM3}square, rounded, circle, leaf, drop, squircle,${RESET3}
|
|
1264
1237
|
${DIM3}octagon, flower, heart, plus, dots${RESET3}
|
|
1265
|
-
${DIM3}All options: https://qrdx.dev/docs/styles${RESET3}
|
|
1238
|
+
${DIM3}All options: https://qrdx.dev/docs/styles?ref=cli${RESET3}
|
|
1266
1239
|
--fg Foreground color \u2014 hex or JSON gradient ${DIM3}(default: #000000)${RESET3}
|
|
1267
1240
|
--bg Background color \u2014 hex or JSON gradient ${DIM3}(default: #ffffff)${RESET3}
|
|
1268
1241
|
--eye-color Corner eye color \u2014 hex or JSON gradient ${DIM3}(default: matches --fg)${RESET3}
|
|
@@ -1276,7 +1249,7 @@ ${BOLD}Generate Options:${RESET3}
|
|
|
1276
1249
|
--frame Call-to-action frame (adds space around the code), e.g.:
|
|
1277
1250
|
${DIM3}none, banner-bottom, banner-top, rounded-border,${RESET3}
|
|
1278
1251
|
${DIM3}speech-bubble, polaroid, phone, ticket, sticker${RESET3}
|
|
1279
|
-
${DIM3}All frames: https://qrdx.dev/docs/styles${RESET3}
|
|
1252
|
+
${DIM3}All frames: https://qrdx.dev/docs/styles?ref=cli${RESET3}
|
|
1280
1253
|
--frame-text Frame label, up to 24 characters ${DIM3}(default: Scan me; "" hides it)${RESET3}
|
|
1281
1254
|
--frame-color Frame color \u2014 hex ${DIM3}(default: #000000)${RESET3}
|
|
1282
1255
|
--frame-text-color Frame label color \u2014 hex ${DIM3}(default: white on banners)${RESET3}
|
|
@@ -1318,7 +1291,7 @@ ${BOLD}Examples:${RESET3}
|
|
|
1318
1291
|
${DIM3}$${RESET3} qrdx batch products.csv -o codes --format png --size 1024 --body circle
|
|
1319
1292
|
${DIM3}$${RESET3} qrdx generate "https://qrdx.dev" --fg '{"type":"linear","stops":[{"color":"#6366f1","offset":0},{"color":"#ec4899","offset":100}],"angle":135}' -o gradient.svg
|
|
1320
1293
|
|
|
1321
|
-
Visit ${TEXT3}https://qrdx.dev${RESET3} for more.
|
|
1294
|
+
Visit ${TEXT3}https://qrdx.dev/?ref=cli${RESET3} for more.
|
|
1322
1295
|
`);
|
|
1323
1296
|
}
|
|
1324
1297
|
async function main() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qrdx/cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"description": "QR code generator CLI for developers: styled SVG, PNG, WebP and vector EPS QR codes from the command line",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"qr-code",
|
|
@@ -32,38 +32,39 @@
|
|
|
32
32
|
"dist",
|
|
33
33
|
"README.md"
|
|
34
34
|
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"qrdx": "node ./dist/index.js",
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"dev": "tsup --watch",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"clean": "git clean -xdf .cache .turbo dist node_modules",
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
|
+
"prepublishOnly": "node -e \"if(!(process.env.npm_config_user_agent||'').startsWith('bun/')){console.error('Publish through the Publish workflow, or with bun publish, which replaces workspace ranges with real versions.');process.exit(1)}\" && bun run build && bun run test && node ../qrdx/scripts/readme-catalogue.mjs --check"
|
|
44
|
+
},
|
|
35
45
|
"dependencies": {
|
|
36
46
|
"@clack/prompts": "^1.8.1",
|
|
37
47
|
"picocolors": "^1.1.1",
|
|
48
|
+
"qrdx": "0.0.50",
|
|
38
49
|
"react": "19.3.1",
|
|
39
|
-
"react-dom": "19.3.1"
|
|
40
|
-
"qrdx": "0.0.48"
|
|
50
|
+
"react-dom": "19.3.1"
|
|
41
51
|
},
|
|
42
52
|
"optionalDependencies": {
|
|
43
53
|
"sharp": "^0.35.5"
|
|
44
54
|
},
|
|
45
55
|
"devDependencies": {
|
|
56
|
+
"@repo/typescript-config": "0.0.0",
|
|
46
57
|
"@types/node": "^26.6.6",
|
|
47
58
|
"@types/react": "19.3.0",
|
|
48
59
|
"@types/react-dom": "19.3.0",
|
|
49
60
|
"tsup": "^9.0.0",
|
|
50
61
|
"typescript": "6.0.3",
|
|
51
|
-
"vitest": "^5.0.5"
|
|
52
|
-
"@repo/typescript-config": "0.0.0"
|
|
62
|
+
"vitest": "^5.0.5"
|
|
53
63
|
},
|
|
54
64
|
"engines": {
|
|
55
65
|
"node": ">=22"
|
|
56
66
|
},
|
|
57
67
|
"publishConfig": {
|
|
58
68
|
"access": "public"
|
|
59
|
-
},
|
|
60
|
-
"scripts": {
|
|
61
|
-
"qrdx": "node ./dist/index.js",
|
|
62
|
-
"build": "tsup",
|
|
63
|
-
"dev": "tsup --watch",
|
|
64
|
-
"test": "vitest run",
|
|
65
|
-
"test:watch": "vitest",
|
|
66
|
-
"clean": "git clean -xdf .cache .turbo dist node_modules",
|
|
67
|
-
"typecheck": "tsc --noEmit"
|
|
68
69
|
}
|
|
69
|
-
}
|
|
70
|
+
}
|