@lynxship/cli 0.1.15 → 0.1.18

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
@@ -3,6 +3,7 @@ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { existsSync } from "node:fs";
4
4
  import { createHash, randomBytes, randomUUID } from "node:crypto";
5
5
  import { basename, dirname, join, resolve } from "node:path";
6
+ import { detectLynxFramework } from "./frameworks.js";
6
7
  import { BuildOrchestrator } from "@lynxship/build-orchestrator";
7
8
  import { JsonRepository } from "@lynxship/db";
8
9
  import { assert, createId, sha256, } from "@lynxship/contracts";
@@ -34,6 +35,7 @@ import { hasHarmonyHost, runRealHarmonyBuild } from "./harmony-build.js";
34
35
  import { hasWebConfiguration, runRealWebBuild } from "./web-build.js";
35
36
  import { hasDesktopHost, runRealDesktopBuild } from "./desktop-build.js";
36
37
  import { extractDevServerUrl, shouldPrintDevServerQr } from "./dev-qr.js";
38
+ import { applyProjectPlugins, inspectProjectPlugins } from "./plugins.js";
37
39
  import { inspectDesktopTarget, inspectHarmonyTarget, inspectWebTarget, } from "./target-toolchain.js";
38
40
  const rawArgs = process.argv.slice(2);
39
41
  const args = [...rawArgs];
@@ -215,7 +217,20 @@ async function executeBuild(options, showResult = true) {
215
217
  // Host initialization can write lynxship.json (for example, the generated
216
218
  // iOS scheme). Always read it again before resolving the build profile so a
217
219
  // first build uses the host that was just created in the same invocation.
218
- const effectiveConfig = wait && !local ? await loadConfig(root) : config;
220
+ const loadedConfig = wait && !local ? await loadConfig(root) : config;
221
+ const pluginApplication = wait && !local
222
+ ? await applyProjectPlugins(root, loadedConfig, { platform, profile })
223
+ : {
224
+ config: loadedConfig,
225
+ report: { configured: 0, plugins: [] },
226
+ applied: [],
227
+ templates: [],
228
+ cloud: [],
229
+ build: [],
230
+ changes: [],
231
+ autolink: [],
232
+ };
233
+ const effectiveConfig = pluginApplication.config;
219
234
  if (platform === "android" || platform === "ios")
220
235
  await requireAutolinkReady(root, platform);
221
236
  const runtime = await inspectRuntimeFingerprint(root, platform, effectiveConfig);
@@ -364,13 +379,14 @@ async function executeBuild(options, showResult = true) {
364
379
  }
365
380
  return result;
366
381
  }
367
- async function buildSharedLynxBundle() {
382
+ async function buildSharedLynxBundle(miso) {
368
383
  const progress = ui.progress("Shared Lynx bundle");
369
384
  try {
370
385
  progress.update(undefined, "Building the shared Lynx bundle once for native targets…");
371
386
  await buildLynxBundle(root, {
372
387
  quiet: json,
373
388
  onOutput: (message) => progress.event(message),
389
+ miso,
374
390
  });
375
391
  progress.update(100, "Shared native Lynx bundle ready");
376
392
  }
@@ -416,6 +432,7 @@ function commandTitle(command) {
416
432
  devtool: "Lynx DevTool diagnostics",
417
433
  trace: "Lynx Trace diagnostics",
418
434
  recorder: "Lynx Recorder diagnostics",
435
+ plugin: "LynxShip plugins",
419
436
  }[command] ?? command);
420
437
  }
