@lynxship/cli 0.1.10 → 0.1.12
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 +39 -0
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +25 -2
- package/dist/dev-qr.d.ts +3 -0
- package/dist/dev-qr.d.ts.map +1 -0
- package/dist/dev-qr.js +12 -0
- package/dist/guidance.d.ts +6 -1
- package/dist/guidance.d.ts.map +1 -1
- package/dist/guidance.js +130 -3
- package/dist/index.js +85 -29
- package/dist/ios-build.d.ts +2 -0
- package/dist/ios-build.d.ts.map +1 -1
- package/dist/ios-build.js +173 -1
- package/dist/ios-toolchain.d.ts +2 -1
- package/dist/ios-toolchain.d.ts.map +1 -1
- package/dist/ios-toolchain.js +86 -50
- package/dist/ui/components.d.ts +2 -1
- package/dist/ui/components.d.ts.map +1 -1
- package/dist/ui/components.js +10 -1
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +5 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,6 +25,33 @@ npx @lynxship/cli doctor --project-dir ./my-lynx-app
|
|
|
25
25
|
|
|
26
26
|
The package exposes the `lynxship` executable.
|
|
27
27
|
|
|
28
|
+
## Vue Lynx projects
|
|
29
|
+
|
|
30
|
+
LynxShip supports projects created with the official Vue Lynx scaffold. Vue
|
|
31
|
+
Lynx is integrated through the project's Rspeedy configuration, so LynxShip
|
|
32
|
+
does not swap plugins or mutate `lynx.config.*`; it invokes the project's
|
|
33
|
+
package-manager `build` script and then passes the generated Lynx bundle to
|
|
34
|
+
the selected native or non-native target pipeline.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm create vue-lynx@latest my-vue-lynx-app
|
|
38
|
+
cd my-vue-lynx-app
|
|
39
|
+
npm install
|
|
40
|
+
npx @lynxship/cli@latest init
|
|
41
|
+
npx @lynxship/cli@latest doctor
|
|
42
|
+
npx @lynxship/cli@latest build --platform android --profile production
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For iOS Simulator on macOS:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx @lynxship/cli@latest build --platform ios --simulator --no-upload
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The CLI generator itself creates ReactLynx templates by default. Use the
|
|
52
|
+
official `create-vue-lynx` scaffold when Vue is required, then add LynxShip to
|
|
53
|
+
that existing project with `init`.
|
|
54
|
+
|
|
28
55
|
## Requirements
|
|
29
56
|
|
|
30
57
|
- Node.js 22 or 24 LTS, or Node.js 26 Current; Node.js 24 LTS is the
|
|
@@ -386,6 +413,18 @@ script. On macOS, `lynxship build --platform ios` installs CocoaPods before
|
|
|
386
413
|
archiving. Xcode, CocoaPods and real Apple signing credentials are still
|
|
387
414
|
required for a signed IPA; the CLI never fabricates them.
|
|
388
415
|
|
|
416
|
+
For an iOS Simulator build, use the dedicated simulator profile:
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
lynxship doctor --project-dir ./my-lynx-app --platform ios --profile simulator
|
|
420
|
+
lynxship build --project-dir ./my-lynx-app --platform ios --simulator --profile simulator --no-upload
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
This uses Xcode's `iphonesimulator` SDK, boots an available Simulator, creates
|
|
424
|
+
and installs a local `.app`, and does not require a physical iPhone, Apple
|
|
425
|
+
Distribution certificate, provisioning profile or R2 upload. Use the
|
|
426
|
+
production profile only for a signed device IPA.
|
|
427
|
+
|
|
389
428
|
Run the complete first-use diagnosis before building:
|
|
390
429
|
|
|
391
430
|
```bash
|
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,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;KACvB,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,
|
|
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;KACvB,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/config.js
CHANGED
|
@@ -4,6 +4,18 @@ import { assert } from "@lynxship/contracts";
|
|
|
4
4
|
export const DEFAULT_CONFIG = {
|
|
5
5
|
runtimeVersion: { policy: "fingerprint" },
|
|
6
6
|
build: {
|
|
7
|
+
development: {
|
|
8
|
+
distribution: "development",
|
|
9
|
+
channel: "development",
|
|
10
|
+
environment: "development",
|
|
11
|
+
ios: { configuration: "Debug" },
|
|
12
|
+
},
|
|
13
|
+
simulator: {
|
|
14
|
+
distribution: "development",
|
|
15
|
+
channel: "development",
|
|
16
|
+
environment: "development",
|
|
17
|
+
ios: { configuration: "Debug", simulator: true },
|
|
18
|
+
},
|
|
7
19
|
production: {
|
|
8
20
|
distribution: "store",
|
|
9
21
|
channel: "production",
|
|
@@ -52,9 +64,20 @@ export async function loadConfig(root, options = {}) {
|
|
|
52
64
|
}
|
|
53
65
|
}
|
|
54
66
|
export function resolveProfile(config, name = "production") {
|
|
55
|
-
const profile = config.build?.[name];
|
|
67
|
+
const profile = config.build?.[name] ?? DEFAULT_CONFIG.build?.[name];
|
|
56
68
|
assert(profile, "PROFILE_NOT_FOUND", `Build profile '${name}' was not found`);
|
|
57
|
-
|
|
69
|
+
const inherited = DEFAULT_CONFIG.build?.[name] ?? {};
|
|
70
|
+
const productionIos = config.build?.production?.ios ?? {};
|
|
71
|
+
return {
|
|
72
|
+
name,
|
|
73
|
+
...inherited,
|
|
74
|
+
...profile,
|
|
75
|
+
ios: {
|
|
76
|
+
...(inherited.ios ?? {}),
|
|
77
|
+
...(name !== "production" ? productionIos : {}),
|
|
78
|
+
...(profile.ios ?? {}),
|
|
79
|
+
},
|
|
80
|
+
};
|
|
58
81
|
}
|
|
59
82
|
export function platformValue(value) {
|
|
60
83
|
assert(["android", "ios", "harmony", "web", "desktop"].includes(value), "PLATFORM_INVALID", "Platform must be android, ios, harmony, web or desktop");
|
package/dist/dev-qr.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-qr.d.ts","sourceRoot":"","sources":["../src/dev-qr.ts"],"names":[],"mappings":"AAMA,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMpE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5D"}
|
package/dist/dev-qr.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const URL_PATTERN = /https?:\/\/[^\s<>"'`]+/g;
|
|
2
|
+
function cleanUrl(value) {
|
|
3
|
+
return value.replace(/[),.;!?]+$/u, "");
|
|
4
|
+
}
|
|
5
|
+
export function extractDevServerUrl(line) {
|
|
6
|
+
const urls = (line.match(URL_PATTERN) ?? []).map(cleanUrl);
|
|
7
|
+
return (urls.find((url) => url.includes("fullscreen=true")) ??
|
|
8
|
+
urls.find((url) => url.includes(".lynx.bundle")));
|
|
9
|
+
}
|
|
10
|
+
export function shouldPrintDevServerQr(line) {
|
|
11
|
+
return /(?:default:|fullscreen=true|\bready\b)/iu.test(line);
|
|
12
|
+
}
|
package/dist/guidance.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export interface CliGuidance {
|
|
2
2
|
commands: string[];
|
|
3
3
|
note?: string;
|
|
4
|
+
environment?: string;
|
|
4
5
|
}
|
|
5
|
-
export
|
|
6
|
+
export interface GuidanceContext {
|
|
7
|
+
args?: readonly string[];
|
|
8
|
+
hostPlatform?: NodeJS.Platform;
|
|
9
|
+
}
|
|
10
|
+
export declare function guidanceForError(error: unknown, context?: GuidanceContext): CliGuidance;
|
|
6
11
|
//# sourceMappingURL=guidance.d.ts.map
|
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;
|
|
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;AAumBD,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,eAAoB,GAC5B,WAAW,CAEb"}
|
package/dist/guidance.js
CHANGED
|
@@ -223,6 +223,35 @@ const guidance = {
|
|
|
223
223
|
],
|
|
224
224
|
note: "The iOS doctor checks macOS, Xcode, xcrun, CocoaPods, Xcode settings, Apple signing identities, provisioning and export options without printing certificate contents.",
|
|
225
225
|
},
|
|
226
|
+
IOS_SIMULATOR_PLATFORM: {
|
|
227
|
+
commands: [
|
|
228
|
+
"lynxship doctor --platform ios --profile simulator",
|
|
229
|
+
"lynxship build --platform ios --simulator --profile simulator --no-upload",
|
|
230
|
+
],
|
|
231
|
+
note: "The iOS Simulator target is selected with --platform ios; Android, Web and Desktop targets cannot use this flag.",
|
|
232
|
+
},
|
|
233
|
+
IOS_SIMULATOR_RUNTIME_REQUIRED: {
|
|
234
|
+
commands: [
|
|
235
|
+
"xcode-select --install",
|
|
236
|
+
"lynxship doctor --platform ios --profile simulator",
|
|
237
|
+
"lynxship build --platform ios --simulator --profile simulator --no-upload",
|
|
238
|
+
],
|
|
239
|
+
note: "Install an iOS Simulator runtime in Xcode > Settings > Components, then rerun the doctor.",
|
|
240
|
+
},
|
|
241
|
+
IOS_SIMULATOR_ARTIFACT_MISSING: {
|
|
242
|
+
commands: [
|
|
243
|
+
"lynxship doctor --platform ios --profile simulator",
|
|
244
|
+
"lynxship build --platform ios --simulator --profile simulator --no-upload",
|
|
245
|
+
],
|
|
246
|
+
note: "The simulator build must produce an .app under DerivedData before it can be installed.",
|
|
247
|
+
},
|
|
248
|
+
IOS_SIMULATOR_UPLOAD_BLOCKED: {
|
|
249
|
+
commands: [
|
|
250
|
+
"lynxship build --platform ios --simulator --profile simulator --no-upload",
|
|
251
|
+
"lynxship build --platform ios --profile production",
|
|
252
|
+
],
|
|
253
|
+
note: "Simulator .app artifacts stay local; use a signed production device build for R2 upload and distribution.",
|
|
254
|
+
},
|
|
226
255
|
IOS_COCOAPODS_REQUIRED: {
|
|
227
256
|
commands: ["brew install cocoapods", "lynxship build --platform ios"],
|
|
228
257
|
note: "CocoaPods is required when the iOS host contains a Podfile.",
|
|
@@ -234,15 +263,20 @@ const guidance = {
|
|
|
234
263
|
],
|
|
235
264
|
},
|
|
236
265
|
IOS_SCHEME_REQUIRED: {
|
|
237
|
-
commands: [
|
|
266
|
+
commands: [
|
|
267
|
+
"lynxship inspect",
|
|
268
|
+
"lynxship doctor --platform ios --profile simulator",
|
|
269
|
+
"lynxship doctor --platform ios --profile production",
|
|
270
|
+
],
|
|
238
271
|
note: "Set build.<profile>.ios.scheme in lynxship.json.",
|
|
239
272
|
},
|
|
240
273
|
IOS_EXPORT_OPTIONS_REQUIRED: {
|
|
241
274
|
commands: [
|
|
242
275
|
"lynxship ios host init --bundle-identifier com.example.myapp",
|
|
243
276
|
"lynxship doctor --platform ios",
|
|
277
|
+
"lynxship build --platform ios --simulator --profile simulator --no-upload",
|
|
244
278
|
],
|
|
245
|
-
note: "Set build.<profile>.ios.exportOptionsPlist
|
|
279
|
+
note: "Set build.<profile>.ios.exportOptionsPlist for a device IPA; simulator .app builds do not require export options.",
|
|
246
280
|
},
|
|
247
281
|
IOS_DEVICE_REQUIRED: {
|
|
248
282
|
commands: [
|
|
@@ -364,7 +398,7 @@ const guidance = {
|
|
|
364
398
|
note: "Lynx Trace and Recorder are operated from Lynx DevTool Desktop after the native -dev runtime is integrated.",
|
|
365
399
|
},
|
|
366
400
|
};
|
|
367
|
-
|
|
401
|
+
function guidanceForErrorBase(error) {
|
|
368
402
|
const code = error.code;
|
|
369
403
|
if (typeof code === "string" && guidance[code])
|
|
370
404
|
return guidance[code];
|
|
@@ -461,3 +495,96 @@ export function guidanceForError(error) {
|
|
|
461
495
|
}
|
|
462
496
|
return { commands: [] };
|
|
463
497
|
}
|
|
498
|
+
function argumentValue(args, name) {
|
|
499
|
+
const index = args.indexOf(name);
|
|
500
|
+
if (index >= 0)
|
|
501
|
+
return args[index + 1];
|
|
502
|
+
const inline = args.find((value) => value.startsWith(`${name}=`));
|
|
503
|
+
return inline?.slice(name.length + 1);
|
|
504
|
+
}
|
|
505
|
+
function targetFromCode(code) {
|
|
506
|
+
if (!code)
|
|
507
|
+
return undefined;
|
|
508
|
+
if (code.startsWith("ANDROID_"))
|
|
509
|
+
return "android";
|
|
510
|
+
if (code.startsWith("IOS_"))
|
|
511
|
+
return "ios";
|
|
512
|
+
if (code.startsWith("HARMONY_"))
|
|
513
|
+
return "harmony";
|
|
514
|
+
if (code.startsWith("WEB_"))
|
|
515
|
+
return "web";
|
|
516
|
+
if (code.startsWith("DESKTOP_"))
|
|
517
|
+
return "desktop";
|
|
518
|
+
if (code === "BUILD_ALL_MACOS_REQUIRED")
|
|
519
|
+
return "all";
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
function targetFromCommand(command) {
|
|
523
|
+
const match = command.match(/--platform\s+(android|ios|harmony|web|desktop|all)/);
|
|
524
|
+
return match?.[1];
|
|
525
|
+
}
|
|
526
|
+
function targetCommand(platform, simulator, action) {
|
|
527
|
+
if (platform === "all")
|
|
528
|
+
return action === "doctor"
|
|
529
|
+
? "lynxship doctor --platform android"
|
|
530
|
+
: "lynxship build --platform all --profile production";
|
|
531
|
+
if (platform === "ios" && simulator) {
|
|
532
|
+
return action === "doctor"
|
|
533
|
+
? "lynxship doctor --platform ios --profile simulator"
|
|
534
|
+
: "lynxship build --platform ios --simulator --profile simulator --no-upload";
|
|
535
|
+
}
|
|
536
|
+
if (action === "doctor")
|
|
537
|
+
return `lynxship doctor --platform ${platform}`;
|
|
538
|
+
return `lynxship build --platform ${platform} --profile production`;
|
|
539
|
+
}
|
|
540
|
+
function adaptGuidance(guidance, error, context) {
|
|
541
|
+
const code = error.code;
|
|
542
|
+
const errorCode = typeof code === "string" ? code : undefined;
|
|
543
|
+
const args = context.args ?? [];
|
|
544
|
+
const requested = argumentValue(args, "--platform");
|
|
545
|
+
const target = requested ?? targetFromCode(errorCode);
|
|
546
|
+
const simulator = args.includes("--simulator");
|
|
547
|
+
let commands = guidance.commands;
|
|
548
|
+
// Generic errors must preserve the platform the developer actually asked
|
|
549
|
+
// for. The old fallback to Android was misleading for iOS/Web/Desktop jobs.
|
|
550
|
+
if (target &&
|
|
551
|
+
errorCode !== "BUILD_ALL_MACOS_REQUIRED" &&
|
|
552
|
+
["BUILD_", "PROFILE_NOT_FOUND"].some((prefix) => errorCode?.startsWith(prefix))) {
|
|
553
|
+
commands = [
|
|
554
|
+
targetCommand(target, target === "ios" && simulator, "doctor"),
|
|
555
|
+
targetCommand(target, target === "ios" && simulator, "build"),
|
|
556
|
+
];
|
|
557
|
+
}
|
|
558
|
+
const hostPlatform = context.hostPlatform ?? process.platform;
|
|
559
|
+
const macOnly = hostPlatform !== "darwin";
|
|
560
|
+
let requiredEnvironment;
|
|
561
|
+
commands = commands.map((command) => {
|
|
562
|
+
const commandTarget = targetFromCommand(command);
|
|
563
|
+
const isIosCommand = commandTarget === "ios" ||
|
|
564
|
+
command.includes("ios host") ||
|
|
565
|
+
command.startsWith("xcode-select") ||
|
|
566
|
+
command.startsWith("xcrun ") ||
|
|
567
|
+
command.startsWith("brew ");
|
|
568
|
+
const isAllCommand = commandTarget === "all";
|
|
569
|
+
if (macOnly && isIosCommand) {
|
|
570
|
+
requiredEnvironment = "macOS or a macOS CI runner";
|
|
571
|
+
return command;
|
|
572
|
+
}
|
|
573
|
+
if (macOnly && isAllCommand) {
|
|
574
|
+
requiredEnvironment = "macOS or a macOS CI runner";
|
|
575
|
+
return command;
|
|
576
|
+
}
|
|
577
|
+
return command;
|
|
578
|
+
});
|
|
579
|
+
const notes = guidance.note ? [guidance.note] : [];
|
|
580
|
+
if (requiredEnvironment)
|
|
581
|
+
notes.push("These commands must be run on macOS or a macOS CI runner; Android remains supported on Windows, macOS and Linux.");
|
|
582
|
+
return {
|
|
583
|
+
commands,
|
|
584
|
+
note: notes.length > 0 ? notes.join(" ") : undefined,
|
|
585
|
+
environment: requiredEnvironment,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
export function guidanceForError(error, context = {}) {
|
|
589
|
+
return adaptGuidance(guidanceForErrorBase(error), error, context);
|
|
590
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ import { formatDevToolFailure, inspectLynxDevTool } from "./lynx-devtool.js";
|
|
|
33
33
|
import { hasHarmonyHost, runRealHarmonyBuild } from "./harmony-build.js";
|
|
34
34
|
import { hasWebConfiguration, runRealWebBuild } from "./web-build.js";
|
|
35
35
|
import { hasDesktopHost, runRealDesktopBuild } from "./desktop-build.js";
|
|
36
|
+
import { extractDevServerUrl, shouldPrintDevServerQr } from "./dev-qr.js";
|
|
36
37
|
import { inspectDesktopTarget, inspectHarmonyTarget, inspectWebTarget, } from "./target-toolchain.js";
|
|
37
38
|
const rawArgs = process.argv.slice(2);
|
|
38
39
|
const args = [...rawArgs];
|
|
@@ -149,6 +150,12 @@ function mobilePlatformValue(value) {
|
|
|
149
150
|
async function renderConfigurationFooter() {
|
|
150
151
|
if (!ui.interactive || ui.options.quiet)
|
|
151
152
|
return;
|
|
153
|
+
if (rawArgs[0] === "inspect")
|
|
154
|
+
return;
|
|
155
|
+
const profileIndex = rawArgs.indexOf("--profile");
|
|
156
|
+
const simulatorProfile = profileIndex >= 0 && rawArgs[profileIndex + 1] === "simulator";
|
|
157
|
+
if (rawArgs.includes("--simulator") || simulatorProfile)
|
|
158
|
+
return;
|
|
152
159
|
const status = await readConfigurationStatus();
|
|
153
160
|
const ready = status.r2 && status.android;
|
|
154
161
|
if (ready)
|
|
@@ -202,14 +209,19 @@ async function saveState(state, repository, builds, submissions) {
|
|
|
202
209
|
}
|
|
203
210
|
let stateSaveQueue = Promise.resolve();
|
|
204
211
|
async function executeBuild(options, showResult = true) {
|
|
205
|
-
const { config, profile, platform, skipUpload, wait, local, state, repository, builds, submissions, progress: sharedProgress, progressPrefix, onProgress, onEvent, skipBundleBuild, } = options;
|
|
212
|
+
const { config, profile, platform, skipUpload, wait, local, state, repository, builds, submissions, progress: sharedProgress, progressPrefix, onProgress, onEvent, skipBundleBuild, simulator = false, simulatorDevice, } = options;
|
|
206
213
|
if (wait && !local)
|
|
207
214
|
await ensureNativeHostForBuild(platform, profile, config);
|
|
215
|
+
// Host initialization can write lynxship.json (for example, the generated
|
|
216
|
+
// iOS scheme). Always read it again before resolving the build profile so a
|
|
217
|
+
// first build uses the host that was just created in the same invocation.
|
|
218
|
+
const effectiveConfig = wait && !local ? await loadConfig(root) : config;
|
|
208
219
|
if (platform === "android" || platform === "ios")
|
|
209
220
|
await requireAutolinkReady(root, platform);
|
|
210
|
-
const runtime = await inspectRuntimeFingerprint(root, platform,
|
|
221
|
+
const runtime = await inspectRuntimeFingerprint(root, platform, effectiveConfig);
|
|
222
|
+
const resolvedBuildProfile = resolveProfile(effectiveConfig, profile);
|
|
211
223
|
const job = await builds.create({
|
|
212
|
-
projectId: configuredProjectId(
|
|
224
|
+
projectId: configuredProjectId(effectiveConfig),
|
|
213
225
|
organizationId: "local_org",
|
|
214
226
|
platform,
|
|
215
227
|
profile,
|
|
@@ -242,7 +254,8 @@ async function executeBuild(options, showResult = true) {
|
|
|
242
254
|
const realAndroid = platform === "android" &&
|
|
243
255
|
isSupportedAndroidPlatform() &&
|
|
244
256
|
(await hasAndroidHost(root));
|
|
245
|
-
const realIos = platform === "ios" &&
|
|
257
|
+
const realIos = platform === "ios" &&
|
|
258
|
+
hasIosHost(root, resolveProfile(effectiveConfig, profile));
|
|
246
259
|
if (platform === "ios" && !realIos && !local)
|
|
247
260
|
assert(false, "IOS_HOST_REQUIRED", "A macOS Xcode host is required for a real iOS build. Run `lynxship ios host init --bundle-identifier <id>` or pass --bundle-identifier <id> to build.");
|
|
248
261
|
if (platform === "android" && !realAndroid && !local)
|
|
@@ -252,7 +265,7 @@ async function executeBuild(options, showResult = true) {
|
|
|
252
265
|
assert(toolchain.ok, "ANDROID_TOOLCHAIN_REQUIRED", `Android toolchain is not ready: ${formatAndroidToolchainFailure(toolchain)}`);
|
|
253
266
|
await runRealAndroidBuild(job, {
|
|
254
267
|
root,
|
|
255
|
-
profile:
|
|
268
|
+
profile: resolvedBuildProfile,
|
|
256
269
|
uploadArtifacts: !skipUpload,
|
|
257
270
|
skipBundleBuild,
|
|
258
271
|
quiet: json,
|
|
@@ -261,11 +274,13 @@ async function executeBuild(options, showResult = true) {
|
|
|
261
274
|
});
|
|
262
275
|
}
|
|
263
276
|
else if (realIos) {
|
|
264
|
-
const toolchain = await inspectIosToolchain(root, resolveProfile(
|
|
277
|
+
const toolchain = await inspectIosToolchain(root, resolveProfile(effectiveConfig, profile), simulator ? "simulator" : "device");
|
|
265
278
|
assert(toolchain.ok, "IOS_TOOLCHAIN_REQUIRED", `iOS toolchain is not ready: ${formatIosToolchainFailure(toolchain)}`);
|
|
266
279
|
await runRealIosBuild(job, {
|
|
267
280
|
root,
|
|
268
|
-
profile:
|
|
281
|
+
profile: resolvedBuildProfile,
|
|
282
|
+
simulator,
|
|
283
|
+
simulatorDevice,
|
|
269
284
|
uploadArtifacts: !skipUpload,
|
|
270
285
|
skipBundleBuild,
|
|
271
286
|
quiet: json,
|
|
@@ -276,7 +291,7 @@ async function executeBuild(options, showResult = true) {
|
|
|
276
291
|
else if (!local && platform === "web") {
|
|
277
292
|
await runRealWebBuild(job, {
|
|
278
293
|
root,
|
|
279
|
-
profile: resolveProfile(
|
|
294
|
+
profile: resolveProfile(effectiveConfig, profile),
|
|
280
295
|
uploadArtifacts: !skipUpload,
|
|
281
296
|
skipBundleBuild,
|
|
282
297
|
quiet: json,
|
|
@@ -287,7 +302,7 @@ async function executeBuild(options, showResult = true) {
|
|
|
287
302
|
else if (!local && platform === "harmony") {
|
|
288
303
|
await runRealHarmonyBuild(job, {
|
|
289
304
|
root,
|
|
290
|
-
profile: resolveProfile(
|
|
305
|
+
profile: resolveProfile(effectiveConfig, profile),
|
|
291
306
|
uploadArtifacts: !skipUpload,
|
|
292
307
|
skipBundleBuild,
|
|
293
308
|
quiet: json,
|
|
@@ -298,7 +313,7 @@ async function executeBuild(options, showResult = true) {
|
|
|
298
313
|
else if (!local && platform === "desktop") {
|
|
299
314
|
await runRealDesktopBuild(job, {
|
|
300
315
|
root,
|
|
301
|
-
profile: resolveProfile(
|
|
316
|
+
profile: resolveProfile(effectiveConfig, profile),
|
|
302
317
|
uploadArtifacts: !skipUpload,
|
|
303
318
|
allowUnsigned: options.allowUnsigned,
|
|
304
319
|
skipBundleBuild,
|
|
@@ -439,9 +454,11 @@ Commands:
|
|
|
439
454
|
|
|
440
455
|
Build options:
|
|
441
456
|
--platform <p> Target android, ios, harmony, web, desktop or all (default: android)
|
|
442
|
-
--profile <name> Build profile (default: production)
|
|
457
|
+
--profile <name> Build profile (default: production; simulator uses simulator)
|
|
443
458
|
--no-wait Queue the build without executing it locally
|
|
444
459
|
--no-upload Keep the artifact local and skip R2 (CI verification)
|
|
460
|
+
--simulator Build and install an iOS Simulator .app locally
|
|
461
|
+
--device <id> Select the iOS Simulator device for a simulator build
|
|
445
462
|
--allow-unsigned Allow an unsigned Desktop artifact only with --no-upload (local tests)
|
|
446
463
|
--local Use the contract-only build path for tests
|
|
447
464
|
|
|
@@ -487,7 +504,7 @@ Global options:
|
|
|
487
504
|
--application-id <id> Android package/application ID for host init
|
|
488
505
|
--bundle-identifier <id> iOS bundle identifier for host init
|
|
489
506
|
--library-dir <path> Native library directory for autolink codegen
|
|
490
|
-
--simulator
|
|
507
|
+
--simulator Build/install an iOS .app or install one with simctl
|
|
491
508
|
--help Show this complete command reference
|
|
492
509
|
|
|
493
510
|
Node support: Node 22/24 LTS or Node 26 Current. Use Node 24 LTS for production.`;
|
|
@@ -507,7 +524,7 @@ async function looksLikeLynxProject() {
|
|
|
507
524
|
...packageJson.dependencies,
|
|
508
525
|
...packageJson.devDependencies,
|
|
509
526
|
};
|
|
510
|
-
return (Object.keys(dependencies).some((name) => name.startsWith("@lynx-js/")) ||
|
|
527
|
+
return (Object.keys(dependencies).some((name) => name.startsWith("@lynx-js/") || name === "vue-lynx") ||
|
|
511
528
|
Object.values(packageJson.scripts ?? {}).some((script) => script.includes("rspeedy")));
|
|
512
529
|
}
|
|
513
530
|
catch {
|
|
@@ -618,10 +635,34 @@ async function runRspeedyCommand(subcommand, environment) {
|
|
|
618
635
|
if (subcommand === "dev") {
|
|
619
636
|
ui.info("Lynx Explorer mode: no Android or iOS native host is required. Scan the QR code printed by Rspeedy; source changes reload automatically.");
|
|
620
637
|
}
|
|
638
|
+
let devUrl;
|
|
639
|
+
let devQrPrinted = false;
|
|
640
|
+
let devQrTimer;
|
|
641
|
+
const printDevQr = () => {
|
|
642
|
+
if (!devUrl || devQrPrinted || json || ui.options.quiet)
|
|
643
|
+
return;
|
|
644
|
+
devQrPrinted = true;
|
|
645
|
+
if (devQrTimer)
|
|
646
|
+
clearTimeout(devQrTimer);
|
|
647
|
+
ui.devServerQr(devUrl);
|
|
648
|
+
};
|
|
621
649
|
await runRspeedy(root, subcommand, forwarded, {
|
|
622
650
|
env: environment,
|
|
623
651
|
quiet: json,
|
|
624
|
-
onOutput: (line) =>
|
|
652
|
+
onOutput: (line) => {
|
|
653
|
+
ui.info(`│ ${line}`);
|
|
654
|
+
if (subcommand !== "dev")
|
|
655
|
+
return;
|
|
656
|
+
const url = extractDevServerUrl(line);
|
|
657
|
+
if (url && (!devUrl || url.includes("fullscreen=true"))) {
|
|
658
|
+
devUrl = url;
|
|
659
|
+
if (devQrTimer)
|
|
660
|
+
clearTimeout(devQrTimer);
|
|
661
|
+
devQrTimer = setTimeout(printDevQr, 500);
|
|
662
|
+
}
|
|
663
|
+
if (devUrl && shouldPrintDevServerQr(line))
|
|
664
|
+
printDevQr();
|
|
665
|
+
},
|
|
625
666
|
});
|
|
626
667
|
printValue({ status: "success", command: `rspeedy ${subcommand}` }, {
|
|
627
668
|
title: `Rspeedy ${subcommand}`,
|
|
@@ -860,12 +901,7 @@ async function main() {
|
|
|
860
901
|
const nativeCredentialStore = !credentialStore.includes("owner-only");
|
|
861
902
|
const androidHost = doctorPlatform === "android" ? await hasAndroidHost(root) : false;
|
|
862
903
|
const doctorProfileName = flag("--profile", "production");
|
|
863
|
-
const doctorProfile = config
|
|
864
|
-
? resolveProfile(config, doctorProfileName)
|
|
865
|
-
: {
|
|
866
|
-
name: doctorProfileName,
|
|
867
|
-
...(DEFAULT_CONFIG.build?.production ?? {}),
|
|
868
|
-
};
|
|
904
|
+
const doctorProfile = resolveProfile(config, doctorProfileName);
|
|
869
905
|
const targetToolchain = doctorPlatform === "web"
|
|
870
906
|
? await inspectWebTarget(root, doctorProfile)
|
|
871
907
|
: doctorPlatform === "harmony"
|
|
@@ -879,7 +915,9 @@ async function main() {
|
|
|
879
915
|
? await inspectAndroidToolchain(root)
|
|
880
916
|
: undefined;
|
|
881
917
|
const iosToolchain = doctorPlatform === "ios"
|
|
882
|
-
? await inspectIosToolchain(root, doctorProfile
|
|
918
|
+
? await inspectIosToolchain(root, doctorProfile, doctorProfile.ios?.simulator || doctorProfileName === "simulator"
|
|
919
|
+
? "simulator"
|
|
920
|
+
: "device")
|
|
883
921
|
: undefined;
|
|
884
922
|
if (androidToolchain && args.includes("--fix")) {
|
|
885
923
|
await fixAndroidToolchain(root, androidToolchain, (message) => confirm(message), (line) => ui.info(line));
|
|
@@ -963,11 +1001,19 @@ async function main() {
|
|
|
963
1001
|
}))),
|
|
964
1002
|
{
|
|
965
1003
|
name: "cloudflare-r2",
|
|
966
|
-
ok:
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1004
|
+
ok: doctorPlatform === "ios" && doctorProfile.ios?.simulator
|
|
1005
|
+
? true
|
|
1006
|
+
: configuration.r2,
|
|
1007
|
+
status: doctorPlatform === "ios" && doctorProfile.ios?.simulator
|
|
1008
|
+
? "pass"
|
|
1009
|
+
: configuration.r2
|
|
1010
|
+
? "pass"
|
|
1011
|
+
: "fail",
|
|
1012
|
+
value: doctorPlatform === "ios" && doctorProfile.ios?.simulator
|
|
1013
|
+
? "not required for iOS Simulator"
|
|
1014
|
+
: configuration.r2
|
|
1015
|
+
? "configured"
|
|
1016
|
+
: "missing · fix: lynxship storage configure",
|
|
971
1017
|
},
|
|
972
1018
|
...(doctorPlatform === "android"
|
|
973
1019
|
? [
|
|
@@ -1594,7 +1640,9 @@ async function main() {
|
|
|
1594
1640
|
const platformArgument = flag("--platform", "android");
|
|
1595
1641
|
const buildAll = subcommand === "all" || platformArgument === "all";
|
|
1596
1642
|
const platform = buildAll ? "android" : platformValue(platformArgument);
|
|
1597
|
-
const
|
|
1643
|
+
const simulator = args.includes("--simulator");
|
|
1644
|
+
const skipUpload = args.includes("--no-upload") || simulator;
|
|
1645
|
+
assert(!simulator || platformArgument === "ios", "IOS_SIMULATOR_PLATFORM", "The --simulator option is only available with --platform ios.");
|
|
1598
1646
|
const allowUnsigned = args.includes("--allow-unsigned");
|
|
1599
1647
|
assert(!allowUnsigned || skipUpload, "CLI_UNSIGNED_UPLOAD_BLOCKED", "--allow-unsigned is only available with --no-upload and can never upload an unsigned Desktop artifact.");
|
|
1600
1648
|
await requireOperationalConfiguration(platform, { requireR2: !skipUpload });
|
|
@@ -1621,7 +1669,8 @@ async function main() {
|
|
|
1621
1669
|
}
|
|
1622
1670
|
assert(subcommand === "create" || buildAll, "CLI_BUILD_COMMAND", `Unknown build command: ${subcommand}`);
|
|
1623
1671
|
const config = await loadConfig(root);
|
|
1624
|
-
const profile = flag("--profile", "production");
|
|
1672
|
+
const profile = flag("--profile", simulator ? "simulator" : "production");
|
|
1673
|
+
const simulatorDevice = flag("--device") ?? undefined;
|
|
1625
1674
|
const wait = !args.includes("--no-wait");
|
|
1626
1675
|
const local = args.includes("--local");
|
|
1627
1676
|
const platforms = buildAll
|
|
@@ -1641,6 +1690,8 @@ async function main() {
|
|
|
1641
1690
|
allowUnsigned,
|
|
1642
1691
|
wait,
|
|
1643
1692
|
local,
|
|
1693
|
+
simulator,
|
|
1694
|
+
simulatorDevice,
|
|
1644
1695
|
state,
|
|
1645
1696
|
repository,
|
|
1646
1697
|
builds,
|
|
@@ -1670,6 +1721,8 @@ async function main() {
|
|
|
1670
1721
|
allowUnsigned,
|
|
1671
1722
|
wait,
|
|
1672
1723
|
local,
|
|
1724
|
+
simulator,
|
|
1725
|
+
simulatorDevice,
|
|
1673
1726
|
state,
|
|
1674
1727
|
repository,
|
|
1675
1728
|
builds,
|
|
@@ -1756,7 +1809,7 @@ void main()
|
|
|
1756
1809
|
.catch((error) => {
|
|
1757
1810
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1758
1811
|
const code = error.code ?? "CLI_ERROR";
|
|
1759
|
-
const nextSteps = guidanceForError(error);
|
|
1812
|
+
const nextSteps = guidanceForError(error, { args: rawArgs });
|
|
1760
1813
|
if (json) {
|
|
1761
1814
|
console.log(JSON.stringify({
|
|
1762
1815
|
error: message,
|
|
@@ -1765,6 +1818,9 @@ void main()
|
|
|
1765
1818
|
? {
|
|
1766
1819
|
nextSteps: nextSteps.commands,
|
|
1767
1820
|
...(nextSteps.note ? { note: nextSteps.note } : {}),
|
|
1821
|
+
...(nextSteps.environment
|
|
1822
|
+
? { environment: nextSteps.environment }
|
|
1823
|
+
: {}),
|
|
1768
1824
|
}
|
|
1769
1825
|
: {}),
|
|
1770
1826
|
}));
|
package/dist/ios-build.d.ts
CHANGED
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":"AAIA,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,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;AAwRD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAcxE;AAsDD,wBAAsB,eAAe,CACnC,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,QAAQ,CAAC,CAwMnB"}
|
package/dist/ios-build.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { copyFile, mkdir, readdir, readFile } from "node:fs/promises";
|
|
2
3
|
import { existsSync, readdirSync } from "node:fs";
|
|
3
4
|
import { join, resolve } from "node:path";
|
|
@@ -5,8 +6,177 @@ import { assert, sha256 } from "@lynxship/contracts";
|
|
|
5
6
|
import { transitionBuild } from "@lynxship/build-orchestrator";
|
|
6
7
|
import { loadR2, uploadR2Artifact } from "./r2.js";
|
|
7
8
|
import { nativeArtifactName } from "./artifact-name.js";
|
|
8
|
-
import { commandExists, runProcess } from "./process-runner.js";
|
|
9
|
+
import { captureProcess, commandExists, runProcess } from "./process-runner.js";
|
|
9
10
|
import { buildLynxBundle } from "./bundle-build.js";
|
|
11
|
+
async function listSimulatorDevices(root, filter) {
|
|
12
|
+
const result = await captureProcess("xcrun", ["simctl", "list", "devices", filter, "--json"], { cwd: root });
|
|
13
|
+
if (result.code !== 0)
|
|
14
|
+
return [];
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(result.stdout);
|
|
17
|
+
return Object.values(parsed.devices ?? {}).flat();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function selectSimulatorDevice(root, requested) {
|
|
24
|
+
if (requested)
|
|
25
|
+
return requested;
|
|
26
|
+
const booted = (await listSimulatorDevices(root, "booted"))[0];
|
|
27
|
+
if (booted)
|
|
28
|
+
return booted.udid;
|
|
29
|
+
const available = (await listSimulatorDevices(root, "available")).find((device) => device.isAvailable !== false);
|
|
30
|
+
assert(available, "IOS_SIMULATOR_RUNTIME_REQUIRED", "No available iOS Simulator device was found. Install an iOS Simulator runtime in Xcode, then rerun the build.");
|
|
31
|
+
return available.udid;
|
|
32
|
+
}
|
|
33
|
+
async function ensureSimulatorBooted(root, device, onEvent, quiet) {
|
|
34
|
+
const booted = (await listSimulatorDevices(root, "booted")).some((entry) => entry.udid === device);
|
|
35
|
+
if (!booted) {
|
|
36
|
+
onEvent?.(`Booting iOS Simulator ${device}…`);
|
|
37
|
+
await runProcess("xcrun", ["simctl", "boot", device], {
|
|
38
|
+
cwd: root,
|
|
39
|
+
quiet,
|
|
40
|
+
onOutput: onEvent,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
await runProcess("xcrun", ["simctl", "bootstatus", device, "-b"], {
|
|
44
|
+
cwd: root,
|
|
45
|
+
quiet,
|
|
46
|
+
onOutput: onEvent,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function findSimulatorApp(root, derivedData, configuration, scheme) {
|
|
50
|
+
const products = join(derivedData, "Build", "Products", `${configuration}-iphonesimulator`);
|
|
51
|
+
const expected = join(products, `${scheme}.app`);
|
|
52
|
+
if (existsSync(expected))
|
|
53
|
+
return expected;
|
|
54
|
+
const entries = await readdir(products, { withFileTypes: true }).catch(() => []);
|
|
55
|
+
const app = entries.find((entry) => entry.isDirectory() && entry.name.endsWith(".app"));
|
|
56
|
+
assert(app, "IOS_SIMULATOR_ARTIFACT_MISSING", `Xcode did not produce a Simulator .app under ${products}`);
|
|
57
|
+
return join(products, app.name);
|
|
58
|
+
}
|
|
59
|
+
async function hashDirectory(directory) {
|
|
60
|
+
const hash = createHash("sha256");
|
|
61
|
+
let size = 0;
|
|
62
|
+
async function visit(current, relative) {
|
|
63
|
+
const entries = await readdir(current, { withFileTypes: true });
|
|
64
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const entryRelative = relative ? `${relative}/${entry.name}` : entry.name;
|
|
67
|
+
const entryPath = join(current, entry.name);
|
|
68
|
+
if (entry.isDirectory()) {
|
|
69
|
+
hash.update(`dir:${entryRelative}\n`);
|
|
70
|
+
await visit(entryPath, entryRelative);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const content = await readFile(entryPath);
|
|
74
|
+
size += content.length;
|
|
75
|
+
hash.update(`file:${entryRelative}:${content.length}\n`);
|
|
76
|
+
hash.update(content.toString("latin1"), "latin1");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
await visit(directory, "");
|
|
81
|
+
return { hash: hash.digest("hex"), size };
|
|
82
|
+
}
|
|
83
|
+
async function runRealIosSimulatorBuild(job, options) {
|
|
84
|
+
assert(process.platform === "darwin", "IOS_MACOS_REQUIRED", "iOS Simulator builds require macOS with Xcode");
|
|
85
|
+
assert(commandExists("xcodebuild"), "IOS_XCODE_REQUIRED", "xcodebuild was not found. Install Xcode and select its command-line tools.");
|
|
86
|
+
assert(commandExists("xcrun"), "IOS_XCRUN_REQUIRED", "xcrun was not found. Install Xcode command-line tools.");
|
|
87
|
+
const project = findProject(options.root, options.profile);
|
|
88
|
+
const ios = options.profile.ios ?? {};
|
|
89
|
+
assert(ios.scheme, "IOS_SCHEME_REQUIRED", "Configure ios.scheme in the selected Simulator build profile");
|
|
90
|
+
const projectFlag = project.endsWith(".xcworkspace")
|
|
91
|
+
? "-workspace"
|
|
92
|
+
: "-project";
|
|
93
|
+
const configuration = ios.configuration ?? "Debug";
|
|
94
|
+
const device = await selectSimulatorDevice(options.root, options.simulatorDevice);
|
|
95
|
+
await installCocoaPods(options.root, options.quiet, options.onEvent);
|
|
96
|
+
const directory = join(options.root, ".lynxship", "ios", job.id, "simulator");
|
|
97
|
+
const derivedData = join(directory, "derived-data");
|
|
98
|
+
const step = (message, progress) => {
|
|
99
|
+
options.onEvent?.(message);
|
|
100
|
+
options.onProgress?.(progress, message);
|
|
101
|
+
};
|
|
102
|
+
try {
|
|
103
|
+
assert(options.uploadArtifacts !== true, "IOS_SIMULATOR_UPLOAD_BLOCKED", "iOS Simulator .app builds stay local. Use --no-upload; simulator artifacts are not store artifacts.");
|
|
104
|
+
await mkdir(directory, { recursive: true });
|
|
105
|
+
transitionBuild(job, "uploading_source", "iOS Simulator source prepared");
|
|
106
|
+
if (options.skipBundleBuild) {
|
|
107
|
+
step("Using shared Lynx bundle", 5);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
step("Building Lynx bundle with Rspeedy…", 5);
|
|
111
|
+
await buildLynxBundle(options.root, {
|
|
112
|
+
quiet: options.quiet,
|
|
113
|
+
onOutput: options.onEvent,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (ios.bundleScript) {
|
|
117
|
+
step("Syncing bundle into the iOS host…", 8);
|
|
118
|
+
await runProcess(process.execPath, [resolve(options.root, ios.bundleScript)], {
|
|
119
|
+
cwd: options.root,
|
|
120
|
+
quiet: options.quiet,
|
|
121
|
+
onOutput: options.onEvent,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
transitionBuild(job, "queued", "iOS Simulator build queued locally");
|
|
125
|
+
transitionBuild(job, "provisioning", "iOS Simulator destination selected");
|
|
126
|
+
transitionBuild(job, "installing_dependencies", "iOS Simulator dependencies ready");
|
|
127
|
+
await ensureSimulatorBooted(options.root, device, options.onEvent, options.quiet);
|
|
128
|
+
step("Building iOS Simulator .app…", 15);
|
|
129
|
+
transitionBuild(job, "building", "iOS Simulator build started");
|
|
130
|
+
await runProcess("xcodebuild", [
|
|
131
|
+
projectFlag,
|
|
132
|
+
project,
|
|
133
|
+
"-scheme",
|
|
134
|
+
ios.scheme,
|
|
135
|
+
"-configuration",
|
|
136
|
+
configuration,
|
|
137
|
+
"-sdk",
|
|
138
|
+
"iphonesimulator",
|
|
139
|
+
"-destination",
|
|
140
|
+
`id=${device}`,
|
|
141
|
+
"-derivedDataPath",
|
|
142
|
+
derivedData,
|
|
143
|
+
"CODE_SIGNING_ALLOWED=NO",
|
|
144
|
+
"CODE_SIGNING_REQUIRED=NO",
|
|
145
|
+
"build",
|
|
146
|
+
], { cwd: options.root, quiet: options.quiet, onOutput: options.onEvent });
|
|
147
|
+
const appPath = await findSimulatorApp(options.root, derivedData, configuration, ios.scheme);
|
|
148
|
+
step("Installing .app in iOS Simulator…", 75);
|
|
149
|
+
await runProcess("xcrun", ["simctl", "install", device, appPath], {
|
|
150
|
+
cwd: options.root,
|
|
151
|
+
quiet: options.quiet,
|
|
152
|
+
onOutput: options.onEvent,
|
|
153
|
+
});
|
|
154
|
+
const artifact = await hashDirectory(appPath);
|
|
155
|
+
const artifactName = nativeArtifactName("app");
|
|
156
|
+
job.attempts += 1;
|
|
157
|
+
job.artifact = {
|
|
158
|
+
name: artifactName,
|
|
159
|
+
hash: artifact.hash,
|
|
160
|
+
path: appPath,
|
|
161
|
+
size: artifact.size,
|
|
162
|
+
contentType: "application/octet-stream",
|
|
163
|
+
};
|
|
164
|
+
transitionBuild(job, "signing", "Simulator app verification completed");
|
|
165
|
+
transitionBuild(job, "uploading_artifacts", "Simulator app collected locally");
|
|
166
|
+
step(`Simulator app ready: ${artifactName}`, 100);
|
|
167
|
+
return transitionBuild(job, "success", "iOS Simulator app created");
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
if (!["success", "failed", "canceled", "timed_out"].includes(job.state))
|
|
171
|
+
transitionBuild(job, "failed", error instanceof Error ? error.message : "iOS Simulator build failed");
|
|
172
|
+
job.logs.push({
|
|
173
|
+
level: "error",
|
|
174
|
+
message: error instanceof Error ? error.message : "iOS Simulator build failed",
|
|
175
|
+
at: new Date().toISOString(),
|
|
176
|
+
});
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
10
180
|
export function hasIosHost(root, profile) {
|
|
11
181
|
if (process.platform !== "darwin")
|
|
12
182
|
return false;
|
|
@@ -66,6 +236,8 @@ async function findIpa(directory) {
|
|
|
66
236
|
return join(directory, ipa.name);
|
|
67
237
|
}
|
|
68
238
|
export async function runRealIosBuild(job, options) {
|
|
239
|
+
if (options.simulator)
|
|
240
|
+
return runRealIosSimulatorBuild(job, options);
|
|
69
241
|
assert(process.platform === "darwin", "IOS_MACOS_REQUIRED", "iOS builds require macOS with Xcode");
|
|
70
242
|
assert(commandExists("xcodebuild"), "IOS_XCODE_REQUIRED", "xcodebuild was not found. Install Xcode and select its command-line tools.");
|
|
71
243
|
assert(commandExists("xcrun"), "IOS_XCRUN_REQUIRED", "xcrun was not found. Install Xcode command-line tools.");
|
package/dist/ios-toolchain.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface IosToolchainReport {
|
|
|
14
14
|
scheme?: string;
|
|
15
15
|
automaticSigning: boolean;
|
|
16
16
|
}
|
|
17
|
-
export
|
|
17
|
+
export type IosBuildTarget = "device" | "simulator";
|
|
18
|
+
export declare function inspectIosToolchain(root: string, profile: BuildProfile, target?: IosBuildTarget): Promise<IosToolchainReport>;
|
|
18
19
|
export declare function formatIosToolchainFailure(report: IosToolchainReport): string;
|
|
19
20
|
//# sourceMappingURL=ios-toolchain.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ios-toolchain.d.ts","sourceRoot":"","sources":["../src/ios-toolchain.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAOhD,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,kBAAkB,CAAC;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;CAC3B;
|
|
1
|
+
{"version":3,"file":"ios-toolchain.d.ts","sourceRoot":"","sources":["../src/ios-toolchain.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAOhD,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,kBAAkB,CAAC;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,WAAW,CAAC;AAuKpD,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,EACrB,MAAM,GAAE,cAAyB,GAChC,OAAO,CAAC,kBAAkB,CAAC,CA2W7B;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAQ5E"}
|
package/dist/ios-toolchain.js
CHANGED
|
@@ -86,10 +86,11 @@ async function hasValidProvisioningProfile(root, bundleIdentifier) {
|
|
|
86
86
|
}
|
|
87
87
|
return false;
|
|
88
88
|
}
|
|
89
|
-
async function probeProject(root, context, profile) {
|
|
89
|
+
async function probeProject(root, context, profile, target) {
|
|
90
90
|
if (!context.path || !context.scheme || !commandExists("xcodebuild"))
|
|
91
91
|
return context;
|
|
92
|
-
const configuration = profile.ios?.configuration ??
|
|
92
|
+
const configuration = profile.ios?.configuration ??
|
|
93
|
+
(target === "simulator" ? "Debug" : "Release");
|
|
93
94
|
const settings = await captureProcess("xcodebuild", [
|
|
94
95
|
context.flag,
|
|
95
96
|
context.path,
|
|
@@ -124,16 +125,19 @@ async function probeCommand(command, args, cwd) {
|
|
|
124
125
|
return false;
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
|
-
export async function inspectIosToolchain(root, profile) {
|
|
128
|
+
export async function inspectIosToolchain(root, profile, target = "device") {
|
|
128
129
|
const checks = [];
|
|
129
130
|
const isMac = platform() === "darwin";
|
|
130
131
|
const host = findHost(root, profile);
|
|
131
132
|
const hostExists = Boolean(host && existsSync(host));
|
|
132
133
|
const scheme = profile.ios?.scheme;
|
|
134
|
+
const simulator = target === "simulator";
|
|
133
135
|
const exportOptions = profile.ios?.exportOptionsPlist
|
|
134
136
|
? resolve(root, profile.ios.exportOptionsPlist)
|
|
135
137
|
: undefined;
|
|
136
|
-
checks.push(check("ios-platform", isMac ? "pass" : "fail", isMac
|
|
138
|
+
checks.push(check("ios-platform", isMac ? "pass" : "fail", isMac
|
|
139
|
+
? "macOS"
|
|
140
|
+
: `${platform()} · macOS is required for an iOS ${simulator ? "Simulator app" : "IPA"}`, isMac
|
|
137
141
|
? undefined
|
|
138
142
|
: "Run the iOS build on a macOS machine or macOS CI runner"));
|
|
139
143
|
if (!isMac) {
|
|
@@ -168,12 +172,12 @@ export async function inspectIosToolchain(root, profile) {
|
|
|
168
172
|
checks.push(check("ios-host", hostExists ? "pass" : "fail", hostExists ? host : "Xcode project or workspace not found", hostExists
|
|
169
173
|
? undefined
|
|
170
174
|
: "lynxship ios host init --bundle-identifier com.example.myapp"));
|
|
171
|
-
checks.push(check("ios-scheme", scheme ? "pass" : "fail", scheme ?? "missing from the
|
|
175
|
+
checks.push(check("ios-scheme", scheme ? "pass" : "fail", scheme ?? "missing from the selected build profile", scheme ? undefined : "Set ios.scheme in the selected build profile"));
|
|
172
176
|
const context = await probeProject(root, {
|
|
173
177
|
path: hostExists ? host : undefined,
|
|
174
178
|
flag: host ? projectFlag(host) : "-project",
|
|
175
179
|
scheme,
|
|
176
|
-
}, profile);
|
|
180
|
+
}, profile, target);
|
|
177
181
|
const settingsOutput = context.settings
|
|
178
182
|
? commandOutput(context.settings)
|
|
179
183
|
: "";
|
|
@@ -190,29 +194,40 @@ export async function inspectIosToolchain(root, profile) {
|
|
|
190
194
|
: "Open the project in Xcode and verify the scheme is shared and buildable"));
|
|
191
195
|
const automaticSigning = setting(settingsOutput, "CODE_SIGN_STYLE") === "Automatic";
|
|
192
196
|
const team = setting(settingsOutput, "DEVELOPMENT_TEAM");
|
|
193
|
-
checks.push(check("apple-team",
|
|
194
|
-
? "
|
|
195
|
-
:
|
|
196
|
-
? "
|
|
197
|
-
:
|
|
197
|
+
checks.push(check("apple-team", simulator
|
|
198
|
+
? "pass"
|
|
199
|
+
: context.settings?.code !== 0 || !context.settings
|
|
200
|
+
? "warn"
|
|
201
|
+
: team
|
|
202
|
+
? "pass"
|
|
203
|
+
: "fail", simulator
|
|
204
|
+
? "not required for iOS Simulator"
|
|
205
|
+
: (team ?? "team not resolved from Xcode build settings"), simulator || team
|
|
198
206
|
? undefined
|
|
199
207
|
: "Select the Apple Developer Team in Xcode Signing & Capabilities"));
|
|
200
208
|
const distribution = profile.distribution ?? profile.ios?.distribution ?? "store";
|
|
201
|
-
const identityResult =
|
|
202
|
-
?
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
209
|
+
const identityResult = simulator
|
|
210
|
+
? undefined
|
|
211
|
+
: await (commandExists("security")
|
|
212
|
+
? captureProcess("security", ["find-identity", "-v", "-p", "codesigning"], {
|
|
213
|
+
cwd: root,
|
|
214
|
+
})
|
|
215
|
+
: undefined);
|
|
206
216
|
const identityOutput = identityResult ? commandOutput(identityResult) : "";
|
|
207
217
|
const identityCount = Number(identityOutput.match(/(\d+) valid identities found/)?.[1] ?? 0);
|
|
208
218
|
const requiredIdentity = distribution === "store" ? "Apple Distribution" : "Apple Development";
|
|
209
|
-
checks.push(check("apple-signing-identity",
|
|
210
|
-
? "
|
|
211
|
-
:
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
219
|
+
checks.push(check("apple-signing-identity", simulator
|
|
220
|
+
? "pass"
|
|
221
|
+
: identityCount === 0 || !identityOutput.includes(requiredIdentity)
|
|
222
|
+
? "fail"
|
|
223
|
+
: "pass", simulator
|
|
224
|
+
? "not required for iOS Simulator"
|
|
225
|
+
: identityCount === 0
|
|
226
|
+
? "no valid code-signing identity found"
|
|
227
|
+
: identityOutput.includes(requiredIdentity)
|
|
228
|
+
? `${requiredIdentity} identity available`
|
|
229
|
+
: `${requiredIdentity} identity not found`, simulator ||
|
|
230
|
+
(identityCount > 0 && identityOutput.includes(requiredIdentity))
|
|
216
231
|
? undefined
|
|
217
232
|
: "Install or select the Apple certificate in Xcode Settings > Accounts, then rerun doctor"));
|
|
218
233
|
const podfile = join(root, "ios", "Podfile");
|
|
@@ -241,40 +256,49 @@ export async function inspectIosToolchain(root, profile) {
|
|
|
241
256
|
"development",
|
|
242
257
|
"enterprise",
|
|
243
258
|
]);
|
|
244
|
-
checks.push(check("export-options",
|
|
245
|
-
? "
|
|
246
|
-
: !
|
|
259
|
+
checks.push(check("export-options", simulator
|
|
260
|
+
? "pass"
|
|
261
|
+
: !exportOptions
|
|
247
262
|
? "fail"
|
|
248
|
-
: !
|
|
263
|
+
: !existsSync(exportOptions)
|
|
249
264
|
? "fail"
|
|
250
|
-
:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
265
|
+
: !exportMethod || !supportedExportMethods.has(exportMethod)
|
|
266
|
+
? "fail"
|
|
267
|
+
: "pass", simulator
|
|
268
|
+
? "not required for iOS Simulator"
|
|
269
|
+
: !exportOptions
|
|
270
|
+
? "missing from the production build profile"
|
|
271
|
+
: !existsSync(exportOptions)
|
|
272
|
+
? `${exportOptions} not found`
|
|
273
|
+
: exportMethod && supportedExportMethods.has(exportMethod)
|
|
274
|
+
? `method: ${exportMethod}`
|
|
275
|
+
: "method missing or invalid for an iOS export", simulator ||
|
|
276
|
+
(exportOptions &&
|
|
277
|
+
existsSync(exportOptions) &&
|
|
278
|
+
exportMethod &&
|
|
279
|
+
supportedExportMethods.has(exportMethod))
|
|
260
280
|
? undefined
|
|
261
281
|
: "Set build.production.ios.exportOptionsPlist to a valid ExportOptions.plist"));
|
|
262
282
|
const manualProvisioning = setting(settingsOutput, "CODE_SIGN_STYLE") === "Manual" ||
|
|
263
283
|
exportOptionsContent?.includes("<key>provisioningProfiles</key>") === true;
|
|
264
284
|
const profilesAvailable = await hasValidProvisioningProfile(root, setting(settingsOutput, "PRODUCT_BUNDLE_IDENTIFIER"));
|
|
265
|
-
checks.push(check("ios-provisioning",
|
|
266
|
-
? "
|
|
267
|
-
:
|
|
268
|
-
? "
|
|
269
|
-
:
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
285
|
+
checks.push(check("ios-provisioning", simulator
|
|
286
|
+
? "pass"
|
|
287
|
+
: manualProvisioning && !profilesAvailable
|
|
288
|
+
? "fail"
|
|
289
|
+
: automaticSigning && !profilesAvailable
|
|
290
|
+
? "warn"
|
|
291
|
+
: "pass", simulator
|
|
292
|
+
? "not required for iOS Simulator"
|
|
293
|
+
: manualProvisioning && !profilesAvailable
|
|
294
|
+
? "manual signing selected but no local provisioning profile was found"
|
|
295
|
+
: automaticSigning && !profilesAvailable
|
|
296
|
+
? "Xcode-managed provisioning will be requested during the build"
|
|
297
|
+
: profilesAvailable
|
|
298
|
+
? "local provisioning profile available"
|
|
299
|
+
: "not required by the detected signing mode", simulator || !(manualProvisioning && !profilesAvailable)
|
|
300
|
+
? undefined
|
|
301
|
+
: "Download a matching .mobileprovision/.provisionprofile from Apple Developer and install it in Xcode"));
|
|
278
302
|
const simctlAvailable = await probeCommand("xcrun", ["--find", "simctl"], root);
|
|
279
303
|
const devicectlAvailable = await probeCommand("xcrun", ["--find", "devicectl"], root);
|
|
280
304
|
checks.push(check("ios-device-tools", simctlAvailable || devicectlAvailable ? "pass" : "warn", simctlAvailable && devicectlAvailable
|
|
@@ -286,6 +310,18 @@ export async function inspectIosToolchain(root, profile) {
|
|
|
286
310
|
: "simctl and devicectl unavailable", simctlAvailable || devicectlAvailable
|
|
287
311
|
? undefined
|
|
288
312
|
: "Install the full Xcode toolchain, then rerun lynxship doctor --platform ios"));
|
|
313
|
+
if (simulator) {
|
|
314
|
+
const runtimeResult = await (commandExists("xcrun")
|
|
315
|
+
? captureProcess("xcrun", ["simctl", "list", "runtimes"], { cwd: root })
|
|
316
|
+
: undefined);
|
|
317
|
+
const runtimeOutput = runtimeResult ? commandOutput(runtimeResult) : "";
|
|
318
|
+
const hasRuntime = /iOS\s+[0-9]/.test(runtimeOutput);
|
|
319
|
+
checks.push(check("ios-simulator-runtime", hasRuntime ? "pass" : "fail", hasRuntime
|
|
320
|
+
? "iOS Simulator runtime available"
|
|
321
|
+
: "no iOS Simulator runtime found", hasRuntime
|
|
322
|
+
? undefined
|
|
323
|
+
: "Open Xcode > Settings > Components and install an iOS Simulator runtime"));
|
|
324
|
+
}
|
|
289
325
|
const failures = checks.filter((item) => item.status === "fail");
|
|
290
326
|
return {
|
|
291
327
|
ok: failures.length === 0,
|
package/dist/ui/components.d.ts
CHANGED
|
@@ -33,6 +33,7 @@ export declare const pill: {
|
|
|
33
33
|
error: (text: string) => string;
|
|
34
34
|
};
|
|
35
35
|
export declare function finalLine(message: string, success?: boolean): void;
|
|
36
|
-
export declare function nextSteps(commands: string[], note?: string): void;
|
|
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
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;AAI7C,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,
|
|
1
|
+
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../src/ui/components.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAI7C,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,CAK7D"}
|
package/dist/ui/components.js
CHANGED
|
@@ -265,10 +265,12 @@ export const pill = {
|
|
|
265
265
|
export function finalLine(message, success = true) {
|
|
266
266
|
console.log(`\n${success ? c.brand("◆") : c.red("◆")} ${c.text(message)}`);
|
|
267
267
|
}
|
|
268
|
-
export function nextSteps(commands, note) {
|
|
268
|
+
export function nextSteps(commands, note, environment) {
|
|
269
269
|
if (commands.length === 0)
|
|
270
270
|
return;
|
|
271
271
|
console.error(`\n${c.yellow("Next steps")}`);
|
|
272
|
+
if (environment)
|
|
273
|
+
console.error(` ${c.muted(`Run on ${environment}:`)}`);
|
|
272
274
|
commands.forEach((command, index) => {
|
|
273
275
|
console.error(` ${c.muted(`${index + 1}.`)} ${c.teal(command)}`);
|
|
274
276
|
});
|
|
@@ -286,3 +288,10 @@ export function downloadArtifact(url, expiresAt, enabled = true) {
|
|
|
286
288
|
if (expiresAt)
|
|
287
289
|
console.log(` ${c.muted(`Link expires at ${expiresAt}`)}`);
|
|
288
290
|
}
|
|
291
|
+
export function devServerQr(url, enabled = true) {
|
|
292
|
+
if (!enabled)
|
|
293
|
+
return;
|
|
294
|
+
console.log(`\n${c.brandBold("Lynx Explorer")}`);
|
|
295
|
+
qrcode.generate(url, { small: true }, (code) => console.log(code));
|
|
296
|
+
console.log(` ${c.teal("URL")} ${url}\n`);
|
|
297
|
+
}
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -19,6 +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
23
|
}
|
|
23
24
|
export declare function createCliUi(args: string[]): CliUi;
|
|
24
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,
|
|
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;CAG/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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { finalLine, log, sectionHeader, summaryBox, nextSteps, createProgress, spin, downloadArtifact, } from "./components.js";
|
|
1
|
+
import { finalLine, log, sectionHeader, summaryBox, nextSteps, createProgress, spin, downloadArtifact, devServerQr, } from "./components.js";
|
|
2
2
|
import { printBanner } from "./logo.js";
|
|
3
3
|
import { setColors } from "./state.js";
|
|
4
4
|
import { isInteractive, terminalOptions, } from "./terminal.js";
|
|
@@ -43,7 +43,7 @@ export class CliUi {
|
|
|
43
43
|
nextSteps(guidance) {
|
|
44
44
|
if (this.options.json || this.options.quiet)
|
|
45
45
|
return;
|
|
46
|
-
nextSteps(guidance.commands, guidance.note);
|
|
46
|
+
nextSteps(guidance.commands, guidance.note, guidance.environment);
|
|
47
47
|
}
|
|
48
48
|
summary(title, rows) {
|
|
49
49
|
if (this.interactive)
|
|
@@ -66,6 +66,9 @@ export class CliUi {
|
|
|
66
66
|
downloadArtifact(url, expiresAt) {
|
|
67
67
|
downloadArtifact(url, expiresAt, this.interactive);
|
|
68
68
|
}
|
|
69
|
+
devServerQr(url) {
|
|
70
|
+
devServerQr(url, this.interactive && !this.options.quiet);
|
|
71
|
+
}
|
|
69
72
|
}
|
|
70
73
|
export function createCliUi(args) {
|
|
71
74
|
return new CliUi(args);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lynxship/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "Build, sign, store, submit and update LynxJS applications from the terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
"ora": "8.2.0",
|
|
70
70
|
"qrcode-terminal": "0.12.0",
|
|
71
71
|
"@lynxship/build-orchestrator": "0.1.0",
|
|
72
|
-
"@lynxship/submit": "0.1.0",
|
|
73
72
|
"@lynxship/db": "0.1.0",
|
|
73
|
+
"@lynxship/submit": "0.1.0",
|
|
74
74
|
"@lynxship/contracts": "0.1.0",
|
|
75
75
|
"@lynxship/signing": "0.1.0"
|
|
76
76
|
},
|