@gtkx/cli 2.0.0-beta.7 → 2.0.0-beta.9

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 (155) hide show
  1. package/dist/commands/deploy.d.ts +4 -0
  2. package/dist/commands/deploy.d.ts.map +1 -1
  3. package/dist/commands/deploy.js +6 -0
  4. package/dist/commands/deploy.js.map +1 -1
  5. package/dist/deploy/depends.d.ts.map +1 -1
  6. package/dist/deploy/depends.js +24 -2
  7. package/dist/deploy/depends.js.map +1 -1
  8. package/dist/deploy/download.d.ts +7 -3
  9. package/dist/deploy/download.d.ts.map +1 -1
  10. package/dist/deploy/download.js +19 -15
  11. package/dist/deploy/download.js.map +1 -1
  12. package/dist/deploy/native-addon.d.ts +5 -0
  13. package/dist/deploy/native-addon.d.ts.map +1 -0
  14. package/dist/deploy/native-addon.js +134 -0
  15. package/dist/deploy/native-addon.js.map +1 -0
  16. package/dist/deploy/node-runtime/download.js +2 -2
  17. package/dist/deploy/node-runtime/download.js.map +1 -1
  18. package/dist/deploy/node-runtime/elf.d.ts +3 -1
  19. package/dist/deploy/node-runtime/elf.d.ts.map +1 -1
  20. package/dist/deploy/node-runtime/elf.js +3 -1
  21. package/dist/deploy/node-runtime/elf.js.map +1 -1
  22. package/dist/deploy/node-runtime/guard.d.ts.map +1 -1
  23. package/dist/deploy/node-runtime/guard.js +7 -14
  24. package/dist/deploy/node-runtime/guard.js.map +1 -1
  25. package/dist/deploy/node-runtime/index.d.ts +2 -1
  26. package/dist/deploy/node-runtime/index.d.ts.map +1 -1
  27. package/dist/deploy/node-runtime/index.js +29 -6
  28. package/dist/deploy/node-runtime/index.js.map +1 -1
  29. package/dist/deploy/node-runtime/sonames.d.ts +5 -0
  30. package/dist/deploy/node-runtime/sonames.d.ts.map +1 -0
  31. package/dist/deploy/node-runtime/sonames.js +13 -0
  32. package/dist/deploy/node-runtime/sonames.js.map +1 -0
  33. package/dist/deploy/payload/stage.d.ts +2 -1
  34. package/dist/deploy/payload/stage.d.ts.map +1 -1
  35. package/dist/deploy/payload/stage.js +22 -2
  36. package/dist/deploy/payload/stage.js.map +1 -1
  37. package/dist/deploy/run-deploy.d.ts +1 -0
  38. package/dist/deploy/run-deploy.d.ts.map +1 -1
  39. package/dist/deploy/run-deploy.js +139 -80
  40. package/dist/deploy/run-deploy.js.map +1 -1
  41. package/dist/deploy/settings/arch.d.ts +9 -3
  42. package/dist/deploy/settings/arch.d.ts.map +1 -1
  43. package/dist/deploy/settings/arch.js +27 -6
  44. package/dist/deploy/settings/arch.js.map +1 -1
  45. package/dist/deploy/settings/index.d.ts +2 -1
  46. package/dist/deploy/settings/index.d.ts.map +1 -1
  47. package/dist/deploy/settings/index.js +2 -1
  48. package/dist/deploy/settings/index.js.map +1 -1
  49. package/dist/deploy/settings/paths.d.ts +2 -1
  50. package/dist/deploy/settings/paths.d.ts.map +1 -1
  51. package/dist/deploy/settings/paths.js +6 -5
  52. package/dist/deploy/settings/paths.js.map +1 -1
  53. package/dist/deploy/targets/appimage.d.ts.map +1 -1
  54. package/dist/deploy/targets/appimage.js +1 -0
  55. package/dist/deploy/targets/appimage.js.map +1 -1
  56. package/dist/deploy/targets/flatpak-pnpm.js +4 -4
  57. package/dist/deploy/targets/flatpak-pnpm.js.map +1 -1
  58. package/dist/deploy/targets/flatpak.d.ts.map +1 -1
  59. package/dist/deploy/targets/flatpak.js +1 -0
  60. package/dist/deploy/targets/flatpak.js.map +1 -1
  61. package/dist/deploy/targets/nfpm-target.d.ts.map +1 -1
  62. package/dist/deploy/targets/nfpm-target.js +1 -0
  63. package/dist/deploy/targets/nfpm-target.js.map +1 -1
  64. package/dist/deploy/types.d.ts +4 -2
  65. package/dist/deploy/types.d.ts.map +1 -1
  66. package/dist/deploy/types.js.map +1 -1
  67. package/dist/deploy/vendored/appimagetool.js +2 -2
  68. package/dist/deploy/vendored/appimagetool.js.map +1 -1
  69. package/dist/deploy/vendored/nfpm.js +1 -1
  70. package/dist/deploy/vendored/nfpm.js.map +1 -1
  71. package/dist/dev/font-dir.d.ts +1 -1
  72. package/dist/dev/font-dir.d.ts.map +1 -1
  73. package/dist/dev/font-dir.js +0 -3
  74. package/dist/dev/font-dir.js.map +1 -1
  75. package/dist/dev/runner.d.ts.map +1 -1
  76. package/dist/dev/runner.js +19 -6
  77. package/dist/dev/runner.js.map +1 -1
  78. package/dist/internal/font-staging.d.ts +5 -2
  79. package/dist/internal/font-staging.d.ts.map +1 -1
  80. package/dist/internal/font-staging.js +76 -27
  81. package/dist/internal/font-staging.js.map +1 -1
  82. package/dist/internal/icon-staging.d.ts +3 -0
  83. package/dist/internal/icon-staging.d.ts.map +1 -0
  84. package/dist/internal/icon-staging.js +40 -0
  85. package/dist/internal/icon-staging.js.map +1 -0
  86. package/dist/internal/source-imports.js +1 -1
  87. package/dist/internal/source-imports.js.map +1 -1
  88. package/dist/vite-plugins/font-worker-env.d.ts.map +1 -1
  89. package/dist/vite-plugins/font-worker-env.js +2 -5
  90. package/dist/vite-plugins/font-worker-env.js.map +1 -1
  91. package/dist/vite-plugins/font.d.ts.map +1 -1
  92. package/dist/vite-plugins/font.js +16 -4
  93. package/dist/vite-plugins/font.js.map +1 -1
  94. package/dist/vite-plugins/icon-worker-env.d.ts +4 -0
  95. package/dist/vite-plugins/icon-worker-env.d.ts.map +1 -0
  96. package/dist/vite-plugins/icon-worker-env.js +23 -0
  97. package/dist/vite-plugins/icon-worker-env.js.map +1 -0
  98. package/dist/vite-plugins/icons.js +2 -2
  99. package/dist/vite-plugins/icons.js.map +1 -1
  100. package/dist/vite-plugins/react-compiler.js +2 -2
  101. package/dist/vite-plugins/react-compiler.js.map +1 -1
  102. package/dist/vite-plugins/resources.js +2 -2
  103. package/dist/vite-plugins/resources.js.map +1 -1
  104. package/dist/vite-plugins/settings-worker-env.js +2 -2
  105. package/dist/vite-plugins/settings-worker-env.js.map +1 -1
  106. package/dist/vite-plugins/store-links.d.ts.map +1 -1
  107. package/dist/vite-plugins/store-links.js +2 -1
  108. package/dist/vite-plugins/store-links.js.map +1 -1
  109. package/dist/vite-plugins/undeclared-library.js +2 -2
  110. package/dist/vite-plugins/undeclared-library.js.map +1 -1
  111. package/dist/vite-plugins/worker.d.ts.map +1 -1
  112. package/dist/vite-plugins/worker.js +21 -13
  113. package/dist/vite-plugins/worker.js.map +1 -1
  114. package/dist/vitest-plugin.d.ts +2 -2
  115. package/dist/vitest-plugin.d.ts.map +1 -1
  116. package/dist/vitest-plugin.js +4 -2
  117. package/dist/vitest-plugin.js.map +1 -1
  118. package/package.json +13 -13
  119. package/src/commands/deploy.ts +6 -0
  120. package/src/deploy/depends.ts +30 -3
  121. package/src/deploy/download.ts +33 -17
  122. package/src/deploy/native-addon.ts +183 -0
  123. package/src/deploy/node-runtime/download.ts +2 -2
  124. package/src/deploy/node-runtime/elf.ts +4 -1
  125. package/src/deploy/node-runtime/guard.ts +10 -14
  126. package/src/deploy/node-runtime/index.ts +42 -7
  127. package/src/deploy/node-runtime/sonames.ts +17 -0
  128. package/src/deploy/payload/stage.ts +34 -2
  129. package/src/deploy/run-deploy.ts +209 -94
  130. package/src/deploy/settings/arch.ts +40 -8
  131. package/src/deploy/settings/index.ts +4 -1
  132. package/src/deploy/settings/paths.ts +8 -6
  133. package/src/deploy/targets/appimage.ts +1 -0
  134. package/src/deploy/targets/flatpak-pnpm.ts +4 -4
  135. package/src/deploy/targets/flatpak.ts +1 -0
  136. package/src/deploy/targets/nfpm-target.ts +1 -0
  137. package/src/deploy/types.ts +4 -1
  138. package/src/deploy/vendored/appimagetool.ts +2 -2
  139. package/src/deploy/vendored/nfpm.ts +1 -1
  140. package/src/dev/font-dir.ts +1 -6
  141. package/src/dev/runner.ts +24 -6
  142. package/src/internal/font-staging.ts +99 -27
  143. package/src/internal/icon-staging.ts +58 -0
  144. package/src/internal/source-imports.ts +1 -1
  145. package/src/vite-plugins/font-worker-env.ts +2 -6
  146. package/src/vite-plugins/font.ts +21 -3
  147. package/src/vite-plugins/icon-worker-env.ts +36 -0
  148. package/src/vite-plugins/icons.ts +2 -2
  149. package/src/vite-plugins/react-compiler.ts +2 -2
  150. package/src/vite-plugins/resources.ts +2 -2
  151. package/src/vite-plugins/settings-worker-env.ts +2 -2
  152. package/src/vite-plugins/store-links.ts +2 -1
  153. package/src/vite-plugins/undeclared-library.ts +2 -2
  154. package/src/vite-plugins/worker.ts +31 -15
  155. package/src/vitest-plugin.ts +4 -2