421
438
  function helpText() {
@@ -431,6 +448,10 @@ Commands:
431
448
  devtool doctor Check Lynx DevTool integration and dev runtime
432
449
  trace doctor Check Lynx Trace prerequisites
433
450
  recorder doctor Check Lynx Recorder prerequisites
451
+ plugin list List project plugins and their capabilities
452
+ plugin doctor Validate plugin packages without modifying native files
453
+ plugin apply Apply project plugin native changes for a platform
454
+ plugin apply --dry-run Preview plugin native changes without writing files
434
455
  autolink check Check Lynx native-library Autolink wiring
435
456
  autolink codegen Run the project's Native Module codegen script
436
457
  ota doctor Check native OTA host integration
@@ -515,6 +536,9 @@ Global options:
515
536
  Node support: Node 22/24 LTS or Node 26 Current. Use Node 24 LTS for production.`;
516
537
  }
517
538
  async function looksLikeLynxProject() {
539
+ const framework = await detectLynxFramework(root);
540
+ if (framework.framework !== "unknown")
541
+ return true;
518
542
  const configFiles = [
519
543
  "lynx.config.ts",
520
544
  "lynx.config.js",
@@ -900,6 +924,80 @@ async function main() {
900
924
  });
901
925
  return;
902
926
  }
927
+ if (command === "plugin") {
928
+ const subcommand = args.shift() ?? "list";
929
+ assert(["list", "doctor", "apply"].includes(subcommand), "CLI_PLUGIN_COMMAND", "Use \`lynxship plugin list\`, \`lynxship plugin doctor\` or \`lynxship plugin apply\`.");
930
+ await requireProjectRoot();
931
+ const config = await loadConfig(root);
932
+ const report = await inspectProjectPlugins(root, config);
933
+ if (subcommand === "apply") {
934
+ const platform = platformValue(flag("--platform", "android"));
935
+ const profile = flag("--profile", "production");
936
+ const dryRun = args.includes("--dry-run");
937
+ const result = await applyProjectPlugins(root, config, {
938
+ platform,
939
+ profile,
940
+ mode: dryRun ? "plan" : "apply",
941
+ });
942
+ printValue({
943
+ status: dryRun ? "planned" : "applied",
944
+ platform,
945
+ profile,
946
+ plugins: result.applied,
947
+ nativeChanges: result.changes.filter((change) => change.changed)
948
+ .length,
949
+ changes: result.changes,
950
+ templates: result.templates,
951
+ cloud: result.cloud,
952
+ build: result.build,
953
+ }, {
954
+ title: `LynxShip plugins · ${platform}`,
955
+ rows: [
956
+ {
957
+ label: "Applied",
958
+ value: result.applied.length ? result.applied.join(", ") : "none",
959
+ valueColor: "green",
960
+ },
961
+ {
962
+ label: "Native changes",
963
+ value: String(result.changes.filter((change) => change.changed).length),
964
+ valueColor: "blue",
965
+ },
966
+ ],
967
+ done: dryRun
968
+ ? "No native files were modified; review the planned changes."
969
+ : "Project plugin changes are applied atomically and idempotently.",
970
+ });
971
+ return;
972
+ }
973
+ const invalid = report.plugins.filter((plugin) => plugin.status !== "ready");
974
+ printValue(report, {
975
+ title: `LynxShip plugins · ${subcommand}`,
976
+ rows: report.plugins.length > 0
977
+ ? report.plugins.map((plugin) => ({
978
+ label: plugin.name,
979
+ value: `${plugin.status} · ${plugin.capabilities.join(", ") || "no capabilities"} · ${plugin.reason}`,
980
+ valueColor: plugin.status === "ready"
981
+ ? "green"
982
+ : plugin.status === "missing"
983
+ ? "red"
984
+ : "yellow",
985
+ }))
986
+ : [
987
+ {
988
+ label: "Plugins",
989
+ value: "none configured",
990
+ valueColor: "muted",
991
+ },
992
+ ],
993
+ done: invalid.length === 0
994
+ ? "All project plugin manifests are valid."
995
+ : "Fix the invalid plugin package before building.",
996
+ });
997
+ if (subcommand === "doctor" && invalid.length > 0)
998
+ process.exitCode = 1;
999
+ return;
1000
+ }
903
1001
  if (command === "doctor") {
904
1002
  const config = await loadConfig(root);
905
1003
  const configuration = await readConfigurationStatus();
@@ -940,7 +1038,39 @@ async function main() {
940
1038
  const nodeMajor = Number(process.versions.node.split(".")[0]);
941
1039
  const nodeSupported = nodeMajor >= 22;
942
1040
  const nodeRecommended = nodeMajor % 2 === 0;
1041
+ const framework = await detectLynxFramework(root);
1042
+ const pluginReport = await inspectProjectPlugins(root, config);
1043
+ const invalidPlugins = pluginReport.plugins.filter((plugin) => plugin.status !== "ready");
943
1044
  const checks = [
1045
+ {
1046
+ name: "lynx-framework",
1047
+ ok: framework.framework !== "unknown",
1048
+ status: framework.framework === "unknown"
1049
+ ? "warn"
1050
+ : framework.experimental
1051
+ ? "warn"
1052
+ : "pass",
1053
+ value: framework.framework === "unknown"
1054
+ ? "unknown · verify the official Lynx/Rspeedy integration"
1055
+ : framework.label +
1056
+ " · " +
1057
+ framework.evidence +
1058
+ (framework.experimental ? " · early access" : ""),
1059
+ },
1060
+ ...(framework.framework === "miso"
1061
+ ? [
1062
+ {
1063
+ name: "miso-nix",
1064
+ ok: commandExists("nix"),
1065
+ status: commandExists("nix")
1066
+ ? "pass"
1067
+ : "fail",
1068
+ value: commandExists("nix")
1069
+ ? "Nix detected"
1070
+ : "missing · fix: install Nix and rerun doctor",
1071
+ },
1072
+ ]
1073
+ : []),
944
1074
  {
945
1075
  name: "node",
946
1076
  ok: nodeSupported,
@@ -967,6 +1097,16 @@ async function main() {
967
1097
  status: config.projectId ? "pass" : "fail",
968
1098
  value: config.projectId ? "found" : "missing · fix: lynxship init",
969
1099
  },
1100
+ {
1101
+ name: "lynxship-plugins",
1102
+ ok: invalidPlugins.length === 0,
1103
+ status: invalidPlugins.length === 0 ? "pass" : "fail",
1104
+ value: invalidPlugins.length === 0
1105
+ ? pluginReport.configured === 0
1106
+ ? "none configured"
1107
+ : `${pluginReport.configured} plugin(s) ready`
1108
+ : `${invalidPlugins.map((plugin) => plugin.name).join(", ")} · fix: lynxship plugin doctor`,
1109
+ },
970
1110
  {
971
1111
  name: "credential-store",
972
1112
  ok: true,
@@ -1719,7 +1859,7 @@ async function main() {
1719
1859
  return;
1720
1860
  }
1721
1861
  if (buildAll && wait && !local)
1722
- await buildSharedLynxBundle();
1862
+ await buildSharedLynxBundle(resolveProfile(config, profile).miso);
1723
1863
  const progress = ui.progress("All Lynx targets build");
1724
1864
  const progressValues = {};
1725
1865
  const progressLabels = {};
@@ -1 +1 @@
1
- {"version":3,"file":"ios-build.d.ts","sourceRoot":"","sources":["../src/ios-build.ts"],"names":[],"mappings":"AAYA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMhD,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAyHD;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAoBnB;AAoED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAuD7B;AAgCD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IACP,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC,GACL,OAAO,CAAC,IAAI,CAAC,CAcf;AAkND,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAcxE;AA+DD,wBAAsB,eAAe,CACnC,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,QAAQ,CAAC,CAqNnB"}
1
+ {"version":3,"file":"ios-build.d.ts","sourceRoot":"","sources":["../src/ios-build.ts"],"names":[],"mappings":"AAYA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMhD,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAyHD;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,EAAE,CAAC,CAoBnB;AAoED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAuD7B;AAgCD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IACP,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC,GACL,OAAO,CAAC,IAAI,CAAC,CAcf;AAmND,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAcxE;AA+DD,wBAAsB,eAAe,CACnC,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,QAAQ,CAAC,CAsNnB"}
package/dist/ios-build.js CHANGED
@@ -264,6 +264,7 @@ async function runRealIosSimulatorBuild(job, options) {
264
264
  await buildLynxBundle(options.root, {
265
265
  quiet: options.quiet,
266
266
  onOutput: options.onEvent,
267
+ miso: options.profile.miso,
267
268
  });
268
269
  }
269
270
  if (ios.bundleScript) {
@@ -454,6 +455,7 @@ export async function runRealIosBuild(job, options) {
454
455
  await buildLynxBundle(options.root, {
455
456
  quiet: options.quiet,
456
457
  onOutput: options.onEvent,
458
+ miso: options.profile.miso,
457
459
  });
458
460
  }
459
461
  if (ios.bundleScript) {
@@ -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"}