@lynxship/cli 0.1.6 → 0.1.7
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 +42 -0
- package/dist/android-build.d.ts +1 -0
- package/dist/android-build.d.ts.map +1 -1
- package/dist/android-build.js +12 -19
- package/dist/bundle-build.d.ts +7 -0
- package/dist/bundle-build.d.ts.map +1 -0
- package/dist/bundle-build.js +24 -0
- package/dist/guidance.d.ts +6 -0
- package/dist/guidance.d.ts.map +1 -0
- package/dist/guidance.js +354 -0
- package/dist/index.js +253 -90
- package/dist/ios-build.d.ts +1 -0
- package/dist/ios-build.d.ts.map +1 -1
- package/dist/ios-build.js +12 -8
- package/dist/ui/components.d.ts +2 -0
- package/dist/ui/components.d.ts.map +1 -1
- package/dist/ui/components.js +30 -16
- package/dist/ui/index.d.ts +3 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +6 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -120,6 +120,7 @@ doctor Check the local toolchain and project
|
|
|
120
120
|
dev Run Rspeedy dev with Lynx Explorer QR/HMR
|
|
121
121
|
preview Preview the production bundle locally
|
|
122
122
|
build create Build, sign and upload an artifact
|
|
123
|
+
build all Build Android and iOS on a macOS host
|
|
123
124
|
build list List build jobs
|
|
124
125
|
build status <id> Inspect one build job
|
|
125
126
|
build cancel <id> Cancel a build job
|
|
@@ -143,6 +144,47 @@ Use `lynxship --help` or `lynxship <command> --help` for the complete option
|
|
|
143
144
|
list. `--json`, `--quiet`, `--no-color` and `--non-interactive` are available
|
|
144
145
|
for automation.
|
|
145
146
|
|
|
147
|
+
### Actionable error guidance
|
|
148
|
+
|
|
149
|
+
Recoverable errors print a `Next steps` section with the exact commands to
|
|
150
|
+
run. For example, a pure LynxJS project without a native Android host reports
|
|
151
|
+
the two supported paths instead of stopping at a generic Gradle error:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
x This project has no Android Gradle host...
|
|
155
|
+
|
|
156
|
+
Next steps
|
|
157
|
+
1. lynxship dev
|
|
158
|
+
2. lynxship android host init --application-id com.example.myapp
|
|
159
|
+
3. lynxship doctor --platform android
|
|
160
|
+
4. lynxship build --platform android --profile production
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
`lynxship dev` is for Lynx Explorer and live QR/HMR development. The Android
|
|
164
|
+
host command creates the native Gradle project needed for a real APK/AAB.
|
|
165
|
+
`--local` remains a contract-test mode and never creates a fake artifact.
|
|
166
|
+
|
|
167
|
+
To build both native platforms in one local workflow, use the multi-platform
|
|
168
|
+
selector:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
lynxship build --platform all --profile production
|
|
172
|
+
# equivalent convenience form:
|
|
173
|
+
lynxship build all --profile production
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
This builds Android and iOS concurrently after one shared Lynx bundle step.
|
|
177
|
+
Their jobs, progress events and artifacts remain isolated. A real local `all`
|
|
178
|
+
build requires macOS, an Android host, an iOS Xcode host and the corresponding
|
|
179
|
+
signing setup. Windows and Linux can still run
|
|
180
|
+
`lynxship build --platform android`; use a macOS CI worker for the iOS half.
|
|
181
|
+
`--local` can exercise both contract paths without creating APK or IPA files.
|
|
182
|
+
|
|
183
|
+
The same guidance covers missing R2 or signing setup, Android SDK tools,
|
|
184
|
+
Autolink/codegen, iOS Xcode/CocoaPods, device tools, store credentials and
|
|
185
|
+
OTA compatibility. In automation, use `--json`; failures include a
|
|
186
|
+
machine-readable `nextSteps` array and optional `note` field.
|
|
187
|
+
|
|
146
188
|
## OTA rollback
|
|
147
189
|
|
|
148
190
|
```bash
|
package/dist/android-build.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"android-build.d.ts","sourceRoot":"","sources":["../src/android-build.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAWhD,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,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;
|
|
1
|
+
{"version":3,"file":"android-build.d.ts","sourceRoot":"","sources":["../src/android-build.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAWhD,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,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;AA8KD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAa7D;AAED,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAsCpE;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,QAAQ,CAAC,CA+JnB"}
|
package/dist/android-build.js
CHANGED
|
@@ -8,18 +8,8 @@ import { transitionBuild } from "@lynxship/build-orchestrator";
|
|
|
8
8
|
import { loadR2, uploadR2Artifact } from "./r2.js";
|
|
9
9
|
import { loadCredentials } from "./secure-store.js";
|
|
10
10
|
import { nativeArtifactName } from "./artifact-name.js";
|
|
11
|
-
import { commandExists, executableExists,
|
|
12
|
-
|
|
13
|
-
try {
|
|
14
|
-
const packageJson = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
|
|
15
|
-
if (packageJson.scripts?.["build:mobile"])
|
|
16
|
-
return ["run", "build:mobile"];
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
// The package manager will report the useful project error below.
|
|
20
|
-
}
|
|
21
|
-
return ["run", "build"];
|
|
22
|
-
}
|
|
11
|
+
import { commandExists, executableExists, runProcess, } from "./process-runner.js";
|
|
12
|
+
import { buildLynxBundle } from "./bundle-build.js";
|
|
23
13
|
async function signingEnvironment(root) {
|
|
24
14
|
const android = (await loadCredentials(root)).android;
|
|
25
15
|
const values = {
|
|
@@ -220,13 +210,16 @@ export async function runRealAndroidBuild(job, options) {
|
|
|
220
210
|
message: "rspeedy:build",
|
|
221
211
|
at: new Date().toISOString(),
|
|
222
212
|
});
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
213
|
+
if (options.skipBundleBuild) {
|
|
214
|
+
step("Using shared Lynx bundle", 20);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
step("Building Lynx bundle with Rspeedy…");
|
|
218
|
+
await buildLynxBundle(options.root, {
|
|
219
|
+
env: environment,
|
|
220
|
+
...processOptions,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
230
223
|
step("Rspeedy bundle ready", 20);
|
|
231
224
|
transitionBuild(job, "queued", "Android build queued locally");
|
|
232
225
|
step("Build queued…");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface BundleBuildOptions {
|
|
2
|
+
env?: NodeJS.ProcessEnv;
|
|
3
|
+
quiet?: boolean;
|
|
4
|
+
onOutput?: (line: string) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function buildLynxBundle(root: string, options?: BundleBuildOptions): Promise<void>;
|
|
7
|
+
//# sourceMappingURL=bundle-build.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle-build.d.ts","sourceRoot":"","sources":["../src/bundle-build.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAcD,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CASf"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { packageManagerScriptCommand, runProcess } from "./process-runner.js";
|
|
4
|
+
async function resolveBundleScript(root) {
|
|
5
|
+
try {
|
|
6
|
+
const manifest = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
|
|
7
|
+
if (manifest.scripts?.["build:mobile"])
|
|
8
|
+
return "build:mobile";
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// The package manager will report the useful project error below.
|
|
12
|
+
}
|
|
13
|
+
return "build";
|
|
14
|
+
}
|
|
15
|
+
export async function buildLynxBundle(root, options = {}) {
|
|
16
|
+
const script = await resolveBundleScript(root);
|
|
17
|
+
const packageManager = packageManagerScriptCommand(root, script);
|
|
18
|
+
await runProcess(packageManager.command, packageManager.args, {
|
|
19
|
+
cwd: root,
|
|
20
|
+
env: options.env,
|
|
21
|
+
quiet: options.quiet,
|
|
22
|
+
onOutput: options.onOutput,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +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;CACf;AAoQD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,CAiG5D"}
|
package/dist/guidance.js
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
const guidance = {
|
|
2
|
+
CLI_PROJECT_REQUIRED: {
|
|
3
|
+
commands: ["lynxship init", "lynxship doctor"],
|
|
4
|
+
note: "Run them from the project directory or add --project-dir <path>.",
|
|
5
|
+
},
|
|
6
|
+
CLI_PROJECT_ID_REQUIRED: {
|
|
7
|
+
commands: ["lynxship init", "lynxship doctor"],
|
|
8
|
+
},
|
|
9
|
+
CLI_R2_REQUIRED: {
|
|
10
|
+
commands: ["lynxship storage configure", "lynxship doctor"],
|
|
11
|
+
},
|
|
12
|
+
CLI_R2_ACCOUNT_ID: {
|
|
13
|
+
commands: ["lynxship storage configure"],
|
|
14
|
+
note: "Use the 32-character Cloudflare account ID.",
|
|
15
|
+
},
|
|
16
|
+
CLI_R2_BUCKET: {
|
|
17
|
+
commands: ["lynxship storage configure"],
|
|
18
|
+
note: "Use a lowercase R2 bucket name with no spaces or underscores.",
|
|
19
|
+
},
|
|
20
|
+
CLI_R2_ENDPOINT: {
|
|
21
|
+
commands: ["lynxship storage configure"],
|
|
22
|
+
note: "Use the HTTPS S3 endpoint for the same Cloudflare account.",
|
|
23
|
+
},
|
|
24
|
+
CLI_R2_EXPIRY: {
|
|
25
|
+
commands: ["lynxship storage configure"],
|
|
26
|
+
note: "Choose a download lifetime between 1 second and 7 days.",
|
|
27
|
+
},
|
|
28
|
+
CLI_R2_CREDENTIALS: {
|
|
29
|
+
commands: ["lynxship storage configure", "lynxship doctor"],
|
|
30
|
+
},
|
|
31
|
+
BUILD_SIGNING_REQUIRED: {
|
|
32
|
+
commands: [
|
|
33
|
+
"lynxship android configure",
|
|
34
|
+
"lynxship doctor --platform android",
|
|
35
|
+
],
|
|
36
|
+
note: "An existing .jks/.keystore is supported; production builds need a real release key.",
|
|
37
|
+
},
|
|
38
|
+
LYNXSHIP_KEYSTORE_PATH: {
|
|
39
|
+
commands: ["lynxship android configure"],
|
|
40
|
+
},
|
|
41
|
+
LYNXSHIP_KEY_ALIAS: {
|
|
42
|
+
commands: ["lynxship android configure"],
|
|
43
|
+
},
|
|
44
|
+
LYNXSHIP_KEYSTORE_PASSWORD: {
|
|
45
|
+
commands: ["lynxship android configure"],
|
|
46
|
+
},
|
|
47
|
+
LYNXSHIP_KEY_PASSWORD: {
|
|
48
|
+
commands: ["lynxship android configure"],
|
|
49
|
+
},
|
|
50
|
+
ANDROID_HOST_REQUIRED: {
|
|
51
|
+
commands: [
|
|
52
|
+
"lynxship dev",
|
|
53
|
+
"lynxship android host init --application-id com.example.myapp",
|
|
54
|
+
"lynxship doctor --platform android",
|
|
55
|
+
"lynxship build --platform android --profile production",
|
|
56
|
+
],
|
|
57
|
+
note: "Use dev for Lynx Explorer. The host init path is required for a real APK/AAB; --local is only a contract test.",
|
|
58
|
+
},
|
|
59
|
+
ANDROID_HOST_EXISTS: {
|
|
60
|
+
commands: ["lynxship doctor --platform android"],
|
|
61
|
+
note: "Review the existing android/ host instead of overwriting it.",
|
|
62
|
+
},
|
|
63
|
+
ANDROID_APPLICATION_ID_INVALID: {
|
|
64
|
+
commands: ["lynxship android host init --application-id com.example.myapp"],
|
|
65
|
+
note: "Use a reverse-domain Android application ID, for example com.company.app.",
|
|
66
|
+
},
|
|
67
|
+
ANDROID_PLATFORM_UNSUPPORTED: {
|
|
68
|
+
commands: ["lynxship doctor --platform android"],
|
|
69
|
+
note: "Android builds run on Windows, macOS or Linux. Use macOS for iOS builds.",
|
|
70
|
+
},
|
|
71
|
+
BUILD_ALL_MACOS_REQUIRED: {
|
|
72
|
+
commands: [
|
|
73
|
+
"lynxship build --platform android --profile production",
|
|
74
|
+
"lynxship build --platform all --profile production --no-upload",
|
|
75
|
+
],
|
|
76
|
+
note: "Run the all-platform command on a macOS machine or macOS CI worker; the second command is for CI verification.",
|
|
77
|
+
},
|
|
78
|
+
ANDROID_APKSIGNER_REQUIRED: {
|
|
79
|
+
commands: [
|
|
80
|
+
"lynxship doctor --platform android",
|
|
81
|
+
'sdkmanager "build-tools;latest"',
|
|
82
|
+
],
|
|
83
|
+
note: "Ensure Android SDK Build Tools are installed and available through ANDROID_HOME or ANDROID_SDK_ROOT.",
|
|
84
|
+
},
|
|
85
|
+
ANDROID_JARSIGNER_REQUIRED: {
|
|
86
|
+
commands: ["lynxship doctor --platform android", "java -version"],
|
|
87
|
+
note: "Install JDK 17 or newer and make sure jarsigner is on PATH.",
|
|
88
|
+
},
|
|
89
|
+
ANDROID_ADB_REQUIRED: {
|
|
90
|
+
commands: ["lynxship doctor --platform android", "adb devices"],
|
|
91
|
+
note: "Install Android SDK Platform-Tools and connect or start a device.",
|
|
92
|
+
},
|
|
93
|
+
LYNX_BUNDLE_MISSING: {
|
|
94
|
+
commands: ["lynxship dev", "lynxship build --platform android"],
|
|
95
|
+
note: "Build the Lynx bundle with the project's configured package manager.",
|
|
96
|
+
},
|
|
97
|
+
IOS_HOST_REQUIRED: {
|
|
98
|
+
commands: [
|
|
99
|
+
"lynxship dev",
|
|
100
|
+
"lynxship ios host init --bundle-identifier com.example.myapp",
|
|
101
|
+
"lynxship doctor --platform ios",
|
|
102
|
+
"lynxship build --platform ios --profile production",
|
|
103
|
+
],
|
|
104
|
+
note: "Use dev for Lynx Explorer. A real IPA requires macOS, Xcode and an iOS host.",
|
|
105
|
+
},
|
|
106
|
+
IOS_HOST_EXISTS: {
|
|
107
|
+
commands: ["lynxship doctor --platform ios"],
|
|
108
|
+
note: "Review the existing ios/ host instead of overwriting it.",
|
|
109
|
+
},
|
|
110
|
+
IOS_BUNDLE_IDENTIFIER_INVALID: {
|
|
111
|
+
commands: ["lynxship ios host init --bundle-identifier com.example.myapp"],
|
|
112
|
+
note: "Use a reverse-domain bundle identifier, for example com.company.app.",
|
|
113
|
+
},
|
|
114
|
+
IOS_MACOS_REQUIRED: {
|
|
115
|
+
commands: ["lynxship doctor --platform ios"],
|
|
116
|
+
note: "Run real iOS commands on macOS. Windows and Linux cannot produce an IPA.",
|
|
117
|
+
},
|
|
118
|
+
IOS_XCODE_REQUIRED: {
|
|
119
|
+
commands: ["xcode-select --install", "lynxship doctor --platform ios"],
|
|
120
|
+
note: "Install Xcode from the Mac App Store and select its command-line tools.",
|
|
121
|
+
},
|
|
122
|
+
IOS_XCRUN_REQUIRED: {
|
|
123
|
+
commands: ["xcode-select --install", "lynxship doctor --platform ios"],
|
|
124
|
+
},
|
|
125
|
+
IOS_COCOAPODS_REQUIRED: {
|
|
126
|
+
commands: ["brew install cocoapods", "lynxship build --platform ios"],
|
|
127
|
+
note: "CocoaPods is required when the iOS host contains a Podfile.",
|
|
128
|
+
},
|
|
129
|
+
IOS_PROJECT_REQUIRED: {
|
|
130
|
+
commands: [
|
|
131
|
+
"lynxship ios host init --bundle-identifier com.example.myapp",
|
|
132
|
+
"lynxship doctor --platform ios",
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
IOS_SCHEME_REQUIRED: {
|
|
136
|
+
commands: ["lynxship inspect", "lynxship doctor --platform ios"],
|
|
137
|
+
note: "Set build.<profile>.ios.scheme in lynxship.json.",
|
|
138
|
+
},
|
|
139
|
+
IOS_EXPORT_OPTIONS_REQUIRED: {
|
|
140
|
+
commands: [
|
|
141
|
+
"lynxship ios host init --bundle-identifier com.example.myapp",
|
|
142
|
+
"lynxship doctor --platform ios",
|
|
143
|
+
],
|
|
144
|
+
note: "Set build.<profile>.ios.exportOptionsPlist to a valid export options file.",
|
|
145
|
+
},
|
|
146
|
+
IOS_DEVICE_REQUIRED: {
|
|
147
|
+
commands: [
|
|
148
|
+
"xcrun devicectl list devices",
|
|
149
|
+
"lynxship run --platform ios --device <device-id>",
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
IOS_DEVICE_LOGS_UNSUPPORTED: {
|
|
153
|
+
commands: [
|
|
154
|
+
"xcrun simctl list devices",
|
|
155
|
+
"lynxship logs --platform ios --device <simulator-id>",
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
DEVICE_ARTIFACT_REQUIRED: {
|
|
159
|
+
commands: [
|
|
160
|
+
"lynxship build --platform android --profile production",
|
|
161
|
+
"lynxship run --platform android --artifact <path-to-apk>",
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
LYNX_CODEGEN_SCRIPT_REQUIRED: {
|
|
165
|
+
commands: [
|
|
166
|
+
"lynxship autolink check --platform android",
|
|
167
|
+
"lynxship autolink codegen --library-dir <native-library>",
|
|
168
|
+
],
|
|
169
|
+
note: "The native library must expose its official codegen script.",
|
|
170
|
+
},
|
|
171
|
+
LYNX_AUTOLINK_ANDROID_REQUIRED: {
|
|
172
|
+
commands: [
|
|
173
|
+
"lynxship autolink check --platform android",
|
|
174
|
+
"lynxship autolink codegen --library-dir <native-library>",
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
LYNX_AUTOLINK_IOS_REQUIRED: {
|
|
178
|
+
commands: [
|
|
179
|
+
"lynxship autolink check --platform ios",
|
|
180
|
+
"lynxship autolink codegen --library-dir <native-library>",
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
OTA_HOST_INTEGRATION_REQUIRED: {
|
|
184
|
+
commands: [
|
|
185
|
+
"lynxship ota doctor --platform android",
|
|
186
|
+
"lynxship ota doctor --platform ios",
|
|
187
|
+
],
|
|
188
|
+
note: "Integrate the LynxShip OTA client in the native host before publishing updates.",
|
|
189
|
+
},
|
|
190
|
+
OTA_NATIVE_CHANGE_REQUIRED: {
|
|
191
|
+
commands: [
|
|
192
|
+
"lynxship ota doctor --platform android",
|
|
193
|
+
"lynxship build --platform android --profile production",
|
|
194
|
+
"lynxship update --platform android --bundle dist/main.lynx.bundle",
|
|
195
|
+
],
|
|
196
|
+
note: "Native changes require a new compatible binary before an OTA release.",
|
|
197
|
+
},
|
|
198
|
+
OTA_BUNDLE_REQUIRED: {
|
|
199
|
+
commands: [
|
|
200
|
+
"lynxship build --platform android --profile production",
|
|
201
|
+
"lynxship update --platform android --bundle dist/main.lynx.bundle",
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
BUILD_REQUIRED: {
|
|
205
|
+
commands: ["lynxship build --platform android --profile production"],
|
|
206
|
+
},
|
|
207
|
+
STORE_ARTIFACT_REQUIRED: {
|
|
208
|
+
commands: [
|
|
209
|
+
"lynxship build --platform android --profile production",
|
|
210
|
+
"lynxship submit --platform android --latest",
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
STORE_SUBMISSION_REQUIRED: {
|
|
214
|
+
commands: [
|
|
215
|
+
"lynxship store configure --platform android",
|
|
216
|
+
"lynxship submit --platform android --latest",
|
|
217
|
+
],
|
|
218
|
+
},
|
|
219
|
+
RELEASE_NOT_FOUND: {
|
|
220
|
+
commands: [
|
|
221
|
+
"lynxship update --platform android",
|
|
222
|
+
"lynxship update rollback --help",
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
ROLLBACK_RELEASE_REQUIRED: {
|
|
226
|
+
commands: [
|
|
227
|
+
'lynxship update rollback --platform android --release-id <release-id> --reason "reason"',
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
ROLLBACK_REASON_REQUIRED: {
|
|
231
|
+
commands: [
|
|
232
|
+
'lynxship update rollback --platform android --release-id <release-id> --reason "reason"',
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
CLI_INTERACTIVE_REQUIRED: {
|
|
236
|
+
commands: ["lynxship <command>"],
|
|
237
|
+
note: "Run without --non-interactive, or provide the documented CI environment variables.",
|
|
238
|
+
},
|
|
239
|
+
PROFILE_NOT_FOUND: {
|
|
240
|
+
commands: ["lynxship inspect", "lynxship build --profile production"],
|
|
241
|
+
note: "Use a profile declared under build in lynxship.json.",
|
|
242
|
+
},
|
|
243
|
+
PLATFORM_INVALID: {
|
|
244
|
+
commands: [
|
|
245
|
+
"lynxship build --platform android",
|
|
246
|
+
"lynxship build --platform ios",
|
|
247
|
+
"lynxship build --platform all",
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
IOS_SIGNATURE_INVALID: {
|
|
251
|
+
commands: [
|
|
252
|
+
"lynxship doctor --platform ios",
|
|
253
|
+
"lynxship build --platform ios --profile production",
|
|
254
|
+
],
|
|
255
|
+
note: "Check the Apple certificate, provisioning profile, team and export options.",
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
export function guidanceForError(error) {
|
|
259
|
+
const code = error.code;
|
|
260
|
+
if (typeof code === "string" && guidance[code])
|
|
261
|
+
return guidance[code];
|
|
262
|
+
if (typeof code === "string") {
|
|
263
|
+
if (code.startsWith("SUBMISSION_") || code.startsWith("AUTH_")) {
|
|
264
|
+
return {
|
|
265
|
+
commands: [
|
|
266
|
+
"lynxship store configure --platform android",
|
|
267
|
+
"lynxship submit --platform android --latest",
|
|
268
|
+
],
|
|
269
|
+
note: "Check the provider credentials and required account permissions.",
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
if (code.startsWith("OTA_")) {
|
|
273
|
+
return {
|
|
274
|
+
commands: [
|
|
275
|
+
"lynxship ota doctor --platform android",
|
|
276
|
+
"lynxship update --platform android --bundle dist/main.lynx.bundle",
|
|
277
|
+
],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (code.startsWith("CONFIG_")) {
|
|
281
|
+
return {
|
|
282
|
+
commands: ["lynxship init", "lynxship doctor"],
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
if (code.startsWith("BUILD_")) {
|
|
286
|
+
return {
|
|
287
|
+
commands: [
|
|
288
|
+
"lynxship doctor --platform android",
|
|
289
|
+
"lynxship build --platform android --profile production",
|
|
290
|
+
],
|
|
291
|
+
note: "Inspect the first failed build event before retrying.",
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
if (code.startsWith("STORE_")) {
|
|
295
|
+
return {
|
|
296
|
+
commands: [
|
|
297
|
+
"lynxship store configure --platform android",
|
|
298
|
+
"lynxship submit --platform android --latest",
|
|
299
|
+
],
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
if (code.startsWith("IOS_")) {
|
|
303
|
+
return {
|
|
304
|
+
commands: [
|
|
305
|
+
"lynxship doctor --platform ios",
|
|
306
|
+
"lynxship build --platform ios --profile production",
|
|
307
|
+
],
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (code.startsWith("ANDROID_")) {
|
|
311
|
+
return {
|
|
312
|
+
commands: [
|
|
313
|
+
"lynxship doctor --platform android",
|
|
314
|
+
"lynxship build --platform android --profile production",
|
|
315
|
+
],
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (code.startsWith("CLI_")) {
|
|
319
|
+
return { commands: ["lynxship --help"] };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
323
|
+
if (/pnpm|npm|yarn|corepack|not recognized|not found/i.test(message)) {
|
|
324
|
+
return {
|
|
325
|
+
commands: ["corepack enable", "pnpm install", "lynxship doctor"],
|
|
326
|
+
note: "Use the package manager selected by the project's lockfile.",
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
if (/ENOENT|no such file or directory/i.test(message)) {
|
|
330
|
+
return {
|
|
331
|
+
commands: ["lynxship init", "lynxship doctor"],
|
|
332
|
+
note: "Check that the project directory and required configuration files exist.",
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
if (/gradle|gradlew|android sdk|build tools/i.test(message)) {
|
|
336
|
+
return {
|
|
337
|
+
commands: [
|
|
338
|
+
"lynxship doctor --platform android",
|
|
339
|
+
"lynxship build --platform android --profile production",
|
|
340
|
+
],
|
|
341
|
+
note: "Check JDK 17, Android SDK, Build Tools and the project Gradle wrapper.",
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
if (/xcode|xcrun|cocoapods|pod install/i.test(message)) {
|
|
345
|
+
return {
|
|
346
|
+
commands: [
|
|
347
|
+
"lynxship doctor --platform ios",
|
|
348
|
+
"lynxship build --platform ios",
|
|
349
|
+
],
|
|
350
|
+
note: "Check macOS, Xcode command-line tools and CocoaPods.",
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
return { commands: [] };
|
|
354
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,8 @@ import { inspectOtaHost } from "./ota-doctor.js";
|
|
|
25
25
|
import { otaAssetName, otaAssetPaths } from "./ota-assets.js";
|
|
26
26
|
import { prompt } from "./prompt.js";
|
|
27
27
|
import { commandExists, packageManagerCommand, runProcess, runRspeedy, } from "./process-runner.js";
|
|
28
|
+
import { guidanceForError } from "./guidance.js";
|
|
29
|
+
import { buildLynxBundle } from "./bundle-build.js";
|
|
28
30
|
const rawArgs = process.argv.slice(2);
|
|
29
31
|
const args = [...rawArgs];
|
|
30
32
|
const ui = createCliUi(rawArgs);
|
|
@@ -178,9 +180,134 @@ async function loadState() {
|
|
|
178
180
|
return { state, repository, builds, submissions };
|
|
179
181
|
}
|
|
180
182
|
async function saveState(state, repository, builds, submissions) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
const next = stateSaveQueue.then(async () => {
|
|
184
|
+
state.builds = builds.list();
|
|
185
|
+
state.submissions = submissions.list();
|
|
186
|
+
await repository.write(state);
|
|
187
|
+
});
|
|
188
|
+
stateSaveQueue = next.catch(() => undefined);
|
|
189
|
+
await next;
|
|
190
|
+
}
|
|
191
|
+
let stateSaveQueue = Promise.resolve();
|
|
192
|
+
async function executeBuild(options, showResult = true) {
|
|
193
|
+
const { config, profile, platform, skipUpload, wait, local, state, repository, builds, submissions, progress: sharedProgress, progressPrefix, onProgress, onEvent, skipBundleBuild, } = options;
|
|
194
|
+
await requireAutolinkReady(root, platform);
|
|
195
|
+
const runtime = await inspectRuntimeFingerprint(root, platform, config);
|
|
196
|
+
const job = await builds.create({
|
|
197
|
+
projectId: configuredProjectId(config),
|
|
198
|
+
organizationId: "local_org",
|
|
199
|
+
platform,
|
|
200
|
+
profile,
|
|
201
|
+
sourceHash: createHash("sha256").update(root).digest("hex"),
|
|
202
|
+
runtimeVersion: runtime.value,
|
|
203
|
+
runtimeInputs: runtime.inputs,
|
|
204
|
+
});
|
|
205
|
+
ui.info(`Using profile: ${profile} · platform: ${platform}`);
|
|
206
|
+
const ownsProgress = !sharedProgress;
|
|
207
|
+
const progress = sharedProgress ??
|
|
208
|
+
ui.progress(platform === "android" ? "Android build" : "iOS build");
|
|
209
|
+
const prefix = progressPrefix ? `${progressPrefix} · ` : "";
|
|
210
|
+
const reportEvent = (message) => {
|
|
211
|
+
if (onEvent)
|
|
212
|
+
onEvent(message);
|
|
213
|
+
else
|
|
214
|
+
progress.event(`${prefix}${message}`);
|
|
215
|
+
};
|
|
216
|
+
const reportProgress = (value, label) => {
|
|
217
|
+
if (onProgress)
|
|
218
|
+
onProgress(value, label);
|
|
219
|
+
else
|
|
220
|
+
progress.update(value, label ? `${prefix}${label}` : undefined);
|
|
221
|
+
};
|
|
222
|
+
try {
|
|
223
|
+
reportProgress(undefined, "Preparing build pipeline…");
|
|
224
|
+
if (wait) {
|
|
225
|
+
if (platform === "android" && !isSupportedAndroidPlatform() && !local)
|
|
226
|
+
assert(false, "ANDROID_PLATFORM_UNSUPPORTED", "Android builds are supported only on Linux, macOS and Windows.");
|
|
227
|
+
const realAndroid = platform === "android" &&
|
|
228
|
+
isSupportedAndroidPlatform() &&
|
|
229
|
+
(await hasAndroidHost(root));
|
|
230
|
+
const realIos = platform === "ios" && hasIosHost(root, config.build?.[profile]);
|
|
231
|
+
if (platform === "ios" && !realIos && !local)
|
|
232
|
+
assert(false, "IOS_HOST_REQUIRED", "A macOS Xcode host is required for a real iOS build. No local fake iOS build is created.");
|
|
233
|
+
if (platform === "android" && !realAndroid && !local)
|
|
234
|
+
assert(false, "ANDROID_HOST_REQUIRED", "This project has no Android Gradle host. For live development, run `lynxship dev` and scan the QR code with Lynx Explorer; for an APK/AAB, integrate the official Lynx Android host under `android/` (including android/gradlew). `--local` is only for contract tests and does not create an APK.");
|
|
235
|
+
if (realAndroid) {
|
|
236
|
+
await runRealAndroidBuild(job, {
|
|
237
|
+
root,
|
|
238
|
+
profile: config.build?.[profile] ?? {},
|
|
239
|
+
uploadArtifacts: !skipUpload,
|
|
240
|
+
skipBundleBuild,
|
|
241
|
+
quiet: json,
|
|
242
|
+
onEvent: reportEvent,
|
|
243
|
+
onProgress: reportProgress,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
else if (realIos) {
|
|
247
|
+
await runRealIosBuild(job, {
|
|
248
|
+
root,
|
|
249
|
+
profile: config.build?.[profile] ?? {},
|
|
250
|
+
uploadArtifacts: !skipUpload,
|
|
251
|
+
skipBundleBuild,
|
|
252
|
+
quiet: json,
|
|
253
|
+
onEvent: reportEvent,
|
|
254
|
+
onProgress: reportProgress,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
await builds.run(job.id);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
reportProgress(100);
|
|
262
|
+
await saveState(state, repository, builds, submissions);
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
await saveState(state, repository, builds, submissions);
|
|
266
|
+
if (error instanceof Error) {
|
|
267
|
+
Object.assign(error, { buildId: job.id });
|
|
268
|
+
}
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
if (ownsProgress)
|
|
273
|
+
progress.stop();
|
|
274
|
+
}
|
|
275
|
+
const result = builds.get(job.id);
|
|
276
|
+
if (showResult) {
|
|
277
|
+
printValue(result, {
|
|
278
|
+
title: `${platform === "android" ? "Android" : "iOS"} build result`,
|
|
279
|
+
rows: [
|
|
280
|
+
{ label: "Build ID", value: result.id, valueColor: "purple" },
|
|
281
|
+
{ label: "Platform", value: result.platform, valueColor: "blue" },
|
|
282
|
+
{ label: "Profile", value: result.profile, valueColor: "text" },
|
|
283
|
+
{
|
|
284
|
+
label: "Status",
|
|
285
|
+
value: result.state,
|
|
286
|
+
valueColor: result.state === "success" ? "green" : "yellow",
|
|
287
|
+
},
|
|
288
|
+
],
|
|
289
|
+
done: result.state === "success"
|
|
290
|
+
? "Build complete. Run lynxship submit to publish."
|
|
291
|
+
: "Build queued.",
|
|
292
|
+
});
|
|
293
|
+
if (result.state === "success" && result.artifact?.url)
|
|
294
|
+
ui.downloadArtifact(result.artifact.url, result.artifact.expiresAt);
|
|
295
|
+
}
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
async function buildSharedLynxBundle() {
|
|
299
|
+
const progress = ui.progress("Shared Lynx bundle");
|
|
300
|
+
try {
|
|
301
|
+
progress.update(undefined, "Building Lynx bundle once for Android and iOS…");
|
|
302
|
+
await buildLynxBundle(root, {
|
|
303
|
+
quiet: json,
|
|
304
|
+
onOutput: (message) => progress.event(message),
|
|
305
|
+
});
|
|
306
|
+
progress.update(100, "Shared Lynx bundle ready");
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
progress.stop();
|
|
310
|
+
}
|
|
184
311
|
}
|
|
185
312
|
async function initSelfHost() {
|
|
186
313
|
const directory = join(root, ".lynxship");
|
|
@@ -239,6 +366,7 @@ Commands:
|
|
|
239
366
|
build status <id> Show one build job
|
|
240
367
|
build cancel <id> Cancel a build job
|
|
241
368
|
build retry <id> Retry a failed build job
|
|
369
|
+
build all Build Android and iOS on macOS
|
|
242
370
|
submit Submit the latest successful build
|
|
243
371
|
update Upload and publish a signed OTA update
|
|
244
372
|
update rollback Roll back an OTA channel to a previous release
|
|
@@ -251,7 +379,7 @@ Commands:
|
|
|
251
379
|
store configure Configure Google Play or App Store Connect submission
|
|
252
380
|
|
|
253
381
|
Build options:
|
|
254
|
-
--platform <p> Target android or
|
|
382
|
+
--platform <p> Target android, ios or all for build (default: android)
|
|
255
383
|
--profile <name> Build profile (default: production)
|
|
256
384
|
--no-wait Queue the build without executing it locally
|
|
257
385
|
--no-upload Keep the signed artifact local and skip R2 (CI verification)
|
|
@@ -578,7 +706,7 @@ async function main() {
|
|
|
578
706
|
name: "package-manager-lockfile",
|
|
579
707
|
ok: Boolean(lockfile),
|
|
580
708
|
status: lockfile ? "pass" : "fail",
|
|
581
|
-
value: lockfile ?? "missing · fix:
|
|
709
|
+
value: lockfile ?? "missing · fix: pnpm install (or npm install)",
|
|
582
710
|
},
|
|
583
711
|
{
|
|
584
712
|
name: "lynxship.json",
|
|
@@ -602,7 +730,7 @@ async function main() {
|
|
|
602
730
|
status: androidHost ? "pass" : "fail",
|
|
603
731
|
value: androidHost
|
|
604
732
|
? "Gradle host found"
|
|
605
|
-
: "missing ·
|
|
733
|
+
: "missing · fix: lynxship android host init --application-id com.example.myapp",
|
|
606
734
|
},
|
|
607
735
|
]
|
|
608
736
|
: []),
|
|
@@ -612,7 +740,7 @@ async function main() {
|
|
|
612
740
|
status: configuration.r2 ? "pass" : "fail",
|
|
613
741
|
value: configuration.r2
|
|
614
742
|
? "configured"
|
|
615
|
-
: "
|
|
743
|
+
: "missing · fix: lynxship storage configure",
|
|
616
744
|
},
|
|
617
745
|
{
|
|
618
746
|
name: doctorPlatform === "android" ? "android-signing" : "ios-host",
|
|
@@ -627,10 +755,10 @@ async function main() {
|
|
|
627
755
|
value: doctorPlatform === "android"
|
|
628
756
|
? configuration.android
|
|
629
757
|
? "configured"
|
|
630
|
-
: "
|
|
758
|
+
: "missing · fix: lynxship android configure"
|
|
631
759
|
: process.platform === "darwin" && hasIosHost(root)
|
|
632
760
|
? "Xcode host found"
|
|
633
|
-
: "missing · fix: use macOS
|
|
761
|
+
: "missing · fix: use macOS, then lynxship ios host init --bundle-identifier com.example.myapp",
|
|
634
762
|
},
|
|
635
763
|
{
|
|
636
764
|
name: `lynx-autolink-${doctorPlatform}`,
|
|
@@ -1213,7 +1341,9 @@ async function main() {
|
|
|
1213
1341
|
}
|
|
1214
1342
|
assert(command === "build", "CLI_COMMAND", `Unknown command: ${command}`);
|
|
1215
1343
|
const subcommand = args[0] && !args[0].startsWith("--") ? args.shift() : "create";
|
|
1216
|
-
const
|
|
1344
|
+
const platformArgument = flag("--platform", "android");
|
|
1345
|
+
const buildAll = subcommand === "all" || platformArgument === "all";
|
|
1346
|
+
const platform = buildAll ? "android" : platformValue(platformArgument);
|
|
1217
1347
|
const skipUpload = args.includes("--no-upload");
|
|
1218
1348
|
await requireOperationalConfiguration(platform, { requireR2: !skipUpload });
|
|
1219
1349
|
if (subcommand === "list") {
|
|
@@ -1237,90 +1367,114 @@ async function main() {
|
|
|
1237
1367
|
printValue(job);
|
|
1238
1368
|
return;
|
|
1239
1369
|
}
|
|
1240
|
-
assert(subcommand === "create", "CLI_BUILD_COMMAND", `Unknown build command: ${subcommand}`);
|
|
1370
|
+
assert(subcommand === "create" || buildAll, "CLI_BUILD_COMMAND", `Unknown build command: ${subcommand}`);
|
|
1241
1371
|
const config = await loadConfig(root);
|
|
1242
1372
|
const profile = flag("--profile", "production");
|
|
1243
|
-
|
|
1244
|
-
const
|
|
1245
|
-
const
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
platform
|
|
1249
|
-
profile,
|
|
1250
|
-
sourceHash: createHash("sha256").update(root).digest("hex"),
|
|
1251
|
-
runtimeVersion: runtime.value,
|
|
1252
|
-
runtimeInputs: runtime.inputs,
|
|
1253
|
-
});
|
|
1254
|
-
ui.info(`Using profile: ${profile} · platform: ${platform}`);
|
|
1255
|
-
const progress = ui.progress("Build execution");
|
|
1256
|
-
try {
|
|
1257
|
-
progress.update(undefined, "Preparing build pipeline…");
|
|
1258
|
-
if (!args.includes("--no-wait")) {
|
|
1259
|
-
if (platform === "android" &&
|
|
1260
|
-
!isSupportedAndroidPlatform() &&
|
|
1261
|
-
!args.includes("--local"))
|
|
1262
|
-
assert(false, "ANDROID_PLATFORM_UNSUPPORTED", "Android builds are supported only on Linux, macOS and Windows.");
|
|
1263
|
-
const realAndroid = platform === "android" &&
|
|
1264
|
-
isSupportedAndroidPlatform() &&
|
|
1265
|
-
(await hasAndroidHost(root));
|
|
1266
|
-
const realIos = platform === "ios" && hasIosHost(root, config.build?.[profile]);
|
|
1267
|
-
if (platform === "ios" && !realIos && !args.includes("--local"))
|
|
1268
|
-
assert(false, "IOS_HOST_REQUIRED", "A macOS Xcode host is required for a real iOS build. No local fake iOS build is created.");
|
|
1269
|
-
if (platform === "android" && !realAndroid && !args.includes("--local"))
|
|
1270
|
-
assert(false, "ANDROID_HOST_REQUIRED", "This project has no Android Gradle host. For live development, run `lynxship dev` and scan the QR code with Lynx Explorer; for an APK/AAB, integrate the official Lynx Android host under `android/` (including android/gradlew). `--local` is only for contract tests and does not create an APK.");
|
|
1271
|
-
if (realAndroid) {
|
|
1272
|
-
await runRealAndroidBuild(job, {
|
|
1273
|
-
root,
|
|
1274
|
-
profile: config.build?.[profile] ?? {},
|
|
1275
|
-
uploadArtifacts: !skipUpload,
|
|
1276
|
-
quiet: json,
|
|
1277
|
-
onEvent: (message) => progress.event(message),
|
|
1278
|
-
onProgress: (value, label) => progress.update(value, label),
|
|
1279
|
-
});
|
|
1280
|
-
}
|
|
1281
|
-
else if (realIos) {
|
|
1282
|
-
await runRealIosBuild(job, {
|
|
1283
|
-
root,
|
|
1284
|
-
profile: config.build?.[profile] ?? {},
|
|
1285
|
-
uploadArtifacts: !skipUpload,
|
|
1286
|
-
quiet: json,
|
|
1287
|
-
onEvent: (message) => progress.event(message),
|
|
1288
|
-
onProgress: (value, label) => progress.update(value, label),
|
|
1289
|
-
});
|
|
1290
|
-
}
|
|
1291
|
-
else {
|
|
1292
|
-
await builds.run(job.id);
|
|
1293
|
-
}
|
|
1294
|
-
}
|
|
1295
|
-
progress.update(100);
|
|
1296
|
-
await saveState(state, repository, builds, submissions);
|
|
1297
|
-
}
|
|
1298
|
-
catch (error) {
|
|
1299
|
-
await saveState(state, repository, builds, submissions);
|
|
1300
|
-
throw error;
|
|
1373
|
+
const wait = !args.includes("--no-wait");
|
|
1374
|
+
const local = args.includes("--local");
|
|
1375
|
+
const platforms = buildAll ? ["android", "ios"] : [platform];
|
|
1376
|
+
if (buildAll && wait && !local) {
|
|
1377
|
+
assert(process.platform === "darwin", "BUILD_ALL_MACOS_REQUIRED", "A real build for both Android and iOS requires macOS locally. Use `lynxship build --platform android` on Windows/Linux, or run this command on a macOS CI worker.");
|
|
1378
|
+
assert(await hasAndroidHost(root), "ANDROID_HOST_REQUIRED", "The Android host is required for `lynxship build --platform all`.");
|
|
1379
|
+
assert(hasIosHost(root, config.build?.[profile]), "IOS_HOST_REQUIRED", "The iOS Xcode host is required for `lynxship build --platform all`.");
|
|
1301
1380
|
}
|
|
1302
|
-
|
|
1303
|
-
|
|
1381
|
+
if (!buildAll) {
|
|
1382
|
+
await executeBuild({
|
|
1383
|
+
config,
|
|
1384
|
+
profile,
|
|
1385
|
+
platform,
|
|
1386
|
+
skipUpload,
|
|
1387
|
+
wait,
|
|
1388
|
+
local,
|
|
1389
|
+
state,
|
|
1390
|
+
repository,
|
|
1391
|
+
builds,
|
|
1392
|
+
submissions,
|
|
1393
|
+
});
|
|
1394
|
+
return;
|
|
1304
1395
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1396
|
+
if (buildAll && wait && !local)
|
|
1397
|
+
await buildSharedLynxBundle();
|
|
1398
|
+
const progress = ui.progress("Android + iOS build");
|
|
1399
|
+
const progressValues = {
|
|
1400
|
+
android: 0,
|
|
1401
|
+
ios: 0,
|
|
1402
|
+
};
|
|
1403
|
+
const progressLabels = {
|
|
1404
|
+
android: "Starting Android build…",
|
|
1405
|
+
ios: "Starting iOS build…",
|
|
1406
|
+
};
|
|
1407
|
+
const platformName = (target) => target === "android" ? "Android" : "iOS";
|
|
1408
|
+
const outcomes = await Promise.allSettled(platforms.map((target) => executeBuild({
|
|
1409
|
+
config,
|
|
1410
|
+
profile,
|
|
1411
|
+
platform: target,
|
|
1412
|
+
skipUpload,
|
|
1413
|
+
wait,
|
|
1414
|
+
local,
|
|
1415
|
+
state,
|
|
1416
|
+
repository,
|
|
1417
|
+
builds,
|
|
1418
|
+
submissions,
|
|
1419
|
+
progress,
|
|
1420
|
+
progressPrefix: platformName(target),
|
|
1421
|
+
skipBundleBuild: wait && !local,
|
|
1422
|
+
onEvent: (message) => progress.event(`${platformName(target)} · ${message}`),
|
|
1423
|
+
onProgress: (value, label) => {
|
|
1424
|
+
if (value !== undefined)
|
|
1425
|
+
progressValues[target] = value;
|
|
1426
|
+
if (label)
|
|
1427
|
+
progressLabels[target] = label;
|
|
1428
|
+
const average = (progressValues.android + progressValues.ios) / platforms.length;
|
|
1429
|
+
progress.update(average, `${platformName(target)} · ${progressLabels[target]}`);
|
|
1430
|
+
},
|
|
1431
|
+
}, false)));
|
|
1432
|
+
progress.update(100, "Android and iOS builds finished");
|
|
1433
|
+
progress.stop();
|
|
1434
|
+
const summaryBuilds = outcomes.map((outcome, index) => {
|
|
1435
|
+
if (outcome.status === "fulfilled")
|
|
1436
|
+
return outcome.value;
|
|
1437
|
+
const error = outcome.reason;
|
|
1438
|
+
const buildId = error && typeof error === "object" && "buildId" in error
|
|
1439
|
+
? String(error.buildId)
|
|
1440
|
+
: undefined;
|
|
1441
|
+
const job = buildId ? builds.jobs.get(buildId) : undefined;
|
|
1442
|
+
return (job ?? {
|
|
1443
|
+
platform: platforms[index] ?? "android",
|
|
1444
|
+
state: "failed",
|
|
1445
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1446
|
+
});
|
|
1447
|
+
});
|
|
1448
|
+
const failed = summaryBuilds.some((result) => result.state === "failed");
|
|
1449
|
+
printValue({
|
|
1450
|
+
status: failed
|
|
1451
|
+
? "failed"
|
|
1452
|
+
: summaryBuilds.every((result) => result.state === "success")
|
|
1453
|
+
? "success"
|
|
1454
|
+
: "queued",
|
|
1455
|
+
builds: summaryBuilds,
|
|
1456
|
+
}, {
|
|
1457
|
+
title: "Build all result",
|
|
1458
|
+
rows: summaryBuilds.map((result) => ({
|
|
1459
|
+
label: platformName(result.platform),
|
|
1460
|
+
value: `${result.state}${"id" in result ? ` · ${result.id}` : ""}`,
|
|
1461
|
+
valueColor: result.state === "success"
|
|
1462
|
+
? "green"
|
|
1463
|
+
: result.state === "failed"
|
|
1464
|
+
? "red"
|
|
1465
|
+
: "yellow",
|
|
1466
|
+
})),
|
|
1467
|
+
done: failed
|
|
1468
|
+
? "At least one platform build failed. Review its events and retry that platform."
|
|
1469
|
+
: "Android and iOS build jobs completed. Submit each successful artifact separately.",
|
|
1321
1470
|
});
|
|
1322
|
-
|
|
1323
|
-
|
|
1471
|
+
for (const result of summaryBuilds)
|
|
1472
|
+
if (result.state === "success" &&
|
|
1473
|
+
"artifact" in result &&
|
|
1474
|
+
result.artifact?.url)
|
|
1475
|
+
ui.downloadArtifact(result.artifact.url, result.artifact.expiresAt);
|
|
1476
|
+
if (failed)
|
|
1477
|
+
process.exitCode = 5;
|
|
1324
1478
|
}
|
|
1325
1479
|
function exitCode(error) {
|
|
1326
1480
|
const code = error.code;
|
|
@@ -1341,14 +1495,23 @@ function exitCode(error) {
|
|
|
1341
1495
|
void main()
|
|
1342
1496
|
.catch((error) => {
|
|
1343
1497
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1498
|
+
const code = error.code ?? "CLI_ERROR";
|
|
1499
|
+
const nextSteps = guidanceForError(error);
|
|
1344
1500
|
if (json) {
|
|
1345
1501
|
console.log(JSON.stringify({
|
|
1346
1502
|
error: message,
|
|
1347
|
-
code
|
|
1503
|
+
code,
|
|
1504
|
+
...(nextSteps.commands.length > 0
|
|
1505
|
+
? {
|
|
1506
|
+
nextSteps: nextSteps.commands,
|
|
1507
|
+
...(nextSteps.note ? { note: nextSteps.note } : {}),
|
|
1508
|
+
}
|
|
1509
|
+
: {}),
|
|
1348
1510
|
}));
|
|
1349
1511
|
}
|
|
1350
1512
|
else {
|
|
1351
1513
|
ui.error(message);
|
|
1514
|
+
ui.nextSteps(nextSteps);
|
|
1352
1515
|
}
|
|
1353
1516
|
process.exitCode = exitCode(error);
|
|
1354
1517
|
})
|
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":"AAGA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"ios-build.d.ts","sourceRoot":"","sources":["../src/ios-build.ts"],"names":[],"mappings":"AAGA,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,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAED,wBAAgB,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,CAuMnB"}
|
package/dist/ios-build.js
CHANGED
|
@@ -5,7 +5,8 @@ import { assert, sha256 } from "@lynxship/contracts";
|
|
|
5
5
|
import { transitionBuild } from "@lynxship/build-orchestrator";
|
|
6
6
|
import { loadR2, uploadR2Artifact } from "./r2.js";
|
|
7
7
|
import { nativeArtifactName } from "./artifact-name.js";
|
|
8
|
-
import { commandExists,
|
|
8
|
+
import { commandExists, runProcess } from "./process-runner.js";
|
|
9
|
+
import { buildLynxBundle } from "./bundle-build.js";
|
|
9
10
|
export function hasIosHost(root, profile) {
|
|
10
11
|
if (process.platform !== "darwin")
|
|
11
12
|
return false;
|
|
@@ -96,13 +97,16 @@ export async function runRealIosBuild(job, options) {
|
|
|
96
97
|
await loadR2(options.root);
|
|
97
98
|
await mkdir(archiveDirectory, { recursive: true });
|
|
98
99
|
transitionBuild(job, "uploading_source", "iOS source prepared");
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
if (options.skipBundleBuild) {
|
|
101
|
+
step("Using shared Lynx bundle", 5);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
step("Building Lynx bundle with Rspeedy…", 5);
|
|
105
|
+
await buildLynxBundle(options.root, {
|
|
106
|
+
quiet: options.quiet,
|
|
107
|
+
onOutput: options.onEvent,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
106
110
|
if (ios.bundleScript) {
|
|
107
111
|
step("Syncing bundle into the iOS host…", 8);
|
|
108
112
|
await runProcess(process.execPath, [resolve(options.root, ios.bundleScript)], {
|
package/dist/ui/components.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface ProgressHandle {
|
|
|
18
18
|
update(value?: number, label?: string): void;
|
|
19
19
|
stop(): void;
|
|
20
20
|
}
|
|
21
|
+
export declare function formatEvent(message: string): string;
|
|
21
22
|
export declare function createProgress(label: string, enabled: boolean): ProgressHandle;
|
|
22
23
|
export interface SpinnerHandle {
|
|
23
24
|
succeed(message: string): void;
|
|
@@ -32,5 +33,6 @@ export declare const pill: {
|
|
|
32
33
|
error: (text: string) => string;
|
|
33
34
|
};
|
|
34
35
|
export declare function finalLine(message: string, success?: boolean): void;
|
|
36
|
+
export declare function nextSteps(commands: string[], note?: string): void;
|
|
35
37
|
export declare function downloadArtifact(url: string, expiresAt?: string, enabled?: boolean): void;
|
|
36
38
|
//# 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;
|
|
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;AAyBD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,GACf,cAAc,CAkEhB;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,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAOjE;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,UAAO,GACb,IAAI,CAQN"}
|
package/dist/ui/components.js
CHANGED
|
@@ -10,13 +10,13 @@ export function sectionHeader(title) {
|
|
|
10
10
|
}
|
|
11
11
|
export const log = {
|
|
12
12
|
success(message) {
|
|
13
|
-
console.log(` ${c.
|
|
13
|
+
console.log(` ${c.green("◆")} ${c.text(message)}`);
|
|
14
14
|
},
|
|
15
15
|
warn(message) {
|
|
16
|
-
console.log(` ${c.yellow("
|
|
16
|
+
console.log(` ${c.yellow("!")} ${c.yellow(message)}`);
|
|
17
17
|
},
|
|
18
18
|
error(message) {
|
|
19
|
-
console.error(` ${c.red("
|
|
19
|
+
console.error(` ${c.red("x")} ${c.red(message)}`);
|
|
20
20
|
},
|
|
21
21
|
info(message) {
|
|
22
22
|
console.log(` ${c.muted(message)}`);
|
|
@@ -105,7 +105,7 @@ function eventTone(message) {
|
|
|
105
105
|
return "step";
|
|
106
106
|
return "output";
|
|
107
107
|
}
|
|
108
|
-
function formatEvent(message) {
|
|
108
|
+
export function formatEvent(message) {
|
|
109
109
|
const colors = {
|
|
110
110
|
command: c.blue,
|
|
111
111
|
step: c.teal,
|
|
@@ -115,17 +115,16 @@ function formatEvent(message) {
|
|
|
115
115
|
output: c.text,
|
|
116
116
|
};
|
|
117
117
|
const tone = eventTone(message);
|
|
118
|
-
const tag = {
|
|
119
|
-
command: "[CMD]",
|
|
120
|
-
step: "[STEP]",
|
|
121
|
-
success: "[OK]",
|
|
122
|
-
warning: "[WARN]",
|
|
123
|
-
error: "[ERROR]",
|
|
124
|
-
output: "[LOG]",
|
|
125
|
-
}[tone];
|
|
126
118
|
const normalized = message.trim().replace(/^\$\s*/, "");
|
|
127
|
-
const
|
|
128
|
-
|
|
119
|
+
const marker = {
|
|
120
|
+
command: supportsUnicode() ? "➜" : ">",
|
|
121
|
+
step: "-",
|
|
122
|
+
success: supportsUnicode() ? "◆" : "*",
|
|
123
|
+
warning: "!",
|
|
124
|
+
error: "x",
|
|
125
|
+
output: supportsUnicode() ? "│" : "|",
|
|
126
|
+
}[tone];
|
|
127
|
+
return ` ${colors[tone](marker)} ${colors[tone](normalized)}`;
|
|
129
128
|
}
|
|
130
129
|
const activeAnimations = new Set();
|
|
131
130
|
let cleanupHooksInstalled = false;
|
|
@@ -162,6 +161,7 @@ export function createProgress(label, enabled) {
|
|
|
162
161
|
let spinnerIndex = 0;
|
|
163
162
|
let currentValue = 0;
|
|
164
163
|
let hasMeasuredValue = false;
|
|
164
|
+
let previousTone;
|
|
165
165
|
const formatPercent = (value) => `${value.toFixed(2).replace(/\.?0+$/, "")}%`;
|
|
166
166
|
const bar = new cliProgress.SingleBar({
|
|
167
167
|
format: ` {spinner} {stage} ${c.teal("{bar}")} ${c.muted("{percent}")}`,
|
|
@@ -191,9 +191,13 @@ export function createProgress(label, enabled) {
|
|
|
191
191
|
const logEvent = (message) => {
|
|
192
192
|
if (!bar.isActive)
|
|
193
193
|
return;
|
|
194
|
+
const tone = eventTone(message);
|
|
194
195
|
bar.stop();
|
|
196
|
+
if (tone === "output" && previousTone === "output")
|
|
197
|
+
console.log();
|
|
195
198
|
console.log(formatEvent(message));
|
|
196
199
|
bar.start(100, currentValue, payload(currentLabel));
|
|
200
|
+
previousTone = tone;
|
|
197
201
|
};
|
|
198
202
|
return {
|
|
199
203
|
event: logEvent,
|
|
@@ -228,11 +232,11 @@ export function spin(text, enabled) {
|
|
|
228
232
|
const untrack = trackAnimation(() => spinner.stop());
|
|
229
233
|
return {
|
|
230
234
|
succeed: (message) => {
|
|
231
|
-
spinner.succeed(`${c.
|
|
235
|
+
spinner.succeed(`${c.green("◆")} ${c.text(message)}`);
|
|
232
236
|
untrack();
|
|
233
237
|
},
|
|
234
238
|
fail: (message) => {
|
|
235
|
-
spinner.fail(`${c.red("
|
|
239
|
+
spinner.fail(`${c.red("x")} ${c.red(message)}`);
|
|
236
240
|
untrack();
|
|
237
241
|
},
|
|
238
242
|
stop: () => {
|
|
@@ -255,6 +259,16 @@ export const pill = {
|
|
|
255
259
|
export function finalLine(message, success = true) {
|
|
256
260
|
console.log(`\n${success ? c.brand("◆") : c.red("◆")} ${c.text(message)}`);
|
|
257
261
|
}
|
|
262
|
+
export function nextSteps(commands, note) {
|
|
263
|
+
if (commands.length === 0)
|
|
264
|
+
return;
|
|
265
|
+
console.error(`\n${c.yellow("Next steps")}`);
|
|
266
|
+
commands.forEach((command, index) => {
|
|
267
|
+
console.error(` ${c.muted(`${index + 1}.`)} ${c.teal(command)}`);
|
|
268
|
+
});
|
|
269
|
+
if (note)
|
|
270
|
+
console.error(` ${c.muted(note)}`);
|
|
271
|
+
}
|
|
258
272
|
export function downloadArtifact(url, expiresAt, enabled = true) {
|
|
259
273
|
if (!enabled)
|
|
260
274
|
return;
|
package/dist/ui/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type BoxRow, type ProgressHandle, type SpinnerHandle } from "./components.js";
|
|
2
2
|
import { terminalOptions, type TerminalOptions } from "./terminal.js";
|
|
3
|
+
import type { CliGuidance } from "../guidance.js";
|
|
3
4
|
export declare class CliUi {
|
|
4
5
|
readonly options: TerminalOptions;
|
|
5
6
|
readonly interactive: boolean;
|
|
@@ -11,6 +12,7 @@ export declare class CliUi {
|
|
|
11
12
|
info(message: string): void;
|
|
12
13
|
debug(message: string): void;
|
|
13
14
|
error(message: string): void;
|
|
15
|
+
nextSteps(guidance: CliGuidance): void;
|
|
14
16
|
summary(title: string, rows: BoxRow[]): void;
|
|
15
17
|
configurationStatus(rows: BoxRow[]): void;
|
|
16
18
|
progress(label: string): ProgressHandle;
|
|
@@ -20,5 +22,5 @@ export declare class CliUi {
|
|
|
20
22
|
}
|
|
21
23
|
export declare function createCliUi(args: string[]): CliUi;
|
|
22
24
|
export { terminalOptions };
|
|
23
|
-
export type { BoxRow, TerminalOptions };
|
|
25
|
+
export type { BoxRow, ProgressHandle, TerminalOptions };
|
|
24
26
|
//# sourceMappingURL=index.d.ts.map
|
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,EASL,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;CAGxD;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, createProgress, spin, downloadArtifact, } from "./components.js";
|
|
1
|
+
import { finalLine, log, sectionHeader, summaryBox, nextSteps, createProgress, spin, downloadArtifact, } 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";
|
|
@@ -40,6 +40,11 @@ export class CliUi {
|
|
|
40
40
|
if (!this.options.json)
|
|
41
41
|
log.error(message);
|
|
42
42
|
}
|
|
43
|
+
nextSteps(guidance) {
|
|
44
|
+
if (this.options.json || this.options.quiet)
|
|
45
|
+
return;
|
|
46
|
+
nextSteps(guidance.commands, guidance.note);
|
|
47
|
+
}
|
|
43
48
|
summary(title, rows) {
|
|
44
49
|
if (this.interactive)
|
|
45
50
|
summaryBox(title, rows);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lynxship/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Build, sign, store, submit and update LynxJS applications from the terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -70,9 +70,9 @@
|
|
|
70
70
|
"qrcode-terminal": "0.12.0",
|
|
71
71
|
"@lynxship/build-orchestrator": "0.1.0",
|
|
72
72
|
"@lynxship/contracts": "0.1.0",
|
|
73
|
+
"@lynxship/signing": "0.1.0",
|
|
73
74
|
"@lynxship/submit": "0.1.0",
|
|
74
|
-
"@lynxship/db": "0.1.0"
|
|
75
|
-
"@lynxship/signing": "0.1.0"
|
|
75
|
+
"@lynxship/db": "0.1.0"
|
|
76
76
|
},
|
|
77
77
|
"devDependencies": {
|
|
78
78
|
"@types/cli-progress": "3.11.6",
|