@gtkx/cli 1.4.0 → 1.6.0

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 (206) hide show
  1. package/README.md +1 -1
  2. package/dist/builder.d.ts +1 -0
  3. package/dist/builder.d.ts.map +1 -1
  4. package/dist/builder.js +2 -2
  5. package/dist/builder.js.map +1 -1
  6. package/dist/cli.js +4 -0
  7. package/dist/cli.js.map +1 -1
  8. package/dist/codegen/freshness.d.ts.map +1 -1
  9. package/dist/codegen/freshness.js +2 -1
  10. package/dist/codegen/freshness.js.map +1 -1
  11. package/dist/codegen/reference.d.ts +17 -0
  12. package/dist/codegen/reference.d.ts.map +1 -0
  13. package/dist/codegen/reference.js +37 -0
  14. package/dist/codegen/reference.js.map +1 -0
  15. package/dist/codegen/report.d.ts.map +1 -1
  16. package/dist/codegen/report.js +17 -3
  17. package/dist/codegen/report.js.map +1 -1
  18. package/dist/codegen/run-codegen.d.ts +4 -0
  19. package/dist/codegen/run-codegen.d.ts.map +1 -1
  20. package/dist/codegen/run-codegen.js +93 -27
  21. package/dist/codegen/run-codegen.js.map +1 -1
  22. package/dist/commands/codegen.js +1 -1
  23. package/dist/commands/codegen.js.map +1 -1
  24. package/dist/commands/docs.d.ts.map +1 -1
  25. package/dist/commands/docs.js +10 -16
  26. package/dist/commands/docs.js.map +1 -1
  27. package/dist/commands/mcp.d.ts +13 -0
  28. package/dist/commands/mcp.d.ts.map +1 -0
  29. package/dist/commands/mcp.js +68 -0
  30. package/dist/commands/mcp.js.map +1 -0
  31. package/dist/deploy/freedesktop/localize.d.ts +6 -0
  32. package/dist/deploy/freedesktop/localize.d.ts.map +1 -0
  33. package/dist/deploy/freedesktop/localize.js +107 -0
  34. package/dist/deploy/freedesktop/localize.js.map +1 -0
  35. package/dist/deploy/install-hints.d.ts.map +1 -1
  36. package/dist/deploy/install-hints.js +5 -0
  37. package/dist/deploy/install-hints.js.map +1 -1
  38. package/dist/deploy/notices/gtkx.js +1 -1
  39. package/dist/deploy/notices/gtkx.js.map +1 -1
  40. package/dist/deploy/notices/libraries.d.ts.map +1 -1
  41. package/dist/deploy/notices/libraries.js +2 -1
  42. package/dist/deploy/notices/libraries.js.map +1 -1
  43. package/dist/deploy/payload/launcher.d.ts.map +1 -1
  44. package/dist/deploy/payload/launcher.js +2 -0
  45. package/dist/deploy/payload/launcher.js.map +1 -1
  46. package/dist/deploy/payload/stage.d.ts +1 -1
  47. package/dist/deploy/payload/stage.d.ts.map +1 -1
  48. package/dist/deploy/payload/stage.js +20 -11
  49. package/dist/deploy/payload/stage.js.map +1 -1
  50. package/dist/deploy/run-deploy.d.ts.map +1 -1
  51. package/dist/deploy/run-deploy.js +87 -15
  52. package/dist/deploy/run-deploy.js.map +1 -1
  53. package/dist/deploy/settings/libraries.d.ts.map +1 -1
  54. package/dist/deploy/settings/libraries.js +7 -9
  55. package/dist/deploy/settings/libraries.js.map +1 -1
  56. package/dist/deploy/targets/flatpak-source.d.ts.map +1 -1
  57. package/dist/deploy/targets/flatpak-source.js +21 -10
  58. package/dist/deploy/targets/flatpak-source.js.map +1 -1
  59. package/dist/deploy/tools.d.ts +6 -1
  60. package/dist/deploy/tools.d.ts.map +1 -1
  61. package/dist/deploy/tools.js +27 -4
  62. package/dist/deploy/tools.js.map +1 -1
  63. package/dist/dev/locale-dir.d.ts +3 -0
  64. package/dist/dev/locale-dir.d.ts.map +1 -0
  65. package/dist/dev/locale-dir.js +17 -0
  66. package/dist/dev/locale-dir.js.map +1 -0
  67. package/dist/dev/runner-main.d.ts.map +1 -1
  68. package/dist/dev/runner-main.js +4 -1
  69. package/dist/dev/runner-main.js.map +1 -1
  70. package/dist/dev/runner.d.ts.map +1 -1
  71. package/dist/dev/runner.js +6 -0
  72. package/dist/dev/runner.js.map +1 -1
  73. package/dist/dev/vite-dev-server.d.ts +1 -0
  74. package/dist/dev/vite-dev-server.d.ts.map +1 -1
  75. package/dist/dev/vite-dev-server.js.map +1 -1
  76. package/dist/i18n/binding-extractor.d.ts +30 -0
  77. package/dist/i18n/binding-extractor.d.ts.map +1 -0
  78. package/dist/i18n/binding-extractor.js +1324 -0
  79. package/dist/i18n/binding-extractor.js.map +1 -0
  80. package/dist/i18n/catalog-template.d.ts +3 -0
  81. package/dist/i18n/catalog-template.d.ts.map +1 -0
  82. package/dist/i18n/catalog-template.js +18 -0
  83. package/dist/i18n/catalog-template.js.map +1 -0
  84. package/dist/i18n/catalogs.d.ts +21 -0
  85. package/dist/i18n/catalogs.d.ts.map +1 -0
  86. package/dist/i18n/catalogs.js +139 -0
  87. package/dist/i18n/catalogs.js.map +1 -0
  88. package/dist/i18n/extractor-plugin.d.ts +8 -0
  89. package/dist/i18n/extractor-plugin.d.ts.map +1 -0
  90. package/dist/i18n/extractor-plugin.js +247 -0
  91. package/dist/i18n/extractor-plugin.js.map +1 -0
  92. package/dist/i18n/metadata-templates.d.ts +11 -0
  93. package/dist/i18n/metadata-templates.d.ts.map +1 -0
  94. package/dist/i18n/metadata-templates.js +11 -0
  95. package/dist/i18n/metadata-templates.js.map +1 -0
  96. package/dist/i18n/source-messages.d.ts +23 -0
  97. package/dist/i18n/source-messages.d.ts.map +1 -0
  98. package/dist/i18n/source-messages.js +403 -0
  99. package/dist/i18n/source-messages.js.map +1 -0
  100. package/dist/i18n/types.d.ts +10 -0
  101. package/dist/i18n/types.d.ts.map +1 -0
  102. package/dist/i18n/types.js +108 -0
  103. package/dist/i18n/types.js.map +1 -0
  104. package/dist/internal/agent-rules.d.ts +7 -0
  105. package/dist/internal/agent-rules.d.ts.map +1 -0
  106. package/dist/internal/agent-rules.js +106 -0
  107. package/dist/internal/agent-rules.js.map +1 -0
  108. package/dist/internal/docs-elements.d.ts +8 -0
  109. package/dist/internal/docs-elements.d.ts.map +1 -0
  110. package/dist/internal/docs-elements.js +12 -0
  111. package/dist/internal/docs-elements.js.map +1 -0
  112. package/dist/internal/source-imports.d.ts +5 -1
  113. package/dist/internal/source-imports.d.ts.map +1 -1
  114. package/dist/internal/source-imports.js +25 -47
  115. package/dist/internal/source-imports.js.map +1 -1
  116. package/dist/mcp/client-config.d.ts +22 -0
  117. package/dist/mcp/client-config.d.ts.map +1 -0
  118. package/dist/mcp/client-config.js +94 -0
  119. package/dist/mcp/client-config.js.map +1 -0
  120. package/dist/mcp/client.d.ts +0 -1
  121. package/dist/mcp/client.d.ts.map +1 -1
  122. package/dist/mcp/client.js +11 -39
  123. package/dist/mcp/client.js.map +1 -1
  124. package/dist/mcp/handlers.d.ts +2 -1
  125. package/dist/mcp/handlers.d.ts.map +1 -1
  126. package/dist/mcp/handlers.js.map +1 -1
  127. package/dist/settings/render.d.ts +4 -1
  128. package/dist/settings/render.d.ts.map +1 -1
  129. package/dist/settings/render.js +11 -2
  130. package/dist/settings/render.js.map +1 -1
  131. package/dist/settings/schema.d.ts.map +1 -1
  132. package/dist/settings/schema.js +3 -1
  133. package/dist/settings/schema.js.map +1 -1
  134. package/dist/vite-plugins/animated.d.ts +5 -0
  135. package/dist/vite-plugins/animated.d.ts.map +1 -0
  136. package/dist/vite-plugins/animated.js +325 -0
  137. package/dist/vite-plugins/animated.js.map +1 -0
  138. package/dist/vite-plugins/asset-imports.d.ts.map +1 -1
  139. package/dist/vite-plugins/asset-imports.js +4 -14
  140. package/dist/vite-plugins/asset-imports.js.map +1 -1
  141. package/dist/vite-plugins/i18n.d.ts +5 -0
  142. package/dist/vite-plugins/i18n.d.ts.map +1 -0
  143. package/dist/vite-plugins/i18n.js +88 -0
  144. package/dist/vite-plugins/i18n.js.map +1 -0
  145. package/dist/vite-plugins/index.d.ts +1 -1
  146. package/dist/vite-plugins/index.d.ts.map +1 -1
  147. package/dist/vite-plugins/index.js +5 -1
  148. package/dist/vite-plugins/index.js.map +1 -1
  149. package/dist/vite-plugins/resources.d.ts.map +1 -1
  150. package/dist/vite-plugins/resources.js +12 -57
  151. package/dist/vite-plugins/resources.js.map +1 -1
  152. package/dist/vite-plugins/self-contained.d.ts.map +1 -1
  153. package/dist/vite-plugins/self-contained.js +128 -135
  154. package/dist/vite-plugins/self-contained.js.map +1 -1
  155. package/dist/vite-plugins/settings-worker-env.js +2 -2
  156. package/dist/vite-plugins/settings-worker-env.js.map +1 -1
  157. package/dist/vite-plugins/settings.d.ts.map +1 -1
  158. package/dist/vite-plugins/settings.js +2 -1
  159. package/dist/vite-plugins/settings.js.map +1 -1
  160. package/package.json +11 -9
  161. package/src/builder.ts +3 -2
  162. package/src/cli.ts +5 -0
  163. package/src/codegen/freshness.ts +2 -1
  164. package/src/codegen/reference.ts +59 -0
  165. package/src/codegen/report.ts +18 -3
  166. package/src/codegen/run-codegen.ts +134 -31
  167. package/src/commands/codegen.ts +1 -1
  168. package/src/commands/docs.ts +10 -28
  169. package/src/commands/mcp.ts +81 -0
  170. package/src/deploy/freedesktop/localize.ts +144 -0
  171. package/src/deploy/install-hints.ts +5 -0
  172. package/src/deploy/notices/gtkx.ts +1 -1
  173. package/src/deploy/notices/libraries.ts +2 -1
  174. package/src/deploy/payload/launcher.ts +2 -0
  175. package/src/deploy/payload/stage.ts +35 -10
  176. package/src/deploy/run-deploy.ts +128 -13
  177. package/src/deploy/settings/libraries.ts +7 -10
  178. package/src/deploy/targets/flatpak-source.ts +33 -10
  179. package/src/deploy/tools.ts +36 -3
  180. package/src/dev/locale-dir.ts +22 -0
  181. package/src/dev/runner-main.ts +4 -1
  182. package/src/dev/runner.ts +8 -0
  183. package/src/dev/vite-dev-server.ts +1 -0
  184. package/src/i18n/binding-extractor.ts +1969 -0
  185. package/src/i18n/catalog-template.ts +24 -0
  186. package/src/i18n/catalogs.ts +216 -0
  187. package/src/i18n/extractor-plugin.ts +413 -0
  188. package/src/i18n/metadata-templates.ts +29 -0
  189. package/src/i18n/source-messages.ts +608 -0
  190. package/src/i18n/types.ts +154 -0
  191. package/src/internal/agent-rules.ts +135 -0
  192. package/src/internal/docs-elements.ts +21 -0
  193. package/src/internal/source-imports.ts +31 -60
  194. package/src/mcp/client-config.ts +130 -0
  195. package/src/mcp/client.ts +17 -44
  196. package/src/mcp/handlers.ts +7 -6
  197. package/src/settings/render.ts +16 -2
  198. package/src/settings/schema.ts +3 -1
  199. package/src/vite-plugins/animated.ts +486 -0
  200. package/src/vite-plugins/asset-imports.ts +5 -17
  201. package/src/vite-plugins/i18n.ts +146 -0
  202. package/src/vite-plugins/index.ts +5 -0
  203. package/src/vite-plugins/resources.ts +12 -74
  204. package/src/vite-plugins/self-contained.ts +150 -169
  205. package/src/vite-plugins/settings-worker-env.ts +2 -2
  206. package/src/vite-plugins/settings.ts +2 -1
