@gtkx/cli 1.1.0 → 1.2.1

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 (153) hide show
  1. package/dist/builder.d.ts.map +1 -1
  2. package/dist/builder.js +2 -0
  3. package/dist/builder.js.map +1 -1
  4. package/dist/codegen/report.d.ts.map +1 -1
  5. package/dist/codegen/report.js +3 -0
  6. package/dist/codegen/report.js.map +1 -1
  7. package/dist/codegen/run-codegen.d.ts +1 -0
  8. package/dist/codegen/run-codegen.d.ts.map +1 -1
  9. package/dist/codegen/run-codegen.js +7 -1
  10. package/dist/codegen/run-codegen.js.map +1 -1
  11. package/dist/commands/docs.d.ts.map +1 -1
  12. package/dist/commands/docs.js +1 -0
  13. package/dist/commands/docs.js.map +1 -1
  14. package/dist/deploy/freedesktop/copyright.d.ts +2 -2
  15. package/dist/deploy/freedesktop/copyright.d.ts.map +1 -1
  16. package/dist/deploy/freedesktop/copyright.js +105 -14
  17. package/dist/deploy/freedesktop/copyright.js.map +1 -1
  18. package/dist/deploy/install-hints.d.ts.map +1 -1
  19. package/dist/deploy/install-hints.js +7 -4
  20. package/dist/deploy/install-hints.js.map +1 -1
  21. package/dist/deploy/node-runtime/download.d.ts +5 -1
  22. package/dist/deploy/node-runtime/download.d.ts.map +1 -1
  23. package/dist/deploy/node-runtime/download.js +6 -3
  24. package/dist/deploy/node-runtime/download.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 +10 -9
  28. package/dist/deploy/node-runtime/index.js.map +1 -1
  29. package/dist/deploy/node-runtime/license.d.ts +4 -0
  30. package/dist/deploy/node-runtime/license.d.ts.map +1 -0
  31. package/dist/deploy/node-runtime/license.js +19 -0
  32. package/dist/deploy/node-runtime/license.js.map +1 -0
  33. package/dist/deploy/notices/collect.d.ts +8 -0
  34. package/dist/deploy/notices/collect.d.ts.map +1 -0
  35. package/dist/deploy/notices/collect.js +17 -0
  36. package/dist/deploy/notices/collect.js.map +1 -0
  37. package/dist/deploy/notices/dependencies.d.ts +5 -0
  38. package/dist/deploy/notices/dependencies.d.ts.map +1 -0
  39. package/dist/deploy/notices/dependencies.js +39 -0
  40. package/dist/deploy/notices/dependencies.js.map +1 -0
  41. package/dist/deploy/notices/gtkx.d.ts +6 -0
  42. package/dist/deploy/notices/gtkx.d.ts.map +1 -0
  43. package/dist/deploy/notices/gtkx.js +68 -0
  44. package/dist/deploy/notices/gtkx.js.map +1 -0
  45. package/dist/deploy/notices/libraries.d.ts +4 -0
  46. package/dist/deploy/notices/libraries.d.ts.map +1 -0
  47. package/dist/deploy/notices/libraries.js +60 -0
  48. package/dist/deploy/notices/libraries.js.map +1 -0
  49. package/dist/deploy/notices/node-runtime.d.ts +4 -0
  50. package/dist/deploy/notices/node-runtime.d.ts.map +1 -0
  51. package/dist/deploy/notices/node-runtime.js +78 -0
  52. package/dist/deploy/notices/node-runtime.js.map +1 -0
  53. package/dist/deploy/notices/packages.d.ts +13 -0
  54. package/dist/deploy/notices/packages.d.ts.map +1 -0
  55. package/dist/deploy/notices/packages.js +115 -0
  56. package/dist/deploy/notices/packages.js.map +1 -0
  57. package/dist/deploy/notices/render.d.ts +4 -0
  58. package/dist/deploy/notices/render.d.ts.map +1 -0
  59. package/dist/deploy/notices/render.js +41 -0
  60. package/dist/deploy/notices/render.js.map +1 -0
  61. package/dist/deploy/notices/text.d.ts +5 -0
  62. package/dist/deploy/notices/text.d.ts.map +1 -0
  63. package/dist/deploy/notices/text.js +34 -0
  64. package/dist/deploy/notices/text.js.map +1 -0
  65. package/dist/deploy/payload/copy-tree.d.ts +2 -1
  66. package/dist/deploy/payload/copy-tree.d.ts.map +1 -1
  67. package/dist/deploy/payload/copy-tree.js +3 -1
  68. package/dist/deploy/payload/copy-tree.js.map +1 -1
  69. package/dist/deploy/payload/stage.d.ts +7 -3
  70. package/dist/deploy/payload/stage.d.ts.map +1 -1
  71. package/dist/deploy/payload/stage.js +25 -14
  72. package/dist/deploy/payload/stage.js.map +1 -1
  73. package/dist/deploy/run-deploy.d.ts.map +1 -1
  74. package/dist/deploy/run-deploy.js +28 -7
  75. package/dist/deploy/run-deploy.js.map +1 -1
  76. package/dist/deploy/settings/extra-files.d.ts +4 -0
  77. package/dist/deploy/settings/extra-files.d.ts.map +1 -0
  78. package/dist/deploy/settings/extra-files.js +11 -0
  79. package/dist/deploy/settings/extra-files.js.map +1 -0
  80. package/dist/deploy/settings/index.d.ts.map +1 -1
  81. package/dist/deploy/settings/index.js +2 -1
  82. package/dist/deploy/settings/index.js.map +1 -1
  83. package/dist/deploy/settings/package-manifest.d.ts +2 -0
  84. package/dist/deploy/settings/package-manifest.d.ts.map +1 -1
  85. package/dist/deploy/settings/package-manifest.js +8 -0
  86. package/dist/deploy/settings/package-manifest.js.map +1 -1
  87. package/dist/deploy/settings/paths.d.ts +2 -1
  88. package/dist/deploy/settings/paths.d.ts.map +1 -1
  89. package/dist/deploy/settings/paths.js +1 -1
  90. package/dist/deploy/settings/paths.js.map +1 -1
  91. package/dist/deploy/targets/flatpak-manifest.d.ts +3 -1
  92. package/dist/deploy/targets/flatpak-manifest.d.ts.map +1 -1
  93. package/dist/deploy/targets/flatpak-manifest.js +50 -4
  94. package/dist/deploy/targets/flatpak-manifest.js.map +1 -1
  95. package/dist/deploy/targets/flatpak-pnpm.d.ts +15 -0
  96. package/dist/deploy/targets/flatpak-pnpm.d.ts.map +1 -0
  97. package/dist/deploy/targets/flatpak-pnpm.js +69 -0
  98. package/dist/deploy/targets/flatpak-pnpm.js.map +1 -0
  99. package/dist/deploy/targets/flatpak-source.d.ts.map +1 -1
  100. package/dist/deploy/targets/flatpak-source.js +70 -7
  101. package/dist/deploy/targets/flatpak-source.js.map +1 -1
  102. package/dist/deploy/targets/flatpak-sources.d.ts +5 -3
  103. package/dist/deploy/targets/flatpak-sources.d.ts.map +1 -1
  104. package/dist/deploy/targets/flatpak-sources.js +23 -9
  105. package/dist/deploy/targets/flatpak-sources.js.map +1 -1
  106. package/dist/deploy/targets/flatpak.d.ts.map +1 -1
  107. package/dist/deploy/targets/flatpak.js +3 -2
  108. package/dist/deploy/targets/flatpak.js.map +1 -1
  109. package/dist/deploy/tools.d.ts +3 -1
  110. package/dist/deploy/tools.d.ts.map +1 -1
  111. package/dist/deploy/tools.js +17 -3
  112. package/dist/deploy/tools.js.map +1 -1
  113. package/dist/deploy/types.d.ts +22 -2
  114. package/dist/deploy/types.d.ts.map +1 -1
  115. package/dist/deploy/types.js.map +1 -1
  116. package/dist/vite-plugins/bundled-packages.d.ts +5 -0
  117. package/dist/vite-plugins/bundled-packages.d.ts.map +1 -0
  118. package/dist/vite-plugins/bundled-packages.js +63 -0
  119. package/dist/vite-plugins/bundled-packages.js.map +1 -0
  120. package/package.json +9 -9
  121. package/src/builder.ts +2 -0
  122. package/src/codegen/report.ts +4 -0
  123. package/src/codegen/run-codegen.ts +11 -1
  124. package/src/commands/docs.ts +1 -0
  125. package/src/deploy/freedesktop/copyright.ts +153 -18
  126. package/src/deploy/install-hints.ts +9 -6
  127. package/src/deploy/node-runtime/download.ts +13 -4
  128. package/src/deploy/node-runtime/index.ts +17 -11
  129. package/src/deploy/node-runtime/license.ts +24 -0
  130. package/src/deploy/notices/collect.ts +26 -0
  131. package/src/deploy/notices/dependencies.ts +53 -0
  132. package/src/deploy/notices/gtkx.ts +92 -0
  133. package/src/deploy/notices/libraries.ts +81 -0
  134. package/src/deploy/notices/node-runtime.ts +101 -0
  135. package/src/deploy/notices/packages.ts +168 -0
  136. package/src/deploy/notices/render.ts +52 -0
  137. package/src/deploy/notices/text.ts +42 -0
  138. package/src/deploy/payload/copy-tree.ts +5 -1
  139. package/src/deploy/payload/stage.ts +40 -16
  140. package/src/deploy/run-deploy.ts +34 -6
  141. package/src/deploy/settings/extra-files.ts +21 -0
  142. package/src/deploy/settings/index.ts +2 -1
  143. package/src/deploy/settings/package-manifest.ts +12 -0
  144. package/src/deploy/settings/paths.ts +1 -1
  145. package/src/deploy/targets/flatpak-manifest.ts +69 -4
  146. package/src/deploy/targets/flatpak-pnpm.ts +115 -0
  147. package/src/deploy/targets/flatpak-source.ts +105 -6
  148. package/src/deploy/targets/flatpak-sources.ts +36 -9
  149. package/src/deploy/targets/flatpak.ts +3 -2
  150. package/src/deploy/tools.ts +23 -2
  151. package/src/deploy/types.ts +27 -1
  152. package/src/vite-plugins/bundled-packages.ts +95 -0
  153. package/LICENSE +0 -373
