@lynxship/cli 0.1.12 → 0.1.14
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 +33 -2
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/dev-qr.d.ts.map +1 -1
- package/dist/dev-qr.js +5 -3
- package/dist/guidance.d.ts.map +1 -1
- package/dist/guidance.js +11 -5
- package/dist/harmony-build.d.ts +1 -0
- package/dist/harmony-build.d.ts.map +1 -1
- package/dist/harmony-build.js +19 -6
- package/dist/index.js +20 -2
- package/dist/ios-build.d.ts +21 -0
- package/dist/ios-build.d.ts.map +1 -1
- package/dist/ios-build.js +189 -3
- package/dist/ios-host.d.ts +2 -0
- package/dist/ios-host.d.ts.map +1 -1
- package/dist/ios-host.js +2 -0
- package/dist/ui/components.d.ts.map +1 -1
- package/dist/ui/components.js +7 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +4 -1
- package/dist/ui/qr.d.ts +63 -0
- package/dist/ui/qr.d.ts.map +1 -0
- package/dist/ui/qr.js +124 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -212,6 +212,11 @@ Next steps
|
|
|
212
212
|
```
|
|
213
213
|
|
|
214
214
|
`lynxship dev` is for Lynx Explorer and live QR/HMR development. The Android
|
|
215
|
+
Rspeedy URLs are rendered by LynxShip's own compact QR adapter so the QR
|
|
216
|
+
remains visible when child-process output is captured. It uses the WISA
|
|
217
|
+
renderer settings (H-level correction, dots, dotted corners and 50-degree
|
|
218
|
+
linear gradient) with LynxShip pink-to-cyan colors and no center logo;
|
|
219
|
+
non-interactive output keeps the URL and omits terminal decoration. The Android
|
|
215
220
|
host command creates the native Gradle project needed for a real APK/AAB. An
|
|
216
221
|
interactive real build creates a missing host after asking for the application
|
|
217
222
|
ID; CI must pass `--application-id`. Existing native directories are never
|
|
@@ -277,6 +282,10 @@ Use the official Lynx Harmony integration under `harmony/`. The project must
|
|
|
277
282
|
provide `hvigorw`, `hvigorfile.ts`, `build-profile.json5` and
|
|
278
283
|
`oh-package.json5`. LynxShip runs `ohpm install`, the pinned wrapper in release
|
|
279
284
|
mode, then verifies the signed HAP with the official `hap-sign-tool.jar`.
|
|
285
|
+
Before Hvigor runs, it copies root `.lynx.bundle` files plus `dist/static` and
|
|
286
|
+
`dist/async` into the configured HarmonyOS `rawfile` directory. This keeps
|
|
287
|
+
imported images and lazy bundles inside the HAP instead of leaving them only in
|
|
288
|
+
the JavaScript build output.
|
|
280
289
|
|
|
281
290
|
Optional profile overrides are explicit and remain project-owned:
|
|
282
291
|
|
|
@@ -406,6 +415,19 @@ with:
|
|
|
406
415
|
lynxship ios host init --bundle-identifier com.example.myapp
|
|
407
416
|
```
|
|
408
417
|
|
|
418
|
+
If the project already has its app icon, provide the required high-resolution
|
|
419
|
+
PNG during host creation:
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
lynxship ios host init --bundle-identifier com.example.myapp --icon ./assets/icon.png
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
The icon must be a square 1024x1024 PNG. For an existing host, set
|
|
426
|
+
`build.production.ios.appIcon` in `lynxship.json`, or add `icon.png` in the
|
|
427
|
+
project root; LynxShip discovers it before the next native build. Simulator
|
|
428
|
+
builds may fall back to a square `lynx-logo*.png` emitted by Rspeedy for local
|
|
429
|
+
preview only; provide the project icon for an IPA or store release.
|
|
430
|
+
|
|
409
431
|
The command refuses to overwrite an existing `ios/` directory and creates a
|
|
410
432
|
Swift host based on Lynx's official integration shape: `LynxEnv`, `LynxView`,
|
|
411
433
|
`LynxTemplateProvider`, `Podfile`, `ExportOptions.plist` and a bundle sync
|
|
@@ -421,9 +443,18 @@ lynxship build --project-dir ./my-lynx-app --platform ios --simulator --profile
|
|
|
421
443
|
```
|
|
422
444
|
|
|
423
445
|
This uses Xcode's `iphonesimulator` SDK, boots an available Simulator, creates
|
|
424
|
-
and installs a local `.app`,
|
|
446
|
+
and installs a local `.app`, opens the Simulator and launches the app in an
|
|
447
|
+
interactive terminal, and does not require a physical iPhone, Apple
|
|
425
448
|
Distribution certificate, provisioning profile or R2 upload. Use the
|
|
426
|
-
production profile only for a signed device IPA.
|
|
449
|
+
production profile only for a signed device IPA. Pass `--no-autostart` to
|
|
450
|
+
install without launching, or use `run --platform ios --simulator --launch
|
|
451
|
+
--artifact <app>` to launch an already-built app.
|
|
452
|
+
|
|
453
|
+
Rspeedy can emit external files beside the main bundle. The iOS adapter copies
|
|
454
|
+
all root `.lynx.bundle` files plus `dist/static` and `dist/async` into the
|
|
455
|
+
compiled `.app` before installation/export, so imported images and lazy assets
|
|
456
|
+
are included in Simulator and device builds. The same synchronization is
|
|
457
|
+
applied to older LynxShip-generated iOS hosts.
|
|
427
458
|
|
|
428
459
|
Run the complete first-use diagnosis before building:
|
|
429
460
|
|
package/dist/config.d.ts
CHANGED
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChC,GAAG,CAAC,EAAE;QACJ,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChC,GAAG,CAAC,EAAE;QACJ,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;QAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAChC,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,GAAG,CAAC,EAAE;QACJ,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE;QACJ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,cAAc,CAAC,EAAE;QAAE,MAAM,EAAE,aAAa,GAAG,QAAQ,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE;QACP,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE;YAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KAC1C,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,cAAc,EAAE,cAsB5B,CAAC;AAaF,wBAAgB,cAAc,CAC5B,MAAM,EAAE,OAAO,EACf,OAAO,GAAE;IAAE,EAAE,CAAC,EAAE,OAAO,CAAA;CAAO,GAC7B,cAAc,CAyChB;AAED,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,EAAE,CAAC,EAAE,OAAO,CAAA;CAAO,GAC7B,OAAO,CAAC,cAAc,CAAC,CAazB;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,cAAc,EACtB,IAAI,SAAe,GAClB,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAejC;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAOrD"}
|
package/dist/dev-qr.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-qr.d.ts","sourceRoot":"","sources":["../src/dev-qr.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dev-qr.d.ts","sourceRoot":"","sources":["../src/dev-qr.ts"],"names":[],"mappings":"AAOA,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOpE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5D"}
|
package/dist/dev-qr.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
|
|
1
|
+
// Rspeedy's QR plugin accepts custom schemas, so a dev URL is not always HTTP.
|
|
2
|
+
const URL_PATTERN = /[a-z][a-z\d+.-]*:\/\/[^\s<>"'`]+/gi;
|
|
2
3
|
function cleanUrl(value) {
|
|
3
4
|
return value.replace(/[),.;!?]+$/u, "");
|
|
4
5
|
}
|
|
5
6
|
export function extractDevServerUrl(line) {
|
|
6
7
|
const urls = (line.match(URL_PATTERN) ?? []).map(cleanUrl);
|
|
7
8
|
return (urls.find((url) => url.includes("fullscreen=true")) ??
|
|
8
|
-
urls.find((url) => url.includes(".lynx.bundle"))
|
|
9
|
+
urls.find((url) => url.includes(".lynx.bundle")) ??
|
|
10
|
+
urls[0]);
|
|
9
11
|
}
|
|
10
12
|
export function shouldPrintDevServerQr(line) {
|
|
11
|
-
return /(?:default:|fullscreen
|
|
13
|
+
return /(?:default:|fullscreen|\bready\b|\blynx\b)/iu.test(line);
|
|
12
14
|
}
|
package/dist/guidance.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guidance.d.ts","sourceRoot":"","sources":["../src/guidance.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAChC;
|
|
1
|
+
{"version":3,"file":"guidance.d.ts","sourceRoot":"","sources":["../src/guidance.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;CAChC;AA6mBD,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,eAAoB,GAC5B,WAAW,CAEb"}
|
package/dist/guidance.js
CHANGED
|
@@ -253,8 +253,13 @@ const guidance = {
|
|
|
253
253
|
note: "Simulator .app artifacts stay local; use a signed production device build for R2 upload and distribution.",
|
|
254
254
|
},
|
|
255
255
|
IOS_COCOAPODS_REQUIRED: {
|
|
256
|
-
commands: [
|
|
257
|
-
|
|
256
|
+
commands: [
|
|
257
|
+
"brew install cocoapods",
|
|
258
|
+
"cd ios && pod install --repo-update",
|
|
259
|
+
"lynxship doctor --platform ios --profile simulator",
|
|
260
|
+
"lynxship build --platform ios --simulator --profile simulator --no-upload",
|
|
261
|
+
],
|
|
262
|
+
note: "CocoaPods is required when the iOS host contains a Podfile. The first install refreshes the CocoaPods specs repository.",
|
|
258
263
|
},
|
|
259
264
|
IOS_PROJECT_REQUIRED: {
|
|
260
265
|
commands: [
|
|
@@ -487,10 +492,11 @@ function guidanceForErrorBase(error) {
|
|
|
487
492
|
if (/xcode|xcrun|cocoapods|pod install/i.test(message)) {
|
|
488
493
|
return {
|
|
489
494
|
commands: [
|
|
490
|
-
"
|
|
491
|
-
"lynxship
|
|
495
|
+
"cd ios && pod install --repo-update",
|
|
496
|
+
"lynxship doctor --platform ios --profile simulator",
|
|
497
|
+
"lynxship build --platform ios --simulator --profile simulator --no-upload",
|
|
492
498
|
],
|
|
493
|
-
note: "Check macOS, Xcode command-line tools and CocoaPods.",
|
|
499
|
+
note: "Check macOS, Xcode command-line tools and CocoaPods. For a first install, refresh the CocoaPods specs repository.",
|
|
494
500
|
};
|
|
495
501
|
}
|
|
496
502
|
return { commands: [] };
|
package/dist/harmony-build.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare function harmonyToolchain(root: string): {
|
|
|
17
17
|
hdc: boolean;
|
|
18
18
|
message: string;
|
|
19
19
|
};
|
|
20
|
+
export declare function syncHarmonyAssets(root: string, profile: BuildProfile): Promise<string[]>;
|
|
20
21
|
export declare function runRealHarmonyBuild(job: BuildJob, options: HarmonyBuildOptions): Promise<BuildJob>;
|
|
21
22
|
export {};
|
|
22
23
|
//# sourceMappingURL=harmony-build.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"harmony-build.d.ts","sourceRoot":"","sources":["../src/harmony-build.ts"],"names":[],"mappings":"AAIA,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAeD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOpD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAC9C,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAcA;
|
|
1
|
+
{"version":3,"file":"harmony-build.d.ts","sourceRoot":"","sources":["../src/harmony-build.ts"],"names":[],"mappings":"AAIA,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAeD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOpD;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAC9C,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAcA;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,MAAM,EAAE,CAAC,CA4BnB;AA2GD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,QAAQ,CAAC,CAoGnB"}
|
package/dist/harmony-build.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { copyFile, mkdir, mkdtemp, readdir, rm } from "node:fs/promises";
|
|
1
|
+
import { copyFile, cp, mkdir, mkdtemp, readdir, rm } from "node:fs/promises";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
@@ -37,7 +37,7 @@ export function harmonyToolchain(root) {
|
|
|
37
37
|
: "Harmony host requires the project hvigorw wrapper and ohpm from DevEco Studio",
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
-
async function
|
|
40
|
+
export async function syncHarmonyAssets(root, profile) {
|
|
41
41
|
const dist = join(root, "dist");
|
|
42
42
|
const entries = await readdir(dist, { withFileTypes: true }).catch(() => []);
|
|
43
43
|
const bundles = entries
|
|
@@ -46,9 +46,20 @@ async function syncBundle(root, profile) {
|
|
|
46
46
|
assert(bundles.length > 0, "LYNX_BUNDLE_MISSING", `No .lynx.bundle was found in ${dist}. Check the Rspeedy output configuration.`);
|
|
47
47
|
const target = resolve(root, profile.harmony?.bundleDir ?? "harmony/entry/src/main/resources/rawfile");
|
|
48
48
|
await mkdir(target, { recursive: true });
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
const outputNames = [...bundles, "async", "static"];
|
|
50
|
+
const copied = [];
|
|
51
|
+
for (const name of outputNames) {
|
|
52
|
+
const source = join(dist, name);
|
|
53
|
+
if (!existsSync(source))
|
|
54
|
+
continue;
|
|
55
|
+
await rm(join(target, name), { recursive: true, force: true });
|
|
56
|
+
if (name.endsWith(".lynx.bundle"))
|
|
57
|
+
await copyFile(source, join(target, name));
|
|
58
|
+
else
|
|
59
|
+
await cp(source, join(target, name), { recursive: true, force: true });
|
|
60
|
+
copied.push(name);
|
|
61
|
+
}
|
|
62
|
+
return copied;
|
|
52
63
|
}
|
|
53
64
|
async function findHap(root, configured) {
|
|
54
65
|
if (configured) {
|
|
@@ -133,7 +144,9 @@ export async function runRealHarmonyBuild(job, options) {
|
|
|
133
144
|
});
|
|
134
145
|
}
|
|
135
146
|
step("Syncing bundle into the HarmonyOS HAP resources…", 20);
|
|
136
|
-
await
|
|
147
|
+
const copiedAssets = await syncHarmonyAssets(options.root, options.profile);
|
|
148
|
+
for (const name of copiedAssets)
|
|
149
|
+
options.onEvent?.(`Synced dist/${name} into HarmonyOS rawfile resources`);
|
|
137
150
|
transitionBuild(job, "queued", "HarmonyOS build queued locally");
|
|
138
151
|
transitionBuild(job, "provisioning", "HarmonyOS Hvigor toolchain selected");
|
|
139
152
|
transitionBuild(job, "installing_dependencies", "HarmonyOS dependencies selected");
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { DEFAULT_CONFIG, loadConfig, platformValue, resolveProfile, } from "./co
|
|
|
12
12
|
import { hasAndroidHost, isSupportedAndroidPlatform, runRealAndroidBuild, } from "./android-build.js";
|
|
13
13
|
import { initializeAndroidHost, suggestedAndroidApplicationId, } from "./android-host.js";
|
|
14
14
|
import { initializeIosHost, suggestedIosBundleIdentifier } from "./ios-host.js";
|
|
15
|
-
import { hasIosHost, runRealIosBuild } from "./ios-build.js";
|
|
15
|
+
import { hasIosHost, launchIosSimulatorApp, runRealIosBuild, } from "./ios-build.js";
|
|
16
16
|
import { configureAndroid, configureAppStoreConnect, configureGooglePlay, configureR2, } from "./configure.js";
|
|
17
17
|
import { fetchOtaPublicKey, publishOtaRelease, rollbackOtaRelease, submitRealArtifact, } from "./remote.js";
|
|
18
18
|
import { uploadR2Artifact } from "./r2.js";
|
|
@@ -209,7 +209,7 @@ async function saveState(state, repository, builds, submissions) {
|
|
|
209
209
|
}
|
|
210
210
|
let stateSaveQueue = Promise.resolve();
|
|
211
211
|
async function executeBuild(options, showResult = true) {
|
|
212
|
-
const { config, profile, platform, skipUpload, wait, local, state, repository, builds, submissions, progress: sharedProgress, progressPrefix, onProgress, onEvent, skipBundleBuild, simulator = false, simulatorDevice, } = options;
|
|
212
|
+
const { config, profile, platform, skipUpload, wait, local, state, repository, builds, submissions, progress: sharedProgress, progressPrefix, onProgress, onEvent, skipBundleBuild, simulator = false, simulatorDevice, simulatorAutostart = false, } = options;
|
|
213
213
|
if (wait && !local)
|
|
214
214
|
await ensureNativeHostForBuild(platform, profile, config);
|
|
215
215
|
// Host initialization can write lynxship.json (for example, the generated
|
|
@@ -281,6 +281,7 @@ async function executeBuild(options, showResult = true) {
|
|
|
281
281
|
profile: resolvedBuildProfile,
|
|
282
282
|
simulator,
|
|
283
283
|
simulatorDevice,
|
|
284
|
+
simulatorAutostart,
|
|
284
285
|
uploadArtifacts: !skipUpload,
|
|
285
286
|
skipBundleBuild,
|
|
286
287
|
quiet: json,
|
|
@@ -459,6 +460,8 @@ Build options:
|
|
|
459
460
|
--no-upload Keep the artifact local and skip R2 (CI verification)
|
|
460
461
|
--simulator Build and install an iOS Simulator .app locally
|
|
461
462
|
--device <id> Select the iOS Simulator device for a simulator build
|
|
463
|
+
--autostart Open and launch the iOS Simulator app after install
|
|
464
|
+
--no-autostart Install the Simulator app without launching it
|
|
462
465
|
--allow-unsigned Allow an unsigned Desktop artifact only with --no-upload (local tests)
|
|
463
466
|
--local Use the contract-only build path for tests
|
|
464
467
|
|
|
@@ -490,6 +493,7 @@ Device and diagnostics options:
|
|
|
490
493
|
run --artifact <path> Install a specific APK, IPA, app or signed HAP
|
|
491
494
|
run --device <id> Select an Android, iOS or HarmonyOS device
|
|
492
495
|
run --simulator Install an iOS .app with simctl
|
|
496
|
+
run --launch Launch the installed iOS Simulator app after install
|
|
493
497
|
logs --device <id> Select the device or simulator for native logs
|
|
494
498
|
|
|
495
499
|
Global options:
|
|
@@ -503,6 +507,7 @@ Global options:
|
|
|
503
507
|
--project-id <id> Project ID used by init
|
|
504
508
|
--application-id <id> Android package/application ID for host init
|
|
505
509
|
--bundle-identifier <id> iOS bundle identifier for host init
|
|
510
|
+
--icon <path> 1024x1024 PNG app icon for iOS host init
|
|
506
511
|
--library-dir <path> Native library directory for autolink codegen
|
|
507
512
|
--simulator Build/install an iOS .app or install one with simctl
|
|
508
513
|
--help Show this complete command reference
|
|
@@ -594,6 +599,7 @@ async function ensureNativeHostForBuild(platform, profile, config) {
|
|
|
594
599
|
const result = await initializeIosHost(root, {
|
|
595
600
|
bundleIdentifier,
|
|
596
601
|
appName: basename(root),
|
|
602
|
+
appIcon: flag("--icon") ?? undefined,
|
|
597
603
|
});
|
|
598
604
|
ui.success(`iOS host created: ${result.directory}`);
|
|
599
605
|
}
|
|
@@ -753,6 +759,12 @@ async function runDevice() {
|
|
|
753
759
|
quiet: json,
|
|
754
760
|
onOutput: (line) => ui.info(`│ ${line}`),
|
|
755
761
|
});
|
|
762
|
+
if (args.includes("--launch")) {
|
|
763
|
+
await launchIosSimulatorApp(root, device, artifactPath, {
|
|
764
|
+
quiet: json,
|
|
765
|
+
onEvent: (line) => ui.info(`│ ${line}`),
|
|
766
|
+
});
|
|
767
|
+
}
|
|
756
768
|
}
|
|
757
769
|
else {
|
|
758
770
|
assert(device !== "booted", "IOS_DEVICE_REQUIRED", "A physical iOS install requires --device <device-identifier>; use --simulator for a booted simulator.");
|
|
@@ -1228,6 +1240,7 @@ async function main() {
|
|
|
1228
1240
|
const result = await initializeIosHost(root, {
|
|
1229
1241
|
bundleIdentifier,
|
|
1230
1242
|
appName: basename(root),
|
|
1243
|
+
appIcon: flag("--icon") ?? undefined,
|
|
1231
1244
|
});
|
|
1232
1245
|
ui.success(`iOS host created: ${result.directory}`);
|
|
1233
1246
|
printValue({
|
|
@@ -1671,6 +1684,9 @@ async function main() {
|
|
|
1671
1684
|
const config = await loadConfig(root);
|
|
1672
1685
|
const profile = flag("--profile", simulator ? "simulator" : "production");
|
|
1673
1686
|
const simulatorDevice = flag("--device") ?? undefined;
|
|
1687
|
+
const simulatorAutostart = simulator &&
|
|
1688
|
+
!args.includes("--no-autostart") &&
|
|
1689
|
+
(args.includes("--autostart") || (!json && ui.interactive));
|
|
1674
1690
|
const wait = !args.includes("--no-wait");
|
|
1675
1691
|
const local = args.includes("--local");
|
|
1676
1692
|
const platforms = buildAll
|
|
@@ -1692,6 +1708,7 @@ async function main() {
|
|
|
1692
1708
|
local,
|
|
1693
1709
|
simulator,
|
|
1694
1710
|
simulatorDevice,
|
|
1711
|
+
simulatorAutostart,
|
|
1695
1712
|
state,
|
|
1696
1713
|
repository,
|
|
1697
1714
|
builds,
|
|
@@ -1723,6 +1740,7 @@ async function main() {
|
|
|
1723
1740
|
local,
|
|
1724
1741
|
simulator,
|
|
1725
1742
|
simulatorDevice,
|
|
1743
|
+
simulatorAutostart,
|
|
1726
1744
|
state,
|
|
1727
1745
|
repository,
|
|
1728
1746
|
builds,
|
package/dist/ios-build.d.ts
CHANGED
|
@@ -6,11 +6,32 @@ interface IosBuildOptions {
|
|
|
6
6
|
uploadArtifacts?: boolean;
|
|
7
7
|
simulator?: boolean;
|
|
8
8
|
simulatorDevice?: string;
|
|
9
|
+
simulatorAutostart?: boolean;
|
|
9
10
|
skipBundleBuild?: boolean;
|
|
10
11
|
quiet?: boolean;
|
|
11
12
|
onEvent?: (message: string) => void;
|
|
12
13
|
onProgress?: (value?: number, label?: string) => void;
|
|
13
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Rspeedy leaves external resources beside the Lynx bundle. Xcode does not
|
|
17
|
+
* know about those generated files, so copy the complete output into the
|
|
18
|
+
* final app bundle after the native host has been compiled. This also makes
|
|
19
|
+
* older LynxShip-generated iOS hosts behave correctly without editing their
|
|
20
|
+
* Xcode project files.
|
|
21
|
+
*/
|
|
22
|
+
export declare function syncIosRuntimeResources(root: string, appBundle: string): Promise<string[]>;
|
|
23
|
+
/**
|
|
24
|
+
* Apply a project-owned 1024x1024 PNG to the generated or existing Xcode
|
|
25
|
+
* AppIcon set. A missing icon is reported as a warning by the caller rather
|
|
26
|
+
* than silently inventing product branding.
|
|
27
|
+
*/
|
|
28
|
+
export declare function prepareIosAppIcon(root: string, configured?: string, options?: {
|
|
29
|
+
allowFallback?: boolean;
|
|
30
|
+
}): Promise<string | undefined>;
|
|
31
|
+
export declare function launchIosSimulatorApp(root: string, device: string, appBundle: string, options?: {
|
|
32
|
+
quiet?: boolean;
|
|
33
|
+
onEvent?: (message: string) => void;
|
|
34
|
+
}): Promise<void>;
|
|
14
35
|
export declare function hasIosHost(root: string, profile?: BuildProfile): boolean;
|
|
15
36
|
export declare function runRealIosBuild(job: BuildJob, options: IosBuildOptions): Promise<BuildJob>;
|
|
16
37
|
export {};
|
package/dist/ios-build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ios-build.d.ts","sourceRoot":"","sources":["../src/ios-build.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ios-build.d.ts","sourceRoot":"","sources":["../src/ios-build.ts"],"names":[],"mappings":"AAYA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMhD,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAyHD;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAoBnB;AAoED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAuD7B;AAgCD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IACP,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC,GACL,OAAO,CAAC,IAAI,CAAC,CAcf;AAkND,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAcxE;AA+DD,wBAAsB,eAAe,CACnC,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,QAAQ,CAAC,CAqNnB"}
|
package/dist/ios-build.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { copyFile, mkdir, readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { copyFile, cp, mkdir, readdir, readFile, rm, writeFile, } from "node:fs/promises";
|
|
3
3
|
import { existsSync, readdirSync } from "node:fs";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import { assert, sha256 } from "@lynxship/contracts";
|
|
@@ -56,6 +56,159 @@ async function findSimulatorApp(root, derivedData, configuration, scheme) {
|
|
|
56
56
|
assert(app, "IOS_SIMULATOR_ARTIFACT_MISSING", `Xcode did not produce a Simulator .app under ${products}`);
|
|
57
57
|
return join(products, app.name);
|
|
58
58
|
}
|
|
59
|
+
async function findArchiveApp(archivePath) {
|
|
60
|
+
const products = join(archivePath, "Products", "Applications");
|
|
61
|
+
const entries = await readdir(products, { withFileTypes: true }).catch(() => []);
|
|
62
|
+
const app = entries.find((entry) => entry.isDirectory() && entry.name.endsWith(".app"));
|
|
63
|
+
assert(app, "IOS_ARCHIVE_ARTIFACT_MISSING", `Xcode did not produce an app bundle under ${products}`);
|
|
64
|
+
return join(products, app.name);
|
|
65
|
+
}
|
|
66
|
+
async function copyIosOutput(source, target) {
|
|
67
|
+
if (!existsSync(source))
|
|
68
|
+
return false;
|
|
69
|
+
await rm(target, { recursive: true, force: true });
|
|
70
|
+
await cp(source, target, { recursive: true, force: true });
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Rspeedy leaves external resources beside the Lynx bundle. Xcode does not
|
|
75
|
+
* know about those generated files, so copy the complete output into the
|
|
76
|
+
* final app bundle after the native host has been compiled. This also makes
|
|
77
|
+
* older LynxShip-generated iOS hosts behave correctly without editing their
|
|
78
|
+
* Xcode project files.
|
|
79
|
+
*/
|
|
80
|
+
export async function syncIosRuntimeResources(root, appBundle) {
|
|
81
|
+
const distRoot = join(root, "dist");
|
|
82
|
+
const entries = await readdir(distRoot, { withFileTypes: true }).catch(() => []);
|
|
83
|
+
const bundles = entries
|
|
84
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".lynx.bundle"))
|
|
85
|
+
.map((entry) => entry.name);
|
|
86
|
+
assert(bundles.length > 0, "LYNX_BUNDLE_MISSING", `No .lynx.bundle was found in ${distRoot}. Check the Rspeedy output configuration.`);
|
|
87
|
+
const copied = [];
|
|
88
|
+
for (const name of [...bundles, "async", "static"]) {
|
|
89
|
+
if (await copyIosOutput(join(distRoot, name), join(appBundle, name)))
|
|
90
|
+
copied.push(name);
|
|
91
|
+
}
|
|
92
|
+
return copied;
|
|
93
|
+
}
|
|
94
|
+
async function findAppIconSet(directory) {
|
|
95
|
+
const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
const path = join(directory, entry.name);
|
|
98
|
+
if (entry.isDirectory() && entry.name === "AppIcon.appiconset") {
|
|
99
|
+
if (existsSync(join(path, "Contents.json")))
|
|
100
|
+
return path;
|
|
101
|
+
}
|
|
102
|
+
if (entry.isDirectory() &&
|
|
103
|
+
!entry.name.endsWith(".xcodeproj") &&
|
|
104
|
+
!entry.name.endsWith(".xcworkspace")) {
|
|
105
|
+
const result = await findAppIconSet(path);
|
|
106
|
+
if (result)
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
function pngDimensions(content) {
|
|
113
|
+
assert(content.length >= 24 &&
|
|
114
|
+
content.subarray(0, 8).toString("hex") === "89504e470d0a1a0a", "IOS_APP_ICON_INVALID", "The iOS app icon must be a valid PNG file.");
|
|
115
|
+
return { width: content.readUInt32BE(16), height: content.readUInt32BE(20) };
|
|
116
|
+
}
|
|
117
|
+
async function findConfiguredIosIcon(root, configured, allowFallback = false) {
|
|
118
|
+
const candidates = [
|
|
119
|
+
configured,
|
|
120
|
+
"icon.png",
|
|
121
|
+
"app-icon.png",
|
|
122
|
+
"assets/icon.png",
|
|
123
|
+
"assets/app-icon.png",
|
|
124
|
+
"src/assets/icon.png",
|
|
125
|
+
"src/assets/app-icon.png",
|
|
126
|
+
"public/icon.png",
|
|
127
|
+
].filter((value) => Boolean(value));
|
|
128
|
+
for (const candidate of candidates) {
|
|
129
|
+
const path = resolve(root, candidate);
|
|
130
|
+
if (existsSync(path))
|
|
131
|
+
return { path, fallback: false };
|
|
132
|
+
}
|
|
133
|
+
if (allowFallback) {
|
|
134
|
+
const staticImages = join(root, "dist", "static", "image");
|
|
135
|
+
const entries = await readdir(staticImages, { withFileTypes: true }).catch(() => []);
|
|
136
|
+
const logo = entries.find((entry) => entry.isFile() &&
|
|
137
|
+
entry.name.endsWith(".png") &&
|
|
138
|
+
/(?:lynx|logo|icon)/i.test(entry.name));
|
|
139
|
+
if (logo)
|
|
140
|
+
return { path: join(staticImages, logo.name), fallback: true };
|
|
141
|
+
}
|
|
142
|
+
return undefined;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Apply a project-owned 1024x1024 PNG to the generated or existing Xcode
|
|
146
|
+
* AppIcon set. A missing icon is reported as a warning by the caller rather
|
|
147
|
+
* than silently inventing product branding.
|
|
148
|
+
*/
|
|
149
|
+
export async function prepareIosAppIcon(root, configured, options = {}) {
|
|
150
|
+
const source = await findConfiguredIosIcon(root, configured, options.allowFallback);
|
|
151
|
+
const iconSet = await findAppIconSet(join(root, "ios"));
|
|
152
|
+
if (!source || !iconSet)
|
|
153
|
+
return undefined;
|
|
154
|
+
const dimensions = pngDimensions(await readFile(source.path));
|
|
155
|
+
const filename = "AppIcon.png";
|
|
156
|
+
const destination = join(iconSet, filename);
|
|
157
|
+
if (dimensions.width === 1024 && dimensions.height === 1024) {
|
|
158
|
+
await copyFile(source.path, destination);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
assert(source.fallback &&
|
|
162
|
+
dimensions.width === dimensions.height &&
|
|
163
|
+
dimensions.width >= 512, "IOS_APP_ICON_INVALID", `The iOS app icon must be exactly 1024x1024 PNG; received ${dimensions.width}x${dimensions.height} from ${source.path}.`);
|
|
164
|
+
assert(commandExists("sips"), "IOS_SIPS_REQUIRED", "The fallback Simulator icon needs Apple's sips tool. Install Xcode command-line tools, or provide a 1024x1024 icon.png.");
|
|
165
|
+
await runProcess("sips", ["-z", "1024", "1024", source.path, "--out", destination], {
|
|
166
|
+
cwd: root,
|
|
167
|
+
quiet: true,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
await writeFile(join(iconSet, "Contents.json"), `${JSON.stringify({
|
|
171
|
+
images: [
|
|
172
|
+
{
|
|
173
|
+
filename,
|
|
174
|
+
idiom: "universal",
|
|
175
|
+
platform: "ios",
|
|
176
|
+
size: "1024x1024",
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
info: { author: "xcode", version: 1 },
|
|
180
|
+
}, null, 2)}\n`, "utf8");
|
|
181
|
+
return source.path;
|
|
182
|
+
}
|
|
183
|
+
async function appBundleIdentifier(root, appBundle) {
|
|
184
|
+
assert(commandExists("plutil"), "IOS_PLUTIL_REQUIRED", "plutil was not found. Install Xcode command-line tools before launching the Simulator app.");
|
|
185
|
+
const result = await captureProcess("plutil", [
|
|
186
|
+
"-extract",
|
|
187
|
+
"CFBundleIdentifier",
|
|
188
|
+
"raw",
|
|
189
|
+
"-o",
|
|
190
|
+
"-",
|
|
191
|
+
join(appBundle, "Info.plist"),
|
|
192
|
+
], { cwd: root });
|
|
193
|
+
const identifier = result.stdout.trim();
|
|
194
|
+
assert(result.code === 0 && identifier, "IOS_BUNDLE_IDENTIFIER_MISSING", `Could not read CFBundleIdentifier from ${join(appBundle, "Info.plist")}.`);
|
|
195
|
+
return identifier;
|
|
196
|
+
}
|
|
197
|
+
export async function launchIosSimulatorApp(root, device, appBundle, options = {}) {
|
|
198
|
+
const identifier = await appBundleIdentifier(root, appBundle);
|
|
199
|
+
options.onEvent?.("Opening iOS Simulator…");
|
|
200
|
+
await runProcess("open", ["-a", "Simulator"], {
|
|
201
|
+
cwd: root,
|
|
202
|
+
quiet: options.quiet,
|
|
203
|
+
onOutput: options.onEvent,
|
|
204
|
+
});
|
|
205
|
+
options.onEvent?.(`Launching ${identifier} in iOS Simulator…`);
|
|
206
|
+
await runProcess("xcrun", ["simctl", "launch", device, identifier], {
|
|
207
|
+
cwd: root,
|
|
208
|
+
quiet: options.quiet,
|
|
209
|
+
onOutput: options.onEvent,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
59
212
|
async function hashDirectory(directory) {
|
|
60
213
|
const hash = createHash("sha256");
|
|
61
214
|
let size = 0;
|
|
@@ -121,6 +274,13 @@ async function runRealIosSimulatorBuild(job, options) {
|
|
|
121
274
|
onOutput: options.onEvent,
|
|
122
275
|
});
|
|
123
276
|
}
|
|
277
|
+
const icon = await prepareIosAppIcon(options.root, ios.appIcon, {
|
|
278
|
+
allowFallback: true,
|
|
279
|
+
});
|
|
280
|
+
if (icon)
|
|
281
|
+
step(`Using iOS app icon: ${icon}`, 9);
|
|
282
|
+
else
|
|
283
|
+
options.onEvent?.("No 1024x1024 PNG app icon was found; configure ios.appIcon or add icon.png before distribution.");
|
|
124
284
|
transitionBuild(job, "queued", "iOS Simulator build queued locally");
|
|
125
285
|
transitionBuild(job, "provisioning", "iOS Simulator destination selected");
|
|
126
286
|
transitionBuild(job, "installing_dependencies", "iOS Simulator dependencies ready");
|
|
@@ -145,12 +305,26 @@ async function runRealIosSimulatorBuild(job, options) {
|
|
|
145
305
|
"build",
|
|
146
306
|
], { cwd: options.root, quiet: options.quiet, onOutput: options.onEvent });
|
|
147
307
|
const appPath = await findSimulatorApp(options.root, derivedData, configuration, ios.scheme);
|
|
308
|
+
const copiedResources = await syncIosRuntimeResources(options.root, appPath);
|
|
309
|
+
for (const name of copiedResources)
|
|
310
|
+
options.onEvent?.(`Packaged ${name} in the iOS Simulator app`);
|
|
148
311
|
step("Installing .app in iOS Simulator…", 75);
|
|
149
312
|
await runProcess("xcrun", ["simctl", "install", device, appPath], {
|
|
150
313
|
cwd: options.root,
|
|
151
314
|
quiet: options.quiet,
|
|
152
315
|
onOutput: options.onEvent,
|
|
153
316
|
});
|
|
317
|
+
if (options.simulatorAutostart) {
|
|
318
|
+
try {
|
|
319
|
+
await launchIosSimulatorApp(options.root, device, appPath, {
|
|
320
|
+
quiet: options.quiet,
|
|
321
|
+
onEvent: options.onEvent,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
catch (error) {
|
|
325
|
+
options.onEvent?.(`Simulator app installed but could not be launched automatically: ${error instanceof Error ? error.message : String(error)}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
154
328
|
const artifact = await hashDirectory(appPath);
|
|
155
329
|
const artifactName = nativeArtifactName("app");
|
|
156
330
|
job.attempts += 1;
|
|
@@ -221,8 +395,11 @@ async function installCocoaPods(root, quiet, onEvent) {
|
|
|
221
395
|
if (!existsSync(podfile))
|
|
222
396
|
return;
|
|
223
397
|
assert(commandExists("pod"), "IOS_COCOAPODS_REQUIRED", "CocoaPods was not found. Install CocoaPods on macOS, then rerun the build.");
|
|
224
|
-
|
|
225
|
-
|
|
398
|
+
const hasLockfile = existsSync(join(iosDirectory, "Podfile.lock"));
|
|
399
|
+
onEvent?.(hasLockfile
|
|
400
|
+
? "Installing iOS CocoaPods dependencies…"
|
|
401
|
+
: "Updating CocoaPods specs and installing iOS dependencies…");
|
|
402
|
+
await runProcess("pod", hasLockfile ? ["install"] : ["install", "--repo-update"], {
|
|
226
403
|
cwd: iosDirectory,
|
|
227
404
|
quiet,
|
|
228
405
|
onOutput: onEvent,
|
|
@@ -287,6 +464,11 @@ export async function runRealIosBuild(job, options) {
|
|
|
287
464
|
onOutput: options.onEvent,
|
|
288
465
|
});
|
|
289
466
|
}
|
|
467
|
+
const icon = await prepareIosAppIcon(options.root, ios.appIcon);
|
|
468
|
+
if (icon)
|
|
469
|
+
step(`Using iOS app icon: ${icon}`, 9);
|
|
470
|
+
else
|
|
471
|
+
options.onEvent?.("No 1024x1024 PNG app icon was found; configure ios.appIcon or add icon.png before distribution.");
|
|
290
472
|
step("Preparing Xcode archive…", 10);
|
|
291
473
|
await runProcess("xcodebuild", [
|
|
292
474
|
projectFlag,
|
|
@@ -301,6 +483,10 @@ export async function runRealIosBuild(job, options) {
|
|
|
301
483
|
archivePath,
|
|
302
484
|
"archive",
|
|
303
485
|
], { cwd: options.root, quiet: options.quiet, onOutput: options.onEvent });
|
|
486
|
+
const archivedApp = await findArchiveApp(archivePath);
|
|
487
|
+
const copiedResources = await syncIosRuntimeResources(options.root, archivedApp);
|
|
488
|
+
for (const name of copiedResources)
|
|
489
|
+
options.onEvent?.(`Packaged ${name} in the iOS archive`);
|
|
304
490
|
transitionBuild(job, "queued", "iOS build queued locally");
|
|
305
491
|
transitionBuild(job, "provisioning", "Xcode signing configuration selected");
|
|
306
492
|
step("Exporting signed IPA…", 55);
|
package/dist/ios-host.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface IosHostOptions {
|
|
2
2
|
bundleIdentifier: string;
|
|
3
3
|
appName: string;
|
|
4
|
+
appIcon?: string;
|
|
4
5
|
}
|
|
5
6
|
export interface IosHostResult {
|
|
6
7
|
directory: string;
|
|
@@ -10,6 +11,7 @@ export interface IosHostResult {
|
|
|
10
11
|
scheme: string;
|
|
11
12
|
exportOptionsPlist: string;
|
|
12
13
|
bundleScript: string;
|
|
14
|
+
appIcon?: string;
|
|
13
15
|
configUpdated: boolean;
|
|
14
16
|
}
|
|
15
17
|
export declare function initializeIosHost(root: string, options: IosHostOptions): Promise<IosHostResult>;
|
package/dist/ios-host.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ios-host.d.ts","sourceRoot":"","sources":["../src/ios-host.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,cAAc;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ios-host.d.ts","sourceRoot":"","sources":["../src/ios-host.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,cAAc;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,OAAO,CAAC;CACxB;AAqED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CA2CxB;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKjE"}
|
package/dist/ios-host.js
CHANGED
|
@@ -51,6 +51,7 @@ async function updateProjectConfig(root, result) {
|
|
|
51
51
|
configuration: "Release",
|
|
52
52
|
exportOptionsPlist: "ios/ExportOptions.plist",
|
|
53
53
|
bundleScript: "ios/sync-bundle.mjs",
|
|
54
|
+
...(result.appIcon ? { appIcon: result.appIcon } : {}),
|
|
54
55
|
};
|
|
55
56
|
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
56
57
|
return true;
|
|
@@ -81,6 +82,7 @@ export async function initializeIosHost(root, options) {
|
|
|
81
82
|
scheme,
|
|
82
83
|
exportOptionsPlist: "ios/ExportOptions.plist",
|
|
83
84
|
bundleScript: "ios/sync-bundle.mjs",
|
|
85
|
+
...(options.appIcon ? { appIcon: options.appIcon } : {}),
|
|
84
86
|
};
|
|
85
87
|
const configUpdated = await updateProjectConfig(root, result);
|
|
86
88
|
return { ...result, configUpdated };
|
|
@@ -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;
|
|
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"}
|
package/dist/ui/components.js
CHANGED
|
@@ -5,6 +5,7 @@ import ora from "ora";
|
|
|
5
5
|
import qrcode from "qrcode-terminal";
|
|
6
6
|
import { c, colorsEnabled } from "./state.js";
|
|
7
7
|
import { supportsUnicode, terminalWidth } from "./terminal.js";
|
|
8
|
+
import { renderTerminalQr } from "./qr.js";
|
|
8
9
|
export function sectionHeader(title) {
|
|
9
10
|
console.log(`\n${c.brandBold(`◆ LynxShip — ${title}`)}`);
|
|
10
11
|
}
|
|
@@ -292,6 +293,11 @@ export function devServerQr(url, enabled = true) {
|
|
|
292
293
|
if (!enabled)
|
|
293
294
|
return;
|
|
294
295
|
console.log(`\n${c.brandBold("Lynx Explorer")}`);
|
|
295
|
-
|
|
296
|
+
if (colorsEnabled && supportsUnicode()) {
|
|
297
|
+
console.log(renderTerminalQr(url, true));
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
qrcode.generate(url, { small: true }, (code) => console.log(code));
|
|
301
|
+
}
|
|
296
302
|
console.log(` ${c.teal("URL")} ${url}\n`);
|
|
297
303
|
}
|
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;IAIvD,WAAW,CAAC,GAAG,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;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"}
|
package/dist/ui/index.js
CHANGED
|
@@ -67,7 +67,10 @@ export class CliUi {
|
|
|
67
67
|
downloadArtifact(url, expiresAt, this.interactive);
|
|
68
68
|
}
|
|
69
69
|
devServerQr(url) {
|
|
70
|
-
|
|
70
|
+
// Rspeedy suppresses its own QR when its stdout is piped. LynxShip
|
|
71
|
+
// captures that output, so render the fallback unless the caller asked
|
|
72
|
+
// for machine JSON, quiet output, or an explicitly non-interactive run.
|
|
73
|
+
devServerQr(url, !this.options.json && !this.options.quiet && !this.options.nonInteractive);
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
export function createCliUi(args) {
|
package/dist/ui/qr.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export declare const LYNXSHIP_QR_STYLE: {
|
|
2
|
+
readonly type: "svg";
|
|
3
|
+
readonly width: 300;
|
|
4
|
+
readonly height: 300;
|
|
5
|
+
readonly margin: 0;
|
|
6
|
+
readonly qrOptions: {
|
|
7
|
+
readonly errorCorrectionLevel: "H";
|
|
8
|
+
};
|
|
9
|
+
readonly dotsOptions: {
|
|
10
|
+
readonly type: "dots";
|
|
11
|
+
readonly gradient: {
|
|
12
|
+
readonly type: "linear";
|
|
13
|
+
readonly rotation: number;
|
|
14
|
+
readonly colorStops: readonly [{
|
|
15
|
+
readonly offset: 0;
|
|
16
|
+
readonly color: "#ff6b9d";
|
|
17
|
+
}, {
|
|
18
|
+
readonly offset: 1;
|
|
19
|
+
readonly color: "#45b7d1";
|
|
20
|
+
}];
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
readonly cornersSquareOptions: {
|
|
24
|
+
readonly type: "dot";
|
|
25
|
+
readonly gradient: {
|
|
26
|
+
readonly type: "linear";
|
|
27
|
+
readonly rotation: number;
|
|
28
|
+
readonly colorStops: readonly [{
|
|
29
|
+
readonly offset: 0;
|
|
30
|
+
readonly color: "#ff6b9d";
|
|
31
|
+
}, {
|
|
32
|
+
readonly offset: 1;
|
|
33
|
+
readonly color: "#45b7d1";
|
|
34
|
+
}];
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
readonly cornersDotOptions: {
|
|
38
|
+
readonly type: "dot";
|
|
39
|
+
readonly gradient: {
|
|
40
|
+
readonly type: "linear";
|
|
41
|
+
readonly rotation: number;
|
|
42
|
+
readonly colorStops: readonly [{
|
|
43
|
+
readonly offset: 0;
|
|
44
|
+
readonly color: "#ff6b9d";
|
|
45
|
+
}, {
|
|
46
|
+
readonly offset: 1;
|
|
47
|
+
readonly color: "#45b7d1";
|
|
48
|
+
}];
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
readonly backgroundOptions: {
|
|
52
|
+
readonly color: "#FFFFFF";
|
|
53
|
+
};
|
|
54
|
+
};
|
|
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.
|
|
61
|
+
*/
|
|
62
|
+
export declare function renderTerminalQr(data: string, colored: boolean): string;
|
|
63
|
+
//# sourceMappingURL=qr.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/ui/qr.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import qrcodeGenerator from "qrcode-generator";
|
|
2
|
+
import { LYNX_BRAND } from "./colors.js";
|
|
3
|
+
export const LYNXSHIP_QR_STYLE = {
|
|
4
|
+
type: "svg",
|
|
5
|
+
width: 300,
|
|
6
|
+
height: 300,
|
|
7
|
+
margin: 0,
|
|
8
|
+
qrOptions: { errorCorrectionLevel: "H" },
|
|
9
|
+
dotsOptions: {
|
|
10
|
+
type: "dots",
|
|
11
|
+
gradient: {
|
|
12
|
+
type: "linear",
|
|
13
|
+
rotation: (50 * Math.PI) / 180,
|
|
14
|
+
colorStops: [
|
|
15
|
+
{ offset: 0, color: LYNX_BRAND.pink },
|
|
16
|
+
{ offset: 1, color: LYNX_BRAND.cyan },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
cornersSquareOptions: {
|
|
21
|
+
type: "dot",
|
|
22
|
+
gradient: {
|
|
23
|
+
type: "linear",
|
|
24
|
+
rotation: (50 * Math.PI) / 180,
|
|
25
|
+
colorStops: [
|
|
26
|
+
{ offset: 0, color: LYNX_BRAND.pink },
|
|
27
|
+
{ offset: 1, color: LYNX_BRAND.cyan },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
cornersDotOptions: {
|
|
32
|
+
type: "dot",
|
|
33
|
+
gradient: {
|
|
34
|
+
type: "linear",
|
|
35
|
+
rotation: (50 * Math.PI) / 180,
|
|
36
|
+
colorStops: [
|
|
37
|
+
{ offset: 0, color: LYNX_BRAND.pink },
|
|
38
|
+
{ offset: 1, color: LYNX_BRAND.cyan },
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
backgroundOptions: { color: "#FFFFFF" },
|
|
43
|
+
};
|
|
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
|
+
];
|
|
54
|
+
}
|
|
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);
|
|
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),
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
function ansiForeground([red, green, blue]) {
|
|
72
|
+
return `\u001b[38;2;${red};${green};${blue}m`;
|
|
73
|
+
}
|
|
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);
|
|
81
|
+
}
|
|
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;
|
|
95
|
+
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) {
|
|
102
|
+
line += " ";
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (!colored) {
|
|
106
|
+
line += top && bottom ? "█" : top ? "▀" : "▄";
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const topColor = colorAt(row, column, fullSize);
|
|
110
|
+
const bottomColor = colorAt(row + 1, column, fullSize);
|
|
111
|
+
if (top && bottom) {
|
|
112
|
+
line += `${ansiForeground(topColor)}█`;
|
|
113
|
+
}
|
|
114
|
+
else if (top) {
|
|
115
|
+
line += `${ansiForeground(topColor)}▀`;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
line += `${ansiForeground(bottomColor)}▄`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
lines.push(`${line}${colored ? RESET : ""}`);
|
|
122
|
+
}
|
|
123
|
+
return lines.join("\n");
|
|
124
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lynxship/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.14",
|
|
4
4
|
"description": "Build, sign, store, submit and update LynxJS applications from the terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -67,11 +67,13 @@
|
|
|
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",
|
|
70
71
|
"qrcode-terminal": "0.12.0",
|
|
72
|
+
"qr-code-styling": "1.9.2",
|
|
73
|
+
"@lynxship/contracts": "0.1.0",
|
|
71
74
|
"@lynxship/build-orchestrator": "0.1.0",
|
|
72
75
|
"@lynxship/db": "0.1.0",
|
|
73
76
|
"@lynxship/submit": "0.1.0",
|
|
74
|
-
"@lynxship/contracts": "0.1.0",
|
|
75
77
|
"@lynxship/signing": "0.1.0"
|
|
76
78
|
},
|
|
77
79
|
"devDependencies": {
|