@lynxship/cli 0.1.20 → 0.1.21
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 +66 -5
- package/dist/android/signing.d.ts +16 -0
- package/dist/android/signing.d.ts.map +1 -0
- package/dist/android/signing.js +131 -0
- package/dist/android/types.d.ts +12 -0
- package/dist/android/types.d.ts.map +1 -0
- package/dist/android/types.js +1 -0
- package/dist/android-build.d.ts +4 -13
- package/dist/android-build.d.ts.map +1 -1
- package/dist/android-build.js +14 -133
- package/dist/android-toolchain/inspection.d.ts +3 -0
- package/dist/android-toolchain/inspection.d.ts.map +1 -0
- package/dist/android-toolchain/inspection.js +122 -0
- package/dist/android-toolchain/probes.d.ts +23 -0
- package/dist/android-toolchain/probes.d.ts.map +1 -0
- package/dist/android-toolchain/probes.js +183 -0
- package/dist/android-toolchain/types.d.ts +20 -0
- package/dist/android-toolchain/types.d.ts.map +1 -0
- package/dist/android-toolchain/types.js +1 -0
- package/dist/android-toolchain.d.ts +4 -20
- package/dist/android-toolchain.d.ts.map +1 -1
- package/dist/android-toolchain.js +4 -300
- package/dist/app-config.d.ts +25 -0
- package/dist/app-config.d.ts.map +1 -0
- package/dist/app-config.js +106 -0
- package/dist/bundle-build.d.ts.map +1 -1
- package/dist/bundle-build.js +11 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +66 -0
- package/dist/commands/build-execution.d.ts +41 -0
- package/dist/commands/build-execution.d.ts.map +1 -0
- package/dist/commands/build-execution.js +182 -0
- package/dist/commands/configuration.d.ts +19 -0
- package/dist/commands/configuration.d.ts.map +1 -0
- package/dist/commands/configuration.js +174 -0
- package/dist/commands/development.d.ts +22 -0
- package/dist/commands/development.d.ts.map +1 -0
- package/dist/commands/development.js +132 -0
- package/dist/commands/device.d.ts +3 -0
- package/dist/commands/device.d.ts.map +1 -0
- package/dist/commands/device.js +86 -0
- package/dist/commands/doctor.d.ts +18 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +320 -0
- package/dist/commands/help.d.ts +2 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +118 -0
- package/dist/commands/i18n.d.ts +14 -0
- package/dist/commands/i18n.d.ts.map +1 -0
- package/dist/commands/i18n.js +88 -0
- package/dist/commands/logs.d.ts +3 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +51 -0
- package/dist/commands/metadata.d.ts +3 -0
- package/dist/commands/metadata.d.ts.map +1 -0
- package/dist/commands/metadata.js +45 -0
- package/dist/commands/ota.d.ts +28 -0
- package/dist/commands/ota.d.ts.map +1 -0
- package/dist/commands/ota.js +203 -0
- package/dist/commands/project.d.ts +14 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +122 -0
- package/dist/commands/remote-build.d.ts +35 -0
- package/dist/commands/remote-build.d.ts.map +1 -0
- package/dist/commands/remote-build.js +73 -0
- package/dist/commands/submit.d.ts +27 -0
- package/dist/commands/submit.d.ts.map +1 -0
- package/dist/commands/submit.js +92 -0
- package/dist/ecosystem.d.ts +9 -0
- package/dist/ecosystem.d.ts.map +1 -0
- package/dist/ecosystem.js +95 -0
- package/dist/frameworks.d.ts.map +1 -1
- package/dist/frameworks.js +5 -1
- package/dist/guidance/catalog.d.ts +3 -0
- package/dist/guidance/catalog.d.ts.map +1 -0
- package/dist/guidance/catalog.js +504 -0
- package/dist/guidance/types.d.ts +10 -0
- package/dist/guidance/types.d.ts.map +1 -0
- package/dist/guidance/types.js +1 -0
- package/dist/guidance.d.ts +2 -9
- package/dist/guidance.d.ts.map +1 -1
- package/dist/guidance.js +1 -502
- package/dist/i18n/discovery.d.ts +14 -0
- package/dist/i18n/discovery.d.ts.map +1 -0
- package/dist/i18n/discovery.js +106 -0
- package/dist/i18n/plan.d.ts +12 -0
- package/dist/i18n/plan.d.ts.map +1 -0
- package/dist/i18n/plan.js +95 -0
- package/dist/i18n/setup.d.ts +11 -0
- package/dist/i18n/setup.d.ts.map +1 -0
- package/dist/i18n/setup.js +47 -0
- package/dist/i18n/types.d.ts +17 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2069
- package/dist/ios/assets.d.ts +19 -0
- package/dist/ios/assets.d.ts.map +1 -0
- package/dist/ios/assets.js +128 -0
- package/dist/ios/production-build.d.ts +6 -0
- package/dist/ios/production-build.d.ts.map +1 -0
- package/dist/ios/production-build.js +200 -0
- package/dist/ios/project.d.ts +4 -0
- package/dist/ios/project.d.ts.map +1 -0
- package/dist/ios/project.js +43 -0
- package/dist/ios/simulator-build.d.ts +8 -0
- package/dist/ios/simulator-build.d.ts.map +1 -0
- package/dist/ios/simulator-build.js +188 -0
- package/dist/ios/simulator.d.ts +5 -0
- package/dist/ios/simulator.d.ts.map +1 -0
- package/dist/ios/simulator.js +60 -0
- package/dist/ios/types.d.ts +14 -0
- package/dist/ios/types.d.ts.map +1 -0
- package/dist/ios/types.js +1 -0
- package/dist/ios-build.d.ts +4 -37
- package/dist/ios-build.d.ts.map +1 -1
- package/dist/ios-build.js +3 -575
- package/dist/ios-host.d.ts.map +1 -1
- package/dist/ios-toolchain/inspection.d.ts +4 -0
- package/dist/ios-toolchain/inspection.d.ts.map +1 -0
- package/dist/ios-toolchain/inspection.js +211 -0
- package/dist/ios-toolchain/probes.d.ts +24 -0
- package/dist/ios-toolchain/probes.d.ts.map +1 -0
- package/dist/ios-toolchain/probes.js +127 -0
- package/dist/ios-toolchain/types.d.ts +17 -0
- package/dist/ios-toolchain/types.d.ts.map +1 -0
- package/dist/ios-toolchain/types.js +1 -0
- package/dist/ios-toolchain.d.ts +3 -18
- package/dist/ios-toolchain.d.ts.map +1 -1
- package/dist/ios-toolchain.js +1 -333
- package/dist/plugins/contracts.d.ts +28 -0
- package/dist/plugins/contracts.d.ts.map +1 -0
- package/dist/plugins/contracts.js +1 -0
- package/dist/plugins/discovery.d.ts +19 -0
- package/dist/plugins/discovery.d.ts.map +1 -0
- package/dist/plugins/discovery.js +250 -0
- package/dist/plugins/native-operations.d.ts +28 -0
- package/dist/plugins/native-operations.d.ts.map +1 -0
- package/dist/plugins/native-operations.js +217 -0
- package/dist/plugins/operations.d.ts +9 -0
- package/dist/plugins/operations.d.ts.map +1 -0
- package/dist/plugins/operations.js +76 -0
- package/dist/plugins/validation.d.ts +9 -0
- package/dist/plugins/validation.d.ts.map +1 -0
- package/dist/plugins/validation.js +52 -0
- package/dist/plugins.d.ts +3 -34
- package/dist/plugins.d.ts.map +1 -1
- package/dist/plugins.js +2 -595
- package/dist/process-runner.d.ts +4 -0
- package/dist/process-runner.d.ts.map +1 -1
- package/dist/process-runner.js +14 -0
- package/dist/remote.d.ts +22 -1
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +141 -2
- package/dist/rspeedy-app-config.d.ts +4 -0
- package/dist/rspeedy-app-config.d.ts.map +1 -0
- package/dist/rspeedy-app-config.js +32 -0
- package/dist/runtime/args.d.ts +3 -0
- package/dist/runtime/args.d.ts.map +1 -0
- package/dist/runtime/args.js +11 -0
- package/dist/runtime/build-commands.d.ts +3 -0
- package/dist/runtime/build-commands.d.ts.map +1 -0
- package/dist/runtime/build-commands.js +282 -0
- package/dist/runtime/context.d.ts +63 -0
- package/dist/runtime/context.d.ts.map +1 -0
- package/dist/runtime/context.js +179 -0
- package/dist/runtime/early-commands.d.ts +4 -0
- package/dist/runtime/early-commands.d.ts.map +1 -0
- package/dist/runtime/early-commands.js +284 -0
- package/dist/runtime/project.d.ts +8 -0
- package/dist/runtime/project.d.ts.map +1 -0
- package/dist/runtime/project.js +38 -0
- package/dist/runtime/state.d.ts +47 -0
- package/dist/runtime/state.d.ts.map +1 -0
- package/dist/runtime/state.js +33 -0
- package/dist/secure-store/backends.d.ts +20 -0
- package/dist/secure-store/backends.d.ts.map +1 -0
- package/dist/secure-store/backends.js +248 -0
- package/dist/secure-store/types.d.ts +27 -0
- package/dist/secure-store/types.d.ts.map +1 -0
- package/dist/secure-store/types.js +1 -0
- package/dist/secure-store.d.ts +2 -26
- package/dist/secure-store.d.ts.map +1 -1
- package/dist/secure-store.js +3 -245
- package/package.json +7 -4
- package/templates/android-host/app/build.gradle +1 -0
- package/templates/android-host/app/src/main/java/template/MainActivity.java +98 -3
- package/templates/ios-host/__IOS_TARGET_NAME__/DemoLynxProvider.swift +26 -16
- package/templates/ios-host/__IOS_TARGET_NAME__/ViewController.swift +107 -19
package/README.md
CHANGED
|
@@ -11,6 +11,33 @@ expiring download URL with a compact terminal QR code.
|
|
|
11
11
|
> native hosts; HarmonyOS requires the official Hvigor/DevEco host; Web and
|
|
12
12
|
> Desktop require the project's official Rspeedy/Lynxtron configuration.
|
|
13
13
|
|
|
14
|
+
## Sparkling-compatible app configuration
|
|
15
|
+
|
|
16
|
+
Projects may use the official Sparkling-style `app.config.ts` (or its JavaScript
|
|
17
|
+
variants) as the single Lynx build entrypoint:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { defineConfig } from "@lynx-js/rspeedy";
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
lynxConfig: defineConfig({
|
|
24
|
+
source: { entry: { main: "./src/pages/main/index.tsx" } },
|
|
25
|
+
environments: { lynx: {} },
|
|
26
|
+
}),
|
|
27
|
+
appName: "My Lynx app",
|
|
28
|
+
platform: { android: { packageName: "com.example.myapp" } },
|
|
29
|
+
paths: { android: "android/app/src/main/assets" },
|
|
30
|
+
};
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
When `lynxship build` finds this file, it loads it only for the explicit build
|
|
34
|
+
operation, validates its routes/plugins and safe project-relative paths, then
|
|
35
|
+
calls the installed project's `createRspeedy` API with `lynxConfig`. Android
|
|
36
|
+
and iOS host generation also reuses the declared identifiers, name, icon and
|
|
37
|
+
asset paths. `lynxship.json` remains the place for LynxShip profiles, signing,
|
|
38
|
+
OTA and cloud settings; projects with a normal `lynx.config.*` keep their
|
|
39
|
+
existing package-manager build workflow.
|
|
40
|
+
|
|
14
41
|
## Install
|
|
15
42
|
|
|
16
43
|
```bash
|
|
@@ -25,6 +52,35 @@ npx @lynxship/cli@latest doctor --project-dir ./my-lynx-app
|
|
|
25
52
|
|
|
26
53
|
The package exposes the `lynxship` executable.
|
|
27
54
|
|
|
55
|
+
## Automatic i18n setup
|
|
56
|
+
|
|
57
|
+
For a Lynx project using i18next, the CLI can perform the repeatable setup in
|
|
58
|
+
one explicit, reviewable command:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx @lynxship/cli@latest init
|
|
62
|
+
npx @lynxship/cli@latest i18n setup --dry-run
|
|
63
|
+
npx @lynxship/cli@latest i18n setup
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The setup command detects the entry file and JSON locale catalog, installs
|
|
67
|
+
`@lynxship/i18n`, a compatible i18next major, the LynxShip device-storage
|
|
68
|
+
bridge and only the FormatJS polyfills needed by detected `Intl.*` usage. It
|
|
69
|
+
generates `src/lynxship/i18n-polyfills.ts` with static imports in dependency
|
|
70
|
+
order and adds one import to the detected entry file. Use `--intl all` when a
|
|
71
|
+
library accesses Intl dynamically, `--locales en,fr,ar` to override locale
|
|
72
|
+
discovery, or `--entry src/index.tsx` when the entry file is non-standard. The
|
|
73
|
+
command fails rather than guessing an ambiguous entry file.
|
|
74
|
+
|
|
75
|
+
The adapter restores the last resolved language automatically when the host
|
|
76
|
+
exposes `LynxShipDeviceStorage` (Android, iOS or another linked Lynx host), or
|
|
77
|
+
browser `localStorage` on Web. The lookup is local and best-effort; no network
|
|
78
|
+
request is made and a missing host bridge does not prevent the app from
|
|
79
|
+
starting. Projects with their own storage can inject the `persistence.storage`
|
|
80
|
+
contract. `--no-persistence` skips installation of the device-storage bridge.
|
|
81
|
+
Generated polyfills are static and recorded in the project lockfile: npm
|
|
82
|
+
cannot be downloaded silently from an application bundle at runtime.
|
|
83
|
+
|
|
28
84
|
## Plugin ecosystem
|
|
29
85
|
|
|
30
86
|
Install a community plugin as a normal project dependency, list it in
|
|
@@ -273,7 +329,7 @@ autolink check Check Lynx native-library wiring
|
|
|
273
329
|
autolink codegen Run native-module codegen
|
|
274
330
|
ota doctor Check native OTA host integration
|
|
275
331
|
storage configure Configure Cloudflare R2
|
|
276
|
-
android host init Create
|
|
332
|
+
android host init Create an official Lynx Android host baseline
|
|
277
333
|
android configure Configure Android signing
|
|
278
334
|
store configure Configure store submission credentials
|
|
279
335
|
```
|
|
@@ -491,9 +547,12 @@ lynxship android host init --application-id com.example.myapp
|
|
|
491
547
|
|
|
492
548
|
This command never overwrites an existing `android/` directory. It creates a
|
|
493
549
|
real Gradle application with the official Lynx Android dependencies,
|
|
494
|
-
`LynxEnv`, `LynxView`, a bundle loader and a Gradle wrapper.
|
|
495
|
-
|
|
496
|
-
|
|
550
|
+
`LynxEnv`, `LynxView`, a bundle loader and a Gradle wrapper. The generated
|
|
551
|
+
Activity also provides lifecycle-aware loading/error feedback, tap-to-retry,
|
|
552
|
+
the official `onFirstScreen` readiness signal and explicit `LynxView.destroy()`
|
|
553
|
+
cleanup. Replace the example application ID before a store release and add
|
|
554
|
+
any project-specific native modules, permissions, services and OTA integration
|
|
555
|
+
explicitly.
|
|
497
556
|
|
|
498
557
|
For a pure project that targets iOS, create the native Xcode/CocoaPods host
|
|
499
558
|
with:
|
|
@@ -518,7 +577,9 @@ preview only; provide the project icon for an IPA or store release.
|
|
|
518
577
|
The command refuses to overwrite an existing `ios/` directory and creates a
|
|
519
578
|
Swift host based on Lynx's official integration shape: `LynxEnv`, `LynxView`,
|
|
520
579
|
`LynxTemplateProvider`, `Podfile`, `ExportOptions.plist` and a bundle sync
|
|
521
|
-
script.
|
|
580
|
+
script. The generated view also provides adaptive full-screen layout,
|
|
581
|
+
lifecycle-aware loading/error feedback, tap-to-retry and first-screen
|
|
582
|
+
readiness. On macOS, `lynxship build --platform ios` installs CocoaPods before
|
|
522
583
|
archiving. Xcode, CocoaPods and real Apple signing credentials are still
|
|
523
584
|
required for a signed IPA; the CLI never fabricates them.
|
|
524
585
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BuildProfile } from "../config.js";
|
|
2
|
+
export declare function signingEnvironment(root: string): Promise<NodeJS.ProcessEnv>;
|
|
3
|
+
export declare function createSigningInitScript(): Promise<{
|
|
4
|
+
directory: string;
|
|
5
|
+
file: string;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function verifySignedArtifact(root: string, artifactPath: string, options: {
|
|
8
|
+
quiet: boolean;
|
|
9
|
+
onOutput?: (line: string) => void;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
export declare function androidTool(name: string): string | undefined;
|
|
12
|
+
export declare function artifactDetails(root: string, profile: BuildProfile): {
|
|
13
|
+
task: string;
|
|
14
|
+
path: string;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=signing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.d.ts","sourceRoot":"","sources":["../../src/android/signing.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAQjD,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAoB5B;AAED,wBAAsB,uBAAuB,IAAI,OAAO,CAAC;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC,CAiDD;AAED,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,GAC7D,OAAO,CAAC,IAAI,CAAC,CAwBf;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAyB5D;AAED,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,YAAY,GACpB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAkChC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { chmod, mkdtemp, writeFile } from "node:fs/promises";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { readdirSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { assert } from "@lynxship/contracts";
|
|
7
|
+
import { loadCredentials } from "../secure-store.js";
|
|
8
|
+
import { commandExists, executableExists, runProcess, } from "../process-runner.js";
|
|
9
|
+
export async function signingEnvironment(root) {
|
|
10
|
+
const android = (await loadCredentials(root)).android;
|
|
11
|
+
const values = {
|
|
12
|
+
LYNXSHIP_KEYSTORE_PATH: process.env.LYNXSHIP_KEYSTORE_PATH ?? android?.keystorePath,
|
|
13
|
+
LYNXSHIP_KEY_ALIAS: process.env.LYNXSHIP_KEY_ALIAS ?? android?.keyAlias,
|
|
14
|
+
LYNXSHIP_KEYSTORE_PASSWORD: process.env.LYNXSHIP_KEYSTORE_PASSWORD ?? android?.keystorePassword,
|
|
15
|
+
LYNXSHIP_KEY_PASSWORD: process.env.LYNXSHIP_KEY_PASSWORD ?? android?.keyPassword,
|
|
16
|
+
};
|
|
17
|
+
const missing = Object.entries(values)
|
|
18
|
+
.filter(([, value]) => !value)
|
|
19
|
+
.map(([name]) => name);
|
|
20
|
+
assert(missing.length === 0, "BUILD_SIGNING_REQUIRED", `Signed Android builds require configuration. Missing: ${missing.join(", ")}. Run \`lynxship android configure\`.`);
|
|
21
|
+
return { ...process.env, ...values };
|
|
22
|
+
}
|
|
23
|
+
export async function createSigningInitScript() {
|
|
24
|
+
const directory = await mkdtemp(join(tmpdir(), "lynxship-gradle-"));
|
|
25
|
+
const file = join(directory, "android-signing.init.gradle");
|
|
26
|
+
const script = `
|
|
27
|
+
def keystorePath = System.getenv("LYNXSHIP_KEYSTORE_PATH")
|
|
28
|
+
def keyAlias = System.getenv("LYNXSHIP_KEY_ALIAS")
|
|
29
|
+
def keystorePassword = System.getenv("LYNXSHIP_KEYSTORE_PASSWORD")
|
|
30
|
+
def keyPassword = System.getenv("LYNXSHIP_KEY_PASSWORD")
|
|
31
|
+
|
|
32
|
+
gradle.allprojects { project ->
|
|
33
|
+
project.plugins.withId("com.android.application") {
|
|
34
|
+
def configureSigning = { android ->
|
|
35
|
+
def signing = android.signingConfigs.findByName("release")
|
|
36
|
+
if (signing == null) {
|
|
37
|
+
signing = android.signingConfigs.create("lynxshipRelease")
|
|
38
|
+
}
|
|
39
|
+
signing.storeFile = project.file(keystorePath)
|
|
40
|
+
signing.storePassword = keystorePassword
|
|
41
|
+
signing.keyAlias = keyAlias
|
|
42
|
+
signing.keyPassword = keyPassword
|
|
43
|
+
|
|
44
|
+
def release = android.buildTypes.findByName("release")
|
|
45
|
+
if (release == null) {
|
|
46
|
+
throw new GradleException(
|
|
47
|
+
"LynxShip requires an Android release build type"
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
release.signingConfig = signing
|
|
51
|
+
project.logger.lifecycle(
|
|
52
|
+
"[LynxShip] Applied machine signing credentials to \${project.path}:release"
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
def androidComponents = project.extensions.findByName("androidComponents")
|
|
57
|
+
if (androidComponents != null) {
|
|
58
|
+
androidComponents.finalizeDsl { android ->
|
|
59
|
+
configureSigning(android)
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
project.afterEvaluate {
|
|
63
|
+
configureSigning(project.extensions.findByName("android"))
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
await writeFile(file, script, "utf8");
|
|
70
|
+
await chmod(file, 0o600).catch(() => undefined);
|
|
71
|
+
return { directory, file };
|
|
72
|
+
}
|
|
73
|
+
export async function verifySignedArtifact(root, artifactPath, options) {
|
|
74
|
+
if (artifactPath.endsWith(".apk")) {
|
|
75
|
+
const apksigner = androidTool("apksigner");
|
|
76
|
+
assert(apksigner, "ANDROID_APKSIGNER_REQUIRED", "apksigner was not found in PATH. Install the Android SDK Build Tools.");
|
|
77
|
+
await runProcess(apksigner, ["verify", "--verbose", artifactPath], {
|
|
78
|
+
cwd: root,
|
|
79
|
+
...options,
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const jarsigner = commandExists("jarsigner") ? "jarsigner" : undefined;
|
|
84
|
+
assert(jarsigner, "ANDROID_JARSIGNER_REQUIRED", "jarsigner was not found in PATH. Install JDK 17.");
|
|
85
|
+
await runProcess(jarsigner, ["-verify", "-verbose", "-certs", artifactPath], {
|
|
86
|
+
cwd: root,
|
|
87
|
+
...options,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export function androidTool(name) {
|
|
91
|
+
if (commandExists(name)) {
|
|
92
|
+
if (process.platform !== "win32")
|
|
93
|
+
return name;
|
|
94
|
+
try {
|
|
95
|
+
return execFileSync("where.exe", [name], { encoding: "utf8" })
|
|
96
|
+
.split(/\r?\n/)
|
|
97
|
+
.map((value) => value.trim())
|
|
98
|
+
.find(Boolean);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return name;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const sdk = process.env.ANDROID_SDK_ROOT ?? process.env.ANDROID_HOME ?? undefined;
|
|
105
|
+
if (!sdk)
|
|
106
|
+
return undefined;
|
|
107
|
+
const executable = process.platform === "win32" ? `${name}.bat` : name;
|
|
108
|
+
try {
|
|
109
|
+
return readdirSync(join(sdk, "build-tools"))
|
|
110
|
+
.sort()
|
|
111
|
+
.reverse()
|
|
112
|
+
.map((version) => join(sdk, "build-tools", version, executable))
|
|
113
|
+
.find((candidate) => executableExists(candidate));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export function artifactDetails(root, profile) {
|
|
120
|
+
const artifact = profile.android?.artifact ?? "apk";
|
|
121
|
+
assert(artifact === "apk" || artifact === "aab", "BUILD_ARTIFACT_INVALID", "Android artifact must be apk or aab");
|
|
122
|
+
return artifact === "aab"
|
|
123
|
+
? {
|
|
124
|
+
task: "bundleRelease",
|
|
125
|
+
path: join(root, "android", "app", "build", "outputs", "bundle", "release", "app-release.aab"),
|
|
126
|
+
}
|
|
127
|
+
: {
|
|
128
|
+
task: "assembleRelease",
|
|
129
|
+
path: join(root, "android", "app", "build", "outputs", "apk", "release", "app-release.apk"),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BuildProfile } from "../config.js";
|
|
2
|
+
export interface AndroidBuildOptions {
|
|
3
|
+
root: string;
|
|
4
|
+
profile: BuildProfile;
|
|
5
|
+
uploadArtifacts?: boolean;
|
|
6
|
+
skipBundleBuild?: boolean;
|
|
7
|
+
quiet?: boolean;
|
|
8
|
+
onStep?: (message: string) => void;
|
|
9
|
+
onEvent?: (message: string) => void;
|
|
10
|
+
onProgress?: (value?: number, label?: string) => void;
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/android/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,WAAW,mBAAmB;IAClC,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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/android-build.d.ts
CHANGED
|
@@ -1,15 +1,5 @@
|
|
|
1
1
|
import { type BuildJob } from "@lynxship/contracts";
|
|
2
|
-
import type {
|
|
3
|
-
interface AndroidBuildOptions {
|
|
4
|
-
root: string;
|
|
5
|
-
profile: BuildProfile;
|
|
6
|
-
uploadArtifacts?: boolean;
|
|
7
|
-
skipBundleBuild?: boolean;
|
|
8
|
-
quiet?: boolean;
|
|
9
|
-
onStep?: (message: string) => void;
|
|
10
|
-
onEvent?: (message: string) => void;
|
|
11
|
-
onProgress?: (value?: number, label?: string) => void;
|
|
12
|
-
}
|
|
2
|
+
import type { AndroidBuildOptions } from "./android/types.js";
|
|
13
3
|
export declare function hasAndroidHost(root: string): Promise<boolean>;
|
|
14
4
|
export declare function isSupportedAndroidPlatform(): boolean;
|
|
15
5
|
/**
|
|
@@ -19,7 +9,8 @@ export declare function isSupportedAndroidPlatform(): boolean;
|
|
|
19
9
|
* project to carry a helper script. Rspeedy writes bundles and static assets
|
|
20
10
|
* to `dist`, while the Lynx Android host reads them from `app/src/main/assets`.
|
|
21
11
|
*/
|
|
22
|
-
export declare function syncLynxAssets(root: string
|
|
12
|
+
export declare function syncLynxAssets(root: string, options?: {
|
|
13
|
+
destination?: string;
|
|
14
|
+
}): Promise<string[]>;
|
|
23
15
|
export declare function runRealAndroidBuild(job: BuildJob, options: AndroidBuildOptions): Promise<BuildJob>;
|
|
24
|
-
export {};
|
|
25
16
|
//# sourceMappingURL=android-build.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"android-build.d.ts","sourceRoot":"","sources":["../src/android-build.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"android-build.d.ts","sourceRoot":"","sources":["../src/android-build.ts"],"names":[],"mappings":"AAWA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAQpE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAQ9D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAa7D;AAED,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GACrC,OAAO,CAAC,MAAM,EAAE,CAAC,CAwCnB;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,QAAQ,CAAC,CAuKnB"}
|
package/dist/android-build.js
CHANGED
|
@@ -1,138 +1,14 @@
|
|
|
1
|
-
import { access,
|
|
2
|
-
import {
|
|
3
|
-
import { constants as fsConstants, existsSync, readdirSync } from "node:fs";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
1
|
+
import { access, copyFile, cp, mkdir, readFile, readdir, rm, } from "node:fs/promises";
|
|
2
|
+
import { constants as fsConstants, existsSync } from "node:fs";
|
|
5
3
|
import { join } from "node:path";
|
|
6
4
|
import { assert, sha256 } from "@lynxship/contracts";
|
|
7
5
|
import { transitionBuild } from "@lynxship/build-orchestrator";
|
|
8
6
|
import { loadR2, uploadR2Artifact } from "./r2.js";
|
|
9
|
-
import { loadCredentials } from "./secure-store.js";
|
|
10
7
|
import { nativeArtifactName } from "./artifact-name.js";
|
|
11
|
-
import {
|
|
8
|
+
import { runProcess } from "./process-runner.js";
|
|
12
9
|
import { buildLynxBundle } from "./bundle-build.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const values = {
|
|
16
|
-
LYNXSHIP_KEYSTORE_PATH: process.env.LYNXSHIP_KEYSTORE_PATH ?? android?.keystorePath,
|
|
17
|
-
LYNXSHIP_KEY_ALIAS: process.env.LYNXSHIP_KEY_ALIAS ?? android?.keyAlias,
|
|
18
|
-
LYNXSHIP_KEYSTORE_PASSWORD: process.env.LYNXSHIP_KEYSTORE_PASSWORD ?? android?.keystorePassword,
|
|
19
|
-
LYNXSHIP_KEY_PASSWORD: process.env.LYNXSHIP_KEY_PASSWORD ?? android?.keyPassword,
|
|
20
|
-
};
|
|
21
|
-
const missing = Object.entries(values)
|
|
22
|
-
.filter(([, value]) => !value)
|
|
23
|
-
.map(([name]) => name);
|
|
24
|
-
assert(missing.length === 0, "BUILD_SIGNING_REQUIRED", `Signed Android builds require configuration. Missing: ${missing.join(", ")}. Run \`lynxship android configure\`.`);
|
|
25
|
-
return { ...process.env, ...values };
|
|
26
|
-
}
|
|
27
|
-
async function createSigningInitScript() {
|
|
28
|
-
const directory = await mkdtemp(join(tmpdir(), "lynxship-gradle-"));
|
|
29
|
-
const file = join(directory, "android-signing.init.gradle");
|
|
30
|
-
const script = `
|
|
31
|
-
def keystorePath = System.getenv("LYNXSHIP_KEYSTORE_PATH")
|
|
32
|
-
def keyAlias = System.getenv("LYNXSHIP_KEY_ALIAS")
|
|
33
|
-
def keystorePassword = System.getenv("LYNXSHIP_KEYSTORE_PASSWORD")
|
|
34
|
-
def keyPassword = System.getenv("LYNXSHIP_KEY_PASSWORD")
|
|
35
|
-
|
|
36
|
-
gradle.allprojects { project ->
|
|
37
|
-
project.plugins.withId("com.android.application") {
|
|
38
|
-
def configureSigning = { android ->
|
|
39
|
-
def signing = android.signingConfigs.findByName("release")
|
|
40
|
-
if (signing == null) {
|
|
41
|
-
signing = android.signingConfigs.create("lynxshipRelease")
|
|
42
|
-
}
|
|
43
|
-
signing.storeFile = project.file(keystorePath)
|
|
44
|
-
signing.storePassword = keystorePassword
|
|
45
|
-
signing.keyAlias = keyAlias
|
|
46
|
-
signing.keyPassword = keyPassword
|
|
47
|
-
|
|
48
|
-
def release = android.buildTypes.findByName("release")
|
|
49
|
-
if (release == null) {
|
|
50
|
-
throw new GradleException(
|
|
51
|
-
"LynxShip requires an Android release build type"
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
release.signingConfig = signing
|
|
55
|
-
project.logger.lifecycle(
|
|
56
|
-
"[LynxShip] Applied machine signing credentials to \${project.path}:release"
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
def androidComponents = project.extensions.findByName("androidComponents")
|
|
61
|
-
if (androidComponents != null) {
|
|
62
|
-
androidComponents.finalizeDsl { android ->
|
|
63
|
-
configureSigning(android)
|
|
64
|
-
}
|
|
65
|
-
} else {
|
|
66
|
-
project.afterEvaluate {
|
|
67
|
-
configureSigning(project.extensions.findByName("android"))
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
`;
|
|
73
|
-
await writeFile(file, script, "utf8");
|
|
74
|
-
await chmod(file, 0o600).catch(() => undefined);
|
|
75
|
-
return { directory, file };
|
|
76
|
-
}
|
|
77
|
-
async function verifySignedArtifact(root, artifactPath, options) {
|
|
78
|
-
if (artifactPath.endsWith(".apk")) {
|
|
79
|
-
const apksigner = androidTool("apksigner");
|
|
80
|
-
assert(apksigner, "ANDROID_APKSIGNER_REQUIRED", "apksigner was not found in PATH. Install the Android SDK Build Tools.");
|
|
81
|
-
await runProcess(apksigner, ["verify", "--verbose", artifactPath], {
|
|
82
|
-
cwd: root,
|
|
83
|
-
...options,
|
|
84
|
-
});
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const jarsigner = commandExists("jarsigner") ? "jarsigner" : undefined;
|
|
88
|
-
assert(jarsigner, "ANDROID_JARSIGNER_REQUIRED", "jarsigner was not found in PATH. Install JDK 17.");
|
|
89
|
-
await runProcess(jarsigner, ["-verify", "-verbose", "-certs", artifactPath], {
|
|
90
|
-
cwd: root,
|
|
91
|
-
...options,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
function androidTool(name) {
|
|
95
|
-
if (commandExists(name)) {
|
|
96
|
-
if (process.platform !== "win32")
|
|
97
|
-
return name;
|
|
98
|
-
try {
|
|
99
|
-
return execFileSync("where.exe", [name], { encoding: "utf8" })
|
|
100
|
-
.split(/\r?\n/)
|
|
101
|
-
.map((value) => value.trim())
|
|
102
|
-
.find(Boolean);
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
return name;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
const sdk = process.env.ANDROID_SDK_ROOT ?? process.env.ANDROID_HOME ?? undefined;
|
|
109
|
-
if (!sdk)
|
|
110
|
-
return undefined;
|
|
111
|
-
const executable = process.platform === "win32" ? `${name}.bat` : name;
|
|
112
|
-
try {
|
|
113
|
-
return readdirSync(join(sdk, "build-tools"))
|
|
114
|
-
.sort()
|
|
115
|
-
.reverse()
|
|
116
|
-
.map((version) => join(sdk, "build-tools", version, executable))
|
|
117
|
-
.find((candidate) => executableExists(candidate));
|
|
118
|
-
}
|
|
119
|
-
catch {
|
|
120
|
-
return undefined;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
function artifactDetails(root, profile) {
|
|
124
|
-
const artifact = profile.android?.artifact ?? "apk";
|
|
125
|
-
assert(artifact === "apk" || artifact === "aab", "BUILD_ARTIFACT_INVALID", "Android artifact must be apk or aab");
|
|
126
|
-
return artifact === "aab"
|
|
127
|
-
? {
|
|
128
|
-
task: "bundleRelease",
|
|
129
|
-
path: join(root, "android", "app", "build", "outputs", "bundle", "release", "app-release.aab"),
|
|
130
|
-
}
|
|
131
|
-
: {
|
|
132
|
-
task: "assembleRelease",
|
|
133
|
-
path: join(root, "android", "app", "build", "outputs", "apk", "release", "app-release.apk"),
|
|
134
|
-
};
|
|
135
|
-
}
|
|
10
|
+
import { loadAppConfig, resolveAppConfigAssetPath } from "./app-config.js";
|
|
11
|
+
import { artifactDetails, createSigningInitScript, signingEnvironment, verifySignedArtifact, } from "./android/signing.js";
|
|
136
12
|
export function hasAndroidHost(root) {
|
|
137
13
|
if (!isSupportedAndroidPlatform())
|
|
138
14
|
return Promise.resolve(false);
|
|
@@ -151,9 +27,10 @@ export function isSupportedAndroidPlatform() {
|
|
|
151
27
|
* project to carry a helper script. Rspeedy writes bundles and static assets
|
|
152
28
|
* to `dist`, while the Lynx Android host reads them from `app/src/main/assets`.
|
|
153
29
|
*/
|
|
154
|
-
export async function syncLynxAssets(root) {
|
|
30
|
+
export async function syncLynxAssets(root, options = {}) {
|
|
155
31
|
const distRoot = join(root, "dist");
|
|
156
|
-
const assetsRoot =
|
|
32
|
+
const assetsRoot = options.destination ??
|
|
33
|
+
join(root, "android", "app", "src", "main", "assets");
|
|
157
34
|
let entries;
|
|
158
35
|
try {
|
|
159
36
|
entries = await readdir(distRoot, { withFileTypes: true });
|
|
@@ -228,9 +105,13 @@ export async function runRealAndroidBuild(job, options) {
|
|
|
228
105
|
step("Checking Android SDK and Gradle toolchain…");
|
|
229
106
|
transitionBuild(job, "installing_dependencies", "syncing Lynx bundle into Android assets");
|
|
230
107
|
step("Syncing bundle into the Android host…");
|
|
231
|
-
const
|
|
108
|
+
const appConfig = await loadAppConfig(options.root);
|
|
109
|
+
const assetsRoot = resolveAppConfigAssetPath(options.root, appConfig, "android", "android/app/src/main/assets");
|
|
110
|
+
const copiedAssets = await syncLynxAssets(options.root, {
|
|
111
|
+
destination: assetsRoot,
|
|
112
|
+
});
|
|
232
113
|
for (const name of copiedAssets) {
|
|
233
|
-
options.onEvent?.(`Synced dist/${name} ->
|
|
114
|
+
options.onEvent?.(`Synced dist/${name} -> ${assetsRoot}/${name}`);
|
|
234
115
|
}
|
|
235
116
|
step("Android host synchronized", 40);
|
|
236
117
|
transitionBuild(job, "building", `Gradle ${artifact.task}`);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspection.d.ts","sourceRoot":"","sources":["../../src/android-toolchain/inspection.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,sBAAsB,EAAkB,MAAM,YAAY,CAAC;AAczE,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,sBAAsB,CAAC,CA2LjC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { platform } from "node:os";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { commandExists, executableExists } from "../process-runner.js";
|
|
5
|
+
import { check, compareVersions, javaCandidates, javaVersion, readAndroidInputs, requiredGradleForAgp, requiredJavaForAgp, sdkCandidates, sdkManagerPath, sdkExecutable, } from "./probes.js";
|
|
6
|
+
export async function inspectAndroidToolchain(root) {
|
|
7
|
+
const inputs = readAndroidInputs(root);
|
|
8
|
+
const checks = [];
|
|
9
|
+
const wrapper = join(root, "android", platform() === "win32" ? "gradlew.bat" : "gradlew");
|
|
10
|
+
const wrapperExists = existsSync(wrapper) &&
|
|
11
|
+
(platform() === "win32" || executableExists(wrapper));
|
|
12
|
+
checks.push(check("gradle-wrapper", wrapperExists ? "pass" : "fail", wrapperExists
|
|
13
|
+
? inputs.gradleVersion
|
|
14
|
+
? `Gradle ${inputs.gradleVersion}`
|
|
15
|
+
: "found · version unknown"
|
|
16
|
+
: "missing", wrapperExists
|
|
17
|
+
? undefined
|
|
18
|
+
: "lynxship android host init --application-id com.example.myapp"));
|
|
19
|
+
const minimumGradle = requiredGradleForAgp(inputs.agpVersion);
|
|
20
|
+
const gradleCompatible = Boolean(inputs.gradleVersion &&
|
|
21
|
+
minimumGradle &&
|
|
22
|
+
compareVersions(inputs.gradleVersion, minimumGradle) >= 0);
|
|
23
|
+
const gradleStatus = !inputs.gradleVersion || !inputs.agpVersion
|
|
24
|
+
? "warn"
|
|
25
|
+
: gradleCompatible
|
|
26
|
+
? "pass"
|
|
27
|
+
: "fail";
|
|
28
|
+
checks.push(check("agp-gradle-compatibility", gradleStatus, inputs.agpVersion && inputs.gradleVersion
|
|
29
|
+
? `AGP ${inputs.agpVersion} · Gradle ${inputs.gradleVersion}${minimumGradle ? ` · minimum Gradle ${minimumGradle}` : ""}`
|
|
30
|
+
: "AGP or wrapper version not detected", gradleStatus === "fail" && minimumGradle
|
|
31
|
+
? `Update the project wrapper to Gradle ${minimumGradle}; do not use a global Gradle installation`
|
|
32
|
+
: undefined));
|
|
33
|
+
const requiredJava = requiredJavaForAgp(inputs.agpVersion);
|
|
34
|
+
let detectedJava;
|
|
35
|
+
let javaPath;
|
|
36
|
+
for (const candidate of javaCandidates()) {
|
|
37
|
+
const version = await javaVersion(candidate, root);
|
|
38
|
+
if (version !== undefined) {
|
|
39
|
+
detectedJava = version;
|
|
40
|
+
javaPath = candidate;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const javaStatus = detectedJava === undefined
|
|
45
|
+
? "fail"
|
|
46
|
+
: detectedJava >= requiredJava
|
|
47
|
+
? "pass"
|
|
48
|
+
: "fail";
|
|
49
|
+
checks.push(check("java", javaStatus, detectedJava === undefined
|
|
50
|
+
? "missing"
|
|
51
|
+
: `JDK ${detectedJava}${javaPath === "java" ? " · PATH" : ""}`, javaStatus === "fail"
|
|
52
|
+
? `Install/configure JDK ${requiredJava} and set JAVA_HOME, then run lynxship doctor --platform android`
|
|
53
|
+
: undefined));
|
|
54
|
+
const sdkPath = sdkCandidates().find((candidate) => existsSync(candidate));
|
|
55
|
+
const compileSdk = inputs.compileSdk;
|
|
56
|
+
const buildTools = inputs.buildToolsVersion;
|
|
57
|
+
const platformPackage = compileSdk
|
|
58
|
+
? `platforms;android-${compileSdk}`
|
|
59
|
+
: undefined;
|
|
60
|
+
const buildToolsPackage = buildTools
|
|
61
|
+
? `build-tools;${buildTools}`
|
|
62
|
+
: undefined;
|
|
63
|
+
const missingPackages = [
|
|
64
|
+
platformPackage &&
|
|
65
|
+
sdkPath &&
|
|
66
|
+
!existsSync(join(sdkPath, "platforms", `android-${compileSdk}`))
|
|
67
|
+
? platformPackage
|
|
68
|
+
: undefined,
|
|
69
|
+
buildTools &&
|
|
70
|
+
sdkPath &&
|
|
71
|
+
!existsSync(join(sdkPath, "build-tools", buildTools))
|
|
72
|
+
? buildToolsPackage
|
|
73
|
+
: undefined,
|
|
74
|
+
].filter((value) => Boolean(value));
|
|
75
|
+
const sdkStatus = !sdkPath
|
|
76
|
+
? "fail"
|
|
77
|
+
: missingPackages.length
|
|
78
|
+
? "fail"
|
|
79
|
+
: "pass";
|
|
80
|
+
checks.push(check("android-sdk", sdkStatus, !sdkPath
|
|
81
|
+
? "missing · set ANDROID_HOME or ANDROID_SDK_ROOT"
|
|
82
|
+
: missingPackages.length
|
|
83
|
+
? `missing: ${missingPackages.join(", ")}`
|
|
84
|
+
: sdkPath, sdkStatus === "fail"
|
|
85
|
+
? "Install Android Studio or the Android command-line tools, then run lynxship doctor --platform android --fix"
|
|
86
|
+
: undefined));
|
|
87
|
+
const manager = sdkPath ? sdkManagerPath(sdkPath) : undefined;
|
|
88
|
+
checks.push(check("sdkmanager", manager ? "pass" : sdkPath ? "warn" : "fail", manager
|
|
89
|
+
? "available"
|
|
90
|
+
: sdkPath
|
|
91
|
+
? "missing · use Android Studio SDK Manager"
|
|
92
|
+
: "unavailable until Android SDK is installed", manager && missingPackages.length
|
|
93
|
+
? `sdkmanager ${missingPackages.map((value) => `"${value}"`).join(" ")}`
|
|
94
|
+
: undefined));
|
|
95
|
+
const adb = sdkPath ? sdkExecutable(sdkPath, "adb") : undefined;
|
|
96
|
+
checks.push(check("adb", adb || commandExists("adb") ? "pass" : "warn", adb || commandExists("adb")
|
|
97
|
+
? "Android Platform-Tools available"
|
|
98
|
+
: "missing · required for lynxship run/logs", !adb && !commandExists("adb")
|
|
99
|
+
? "Install Android SDK Platform-Tools, then run adb devices"
|
|
100
|
+
: undefined));
|
|
101
|
+
const apksigner = sdkPath ? sdkExecutable(sdkPath, "apksigner") : undefined;
|
|
102
|
+
checks.push(check("apksigner", apksigner || commandExists("apksigner") ? "pass" : "warn", apksigner || commandExists("apksigner")
|
|
103
|
+
? "Android Build Tools available"
|
|
104
|
+
: "missing · required to verify APK signatures", !apksigner && !commandExists("apksigner")
|
|
105
|
+
? "Install Android SDK Build Tools, then run lynxship doctor --platform android"
|
|
106
|
+
: undefined));
|
|
107
|
+
checks.push(check("android-sdk-licenses", sdkPath && existsSync(join(sdkPath, "licenses")) ? "pass" : "warn", sdkPath && existsSync(join(sdkPath, "licenses"))
|
|
108
|
+
? "license directory found"
|
|
109
|
+
: "not verified", sdkPath ? "sdkmanager --licenses" : undefined));
|
|
110
|
+
const failures = checks.filter((item) => item.status === "fail");
|
|
111
|
+
return {
|
|
112
|
+
ok: failures.length === 0,
|
|
113
|
+
checks,
|
|
114
|
+
sdkPath,
|
|
115
|
+
compileSdk,
|
|
116
|
+
buildToolsVersion: buildTools,
|
|
117
|
+
agpVersion: inputs.agpVersion,
|
|
118
|
+
gradleVersion: inputs.gradleVersion,
|
|
119
|
+
javaVersion: detectedJava,
|
|
120
|
+
sdkPackages: missingPackages,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ToolchainCheck, ToolchainCheckStatus } from "./types.js";
|
|
2
|
+
interface AndroidProjectInputs {
|
|
3
|
+
agpVersion?: string;
|
|
4
|
+
compileSdk?: number;
|
|
5
|
+
buildToolsVersion?: string;
|
|
6
|
+
gradleVersion?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function readProjectFiles(root: string): string;
|
|
9
|
+
export declare function readWrapperVersion(root: string): string | undefined;
|
|
10
|
+
export declare function readAndroidInputs(root: string): AndroidProjectInputs;
|
|
11
|
+
export declare function versionParts(version: string): number[];
|
|
12
|
+
export declare function compareVersions(left: string, right: string): number;
|
|
13
|
+
export declare function requiredGradleForAgp(agp?: string): string | undefined;
|
|
14
|
+
export declare function requiredJavaForAgp(agp?: string): number;
|
|
15
|
+
export declare function sdkCandidates(): string[];
|
|
16
|
+
export declare function sdkExecutable(sdkPath: string, name: string): string | undefined;
|
|
17
|
+
export declare function readDirectories(directory: string): string[];
|
|
18
|
+
export declare function sdkManagerPath(sdkPath: string): string | undefined;
|
|
19
|
+
export declare function javaVersion(javaPath: string, cwd: string): Promise<number | undefined>;
|
|
20
|
+
export declare function javaCandidates(): string[];
|
|
21
|
+
export declare function check(name: string, status: ToolchainCheckStatus, value: string, fix?: string): ToolchainCheck;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=probes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probes.d.ts","sourceRoot":"","sources":["../../src/android-toolchain/probes.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvE,UAAU,oBAAoB;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AA2BD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAcrD;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAanE;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,CAqBpE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAEtD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAQnE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAKrE;AAED,wBAAgB,kBAAkB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAKvD;AAED,wBAAgB,aAAa,IAAI,MAAM,EAAE,CAkBxC;AAED,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CASpB;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAM3D;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAelE;AAED,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAU7B;AAED,wBAAgB,cAAc,IAAI,MAAM,EAAE,CA0DzC;AAED,wBAAgB,KAAK,CACnB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,oBAAoB,EAC5B,KAAK,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,MAAM,GACX,cAAc,CAEhB"}
|