@@ -0,0 +1,42 @@
1
+ import { readdirSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ const LICENSE_FILE = /^(?:licen[cs]e|copying|notice)(?:[-._].*)?$/i;
5
+ const COPYRIGHT_LINE = /^(?:Copyright\b|©)/;
6
+ const MAX_COPYRIGHT_LINES = 20;
7
+ const MORE_COPYRIGHT = "and the other holders named in the license text";
8
+
9
+ const readLicenseText = (path: string): string | null => {
10
+ try {
11
+ return readFileSync(path, "utf8").trimEnd();
12
+ } catch {
13
+ return null;
14
+ }
15
+ };
16
+
17
+ const licenseFilesIn = (dir: string): string[] =>
18
+ readdirSync(dir, { withFileTypes: true })
19
+ .filter((entry) => entry.isFile() && LICENSE_FILE.test(entry.name))
20
+ .map((entry) => entry.name)
21
+ .toSorted((left, right) => left.localeCompare(right));
22
+
23
+ const licenseTextIn = (dir: string): string | null => {
24
+ const texts = licenseFilesIn(dir)
25
+ .map((name) => readLicenseText(join(dir, name)))
26
+ .filter((text) => text !== null);
27
+
28
+ return texts.length === 0 ? null : texts.join("\n\n");
29
+ };
30
+
31
+ const copyrightLines = (text: string | null): string[] => {
32
+ const found = (text ?? "")
33
+ .split("\n")
34
+ .map((line) => line.trim())
35
+ .filter((line) => COPYRIGHT_LINE.test(line));
36
+
37
+ const unique = [...new Set(found)];
38
+
39
+ return unique.length > MAX_COPYRIGHT_LINES ? [...unique.slice(0, MAX_COPYRIGHT_LINES), MORE_COPYRIGHT] : unique;
40
+ };
41
+
42
+ export { copyrightLines, licenseTextIn, readLicenseText };
@@ -7,6 +7,7 @@ const EXECUTABLE_MODE = 0o755;
7
7
  const READABLE_MODE = 0o644;
8
8
  const EXECUTABLE_SUFFIXES = [".node", ".so"];
9
9
  const MODE_MASK = 0o7777;
10
+ const EXECUTE_MASK = 0o111;
10
11
 
11
12
  const modeFor = (rel: string): number => {
12
13
  const name = rel.split("/").at(-1) ?? rel;
@@ -44,8 +45,11 @@ const writeInto = (root: string, rel: string, contents: string, mode = READABLE_
44
45
 
45
46
  const sourceMode = (path: string): number => statSync(path).mode & MODE_MASK;
46
47
 
48
+ const executableModeFor = (path: string): number =>
49
+ (sourceMode(path) & EXECUTE_MASK) === 0 ? READABLE_MODE : EXECUTABLE_MODE;
50
+
47
51
  const copyTree = (root: string, relBase: string, sourceDir: string): StagedFile[] =>
48
52
  listFilesRecursive(sourceDir)
49
53
  .map((file) => copyInto(root, join(relBase, file.rel), file.absPath, sourceMode(file.absPath)));
50
54
 
51
- export { copyInto, copyTree, EXECUTABLE_MODE, writeInto };
55
+ export { copyInto, copyTree, EXECUTABLE_MODE, executableModeFor, writeInto };
@@ -1,13 +1,15 @@
1
1
  import { sortStringsBy } from "@gtkx/utils";
2
2
  import { existsSync, rmSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
- import type { DeploySettings, DeployTargetName, NodeRuntime, StagedFile } from "../types.js";
4
+ import type { DeploySettings, DeployTargetName, NodeRuntime, NoticeSection, StagedFile } from "../types.js";
5
5
  import { listFilesRecursive } from "../../internal/list-files.js";
6
+ import { BUNDLED_PACKAGES_FILENAME } from "../../vite-plugins/bundled-packages.js";
6
7
  import { BUNDLE_FILENAME } from "../../vite-plugins/esm-extension.js";
7
8
  import { renderCopyright } from "../freedesktop/copyright.js";
8
9
  import { renderDbusService } from "../freedesktop/dbus-service.js";
9
10
  import { renderDesktopEntry } from "../freedesktop/desktop-entry.js";
10
- import { copyInto, EXECUTABLE_MODE, writeInto } from "./copy-tree.js";
11
+ import { renderNotices } from "../notices/render.js";
12
+ import { copyInto, EXECUTABLE_MODE, executableModeFor, writeInto } from "./copy-tree.js";
11
13
  import { stageIcons } from "./icons.js";
12
14
  import { NODE_FILENAME, renderLauncher } from "./launcher.js";
13
15
  import { stageSchemas } from "./schemas.js";
@@ -25,6 +27,8 @@ type StagedMetadata = {
25
27
  };
26
28
 
27
29
  const DIST_ICONS_DIR = "icons";
30
+ const NOTICES_FILENAME = "THIRD-PARTY-NOTICES";
31
+ const NODE_LICENSE_FILENAME = "LICENSE";
28
32
  const SHARE_APPLICATIONS = "share/applications";
29
33
  const SHARE_METAINFO = "share/metainfo";
30
34
  const SHARE_MIME_PACKAGES = "share/mime/packages";
@@ -40,7 +44,14 @@ const PREFIX_FOR: Record<DeployTargetName, string> = {
40
44
  };
41
45
 
42
46
  const libDirFor = (settings: DeploySettings): string => `lib/${settings.binaryName}`;
47
+ const licenseDirFor = (settings: DeploySettings): string => `${SHARE_LICENSES}/${settings.binaryName}`;
48
+ const licenseDestination = (settings: DeploySettings): string => `${licenseDirFor(settings)}/LICENSE`;
49
+ const noticesDestination = (settings: DeploySettings): string => `${licenseDirFor(settings)}/${NOTICES_FILENAME}`;
43
50
  const isIconAsset = (rel: string): boolean => rel === DIST_ICONS_DIR || rel.startsWith(`${DIST_ICONS_DIR}/`);
51
+ const isBuildMetadata = (rel: string): boolean => rel === BUNDLED_PACKAGES_FILENAME;
52
+
53
+ const nodeLicenseDestination = (settings: DeploySettings): string =>
54
+ `${licenseDirFor(settings)}/${NODE_FILENAME}/${NODE_LICENSE_FILENAME}`;
44
55
 
45
56
  const stageRuntimeFiles = (settings: DeploySettings, root: string): StagedFile[] => {
46
57
  const dist = settings.paths.dist;
@@ -50,7 +61,7 @@ const stageRuntimeFiles = (settings: DeploySettings, root: string): StagedFile[]
50
61
  }
51
62
 
52
63
  return listFilesRecursive(dist)
53
- .filter((file) => !isIconAsset(file.rel))
64
+ .filter((file) => !isIconAsset(file.rel) && !isBuildMetadata(file.rel))
54
65
  .map((file) => copyInto(root, join(libDirFor(settings), file.rel), file.absPath));
55
66
  };
56
67
 
@@ -66,8 +77,11 @@ const stageMetadata = (settings: DeploySettings, root: string, metadata: StagedM
66
77
  ];
67
78
 
68
79
  const stageExtraFiles = (settings: DeploySettings, root: string): StagedFile[] =>
69
- Object.entries(settings.extraFiles).map(([destination, source]) =>
70
- copyInto(root, destination, resolve(settings.paths.root, source)));
80
+ settings.extraFiles.map((file) => {
81
+ const source = resolve(settings.paths.root, file.source);
82
+
83
+ return copyInto(root, file.destination, source, file.mode ?? executableModeFor(source));
84
+ });
71
85
 
72
86
  const byRelativePath = (files: StagedFile[]): StagedFile[] => {
73
87
  const latest: Map<string, StagedFile> = new Map();
@@ -114,7 +128,7 @@ const stageActivation = (settings: DeploySettings, root: string, target: DeployT
114
128
  )];
115
129
  };
116
130
 
117
- const stageOverlay = (settings: DeploySettings, target: DeployTargetName): StagedFile[] => {
131
+ const stageOverlay = (settings: DeploySettings, target: DeployTargetName, notices: NoticeSection[]): StagedFile[] => {
118
132
  const root = join(settings.paths.overlay, target);
119
133
  rmSync(root, { recursive: true, force: true, maxRetries: 5 });
120
134
  const licenseFile = settings.paths.licenseFile;
@@ -123,23 +137,33 @@ const stageOverlay = (settings: DeploySettings, target: DeployTargetName): Stage
123
137
  if (target === "deb") {
124
138
  return [
125
139
  ...activation,
126
- writeInto(root, join(SHARE_DOC, settings.binaryName, "copyright"), renderCopyright(settings)),
140
+ writeInto(root, join(SHARE_DOC, settings.binaryName, "copyright"), renderCopyright(settings, notices)),
127
141
  ];
128
142
  }
129
143
 
130
144
  return [
131
145
  ...activation,
132
- ...(licenseFile === null
133
- ? []
134
- : [copyInto(root, join(SHARE_LICENSES, settings.binaryName, "LICENSE"), licenseFile)]),
146
+ ...(licenseFile === null ? [] : [copyInto(root, licenseDestination(settings), licenseFile)]),
147
+ writeInto(root, noticesDestination(settings), renderNotices(settings, notices)),
135
148
  ];
136
149
  };
137
150
 
138
- const stageOverlays = (settings: DeploySettings): Record<DeployTargetName, StagedFile[]> => ({
139
- appimage: stageOverlay(settings, "appimage"),
140
- deb: stageOverlay(settings, "deb"),
141
- flatpak: stageOverlay(settings, "flatpak"),
142
- rpm: stageOverlay(settings, "rpm"),
151
+ const stageOverlays = (
152
+ settings: DeploySettings,
153
+ notices: NoticeSection[],
154
+ ): Record<DeployTargetName, StagedFile[]> => ({
155
+ appimage: stageOverlay(settings, "appimage", notices),
156
+ deb: stageOverlay(settings, "deb", notices),
157
+ flatpak: stageOverlay(settings, "flatpak", notices),
158
+ rpm: stageOverlay(settings, "rpm", notices),
143
159
  });
144
160
 
145
- export { type StagedMetadata, stageOverlays, stagePayload };
161
+ export {
162
+ licenseDestination,
163
+ nodeLicenseDestination,
164
+ NOTICES_FILENAME,
165
+ noticesDestination,
166
+ type StagedMetadata,
167
+ stageOverlays,
168
+ stagePayload,
169
+ };
@@ -17,16 +17,20 @@ import { renderMetainfo } from "./freedesktop/metainfo.js";
17
17
  import { renderMimePackage } from "./freedesktop/mime-package.js";
18
18
  import { validateDesktopEntry, validateMetainfo } from "./freedesktop/validate.js";
19
19
  import { resolveNodeRuntime } from "./node-runtime/index.js";
20
+ import { collectNotices } from "./notices/collect.js";
20
21
  import { type StagedMetadata, stageOverlays, stagePayload } from "./payload/stage.js";
21
22
  import { DEFAULT_TARGETS, parseTargetList, targetsFor } from "./registry.js";
22
23
  import { resolveDeploySettings } from "./settings/index.js";
23
24
  import { readPackageManifest } from "./settings/package-manifest.js";
24
25
  import { missingDeployError } from "./settings/starter.js";
26
+ import { finishArgsFor, hasDisplaySocket } from "./targets/flatpak-manifest.js";
27
+ import { detectPackageManager } from "./targets/flatpak-sources.js";
25
28
  import {
26
29
  APPSTREAMCLI,
27
30
  assertTools,
28
31
  DESKTOP_FILE_VALIDATE,
29
32
  FLATPAK_NODE_GENERATOR,
33
+ FLATPAK_NODE_GENERATOR_PNPM,
30
34
  probeTools,
31
35
  STRIP,
32
36
  TAR,
@@ -76,10 +80,19 @@ const validateMetadata = (settings: DeploySettings, metadata: StagedMetadata, ar
76
80
  info("Validated the desktop entry and the metainfo");
77
81
  };
78
82
 
79
- const warnMissingNetwork = (settings: DeploySettings): void => {
80
- const finishArgs = settings.deploy.flatpak?.finishArgs;
83
+ const warnMissingDisplay = (settings: DeploySettings, finishArgs: string[]): void => {
84
+ if (hasDisplaySocket(finishArgs)) {
85
+ return;
86
+ }
87
+
88
+ warn(
89
+ `The flatpak permissions grant no display socket, so ${settings.name} will start without a window. ` +
90
+ "Grant one through `deploy.flatpak.finishArgs`, such as `--socket=wayland`.",
91
+ );
92
+ };
81
93
 
82
- if (finishArgs === undefined || finishArgs.includes(NETWORK_ARG)) {
94
+ const warnMissingNetwork = (settings: DeploySettings, finishArgs: string[]): void => {
95
+ if (settings.deploy.flatpak?.finishArgs === undefined || finishArgs.includes(NETWORK_ARG)) {
83
96
  return;
84
97
  }
85
98
 
@@ -90,10 +103,24 @@ const warnMissingNetwork = (settings: DeploySettings): void => {
90
103
  warn(`This app declares ${WEBKIT_LIBRARY} but its flatpak permissions omit ${NETWORK_ARG}, so pages will not load`);
91
104
  };
92
105
 
106
+ const warnFlatpakPermissions = (targets: DeployTarget[], settings: DeploySettings): void => {
107
+ if (targets.every((target) => target.name !== "flatpak")) {
108
+ return;
109
+ }
110
+
111
+ const finishArgs = finishArgsFor(settings);
112
+ warnMissingDisplay(settings, finishArgs);
113
+ warnMissingNetwork(settings, finishArgs);
114
+ };
115
+
93
116
  const sourceModeTools = (targets: DeployTarget[], settings: DeploySettings): DeployTool[] => {
94
117
  const isFlatpakSource = settings.deploy.flatpak?.mode === "source";
95
118
 
96
- return isFlatpakSource && targets.some((target) => target.name === "flatpak") ? [FLATPAK_NODE_GENERATOR] : [];
119
+ if (!isFlatpakSource || targets.every((target) => target.name !== "flatpak")) {
120
+ return [];
121
+ }
122
+
123
+ return detectPackageManager(settings) === "pnpm" ? [FLATPAK_NODE_GENERATOR_PNPM] : [FLATPAK_NODE_GENERATOR];
97
124
  };
98
125
 
99
126
  const isNodeRequired = (targets: DeployTarget[], settings: DeploySettings): boolean =>
@@ -119,7 +146,7 @@ const preflight = (targets: DeployTarget[], settings: DeploySettings, shouldPrin
119
146
  const report = probeTools([...required, ...packagerTools]);
120
147
  assertTools(report);
121
148
  warnMissingOptional(report);
122
- warnMissingNetwork(settings);
149
+ warnFlatpakPermissions(targets, settings);
123
150
  };
124
151
 
125
152
  const resolveTargetNames = (options: DeployOptions, settings: DeploySettings): string[] => {
@@ -159,8 +186,9 @@ const buildPayload = async (
159
186
 
160
187
  const stage = stagePayload({ settings, node, metadata });
161
188
  info(`Staged ${String(stage.length)} files into ${displayPath(settings, settings.paths.stage)}`);
189
+ const notices = collectNotices({ settings, node });
162
190
 
163
- return { settings, node, stage, overlays: stageOverlays(settings) };
191
+ return { settings, node, stage, notices, overlays: stageOverlays(settings, notices) };
164
192
  };
165
193
 
166
194
  const renderTargetManifests = (
@@ -0,0 +1,21 @@
1
+ import type { DeployConfig, DeployExtraFile } from "../types.js";
2
+
3
+ type ExtraFiles = NonNullable<DeployConfig["extraFiles"]>;
4
+ type ExtraFileEntry = NonNullable<ExtraFiles[string]>;
5
+
6
+ const OCTAL_RADIX = 8;
7
+
8
+ const resolveExtraFile = (destination: string, entry: ExtraFileEntry): DeployExtraFile => {
9
+ if (typeof entry === "string") {
10
+ return { destination, source: entry, mode: null };
11
+ }
12
+
13
+ const mode = entry.mode === undefined ? null : Number.parseInt(entry.mode, OCTAL_RADIX);
14
+
15
+ return { destination, source: entry.source, mode };
16
+ };
17
+
18
+ const resolveExtraFiles = (deploy: DeployConfig): DeployExtraFile[] =>
19
+ Object.entries(deploy.extraFiles ?? {}).map(([destination, entry]) => resolveExtraFile(destination, entry));
20
+
21
+ export { resolveExtraFiles };
@@ -9,6 +9,7 @@ import type {
9
9
  } from "../types.js";
10
10
  import { resolveArch } from "./arch.js";
11
11
  import { resolveExecToken, resolveFileAssociations, resolveMimeTypes } from "./exec.js";
12
+ import { resolveExtraFiles } from "./extra-files.js";
12
13
  import {
13
14
  type IdentityRequest,
14
15
  resolveBinaryName,
@@ -177,7 +178,7 @@ const resolveDeploySettings = (request: SettingsRequest): DeploySettings => {
177
178
  ...identitySlice(core),
178
179
  ...metadataSlice(request.root, core),
179
180
  ...desktopSlice(deploy),
180
- extraFiles: deploy.extraFiles ?? {},
181
+ extraFiles: resolveExtraFiles(deploy),
181
182
  versions: core.versions,
182
183
  arch: resolveArch(),
183
184
  paths: resolvePaths({ root: request.root, deploy, outDirOverride: request.outDirOverride }),
@@ -13,6 +13,8 @@ type PackageManifest = {
13
13
  description: string | null;
14
14
  license: string | null;
15
15
  homepage: string | null;
16
+ repository: string | null;
17
+ packageManager: string | null;
16
18
  author: ManifestAuthor;
17
19
  };
18
20
 
@@ -28,6 +30,14 @@ const parseAuthorString = (author: string): ManifestAuthor => {
28
30
  return { name: optionalString(groups?.name), email: optionalString(groups?.email) };
29
31
  };
30
32
 
33
+ const parseRepository = (repository: unknown): string | null => {
34
+ if (isRecord(repository)) {
35
+ return optionalString(repository.url);
36
+ }
37
+
38
+ return optionalString(repository);
39
+ };
40
+
31
41
  const parseAuthor = (author: unknown): ManifestAuthor => {
32
42
  if (typeof author === "string") {
33
43
  return parseAuthorString(author);
@@ -59,6 +69,8 @@ const readPackageManifest = (root: string): PackageManifest => {
59
69
  description: optionalString(manifest.description),
60
70
  license: optionalString(manifest.license),
61
71
  homepage: optionalString(manifest.homepage),
72
+ repository: parseRepository(manifest.repository),
73
+ packageManager: optionalString(manifest.packageManager),
62
74
  author: parseAuthor(manifest.author),
63
75
  };
64
76
  };
@@ -112,4 +112,4 @@ const resolvePaths = (request: PathsRequest): DeployPaths => {
112
112
  };
113
113
  };
114
114
 
115
- export { resolvePaths };
115
+ export { isInside, resolvePaths };
@@ -12,10 +12,76 @@ const DEFAULT_RUNTIME_VERSION = "50";
12
12
  const DEFAULT_SDK = "org.gnome.Sdk";
13
13
  const DEFAULT_FINISH_ARGS = ["--share=ipc", "--socket=wayland", "--socket=fallback-x11", "--device=dri"];
14
14
  const DEFAULT_CLEANUP = ["/include", "/share/pkgconfig", "*.la", "*.a"];
15
+ const DISPLAY_SOCKETS = ["fallback-x11", "wayland", "x11"];
16
+ const SOCKET_ARG = "--socket=";
17
+ const NO_SOCKET_ARG = "--nosocket=";
18
+
19
+ const NEGATED_ARGS: [string, string][] = [
20
+ ["--device=", "--nodevice="],
21
+ ["--share=", "--unshare="],
22
+ [SOCKET_ARG, NO_SOCKET_ARG],
23
+ ];
15
24
 
16
25
  const branchFor = (settings: DeploySettings): string => settings.deploy.flatpak?.branch ?? DEFAULT_BRANCH;
17
26
  const isSourceMode = (settings: DeploySettings): boolean => settings.deploy.flatpak?.mode === "source";
18
27
 
28
+ const negationFor = (arg: string): string | null => {
29
+ const negated = NEGATED_ARGS.find(([prefix]) => arg.startsWith(prefix));
30
+
31
+ if (negated === undefined) {
32
+ return null;
33
+ }
34
+
35
+ const [prefix, negation] = negated;
36
+
37
+ return `${negation}${arg.slice(prefix.length)}`;
38
+ };
39
+
40
+ const mergeArgs = (defaults: string[], overrides: string[]): string[] => {
41
+ const kept = defaults.filter((arg) => {
42
+ const negation = negationFor(arg);
43
+
44
+ return negation === null || !overrides.includes(negation);
45
+ });
46
+
47
+ return [...new Set([...kept, ...overrides])];
48
+ };
49
+
50
+ const finishArgsFor = (settings: DeploySettings): string[] =>
51
+ mergeArgs(DEFAULT_FINISH_ARGS, settings.deploy.flatpak?.finishArgs ?? []);
52
+
53
+ const cleanupFor = (settings: DeploySettings): string[] => {
54
+ const cleanup = settings.deploy.flatpak?.cleanup;
55
+
56
+ if (cleanup === undefined) {
57
+ return DEFAULT_CLEANUP;
58
+ }
59
+
60
+ return cleanup.length === 0 ? [] : mergeArgs(DEFAULT_CLEANUP, cleanup);
61
+ };
62
+
63
+ const grantedSockets = (args: string[]): Set<string> => {
64
+ const granted: Set<string> = new Set();
65
+
66
+ for (const arg of args) {
67
+ if (arg.startsWith(SOCKET_ARG)) {
68
+ granted.add(arg.slice(SOCKET_ARG.length));
69
+ }
70
+
71
+ if (arg.startsWith(NO_SOCKET_ARG)) {
72
+ granted.delete(arg.slice(NO_SOCKET_ARG.length));
73
+ }
74
+ }
75
+
76
+ return granted;
77
+ };
78
+
79
+ const hasDisplaySocket = (args: string[]): boolean => {
80
+ const granted = grantedSockets(args);
81
+
82
+ return DISPLAY_SOCKETS.some((socket) => granted.has(socket));
83
+ };
84
+
19
85
  const sdkExtensionsFor = (settings: DeploySettings): string[] => {
20
86
  const flatpak = settings.deploy.flatpak ?? {};
21
87
  const nodeExtension = isSourceMode(settings) ? [nodeExtensionFor(settings)] : [];
@@ -46,16 +112,15 @@ const runtimeKeys = (settings: DeploySettings): FlatpakManifest => {
46
112
 
47
113
  const renderFlatpakManifest = (payload: DeployPayload): FlatpakManifest => {
48
114
  const settings = payload.settings;
49
- const flatpak = settings.deploy.flatpak ?? {};
50
115
 
51
116
  return {
52
117
  id: settings.applicationId,
53
118
  ...runtimeKeys(settings),
54
119
  command: settings.binaryName,
55
- "finish-args": flatpak.finishArgs ?? DEFAULT_FINISH_ARGS,
56
- cleanup: flatpak.cleanup ?? DEFAULT_CLEANUP,
120
+ "finish-args": finishArgsFor(settings),
121
+ cleanup: cleanupFor(settings),
57
122
  modules: modulesFor(payload),
58
123
  };
59
124
  };
60
125
 
61
- export { branchFor, renderFlatpakManifest };
126
+ export { branchFor, finishArgsFor, hasDisplaySocket, renderFlatpakManifest };
@@ -0,0 +1,115 @@
1
+ import type { DeploySettings } from "../types.js";
2
+ import type { FlatpakModule } from "./flatpak-prebuilt.js";
3
+ import { readPackageManifest } from "../settings/package-manifest.js";
4
+
5
+ type PnpmPin = {
6
+ version: string;
7
+ major: number;
8
+ minor: number;
9
+ sha512: string;
10
+ };
11
+
12
+ const PNPM_VERSION = "11.21.0";
13
+ const PNPM_MAJOR = 11;
14
+ const PNPM_MINOR = 21;
15
+
16
+ const PNPM_SHA512 = "521705bce689924eac72f5a3587122f362689ef6571e55ba80076fd637c11132ecffada26" +
17
+ "fad4ea79c485bfddbfd3d5a2a5b05805a77e893de71ec8a6cca3bb1";
18
+
19
+ const PNPM_DIR = "flatpak-pnpm";
20
+ const PNPM_COMMAND = "pnpm";
21
+ const PNPM_PREFIX = "pnpm@";
22
+ const PNPM_TARBALL_URL = "https://registry.npmjs.org/pnpm/-/pnpm-";
23
+ const PNPM_INSTALL = "pnpm install --offline --frozen-lockfile";
24
+ const TRUST_LOCKFILE = "--trust-lockfile";
25
+ const STORE_V10 = "v10";
26
+ const STORE_V11 = "v11";
27
+ const STORE_V10_MAJOR = 10;
28
+ const STORE_V11_MAJOR = 11;
29
+ const TRUST_LOCKFILE_MINOR = 3;
30
+
31
+ const DEFAULT_PIN: PnpmPin = {
32
+ version: PNPM_VERSION,
33
+ major: PNPM_MAJOR,
34
+ minor: PNPM_MINOR,
35
+ sha512: PNPM_SHA512,
36
+ };
37
+
38
+ const PNPM_PIN_FIELD =
39
+ /^pnpm@(?<version>(?<major>\d+)\.(?<minor>\d+)\.\d+)\+sha512\.(?<sha512>[\da-f]{128})$/;
40
+
41
+ const pinFromGroups = (groups: Record<string, string | undefined>): PnpmPin | null => {
42
+ const { major, minor, sha512, version } = groups;
43
+
44
+ if (major === undefined || minor === undefined || sha512 === undefined || version === undefined) {
45
+ return null;
46
+ }
47
+
48
+ return { version, major: Number(major), minor: Number(minor), sha512 };
49
+ };
50
+
51
+ const isSupportedPin = (pin: PnpmPin): boolean =>
52
+ pin.major === STORE_V10_MAJOR || (pin.major === STORE_V11_MAJOR && pin.minor >= TRUST_LOCKFILE_MINOR);
53
+
54
+ const resolvePnpmPin = (settings: DeploySettings): PnpmPin => {
55
+ const field = readPackageManifest(settings.paths.root).packageManager;
56
+
57
+ if (field === null) {
58
+ return DEFAULT_PIN;
59
+ }
60
+
61
+ if (!field.startsWith(PNPM_PREFIX)) {
62
+ throw new Error(
63
+ `Cannot vendor pnpm for the Flathub sandbox: "packageManager" is "${field}" but the offline install ` +
64
+ "resolves from a pnpm lockfile. Run `corepack use pnpm@<version>` to pin it to pnpm, or point " +
65
+ "`deploy.flatpak.packageManager` at the manager whose lockfile the build should install from.",
66
+ );
67
+ }
68
+
69
+ const pin = pinFromGroups(PNPM_PIN_FIELD.exec(field)?.groups ?? {});
70
+
71
+ if (pin === null) {
72
+ throw new Error(
73
+ `Cannot vendor pnpm for the Flathub sandbox: "packageManager" is "${field}", which carries no sha512 ` +
74
+ "integrity digest, and every source in a Flathub build has to be hash-pinned. Run " +
75
+ "`corepack use pnpm@<version>` to rewrite it with its digest.",
76
+ );
77
+ }
78
+
79
+ if (!isSupportedPin(pin)) {
80
+ throw new Error(
81
+ `Cannot vendor pnpm ${pin.version} for the Flathub sandbox: the offline install runs on pnpm 10, or on ` +
82
+ "pnpm 11 from 11.3.0 on, where `--trust-lockfile` exists. Earlier pnpm 11 releases reach the registry " +
83
+ 'during the supply-chain check and the sandbox has no network. Pin "packageManager" to one of those.',
84
+ );
85
+ }
86
+
87
+ return pin;
88
+ };
89
+
90
+ const pnpmPathFor = (moduleDir: string): string => `${moduleDir}/${PNPM_DIR}`;
91
+
92
+ const pnpmSources = (pin: PnpmPin, moduleDir: string): FlatpakModule[] => [
93
+ { type: "archive", url: `${PNPM_TARBALL_URL}${pin.version}.tgz`, sha512: pin.sha512, dest: PNPM_DIR },
94
+ {
95
+ type: "script",
96
+ dest: PNPM_DIR,
97
+ "dest-filename": PNPM_COMMAND,
98
+ commands: [`exec node ${pnpmPathFor(moduleDir)}/bin/pnpm.cjs "$@"`],
99
+ },
100
+ ];
101
+
102
+ const pnpmInstallCommand = (pin: PnpmPin): string =>
103
+ pin.major === STORE_V11_MAJOR ? `${PNPM_INSTALL} ${TRUST_LOCKFILE}` : PNPM_INSTALL;
104
+
105
+ const pnpmStoreVersionFor = (pin: PnpmPin): string =>
106
+ pin.major === STORE_V10_MAJOR ? STORE_V10 : STORE_V11;
107
+
108
+ export {
109
+ pnpmInstallCommand,
110
+ pnpmPathFor,
111
+ type PnpmPin,
112
+ pnpmSources,
113
+ pnpmStoreVersionFor,
114
+ resolvePnpmPin,
115
+ };