@lynxship/cli 0.1.14 → 0.1.15

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/dist/index.js CHANGED
@@ -644,13 +644,13 @@ async function runRspeedyCommand(subcommand, environment) {
644
644
  let devUrl;
645
645
  let devQrPrinted = false;
646
646
  let devQrTimer;
647
- const printDevQr = () => {
647
+ const printDevQr = async () => {
648
648
  if (!devUrl || devQrPrinted || json || ui.options.quiet)
649
649
  return;
650
650
  devQrPrinted = true;
651
651
  if (devQrTimer)
652
652
  clearTimeout(devQrTimer);
653
- ui.devServerQr(devUrl);
653
+ await ui.devServerQr(devUrl);
654
654
  };
655
655
  await runRspeedy(root, subcommand, forwarded, {
656
656
  env: environment,
@@ -664,10 +664,12 @@ async function runRspeedyCommand(subcommand, environment) {
664
664
  devUrl = url;
665
665
  if (devQrTimer)
666
666
  clearTimeout(devQrTimer);
667
- devQrTimer = setTimeout(printDevQr, 500);
667
+ devQrTimer = setTimeout(() => {
668
+ void printDevQr();
669
+ }, 500);
668
670
  }
669
671
  if (devUrl && shouldPrintDevServerQr(line))
670
- printDevQr();
672
+ void printDevQr();
671
673
  },
672
674
  });
673
675
  printValue({ status: "success", command: `rspeedy ${subcommand}` }, {
@@ -35,5 +35,5 @@ export declare const pill: {
35
35
  export declare function finalLine(message: string, success?: boolean): void;
36
36
  export declare function nextSteps(commands: string[], note?: string, environment?: string): void;
37
37
  export declare function downloadArtifact(url: string, expiresAt?: string, enabled?: boolean): void;
38
- export declare function devServerQr(url: string, enabled?: boolean): void;
38
+ export declare function devServerQr(url: string, enabled?: boolean): Promise<void>;
39
39
  //# sourceMappingURL=components.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../src/ui/components.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAK7C,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,eAAO,MAAM,GAAG;qBACG,MAAM,GAAG,IAAI;kBAGhB,MAAM,GAAG,IAAI;mBAGZ,MAAM,GAAG,IAAI;kBAGd,MAAM,GAAG,IAAI;mBAGZ,MAAM,GAAG,IAAI;CAG7B,CAAC;AAgBF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA6C9D;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,IAAI,IAAI,IAAI,CAAC;CACd;AA6CD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoBnD;AA6BD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,GACf,cAAc,CAuEhB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,IAAI,IAAI,CAAC;CACd;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,aAAa,CA2BlE;AAYD,eAAO,MAAM,IAAI;oBACC,MAAM;iBACT,MAAM;iBACN,MAAM;kBACL,MAAM;CACrB,CAAC;AAEF,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,UAAO,GAAG,IAAI,CAE/D;AAED,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAAE,EAClB,IAAI,CAAC,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,GACnB,IAAI,CAQN;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,UAAO,GACb,IAAI,CAQN;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,UAAO,GAAG,IAAI,CAS7D"}
1
+ {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../src/ui/components.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAK7C,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED,eAAO,MAAM,GAAG;qBACG,MAAM,GAAG,IAAI;kBAGhB,MAAM,GAAG,IAAI;mBAGZ,MAAM,GAAG,IAAI;kBAGd,MAAM,GAAG,IAAI;mBAGZ,MAAM,GAAG,IAAI;CAG7B,CAAC;AAgBF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CA6C9D;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C,IAAI,IAAI,IAAI,CAAC;CACd;AA6CD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAoBnD;AA6BD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,GACf,cAAc,CAuEhB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,IAAI,IAAI,CAAC;CACd;AAED,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,aAAa,CA2BlE;AAYD,eAAO,MAAM,IAAI;oBACC,MAAM;iBACT,MAAM;iBACN,MAAM;kBACL,MAAM;CACrB,CAAC;AAEF,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,UAAO,GAAG,IAAI,CAE/D;AAED,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAAE,EAClB,IAAI,CAAC,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,GACnB,IAAI,CAQN;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,UAAO,GACb,IAAI,CAQN;AAED,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,UAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAmB5E"}
@@ -289,15 +289,25 @@ export function downloadArtifact(url, expiresAt, enabled = true) {
289
289
  if (expiresAt)
290
290
  console.log(` ${c.muted(`Link expires at ${expiresAt}`)}`);
291
291
  }
292
- export function devServerQr(url, enabled = true) {
292
+ export async function devServerQr(url, enabled = true) {
293
293
  if (!enabled)
294
294
  return;
295
- console.log(`\n${c.brandBold("Lynx Explorer")}`);
295
+ const rail = c.teal("");
296
+ const printRail = (value = "") => console.log(` ${rail}${value}`);
297
+ printRail();
298
+ printRail(` ${c.brandBold("Lynx Explorer")}`);
296
299
  if (colorsEnabled && supportsUnicode()) {
297
- console.log(renderTerminalQr(url, true));
300
+ (await renderTerminalQr(url))
301
+ .split("\n")
302
+ .forEach((line) => printRail(` ${line}`));
298
303
  }
299
304
  else {
300
- qrcode.generate(url, { small: true }, (code) => console.log(code));
305
+ qrcode.generate(url, { small: true }, (code) => {
306
+ code.split("\n").forEach((line) => printRail(` ${line}`));
307
+ });
301
308
  }
302
- console.log(` ${c.teal("URL")} ${url}\n`);
309
+ printRail();
310
+ printRail(` ${c.teal("URL")} ${url}`);
311
+ printRail();
312
+ console.log();
303
313
  }
@@ -19,7 +19,7 @@ export declare class CliUi {
19
19
  spinner(text: string): SpinnerHandle;
20
20
  done(message: string, success?: boolean): void;
21
21
  downloadArtifact(url: string, expiresAt?: string): void;
22
- devServerQr(url: string): void;
22
+ devServerQr(url: string): Promise<void>;
23
23
  }
24
24
  export declare function createCliUi(args: string[]): CliUi;
25
25
  export { terminalOptions };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAEL,eAAe,EACf,KAAK,eAAe,EACrB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,qBAAa,KAAK;IAChB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAElC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;gBAElB,IAAI,EAAE,MAAM,EAAE;IAU1B,MAAM,IAAI,IAAI;IAId,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI3B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI9B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK5B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI5B,SAAS,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI;IAKtC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI;IAI5C,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI;IAKzC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc;IAIvC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa;IAIpC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,UAAO,GAAG,IAAI;IAI3C,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAIvD,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAS/B;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,CAEjD;AAED,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,KAAK,MAAM,EACX,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAEL,eAAe,EACf,KAAK,eAAe,EACrB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,qBAAa,KAAK;IAChB,QAAQ,CAAC,OAAO,EAAE,eAAe,CAAC;IAElC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;gBAElB,IAAI,EAAE,MAAM,EAAE;IAU1B,MAAM,IAAI,IAAI;IAId,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAI3B,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI9B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI3B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAK5B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI5B,SAAS,CAAC,QAAQ,EAAE,WAAW,GAAG,IAAI;IAKtC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI;IAI5C,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI;IAKzC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc;IAIvC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa;IAIpC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,UAAO,GAAG,IAAI;IAI3C,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;IAIjD,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAS9C;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,CAEjD;AAED,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC"}
package/dist/ui/index.js CHANGED
@@ -66,11 +66,11 @@ export class CliUi {
66
66
  downloadArtifact(url, expiresAt) {
67
67
  downloadArtifact(url, expiresAt, this.interactive);
68
68
  }
69
- devServerQr(url) {
69
+ async devServerQr(url) {
70
70
  // Rspeedy suppresses its own QR when its stdout is piped. LynxShip
71
71
  // captures that output, so render the fallback unless the caller asked
72
72
  // for machine JSON, quiet output, or an explicitly non-interactive run.
73
- devServerQr(url, !this.options.json && !this.options.quiet && !this.options.nonInteractive);
73
+ await devServerQr(url, !this.options.json && !this.options.quiet && !this.options.nonInteractive);
74
74
  }
75
75
  }
76
76
  export function createCliUi(args) {
package/dist/ui/qr.d.ts CHANGED
@@ -53,11 +53,9 @@ export declare const LYNXSHIP_QR_STYLE: {
53
53
  };
54
54
  };
55
55
  /**
56
- * Render the same QR styling tokens as qr-code-styling in a terminal.
57
- *
58
- * qr-code-styling produces SVG/canvas output, which a normal terminal cannot
59
- * display. The terminal adapter therefore uses its public QR matrix engine
60
- * and applies the exact shared ECC, gradient, rotation and quiet-zone values.
56
+ * Generate the QR with qr-code-styling itself, exactly like the WISA renderer.
57
+ * The SVG is then rasterized only to make the library's real rounded shapes
58
+ * displayable in a normal terminal; no QR modules are recreated by LynxShip.
61
59
  */
62
- export declare function renderTerminalQr(data: string, colored: boolean): string;
60
+ export declare function renderTerminalQr(data: string): Promise<string>;
63
61
  //# sourceMappingURL=qr.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"qr.d.ts","sourceRoot":"","sources":["../../src/ui/qr.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCpB,CAAC;AAoEX;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAkCvE"}
1
+ {"version":3,"file":"qr.d.ts","sourceRoot":"","sources":["../../src/ui/qr.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwCpB,CAAC;AAyLX;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBpE"}
package/dist/ui/qr.js CHANGED
@@ -1,5 +1,6 @@
1
- import qrcodeGenerator from "qrcode-generator";
2
1
  import { LYNX_BRAND } from "./colors.js";
2
+ import { terminalWidth } from "./terminal.js";
3
+ import { createRequire } from "node:module";
3
4
  export const LYNXSHIP_QR_STYLE = {
4
5
  type: "svg",
5
6
  width: 300,
@@ -41,84 +42,139 @@ export const LYNXSHIP_QR_STYLE = {
41
42
  },
42
43
  backgroundOptions: { color: "#FFFFFF" },
43
44
  };
44
- const QUIET_ZONE = 2;
45
- const RESET = "\u001b[0m";
46
- const WHITE_BACKGROUND = "\u001b[48;2;255;255;255m";
47
- function hexToRgb(hex) {
48
- const value = hex.replace("#", "");
49
- return [
50
- Number.parseInt(value.slice(0, 2), 16),
51
- Number.parseInt(value.slice(2, 4), 16),
52
- Number.parseInt(value.slice(4, 6), 16),
53
- ];
45
+ async function loadDependencies() {
46
+ const require = createRequire(import.meta.url);
47
+ const qrModule = require("qr-code-styling");
48
+ const jsdomModule = require("jsdom");
49
+ const resvgModule = require("@resvg/resvg-js");
50
+ const pngModule = require("pngjs");
51
+ return {
52
+ QRCodeStyling: "default" in qrModule && qrModule.default
53
+ ? qrModule.default
54
+ : qrModule,
55
+ JSDOM: jsdomModule.JSDOM,
56
+ Resvg: resvgModule.Resvg,
57
+ PNG: pngModule.PNG,
58
+ };
54
59
  }
55
- function colorAt(row, column, size) {
56
- const angle = LYNXSHIP_QR_STYLE.dotsOptions.gradient.rotation;
57
- const x = size <= 1 ? 0 : column / (size - 1);
58
- const y = size <= 1 ? 0 : row / (size - 1);
59
- const projection = x * Math.cos(angle) + y * Math.sin(angle);
60
- const minimum = Math.min(0, Math.cos(angle), Math.sin(angle));
61
- const maximum = Math.max(0, Math.cos(angle), Math.sin(angle));
62
- const ratio = Math.max(0, Math.min(1, (projection - minimum) / (maximum - minimum)));
63
- const start = hexToRgb(LYNXSHIP_QR_STYLE.dotsOptions.gradient.colorStops[0].color);
64
- const end = hexToRgb(LYNXSHIP_QR_STYLE.dotsOptions.gradient.colorStops[1].color);
60
+ function ansiColor(channel, red, green, blue) {
61
+ return "\u001b[" + channel + ";2;" + red + ";" + green + ";" + blue + "m";
62
+ }
63
+ function pixelAt(image, x, y) {
64
+ const offset = (y * image.width + x) * 4;
65
65
  return [
66
- Math.round(start[0] + (end[0] - start[0]) * ratio),
67
- Math.round(start[1] + (end[1] - start[1]) * ratio),
68
- Math.round(start[2] + (end[2] - start[2]) * ratio),
66
+ image.data[offset] ?? 255,
67
+ image.data[offset + 1] ?? 255,
68
+ image.data[offset + 2] ?? 255,
69
+ image.data[offset + 3] ?? 255,
69
70
  ];
70
71
  }
71
- function ansiForeground([red, green, blue]) {
72
- return `\u001b[38;2;${red};${green};${blue}m`;
72
+ function isBackground([red, green, blue, alpha]) {
73
+ return alpha < 16 || (red > 248 && green > 248 && blue > 248);
73
74
  }
74
- function moduleAt(matrix, row, column, size) {
75
- if (row < QUIET_ZONE ||
76
- column < QUIET_ZONE ||
77
- row >= size + QUIET_ZONE ||
78
- column >= size + QUIET_ZONE)
79
- return false;
80
- return matrix.isDark(row - QUIET_ZONE, column - QUIET_ZONE);
75
+ function cropToQrContent(image) {
76
+ let left = image.width;
77
+ let top = image.height;
78
+ let right = -1;
79
+ let bottom = -1;
80
+ for (let y = 0; y < image.height; y += 1) {
81
+ for (let x = 0; x < image.width; x += 1) {
82
+ if (!isBackground(pixelAt(image, x, y))) {
83
+ left = Math.min(left, x);
84
+ top = Math.min(top, y);
85
+ right = Math.max(right, x);
86
+ bottom = Math.max(bottom, y);
87
+ }
88
+ }
89
+ }
90
+ if (right < left || bottom < top)
91
+ return image;
92
+ const padding = Math.max(3, Math.round(Math.min(image.width, image.height) * 0.02));
93
+ const cropLeft = Math.max(0, left - padding);
94
+ const cropTop = Math.max(0, top - padding);
95
+ const cropRight = Math.min(image.width - 1, right + padding);
96
+ const cropBottom = Math.min(image.height - 1, bottom + padding);
97
+ const width = cropRight - cropLeft + 1;
98
+ const height = cropBottom - cropTop + 1;
99
+ const data = new Uint8Array(width * height * 4);
100
+ for (let y = 0; y < height; y += 1) {
101
+ for (let x = 0; x < width; x += 1) {
102
+ const source = ((cropTop + y) * image.width + cropLeft + x) * 4;
103
+ const target = (y * width + x) * 4;
104
+ data[target] = image.data[source] ?? 255;
105
+ data[target + 1] = image.data[source + 1] ?? 255;
106
+ data[target + 2] = image.data[source + 2] ?? 255;
107
+ data[target + 3] = image.data[source + 3] ?? 255;
108
+ }
109
+ }
110
+ return { width, height, data };
81
111
  }
82
- /**
83
- * Render the same QR styling tokens as qr-code-styling in a terminal.
84
- *
85
- * qr-code-styling produces SVG/canvas output, which a normal terminal cannot
86
- * display. The terminal adapter therefore uses its public QR matrix engine
87
- * and applies the exact shared ECC, gradient, rotation and quiet-zone values.
88
- */
89
- export function renderTerminalQr(data, colored) {
90
- const matrix = qrcodeGenerator(0, "H");
91
- matrix.addData(data, "Byte");
92
- matrix.make();
93
- const size = matrix.getModuleCount();
94
- const fullSize = size + QUIET_ZONE * 2;
112
+ function renderRasterInTerminal(image) {
113
+ const columns = Math.max(35, Math.min(72, terminalWidth() - 8));
114
+ const scale = image.width / columns;
115
+ const rows = Math.ceil(image.height / scale / 2);
95
116
  const lines = [];
96
- for (let row = 0; row < fullSize; row += 2) {
97
- let line = colored ? WHITE_BACKGROUND : "";
98
- for (let column = 0; column < fullSize; column += 1) {
99
- const top = moduleAt(matrix, row, column, size);
100
- const bottom = moduleAt(matrix, row + 1, column, size);
101
- if (!top && !bottom) {
117
+ const whiteBackground = ansiColor("48", 255, 255, 255);
118
+ const reset = "\u001b[0m";
119
+ for (let row = 0; row < rows; row += 1) {
120
+ const topY = Math.min(image.height - 1, Math.floor(row * 2 * scale));
121
+ const bottomY = Math.min(image.height - 1, Math.floor((row * 2 + 1) * scale));
122
+ let line = whiteBackground;
123
+ for (let column = 0; column < columns; column += 1) {
124
+ const x = Math.min(image.width - 1, Math.floor(column * scale));
125
+ const top = pixelAt(image, x, topY);
126
+ const bottom = pixelAt(image, x, bottomY);
127
+ const topBackground = isBackground(top);
128
+ const bottomBackground = isBackground(bottom);
129
+ if (topBackground && bottomBackground) {
102
130
  line += " ";
103
- continue;
104
- }
105
- if (!colored) {
106
- line += top && bottom ? "█" : top ? "▀" : "▄";
107
- continue;
108
131
  }
109
- const topColor = colorAt(row, column, fullSize);
110
- const bottomColor = colorAt(row + 1, column, fullSize);
111
- if (top && bottom) {
112
- line += `${ansiForeground(topColor)}█`;
132
+ else if (!topBackground && bottomBackground) {
133
+ line +=
134
+ ansiColor("38", top[0], top[1], top[2]) +
135
+ ansiColor("48", 255, 255, 255) +
136
+ "▀";
113
137
  }
114
- else if (top) {
115
- line += `${ansiForeground(topColor)}▀`;
138
+ else if (topBackground && !bottomBackground) {
139
+ line +=
140
+ ansiColor("38", bottom[0], bottom[1], bottom[2]) +
141
+ ansiColor("48", 255, 255, 255) +
142
+ "▄";
116
143
  }
117
144
  else {
118
- line += `${ansiForeground(bottomColor)}▄`;
145
+ line +=
146
+ ansiColor("38", top[0], top[1], top[2]) +
147
+ ansiColor("48", bottom[0], bottom[1], bottom[2]) +
148
+ "▀";
119
149
  }
120
150
  }
121
- lines.push(`${line}${colored ? RESET : ""}`);
151
+ lines.push(line + reset);
122
152
  }
123
153
  return lines.join("\n");
124
154
  }
155
+ /**
156
+ * Generate the QR with qr-code-styling itself, exactly like the WISA renderer.
157
+ * The SVG is then rasterized only to make the library's real rounded shapes
158
+ * displayable in a normal terminal; no QR modules are recreated by LynxShip.
159
+ */
160
+ export async function renderTerminalQr(data) {
161
+ const { QRCodeStyling, JSDOM, Resvg, PNG } = await loadDependencies();
162
+ const qrCode = new QRCodeStyling({
163
+ jsdom: JSDOM,
164
+ data,
165
+ ...LYNXSHIP_QR_STYLE,
166
+ });
167
+ const svg = await qrCode.getRawData("svg");
168
+ if (!svg)
169
+ throw new Error("qr-code-styling returned no SVG data");
170
+ const svgText = Buffer.isBuffer(svg)
171
+ ? svg.toString("utf8")
172
+ : new TextDecoder().decode(svg);
173
+ // jsdom serializes SVG references as url('#id'). Normalize only those
174
+ // references for resvg; the SVG generated by qr-code-styling is unchanged
175
+ // for browsers and for the file returned by the library.
176
+ const rasterSvg = svgText.replaceAll("url('#", "url(#").replaceAll("')", ")");
177
+ const png = new Resvg(rasterSvg).render().asPng();
178
+ const image = cropToQrContent(PNG.sync.read(Buffer.from(png)));
179
+ return renderRasterInTerminal(image);
180
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynxship/cli",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Build, sign, store, submit and update LynxJS applications from the terminal.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -67,17 +67,20 @@
67
67
  "chalk": "5.4.1",
68
68
  "cli-progress": "3.12.0",
69
69
  "ora": "8.2.0",
70
- "qrcode-generator": "1.4.4",
71
70
  "qrcode-terminal": "0.12.0",
72
71
  "qr-code-styling": "1.9.2",
73
- "@lynxship/contracts": "0.1.0",
72
+ "@resvg/resvg-js": "2.6.2",
73
+ "jsdom": "29.1.1",
74
+ "pngjs": "7.0.0",
74
75
  "@lynxship/build-orchestrator": "0.1.0",
75
76
  "@lynxship/db": "0.1.0",
76
- "@lynxship/submit": "0.1.0",
77
- "@lynxship/signing": "0.1.0"
77
+ "@lynxship/contracts": "0.1.0",
78
+ "@lynxship/signing": "0.1.0",
79
+ "@lynxship/submit": "0.1.0"
78
80
  },
79
81
  "devDependencies": {
80
82
  "@types/cli-progress": "3.11.6",
83
+ "@types/pngjs": "6.0.4",
81
84
  "@types/qrcode-terminal": "0.12.2"
82
85
  },
83
86
  "scripts": {