@@ -0,0 +1,144 @@
1
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import type { CatalogProject } from "../../i18n/catalogs.js";
5
+ import type { StagedMetadata } from "../payload/stage.js";
6
+ import { metadataTemplateFile, type MetadataTemplateKind } from "../../i18n/metadata-templates.js";
7
+ import { runCliTool } from "../../internal/run-cli-tool.js";
8
+
9
+ type MergeMode = "--desktop" | "--xml";
10
+
11
+ type MergeRequest = {
12
+ mode: MergeMode;
13
+ stem: string;
14
+ extension: string;
15
+ template: string;
16
+ };
17
+
18
+ type MetadataTemplate = {
19
+ contents: string;
20
+ path: string;
21
+ relativePath: string;
22
+ };
23
+
24
+ const metadataTemplates = (project: CatalogProject, metadata: StagedMetadata): MetadataTemplate[] => {
25
+ const template = (kind: MetadataTemplateKind, contents: string): MetadataTemplate => {
26
+ const { path, relativePath } = metadataTemplateFile(project, kind);
27
+
28
+ return { contents, path, relativePath };
29
+ };
30
+
31
+ return [
32
+ template("desktop", metadata.desktopEntry),
33
+ template("metainfo.xml", metadata.metainfo),
34
+ ...(metadata.mimePackage === null ? [] : [template("mime.xml", metadata.mimePackage)]),
35
+ ];
36
+ };
37
+
38
+ const mergeCatalogs = (project: CatalogProject, workDir: string, request: MergeRequest): string => {
39
+ const { extension, mode, stem, template } = request;
40
+ let current = join(workDir, `${stem}-source${extension}`);
41
+ writeFileSync(current, template);
42
+
43
+ for (const [index, catalog] of project.catalogs.entries()) {
44
+ const output = join(workDir, `${stem}-${String(index)}${extension}`);
45
+
46
+ runCliTool({
47
+ tool: "msgfmt",
48
+ args: [
49
+ mode,
50
+ `--template=${current}`,
51
+ `--locale=${catalog.locale}`,
52
+ `--output-file=${output}`,
53
+ catalog.path,
54
+ ],
55
+ target: catalog.path,
56
+ });
57
+
58
+ current = output;
59
+ }
60
+
61
+ return readFileSync(current, "utf8");
62
+ };
63
+
64
+ const localizeMetadata = (metadata: StagedMetadata, project: CatalogProject | null): StagedMetadata => {
65
+ if (project === null || project.catalogs.length === 0) {
66
+ return metadata;
67
+ }
68
+
69
+ const workDir = mkdtempSync(join(tmpdir(), "gtkx-deploy-metadata-"));
70
+
71
+ try {
72
+ return {
73
+ desktopEntry: mergeCatalogs(project, workDir, {
74
+ mode: "--desktop",
75
+ stem: "application",
76
+ extension: ".desktop",
77
+ template: metadata.desktopEntry,
78
+ }),
79
+ metainfo: mergeCatalogs(project, workDir, {
80
+ mode: "--xml",
81
+ stem: "metainfo",
82
+ extension: ".metainfo.xml",
83
+ template: metadata.metainfo,
84
+ }),
85
+ mimePackage: metadata.mimePackage === null
86
+ ? null
87
+ : mergeCatalogs(project, workDir, {
88
+ mode: "--xml",
89
+ stem: "mime",
90
+ extension: ".xml",
91
+ template: metadata.mimePackage,
92
+ }),
93
+ };
94
+ } finally {
95
+ rmSync(workDir, { recursive: true, force: true });
96
+ }
97
+ };
98
+
99
+ const extractMetadataMessages = (
100
+ metadata: StagedMetadata,
101
+ project: CatalogProject,
102
+ catalogTemplate?: string,
103
+ ): void => {
104
+ const potPath = catalogTemplate ?? join(project.poDir, `${project.domain}.pot`);
105
+
106
+ if (!existsSync(potPath)) {
107
+ throw new Error(`Cannot add the generated metadata to the missing catalog template: ${potPath}`);
108
+ }
109
+
110
+ const templates = metadataTemplates(project, metadata);
111
+ const firstTemplate = templates[0];
112
+
113
+ if (firstTemplate === undefined) {
114
+ throw new Error("Cannot extract metadata messages without a generated template");
115
+ }
116
+
117
+ const metadataDir = dirname(firstTemplate.path);
118
+ mkdirSync(metadataDir, { recursive: true });
119
+
120
+ try {
121
+ for (const template of templates) {
122
+ writeFileSync(template.path, template.contents);
123
+ }
124
+
125
+ runCliTool({
126
+ tool: "xgettext",
127
+ args: [
128
+ "--join-existing",
129
+ "--force-po",
130
+ "--from-code=UTF-8",
131
+ `--output=${potPath}`,
132
+ ...templates.map((template) => template.relativePath),
133
+ ],
134
+ target: potPath,
135
+ options: { cwd: project.root },
136
+ });
137
+ } finally {
138
+ for (const template of templates) {
139
+ rmSync(template.path, { force: true });
140
+ }
141
+ }
142
+ };
143
+
144
+ export { extractMetadataMessages, localizeMetadata };
@@ -43,8 +43,13 @@ const PACKAGE_FOR_TOOL: Record<string, FamilyPackages> = {
43
43
  fedora: "flatpak-builder",
44
44
  suse: "flatpak-builder",
45
45
  },
