@lynxship/cli 0.1.16 → 0.1.19

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/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import { assert, createId, sha256, } from "@lynxship/contracts";
10
10
  import { createSigningKey, signManifest, } from "@lynxship/signing";
11
11
  import { AppStoreConnectApiProvider, GooglePlayApiProvider, SubmissionService, } from "@lynxship/submit";
12
12
  import { DEFAULT_CONFIG, loadConfig, platformValue, resolveProfile, } from "./config.js";
13
+ import { microHsHostTriple } from "@lynxship/microhs";
13
14
  import { hasAndroidHost, isSupportedAndroidPlatform, runRealAndroidBuild, } from "./android-build.js";
14
15
  import { initializeAndroidHost, suggestedAndroidApplicationId, } from "./android-host.js";
15
16
  import { initializeIosHost, suggestedIosBundleIdentifier } from "./ios-host.js";
@@ -35,6 +36,7 @@ import { hasHarmonyHost, runRealHarmonyBuild } from "./harmony-build.js";
35
36
  import { hasWebConfiguration, runRealWebBuild } from "./web-build.js";
36
37
  import { hasDesktopHost, runRealDesktopBuild } from "./desktop-build.js";
37
38
  import { extractDevServerUrl, shouldPrintDevServerQr } from "./dev-qr.js";
39
+ import { applyProjectPlugins, inspectProjectPlugins } from "./plugins.js";
38
40
  import { inspectDesktopTarget, inspectHarmonyTarget, inspectWebTarget, } from "./target-toolchain.js";
39
41
  const rawArgs = process.argv.slice(2);
40
42
  const args = [...rawArgs];
@@ -216,7 +218,20 @@ async function executeBuild(options, showResult = true) {
216
218
  // Host initialization can write lynxship.json (for example, the generated
217
219
  // iOS scheme). Always read it again before resolving the build profile so a
218
220
  // first build uses the host that was just created in the same invocation.
219
- const effectiveConfig = wait && !local ? await loadConfig(root) : config;
221
+ const loadedConfig = wait && !local ? await loadConfig(root) : config;
222
+ const pluginApplication = wait && !local
223
+ ? await applyProjectPlugins(root, loadedConfig, { platform, profile })
224
+ : {
225
+ config: loadedConfig,
226
+ report: { configured: 0, plugins: [] },
227
+ applied: [],
228
+ templates: [],
229
+ cloud: [],
230
+ build: [],
231
+ changes: [],
232
+ autolink: [],
233
+ };
234
+ const effectiveConfig = pluginApplication.config;
220
235
  if (platform === "android" || platform === "ios")
221
236
  await requireAutolinkReady(root, platform);
222
237
  const runtime = await inspectRuntimeFingerprint(root, platform, effectiveConfig);
@@ -418,6 +433,7 @@ function commandTitle(command) {
418
433
  devtool: "Lynx DevTool diagnostics",
419
434
  trace: "Lynx Trace diagnostics",
420
435
  recorder: "Lynx Recorder diagnostics",
436
+ plugin: "LynxShip plugins",
421
437
  }[command] ?? command);
422
438
  }
