@lynxship/cli 0.1.11 → 0.1.13
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/guidance.d.ts +6 -1
- package/dist/guidance.d.ts.map +1 -1
- package/dist/guidance.js +141 -8
- package/dist/index.js +59 -28
- package/dist/ios-build.d.ts +2 -0
- package/dist/ios-build.d.ts.map +1 -1
- package/dist/ios-build.js +178 -3
- 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 +1 -1
- package/dist/ui/components.d.ts.map +1 -1
- package/dist/ui/components.js +3 -1
- package/dist/ui/index.js +1 -1
- package/package.json +4 -4
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/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;AA6mBD,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,eAAoB,GAC5B,WAAW,CAEb"}
|
package/dist/guidance.js
CHANGED
|
@@ -223,9 +223,43 @@ 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
|
-
commands: [
|
|
228
|
-
|
|
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.",
|
|
229
263
|
},
|
|
230
264
|
IOS_PROJECT_REQUIRED: {
|
|
231
265
|
commands: [
|
|
@@ -234,15 +268,20 @@ const guidance = {
|
|
|
234
268
|
],
|
|
235
269
|
},
|
|
236
270
|
IOS_SCHEME_REQUIRED: {
|
|
237
|
-
commands: [
|
|
271
|
+
commands: [
|
|
272
|
+
"lynxship inspect",
|
|
273
|
+
"lynxship doctor --platform ios --profile simulator",
|
|
274
|
+
"lynxship doctor --platform ios --profile production",
|
|
275
|
+
],
|
|
238
276
|
note: "Set build.<profile>.ios.scheme in lynxship.json.",
|
|
239
277
|
},
|
|
240
278
|
IOS_EXPORT_OPTIONS_REQUIRED: {
|
|
241
279
|
commands: [
|
|
242
280
|
"lynxship ios host init --bundle-identifier com.example.myapp",
|
|
243
281
|
"lynxship doctor --platform ios",
|
|
282
|
+
"lynxship build --platform ios --simulator --profile simulator --no-upload",
|
|
244
283
|
],
|
|
245
|
-
note: "Set build.<profile>.ios.exportOptionsPlist
|
|
284
|
+
note: "Set build.<profile>.ios.exportOptionsPlist for a device IPA; simulator .app builds do not require export options.",
|
|
246
285
|
},
|
|
247
286
|
IOS_DEVICE_REQUIRED: {
|
|
248
287
|
commands: [
|
|
@@ -364,7 +403,7 @@ const guidance = {
|
|
|
364
403
|
note: "Lynx Trace and Recorder are operated from Lynx DevTool Desktop after the native -dev runtime is integrated.",
|
|
365
404
|
},
|
|
366
405
|
};
|
|
367
|
-
|
|
406
|
+
function guidanceForErrorBase(error) {
|
|
368
407
|
const code = error.code;
|
|
369
408
|
if (typeof code === "string" && guidance[code])
|
|
370
409
|
return guidance[code];
|
|
@@ -453,11 +492,105 @@ export function guidanceForError(error) {
|
|
|
453
492
|
if (/xcode|xcrun|cocoapods|pod install/i.test(message)) {
|
|
454
493
|
return {
|
|
455
494
|
commands: [
|
|
456
|
-
"
|
|
457
|
-
"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",
|
|
458
498
|
],
|
|
459
|
-
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.",
|
|
460
500
|
};
|
|
461
501
|
}
|
|
462
502
|
return { commands: [] };
|
|
463
503
|
}
|
|
504
|
+
function argumentValue(args, name) {
|
|
505
|
+
const index = args.indexOf(name);
|
|
506
|
+
if (index >= 0)
|
|
507
|
+
return args[index + 1];
|
|
508
|
+
const inline = args.find((value) => value.startsWith(`${name}=`));
|
|
509
|
+
return inline?.slice(name.length + 1);
|
|
510
|
+
}
|
|
511
|
+
function targetFromCode(code) {
|
|
512
|
+
if (!code)
|
|
513
|
+
return undefined;
|
|
514
|
+
if (code.startsWith("ANDROID_"))
|
|
515
|
+
return "android";
|
|
516
|
+
if (code.startsWith("IOS_"))
|
|
517
|
+
return "ios";
|
|
518
|
+
if (code.startsWith("HARMONY_"))
|
|
519
|
+
return "harmony";
|
|
520
|
+
if (code.startsWith("WEB_"))
|
|
521
|
+
return "web";
|
|
522
|
+
if (code.startsWith("DESKTOP_"))
|
|
523
|
+
return "desktop";
|
|
524
|
+
if (code === "BUILD_ALL_MACOS_REQUIRED")
|
|
525
|
+
return "all";
|
|
526
|
+
return undefined;
|
|
527
|
+
}
|
|
528
|
+
function targetFromCommand(command) {
|
|
529
|
+
const match = command.match(/--platform\s+(android|ios|harmony|web|desktop|all)/);
|
|
530
|
+
return match?.[1];
|
|
531
|
+
}
|
|
532
|
+
function targetCommand(platform, simulator, action) {
|
|
533
|
+
if (platform === "all")
|
|
534
|
+
return action === "doctor"
|
|
535
|
+
? "lynxship doctor --platform android"
|
|
536
|
+
: "lynxship build --platform all --profile production";
|
|
537
|
+
if (platform === "ios" && simulator) {
|
|
538
|
+
return action === "doctor"
|
|
539
|
+
? "lynxship doctor --platform ios --profile simulator"
|
|
540
|
+
: "lynxship build --platform ios --simulator --profile simulator --no-upload";
|
|
541
|
+
}
|
|
542
|
+
if (action === "doctor")
|
|
543
|
+
return `lynxship doctor --platform ${platform}`;
|
|
544
|
+
return `lynxship build --platform ${platform} --profile production`;
|
|
545
|
+
}
|
|
546
|
+
function adaptGuidance(guidance, error, context) {
|
|
547
|
+
const code = error.code;
|
|
548
|
+
const errorCode = typeof code === "string" ? code : undefined;
|
|
549
|
+
const args = context.args ?? [];
|
|
550
|
+
const requested = argumentValue(args, "--platform");
|
|
551
|
+
const target = requested ?? targetFromCode(errorCode);
|
|
552
|
+
const simulator = args.includes("--simulator");
|
|
553
|
+
let commands = guidance.commands;
|
|
554
|
+
// Generic errors must preserve the platform the developer actually asked
|
|
555
|
+
// for. The old fallback to Android was misleading for iOS/Web/Desktop jobs.
|
|
556
|
+
if (target &&
|
|
557
|
+
errorCode !== "BUILD_ALL_MACOS_REQUIRED" &&
|
|
558
|
+
["BUILD_", "PROFILE_NOT_FOUND"].some((prefix) => errorCode?.startsWith(prefix))) {
|
|
559
|
+
commands = [
|
|
560
|
+
targetCommand(target, target === "ios" && simulator, "doctor"),
|
|
561
|
+
targetCommand(target, target === "ios" && simulator, "build"),
|
|
562
|
+
];
|
|
563
|
+
}
|
|
564
|
+
const hostPlatform = context.hostPlatform ?? process.platform;
|
|
565
|
+
const macOnly = hostPlatform !== "darwin";
|
|
566
|
+
let requiredEnvironment;
|
|
567
|
+
commands = commands.map((command) => {
|
|
568
|
+
const commandTarget = targetFromCommand(command);
|
|
569
|
+
const isIosCommand = commandTarget === "ios" ||
|
|
570
|
+
command.includes("ios host") ||
|
|
571
|
+
command.startsWith("xcode-select") ||
|
|
572
|
+
command.startsWith("xcrun ") ||
|
|
573
|
+
command.startsWith("brew ");
|
|
574
|
+
const isAllCommand = commandTarget === "all";
|
|
575
|
+
if (macOnly && isIosCommand) {
|
|
576
|
+
requiredEnvironment = "macOS or a macOS CI runner";
|
|
577
|
+
return command;
|
|
578
|
+
}
|
|
579
|
+
if (macOnly && isAllCommand) {
|
|
580
|
+
requiredEnvironment = "macOS or a macOS CI runner";
|
|
581
|
+
return command;
|
|
582
|
+
}
|
|
583
|
+
return command;
|
|
584
|
+
});
|
|
585
|
+
const notes = guidance.note ? [guidance.note] : [];
|
|
586
|
+
if (requiredEnvironment)
|
|
587
|
+
notes.push("These commands must be run on macOS or a macOS CI runner; Android remains supported on Windows, macOS and Linux.");
|
|
588
|
+
return {
|
|
589
|
+
commands,
|
|
590
|
+
note: notes.length > 0 ? notes.join(" ") : undefined,
|
|
591
|
+
environment: requiredEnvironment,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
export function guidanceForError(error, context = {}) {
|
|
595
|
+
return adaptGuidance(guidanceForErrorBase(error), error, context);
|
|
596
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -150,6 +150,12 @@ function mobilePlatformValue(value) {
|
|
|
150
150
|
async function renderConfigurationFooter() {
|
|
151
151
|
if (!ui.interactive || ui.options.quiet)
|
|
152
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;
|
|
153
159
|
const status = await readConfigurationStatus();
|
|
154
160
|
const ready = status.r2 && status.android;
|
|
155
161
|
if (ready)
|
|
@@ -203,14 +209,19 @@ async function saveState(state, repository, builds, submissions) {
|
|
|
203
209
|
}
|
|
204
210
|
let stateSaveQueue = Promise.resolve();
|
|
205
211
|
async function executeBuild(options, showResult = true) {
|
|
206
|
-
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;
|
|
207
213
|
if (wait && !local)
|
|
208
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;
|
|
209
219
|
if (platform === "android" || platform === "ios")
|
|
210
220
|
await requireAutolinkReady(root, platform);
|
|
211
|
-
const runtime = await inspectRuntimeFingerprint(root, platform,
|
|
221
|
+
const runtime = await inspectRuntimeFingerprint(root, platform, effectiveConfig);
|
|
222
|
+
const resolvedBuildProfile = resolveProfile(effectiveConfig, profile);
|
|
212
223
|
const job = await builds.create({
|
|
213
|
-
projectId: configuredProjectId(
|
|
224
|
+
projectId: configuredProjectId(effectiveConfig),
|
|
214
225
|
organizationId: "local_org",
|
|
215
226
|
platform,
|
|
216
227
|
profile,
|
|
@@ -243,7 +254,8 @@ async function executeBuild(options, showResult = true) {
|
|
|
243
254
|
const realAndroid = platform === "android" &&
|
|
244
255
|
isSupportedAndroidPlatform() &&
|
|
245
256
|
(await hasAndroidHost(root));
|
|
246
|
-
const realIos = platform === "ios" &&
|
|
257
|
+
const realIos = platform === "ios" &&
|
|
258
|
+
hasIosHost(root, resolveProfile(effectiveConfig, profile));
|
|
247
259
|
if (platform === "ios" && !realIos && !local)
|
|
248
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.");
|
|
249
261
|
if (platform === "android" && !realAndroid && !local)
|
|
@@ -253,7 +265,7 @@ async function executeBuild(options, showResult = true) {
|
|
|
253
265
|
assert(toolchain.ok, "ANDROID_TOOLCHAIN_REQUIRED", `Android toolchain is not ready: ${formatAndroidToolchainFailure(toolchain)}`);
|
|
254
266
|
await runRealAndroidBuild(job, {
|
|
255
267
|
root,
|
|
256
|
-
profile:
|
|
268
|
+
profile: resolvedBuildProfile,
|
|
257
269
|
uploadArtifacts: !skipUpload,
|
|
258
270
|
skipBundleBuild,
|
|
259
271
|
quiet: json,
|
|
@@ -262,11 +274,13 @@ async function executeBuild(options, showResult = true) {
|
|
|
262
274
|
});
|
|
263
275
|
}
|
|
264
276
|
else if (realIos) {
|
|
265
|
-
const toolchain = await inspectIosToolchain(root, resolveProfile(
|
|
277
|
+
const toolchain = await inspectIosToolchain(root, resolveProfile(effectiveConfig, profile), simulator ? "simulator" : "device");
|
|
266
278
|
assert(toolchain.ok, "IOS_TOOLCHAIN_REQUIRED", `iOS toolchain is not ready: ${formatIosToolchainFailure(toolchain)}`);
|
|
267
279
|
await runRealIosBuild(job, {
|
|
268
280
|
root,
|
|
269
|
-
profile:
|
|
281
|
+
profile: resolvedBuildProfile,
|
|
282
|
+
simulator,
|
|
283
|
+
simulatorDevice,
|
|
270
284
|
uploadArtifacts: !skipUpload,
|
|
271
285
|
skipBundleBuild,
|
|
272
286
|
quiet: json,
|
|
@@ -277,7 +291,7 @@ async function executeBuild(options, showResult = true) {
|
|
|
277
291
|
else if (!local && platform === "web") {
|
|
278
292
|
await runRealWebBuild(job, {
|
|
279
293
|
root,
|
|
280
|
-
profile: resolveProfile(
|
|
294
|
+
profile: resolveProfile(effectiveConfig, profile),
|
|
281
295
|
uploadArtifacts: !skipUpload,
|
|
282
296
|
skipBundleBuild,
|
|
283
297
|
quiet: json,
|
|
@@ -288,7 +302,7 @@ async function executeBuild(options, showResult = true) {
|
|
|
288
302
|
else if (!local && platform === "harmony") {
|
|
289
303
|
await runRealHarmonyBuild(job, {
|
|
290
304
|
root,
|
|
291
|
-
profile: resolveProfile(
|
|
305
|
+
profile: resolveProfile(effectiveConfig, profile),
|
|
292
306
|
uploadArtifacts: !skipUpload,
|
|
293
307
|
skipBundleBuild,
|
|
294
308
|
quiet: json,
|
|
@@ -299,7 +313,7 @@ async function executeBuild(options, showResult = true) {
|
|
|
299
313
|
else if (!local && platform === "desktop") {
|
|
300
314
|
await runRealDesktopBuild(job, {
|
|
301
315
|
root,
|
|
302
|
-
profile: resolveProfile(
|
|
316
|
+
profile: resolveProfile(effectiveConfig, profile),
|
|
303
317
|
uploadArtifacts: !skipUpload,
|
|
304
318
|
allowUnsigned: options.allowUnsigned,
|
|
305
319
|
skipBundleBuild,
|
|
@@ -440,9 +454,11 @@ Commands:
|
|
|
440
454
|
|
|
441
455
|
Build options:
|
|
442
456
|
--platform <p> Target android, ios, harmony, web, desktop or all (default: android)
|
|
443
|
-
--profile <name> Build profile (default: production)
|
|
457
|
+
--profile <name> Build profile (default: production; simulator uses simulator)
|
|
444
458
|
--no-wait Queue the build without executing it locally
|
|
445
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
|
|
446
462
|
--allow-unsigned Allow an unsigned Desktop artifact only with --no-upload (local tests)
|
|
447
463
|
--local Use the contract-only build path for tests
|
|
448
464
|
|
|
@@ -488,7 +504,7 @@ Global options:
|
|
|
488
504
|
--application-id <id> Android package/application ID for host init
|
|
489
505
|
--bundle-identifier <id> iOS bundle identifier for host init
|
|
490
506
|
--library-dir <path> Native library directory for autolink codegen
|
|
491
|
-
--simulator
|
|
507
|
+
--simulator Build/install an iOS .app or install one with simctl
|
|
492
508
|
--help Show this complete command reference
|
|
493
509
|
|
|
494
510
|
Node support: Node 22/24 LTS or Node 26 Current. Use Node 24 LTS for production.`;
|
|
@@ -508,7 +524,7 @@ async function looksLikeLynxProject() {
|
|
|
508
524
|
...packageJson.dependencies,
|
|
509
525
|
...packageJson.devDependencies,
|
|
510
526
|
};
|
|
511
|
-
return (Object.keys(dependencies).some((name) => name.startsWith("@lynx-js/")) ||
|
|
527
|
+
return (Object.keys(dependencies).some((name) => name.startsWith("@lynx-js/") || name === "vue-lynx") ||
|
|
512
528
|
Object.values(packageJson.scripts ?? {}).some((script) => script.includes("rspeedy")));
|
|
513
529
|
}
|
|
514
530
|
catch {
|
|
@@ -885,12 +901,7 @@ async function main() {
|
|
|
885
901
|
const nativeCredentialStore = !credentialStore.includes("owner-only");
|
|
886
902
|
const androidHost = doctorPlatform === "android" ? await hasAndroidHost(root) : false;
|
|
887
903
|
const doctorProfileName = flag("--profile", "production");
|
|
888
|
-
const doctorProfile = config
|
|
889
|
-
? resolveProfile(config, doctorProfileName)
|
|
890
|
-
: {
|
|
891
|
-
name: doctorProfileName,
|
|
892
|
-
...(DEFAULT_CONFIG.build?.production ?? {}),
|
|
893
|
-
};
|
|
904
|
+
const doctorProfile = resolveProfile(config, doctorProfileName);
|
|
894
905
|
const targetToolchain = doctorPlatform === "web"
|
|
895
906
|
? await inspectWebTarget(root, doctorProfile)
|
|
896
907
|
: doctorPlatform === "harmony"
|
|
@@ -904,7 +915,9 @@ async function main() {
|
|
|
904
915
|
? await inspectAndroidToolchain(root)
|
|
905
916
|
: undefined;
|
|
906
917
|
const iosToolchain = doctorPlatform === "ios"
|
|
907
|
-
? await inspectIosToolchain(root, doctorProfile
|
|
918
|
+
? await inspectIosToolchain(root, doctorProfile, doctorProfile.ios?.simulator || doctorProfileName === "simulator"
|
|
919
|
+
? "simulator"
|
|
920
|
+
: "device")
|
|
908
921
|
: undefined;
|
|
909
922
|
if (androidToolchain && args.includes("--fix")) {
|
|
910
923
|
await fixAndroidToolchain(root, androidToolchain, (message) => confirm(message), (line) => ui.info(line));
|
|
@@ -988,11 +1001,19 @@ async function main() {
|
|
|
988
1001
|
}))),
|
|
989
1002
|
{
|
|
990
1003
|
name: "cloudflare-r2",
|
|
991
|
-
ok:
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
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",
|
|
996
1017
|
},
|
|
997
1018
|
...(doctorPlatform === "android"
|
|
998
1019
|
? [
|
|
@@ -1619,7 +1640,9 @@ async function main() {
|
|
|
1619
1640
|
const platformArgument = flag("--platform", "android");
|
|
1620
1641
|
const buildAll = subcommand === "all" || platformArgument === "all";
|
|
1621
1642
|
const platform = buildAll ? "android" : platformValue(platformArgument);
|
|
1622
|
-
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.");
|
|
1623
1646
|
const allowUnsigned = args.includes("--allow-unsigned");
|
|
1624
1647
|
assert(!allowUnsigned || skipUpload, "CLI_UNSIGNED_UPLOAD_BLOCKED", "--allow-unsigned is only available with --no-upload and can never upload an unsigned Desktop artifact.");
|
|
1625
1648
|
await requireOperationalConfiguration(platform, { requireR2: !skipUpload });
|
|
@@ -1646,7 +1669,8 @@ async function main() {
|
|
|
1646
1669
|
}
|
|
1647
1670
|
assert(subcommand === "create" || buildAll, "CLI_BUILD_COMMAND", `Unknown build command: ${subcommand}`);
|
|
1648
1671
|
const config = await loadConfig(root);
|
|
1649
|
-
const profile = flag("--profile", "production");
|
|
1672
|
+
const profile = flag("--profile", simulator ? "simulator" : "production");
|
|
1673
|
+
const simulatorDevice = flag("--device") ?? undefined;
|
|
1650
1674
|
const wait = !args.includes("--no-wait");
|
|
1651
1675
|
const local = args.includes("--local");
|
|
1652
1676
|
const platforms = buildAll
|
|
@@ -1666,6 +1690,8 @@ async function main() {
|
|
|
1666
1690
|
allowUnsigned,
|
|
1667
1691
|
wait,
|
|
1668
1692
|
local,
|
|
1693
|
+
simulator,
|
|
1694
|
+
simulatorDevice,
|
|
1669
1695
|
state,
|
|
1670
1696
|
repository,
|
|
1671
1697
|
builds,
|
|
@@ -1695,6 +1721,8 @@ async function main() {
|
|
|
1695
1721
|
allowUnsigned,
|
|
1696
1722
|
wait,
|
|
1697
1723
|
local,
|
|
1724
|
+
simulator,
|
|
1725
|
+
simulatorDevice,
|
|
1698
1726
|
state,
|
|
1699
1727
|
repository,
|
|
1700
1728
|
builds,
|
|
@@ -1781,7 +1809,7 @@ void main()
|
|
|
1781
1809
|
.catch((error) => {
|
|
1782
1810
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1783
1811
|
const code = error.code ?? "CLI_ERROR";
|
|
1784
|
-
const nextSteps = guidanceForError(error);
|
|
1812
|
+
const nextSteps = guidanceForError(error, { args: rawArgs });
|
|
1785
1813
|
if (json) {
|
|
1786
1814
|
console.log(JSON.stringify({
|
|
1787
1815
|
error: message,
|
|
@@ -1790,6 +1818,9 @@ void main()
|
|
|
1790
1818
|
? {
|
|
1791
1819
|
nextSteps: nextSteps.commands,
|
|
1792
1820
|
...(nextSteps.note ? { note: nextSteps.note } : {}),
|
|
1821
|
+
...(nextSteps.environment
|
|
1822
|
+
? { environment: nextSteps.environment }
|
|
1823
|
+
: {}),
|
|
1793
1824
|
}
|
|
1794
1825
|
: {}),
|
|
1795
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;AA+DD,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;
|
|
@@ -51,8 +221,11 @@ async function installCocoaPods(root, quiet, onEvent) {
|
|
|
51
221
|
if (!existsSync(podfile))
|
|
52
222
|
return;
|
|
53
223
|
assert(commandExists("pod"), "IOS_COCOAPODS_REQUIRED", "CocoaPods was not found. Install CocoaPods on macOS, then rerun the build.");
|
|
54
|
-
|
|
55
|
-
|
|
224
|
+
const hasLockfile = existsSync(join(iosDirectory, "Podfile.lock"));
|
|
225
|
+
onEvent?.(hasLockfile
|
|
226
|
+
? "Installing iOS CocoaPods dependencies…"
|
|
227
|
+
: "Updating CocoaPods specs and installing iOS dependencies…");
|
|
228
|
+
await runProcess("pod", hasLockfile ? ["install"] : ["install", "--repo-update"], {
|
|
56
229
|
cwd: iosDirectory,
|
|
57
230
|
quiet,
|
|
58
231
|
onOutput: onEvent,
|
|
@@ -66,6 +239,8 @@ async function findIpa(directory) {
|
|
|
66
239
|
return join(directory, ipa.name);
|
|
67
240
|
}
|
|
68
241
|
export async function runRealIosBuild(job, options) {
|
|
242
|
+
if (options.simulator)
|
|
243
|
+
return runRealIosSimulatorBuild(job, options);
|
|
69
244
|
assert(process.platform === "darwin", "IOS_MACOS_REQUIRED", "iOS builds require macOS with Xcode");
|
|
70
245
|
assert(commandExists("xcodebuild"), "IOS_XCODE_REQUIRED", "xcodebuild was not found. Install Xcode and select its command-line tools.");
|
|
71
246
|
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,7 +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
38
|
export declare function devServerQr(url: string, enabled?: boolean): void;
|
|
39
39
|
//# sourceMappingURL=components.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../src/ui/components.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;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
|
});
|
package/dist/ui/index.js
CHANGED
|
@@ -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)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lynxship/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "Build, sign, store, submit and update LynxJS applications from the terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -68,11 +68,11 @@
|
|
|
68
68
|
"cli-progress": "3.12.0",
|
|
69
69
|
"ora": "8.2.0",
|
|
70
70
|
"qrcode-terminal": "0.12.0",
|
|
71
|
-
"@lynxship/signing": "0.1.0",
|
|
72
|
-
"@lynxship/db": "0.1.0",
|
|
73
71
|
"@lynxship/build-orchestrator": "0.1.0",
|
|
74
72
|
"@lynxship/contracts": "0.1.0",
|
|
75
|
-
"@lynxship/
|
|
73
|
+
"@lynxship/db": "0.1.0",
|
|
74
|
+
"@lynxship/submit": "0.1.0",
|
|
75
|
+
"@lynxship/signing": "0.1.0"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@types/cli-progress": "3.11.6",
|