46
+ msgfmt: { arch: "gettext", debian: "gettext", fedora: "gettext", suse: "gettext-tools" },
47
+ msginit: { arch: "gettext", debian: "gettext", fedora: "gettext", suse: "gettext-tools" },
48
+ msggrep: { arch: "gettext", debian: "gettext", fedora: "gettext", suse: "gettext-tools" },
49
+ msgmerge: { arch: "gettext", debian: "gettext", fedora: "gettext", suse: "gettext-tools" },
46
50
  strip: { arch: "binutils", debian: "binutils", fedora: "binutils", suse: "binutils" },
47
51
  tar: { arch: "tar", debian: "tar", fedora: "tar", suse: "tar" },
52
+ xgettext: { arch: "gettext", debian: "gettext", fedora: "gettext", suse: "gettext-tools" },
48
53
  };
49
54
 
50
55
  const GENERATOR_SOURCE = "git+https://github.com/flatpak/flatpak-builder-tools.git#subdirectory=node";
@@ -44,7 +44,7 @@ const summaryFor = (settings: DeploySettings, packages: BundledPackage[]): strin
44
44
  "Section 3.2(a) asks that whoever receives an executable form be told how to obtain the source code,",
45
45
  "which is what the source pointer below is for: it leads to the release these files were built from.",