@@ -3,7 +3,9 @@ import { info, warn } from "@gtkx/utils";
3
3
  import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { dirname, join, relative } from "node:path";
5
5
  import type {
6
+ DeployArchName,
6
7
  DeployArtifact,
8
+ DeployConfig,
7
9
  DeployManifest,
8
10
  DeployPayload,
9
11
  DeploySettings,
@@ -29,10 +31,12 @@ import { extractMetadataMessages, localizeMetadata } from "./freedesktop/localiz
29
31
  import { renderMetainfo } from "./freedesktop/metainfo.js";
30
32
  import { renderMimePackage } from "./freedesktop/mime-package.js";
31
33
  import { validateDesktopEntry, validateMetainfo } from "./freedesktop/validate.js";
32
- import { resolveNodeRuntime } from "./node-runtime/index.js";
34
+ import { resolveStagedAddon } from "./native-addon.js";
35
+ import { assertPortableRuntimeSource, resolveNodeRuntime } from "./node-runtime/index.js";
33
36
  import { collectNotices } from "./notices/collect.js";
34
37
  import { type StagedMetadata, stageOverlays, stagePayload } from "./payload/stage.js";
35
38
  import { DEFAULT_TARGETS, parseTargetList, targetsFor } from "./registry.js";
39
+ import { archesFor, hostArchName, isHostArch, parseArchList } from "./settings/arch.js";
36
40
  import { readBuildManifest } from "./settings/build-manifest.js";
37
41
  import { resolveDeploySettings } from "./settings/index.js";
38
42
  import { readPackageManifest } from "./settings/package-manifest.js";
@@ -62,31 +66,52 @@ type DeployOptions = {
62
66
  cwd: string;
63
67
  configFile?: string | undefined;
64
68
  targets?: string | undefined;
69
+ arches?: string | undefined;
65
70
  outDir?: string | undefined;
66
71
  shouldPrintManifests: boolean;
67
72
  shouldSkipBuild: boolean;
68
73
  };
69
74
 
70
- type PreflightRequest = {
75
+ type LoadedDeployConfig = {
76
+ config: Config;
77
+ configFile: string;
78
+ root: string;
79
+ deploy: DeployConfig;
80
+ };
81
+
82
+ type DeployPlan = {
83
+ loaded: LoadedDeployConfig;
71
84
  targets: DeployTarget[];
72
- settings: DeploySettings;
85
+ arches: [DeployArchName, ...DeployArchName[]];
73
86
  project: CatalogProject | null;
87
+ };
88
+
89
+ type PreflightRequest = {
90
+ plan: DeployPlan;
91
+ settings: DeploySettings;
74
92
  shouldPrintManifests: boolean;
75
93
  shouldSkipBuild: boolean;
76
94
  };
77
95
 
78
- type LoadedDeploySettings = {
79
- config: Config;
80
- configFile: string;
96
+ type BuildApplicationRequest = {
97
+ options: DeployOptions;
98
+ plan: DeployPlan;
81
99
  settings: DeploySettings;
100
+ buildOutDir: string | null;
82
101
  };
83
102
 
84
103
  type BuildPayloadRequest = {
85
104
  options: DeployOptions;
86
- loaded: LoadedDeploySettings;
87
- targets: DeployTarget[];
88
- project: CatalogProject | null;
89
- buildOutDir: string | null;
105
+ plan: DeployPlan;
106
+ settings: DeploySettings;
107
+ metadata: StagedMetadata;
108
+ };
109
+
110
+ type ArchRunRequest = {
111
+ options: DeployOptions;
112
+ plan: DeployPlan;
113
+ arch: DeployArchName;
114
+ metadata: StagedMetadata;
90
115
  };
91
116
 
92
117
  const BUILD_MODE = "production";
@@ -229,17 +254,28 @@ const sourceModeTools = (targets: DeployTarget[], settings: DeploySettings): Dep
229
254
  const isNodeRequired = (targets: DeployTarget[], settings: DeploySettings): boolean =>
230
255
  !(settings.deploy.flatpak?.mode === "source" && targets.every((target) => target.name === FLATPAK_TARGET));
231
256
 
232
- const runtimeToolsFor = (targets: DeployTarget[], settings: DeploySettings): DeployTool[] => {
233
- if (!isNodeRequired(targets, settings)) {
257
+ const archiveToolsFor = (settings: DeploySettings): DeployTool[] =>
258
+ (settings.deploy.node?.source ?? "download") === "download" ? [TAR] : [];
259
+
260
+ const stripToolsFor = (settings: DeploySettings, arches: DeployArchName[]): DeployTool[] => {
261
+ if (settings.deploy.node?.shouldStrip === false) {
234
262
  return [];
235
263
  }
236
264
 
237
- const node = settings.deploy.node ?? {};
238
- const archiveTools = (node.source ?? "download") === "download" ? [TAR] : [];
265
+ return arches.some((arch) => isHostArch(arch)) ? [STRIP] : [];
266
+ };
267
+
268
+ const runtimeToolsFor = (targets: DeployTarget[], settings: DeploySettings, arches: DeployArchName[]): DeployTool[] => {
269
+ if (!isNodeRequired(targets, settings)) {
270
+ return [];
271
+ }
239
272
 
240
- return node.shouldStrip === false ? archiveTools : [...archiveTools, STRIP];
273
+ return [...archiveToolsFor(settings), ...stripToolsFor(settings, arches)];
241
274
  };
242
275
 
276
+ const crossBuildToolsFor = (arches: DeployArchName[]): DeployTool[] =>
277
+ arches.every((arch) => isHostArch(arch)) ? [] : [TAR];
278
+
243
279
  const mutableCatalogTools = (project: CatalogProject): DeployTool[] => {
244
280
  if (project.catalogs.length === 0) {
245
281
  return [MSGGREP, XGETTEXT];
@@ -266,40 +302,77 @@ const catalogTools = (project: CatalogProject | null, shouldSkipBuild: boolean):
266
302
  return project.catalogs.length === 0 ? [] : [MSGFMT];
267
303
  };
268
304
 
269
- const preflight = ({
270
- targets,
271
- settings,
272
- project,
273
- shouldPrintManifests,
274
- shouldSkipBuild,
275
- }: PreflightRequest): void => {
305
+ const preflight = ({ plan, settings, shouldPrintManifests, shouldSkipBuild }: PreflightRequest): void => {
306
+ const { targets, arches, project } = plan;
307
+
276
308
  const packagerTools = shouldPrintManifests
277
309
  ? []
278
- : [...runtimeToolsFor(targets, settings), ...targets.flatMap((target) => target.tools)];
310
+ : [...runtimeToolsFor(targets, settings, arches), ...targets.flatMap((target) => target.tools)];
279
311
 
280
312
  const required = [
281
313
  DESKTOP_FILE_VALIDATE,
282
314
  APPSTREAMCLI,
315
+ ...crossBuildToolsFor(arches),
283
316
  ...catalogTools(project, shouldSkipBuild),
284
317
  ...sourceModeTools(targets, settings),
285
318
  ];
286
319
 
287
320
  const report = probeTools([...required, ...packagerTools]);
288
321
  assertTools(report);
322
+
323
+ if (isNodeRequired(targets, settings)) {
324
+ assertPortableRuntimeSource(settings);
325
+ }
326
+
289
327
  warnMissingOptional(report);
290
328
  warnFlatpakPermissions(targets, settings);
291
329
  warnLibraryMinimums(targets, settings);
292
330
  };
293
331
 
294
- const resolveTargetNames = (options: DeployOptions, settings: DeploySettings): string[] => {
332
+ const hostOnlyNames = (targets: DeployTarget[]): string[] =>
333
+ targets.filter((target) => target.isHostOnly).map((target) => target.name);
334
+
335
+ const assertCrossBuild = (plan: DeployPlan): void => {
336
+ const foreign = plan.arches.filter((arch) => !isHostArch(arch));
337
+
338
+ if (foreign.length === 0) {
339
+ return;
340
+ }
341
+
342
+ const hostOnly = hostOnlyNames(plan.targets);
343
+
344
+ if (hostOnly.length > 0) {
345
+ throw new Error(
346
+ `Cannot build ${hostOnly.join(" and ")} for ${foreign.join(" and ")}: those targets package with ` +
347
+ `tooling that only runs on ${hostArchName()}. Deploy them in a separate run without --arch.`,
348
+ );
349
+ }
350
+
351
+ if ((plan.loaded.deploy.node?.source ?? "download") !== "download") {
352
+ throw new Error(
353
+ 'Cannot deploy for another architecture with a `deploy.node.source` other than "download": ' +
354
+ `a host or path runtime is always ${hostArchName()}.`,
355
+ );
356
+ }
357
+ };
358
+
359
+ const resolveTargetNames = (options: DeployOptions, deploy: DeployConfig): string[] => {
295
360
  if (options.targets !== undefined) {
296
361
  return parseTargetList(options.targets);
297
362
  }
298
363
 
299
- return settings.deploy.targets ?? DEFAULT_TARGETS;
364
+ return deploy.targets ?? DEFAULT_TARGETS;
300
365
  };
301
366
 
302
- const loadSettings = async (options: DeployOptions): Promise<LoadedDeploySettings> => {
367
+ const resolveArchNames = (options: DeployOptions, deploy: DeployConfig): string[] => {
368
+ if (options.arches !== undefined) {
369
+ return parseArchList(options.arches);
370
+ }
371
+
372
+ return deploy.architectures ?? [hostArchName()];
373
+ };
374
+
375
+ const loadDeployConfig = async (options: DeployOptions): Promise<LoadedDeployConfig> => {
303
376
  const { config, configFile, root } = await loadConfig(options.cwd, {
304
377
  mode: BUILD_MODE,
305
378
  configFile: options.configFile,
@@ -309,15 +382,26 @@ const loadSettings = async (options: DeployOptions): Promise<LoadedDeploySetting
309
382
  throw missingDeployError(config.applicationId, readPackageManifest(root));
310
383
  }
311
384
 
385
+ return { config, configFile, root, deploy: config.deploy };
386
+ };
387
+
388
+ const settingsFor = (plan: DeployPlan, options: DeployOptions, arch: DeployArchName): DeploySettings =>
389
+ resolveDeploySettings({
390
+ root: plan.loaded.root,
391
+ config: plan.loaded.config,
392
+ configFile: plan.loaded.configFile,
393
+ outDirOverride: options.outDir,
394
+ arch,
395
+ });
396
+
397
+ const planDeploy = async (options: DeployOptions): Promise<DeployPlan> => {
398
+ const loaded = await loadDeployConfig(options);
399
+
312
400
  return {
313
- config,
314
- configFile,
315
- settings: resolveDeploySettings({
316
- root,
317
- config,
318
- configFile,
319
- outDirOverride: options.outDir,
320
- }),
401
+ loaded,
402
+ targets: targetsFor(resolveTargetNames(options, loaded.deploy)),
403
+ arches: archesFor(resolveArchNames(options, loaded.deploy)),
404
+ project: resolveCatalogProject(loaded.root, loaded.config.applicationId),
321
405
  };
322
406
  };
323
407
 
@@ -347,46 +431,57 @@ const synchronizeMetadataCatalogs = async (
347
431
  }
348
432
  };
349
433
 
350
- const buildPayload = async ({
434
+ const buildApplication = async ({
351
435
  options,
352
- loaded,
353
- targets,
354
- project,
436
+ plan,
437
+ settings,
355
438
  buildOutDir,
356
- }: BuildPayloadRequest): Promise<DeployPayload> => {
357
- const settings = loaded.settings;
439
+ }: BuildApplicationRequest): Promise<StagedMetadata> => {
358
440
  const templates = renderMetadata(settings);
359
441
 
360
- if (buildOutDir !== null) {
361
- info(`Building ${options.entry}`);
362
- using preparedOutput = prepareBuildOutDir(settings.paths.root, buildOutDir);
363
-
364
- await buildApp({
365
- entry: options.entry,
366
- configFile: options.configFile,
367
- vite: { root: options.cwd, build: { outDir: preparedOutput.path, emptyOutDir: false } },
368
- });
442
+ if (buildOutDir === null) {
443
+ if (plan.project !== null) {
444
+ compileCatalogs(plan.project, join(settings.paths.dist, LOCALE_DIRNAME));
445
+ }
369
446
 
370
- preparedOutput.commit();
371
- await synchronizeMetadataCatalogs(settings, templates, project);
372
- } else if (project !== null) {
373
- compileCatalogs(project, join(settings.paths.dist, LOCALE_DIRNAME));
447
+ return localizeMetadata(templates, plan.project);
374
448
  }
375
449
 
376
- const metadata = localizeMetadata(templates, project);
377
- validateMetadata(settings, metadata, isFlathubSubmission(settings, targets));
378
- const buildManifest = readBuildManifest(settings, loaded);
450
+ info(`Building ${options.entry}`);
451
+ using preparedOutput = prepareBuildOutDir(settings.paths.root, buildOutDir);
452
+
453
+ await buildApp({
454
+ entry: options.entry,
455
+ configFile: options.configFile,
456
+ vite: { root: options.cwd, build: { outDir: preparedOutput.path, emptyOutDir: false } },
457
+ });
458
+
459
+ preparedOutput.commit();
460
+ await synchronizeMetadataCatalogs(settings, templates, plan.project);
461
+
462
+ return localizeMetadata(templates, plan.project);
463
+ };
464
+
465
+ const buildArchPayload = async ({
466
+ options,
467
+ plan,
468
+ settings,
469
+ metadata,
470
+ }: BuildPayloadRequest): Promise<DeployPayload> => {
471
+ validateMetadata(settings, metadata, isFlathubSubmission(settings, plan.targets));
472
+ const buildManifest = readBuildManifest(settings, plan.loaded);
379
473
 
380
474
  const builtSettings: DeploySettings = {
381
475
  ...settings,
382
476
  paths: { ...settings.paths, schemaFiles: buildManifest.schemaFiles },
383
477
  };
384
478
 
385
- const node = options.shouldPrintManifests || !isNodeRequired(targets, builtSettings)
479
+ const node = options.shouldPrintManifests || !isNodeRequired(plan.targets, builtSettings)
386
480
  ? null
387
481
  : await resolveNodeRuntime(builtSettings);
388
482
 
389
- const stage = stagePayload({ settings: builtSettings, node, metadata });
483
+ const addon = await resolveStagedAddon(builtSettings);
484
+ const stage = stagePayload({ settings: builtSettings, node, addon, metadata });
390
485
  info(`Staged ${String(stage.length)} files into ${displayPath(builtSettings, builtSettings.paths.stage)}`);
391
486
  const notices = collectNotices({ settings: builtSettings, node, packages: buildManifest.packages });
392
487
 
@@ -438,56 +533,76 @@ const packTargets = async (
438
533
  return artifacts;
439
534
  };
440
535
 
441
- const announce = (settings: DeploySettings, targets: DeployTarget[]): void => {
442
- const names = targets.map((target) => target.name).join(", ");
536
+ const announce = (settings: DeploySettings, plan: DeployPlan): void => {
537
+ const names = plan.targets.map((target) => target.name).join(", ");
443
538
  const version = `${settings.versions.packageVersion}-${settings.versions.debRevision}`;
444
- info(`Deploying ${settings.name} ${version} as ${settings.binaryName} (${settings.arch.rpm}) to ${names}`);
539
+ const arches = plan.arches.join(", ");
540
+ info(`Deploying ${settings.name} ${version} as ${settings.binaryName} (${arches}) to ${names}`);
445
541
  };
446
542
 
447
- const runDeploy = async (options: DeployOptions): Promise<void> => {
448
- let loaded = await loadSettings(options);
449
- let settings = loaded.settings;
450
- let project = resolveCatalogProject(settings.paths.root, settings.applicationId);
451
- let targets = targetsFor(resolveTargetNames(options, settings));
452
- let buildOutDir = options.shouldSkipBuild ? null : resolveBuildOutDir(settings.paths.root, settings.paths.dist);
453
- announce(settings, targets);
543
+ const deployArch = async (request: ArchRunRequest): Promise<DeployArtifact[]> => {
544
+ const { options, plan, arch, metadata } = request;
545
+ const settings = settingsFor(plan, options, arch);
546
+ const payload = await buildArchPayload({ options, plan, settings, metadata });
547
+ const rendered = renderTargetManifests(plan.targets, payload);
454
548
 
455
- preflight({
456
- targets,
457
- settings,
458
- project,
459
- shouldPrintManifests: options.shouldPrintManifests,
460
- shouldSkipBuild: options.shouldSkipBuild,
461
- });
549
+ if (options.shouldPrintManifests) {
550
+ const count = rendered.values().toArray().flat().length;
551
+ info(`Wrote ${String(count)} manifests for ${arch}, 0 packages built`);
462
552
 
463
- if (!options.shouldSkipBuild) {
464
- await ensureGenerated(options.cwd, {
465
- shouldAnnounce: true,
466
- mode: BUILD_MODE,
467
- configFile: options.configFile,
468
- });
469
-
470
- loaded = await loadSettings(options);
471
- settings = loaded.settings;
472
- project = resolveCatalogProject(settings.paths.root, settings.applicationId);
473
- targets = targetsFor(resolveTargetNames(options, settings));
474
- buildOutDir = resolveBuildOutDir(settings.paths.root, settings.paths.dist);
553
+ return [];
475
554
  }
476
555
 
477
- prepareDeployOutDir(settings.paths.root, settings.paths.outDir);
478
- const payload = await buildPayload({ options, loaded, targets, project, buildOutDir });
479
- const rendered = renderTargetManifests(targets, payload);
556
+ return await packTargets(rendered, payload);
557
+ };
480
558
 
481
- if (options.shouldPrintManifests) {
482
- const count = rendered.values().toArray().flat().length;
483
- info(`Wrote ${String(count)} manifests, 0 packages built`);
559
+ const regenerate = async (options: DeployOptions, plan: DeployPlan): Promise<DeployPlan> => {
560
+ if (options.shouldSkipBuild) {
561
+ return plan;
562
+ }
484
563
 
564
+ await ensureGenerated(options.cwd, {
565
+ shouldAnnounce: true,
566
+ mode: BUILD_MODE,
567
+ configFile: options.configFile,
568
+ });
569
+
570
+ return await planDeploy(options);
571
+ };
572
+
573
+ const finish = (options: DeployOptions, settings: DeploySettings, artifacts: DeployArtifact[]): void => {
574
+ if (options.shouldPrintManifests) {
485
575
  return;
486
576
  }
487
577
 
488
- const artifacts = await packTargets(rendered, payload);
489
578
  const output = displayPath(settings, settings.paths.output);
490
579
  info(`Deploy complete: ${String(artifacts.length)} artifacts in ${output}`);
491
580
  };
492
581
 
582
+ const runDeploy = async (options: DeployOptions): Promise<void> => {
583
+ const planned = await planDeploy(options);
584
+ assertCrossBuild(planned);
585
+ announce(settingsFor(planned, options, planned.arches[0]), planned);
586
+
587
+ preflight({
588
+ plan: planned,
589
+ settings: settingsFor(planned, options, planned.arches[0]),
590
+ shouldPrintManifests: options.shouldPrintManifests,
591
+ shouldSkipBuild: options.shouldSkipBuild,
592
+ });
593
+
594
+ const plan = await regenerate(options, planned);
595
+ const settings = settingsFor(plan, options, plan.arches[0]);
596
+ const buildOutDir = options.shouldSkipBuild ? null : resolveBuildOutDir(settings.paths.root, settings.paths.dist);
597
+ prepareDeployOutDir(settings.paths.root, settings.paths.outDir);
598
+ const metadata = await buildApplication({ options, plan, settings, buildOutDir });
599
+ const artifacts: DeployArtifact[] = [];
600
+
601
+ for (const arch of plan.arches) {
602
+ artifacts.push(...await deployArch({ options, plan, arch, metadata }));
603
+ }
604
+
605
+ finish(options, settings, artifacts);
606
+ };
607
+
493
608
  export { runDeploy };
@@ -1,18 +1,50 @@
1
- import type { DeployArch } from "../types.js";
1
+ import { sortStrings } from "@gtkx/utils";
2
+ import type { DeployArch, DeployArchName } from "../types.js";
2
3
 
3
- const ARCH_TABLE: Record<string, DeployArch> = {
4
+ const EM_X86_64 = 62;
5
+ const EM_AARCH64 = 183;
6
+
7
+ const ARCH_TABLE: Record<DeployArchName, DeployArch> = {
4
8
  arm64: { deb: "arm64", rpm: "aarch64", flatpak: "aarch64", appimage: "aarch64", node: "arm64" },
5
9
  x64: { deb: "amd64", rpm: "x86_64", flatpak: "x86_64", appimage: "x86_64", node: "x64" },
6
10
  };
7
11
 
8
- const resolveArch = (arch: string = process.arch): DeployArch => {
9
- const resolved = ARCH_TABLE[arch];
12
+ const ELF_MACHINE_TABLE: Record<DeployArchName, number> = {
13
+ arm64: EM_AARCH64,
14
+ x64: EM_X86_64,
15
+ };
16
+
17
+ const KNOWN_ARCH_NAMES = sortStrings(Object.keys(ARCH_TABLE)).join(", ");
18
+
19
+ const isArchName = (name: string): name is DeployArchName => Object.hasOwn(ARCH_TABLE, name);
20
+
21
+ const assertKnownArch = (name: string): DeployArchName => {
22
+ if (!isArchName(name)) {
23
+ throw new Error(`Cannot deploy for ${name}: only ${KNOWN_ARCH_NAMES} are supported`);
24
+ }
25
+
26
+ return name;
27
+ };
28
+
29
+ const resolveArch = (name: DeployArchName): DeployArch => ARCH_TABLE[name];
30
+
31
+ const hostArchName = (): DeployArchName => assertKnownArch(process.arch);
32
+
33
+ const isHostArch = (name: DeployArchName): boolean => name === hostArchName();
34
+
35
+ const elfMachineFor = (name: DeployArchName): number => ELF_MACHINE_TABLE[name];
36
+
37
+ const parseArchList = (value: string): string[] => value.split(",").map((name) => name.trim()).filter(Boolean);
38
+
39
+ const archesFor = (names: string[]): [DeployArchName, ...DeployArchName[]] => {
40
+ const requested = sortStrings(new Set(names.map((name) => assertKnownArch(name.trim()))));
41
+ const [first, ...rest] = requested.map((name) => assertKnownArch(name));
10
42
 
11
- if (resolved === undefined) {
12
- throw new Error(`Cannot deploy for ${arch}: only ${Object.keys(ARCH_TABLE).join(" and ")} are supported`);
43
+ if (first === undefined) {
44
+ throw new Error(`Cannot deploy without an architecture; choose from ${KNOWN_ARCH_NAMES}`);
13
45
  }
14
46
 
15
- return resolved;
47
+ return [first, ...rest];
16
48
  };
17
49
 
18
- export { resolveArch };
50
+ export { archesFor, elfMachineFor, hostArchName, isHostArch, KNOWN_ARCH_NAMES, parseArchList, resolveArch };
@@ -1,5 +1,6 @@
1
1
  import type { Config } from "@gtkx/config";
2
2
  import type {
3
+ DeployArchName,
3
4
  DeployConfig,
4
5
  DeployDesktopAction,
5
6
  DeployDeveloper,
@@ -32,6 +33,7 @@ type SettingsRequest = {
32
33
  root: string;
33
34
  config: Config;
34
35
  configFile: string;
36
+ arch: DeployArchName;
35
37
  outDirOverride?: string | undefined;
36
38
  now?: Date | undefined;
37
39
  };
@@ -181,13 +183,14 @@ const resolveDeploySettings = (request: SettingsRequest): DeploySettings => {
181
183
  ...desktopSlice(deploy),
182
184
  extraFiles: resolveExtraFiles(request.root, deploy),
183
185
  versions: core.versions,
184
- arch: resolveArch(),
186
+ arch: resolveArch(request.arch),
185
187
  paths: resolvePaths({
186
188
  root: request.root,
187
189
  deploy,
188
190
  applicationIcon: request.config.applicationIcon,
189
191
  applicationId: request.config.applicationId,
190
192
  outDirOverride: request.outDirOverride,
193
+ arch: request.arch,
191
194
  }),
192
195
  ...resolveLibraries(request.root, request.config),
193
196
  deploy,
@@ -2,7 +2,7 @@ import type { Config } from "@gtkx/config";
2
2
  import { isPathInside } from "@gtkx/utils";
3
3
  import { lstatSync, readdirSync, statSync, writeFileSync } from "node:fs";
4
4
  import { join, relative, resolve, sep } from "node:path";
5
- import type { DeployConfig, DeployPaths } from "../types.js";
5
+ import type { DeployArchName, DeployConfig, DeployPaths } from "../types.js";
6
6
  import { resolveApplicationIcon } from "../../internal/icon-path.js";
7
7
  import { prepareOutputDirectory, readRegularFile } from "../../internal/output-directory.js";
8
8
 
@@ -12,6 +12,7 @@ type PathsRequest = {
12
12
  applicationIcon: Config["applicationIcon"];
13
13
  applicationId: Config["applicationId"];
14
14
  outDirOverride: string | undefined;
15
+ arch: DeployArchName;
15
16
  };
16
17
 
17
18
  const DEFAULT_OUT_DIR = "build";
@@ -113,16 +114,17 @@ const resolveLicenseFile = (root: string, configured: string | undefined): strin
113
114
  const resolvePaths = (request: PathsRequest): DeployPaths => {
114
115
  const { root, deploy } = request;
115
116
  const outDir = resolveOutDir(request);
117
+ const archDir = join(outDir, request.arch);
116
118
 
117
119
  return {
118
120
  root,
119
121
  dist: join(root, DIST_DIR),
120
122
  outDir,
121
- metadata: join(outDir, "metadata"),
122
- runtime: join(outDir, "runtime"),
123
- stage: join(outDir, "stage"),
124
- overlay: join(outDir, "overlay"),
125
- targets: join(outDir, "targets"),
123
+ metadata: join(archDir, "metadata"),
124
+ runtime: join(archDir, "runtime"),
125
+ stage: join(archDir, "stage"),
126
+ overlay: join(archDir, "overlay"),
127
+ targets: join(archDir, "targets"),
126
128
  output: join(outDir, "out"),
127
129
  applicationIcon: resolveApplicationIcon(root, request.applicationId, request.applicationIcon),
128
130
  licenseFile: resolveLicenseFile(root, deploy.licenseFile),
@@ -29,6 +29,7 @@ const EXTRACT_AND_RUN = "APPIMAGE_EXTRACT_AND_RUN";
29
29
  const appimageTarget: DeployTarget = {
30
30
  name: "appimage",
31
31
  prefix: PREFIX,
32
+ isHostOnly: true,
32
33
  tools: [FILE_TOOL],
33
34
  render: (payload) => renderManifests(payload),
34
35
  pack: (payload) => packAppImage(payload),
@@ -9,12 +9,12 @@ type PnpmPin = {
9
9
  sha512: string;
10
10
  };
11
11
 
12
- const PNPM_VERSION = "11.25.0";
12
+ const PNPM_VERSION = "11.26.0";
13
13
  const PNPM_MAJOR = 11;
14
- const PNPM_MINOR = 25;
14
+ const PNPM_MINOR = 26;
15
15
 
16
- const PNPM_SHA512 = "5cde925b4f075f725eb71fbae18a42ffe784524789f19b61c731cb8721ec28aaee160e01a" +
17
- "8d5af4fedb2a42cdbf300efe23db356b0d4a17b4d63e11f8ab7c956";
16
+ const PNPM_SHA512 = "fc0e2bf890b9f983611f1ab68c0637bce914390653699f83c1a78b005ed25f2c81e77920c8" +
17
+ "fd8eee2ecf0b58b28cdcb00a84d97f69bcf7c56b2f344710238664";
18
18
 
19
19
  const PNPM_DIR = "flatpak-pnpm";
20
20
  const PNPM_COMMAND = "pnpm";
@@ -21,6 +21,7 @@ const BUILDER_REF = "org.flatpak.Builder";
21
21
  const flatpakTarget: DeployTarget = {
22
22
  name: "flatpak",
23
23
  prefix: PREFIX,
24
+ isHostOnly: true,
24
25
  tools: [FLATPAK, FLATPAK_BUILDER, DESKTOP_FILE_VALIDATE, APPSTREAMCLI],
25
26
  render: (payload) => renderManifests(payload),
26
27
  pack: (payload) => Promise.try(() => packFlatpak(payload)),
@@ -32,6 +32,7 @@ const packPackage = async (payload: DeployPayload, packager: NfpmPackager): Prom
32
32
  const nfpmTarget = (packager: NfpmPackager): DeployTarget => ({
33
33
  name: packager,
34
34
  prefix: PREFIX,
35
+ isHostOnly: false,
35
36
  tools: [TAR],
36
37
  render: (payload) => renderManifests(payload, packager),
37
38
  pack: (payload) => packPackage(payload, packager),
@@ -2,6 +2,7 @@ import type { Config } from "@gtkx/config";
2
2
  import type { ResolvedApplicationIcon } from "../internal/icon-path.js";
3
3
 
4
4
  type DeployConfig = NonNullable<Config["deploy"]>;
5
+ type DeployArchName = NonNullable<DeployConfig["architectures"]>[number];
5
6
  type DeployTargetName = NonNullable<DeployConfig["targets"]>[number];
6
7
  type PackageFamily = "arch" | "debian" | "fedora" | "suse" | "unknown";
7
8
 
@@ -94,7 +95,7 @@ type DeployArch = {
94
95
  rpm: string;
95
96
  flatpak: string;
96
97
  appimage: string;
97
- node: string;
98
+ node: DeployArchName;
98
99
  };
99
100
 
100
101
  type DeployPaths = {
@@ -170,6 +171,7 @@ type DeployArtifact = {
170
171
  type DeployTarget = {
171
172
  name: DeployTargetName;
172
173
  prefix: string;
174
+ isHostOnly: boolean;
173
175
  tools: DeployTool[];
174
176
  render: (payload: DeployPayload) => DeployManifest[];
175
177
  pack: (payload: DeployPayload, manifests: DeployManifest[]) => Promise<DeployArtifact[]>;
@@ -177,6 +179,7 @@ type DeployTarget = {
177
179
 
178
180
  export type {
179
181
  DeployArch,
182
+ DeployArchName,
180
183
  DeployArtifact,
181
184
  DeployConfig,
182
185
  DeployDesktopAction,
@@ -39,7 +39,7 @@ const resolveAppimageTooling = async (arch: string): Promise<AppimageTooling> =>
39
39
  url: `${APPIMAGETOOL_URL}/appimagetool-${arch}.AppImage`,
40
40
  dest: join(dir, `appimagetool-${arch}`),
41
41
  label: `appimagetool ${APPIMAGETOOL_VERSION}`,
42
- sha256: digestFor(APPIMAGETOOL_DIGESTS, arch, "appimagetool"),
42
+ digest: digestFor(APPIMAGETOOL_DIGESTS, arch, "appimagetool"),
43
43
  mode: EXECUTABLE_MODE,
44
44
  });
45
45
 
@@ -47,7 +47,7 @@ const resolveAppimageTooling = async (arch: string): Promise<AppimageTooling> =>
47
47
  url: `${RUNTIME_URL}/runtime-${arch}`,
48
48
  dest: join(dir, `runtime-${arch}`),
49
49
  label: `the AppImage runtime ${RUNTIME_VERSION}`,
50
- sha256: digestFor(RUNTIME_DIGESTS, arch, "the AppImage runtime"),
50
+ digest: digestFor(RUNTIME_DIGESTS, arch, "the AppImage runtime"),
51
51
  mode: EXECUTABLE_MODE,
52
52
  });
53
53
 
@@ -56,7 +56,7 @@ const downloadNfpm = async (dir: string, binary: string): Promise<string> => {
56
56
  url: `${NFPM_BASE_URL}/${assetName}`,
57
57
  dest: join(dir, assetName),
58
58
  label: `nfpm ${NFPM_VERSION}`,
59
- sha256: digestFor(process.arch),
59
+ digest: digestFor(process.arch),
60
60
  });
61
61
 
62
62
  extractNfpm(archive, binary);