@lynxship/cli 0.1.8 → 0.1.10

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 (51) hide show
  1. package/README.md +175 -19
  2. package/dist/android-toolchain.d.ts +23 -0
  3. package/dist/android-toolchain.d.ts.map +1 -0
  4. package/dist/android-toolchain.js +319 -0
  5. package/dist/artifact-build.d.ts +17 -0
  6. package/dist/artifact-build.d.ts.map +1 -0
  7. package/dist/artifact-build.js +56 -0
  8. package/dist/artifact-name.d.ts +1 -1
  9. package/dist/artifact-name.d.ts.map +1 -1
  10. package/dist/autolink.d.ts +3 -2
  11. package/dist/autolink.d.ts.map +1 -1
  12. package/dist/autolink.js +130 -6
  13. package/dist/bundle-build.d.ts +2 -0
  14. package/dist/bundle-build.d.ts.map +1 -1
  15. package/dist/bundle-build.js +11 -3
  16. package/dist/config.d.ts +19 -0
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +1 -1
  19. package/dist/desktop-build.d.ts +22 -0
  20. package/dist/desktop-build.d.ts.map +1 -0
  21. package/dist/desktop-build.js +161 -0
  22. package/dist/desktop-signing.d.ts +14 -0
  23. package/dist/desktop-signing.d.ts.map +1 -0
  24. package/dist/desktop-signing.js +200 -0
  25. package/dist/guidance.d.ts.map +1 -1
  26. package/dist/guidance.js +113 -4
  27. package/dist/harmony-build.d.ts +22 -0
  28. package/dist/harmony-build.d.ts.map +1 -0
  29. package/dist/harmony-build.js +194 -0
  30. package/dist/index.js +333 -73
  31. package/dist/ios-toolchain.d.ts +19 -0
  32. package/dist/ios-toolchain.d.ts.map +1 -0
  33. package/dist/ios-toolchain.js +303 -0
  34. package/dist/lynx-devtool.d.ts +18 -0
  35. package/dist/lynx-devtool.d.ts.map +1 -0
  36. package/dist/lynx-devtool.js +125 -0
  37. package/dist/process-runner.d.ts +11 -0
  38. package/dist/process-runner.d.ts.map +1 -1
  39. package/dist/process-runner.js +29 -0
  40. package/dist/prompt.d.ts +1 -0
  41. package/dist/prompt.d.ts.map +1 -1
  42. package/dist/prompt.js +6 -0
  43. package/dist/remote.d.ts +3 -3
  44. package/dist/remote.d.ts.map +1 -1
  45. package/dist/target-toolchain.d.ts +17 -0
  46. package/dist/target-toolchain.d.ts.map +1 -0
  47. package/dist/target-toolchain.js +122 -0
  48. package/dist/web-build.d.ts +16 -0
  49. package/dist/web-build.d.ts.map +1 -0
  50. package/dist/web-build.js +116 -0
  51. package/package.json +4 -4
@@ -1,3 +1,3 @@
1
- export type NativeArtifactExtension = "apk" | "aab" | "ipa";
1
+ export type NativeArtifactExtension = "apk" | "aab" | "ipa" | "hap" | "lynx.bundle" | "web.bundle" | "app" | "dmg" | "exe" | "appimage" | "zip";
2
2
  export declare function nativeArtifactName(extension: NativeArtifactExtension): string;
3
3
  //# sourceMappingURL=artifact-name.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"artifact-name.d.ts","sourceRoot":"","sources":["../src/artifact-name.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,uBAAuB,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;AAE5D,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,uBAAuB,GAAG,MAAM,CAE7E"}