423
439
  function helpText() {
@@ -433,6 +449,10 @@ Commands:
433
449
  devtool doctor Check Lynx DevTool integration and dev runtime
434
450
  trace doctor Check Lynx Trace prerequisites
435
451
  recorder doctor Check Lynx Recorder prerequisites
452
+ plugin list List project plugins and their capabilities
453
+ plugin doctor Validate plugin packages without modifying native files
454
+ plugin apply Apply project plugin native changes for a platform
455
+ plugin apply --dry-run Preview plugin native changes without writing files
436
456
  autolink check Check Lynx native-library Autolink wiring
437
457
  autolink codegen Run the project's Native Module codegen script
438
458
  ota doctor Check native OTA host integration
@@ -514,6 +534,14 @@ Global options:
514
534
  --simulator Build/install an iOS .app or install one with simctl
515
535
  --help Show this complete command reference
516
536
 
537
+ Miso compiler profiles:
538
+ ghcjs (default) Use the official Miso GHC/Nix workflow
539
+ microhs Use a pinned MicroHs binary and a real adapter command
540
+
541
+ MicroHs is opt-in. Configure build.<profile>.miso.microhs with either a
542
+ binary path or a pinned manifest, plus an adapter command that writes the
543
+ configured main.lynx.bundle. It is not a drop-in GHCJS replacement.
544
+
517
545
  Node support: Node 22/24 LTS or Node 26 Current. Use Node 24 LTS for production.`;
518
546
  }
519
547
  async function looksLikeLynxProject() {
@@ -905,6 +933,80 @@ async function main() {
905
933
  });
906
934
  return;
907
935
  }
936
+ if (command === "plugin") {
937
+ const subcommand = args.shift() ?? "list";
938
+ assert(["list", "doctor", "apply"].includes(subcommand), "CLI_PLUGIN_COMMAND", "Use \`lynxship plugin list\`, \`lynxship plugin doctor\` or \`lynxship plugin apply\`.");
939
+ await requireProjectRoot();
940
+ const config = await loadConfig(root);
941
+ const report = await inspectProjectPlugins(root, config);
942
+ if (subcommand === "apply") {
943
+ const platform = platformValue(flag("--platform", "android"));
944
+ const profile = flag("--profile", "production");
945
+ const dryRun = args.includes("--dry-run");
946
+ const result = await applyProjectPlugins(root, config, {
947
+ platform,
948
+ profile,
949
+ mode: dryRun ? "plan" : "apply",
950
+ });
951
+ printValue({
952
+ status: dryRun ? "planned" : "applied",
953
+ platform,
954
+ profile,
955
+ plugins: result.applied,
956
+ nativeChanges: result.changes.filter((change) => change.changed)
957
+ .length,
958
+ changes: result.changes,
959
+ templates: result.templates,
960
+ cloud: result.cloud,
961
+ build: result.build,
962
+ }, {
963
+ title: `LynxShip plugins · ${platform}`,
964
+ rows: [
965
+ {
966
+ label: "Applied",
967
+ value: result.applied.length ? result.applied.join(", ") : "none",
968
+ valueColor: "green",
969
+ },
970
+ {
971
+ label: "Native changes",
972
+ value: String(result.changes.filter((change) => change.changed).length),
973
+ valueColor: "blue",
974
+ },
975
+ ],
976
+ done: dryRun
977
+ ? "No native files were modified; review the planned changes."
978
+ : "Project plugin changes are applied atomically and idempotently.",
979
+ });
980
+ return;
981
+ }
982
+ const invalid = report.plugins.filter((plugin) => plugin.status !== "ready");
983
+ printValue(report, {
984
+ title: `LynxShip plugins · ${subcommand}`,
985
+ rows: report.plugins.length > 0
986
+ ? report.plugins.map((plugin) => ({
987
+ label: plugin.name,
988
+ value: `${plugin.status} · ${plugin.capabilities.join(", ") || "no capabilities"} · ${plugin.reason}`,
989
+ valueColor: plugin.status === "ready"
990
+ ? "green"
991
+ : plugin.status === "missing"
992
+ ? "red"
993
+ : "yellow",
994
+ }))
995
+ : [
996
+ {
997
+ label: "Plugins",
998
+ value: "none configured",
999
+ valueColor: "muted",
1000
+ },
1001
+ ],
1002
+ done: invalid.length === 0
1003
+ ? "All project plugin manifests are valid."
1004
+ : "Fix the invalid plugin package before building.",
1005
+ });
1006
+ if (subcommand === "doctor" && invalid.length > 0)
1007
+ process.exitCode = 1;
1008
+ return;
1009
+ }
908
1010
  if (command === "doctor") {
909
1011
  const config = await loadConfig(root);
910
1012
  const configuration = await readConfigurationStatus();
@@ -915,7 +1017,13 @@ async function main() {
915
1017
  const autolinkForPlatform = autolink && (doctorPlatform === "android" || doctorPlatform === "ios")
916
1018
  ? autolink[doctorPlatform]
917
1019
  : undefined;
918
- const lockfile = await findLockfile(root);
1020
+ const framework = await detectLynxFramework(root);
1021
+ const packageLockfile = await findLockfile(root);
1022
+ const lockfile = packageLockfile ??
1023
+ (framework.framework === "miso" &&
1024
+ (await exists(join(root, "flake.lock")))
1025
+ ? join(root, "flake.lock")
1026
+ : null);
919
1027
  const credentialStore = credentialStorageDescription();
920
1028
  const nativeCredentialStore = !credentialStore.includes("owner-only");
921
1029
  const androidHost = doctorPlatform === "android" ? await hasAndroidHost(root) : false;
@@ -945,7 +1053,25 @@ async function main() {
945
1053
  const nodeMajor = Number(process.versions.node.split(".")[0]);
946
1054
  const nodeSupported = nodeMajor >= 22;
947
1055
  const nodeRecommended = nodeMajor % 2 === 0;
948
- const framework = await detectLynxFramework(root);
1056
+ const misoCompiler = framework.framework === "miso"
1057
+ ? (doctorProfile.miso?.compiler ?? "ghcjs")
1058
+ : undefined;
1059
+ const microhsConfig = doctorProfile.miso?.microhs;
1060
+ const microhsBinary = microhsConfig?.binary ?? process.env.LYNXSHIP_MICROHS_BINARY;
1061
+ const microhsBinaryReady = Boolean(microhsBinary && existsSync(resolve(root, microhsBinary)));
1062
+ const microhsManifestPath = microhsConfig?.manifest ?? process.env.LYNXSHIP_MICROHS_MANIFEST;
1063
+ const microhsManifestReady = Boolean((microhsManifestPath && existsSync(resolve(root, microhsManifestPath))) ||
1064
+ microhsConfig?.manifestUrl ||
1065
+ process.env.LYNXSHIP_MICROHS_MANIFEST_URL);
1066
+ let microhsHost;
1067
+ try {
1068
+ microhsHost = microHsHostTriple();
1069
+ }
1070
+ catch {
1071
+ microhsHost = undefined;
1072
+ }
1073
+ const pluginReport = await inspectProjectPlugins(root, config);
1074
+ const invalidPlugins = pluginReport.plugins.filter((plugin) => plugin.status !== "ready");
949
1075
  const checks = [
950
1076
  {
951
1077
  name: "lynx-framework",
@@ -962,7 +1088,7 @@ async function main() {
962
1088
  framework.evidence +
963
1089
  (framework.experimental ? " · early access" : ""),
964
1090
  },
965
- ...(framework.framework === "miso"
1091
+ ...(framework.framework === "miso" && misoCompiler !== "microhs"
966
1092
  ? [
967
1093
  {
968
1094
  name: "miso-nix",
@@ -975,7 +1101,45 @@ async function main() {
975
1101
  : "missing · fix: install Nix and rerun doctor",
976
1102
  },
977
1103
  ]
978
- : []),
1104
+ : framework.framework === "miso"
1105
+ ? [
1106
+ {
1107
+ name: "miso-compiler",
1108
+ ok: true,
1109
+ status: "pass",
1110
+ value: "MicroHs selected explicitly",
1111
+ },
1112
+ {
1113
+ name: "miso-microhs-host",
1114
+ ok: Boolean(microhsHost),
1115
+ status: microhsHost ? "pass" : "fail",
1116
+ value: microhsHost ??
1117
+ "unsupported host architecture · fix: use a supported MicroHs host or compiler=ghcjs",
1118
+ },
1119
+ {
1120
+ name: "miso-microhs-toolchain",
1121
+ ok: microhsBinaryReady || microhsManifestReady,
1122
+ status: microhsBinaryReady || microhsManifestReady
1123
+ ? "pass"
1124
+ : "fail",
1125
+ value: microhsBinaryReady
1126
+ ? "external MicroHs binary found"
1127
+ : microhsManifestReady
1128
+ ? "pinned release manifest configured · downloaded on build"
1129
+ : "missing · fix: configure build.<profile>.miso.microhs.binary or manifestUrl",
1130
+ },
1131
+ {
1132
+ name: "miso-microhs-adapter",
1133
+ ok: Boolean(microhsConfig?.adapter?.command),
1134
+ status: microhsConfig?.adapter?.command
1135
+ ? "pass"
1136
+ : "fail",
1137
+ value: microhsConfig?.adapter?.command
1138
+ ? "adapter command configured"
1139
+ : "missing · fix: configure build.<profile>.miso.microhs.adapter",
1140
+ },
1141
+ ]
1142
+ : []),
979
1143
  {
980
1144
  name: "node",
981
1145
  ok: nodeSupported,
@@ -991,7 +1155,7 @@ async function main() {
991
1155
  : `${process.version} · recommended: Node 24 LTS`,
992
1156
  },
993
1157
  {
994
- name: "package-manager-lockfile",
1158
+ name: "dependency-lockfile",
995
1159
  ok: Boolean(lockfile),
996
1160
  status: lockfile ? "pass" : "fail",
997
1161
  value: lockfile ?? "missing · fix: pnpm install (or npm install)",
@@ -1002,6 +1166,16 @@ async function main() {
1002
1166
  status: config.projectId ? "pass" : "fail",
1003
1167
  value: config.projectId ? "found" : "missing · fix: lynxship init",
1004
1168
  },
1169
+ {
1170
+ name: "lynxship-plugins",
1171
+ ok: invalidPlugins.length === 0,
1172
+ status: invalidPlugins.length === 0 ? "pass" : "fail",
1173
+ value: invalidPlugins.length === 0
1174
+ ? pluginReport.configured === 0
1175
+ ? "none configured"
1176
+ : `${pluginReport.configured} plugin(s) ready`
1177
+ : `${invalidPlugins.map((plugin) => plugin.name).join(", ")} · fix: lynxship plugin doctor`,
1178
+ },
1005
1179
  {
1006
1180
  name: "credential-store",
1007
1181
  ok: true,
@@ -0,0 +1,2 @@
1
+ export * from "@lynxship/plugin-api";
2
+ //# sourceMappingURL=plugin-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-api.d.ts","sourceRoot":"","sources":["../src/plugin-api.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC"}
@@ -0,0 +1 @@
1
+ export * from "@lynxship/plugin-api";
@@ -0,0 +1,35 @@
1
+ import type { Platform } from "@lynxship/contracts";
2
+ import { type BuildContribution, type CloudIntegrationContribution, type PluginCapability, type AutolinkContribution, type PluginPermission, type PluginPlanChange, type TemplateContribution } from "@lynxship/plugin-api";
3
+ import type { LynxShipConfig } from "./config.js";
4
+ export interface ProjectPluginInfo {
5
+ name: string;
6
+ version?: string;
7
+ packagePath?: string;
8
+ entry?: string;
9
+ apiVersion?: number;
10
+ capabilities: PluginCapability[];
11
+ permissions: PluginPermission[];
12
+ status: "ready" | "missing" | "invalid";
13
+ reason: string;
14
+ }
15
+ export interface PluginReport {
16
+ configured: number;
17
+ plugins: ProjectPluginInfo[];
18
+ }
19
+ export interface PluginApplication {
20
+ config: LynxShipConfig;
21
+ report: PluginReport;
22
+ applied: string[];
23
+ templates: TemplateContribution[];
24
+ cloud: CloudIntegrationContribution[];
25
+ build: BuildContribution[];
26
+ changes: PluginPlanChange[];
27
+ autolink: AutolinkContribution[];
28
+ }
29
+ export declare function inspectProjectPlugins(root: string, config: LynxShipConfig): Promise<PluginReport>;
30
+ export declare function applyProjectPlugins(root: string, config: LynxShipConfig, target: {
31
+ platform: Platform;
32
+ profile: string;
33
+ mode?: "plan" | "apply";
34
+ }): Promise<PluginApplication>;
35
+ //# sourceMappingURL=plugins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.d.ts","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,4BAA4B,EAKjC,KAAK,gBAAgB,EAErB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EAGrB,KAAK,oBAAoB,EAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAclD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,WAAW,EAAE,gBAAgB,EAAE,CAAC;IAChC,MAAM,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;IACxC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,oBAAoB,EAAE,CAAC;IAClC,KAAK,EAAE,4BAA4B,EAAE,CAAC;IACtC,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,QAAQ,EAAE,oBAAoB,EAAE,CAAC;CAClC;AAqUD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,YAAY,CAAC,CAQvB;AA+XD,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,GACvE,OAAO,CAAC,iBAAiB,CAAC,CAqF5B"}