@lynxship/cli 0.1.19 → 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.
Files changed (193) hide show
  1. package/README.md +66 -5
  2. package/dist/android/signing.d.ts +16 -0
  3. package/dist/android/signing.d.ts.map +1 -0
  4. package/dist/android/signing.js +131 -0
  5. package/dist/android/types.d.ts +12 -0
  6. package/dist/android/types.d.ts.map +1 -0
  7. package/dist/android/types.js +1 -0
  8. package/dist/android-build.d.ts +4 -13
  9. package/dist/android-build.d.ts.map +1 -1
  10. package/dist/android-build.js +14 -133
  11. package/dist/android-toolchain/inspection.d.ts +3 -0
  12. package/dist/android-toolchain/inspection.d.ts.map +1 -0
  13. package/dist/android-toolchain/inspection.js +122 -0
  14. package/dist/android-toolchain/probes.d.ts +23 -0
  15. package/dist/android-toolchain/probes.d.ts.map +1 -0
  16. package/dist/android-toolchain/probes.js +183 -0
  17. package/dist/android-toolchain/types.d.ts +20 -0
  18. package/dist/android-toolchain/types.d.ts.map +1 -0
  19. package/dist/android-toolchain/types.js +1 -0
  20. package/dist/android-toolchain.d.ts +4 -20
  21. package/dist/android-toolchain.d.ts.map +1 -1
  22. package/dist/android-toolchain.js +4 -300
  23. package/dist/app-config.d.ts +25 -0
  24. package/dist/app-config.d.ts.map +1 -0
  25. package/dist/app-config.js +106 -0
  26. package/dist/bundle-build.d.ts.map +1 -1
  27. package/dist/bundle-build.js +11 -0
  28. package/dist/cli.d.ts +3 -0
  29. package/dist/cli.d.ts.map +1 -0
  30. package/dist/cli.js +66 -0
  31. package/dist/commands/build-execution.d.ts +41 -0
  32. package/dist/commands/build-execution.d.ts.map +1 -0
  33. package/dist/commands/build-execution.js +182 -0
  34. package/dist/commands/configuration.d.ts +19 -0
  35. package/dist/commands/configuration.d.ts.map +1 -0
  36. package/dist/commands/configuration.js +174 -0
  37. package/dist/commands/development.d.ts +22 -0
  38. package/dist/commands/development.d.ts.map +1 -0
  39. package/dist/commands/development.js +132 -0
  40. package/dist/commands/device.d.ts +3 -0
  41. package/dist/commands/device.d.ts.map +1 -0
  42. package/dist/commands/device.js +86 -0
  43. package/dist/commands/doctor.d.ts +18 -0
  44. package/dist/commands/doctor.d.ts.map +1 -0
  45. package/dist/commands/doctor.js +320 -0
  46. package/dist/commands/help.d.ts +2 -0
  47. package/dist/commands/help.d.ts.map +1 -0
  48. package/dist/commands/help.js +118 -0
  49. package/dist/commands/i18n.d.ts +14 -0
  50. package/dist/commands/i18n.d.ts.map +1 -0
  51. package/dist/commands/i18n.js +88 -0
  52. package/dist/commands/logs.d.ts +3 -0
  53. package/dist/commands/logs.d.ts.map +1 -0
  54. package/dist/commands/logs.js +51 -0
  55. package/dist/commands/metadata.d.ts +3 -0
  56. package/dist/commands/metadata.d.ts.map +1 -0
  57. package/dist/commands/metadata.js +45 -0
  58. package/dist/commands/ota.d.ts +28 -0
  59. package/dist/commands/ota.d.ts.map +1 -0
  60. package/dist/commands/ota.js +203 -0
  61. package/dist/commands/project.d.ts +14 -0
  62. package/dist/commands/project.d.ts.map +1 -0
  63. package/dist/commands/project.js +122 -0
  64. package/dist/commands/remote-build.d.ts +35 -0
  65. package/dist/commands/remote-build.d.ts.map +1 -0
  66. package/dist/commands/remote-build.js +73 -0
  67. package/dist/commands/submit.d.ts +27 -0
  68. package/dist/commands/submit.d.ts.map +1 -0
  69. package/dist/commands/submit.js +92 -0
  70. package/dist/ecosystem.d.ts +9 -0
  71. package/dist/ecosystem.d.ts.map +1 -0
  72. package/dist/ecosystem.js +95 -0
  73. package/dist/frameworks.d.ts.map +1 -1
  74. package/dist/frameworks.js +5 -1
  75. package/dist/guidance/catalog.d.ts +3 -0
  76. package/dist/guidance/catalog.d.ts.map +1 -0
  77. package/dist/guidance/catalog.js +504 -0
  78. package/dist/guidance/types.d.ts +10 -0
  79. package/dist/guidance/types.d.ts.map +1 -0
  80. package/dist/guidance/types.js +1 -0
  81. package/dist/guidance.d.ts +2 -9
  82. package/dist/guidance.d.ts.map +1 -1
  83. package/dist/guidance.js +1 -502
  84. package/dist/i18n/discovery.d.ts +14 -0
  85. package/dist/i18n/discovery.d.ts.map +1 -0
  86. package/dist/i18n/discovery.js +106 -0
  87. package/dist/i18n/plan.d.ts +12 -0
  88. package/dist/i18n/plan.d.ts.map +1 -0
  89. package/dist/i18n/plan.js +95 -0
  90. package/dist/i18n/setup.d.ts +11 -0
  91. package/dist/i18n/setup.d.ts.map +1 -0
  92. package/dist/i18n/setup.js +47 -0
  93. package/dist/i18n/types.d.ts +17 -0
  94. package/dist/i18n/types.d.ts.map +1 -0
  95. package/dist/i18n/types.js +1 -0
  96. package/dist/index.d.ts +1 -1
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +4 -2069
  99. package/dist/ios/assets.d.ts +19 -0
  100. package/dist/ios/assets.d.ts.map +1 -0
  101. package/dist/ios/assets.js +128 -0
  102. package/dist/ios/production-build.d.ts +6 -0
  103. package/dist/ios/production-build.d.ts.map +1 -0
  104. package/dist/ios/production-build.js +200 -0
  105. package/dist/ios/project.d.ts +4 -0
  106. package/dist/ios/project.d.ts.map +1 -0
  107. package/dist/ios/project.js +43 -0
  108. package/dist/ios/simulator-build.d.ts +8 -0
  109. package/dist/ios/simulator-build.d.ts.map +1 -0
  110. package/dist/ios/simulator-build.js +188 -0
  111. package/dist/ios/simulator.d.ts +5 -0
  112. package/dist/ios/simulator.d.ts.map +1 -0
  113. package/dist/ios/simulator.js +60 -0
  114. package/dist/ios/types.d.ts +14 -0
  115. package/dist/ios/types.d.ts.map +1 -0
  116. package/dist/ios/types.js +1 -0
  117. package/dist/ios-build.d.ts +4 -37
  118. package/dist/ios-build.d.ts.map +1 -1
  119. package/dist/ios-build.js +3 -575
  120. package/dist/ios-host.d.ts.map +1 -1
  121. package/dist/ios-toolchain/inspection.d.ts +4 -0
  122. package/dist/ios-toolchain/inspection.d.ts.map +1 -0
  123. package/dist/ios-toolchain/inspection.js +211 -0
  124. package/dist/ios-toolchain/probes.d.ts +24 -0
  125. package/dist/ios-toolchain/probes.d.ts.map +1 -0
  126. package/dist/ios-toolchain/probes.js +127 -0
  127. package/dist/ios-toolchain/types.d.ts +17 -0
  128. package/dist/ios-toolchain/types.d.ts.map +1 -0
  129. package/dist/ios-toolchain/types.js +1 -0
  130. package/dist/ios-toolchain.d.ts +3 -18
  131. package/dist/ios-toolchain.d.ts.map +1 -1
  132. package/dist/ios-toolchain.js +1 -333
  133. package/dist/plugins/contracts.d.ts +28 -0
  134. package/dist/plugins/contracts.d.ts.map +1 -0
  135. package/dist/plugins/contracts.js +1 -0
  136. package/dist/plugins/discovery.d.ts +19 -0
  137. package/dist/plugins/discovery.d.ts.map +1 -0
  138. package/dist/plugins/discovery.js +250 -0
  139. package/dist/plugins/native-operations.d.ts +28 -0
  140. package/dist/plugins/native-operations.d.ts.map +1 -0
  141. package/dist/plugins/native-operations.js +217 -0
  142. package/dist/plugins/operations.d.ts +9 -0
  143. package/dist/plugins/operations.d.ts.map +1 -0
  144. package/dist/plugins/operations.js +76 -0
  145. package/dist/plugins/validation.d.ts +9 -0
  146. package/dist/plugins/validation.d.ts.map +1 -0
  147. package/dist/plugins/validation.js +52 -0
  148. package/dist/plugins.d.ts +3 -34
  149. package/dist/plugins.d.ts.map +1 -1
  150. package/dist/plugins.js +2 -595
  151. package/dist/process-runner.d.ts +4 -0
  152. package/dist/process-runner.d.ts.map +1 -1
  153. package/dist/process-runner.js +14 -0
  154. package/dist/remote.d.ts +22 -1
  155. package/dist/remote.d.ts.map +1 -1
  156. package/dist/remote.js +141 -2
  157. package/dist/rspeedy-app-config.d.ts +4 -0
  158. package/dist/rspeedy-app-config.d.ts.map +1 -0
  159. package/dist/rspeedy-app-config.js +32 -0
  160. package/dist/runtime/args.d.ts +3 -0
  161. package/dist/runtime/args.d.ts.map +1 -0
  162. package/dist/runtime/args.js +11 -0
  163. package/dist/runtime/build-commands.d.ts +3 -0
  164. package/dist/runtime/build-commands.d.ts.map +1 -0
  165. package/dist/runtime/build-commands.js +282 -0
  166. package/dist/runtime/context.d.ts +63 -0
  167. package/dist/runtime/context.d.ts.map +1 -0
  168. package/dist/runtime/context.js +179 -0
  169. package/dist/runtime/early-commands.d.ts +4 -0
  170. package/dist/runtime/early-commands.d.ts.map +1 -0
  171. package/dist/runtime/early-commands.js +284 -0
  172. package/dist/runtime/project.d.ts +8 -0
  173. package/dist/runtime/project.d.ts.map +1 -0
  174. package/dist/runtime/project.js +38 -0
  175. package/dist/runtime/state.d.ts +47 -0
  176. package/dist/runtime/state.d.ts.map +1 -0
  177. package/dist/runtime/state.js +33 -0
  178. package/dist/secure-store/backends.d.ts +20 -0
  179. package/dist/secure-store/backends.d.ts.map +1 -0
  180. package/dist/secure-store/backends.js +248 -0
  181. package/dist/secure-store/types.d.ts +27 -0
  182. package/dist/secure-store/types.d.ts.map +1 -0
  183. package/dist/secure-store/types.js +1 -0
  184. package/dist/secure-store.d.ts +2 -26
  185. package/dist/secure-store.d.ts.map +1 -1
  186. package/dist/secure-store.js +3 -245
  187. package/dist/ui/components.d.ts.map +1 -1
  188. package/dist/ui/components.js +2 -1
  189. package/package.json +7 -4
  190. package/templates/android-host/app/build.gradle +1 -0
  191. package/templates/android-host/app/src/main/java/template/MainActivity.java +98 -3
  192. package/templates/ios-host/__IOS_TARGET_NAME__/DemoLynxProvider.swift +26 -16
  193. package/templates/ios-host/__IOS_TARGET_NAME__/ViewController.swift +107 -19