1
+ {"version":3,"file":"artifact-name.d.ts","sourceRoot":"","sources":["../src/artifact-name.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,uBAAuB,GAC/B,KAAK,GACL,KAAK,GACL,KAAK,GACL,KAAK,GACL,aAAa,GACb,YAAY,GACZ,KAAK,GACL,KAAK,GACL,KAAK,GACL,UAAU,GACV,KAAK,CAAC;AAEV,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,uBAAuB,GAAG,MAAM,CAE7E"}
@@ -1,14 +1,15 @@
1
- import { type Platform } from "@lynxship/contracts";
1
+ import { type MobilePlatform } from "@lynxship/contracts";
2
2
  export interface AutolinkPlatformStatus {
3
3
  required: boolean;
4
4
  ready: boolean;
5
5
  manifests: string[];
6
6
  reason: string;
7
+ issues?: string[];
7
8
  }
8
9
  export interface AutolinkStatus {
9
10
  android: AutolinkPlatformStatus;
10
11
  ios: AutolinkPlatformStatus;
11
12
  }
12
13
  export declare function inspectAutolink(root: string): Promise<AutolinkStatus>;
13
- export declare function requireAutolinkReady(root: string, platform: Platform): Promise<AutolinkPlatformStatus>;
14
+ export declare function requireAutolinkReady(root: string, platform: MobilePlatform): Promise<AutolinkPlatformStatus>;
14
15
  //# sourceMappingURL=autolink.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"autolink.d.ts","sourceRoot":"","sources":["../src/autolink.ts"],"names":[],"mappings":"AAGA,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAM5D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,sBAAsB,CAAC;IAChC,GAAG,EAAE,sBAAsB,CAAC;CAC7B;AAqID,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAM3E;AAED,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,sBAAsB,CAAC,CAWjC"}
1
+ {"version":3,"file":"autolink.d.ts","sourceRoot":"","sources":["../src/autolink.ts"],"names":[],"mappings":"AAGA,OAAO,EAAU,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAiBlE,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,sBAAsB,CAAC;IAChC,GAAG,EAAE,sBAAsB,CAAC;CAC7B;AAsQD,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAwB3E;AAED,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,sBAAsB,CAAC,CAWjC"}
package/dist/autolink.js CHANGED
@@ -8,6 +8,99 @@ const emptyStatus = () => ({
8
8
  manifests: [],
9
9
  reason: "No Lynx native-library manifest found",
10
10
  });
11
+ function packageRoot(manifestPath) {
12
+ return dirname(manifestPath);
13
+ }
14
+ function platformSourceDirectory(manifestPath, platform, manifest) {
15
+ const definition = manifest.platforms?.[platform];
16
+ const sourceDir = definition && typeof definition.sourceDir === "string"
17
+ ? definition.sourceDir
18
+ : platform;
19
+ return join(packageRoot(manifestPath), sourceDir);
20
+ }
21
+ async function findNativeSourceFiles(directory) {
22
+ const files = [];
23
+ async function visit(current) {
24
+ let entries;
25
+ try {
26
+ entries = await readdir(current, { withFileTypes: true });
27
+ }
28
+ catch {
29
+ return;
30
+ }
31
+ for (const entry of entries) {
32
+ if (["build", "Pods", ".gradle", "node_modules"].includes(entry.name))
33
+ continue;
34
+ const file = join(current, entry.name);
35
+ if (entry.isDirectory()) {
36
+ await visit(file);
37
+ }
38
+ else if (entry.isFile() &&
39
+ /\.(?:java|kt|m|mm|h|swift|podspec)$/i.test(entry.name)) {
40
+ files.push(file);
41
+ }
42
+ }
43
+ }
44
+ await visit(directory);
45
+ return files;
46
+ }
47
+ async function manifestIssues(file, platform) {
48
+ const issues = [];
49
+ let manifest;
50
+ try {
51
+ manifest = JSON.parse(await readFile(file, "utf8"));
52
+ }
53
+ catch {
54
+ return ["lynx.lib.json is not valid JSON"];
55
+ }
56
+ const definition = manifest.platforms?.[platform];
57
+ if (!definition || typeof definition !== "object")
58
+ return issues;
59
+ const root = packageRoot(file);
60
+ if (platform === "android") {
61
+ if (typeof definition.packageName !== "string" || !definition.packageName)
62
+ issues.push("platforms.android.packageName is required");
63
+ if (definition.sourceDir !== undefined &&
64
+ (typeof definition.sourceDir !== "string" || !definition.sourceDir))
65
+ issues.push("platforms.android.sourceDir must be a non-empty string");
66
+ }
67
+ if (platform === "ios" && definition.podspecPath !== undefined) {
68
+ if (typeof definition.podspecPath !== "string" || !definition.podspecPath)
69
+ issues.push("platforms.ios.podspecPath must be a non-empty string");
70
+ }
71
+ const sourceDirectory = platformSourceDirectory(file, platform, manifest);
72
+ if (!existsSync(sourceDirectory))
73
+ issues.push(`native source directory is missing: ${sourceDirectory}`);
74
+ if (platform === "ios") {
75
+ const podspec = typeof definition.podspecPath === "string"
76
+ ? join(root, definition.podspecPath)
77
+ : (await findNativeSourceFiles(sourceDirectory)).find((candidate) => candidate.endsWith(".podspec"));
78
+ if (definition.podspecPath && !existsSync(podspec ?? ""))
79
+ issues.push(`iOS podspec is missing: ${podspec}`);
80
+ }
81
+ return issues;
82
+ }
83
+ async function capabilityNames(file, platform) {
84
+ let manifest;
85
+ try {
86
+ manifest = JSON.parse(await readFile(file, "utf8"));
87
+ }
88
+ catch {
89
+ return [];
90
+ }
91
+ const source = platformSourceDirectory(file, platform, manifest);
92
+ const files = await findNativeSourceFiles(source);
93
+ const names = new Set();
94
+ for (const nativeFile of files) {
95
+ const text = await readFile(nativeFile, "utf8").catch(() => "");
96
+ const expression = /@Lynx(?:NativeModule|Element|Service)\s*\(\s*["']?([A-Za-z0-9_.-]+)/g;
97
+ for (const match of text.matchAll(expression)) {
98
+ if (match[1])
99
+ names.add(match[1]);
100
+ }
101
+ }
102
+ return [...names];
103
+ }
11
104
  function findNodeModuleRoots(root) {
12
105
  const roots = [];
13
106
  let current = root;
@@ -89,6 +182,7 @@ async function platformStatus(root, platform, manifests) {
89
182
  }
90
183
  if (relevant.length === 0)
91
184
  return emptyStatus();
185
+ const issues = (await Promise.all(relevant.map((file) => manifestIssues(file, platform)))).flat();
92
186
  if (platform === "android") {
93
187
  const settingsFile = [
94
188
  join(root, "android", "settings.gradle"),
@@ -102,38 +196,68 @@ async function platformStatus(root, platform, manifests) {
102
196
  ? await readFile(settingsFile, "utf8")
103
197
  : "";
104
198
  const appText = appFile ? await readFile(appFile, "utf8") : "";
105
- const ready = settingsText.includes("org.lynxsdk.library-settings") &&
106
- appText.includes("org.lynxsdk.library-build");
199
+ const settingsPlugin = settingsText.includes("org.lynxsdk.library-settings") ||
200
+ settingsText.includes("org.lynxsdk.lynx.library-settings");
201
+ const buildPlugin = appText.includes("org.lynxsdk.library-build") ||
202
+ appText.includes("org.lynxsdk.lynx.library-build");
203
+ const pluginsReady = settingsPlugin && buildPlugin;
204
+ const ready = pluginsReady && issues.length === 0;
107
205
  return {
108
206
  required: true,
109
207
  ready,
110
208
  manifests: relevant,
209
+ ...(issues.length > 0 ? { issues } : {}),
111
210
  reason: ready
112
211
  ? "Lynx Android Autolink plugins are enabled"
113
- : "Enable org.lynxsdk.library-settings in settings.gradle and org.lynxsdk.library-build in app/build.gradle",
212
+ : issues.length > 0
213
+ ? issues.join("; ")
214
+ : "Enable the Lynx Autolink settings and build plugins in settings.gradle and app/build.gradle (use the IDs matching the project Lynx release)",
114
215
  };
115
216
  }
116
217
  const podfile = join(root, "ios", "Podfile");
117
218
  const podfileText = (await hasFile(podfile))
118
219
  ? await readFile(podfile, "utf8")
119
220
  : "";
120
- const ready = podfileText.includes("cocoapods-lynx-library") &&
221
+ const pluginsReady = podfileText.includes("cocoapods-lynx-library") &&
121
222
  podfileText.includes("use_lynx_library!");
223
+ const ready = pluginsReady && issues.length === 0;
122
224
  return {
123
225
  required: true,
124
226
  ready,
125
227
  manifests: relevant,
228
+ ...(issues.length > 0 ? { issues } : {}),
126
229
  reason: ready
127
230
  ? "Lynx iOS Autolink CocoaPods integration is enabled"
128
- : "Add cocoapods-lynx-library and use_lynx_library! to ios/Podfile",
231
+ : issues.length > 0
232
+ ? issues.join("; ")
233
+ : "Add cocoapods-lynx-library and use_lynx_library! to ios/Podfile",
129
234
  };
130
235
  }
131
236
  export async function inspectAutolink(root) {
132
237
  const manifests = await findManifests(root);
133
- return {
238
+ const status = {
134
239
  android: await platformStatus(root, "android", manifests),
135
240
  ios: await platformStatus(root, "ios", manifests),
136
241
  };
242
+ for (const platform of ["android", "ios"]) {
243
+ const owners = new Map();
244
+ for (const manifest of status[platform].manifests) {
245
+ for (const name of await capabilityNames(manifest, platform)) {
246
+ const owner = owners.get(name);
247
+ if (owner) {
248
+ const issue = `duplicate ${platform} native capability '${name}' in ${owner} and ${manifest}`;
249
+ status[platform].issues ??= [];
250
+ status[platform].issues.push(issue);
251
+ status[platform].ready = false;
252
+ status[platform].reason = status[platform].issues.join("; ");
253
+ }
254
+ else {
255
+ owners.set(name, manifest);
256
+ }
257
+ }
258
+ }
259
+ }
260
+ return status;
137
261
  }
138
262
  export async function requireAutolinkReady(root, platform) {
139
263
  const status = (await inspectAutolink(root))[platform];
@@ -2,6 +2,8 @@ export interface BundleBuildOptions {
2
2
  env?: NodeJS.ProcessEnv;
3
3
  quiet?: boolean;
4
4
  onOutput?: (line: string) => void;
5
+ script?: string;
6
+ rspeedyArgs?: string[];
5
7
  }
6
8
  export declare function buildLynxBundle(root: string, options?: BundleBuildOptions): Promise<void>;
7
9
  //# sourceMappingURL=bundle-build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"bundle-build.d.ts","sourceRoot":"","sources":["../src/bundle-build.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAcD,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CASf"}
1
+ {"version":3,"file":"bundle-build.d.ts","sourceRoot":"","sources":["../src/bundle-build.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAcD,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAqBf"}
@@ -1,6 +1,6 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
- import { packageManagerScriptCommand, runProcess } from "./process-runner.js";
3
+ import { packageManagerScriptCommand, runProcess, runRspeedy, } from "./process-runner.js";
4
4
  async function resolveBundleScript(root) {
5
5
  try {
6
6
  const manifest = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
@@ -13,8 +13,16 @@ async function resolveBundleScript(root) {
13
13
  return "build";
14
14
  }
15
15
  export async function buildLynxBundle(root, options = {}) {
16
- const script = await resolveBundleScript(root);
17
- const packageManager = packageManagerScriptCommand(root, script);
16
+ if (!options.script && options.rspeedyArgs) {
17
+ await runRspeedy(root, "build", options.rspeedyArgs, {
18
+ env: options.env,
19
+ quiet: options.quiet,
20
+ onOutput: options.onOutput,
21
+ });
22
+ return;
23
+ }
24
+ const script = options.script ?? (await resolveBundleScript(root));
25
+ const packageManager = packageManagerScriptCommand(root, script, options.rspeedyArgs ?? []);
18
26
  await runProcess(packageManager.command, packageManager.args, {
19
27
  cwd: root,
20
28
  env: options.env,
package/dist/config.d.ts CHANGED
@@ -15,6 +15,25 @@ export interface BuildProfile {
15
15
  exportOptionsPlist?: string;
16
16
  bundleScript?: string;
17
17
  };
18
+ harmony?: {
19
+ task?: string;
20
+ module?: string;
21
+ mode?: "project" | "module";
22
+ product?: string;
23
+ buildMode?: "debug" | "release";
24
+ bundleDir?: string;
25
+ artifact?: string;
26
+ signTool?: string;
27
+ };
28
+ web?: {
29
+ environment?: string;
30
+ script?: string;
31
+ artifact?: string;
32
+ };
33
+ desktop?: {
34
+ script?: string;
35
+ artifact?: string;
36
+ };
18
37
  }
19
38
  export interface LynxShipConfig {
20
39
  projectId?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChC,GAAG,CAAC,EAAE;QACJ,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE;QACJ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,cAAc,CAAC,EAAE;QAAE,MAAM,EAAE,aAAa,GAAG,QAAQ,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE;QACP,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE;YAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KAC1C,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,cAAc,EAAE,cAU5B,CAAC;AAaF,wBAAgB,cAAc,CAC5B,MAAM,EAAE,OAAO,EACf,OAAO,GAAE;IAAE,EAAE,CAAC,EAAE,OAAO,CAAA;CAAO,GAC7B,cAAc,CAyChB;AAED,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,EAAE,CAAC,EAAE,OAAO,CAAA;CAAO,GAC7B,OAAO,CAAC,cAAc,CAAC,CAazB;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,cAAc,EACtB,IAAI,SAAe,GAClB,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAIjC;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAOrD"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAChC,GAAG,CAAC,EAAE;QACJ,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;QAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;QAChC,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,GAAG,CAAC,EAAE;QACJ,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,OAAO,CAAC,EAAE;QACR,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE;QACJ,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,cAAc,CAAC,EAAE;QAAE,MAAM,EAAE,aAAa,GAAG,QAAQ,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACrC,MAAM,CAAC,EAAE;QACP,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,OAAO,CAAC,EAAE;YAAE,iBAAiB,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KAC1C,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,cAAc,EAAE,cAU5B,CAAC;AAaF,wBAAgB,cAAc,CAC5B,MAAM,EAAE,OAAO,EACf,OAAO,GAAE;IAAE,EAAE,CAAC,EAAE,OAAO,CAAA;CAAO,GAC7B,cAAc,CAyChB;AAED,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,EAAE,CAAC,EAAE,OAAO,CAAA;CAAO,GAC7B,OAAO,CAAC,cAAc,CAAC,CAazB;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,cAAc,EACtB,IAAI,SAAe,GAClB,YAAY,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAIjC;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAOrD"}
package/dist/config.js CHANGED
@@ -57,6 +57,6 @@ export function resolveProfile(config, name = "production") {
57
57
  return { name, ...profile };
58
58
  }
59
59
  export function platformValue(value) {
60
- assert(value === "android" || value === "ios", "PLATFORM_INVALID", "Platform must be android or ios");
60
+ assert(["android", "ios", "harmony", "web", "desktop"].includes(value), "PLATFORM_INVALID", "Platform must be android, ios, harmony, web or desktop");
61
61
  return value;
62
62
  }
@@ -0,0 +1,22 @@
1
+ import { type BuildJob } from "@lynxship/contracts";
2
+ import type { BuildProfile } from "./config.js";
3
+ interface DesktopBuildOptions {
4
+ root: string;
5
+ profile: BuildProfile;
6
+ uploadArtifacts?: boolean;
7
+ skipBundleBuild?: boolean;
8
+ allowUnsigned?: boolean;
9
+ quiet?: boolean;
10
+ onEvent?: (message: string) => void;
11
+ onProgress?: (value?: number, label?: string) => void;
12
+ }
13
+ interface PackageJson {
14
+ dependencies?: Record<string, string>;
15
+ devDependencies?: Record<string, string>;
16
+ scripts?: Record<string, string>;
17
+ }
18
+ export declare function resolveDesktopPackScript(manifest: PackageJson, profile?: BuildProfile): string | undefined;
19
+ export declare function hasDesktopHost(root: string, profile?: BuildProfile): Promise<boolean>;
20
+ export declare function runRealDesktopBuild(job: BuildJob, options: DesktopBuildOptions): Promise<BuildJob>;
21
+ export {};
22
+ //# sourceMappingURL=desktop-build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desktop-build.d.ts","sourceRoot":"","sources":["../src/desktop-build.ts"],"names":[],"mappings":"AAGA,OAAO,EAAU,KAAK,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAahD,UAAU,mBAAmB;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,YAAY,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,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;AAED,UAAU,WAAW;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,WAAW,EACrB,OAAO,CAAC,EAAE,YAAY,GACrB,MAAM,GAAG,SAAS,CAMpB;AAYD,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,OAAO,CAAC,CAmBlB;AA+DD,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,QAAQ,EACb,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,QAAQ,CAAC,CA0GnB"}
@@ -0,0 +1,161 @@
1
+ import { readFile, readdir, stat } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { extname, join, resolve } from "node:path";
4
+ import { assert } from "@lynxship/contracts";
5
+ import { transitionBuild } from "@lynxship/build-orchestrator";
6
+ import { publishBuiltArtifact } from "./artifact-build.js";
7
+ import { packageManagerCommand, packageManagerScriptCommand, runProcess, } from "./process-runner.js";
8
+ import { buildLynxBundle } from "./bundle-build.js";
9
+ import { inspectDesktopSigning, verifyDesktopArtifactSignature, } from "./desktop-signing.js";
10
+ export function resolveDesktopPackScript(manifest, profile) {
11
+ if (profile?.desktop?.script)
12
+ return profile.desktop.script;
13
+ for (const name of ["pack", "build:desktop", "build:app"]) {
14
+ if (manifest.scripts?.[name])
15
+ return name;
16
+ }
17
+ return undefined;
18
+ }
19
+ async function packageJson(root) {
20
+ try {
21
+ return JSON.parse(await readFile(join(root, "package.json"), "utf8"));
22
+ }
23
+ catch {
24
+ return {};
25
+ }
26
+ }
27
+ export async function hasDesktopHost(root, profile) {
28
+ const manifest = await packageJson(root);
29
+ const dependencies = {
30
+ ...manifest.dependencies,
31
+ ...manifest.devDependencies,
32
+ };
33
+ const hasElectronBuilder = Boolean(dependencies["electron-builder"] ||
34
+ existsSync(join(root, "electron-builder.yml")) ||
35
+ existsSync(join(root, "electron-builder.yaml")) ||
36
+ existsSync(join(root, "electron-builder.json")));
37
+ return Boolean(resolveDesktopPackScript(manifest, profile) ||
38
+ dependencies["@lynx-js/lynxtron"] ||
39
+ dependencies["@lynx-js/lynxtron-builder"] ||
40
+ dependencies.lynxtron ||
41
+ hasElectronBuilder);
42
+ }
43
+ function artifactExtension(file) {
44
+ const extension = extname(file).toLowerCase();
45
+ assert(extension === ".dmg" ||
46
+ extension === ".exe" ||
47
+ extension === ".zip" ||
48
+ extension === ".appimage", "DESKTOP_ARTIFACT_INVALID", "Desktop packaging must produce a .dmg, .exe, .zip or AppImage file.");
49
+ return extension.slice(1);
50
+ }
51
+ async function findDesktopArtifact(root, configured) {
52
+ if (configured) {
53
+ const file = resolve(root, configured);
54
+ assert(existsSync(file), "DESKTOP_ARTIFACT_MISSING", `Configured desktop artifact was not found: ${file}`);
55
+ assert((await stat(file)).isFile(), "DESKTOP_ARTIFACT_INVALID", "Configured desktop artifact must be a file, not an .app directory.");
56
+ return file;
57
+ }
58
+ const directories = [
59
+ "dist",
60
+ "release",
61
+ "out",
62
+ "build/electron",
63
+ "build/dist",
64
+ "build",
65
+ ];
66
+ const candidates = [];
67
+ for (const directory of directories) {
68
+ const absolute = join(root, directory);
69
+ const entries = await readdir(absolute, { withFileTypes: true }).catch(() => []);
70
+ for (const entry of entries) {
71
+ if (!entry.isFile())
72
+ continue;
73
+ if (/\.(?:dmg|exe|zip|appimage)$/i.test(entry.name))
74
+ candidates.push(join(absolute, entry.name));
75
+ }
76
+ }
77
+ assert(candidates.length === 1, "DESKTOP_ARTIFACT_AMBIGUOUS", candidates.length === 0
78
+ ? "The desktop packager produced no distributable file. Configure a pack script or build.<profile>.desktop.artifact."
79
+ : "The desktop packager produced multiple files. Set build.<profile>.desktop.artifact explicitly.");
80
+ return candidates[0];
81
+ }
82
+ export async function runRealDesktopBuild(job, options) {
83
+ assert(await hasDesktopHost(options.root, options.profile), "DESKTOP_HOST_REQUIRED", "No Lynxtron desktop host was found. Use the official Lynxtron template or configure a project pack script with @lynx-js/lynxtron-builder.");
84
+ const signing = await inspectDesktopSigning(options.root);
85
+ if (signing.status === "missing" || signing.status === "disabled") {
86
+ assert(options.allowUnsigned, "DESKTOP_SIGNING_REQUIRED", `${signing.value}. Configure a real desktop signing identity before a production build, or use --allow-unsigned together with --no-upload for local packaging tests. ${signing.fix}`);
87
+ }
88
+ const manifest = await packageJson(options.root);
89
+ const uploadArtifacts = options.uploadArtifacts ?? true;
90
+ const step = (message, value) => {
91
+ options.onEvent?.(message);
92
+ options.onProgress?.(value, message);
93
+ };
94
+ try {
95
+ transitionBuild(job, "uploading_source", "Desktop source prepared");
96
+ const packScript = resolveDesktopPackScript(manifest, options.profile);
97
+ if (!options.skipBundleBuild && !packScript) {
98
+ step("Building Lynx desktop bundle with Rspeedy…", 10);
99
+ await buildLynxBundle(options.root, {
100
+ quiet: options.quiet,
101
+ onOutput: options.onEvent,
102
+ });
103
+ }
104
+ transitionBuild(job, "queued", "Desktop build queued locally");
105
+ transitionBuild(job, "provisioning", "Lynxtron packaging toolchain selected");
106
+ transitionBuild(job, "installing_dependencies", "Desktop dependencies selected");
107
+ step("Preparing desktop package…", 30);
108
+ if (packScript) {
109
+ const command = packageManagerScriptCommand(options.root, packScript);
110
+ await runProcess(command.command, command.args, {
111
+ cwd: options.root,
112
+ quiet: options.quiet,
113
+ onOutput: options.onEvent,
114
+ });
115
+ }
116
+ else {
117
+ const manager = packageManagerCommand(options.root);
118
+ await runProcess(manager.command, [...manager.prefix, "lynxtron-builder", "--publish", "never"], {
119
+ cwd: options.root,
120
+ quiet: options.quiet,
121
+ onOutput: options.onEvent,
122
+ });
123
+ }
124
+ transitionBuild(job, "building", "Desktop package created");
125
+ const artifact = await findDesktopArtifact(options.root, options.profile.desktop?.artifact);
126
+ const signature = await verifyDesktopArtifactSignature(artifact);
127
+ if (!signature.signed && signature.status !== "not-required") {
128
+ assert(options.allowUnsigned, "DESKTOP_SIGNING_REQUIRED", `Desktop artifact is not signed: ${signature.detail} Configure a real desktop signing identity, or use --allow-unsigned together with --no-upload for local packaging tests.`);
129
+ step(`Warning: unsigned desktop artifact accepted for local testing`, 75);
130
+ }
131
+ else if (signature.signed) {
132
+ step("Desktop signature verified", 75);
133
+ }
134
+ else {
135
+ step("Desktop signature verification not required for this target", 75);
136
+ }
137
+ return publishBuiltArtifact({
138
+ root: options.root,
139
+ job,
140
+ platform: "desktop",
141
+ artifactPath: artifact,
142
+ extension: artifactExtension(artifact),
143
+ contentType: "application/octet-stream",
144
+ uploadArtifacts,
145
+ verificationMessage: "Desktop distributable output verified",
146
+ quiet: options.quiet,
147
+ onEvent: options.onEvent,
148
+ onProgress: options.onProgress,
149
+ });
150
+ }
151
+ catch (error) {
152
+ if (!["success", "failed", "canceled", "timed_out"].includes(job.state))
153
+ transitionBuild(job, "failed", error instanceof Error ? error.message : "Desktop build failed");
154
+ job.logs.push({
155
+ level: "error",
156
+ message: error instanceof Error ? error.message : "Desktop build failed",
157
+ at: new Date().toISOString(),
158
+ });
159
+ throw error;
160
+ }
161
+ }
@@ -0,0 +1,14 @@
1
+ export type DesktopSigningStatus = "configured" | "missing" | "disabled" | "unknown" | "not-required";
2
+ export interface DesktopSigningCheck {
3
+ status: DesktopSigningStatus;
4
+ value: string;
5
+ fix: string;
6
+ }
7
+ export interface DesktopArtifactSignature {
8
+ signed: boolean;
9
+ status: "signed" | "unsigned" | "unavailable" | "not-required";
10
+ detail: string;
11
+ }
12
+ export declare function inspectDesktopSigning(root: string): Promise<DesktopSigningCheck>;
13
+ export declare function verifyDesktopArtifactSignature(artifactPath: string): Promise<DesktopArtifactSignature>;
14
+ //# sourceMappingURL=desktop-signing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"desktop-signing.d.ts","sourceRoot":"","sources":["../src/desktop-signing.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,oBAAoB,GAC5B,YAAY,GACZ,SAAS,GACT,UAAU,GACV,SAAS,GACT,cAAc,CAAC;AAEnB,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,oBAAoB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,aAAa,GAAG,cAAc,CAAC;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AAyCD,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,mBAAmB,CAAC,CAyC9B;AAuKD,wBAAsB,8BAA8B,CAClD,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,wBAAwB,CAAC,CAcnC"}