@lynxship/cli 0.1.4 → 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 +90 -2
- 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/android-host.d.ts +12 -0
- package/dist/android-host.d.ts.map +1 -0
- package/dist/android-host.js +70 -0
- 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 +349 -93
- package/dist/ios-build.d.ts +1 -0
- package/dist/ios-build.d.ts.map +1 -1
- package/dist/ios-build.js +38 -12
- package/dist/ios-host.d.ts +17 -0
- package/dist/ios-host.d.ts.map +1 -0
- package/dist/ios-host.js +93 -0
- 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 +5 -4
- package/templates/android-host/app/build.gradle +38 -0
- package/templates/android-host/app/proguard-rules.pro +2 -0
- package/templates/android-host/app/src/main/AndroidManifest.xml +20 -0
- package/templates/android-host/app/src/main/java/template/LynxShipApplication.java +13 -0
- package/templates/android-host/app/src/main/java/template/MainActivity.java +19 -0
- package/templates/android-host/app/src/main/java/template/ProjectTemplateProvider.java +32 -0
- package/templates/android-host/app/src/main/res/values/strings.xml +3 -0
- package/templates/android-host/app/src/main/res/values/themes.xml +6 -0
- package/templates/android-host/build.gradle +3 -0
- package/templates/android-host/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/templates/android-host/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/templates/android-host/gradle.properties +3 -0
- package/templates/android-host/gradlew +185 -0
- package/templates/android-host/gradlew.bat +89 -0
- package/templates/android-host/settings.gradle +20 -0
- package/templates/ios-host/ExportOptions.plist +16 -0
- package/templates/ios-host/Podfile +42 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/AppDelegate.swift +11 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/Assets.xcassets/AppIcon.appiconset/Contents.json +13 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/Assets.xcassets/Contents.json +6 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/Base.lproj/LaunchScreen.storyboard +25 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/Base.lproj/Main.storyboard +24 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/DemoLynxProvider.swift +18 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/Hello-Lynx-Bridging-Header.h +4 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/Info.plist +25 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/SceneDelegate.swift +49 -0
- package/templates/ios-host/__IOS_TARGET_NAME__/ViewController.swift +22 -0
- package/templates/ios-host/__IOS_TARGET_NAME__.xcodeproj/project.pbxproj +452 -0
- package/templates/ios-host/sync-bundle.mjs +11 -0
package/README.md
CHANGED
|
@@ -117,9 +117,10 @@ unverified artifact.
|
|
|
117
117
|
```text
|
|
118
118
|
init Initialize or link a project
|
|
119
119
|
doctor Check the local toolchain and project
|
|
120
|
-
dev Run
|
|
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
|
|
@@ -133,7 +134,8 @@ logs Stream native logs
|
|
|
133
134
|
autolink check Check Lynx native-library wiring
|
|
134
135
|
autolink codegen Run native-module codegen
|
|
135
136
|
ota doctor Check native OTA host integration
|
|
136
|
-
storage configure
|
|
137
|
+
storage configure Configure Cloudflare R2
|
|
138
|
+
android host init Create a minimal official Lynx Android host
|
|
137
139
|
android configure Configure Android signing
|
|
138
140
|
store configure Configure store submission credentials
|
|
139
141
|
```
|
|
@@ -142,6 +144,47 @@ Use `lynxship --help` or `lynxship <command> --help` for the complete option
|
|
|
142
144
|
list. `--json`, `--quiet`, `--no-color` and `--non-interactive` are available
|
|
143
145
|
for automation.
|
|
144
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
|
+
|
|
145
188
|
## OTA rollback
|
|
146
189
|
|
|
147
190
|
```bash
|
|
@@ -170,6 +213,51 @@ command-line tools with `sdkmanager`, accept the required licenses, set
|
|
|
170
213
|
`android/gradlew` is executable. macOS can build both Android and iOS; Windows
|
|
171
214
|
and Linux can build Android only.
|
|
172
215
|
|
|
216
|
+
## Pure Lynx projects and Lynx Explorer
|
|
217
|
+
|
|
218
|
+
A standard Rspeedy project can be developed without a native Android host:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
lynxship dev --project-dir ./my-lynx-app
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Rspeedy serves the development bundle and prints the QR code. Scan it with the
|
|
225
|
+
official Lynx Explorer app; edits to the Lynx source are then reflected live.
|
|
226
|
+
This is the supported path for projects containing `src/` and `lynx.config.*`
|
|
227
|
+
but no `android/` directory.
|
|
228
|
+
|
|
229
|
+
Production APK/AAB builds require a native Android host. The host is the
|
|
230
|
+
Android application that initializes Lynx, creates `LynxView`, loads the bundle
|
|
231
|
+
and contains the Gradle wrapper. `lynxship build` detects that requirement and
|
|
232
|
+
fails clearly when `android/gradlew` is absent; `--local` only tests LynxShip's
|
|
233
|
+
contract state machine and never fabricates an APK.
|
|
234
|
+
|
|
235
|
+
To create a minimal host for a pure project:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
lynxship android host init --application-id com.example.myapp
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
This command never overwrites an existing `android/` directory. It creates a
|
|
242
|
+
real Gradle application with the official Lynx Android dependencies,
|
|
243
|
+
`LynxEnv`, `LynxView`, a bundle loader and a Gradle wrapper. Replace the example
|
|
244
|
+
application ID before a store release and add any project-specific native
|
|
245
|
+
modules, permissions, services and OTA integration explicitly.
|
|
246
|
+
|
|
247
|
+
For a pure project that targets iOS, create the native Xcode/CocoaPods host
|
|
248
|
+
with:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
lynxship ios host init --bundle-identifier com.example.myapp
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
The command refuses to overwrite an existing `ios/` directory and creates a
|
|
255
|
+
Swift host based on Lynx's official integration shape: `LynxEnv`, `LynxView`,
|
|
256
|
+
`LynxTemplateProvider`, `Podfile`, `ExportOptions.plist` and a bundle sync
|
|
257
|
+
script. On macOS, `lynxship build --platform ios` installs CocoaPods before
|
|
258
|
+
archiving. Xcode, CocoaPods and real Apple signing credentials are still
|
|
259
|
+
required for a signed IPA; the CLI never fabricates them.
|
|
260
|
+
|
|
173
261
|
## Package layout
|
|
174
262
|
|
|
175
263
|
The CLI is backed by the public `@lynxship/*` runtime packages in this
|
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,12 @@
|
|
|
1
|
+
export interface AndroidHostOptions {
|
|
2
|
+
applicationId: string;
|
|
3
|
+
appName: string;
|
|
4
|
+
}
|
|
5
|
+
export interface AndroidHostResult {
|
|
6
|
+
directory: string;
|
|
7
|
+
applicationId: string;
|
|
8
|
+
packageName: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function initializeAndroidHost(root: string, options: AndroidHostOptions): Promise<AndroidHostResult>;
|
|
11
|
+
export declare function suggestedAndroidApplicationId(root: string): string;
|
|
12
|
+
//# sourceMappingURL=android-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"android-host.d.ts","sourceRoot":"","sources":["../src/android-host.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB;AA2BD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,iBAAiB,CAAC,CA6D5B;AAED,wBAAgB,6BAA6B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKlE"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { access, chmod, cp, mkdir, readFile, rename, writeFile, } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { basename, dirname, join } from "node:path";
|
|
4
|
+
import { assert } from "@lynxship/contracts";
|
|
5
|
+
const templateRoot = fileURLToPath(new URL("../templates/android-host/", import.meta.url));
|
|
6
|
+
function packagePath(packageName) {
|
|
7
|
+
return packageName.split(".").join("/");
|
|
8
|
+
}
|
|
9
|
+
function validateApplicationId(applicationId) {
|
|
10
|
+
assert(/^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)+$/.test(applicationId), "ANDROID_APPLICATION_ID_INVALID", "Android application ID must contain at least two dot-separated Java package segments, for example com.example.myapp.");
|
|
11
|
+
}
|
|
12
|
+
async function exists(path) {
|
|
13
|
+
try {
|
|
14
|
+
await access(path);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function safeAppName(value) {
|
|
22
|
+
return value.replace(/[^a-zA-Z0-9 ._-]/g, "").trim() || "Lynx App";
|
|
23
|
+
}
|
|
24
|
+
export async function initializeAndroidHost(root, options) {
|
|
25
|
+
validateApplicationId(options.applicationId);
|
|
26
|
+
const android = join(root, "android");
|
|
27
|
+
assert(!(await exists(android)), "ANDROID_HOST_EXISTS", `The Android host already exists at ${android}. LynxShip will not overwrite it.`);
|
|
28
|
+
const packageName = options.applicationId;
|
|
29
|
+
const packageDirectory = join(android, "app", "src", "main", "java", packagePath(packageName));
|
|
30
|
+
await cp(templateRoot, android, { recursive: true, force: false });
|
|
31
|
+
await mkdir(dirname(packageDirectory), { recursive: true });
|
|
32
|
+
await rename(join(android, "app", "src", "main", "java", "template"), packageDirectory);
|
|
33
|
+
const replacements = {
|
|
34
|
+
__APPLICATION_ID__: options.applicationId,
|
|
35
|
+
__PACKAGE_NAME__: packageName,
|
|
36
|
+
__APP_NAME__: safeAppName(options.appName),
|
|
37
|
+
};
|
|
38
|
+
const textFiles = [
|
|
39
|
+
"build.gradle",
|
|
40
|
+
"settings.gradle",
|
|
41
|
+
"gradle.properties",
|
|
42
|
+
"app/build.gradle",
|
|
43
|
+
"app/src/main/AndroidManifest.xml",
|
|
44
|
+
"app/src/main/res/values/strings.xml",
|
|
45
|
+
"app/src/main/res/values/themes.xml",
|
|
46
|
+
"app/src/main/java/__PACKAGE_PATH__/LynxShipApplication.java",
|
|
47
|
+
"app/src/main/java/__PACKAGE_PATH__/MainActivity.java",
|
|
48
|
+
"app/src/main/java/__PACKAGE_PATH__/ProjectTemplateProvider.java",
|
|
49
|
+
];
|
|
50
|
+
for (const relativeFile of textFiles) {
|
|
51
|
+
const target = join(android, relativeFile.replace("__PACKAGE_PATH__", packagePath(packageName)));
|
|
52
|
+
let content = await readFile(target, "utf8");
|
|
53
|
+
for (const [placeholder, value] of Object.entries(replacements))
|
|
54
|
+
content = content.replaceAll(placeholder, value);
|
|
55
|
+
await writeFile(target, content, "utf8");
|
|
56
|
+
}
|
|
57
|
+
if (process.platform !== "win32")
|
|
58
|
+
await chmod(join(android, "gradlew"), 0o755);
|
|
59
|
+
return {
|
|
60
|
+
directory: android,
|
|
61
|
+
applicationId: options.applicationId,
|
|
62
|
+
packageName,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function suggestedAndroidApplicationId(root) {
|
|
66
|
+
const project = basename(root)
|
|
67
|
+
.toLowerCase()
|
|
68
|
+
.replace(/[^a-z0-9]+/g, "");
|
|
69
|
+
return `com.example.${project || "lynxapp"}`;
|
|
70
|
+
}
|
|
@@ -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
|
+
}
|