@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 +6 -4
- package/dist/ui/components.d.ts +1 -1
- package/dist/ui/components.d.ts.map +1 -1
- package/dist/ui/components.js +15 -5
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +2 -2
- package/dist/ui/qr.d.ts +4 -6
- package/dist/ui/qr.d.ts.map +1 -1
- package/dist/ui/qr.js +121 -65
- package/package.json +8 -5
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(
|
|
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}` }, {
|
package/dist/ui/components.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/ui/components.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
300
|
+
(await renderTerminalQr(url))
|
|
301
|
+
.split("\n")
|
|
302
|
+
.forEach((line) => printRail(` ${line}`));
|
|
298
303
|
}
|
|
299
304
|
else {
|
|
300
|
-
qrcode.generate(url, { small: true }, (code) =>
|
|
305
|
+
qrcode.generate(url, { small: true }, (code) => {
|
|
306
|
+
code.split("\n").forEach((line) => printRail(` ${line}`));
|
|
307
|
+
});
|
|
301
308
|
}
|
|
302
|
-
|
|
309
|
+
printRail();
|
|
310
|
+
printRail(` ${c.teal("URL")} ${url}`);
|
|
311
|
+
printRail();
|
|
312
|
+
console.log();
|
|
303
313
|
}
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -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 };
|
package/dist/ui/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
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
|
|
60
|
+
export declare function renderTerminalQr(data: string): Promise<string>;
|
|
63
61
|
//# sourceMappingURL=qr.d.ts.map
|
package/dist/ui/qr.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qr.d.ts","sourceRoot":"","sources":["../../src/ui/qr.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
72
|
-
return
|
|
72
|
+
function isBackground([red, green, blue, alpha]) {
|
|
73
|
+
return alpha < 16 || (red > 248 && green > 248 && blue > 248);
|
|
73
74
|
}
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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 (
|
|
115
|
-
line +=
|
|
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 +=
|
|
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(
|
|
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.
|
|
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
|
-
"@
|
|
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/
|
|
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": {
|