46
46
  `The addon also statically links Rust crates GTKX did not write, so ${lib}/${NATIVE_FILENAME} is not`,
47
- `${LICENSE_NAME} alone. The second notice below names the licenses those crates carry; it does not`,
47
+ `${LICENSE_NAME} alone. The crates notice below names the licenses those crates carry; it does not`,
48
48
  `reproduce their texts, and ${CRATES_MANIFEST} in the source pointed at records which crates went in`,
49
49
  "and at which versions.",
50
50
  ...modulesLine(packages),
@@ -36,7 +36,8 @@ const SUMMARY = [
36
36
  "their own terms; this package does not distribute them at all.",
37
37
  "The GTKX native addon does link GLib, GObject and GIO dynamically against the copies already installed",
38
38
  "on the system, which makes the addon a work that uses those libraries, and section 6 of the LGPL asks",
39
- "such a work to carry three things. This is the notice it asks for. The license itself is published at",
39
+ "such a work to carry a notice, the license, and the copyright notices. This is the notice it asks for,",
40
+ "and the license itself is published at",
40
41
  `${LGPL_URL},`,
41
42
  "and the copyright notice of each library is published with its sources, at the address listed beside",
42
43
  "it below. Linking against an installed shared library is the mechanism section 6(b) allows, so the",
@@ -18,6 +18,8 @@ const renderLauncher = (settings: DeploySettings): string => {
18
18
  "set -e",
19
19
  'self=$(readlink -f "$0")',
20
20
  'prefix=$(dirname "$(dirname "$self")")',
21
+ 'GTKX_LOCALE_DIR="$prefix/share/locale"',
22
+ "export GTKX_LOCALE_DIR",
21
23
  ...(shouldUseCompileCache ? compileCacheLines(settings.binaryName) : []),
22
24
  `exec "${libDir}/${NODE_FILENAME}" "${libDir}/${BUNDLE_FILENAME}" "$@"`,
23
25
  "",
@@ -2,12 +2,12 @@ import { sortStringsBy } from "@gtkx/utils";
2
2
  import { existsSync, rmSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import type { DeploySettings, DeployTargetName, NodeRuntime, NoticeSection, StagedFile } from "../types.js";
5
+ import { LOCALE_DIRNAME } from "../../i18n/catalogs.js";
5
6
  import { BUILD_MANIFEST_FILENAME } from "../../internal/build-manifest.js";
6
7
  import { listFilesRecursive } from "../../internal/list-files.js";
7
8
  import { BUNDLE_FILENAME } from "../../vite-plugins/esm-extension.js";
8
9
  import { renderCopyright } from "../freedesktop/copyright.js";
9
10
  import { renderDbusService } from "../freedesktop/dbus-service.js";
10
- import { renderDesktopEntry } from "../freedesktop/desktop-entry.js";
11
11
  import { renderNotices } from "../notices/render.js";
12
12
  import { copyInto, EXECUTABLE_MODE, executableModeFor, writeInto } from "./copy-tree.js";
13
13
  import { stageIcons } from "./icons.js";
@@ -36,6 +36,7 @@ const SHARE_MIME_PACKAGES = "share/mime/packages";
36
36
  const SHARE_DOC = "share/doc";
37
37
  const SHARE_LICENSES = "share/licenses";
38
38
  const SHARE_DBUS_SERVICES = "share/dbus-1/services";
39
+ const SHARE_LOCALE = `share/${LOCALE_DIRNAME}`;
39
40
 
40
41
  const PREFIX_FOR: Record<DeployTargetName, string> = {
41
42
  appimage: "/usr",
@@ -53,6 +54,8 @@ const isIconAsset = (rel: string): boolean => rel === DIST_ICONS_DIR || rel.star
53
54
  const isBuildMetadata = (rel: string): boolean =>
54
55
  rel === BUILD_MANIFEST_FILENAME || rel === LEGACY_PACKAGES_MANIFEST_FILENAME;
55
56
 
57
+ const isLocaleAsset = (rel: string): boolean => rel === LOCALE_DIRNAME || rel.startsWith(`${LOCALE_DIRNAME}/`);
58
+
56
59
  const nodeLicenseDestination = (settings: DeploySettings): string =>
57
60
  `${licenseDirFor(settings)}/${NODE_FILENAME}/${NODE_LICENSE_FILENAME}`;
58
61
 
@@ -64,10 +67,14 @@ const stageRuntimeFiles = (settings: DeploySettings, root: string): StagedFile[]
64
67
  }
65
68
 
66
69
  return listFilesRecursive(dist)
67
- .filter((file) => !isIconAsset(file.rel) && !isBuildMetadata(file.rel))
70
+ .filter((file) => !isIconAsset(file.rel) && !isBuildMetadata(file.rel) && !isLocaleAsset(file.rel))
68
71
  .map((file) => copyInto(root, join(libDirFor(settings), file.rel), file.absPath));
69
72
  };
70
73
 
74
+ const stageCatalogs = (settings: DeploySettings, root: string): StagedFile[] =>
75
+ listFilesRecursive(join(settings.paths.dist, LOCALE_DIRNAME))
76
+ .map((file) => copyInto(root, join(SHARE_LOCALE, file.rel), file.absPath));
77
+
71
78
  const stageNodeBinary = (settings: DeploySettings, root: string, node: NodeRuntime | null): StagedFile[] =>
72
79
  node === null ? [] : [copyInto(root, join(libDirFor(settings), NODE_FILENAME), node.path, EXECUTABLE_MODE)];
73
80
 
@@ -104,6 +111,7 @@ const stagePayload = ({ settings, node, metadata }: StageRequest): StagedFile[]
104
111
  writeInto(root, join("bin", settings.binaryName), renderLauncher(settings), EXECUTABLE_MODE),
105
112
  ...stageNodeBinary(settings, root, node),
106
113
  ...stageRuntimeFiles(settings, root),
114
+ ...stageCatalogs(settings, root),
107
115
  ...stageMetadata(settings, root, metadata),
108
116
  ...stageIcons(settings, root),
109
117
  ...stageSchemas(settings, root),
@@ -111,7 +119,18 @@ const stagePayload = ({ settings, node, metadata }: StageRequest): StagedFile[]
111
119
  ]);