@@ -0,0 +1,118 @@
1
+ export function helpText() {
2
+ return `lynxship <command> [options]
3
+
4
+ Commands:
5
+ init Initialize or link a LynxShip project
6
+ doctor Check the local toolchain and project
7
+ dev Run Rspeedy dev with Lynx Explorer QR/HMR
8
+ preview Preview the production Lynx bundle locally
9
+ inspect Inspect Rspeedy/Rspack configuration
10
+ profile Build with Rspack profiling enabled
11
+ devtool doctor Check Lynx DevTool integration and dev runtime
12
+ trace doctor Check Lynx Trace prerequisites
13
+ recorder doctor Check Lynx Recorder prerequisites
14
+ plugin list List project plugins and their capabilities
15
+ plugin doctor Validate plugin packages without modifying native files
16
+ plugin apply Apply project plugin native changes for a platform
17
+ plugin apply --dry-run Preview plugin native changes without writing files
18
+ ecosystem Show first-party LynxShip package capabilities
19
+ i18n setup Install and wire i18next, Intl polyfills and locale persistence
20
+ autolink check Check Lynx native-library Autolink wiring
21
+ autolink codegen Run the project's Native Module codegen script
22
+ ota doctor Check native OTA host integration
23
+ run Install an artifact on an Android/iOS/HarmonyOS target
24
+ logs Stream Android/iOS/HarmonyOS native logs
25
+ build create Create a local/cloud build job
26
+ build list List build jobs
27
+ build status <id> Show one build job (add --remote for hosted builds)
28
+ build cancel <id> Cancel a build job (add --remote for hosted builds)
29
+ build retry <id> Retry a failed build job (add --remote for hosted builds)
30
+ build all Build Android, iOS, HarmonyOS, Web and Desktop
31
+ submit Submit the latest successful build
32
+ update Upload and publish a signed OTA update
33
+ update rollback Roll back an OTA channel to a previous release
34
+ rollback Alias for update rollback
35
+ self-host init Generate local self-host credentials
36
+ storage configure Configure Cloudflare R2 and encrypted R2 credentials
37
+ android host init Create a minimal official Lynx Android host
38
+ ios host init Create a minimal official Lynx iOS/Xcode host
39
+ android configure Configure or generate encrypted Android signing credentials
40
+ store configure Configure Google Play or App Store Connect submission
41
+
42
+ Build options:
43
+ --platform <p> Target android, ios, harmony, web, desktop or all (default: android)
44
+ --profile <name> Build profile (default: production; simulator uses simulator)
45
+ --no-wait Queue the build without executing it locally
46
+ --remote Upload a verified source snapshot and queue a hosted build
47
+ --no-upload Keep the artifact local and skip R2 (CI verification)
48
+ --simulator Build and install an iOS Simulator .app locally
49
+ --device <id> Select the iOS Simulator device for a simulator build
50
+ --autostart Open and launch the iOS Simulator app after install
51
+ --no-autostart Install the Simulator app without launching it
52
+ --allow-unsigned Allow an unsigned Desktop artifact only with --no-upload (local tests)
53
+ --local Use the contract-only build path for tests
54
+
55
+ Submit options:
56
+ --platform <p> Target android or ios (default: android)
57
+ --latest Submit the latest successful build
58
+ --local Use the mock submission provider for tests
59
+
60
+ Update options:
61
+ --platform <p> Target android or ios (default: android)
62
+ --bundle <path[,path]> Lynx bundle/assets (default: discover dist/*.lynx.bundle)
63
+ --message <text> Release message
64
+ --local Create a local contract-only update for tests
65
+ --policy-approval-id Required for an iOS OTA release
66
+
67
+ Rollback options:
68
+ --platform <p> Target android or ios (default: android)
69
+ --release-id <id> Previously published compatible release
70
+ --reason <text> Required audit reason for the rollback
71
+ --local Roll back local contract state for tests
72
+
73
+ Device and diagnostics options:
74
+ doctor --platform <p> Check the local toolchain (default: android)
75
+ doctor --fix Install missing Android SDK packages after confirmation
76
+ autolink check --platform <p>
77
+ Check native-library wiring (default: android)
78
+ ota doctor --platform <p>
79
+ Check OTA host integration (default: android)
80
+ run --artifact <path> Install a specific APK, IPA, app or signed HAP
81
+ run --device <id> Select an Android, iOS or HarmonyOS device
82
+ run --simulator Install an iOS .app with simctl
83
+ run --launch Launch the installed iOS Simulator app after install
84
+ logs --device <id> Select the device or simulator for native logs
85
+
86
+ i18n setup options:
87
+ --dry-run Show planned dependencies/files without changing the project
88
+ --entry <path> Explicit Lynx entry file when it cannot be detected
89
+ --intl auto|all|<list> Detect Intl use, install only required polyfills, or choose explicitly
90
+ --locales <list> Comma-separated locale codes for static FormatJS locale data
91
+ --no-persistence Do not install the LynxShip device-storage bridge
92
+
93
+ Global options:
94
+ --json Emit one stable JSON result/error object
95
+ --quiet Print only the final machine-relevant result
96
+ --verbose Include extra operational context
97
+ --no-color Disable ANSI colors
98
+ --non-interactive Never prompt; fail on missing inputs
99
+ --banner Show the Braille LynxShip logo in a TTY
100
+ --project-dir <path> Use a LynxShip project from any working directory
101
+ --project-id <id> Project ID used by init
102
+ --application-id <id> Android package/application ID for host init
103
+ --bundle-identifier <id> iOS bundle identifier for host init
104
+ --icon <path> 1024x1024 PNG app icon for iOS host init
105
+ --library-dir <path> Native library directory for autolink codegen
106
+ --simulator Build/install an iOS .app or install one with simctl
107
+ --help Show this complete command reference
108
+
109
+ Miso compiler profiles:
110
+ ghcjs (default) Use the official Miso GHC/Nix workflow
111
+ microhs Use a pinned MicroHs binary and a real adapter command
112
+
113
+ MicroHs is opt-in. Configure build.<profile>.miso.microhs with either a
114
+ binary path or a pinned manifest, plus an adapter command that writes the
115
+ configured main.lynx.bundle. It is not a drop-in GHCJS replacement.
116
+
117
+ Node support: Node 22/24 LTS or Node 26 Current. Use Node 24 LTS for production.`;
118
+ }
@@ -0,0 +1,14 @@
1
+ import type { BoxRow, CliUi } from "../ui/index.js";
2
+ export interface I18nCommandContext {
3
+ root: string;
4
+ args: string[];
5
+ ui: CliUi;
6
+ flag: (name: string, fallback?: string | null) => string | null;
7
+ printValue: (value: unknown, view?: {
8
+ title: string;
9
+ rows: BoxRow[];
10
+ done: string;
11
+ }) => void;
12
+ }
13
+ export declare function runI18nCommand(context: I18nCommandContext): Promise<void>;
14
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/commands/i18n.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAEpD,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,MAAM,GAAG,IAAI,CAAC;IAChE,UAAU,EAAE,CACV,KAAK,EAAE,OAAO,EACd,IAAI,CAAC,EAAE;QACL,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,KACE,IAAI,CAAC;CACX;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAoDf"}
@@ -0,0 +1,88 @@
1
+ import { assert } from "@lynxship/contracts";
2
+ import { setupI18n } from "../i18n/setup.js";
3
+ export async function runI18nCommand(context) {
4
+ const subcommand = context.args.shift() ?? "setup";
5
+ assert(subcommand === "setup", "CLI_I18N_COMMAND", "Use `lynxship i18n setup`.");
6
+ const capabilities = parseCapabilities(context.flag("--intl"));
7
+ const locales = parseList(context.flag("--locales"));
8
+ const result = await setupI18n({
9
+ root: context.root,
10
+ entry: context.flag("--entry") ?? undefined,
11
+ capabilities,
12
+ locales,
13
+ persistence: !context.args.includes("--no-persistence"),
14
+ dryRun: context.args.includes("--dry-run"),
15
+ onOutput: (line) => context.ui.debug(line),
16
+ });
17
+ context.printValue(result, {
18
+ title: `LynxShip i18n · ${result.status}`,
19
+ rows: [
20
+ { label: "Entry", value: result.entryFile, valueColor: "muted" },
21
+ {
22
+ label: "Locales",
23
+ value: result.locales.join(", "),
24
+ valueColor: "blue",
25
+ },
26
+ {
27
+ label: "Intl",
28
+ value: result.capabilities.join(", "),
29
+ valueColor: "purple",
30
+ },
31
+ {
32
+ label: "Dependencies",
33
+ value: result.packages.length
34
+ ? result.packages.join(", ")
35
+ : "already installed",
36
+ valueColor: result.packages.length ? "yellow" : "green",
37
+ },
38
+ {
39
+ label: "Persistence",
40
+ value: result.persistence
41
+ ? "automatic when host storage is linked"
42
+ : "disabled",
43
+ valueColor: result.persistence ? "green" : "muted",
44
+ },
45
+ ],
46
+ done: result.status === "planned"
47
+ ? "Dry run complete; no dependency or source file was changed."
48
+ : "i18n setup is idempotent; rebuild the Lynx bundle after dependency installation.",
49
+ });
50
+ }
51
+ function parseList(value) {
52
+ if (!value || value === "auto")
53
+ return undefined;
54
+ if (value === "all")
55
+ return [
56
+ "PluralRules",
57
+ "NumberFormat",
58
+ "DateTimeFormat",
59
+ "RelativeTimeFormat",
60
+ "ListFormat",
61
+ "DisplayNames",
62
+ "Segmenter",
63
+ "DurationFormat",
64
+ ];
65
+ return value
66
+ .split(",")
67
+ .map((item) => item.trim())
68
+ .filter(Boolean);
69
+ }
70
+ function parseCapabilities(value) {
71
+ const values = parseList(value);
72
+ if (!values)
73
+ return undefined;
74
+ const allowed = new Set([
75
+ "PluralRules",
76
+ "NumberFormat",
77
+ "DateTimeFormat",
78
+ "RelativeTimeFormat",
79
+ "ListFormat",
80
+ "DisplayNames",
81
+ "Locale",
82
+ "getCanonicalLocales",
83
+ "Segmenter",
84
+ "DurationFormat",
85
+ ]);
86
+ assert(values.every((item) => allowed.has(item)), "CLI_I18N_CAPABILITY", `Unknown Intl capability. Use one of: ${[...allowed].join(", ")}`);
87
+ return values;
88
+ }
@@ -0,0 +1,3 @@
1
+ import type { DevelopmentCommandContext } from "./development.js";
2
+ export declare function streamNativeLogs(context: DevelopmentCommandContext): Promise<void>;
3
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../src/commands/logs.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAElE,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,yBAAyB,GACjC,OAAO,CAAC,IAAI,CAAC,CA2Ef"}
@@ -0,0 +1,51 @@
1
+ import { assert } from "@lynxship/contracts";
2
+ import { commandExists, runProcess } from "../process-runner.js";
3
+ import { platformValue } from "../config.js";
4
+ export async function streamNativeLogs(context) {
5
+ const platform = platformValue(context.flag("--platform", "android"));
6
+ const device = context.flag("--device", platform === "ios" ? "booted" : undefined);
7
+ if (platform === "android") {
8
+ assert(commandExists("adb"), "ANDROID_ADB_REQUIRED", "adb was not found in PATH");
9
+ await runProcess("adb", device ? ["-s", device, "logcat"] : ["logcat"], {
10
+ cwd: context.root,
11
+ quiet: context.json,
12
+ onOutput: (line) => context.ui.info(`│ ${line}`),
13
+ });
14
+ }
15
+ else if (platform === "ios") {
16
+ assert(process.platform === "darwin", "IOS_MACOS_REQUIRED", "iOS logs require macOS");
17
+ assert(commandExists("xcrun"), "IOS_XCRUN_REQUIRED", "xcrun was not found in PATH");
18
+ assert(device !== "booted", "IOS_DEVICE_LOGS_UNSUPPORTED", "Use --device <simulator-identifier> for iOS simulator logs.");
19
+ await runProcess("xcrun", [
20
+ "simctl",
21
+ "spawn",
22
+ device ?? "booted",
23
+ "log",
24
+ "stream",
25
+ "--style",
26
+ "compact",
27
+ "--level",
28
+ "debug",
29
+ ], {
30
+ cwd: context.root,
31
+ quiet: context.json,
32
+ onOutput: (line) => context.ui.info(`│ ${line}`),
33
+ });
34
+ }
35
+ else if (platform === "harmony") {
36
+ assert(commandExists("hdc"), "HARMONY_HDC_REQUIRED", "hdc was not found in PATH. Install the OpenHarmony SDK platform tools before streaming logs.");
37
+ const argsForHdc = device
38
+ ? ["-t", device, "shell", "hilog"]
39
+ : ["shell", "hilog"];
40
+ await runProcess("hdc", argsForHdc, {
41
+ cwd: context.root,
42
+ quiet: context.json,
43
+ onOutput: (line) => context.ui.info(`│ ${line}`),
44
+ });
45
+ }
46
+ else {
47
+ assert(false, "TARGET_LOGS_UNSUPPORTED", platform === "web"
48
+ ? "Web logs are emitted by the browser/runtime console; use lynxship dev or the project's Web DevTools."
49
+ : "Desktop logs are emitted by the packaged runtime; use the target OS logging tools.");
50
+ }
51
+ }
@@ -0,0 +1,3 @@
1
+ export declare function commandTitle(command: string): string;
2
+ export declare function exitCode(error: unknown): number;
3
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/commands/metadata.ts"],"names":[],"mappings":"AAyBA,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAU/C"}
@@ -0,0 +1,45 @@
1
+ const COMMAND_TITLES = {
2
+ init: "Initialize project",
3
+ doctor: "Environment doctor",
4
+ dev: "Rspeedy development",
5
+ preview: "Rspeedy preview",
6
+ inspect: "Rspeedy inspection",
7
+ profile: "Rspeedy profiling",
8
+ autolink: "Lynx Autolink",
9
+ run: "Run on device",
10
+ logs: "Native logs",
11
+ ota: "OTA diagnostics",
12
+ build: "Cloud Build",
13
+ submit: "Store Submission",
14
+ update: "OTA Update",
15
+ rollback: "OTA Rollback",
16
+ "self-host": "Self-host setup",
17
+ storage: "Cloudflare R2 setup",
18
+ android: "Android signing setup",
19
+ store: "App store submission setup",
20
+ devtool: "Lynx DevTool diagnostics",
21
+ trace: "Lynx Trace diagnostics",
22
+ recorder: "Lynx Recorder diagnostics",
23
+ plugin: "LynxShip plugins",
24
+ };
25
+ export function commandTitle(command) {
26
+ return COMMAND_TITLES[command] ?? command;
27
+ }
28
+ export function exitCode(error) {
29
+ const code = error.code;
30
+ if (code?.startsWith("CLI_") || code?.startsWith("CONFIG_"))
31
+ return 2;
32
+ if (code === "BUILD_SIGNING_REQUIRED")
33
+ return 2;
34
+ if (code === "DESKTOP_SIGNING_REQUIRED")
35
+ return 2;
36
+ if (code?.startsWith("AUTH_"))
37
+ return 4;
38
+ if (code?.startsWith("BUILD_"))
39
+ return 5;
40
+ if (code?.startsWith("SUBMISSION_"))
41
+ return 6;
42
+ if (code?.startsWith("OTA_"))
43
+ return 7;
44
+ return 1;
45
+ }
@@ -0,0 +1,28 @@
1
+ import { type Platform } from "@lynxship/contracts";
2
+ import { type CliState } from "../runtime/state.js";
3
+ import type { BuildOrchestrator } from "@lynxship/build-orchestrator";
4
+ import type { JsonRepository } from "@lynxship/db";
5
+ import type { SubmissionService } from "@lynxship/submit";
6
+ import { type LynxShipConfig } from "../config.js";
7
+ import type { BoxRow, CliUi } from "../ui/index.js";
8
+ export interface OtaCommandContext {
9
+ root: string;
10
+ args: string[];
11
+ ui: CliUi;
12
+ flag: (name: string, fallback?: string | null) => string | null;
13
+ printValue: (value: unknown, view?: {
14
+ title: string;
15
+ rows: BoxRow[];
16
+ done: string;
17
+ }) => void;
18
+ requireOperationalConfiguration: (platform: Platform) => Promise<void>;
19
+ requireR2Configuration: () => Promise<void>;
20
+ configuredProjectId: (config: LynxShipConfig) => string;
21
+ mobilePlatformValue: (value: string) => "android" | "ios";
22
+ state: CliState;
23
+ repository: JsonRepository<CliState>;
24
+ builds: BuildOrchestrator;
25
+ submissions: SubmissionService;
26
+ }
27
+ export declare function runOtaCommand(context: OtaCommandContext, command: string): Promise<void>;
28
+ //# sourceMappingURL=ota.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ota.d.ts","sourceRoot":"","sources":["../../src/commands/ota.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAc9E,OAAO,EAA8B,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAChF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAc,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAEpD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,MAAM,GAAG,IAAI,CAAC;IAChE,UAAU,EAAE,CACV,KAAK,EAAE,OAAO,EACd,IAAI,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,KACnD,IAAI,CAAC;IACV,+BAA+B,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,sBAAsB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,mBAAmB,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,MAAM,CAAC;IACxD,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,SAAS,GAAG,KAAK,CAAC;IAC1D,KAAK,EAAE,QAAQ,CAAC;IAChB,UAAU,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,WAAW,EAAE,iBAAiB,CAAC;CAChC;AAED,wBAAsB,aAAa,CACjC,OAAO,EAAE,iBAAiB,EAC1B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CA+Pf"}
@@ -0,0 +1,203 @@
1
+ import { assert, createId, sha256 } from "@lynxship/contracts";
2
+ import { createSigningKey, signManifest } from "@lynxship/signing";
3
+ import { fetchOtaPublicKey, publishOtaRelease, rollbackOtaRelease, } from "../remote.js";
4
+ import { uploadR2Artifact } from "../r2.js";
5
+ import { assertCompatibleBinaryBuild, inspectRuntimeFingerprint, } from "../runtime-fingerprint.js";
6
+ import { otaAssetName, otaAssetPaths } from "../ota-assets.js";
7
+ import { exists } from "../runtime/project.js";
8
+ import { saveState } from "../runtime/state.js";
9
+ import { loadConfig } from "../config.js";
10
+ export async function runOtaCommand(context, command) {
11
+ const { root, args, ui, flag, printValue, requireOperationalConfiguration, requireR2Configuration, configuredProjectId, mobilePlatformValue, state, repository, builds, submissions, } = context;
12
+ if (command === "update" && args[0] !== "rollback") {
13
+ const config = await loadConfig(root);
14
+ const platform = mobilePlatformValue(flag("--platform", "android"));
15
+ await requireOperationalConfiguration(platform);
16
+ const projectId = configuredProjectId(config);
17
+ const localMode = args.includes("--local") || process.env.LYNXSHIP_SUBMIT_MODE === "mock";
18
+ const explicitBundles = flag("--bundle");
19
+ const runtime = await inspectRuntimeFingerprint(root, platform, config);
20
+ const progress = ui.progress("Sign manifest");
21
+ try {
22
+ if (localMode) {
23
+ const key = state.signingKey ?? createSigningKey();
24
+ const data = flag("--bundle", "local-bundle");
25
+ const manifest = {
26
+ protocolVersion: 1,
27
+ projectId,
28
+ channel: config.update?.channel ?? "production",
29
+ platform,
30
+ runtimeVersion: runtime.value,
31
+ sequence: state.releases.length + 1,
32
+ keyId: key.keyId,
33
+ assets: [
34
+ {
35
+ path: "main.js",
36
+ hash: sha256(data),
37
+ size: Buffer.byteLength(data),
38
+ },
39
+ ],
40
+ };
41
+ const release = {
42
+ id: createId("rel"),
43
+ manifest,
44
+ signature: signManifest(manifest, key.privateKey),
45
+ message: flag("--message", "local update"),
46
+ createdAt: new Date().toISOString(),
47
+ };
48
+ state.releases.push(release);
49
+ await saveState(state, repository, builds, submissions);
50
+ progress.update(100);
51
+ printValue(release, {
52
+ title: "OTA update published locally",
53
+ rows: [
54
+ { label: "Release ID", value: release.id, valueColor: "purple" },
55
+ {
56
+ label: "Platform",
57
+ value: release.manifest.platform,
58
+ valueColor: "blue",
59
+ },
60
+ {
61
+ label: "Signature",
62
+ value: "Ed25519 signed",
63
+ valueColor: "muted",
64
+ },
65
+ ],
66
+ done: "Local update created. Use a real API and bundle for devices.",
67
+ });
68
+ return;
69
+ }
70
+ const bundlePaths = await otaAssetPaths(root, explicitBundles);
71
+ for (const bundlePath of bundlePaths)
72
+ assert(await exists(bundlePath), "OTA_BUNDLE_REQUIRED", `Bundle not found: ${bundlePath}. Build the Lynx bundle first or pass --bundle.`);
73
+ assertCompatibleBinaryBuild(builds, platform, runtime.value);
74
+ const releaseId = createId("ota");
75
+ progress.update(25, `Uploading ${bundlePaths.length} Lynx asset(s) to Cloudflare R2…`);
76
+ const uploadedAssets = [];
77
+ for (const [index, bundlePath] of bundlePaths.entries()) {
78
+ const uploaded = await uploadR2Artifact(root, projectId, releaseId, bundlePath, "application/octet-stream", otaAssetName(root, bundlePath));
79
+ uploadedAssets.push({
80
+ path: otaAssetName(root, bundlePath),
81
+ hash: uploaded.hash,
82
+ size: uploaded.size,
83
+ url: uploaded.url,
84
+ });
85
+ progress.update(25 + Math.round(((index + 1) / bundlePaths.length) * 35), `Uploaded ${index + 1}/${bundlePaths.length} Lynx asset(s)…`);
86
+ }
87
+ progress.update(65, "Publishing signed OTA release through LynxShip API…");
88
+ const remoteRelease = (await publishOtaRelease(config, state, {
89
+ projectId,
90
+ organizationId: "local_org",
91
+ channel: config.update?.channel ?? "production",
92
+ platform,
93
+ runtimeVersion: runtime.value,
94
+ assets: uploadedAssets,
95
+ message: flag("--message", "OTA update"),
96
+ rollout: config.update?.rollout?.defaultPercentage ?? 100,
97
+ policyApprovalId: flag("--policy-approval-id"),
98
+ }));
99
+ const publicKey = await fetchOtaPublicKey(config);
100
+ state.releases.push(remoteRelease);
101
+ await saveState(state, repository, builds, submissions);
102
+ progress.update(100, "OTA release signed and published");
103
+ printValue({ ...remoteRelease, signingKey: publicKey }, {
104
+ title: "OTA update published",
105
+ rows: [
106
+ {
107
+ label: "Release ID",
108
+ value: remoteRelease.id,
109
+ valueColor: "purple",
110
+ },
111
+ {
112
+ label: "Platform",
113
+ value: remoteRelease.manifest.platform,
114
+ valueColor: "blue",
115
+ },
116
+ { label: "Bundle", value: "Cloudflare R2", valueColor: "orange" },
117
+ {
118
+ label: "Signature",
119
+ value: "Ed25519 signed by API",
120
+ valueColor: "green",
121
+ },
122
+ ],
123
+ done: "Devices can check and install this compatible OTA release.",
124
+ });
125
+ }
126
+ finally {
127
+ progress.stop();
128
+ }
129
+ return;
130
+ }
131
+ if (command === "rollback" ||
132
+ (command === "update" && args[0] === "rollback")) {
133
+ if (command === "update")
134
+ args.shift();
135
+ const config = await loadConfig(root);
136
+ const platform = mobilePlatformValue(flag("--platform", "android"));
137
+ const releaseId = flag("--release-id");
138
+ const reason = flag("--reason");
139
+ const channel = config.update?.channel ?? "production";
140
+ const localMode = args.includes("--local");
141
+ assert(Boolean(releaseId && releaseId !== "true"), "ROLLBACK_RELEASE_REQUIRED", "Pass the release to restore with `--release-id <id>`.");
142
+ assert(Boolean(reason?.trim()) && reason !== "true", "ROLLBACK_REASON_REQUIRED", 'Pass an audit reason with `--reason "..."`.');
143
+ await requireR2Configuration();
144
+ const progress = ui.progress("OTA rollback");
145
+ try {
146
+ progress.update(10, `Selecting ${releaseId} for ${channel}…`);
147
+ if (localMode) {
148
+ const release = state.releases.find((candidate) => candidate.id === releaseId &&
149
+ candidate.manifest.channel === channel &&
150
+ candidate.manifest.platform === platform);
151
+ assert(release, "RELEASE_NOT_FOUND", `Local release ${releaseId} was not found in channel ${channel} for ${platform}.`);
152
+ state.lastRollback = {
153
+ releaseId: release.id,
154
+ reason: reason,
155
+ at: new Date().toISOString(),
156
+ };
157
+ await saveState(state, repository, builds, submissions);
158
+ progress.update(100, "Local OTA channel rolled back");
159
+ printValue({ status: "rolled_back", release, rollback: state.lastRollback }, {
160
+ title: "OTA rollback",
161
+ rows: [
162
+ { label: "Release ID", value: release.id, valueColor: "purple" },
163
+ { label: "Platform", value: platform, valueColor: "blue" },
164
+ { label: "Channel", value: channel, valueColor: "orange" },
165
+ { label: "Reason", value: reason, valueColor: "muted" },
166
+ ],
167
+ done: "Local OTA channel now points to the selected release.",
168
+ });
169
+ return;
170
+ }
171
+ progress.update(45, "Requesting rollback through LynxShip API…");
172
+ const release = (await rollbackOtaRelease(config, state, {
173
+ projectId: configuredProjectId(config),
174
+ channel,
175
+ platform,
176
+ releaseId: releaseId,
177
+ reason: reason,
178
+ }));
179
+ state.lastRollback = {
180
+ releaseId: release.id,
181
+ reason: reason,
182
+ at: new Date().toISOString(),
183
+ };
184
+ await saveState(state, repository, builds, submissions);
185
+ progress.update(100, "OTA channel rolled back");
186
+ printValue({ status: "rolled_back", release, rollback: state.lastRollback }, {
187
+ title: "OTA rollback",
188
+ rows: [
189
+ { label: "Release ID", value: release.id, valueColor: "purple" },
190
+ { label: "Platform", value: platform, valueColor: "blue" },
191
+ { label: "Channel", value: channel, valueColor: "orange" },
192
+ { label: "Reason", value: reason, valueColor: "muted" },
193
+ ],
194
+ done: "Devices will receive the selected compatible release on their next OTA check.",
195
+ });
196
+ }
197
+ finally {
198
+ progress.stop();
199
+ }
200
+ return;
201
+ }
202
+ assert(command === "build", "CLI_COMMAND", `Unknown command: ${command}`);
203
+ }
@@ -0,0 +1,14 @@
1
+ import { type Platform } from "@lynxship/contracts";
2
+ import { type LynxShipConfig } from "../config.js";
3
+ import type { CliUi } from "../ui/index.js";
4
+ export interface ProjectCommandContext {
5
+ root: string;
6
+ ui: CliUi;
7
+ flag: (name: string, fallback?: string | null) => string | null;
8
+ prompt: (label: string, fallback: string) => Promise<string>;
9
+ }
10
+ export declare function looksLikeLynxProject(context: ProjectCommandContext): Promise<boolean>;
11
+ export declare function initializeProject(context: ProjectCommandContext): Promise<string>;
12
+ export declare function initializeBuildProject(context: ProjectCommandContext): Promise<void>;
13
+ export declare function ensureNativeHostForBuild(context: ProjectCommandContext, platform: Platform, profile: string, config: LynxShipConfig): Promise<void>;
14
+ //# sourceMappingURL=project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../../src/commands/project.ts"],"names":[],"mappings":"AAGA,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAe5D,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,cAAc,CAAC;AAKtB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAG5C,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,MAAM,GAAG,IAAI,CAAC;IAChE,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CAC9D;AAED,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,OAAO,CAAC,CA2ClB;AAED,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,qBAAqB,EAC9B,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,IAAI,CAAC,CA0Gf"}