@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.
Files changed (3) hide show
  1. package/README.md +0 -3
  2. package/dist/index.js +15 -42
  3. 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
- `${format === "webp" ? "WebP" : "PNG"} output needs the sharp package (native binaries). It is optional and may not have installed; use SVG or reinstall: https://sharp.pixelplumbing.com/install`
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
- const svgBuffer = Buffer.from(svgString, "utf-8");
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}pnpm qrdx -- generate \u2026${RESET}`
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.6",
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
+ }