112
120
  };
113
121
 
114
- const stageActivation = (settings: DeploySettings, root: string, target: DeployTargetName): StagedFile[] => {
122
+ const withoutDbusActivation = (desktopEntry: string): string =>
123
+ desktopEntry
124
+ .split("\n")
125
+ .filter((line) => line !== "DBusActivatable=true")
126
+ .join("\n");
127
+
128
+ const stageActivation = (
129
+ settings: DeploySettings,
130
+ root: string,
131
+ target: DeployTargetName,
132
+ metadata: StagedMetadata,
133
+ ): StagedFile[] => {
115
134
  if (!settings.isDbusActivatable) {
116
135
  return [];
117
136
  }
@@ -120,7 +139,7 @@ const stageActivation = (settings: DeploySettings, root: string, target: DeployT
120
139
  return [writeInto(
121
140
  root,
122
141
  join(SHARE_APPLICATIONS, `${settings.applicationId}.desktop`),
123
- renderDesktopEntry({ ...settings, isDbusActivatable: false }),
142
+ withoutDbusActivation(metadata.desktopEntry),
124
143
  )];
125
144
  }
126
145
 
@@ -131,11 +150,16 @@ const stageActivation = (settings: DeploySettings, root: string, target: DeployT
131
150
  )];
132
151
  };
133
152
 
134
- const stageOverlay = (settings: DeploySettings, target: DeployTargetName, notices: NoticeSection[]): StagedFile[] => {
153
+ const stageOverlay = (
154
+ settings: DeploySettings,
155
+ target: DeployTargetName,
156
+ notices: NoticeSection[],
157
+ metadata: StagedMetadata,
158
+ ): StagedFile[] => {
135
159
  const root = join(settings.paths.overlay, target);
136
160
  rmSync(root, { recursive: true, force: true, maxRetries: 5 });
137
161
  const licenseFile = settings.paths.licenseFile;
138
- const activation = stageActivation(settings, root, target);
162
+ const activation = stageActivation(settings, root, target, metadata);
139
163
 
140
164
  if (target === "deb") {
141
165
  return [
@@ -154,11 +178,12 @@ const stageOverlay = (settings: DeploySettings, target: DeployTargetName, notice
154
178
  const stageOverlays = (
155
179
  settings: DeploySettings,
156
180
  notices: NoticeSection[],
181
+ metadata: StagedMetadata,
157
182
  ): Record<DeployTargetName, StagedFile[]> => ({
158
- appimage: stageOverlay(settings, "appimage", notices),
159
- deb: stageOverlay(settings, "deb", notices),
160
- flatpak: stageOverlay(settings, "flatpak", notices),
161
- rpm: stageOverlay(settings, "rpm", notices),
183
+ appimage: stageOverlay(settings, "appimage", notices, metadata),
184
+ deb: stageOverlay(settings, "deb", notices, metadata),
185
+ flatpak: stageOverlay(settings, "flatpak", notices, metadata),
186
+ rpm: stageOverlay(settings, "rpm", notices, metadata),
162
187
  });
163
188
 
164
189
  export {
@@ -1,6 +1,6 @@
1
1
  import { loadConfig } from "@gtkx/config";
2
2
  import { info, warn } from "@gtkx/utils";
3
- import { mkdirSync, writeFileSync } from "node:fs";
3
+ import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { dirname, join, relative } from "node:path";
5
5
  import type {
6
6
  DeployArtifact,
@@ -12,7 +12,19 @@ import type {
12
12
  } from "./types.js";
13
13
  import { build as buildApp } from "../builder.js";
14
14
  import { ensureGenerated } from "../codegen/run-codegen.js";
15
+ import { replaceCatalogTemplate } from "../i18n/catalog-template.js";
16
+ import {
17
+ type CatalogProject,
18
+ compileCatalogs,
19
+ LOCALE_DIRNAME,
20
+ requiresCatalogInitialization,
21
+ resolveCatalogProject,
22
+ synchronizeCatalogs,
23
+ } from "../i18n/catalogs.js";
24
+ import { extractSourceCatalogTo } from "../i18n/source-messages.js";
25
+ import { discoverSourceFiles } from "../internal/source-imports.js";
15
26
  import { renderDesktopEntry } from "./freedesktop/desktop-entry.js";
27
+ import { extractMetadataMessages, localizeMetadata } from "./freedesktop/localize.js";
16
28
  import { renderMetainfo } from "./freedesktop/metainfo.js";
17
29
  import { renderMimePackage } from "./freedesktop/mime-package.js";
18
30
  import { validateDesktopEntry, validateMetainfo } from "./freedesktop/validate.js";
@@ -32,10 +44,15 @@ import {
32
44
  DESKTOP_FILE_VALIDATE,
33
45
  FLATPAK_NODE_GENERATOR,
34
46
  FLATPAK_NODE_GENERATOR_PNPM,
47
+ MSGFMT,
48
+ MSGGREP,
49
+ MSGINIT,
50
+ MSGMERGE,
35
51
  probeTools,
36
52
  STRIP,
37
53
  TAR,
38
54
  warnMissingOptional,
55
+ XGETTEXT,
39
56
  } from "./tools.js";
40
57
 
41
58
  type DeployOptions = {
@@ -47,6 +64,14 @@ type DeployOptions = {
47
64
  shouldSkipBuild: boolean;
48
65
  };
49
66
 
67
+ type PreflightRequest = {
68
+ targets: DeployTarget[];
69
+ settings: DeploySettings;
70
+ project: CatalogProject | null;
71
+ shouldPrintManifests: boolean;
72
+ shouldSkipBuild: boolean;
73
+ };
74
+
50
75
  const BUILD_MODE = "production";
51
76
  const BYTES_PER_MIB = 1024 * 1024;
52
77
  const WEBKIT_LIBRARY = "WebKit-6.0";
@@ -198,12 +223,50 @@ const runtimeToolsFor = (targets: DeployTarget[], settings: DeploySettings): Dep
198
223
  return node.shouldStrip === false ? archiveTools : [...archiveTools, STRIP];
199
224
  };
200
225
 
201
- const preflight = (targets: DeployTarget[], settings: DeploySettings, shouldPrintManifests: boolean): void => {
226
+ const mutableCatalogTools = (project: CatalogProject): DeployTool[] => {
227
+ if (project.catalogs.length === 0) {
228
+ return [MSGGREP, XGETTEXT];
229
+ }
230
+
231
+ return [
232
+ MSGFMT,
233
+ MSGGREP,
234
+ ...(requiresCatalogInitialization(project) ? [MSGINIT] : []),
235
+ MSGMERGE,
236
+ XGETTEXT,
237
+ ];
238
+ };
239
+
240
+ const catalogTools = (project: CatalogProject | null, shouldSkipBuild: boolean): DeployTool[] => {
241
+ if (project === null) {
242
+ return [];
243
+ }
244
+
245
+ if (!shouldSkipBuild) {
246
+ return mutableCatalogTools(project);
247
+ }
248
+
249
+ return project.catalogs.length === 0 ? [] : [MSGFMT];
250
+ };
251
+
252
+ const preflight = ({
253
+ targets,
254
+ settings,
255
+ project,
256
+ shouldPrintManifests,
257
+ shouldSkipBuild,
258
+ }: PreflightRequest): void => {
202
259
  const packagerTools = shouldPrintManifests
203
260
  ? []
204
261
  : [...runtimeToolsFor(targets, settings), ...targets.flatMap((target) => target.tools)];
205
262
 
206
- const required = [DESKTOP_FILE_VALIDATE, APPSTREAMCLI, ...sourceModeTools(targets, settings)];
263
+ const required = [
264
+ DESKTOP_FILE_VALIDATE,
265
+ APPSTREAMCLI,
266
+ ...catalogTools(project, shouldSkipBuild),
267
+ ...sourceModeTools(targets, settings),
268
+ ];
269
+
207
270
  const report = probeTools([...required, ...packagerTools]);
208
271
  assertTools(report);
209
272
  warnMissingOptional(report);
@@ -229,19 +292,55 @@ const loadSettings = async (options: DeployOptions): Promise<DeploySettings> =>
229
292
  return resolveDeploySettings({ root, config, outDirOverride: options.outDir });
230
293
  };
231
294
 
295
+ const synchronizeMetadataCatalogs = async (
296
+ settings: DeploySettings,
297
+ templates: StagedMetadata,
298
+ project: CatalogProject | null,
299
+ ): Promise<void> => {
300
+ if (project === null) {
301
+ return;
302
+ }
303
+
304
+ const srcDir = join(project.root, "src");
305
+ const sourceFiles = discoverSourceFiles(existsSync(srcDir) ? srcDir : project.root);
306
+ const stagingDir = mkdtempSync(join(project.poDir, ".gtkx-deploy-i18n-"));
307
+ const stagedTemplate = join(stagingDir, `${project.domain}.pot`);
308
+ const template = join(project.poDir, `${project.domain}.pot`);
309
+
310
+ try {
311
+ await extractSourceCatalogTo(project, sourceFiles, stagedTemplate);
312
+ extractMetadataMessages(templates, project, stagedTemplate);
313
+ replaceCatalogTemplate(stagedTemplate, template);
314
+ synchronizeCatalogs(project);
315
+ compileCatalogs(project, join(settings.paths.dist, LOCALE_DIRNAME));
316
+ } finally {
317
+ rmSync(stagingDir, { recursive: true, force: true });
318
+ }
319
+ };
320
+
232
321
  const buildPayload = async (
233
322
  options: DeployOptions,
234
323
  settings: DeploySettings,
235
324
  targets: DeployTarget[],
325
+ project: CatalogProject | null,
236
326
  ): Promise<DeployPayload> => {
237
- const metadata = renderMetadata(settings);
238
- validateMetadata(settings, metadata, isFlathubSubmission(settings, targets));
327
+ const templates = renderMetadata(settings);
239
328
 
240
329
  if (!options.shouldSkipBuild) {
241
330
  info(`Building ${options.entry}`);
242
- await buildApp({ entry: options.entry, vite: { root: options.cwd } });
331
+
332
+ await buildApp({
333
+ entry: options.entry,
334
+ vite: { root: options.cwd },
335
+ });
336
+
337
+ await synchronizeMetadataCatalogs(settings, templates, project);
338
+ } else if (project !== null) {
339
+ compileCatalogs(project, join(settings.paths.dist, LOCALE_DIRNAME));
243
340
  }
244
341
 
342
+ const metadata = localizeMetadata(templates, project);
343
+ validateMetadata(settings, metadata, isFlathubSubmission(settings, targets));
245
344
  const buildManifest = readBuildManifest(settings);
246
345
 
247
346
  const builtSettings: DeploySettings = {
@@ -262,7 +361,7 @@ const buildPayload = async (
262
361
  node,
263
362
  stage,
264
363
  notices,
265
- overlays: stageOverlays(builtSettings, notices),
364
+ overlays: stageOverlays(builtSettings, notices, metadata),
266
365
  };
267
366
  };
268
367
 
@@ -312,15 +411,31 @@ const announce = (settings: DeploySettings, targets: DeployTarget[]): void => {
312
411
  };
313
412
 
314
413
  const runDeploy = async (options: DeployOptions): Promise<void> => {
414
+ let settings = await loadSettings(options);
415
+ let project = resolveCatalogProject(settings.paths.root, settings.applicationId);
416
+ let targets = targetsFor(resolveTargetNames(options, settings));
417
+ announce(settings, targets);
418
+
419
+ preflight({
420
+ targets,
421
+ settings,
422
+ project,
423
+ shouldPrintManifests: options.shouldPrintManifests,
424
+ shouldSkipBuild: options.shouldSkipBuild,
425
+ });
426
+
315
427
  if (!options.shouldSkipBuild) {
316
- await ensureGenerated(options.cwd, { shouldAnnounce: true, mode: BUILD_MODE });
428
+ await ensureGenerated(options.cwd, {
429
+ shouldAnnounce: true,
430
+ mode: BUILD_MODE,
431
+ });
432
+
433
+ settings = await loadSettings(options);
434
+ project = resolveCatalogProject(settings.paths.root, settings.applicationId);
435
+ targets = targetsFor(resolveTargetNames(options, settings));
317
436
  }
318
437
 
319
- const settings = await loadSettings(options);
320
- const targets = targetsFor(resolveTargetNames(options, settings));
321
- announce(settings, targets);
322
- preflight(targets, settings, options.shouldPrintManifests);
323
- const payload = await buildPayload(options, settings, targets);
438
+ const payload = await buildPayload(options, settings, targets, project);
324
439
  const rendered = renderTargetManifests(targets, payload);
325
440
 
326
441
  if (options.shouldPrintManifests) {
@@ -6,15 +6,13 @@ import {
6
6
  resolveGirPath,
7
7
  resolveStore,
8
8
  } from "@gtkx/codegen";
9
+ import { resolveFuture } from "@gtkx/config/internal";
9
10
 
10
11
  type ResolvedLibraries = {
11
12
  libraries: string[];
12
13
  minimumLibraryVersions: Record<string, string>;
13
14
  };
14
15
 
15
- const DEFAULT_LIBRARY = "Gtk-4.0";
16
- const DEFAULT_LIBRARY_PREFIX = "Gtk-";
17
-
18
16
  const generatedLibraries = (root: string): GeneratedLibraries | null => {
19
17
  try {
20
18
  return readGeneratedLibraries(resolveStore(root).gi.storeDir);
@@ -23,24 +21,23 @@ const generatedLibraries = (root: string): GeneratedLibraries | null => {
23
21
  }
24
22
  };
25
23
 
26
- const discoveredLibraries = (config: Config): string[] | null => {
24
+ const discoveredLibraries = (config: Config, isAdwaitaDefault: boolean): string[] | null => {
27
25
  try {
28
- return expandLibraries(config.libraries, resolveGirPath(config.girPath));
26
+ return expandLibraries(config.libraries, resolveGirPath(config.girPath), isAdwaitaDefault);
29
27
  } catch {
30
28
  return null;
31
29
  }
32
30
  };
33
31
 
34
32
  const configLibraries = (config: Config): string[] => {
33
+ const { isAdwaitaDefault } = resolveFuture(config.future);
35
34
  const libraries = config.libraries;
36
35
 
37
- if (!Array.isArray(libraries)) {
38
- return discoveredLibraries(config) ?? [DEFAULT_LIBRARY];
36
+ if (Array.isArray(libraries)) {
37
+ return expandLibraries(libraries, [], isAdwaitaDefault);
39
38
  }
40
39
 
41
- return libraries.some((library) => library.startsWith(DEFAULT_LIBRARY_PREFIX))
42
- ? [...libraries]
43
- : [DEFAULT_LIBRARY, ...libraries];
40
+ return discoveredLibraries(config, isAdwaitaDefault) ?? expandLibraries(undefined, [], isAdwaitaDefault);
44
41
  };
45
42
 
46
43
  const resolveLibraries = (root: string, config: Config): ResolvedLibraries => {
@@ -1,12 +1,10 @@
1
- import { existsSync, realpathSync } from "node:fs";
1
+ import { existsSync, readFileSync, realpathSync } from "node:fs";
2
2
  import { posix, relative, resolve } from "node:path";
3
3
  import type { DeployPayload, DeploySettings } from "../types.js";
4
+ import { LOCALE_DIRNAME } from "../../i18n/catalogs.js";
4
5
  import { listFilesRecursive } from "../../internal/list-files.js";
5
6
  import { BUNDLE_FILENAME } from "../../vite-plugins/esm-extension.js";
6
7
  import { renderDbusService } from "../freedesktop/dbus-service.js";
7
- import { renderDesktopEntry } from "../freedesktop/desktop-entry.js";
8
- import { renderMetainfo } from "../freedesktop/metainfo.js";
9
- import { renderMimePackage } from "../freedesktop/mime-package.js";
10
8
  import { optional } from "../nfpm/optional.js";
11
9
  import { renderNotices } from "../notices/render.js";
12
10
  import { executableModeFor } from "../payload/copy-tree.js";
@@ -60,6 +58,7 @@ const installCommand = (source: string, destination: string, mode: string): stri
60
58
 
61
59
  const runtimeInstallCommands = (settings: DeploySettings, nodeExtensionPath: string): string[] => {
62
60
  const lib = `${DESTINATION}/lib/${settings.binaryName}`;
61
+ const locale = `${DESTINATION}/share/${LOCALE_DIRNAME}`;
63
62
 
64
63
  return [
65
64
  installCommand(`${nodeExtensionPath}/bin/node`, `${lib}/node`, "m755"),
@@ -69,6 +68,7 @@ const runtimeInstallCommands = (settings: DeploySettings, nodeExtensionPath: str
69
68
  "test ! -f dist/gschemas.compiled || " +
70
69
  installCommand("dist/gschemas.compiled", `${lib}/gschemas.compiled`, "m644"),
71
70
  `test ! -d dist/assets || cp -a dist/assets ${lib}/assets`,
71
+ `test ! -d dist/${LOCALE_DIRNAME} || { mkdir -p ${locale} && cp -a dist/${LOCALE_DIRNAME}/. ${locale}/; }`,
72
72
  installCommand(LAUNCHER_FILENAME, `${DESTINATION}/bin/${settings.binaryName}`, "m755"),
73
73
  ];
74
74
  };
@@ -87,10 +87,35 @@ const activationInstallCommands = (settings: DeploySettings): string[] =>
87
87
  )]
88
88
  : [];
89
89
 
90
- const mimeSources = (settings: DeploySettings): FlatpakModule[] => {
91
- const contents = renderMimePackage(settings);
90
+ const stagedContents = (payload: DeployPayload, destination: string): string => {
91
+ const file = payload.stage.find((candidate) => candidate.rel === destination);
92
92
 
93
- return contents === null ? [] : [inlineSource(`${settings.applicationId}.xml`, contents)];
93
+ if (file === undefined) {
94
+ throw new Error(`Cannot render the source Flatpak: ${destination} was not staged`);
95
+ }
96
+
97
+ return readFileSync(file.abs, "utf8");
98
+ };
99
+
100
+ const stagedMetadataSources = (payload: DeployPayload): FlatpakModule[] => {
101
+ const settings = payload.settings;
102
+
103
+ return [
104
+ inlineSource(
105
+ `${settings.applicationId}.desktop`,
106
+ stagedContents(payload, posix.join("share", "applications", `${settings.applicationId}.desktop`)),
107
+ ),
108
+ inlineSource(
109
+ `${settings.applicationId}.metainfo.xml`,
110
+ stagedContents(payload, posix.join("share", "metainfo", `${settings.applicationId}.metainfo.xml`)),
111
+ ),
112
+ ...(settings.fileAssociations.length === 0
113
+ ? []
114
+ : [inlineSource(
115
+ `${settings.applicationId}.xml`,
116
+ stagedContents(payload, posix.join("share", "mime", "packages", `${settings.applicationId}.xml`)),
117
+ )]),
118
+ ];
94
119
  };
95
120
 
96
121
  const mimeInstallCommands = (settings: DeploySettings): string[] =>
@@ -239,9 +264,7 @@ const flatpakSourceModule = (payload: DeployPayload): FlatpakModule => {
239
264
  resolveGitSource(settings),
240
265
  ...(pin === null ? [] : pnpmSources(pin, moduleDirFor(settings))),
241
266
  GENERATED_SOURCES,
242
- inlineSource(`${settings.applicationId}.desktop`, renderDesktopEntry(settings)),
243
- inlineSource(`${settings.applicationId}.metainfo.xml`, renderMetainfo(settings)),
244
- ...mimeSources(settings),
267
+ ...stagedMetadataSources(payload),
245
268
  inlineSource(LAUNCHER_FILENAME, renderLauncher(settings)),
246
269
  inlineSource(NOTICES_FILENAME, renderNotices(settings, payload.notices)),
247
270
  ...activationSource(settings),