@gtkx/cli 1.6.0 → 2.0.0-beta.2

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 (309) hide show
  1. package/README.md +5 -5
  2. package/dist/builder.d.ts +1 -0
  3. package/dist/builder.d.ts.map +1 -1
  4. package/dist/builder.js +17 -5
  5. package/dist/builder.js.map +1 -1
  6. package/dist/cli.js +2 -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 +45 -16
  10. package/dist/codegen/freshness.js.map +1 -1
  11. package/dist/codegen/reference.d.ts.map +1 -1
  12. package/dist/codegen/reference.js +1 -7
  13. package/dist/codegen/reference.js.map +1 -1
  14. package/dist/codegen/report.d.ts.map +1 -1
  15. package/dist/codegen/report.js +5 -16
  16. package/dist/codegen/report.js.map +1 -1
  17. package/dist/codegen/run-codegen.d.ts +7 -7
  18. package/dist/codegen/run-codegen.d.ts.map +1 -1
  19. package/dist/codegen/run-codegen.js +35 -107
  20. package/dist/codegen/run-codegen.js.map +1 -1
  21. package/dist/codegen/store-resolver.d.ts +1 -2
  22. package/dist/codegen/store-resolver.d.ts.map +1 -1
  23. package/dist/codegen/store-resolver.js +10 -19
  24. package/dist/codegen/store-resolver.js.map +1 -1
  25. package/dist/commands/build.d.ts +5 -0
  26. package/dist/commands/build.d.ts.map +1 -1
  27. package/dist/commands/build.js +83 -10
  28. package/dist/commands/build.js.map +1 -1
  29. package/dist/commands/codegen.d.ts +1 -0
  30. package/dist/commands/codegen.d.ts.map +1 -1
  31. package/dist/commands/codegen.js +6 -5
  32. package/dist/commands/codegen.js.map +1 -1
  33. package/dist/commands/deploy.d.ts +1 -0
  34. package/dist/commands/deploy.d.ts.map +1 -1
  35. package/dist/commands/deploy.js +4 -2
  36. package/dist/commands/deploy.js.map +1 -1
  37. package/dist/commands/dev.d.ts +8 -0
  38. package/dist/commands/dev.d.ts.map +1 -1
  39. package/dist/commands/dev.js +19 -1
  40. package/dist/commands/dev.js.map +1 -1
  41. package/dist/commands/docs.d.ts.map +1 -1
  42. package/dist/commands/docs.js +5 -15
  43. package/dist/commands/docs.js.map +1 -1
  44. package/dist/deploy/freedesktop/copyright.d.ts.map +1 -1
  45. package/dist/deploy/freedesktop/copyright.js +2 -13
  46. package/dist/deploy/freedesktop/copyright.js.map +1 -1
  47. package/dist/deploy/freedesktop/metainfo.d.ts.map +1 -1
  48. package/dist/deploy/freedesktop/metainfo.js +41 -2
  49. package/dist/deploy/freedesktop/metainfo.js.map +1 -1
  50. package/dist/deploy/freedesktop/xml.d.ts.map +1 -1
  51. package/dist/deploy/freedesktop/xml.js +21 -25
  52. package/dist/deploy/freedesktop/xml.js.map +1 -1
  53. package/dist/deploy/nfpm/config.d.ts.map +1 -1
  54. package/dist/deploy/nfpm/config.js +5 -2
  55. package/dist/deploy/nfpm/config.js.map +1 -1
  56. package/dist/deploy/nfpm/contents.d.ts.map +1 -1
  57. package/dist/deploy/nfpm/contents.js +8 -0
  58. package/dist/deploy/nfpm/contents.js.map +1 -1
  59. package/dist/deploy/node-runtime/elf.d.ts +2 -1
  60. package/dist/deploy/node-runtime/elf.d.ts.map +1 -1
  61. package/dist/deploy/node-runtime/elf.js +32 -5
  62. package/dist/deploy/node-runtime/elf.js.map +1 -1
  63. package/dist/deploy/node-runtime/index.d.ts +2 -1
  64. package/dist/deploy/node-runtime/index.d.ts.map +1 -1
  65. package/dist/deploy/node-runtime/index.js +73 -4
  66. package/dist/deploy/node-runtime/index.js.map +1 -1
  67. package/dist/deploy/node-runtime/license.d.ts.map +1 -1
  68. package/dist/deploy/node-runtime/license.js +2 -9
  69. package/dist/deploy/node-runtime/license.js.map +1 -1
  70. package/dist/deploy/notices/node-runtime.js +2 -2
  71. package/dist/deploy/notices/node-runtime.js.map +1 -1
  72. package/dist/deploy/notices/packages.d.ts.map +1 -1
  73. package/dist/deploy/notices/packages.js +3 -10
  74. package/dist/deploy/notices/packages.js.map +1 -1
  75. package/dist/deploy/payload/copy-tree.d.ts.map +1 -1
  76. package/dist/deploy/payload/copy-tree.js +3 -3
  77. package/dist/deploy/payload/copy-tree.js.map +1 -1
  78. package/dist/deploy/payload/launcher.d.ts.map +1 -1
  79. package/dist/deploy/payload/launcher.js +14 -1
  80. package/dist/deploy/payload/launcher.js.map +1 -1
  81. package/dist/deploy/payload/stage.d.ts.map +1 -1
  82. package/dist/deploy/payload/stage.js +2 -4
  83. package/dist/deploy/payload/stage.js.map +1 -1
  84. package/dist/deploy/run-deploy.d.ts +1 -0
  85. package/dist/deploy/run-deploy.d.ts.map +1 -1
  86. package/dist/deploy/run-deploy.js +100 -14
  87. package/dist/deploy/run-deploy.js.map +1 -1
  88. package/dist/deploy/settings/build-manifest.d.ts +6 -1
  89. package/dist/deploy/settings/build-manifest.d.ts.map +1 -1
  90. package/dist/deploy/settings/build-manifest.js +17 -17
  91. package/dist/deploy/settings/build-manifest.js.map +1 -1
  92. package/dist/deploy/settings/extra-files.d.ts +1 -1
  93. package/dist/deploy/settings/extra-files.d.ts.map +1 -1
  94. package/dist/deploy/settings/extra-files.js +22 -1
  95. package/dist/deploy/settings/extra-files.js.map +1 -1
  96. package/dist/deploy/settings/index.d.ts +1 -0
  97. package/dist/deploy/settings/index.d.ts.map +1 -1
  98. package/dist/deploy/settings/index.js +2 -1
  99. package/dist/deploy/settings/index.js.map +1 -1
  100. package/dist/deploy/settings/libraries.d.ts.map +1 -1
  101. package/dist/deploy/settings/libraries.js +2 -19
  102. package/dist/deploy/settings/libraries.js.map +1 -1
  103. package/dist/deploy/settings/paths.d.ts +2 -2
  104. package/dist/deploy/settings/paths.d.ts.map +1 -1
  105. package/dist/deploy/settings/paths.js +110 -12
  106. package/dist/deploy/settings/paths.js.map +1 -1
  107. package/dist/deploy/settings/screenshots.d.ts.map +1 -1
  108. package/dist/deploy/settings/screenshots.js +6 -31
  109. package/dist/deploy/settings/screenshots.js.map +1 -1
  110. package/dist/deploy/targets/flatpak-pnpm.js +4 -4
  111. package/dist/deploy/targets/flatpak-pnpm.js.map +1 -1
  112. package/dist/deploy/targets/flatpak-source.d.ts.map +1 -1
  113. package/dist/deploy/targets/flatpak-source.js +15 -6
  114. package/dist/deploy/targets/flatpak-source.js.map +1 -1
  115. package/dist/deploy/targets/flatpak-sources.js +1 -1
  116. package/dist/deploy/targets/flatpak-sources.js.map +1 -1
  117. package/dist/deploy/types.d.ts +1 -0
  118. package/dist/deploy/types.d.ts.map +1 -1
  119. package/dist/deploy/types.js.map +1 -1
  120. package/dist/dev/headless.d.ts +3 -0
  121. package/dist/dev/headless.d.ts.map +1 -0
  122. package/dist/dev/headless.js +35 -0
  123. package/dist/dev/headless.js.map +1 -0
  124. package/dist/dev/runner-deps.js +1 -1
  125. package/dist/dev/runner-deps.js.map +1 -1
  126. package/dist/dev/runner-main.d.ts.map +1 -1
  127. package/dist/dev/runner-main.js +1 -2
  128. package/dist/dev/runner-main.js.map +1 -1
  129. package/dist/dev/schema-dir.d.ts +1 -1
  130. package/dist/dev/schema-dir.d.ts.map +1 -1
  131. package/dist/dev/schema-dir.js +2 -4
  132. package/dist/dev/schema-dir.js.map +1 -1
  133. package/dist/i18n/catalogs.d.ts.map +1 -1
  134. package/dist/i18n/catalogs.js +2 -1
  135. package/dist/i18n/catalogs.js.map +1 -1
  136. package/dist/i18n/metadata-templates.d.ts.map +1 -1
  137. package/dist/i18n/metadata-templates.js +2 -1
  138. package/dist/i18n/metadata-templates.js.map +1 -1
  139. package/dist/i18n/source-messages.d.ts +3 -21
  140. package/dist/i18n/source-messages.d.ts.map +1 -1
  141. package/dist/i18n/source-messages.js +900 -85
  142. package/dist/i18n/source-messages.js.map +1 -1
  143. package/dist/i18n/types.d.ts +6 -7
  144. package/dist/i18n/types.d.ts.map +1 -1
  145. package/dist/i18n/types.js +83 -96
  146. package/dist/i18n/types.js.map +1 -1
  147. package/dist/internal/build-manifest.d.ts +5 -2
  148. package/dist/internal/build-manifest.d.ts.map +1 -1
  149. package/dist/internal/build-manifest.js +15 -2
  150. package/dist/internal/build-manifest.js.map +1 -1
  151. package/dist/internal/build-output.d.ts +8 -0
  152. package/dist/internal/build-output.d.ts.map +1 -0
  153. package/dist/internal/build-output.js +69 -0
  154. package/dist/internal/build-output.js.map +1 -0
  155. package/dist/internal/docs-elements.js +2 -2
  156. package/dist/internal/docs-elements.js.map +1 -1
  157. package/dist/internal/entry-arg.d.ts +4 -1
  158. package/dist/internal/entry-arg.d.ts.map +1 -1
  159. package/dist/internal/entry-arg.js +7 -1
  160. package/dist/internal/entry-arg.js.map +1 -1
  161. package/dist/internal/icon-path.d.ts.map +1 -1
  162. package/dist/internal/icon-path.js +5 -16
  163. package/dist/internal/icon-path.js.map +1 -1
  164. package/dist/internal/output-directory.d.ts +15 -0
  165. package/dist/internal/output-directory.d.ts.map +1 -0
  166. package/dist/internal/output-directory.js +490 -0
  167. package/dist/internal/output-directory.js.map +1 -0
  168. package/dist/internal/prepare-project.d.ts +2 -0
  169. package/dist/internal/prepare-project.d.ts.map +1 -1
  170. package/dist/internal/prepare-project.js +3 -3
  171. package/dist/internal/prepare-project.js.map +1 -1
  172. package/dist/internal/source-imports.d.ts.map +1 -1
  173. package/dist/internal/source-imports.js +10 -41
  174. package/dist/internal/source-imports.js.map +1 -1
  175. package/dist/mcp/client.js +2 -2
  176. package/dist/mcp/client.js.map +1 -1
  177. package/dist/settings/render.d.ts +0 -1
  178. package/dist/settings/render.d.ts.map +1 -1
  179. package/dist/settings/render.js +1 -11
  180. package/dist/settings/render.js.map +1 -1
  181. package/dist/settings/schema.d.ts +2 -2
  182. package/dist/settings/schema.d.ts.map +1 -1
  183. package/dist/settings/schema.js +21 -62
  184. package/dist/settings/schema.js.map +1 -1
  185. package/dist/vite-plugins/asset-imports.d.ts +1 -2
  186. package/dist/vite-plugins/asset-imports.d.ts.map +1 -1
  187. package/dist/vite-plugins/asset-imports.js +19 -32
  188. package/dist/vite-plugins/asset-imports.js.map +1 -1
  189. package/dist/vite-plugins/asset-specifier.d.ts +1 -3
  190. package/dist/vite-plugins/asset-specifier.d.ts.map +1 -1
  191. package/dist/vite-plugins/asset-specifier.js +1 -4
  192. package/dist/vite-plugins/asset-specifier.js.map +1 -1
  193. package/dist/vite-plugins/build-manifest.d.ts +5 -1
  194. package/dist/vite-plugins/build-manifest.d.ts.map +1 -1
  195. package/dist/vite-plugins/build-manifest.js +2 -1
  196. package/dist/vite-plugins/build-manifest.js.map +1 -1
  197. package/dist/vite-plugins/i18n.d.ts.map +1 -1
  198. package/dist/vite-plugins/i18n.js +59 -6
  199. package/dist/vite-plugins/i18n.js.map +1 -1
  200. package/dist/vite-plugins/index.d.ts +8 -1
  201. package/dist/vite-plugins/index.d.ts.map +1 -1
  202. package/dist/vite-plugins/index.js +8 -6
  203. package/dist/vite-plugins/index.js.map +1 -1
  204. package/dist/vite-plugins/resources.d.ts.map +1 -1
  205. package/dist/vite-plugins/resources.js +32 -97
  206. package/dist/vite-plugins/resources.js.map +1 -1
  207. package/dist/vite-plugins/self-contained.d.ts.map +1 -1
  208. package/dist/vite-plugins/self-contained.js +159 -158
  209. package/dist/vite-plugins/self-contained.js.map +1 -1
  210. package/dist/vite-plugins/settings-worker-env.d.ts.map +1 -1
  211. package/dist/vite-plugins/settings-worker-env.js +2 -4
  212. package/dist/vite-plugins/settings-worker-env.js.map +1 -1
  213. package/dist/vite-plugins/settings.d.ts +1 -2
  214. package/dist/vite-plugins/settings.d.ts.map +1 -1
  215. package/dist/vite-plugins/settings.js +8 -14
  216. package/dist/vite-plugins/settings.js.map +1 -1
  217. package/dist/vite-plugins/undeclared-library.d.ts.map +1 -1
  218. package/dist/vite-plugins/undeclared-library.js +10 -13
  219. package/dist/vite-plugins/undeclared-library.js.map +1 -1
  220. package/dist/vite-plugins/worker.d.ts.map +1 -1
  221. package/dist/vite-plugins/worker.js +0 -1
  222. package/dist/vite-plugins/worker.js.map +1 -1
  223. package/package.json +21 -15
  224. package/src/builder.ts +18 -5
  225. package/src/cli.ts +3 -0
  226. package/src/codegen/freshness.ts +66 -17
  227. package/src/codegen/reference.ts +1 -8
  228. package/src/codegen/report.ts +5 -21
  229. package/src/codegen/run-codegen.ts +42 -151
  230. package/src/codegen/store-resolver.ts +10 -26
  231. package/src/commands/build.ts +13 -2
  232. package/src/commands/codegen.ts +6 -5
  233. package/src/commands/deploy.ts +4 -2
  234. package/src/commands/dev.ts +20 -1
  235. package/src/commands/docs.ts +5 -17
  236. package/src/deploy/freedesktop/copyright.ts +2 -14
  237. package/src/deploy/freedesktop/metainfo.ts +73 -21
  238. package/src/deploy/freedesktop/xml.ts +24 -32
  239. package/src/deploy/nfpm/config.ts +5 -2
  240. package/src/deploy/nfpm/contents.ts +8 -0
  241. package/src/deploy/node-runtime/elf.ts +43 -5
  242. package/src/deploy/node-runtime/index.ts +105 -5
  243. package/src/deploy/node-runtime/license.ts +2 -8
  244. package/src/deploy/notices/node-runtime.ts +2 -2
  245. package/src/deploy/notices/packages.ts +3 -11
  246. package/src/deploy/payload/copy-tree.ts +3 -4
  247. package/src/deploy/payload/launcher.ts +20 -1
  248. package/src/deploy/payload/stage.ts +3 -7
  249. package/src/deploy/run-deploy.ts +56 -16
  250. package/src/deploy/settings/build-manifest.ts +30 -17
  251. package/src/deploy/settings/extra-files.ts +26 -2
  252. package/src/deploy/settings/index.ts +3 -1
  253. package/src/deploy/settings/libraries.ts +1 -22
  254. package/src/deploy/settings/paths.ts +68 -13
  255. package/src/deploy/settings/screenshots.ts +8 -46
  256. package/src/deploy/targets/flatpak-pnpm.ts +4 -4
  257. package/src/deploy/targets/flatpak-source.ts +17 -6
  258. package/src/deploy/targets/flatpak-sources.ts +1 -1
  259. package/src/deploy/types.ts +1 -0
  260. package/src/dev/headless.ts +40 -0
  261. package/src/dev/runner-deps.ts +1 -1
  262. package/src/dev/runner-main.ts +1 -2
  263. package/src/dev/schema-dir.ts +2 -4
  264. package/src/i18n/catalogs.ts +2 -1
  265. package/src/i18n/metadata-templates.ts +2 -1
  266. package/src/i18n/source-messages.ts +1442 -262
  267. package/src/i18n/types.ts +97 -131
  268. package/src/internal/build-manifest.ts +25 -1
  269. package/src/internal/build-output.ts +99 -0
  270. package/src/internal/docs-elements.ts +2 -2
  271. package/src/internal/entry-arg.ts +8 -1
  272. package/src/internal/icon-path.ts +6 -19
  273. package/src/internal/output-directory.ts +699 -0
  274. package/src/internal/prepare-project.ts +5 -5
  275. package/src/internal/source-imports.ts +14 -50
  276. package/src/mcp/client.ts +2 -2
  277. package/src/settings/render.ts +1 -13
  278. package/src/settings/schema.ts +19 -81
  279. package/src/vite-plugins/asset-imports.ts +20 -44
  280. package/src/vite-plugins/asset-specifier.ts +0 -5
  281. package/src/vite-plugins/build-manifest.ts +7 -1
  282. package/src/vite-plugins/i18n.ts +87 -5
  283. package/src/vite-plugins/index.ts +16 -11
  284. package/src/vite-plugins/resources.ts +37 -140
  285. package/src/vite-plugins/self-contained.ts +184 -185
  286. package/src/vite-plugins/settings-worker-env.ts +2 -4
  287. package/src/vite-plugins/settings.ts +11 -20
  288. package/src/vite-plugins/undeclared-library.ts +14 -20
  289. package/src/vite-plugins/worker.ts +0 -1
  290. package/dist/i18n/binding-extractor.d.ts +0 -30
  291. package/dist/i18n/binding-extractor.d.ts.map +0 -1
  292. package/dist/i18n/binding-extractor.js +0 -1324
  293. package/dist/i18n/binding-extractor.js.map +0 -1
  294. package/dist/i18n/extractor-plugin.d.ts +0 -8
  295. package/dist/i18n/extractor-plugin.d.ts.map +0 -1
  296. package/dist/i18n/extractor-plugin.js +0 -247
  297. package/dist/i18n/extractor-plugin.js.map +0 -1
  298. package/dist/internal/data-dir.d.ts +0 -4
  299. package/dist/internal/data-dir.d.ts.map +0 -1
  300. package/dist/internal/data-dir.js +0 -52
  301. package/dist/internal/data-dir.js.map +0 -1
  302. package/dist/vite-plugins/animated.d.ts +0 -5
  303. package/dist/vite-plugins/animated.d.ts.map +0 -1
  304. package/dist/vite-plugins/animated.js +0 -325
  305. package/dist/vite-plugins/animated.js.map +0 -1
  306. package/src/i18n/binding-extractor.ts +0 -1969
  307. package/src/i18n/extractor-plugin.ts +0 -413
  308. package/src/internal/data-dir.ts +0 -70
  309. package/src/vite-plugins/animated.ts +0 -486
package/src/i18n/types.ts CHANGED
@@ -1,154 +1,120 @@
1
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import type { I18nextToolkitConfig, Logger, Plugin } from "i18next-cli";
2
+ import { runTypesGenerator } from "i18next-cli";
3
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
4
  import { dirname, join } from "node:path";
3
- import type { SourceMessage } from "./source-messages.js";
4
-
5
- type I18nTypesResult = {
6
- isWritten: boolean;
7
- path: string;
8
- };
9
5
 
6
+ const CONTEXT_SEPARATOR = "\u{4}";
7
+ const DEFAULT_NAMESPACE = "translation";
10
8
  const I18N_TYPES_FILENAME = "i18n.d.ts";
11
- const INTERPOLATION_PATTERN = /\{\{[^{}]+\}\}/u;
12
-
13
- const stringType = (values: string[]): string =>
14
- [...new Set(values)].map((value) => JSON.stringify(value)).join(" | ");
15
-
16
- const contextField = (context: string | null): string =>
17
- context === null ? "" : ` context: ${JSON.stringify(context)};`;
18
-
19
- const hasInterpolation = (values: string[]): boolean => values.some((value) => INTERPOLATION_PATTERN.test(value));
9
+ const I18N_RESOURCES_FILENAME = "i18n-resources.d.ts";
20
10
 
21
- const pointVariant = (message: SourceMessage): string => {
22
- const isRequired = message.context !== null || hasInterpolation([message.msgid]);
23
- const isInterpolated = hasInterpolation([message.msgid]);
11
+ const generatedDir = (root: string): string => join(root, "node_modules", ".gtkx");
12
+ const i18nResourceDir = (root: string): string => join(generatedDir(root), "i18n");
13
+ const i18nTypesPath = (root: string): string => join(generatedDir(root), I18N_TYPES_FILENAME);
14
+ const i18nResourcesPath = (root: string): string => join(generatedDir(root), I18N_RESOURCES_FILENAME);
24
15
 
25
- return (
26
- `{${contextField(message.context)} interpolated: ${String(isInterpolated)}; kind: "point"; ` +
27
- `required: ${String(isRequired)}; value: ${JSON.stringify(message.msgid)} }`
28
- );
16
+ const clearI18nResources = (root: string): void => {
17
+ rmSync(i18nResourceDir(root), { force: true, recursive: true });
29
18
  };
30
19
 
31
- const defaultVariant = (message: SourceMessage): string => {
32
- const values = [message.singular];
33
- const isInterpolated = hasInterpolation(values);
34
- const isRequired = message.context !== null || isInterpolated;
20
+ const i18nToolkitConfig = (
21
+ root: string,
22
+ sourceFiles: string[],
23
+ plugins: Plugin[] = [],
24
+ ): I18nextToolkitConfig => ({
25
+ locales: ["en"],
26
+ plugins,
27
+ extract: {
28
+ contextSeparator: CONTEXT_SEPARATOR,
29
+ defaultNS: DEFAULT_NAMESPACE,
30
+ disablePlurals: false,
31
+ extractFromComments: false,
32
+ functions: ["t"],
33
+ generateBasePluralForms: false,
34
+ input: sourceFiles,
35
+ keySeparator: ".",
36
+ nsSeparator: false,
37
+ output: join(i18nResourceDir(root), "{{language}}", "{{namespace}}.json"),
38
+ pluralSeparator: "_",
39
+ primaryLanguage: "en",
40
+ removeUnusedKeys: true,
41
+ transComponents: ["Trans", "TransWithoutContext"],
42
+ useTranslationNames: ["useTranslation"],
43
+ warnOnConflicts: "error",
44
+ },
45
+ types: {
46
+ input: join(i18nResourceDir(root), "en", "*.json"),
47
+ output: i18nTypesPath(root),
48
+ resourcesFile: i18nResourcesPath(root),
49
+ },
50
+ });
51
+
52
+ const declarations = (): string => `import type Resources from "./i18n-resources.js";
53
+
54
+ declare module "@gtkx/i18n" {
55
+ interface I18nResources extends Resources {}
56
+ }
57
+ `;
58
+
59
+ const writeChanged = (path: string, content: string): void => {
60
+ let current: string | null;
35
61
 
36
- return (
37
- `{${contextField(message.context)} defaultValue: ${JSON.stringify(message.singular)}; ` +
38
- `interpolated: ${String(isInterpolated)}; kind: "default"; required: ${String(isRequired)}; ` +
39
- `value: ${stringType(values)} }`
40
- );
41
- };
42
-
43
- const requirePlural = (message: SourceMessage): string => {
44
- if (message.plural === null) {
45
- throw new Error("Plural translation metadata requires a plural source string");
46
- }
47
-
48
- return message.plural;
49
- };
50
-
51
- const pluralVariant = (message: SourceMessage): string => {
52
- const plural = requirePlural(message);
53
- const values = [message.singular, plural];
54
- const isInterpolated = hasInterpolation(values);
55
-
56
- return (
57
- `{${contextField(message.context)} defaultValue: ${JSON.stringify(plural)}; ` +
58
- `interpolated: ${String(isInterpolated)}; kind: "plural"; required: true; ` +
59
- `value: ${stringType(values)} }`
60
- );
61
- };
62
-
63
- const pluralDefaultsVariant = (message: SourceMessage): string => {
64
- const plural = requirePlural(message);
65
- const values = [message.singular, plural];
66
- const isInterpolated = hasInterpolation(values);
67
-
68
- return (
69
- `{${contextField(message.context)} defaultValueOne: ${JSON.stringify(message.singular)}; ` +
70
- `defaultValueOther: ${JSON.stringify(plural)}; interpolated: ${String(isInterpolated)}; ` +
71
- `kind: "pluralDefaults"; required: true; value: ${stringType(values)} }`
72
- );
73
- };
74
-
75
- const variantsFor = (message: SourceMessage): string[] => {
76
- if (message.kind === "plural") {
77
- return [pluralVariant(message)];
62
+ try {
63
+ current = readFileSync(path, "utf8");
64
+ } catch {
65
+ current = null;
78
66
  }
79
67
 
80
- if (message.kind === "pluralDefaults") {
81
- return [pluralDefaultsVariant(message)];
68
+ if (current === content) {
69
+ return;
82
70
  }
83
71
 
84
- return message.kind === "default" ? [defaultVariant(message)] : [pointVariant(message)];
72
+ mkdirSync(dirname(path), { recursive: true });
73
+ writeFileSync(path, content);
85
74
  };
86
75
 
87
- const renderRegistry = (messages: SourceMessage[]): string[] => {
88
- const entries: Map<string, Set<string>> = new Map();
89
-
90
- for (const message of messages) {
91
- const variants = entries.get(message.msgid) ?? new Set<string>();
92
-
93
- for (const variant of variantsFor(message)) {
94
- variants.add(variant);
95
- }
96
-
97
- entries.set(message.msgid, variants);
98
- }
99
-
100
- return [...entries]
101
- .toSorted(([left], [right]) => left.localeCompare(right))
102
- .map(([msgid, variants]) => ` ${JSON.stringify(msgid)}: ${[...variants].join(" | ")};`);
76
+ const typegenLogger = (): { errors: string[]; logger: Logger } => {
77
+ const errors: string[] = [];
78
+
79
+ return {
80
+ errors,
81
+ logger: {
82
+ error(value) {
83
+ errors.push(String(value));
84
+ },
85
+ info() {
86
+ return;
87
+ },
88
+ warn() {
89
+ return;
90
+ },
91
+ },
92
+ };
103
93
  };
104
94
 
105
- const generatedHeader = (): string[] => [
106
- "/**",
107
- " * Translation declarations generated by GTKX. Regenerated by",
108
- " * `gtkx codegen`, `gtkx dev`, and `gtkx build`; do not edit.",
109
- " */",
110
- ];
95
+ const emitI18nTypes = async (root: string): Promise<void> => {
96
+ const typesPath = i18nTypesPath(root);
97
+ const resourcesPath = i18nResourcesPath(root);
98
+ writeChanged(typesPath, declarations());
99
+ const { errors, logger } = typegenLogger();
100
+ await runTypesGenerator(i18nToolkitConfig(root, []), { logger, quiet: true });
111
101
 
112
- const renderI18nTypes = (messages: SourceMessage[], isEnabled = true): string => {
113
- if (!isEnabled) {
114
- return [...generatedHeader(), "export {};", ""].join("\n");
102
+ if (errors.length > 0 || !existsSync(typesPath) || !existsSync(resourcesPath)) {
103
+ throw new Error(errors.join("\n") || "i18next type generation did not produce its outputs");
115
104
  }
116
-
117
- return [
118
- ...generatedHeader(),
119
- 'import "@gtkx/i18n";',
120
- "",
121
- 'declare module "@gtkx/i18n" {',
122
- " interface TranslationRegistry {",
123
- ...renderRegistry(messages),
124
- " }",
125
- "}",
126
- "",
127
- ].join("\n");
128
105
  };
129
106
 
130
- const i18nTypesPath = (root: string): string => join(root, "node_modules", ".gtkx", I18N_TYPES_FILENAME);
131
-
132
- const readFileOrNull = (path: string): string | null => {
133
- try {
134
- return readFileSync(path, "utf8");
135
- } catch {
136
- return null;
137
- }
107
+ const clearI18nTypes = (root: string): void => {
108
+ rmSync(i18nTypesPath(root), { force: true });
109
+ rmSync(i18nResourcesPath(root), { force: true });
110
+ clearI18nResources(root);
138
111
  };
139
112
 
140
- const emitI18nTypes = (root: string, messages: SourceMessage[], isEnabled = true): I18nTypesResult => {
141
- const path = i18nTypesPath(root);
142
- const content = renderI18nTypes(messages, isEnabled);
143
-
144
- if (readFileOrNull(path) === content) {
145
- return { isWritten: false, path };
146
- }
147
-
148
- mkdirSync(dirname(path), { recursive: true });
149
- writeFileSync(path, content);
150
-
151
- return { isWritten: true, path };
113
+ export {
114
+ I18N_TYPES_FILENAME,
115
+ clearI18nResources,
116
+ clearI18nTypes,
117
+ emitI18nTypes,
118
+ i18nToolkitConfig,
119
+ i18nTypesPath,
152
120
  };
153
-
154
- export { I18N_TYPES_FILENAME, emitI18nTypes, i18nTypesPath };
@@ -1,3 +1,5 @@
1
+ import { createHash } from "node:crypto";
2
+
1
3
  type RecordedPackage = {
2
4
  name: string;
3
5
  version: string | null;
@@ -7,6 +9,8 @@ type RecordedPackage = {
7
9
  type BuildManifest = {
8
10
  generator: typeof BUILD_MANIFEST_GENERATOR;
9
11
  formatVersion: typeof BUILD_MANIFEST_FORMAT_VERSION;
12
+ configFile: string;
13
+ configDigest: string;
10
14
  schemas: string[];
11
15
  packages: RecordedPackage[];
12
16
  };
@@ -17,7 +21,26 @@ type BuildManifestCollector = {
17
21
 
18
22
  const BUILD_MANIFEST_FILENAME = "gtkx-schemas.json";
19
23
  const BUILD_MANIFEST_GENERATOR = "gtkx-build";
20
- const BUILD_MANIFEST_FORMAT_VERSION = 1;
24
+ const BUILD_MANIFEST_FORMAT_VERSION = 2;
25
+
26
+ const canonicalConfig = (value: unknown): unknown => {
27
+ if (Array.isArray(value)) {
28
+ return value.map((entry) => canonicalConfig(entry));
29
+ }
30
+
31
+ if (typeof value !== "object" || value === null) {
32
+ return value;
33
+ }
34
+
35
+ return Object.fromEntries(
36
+ Object.entries(value)
37
+ .toSorted(([left], [right]) => left.localeCompare(right))
38
+ .map(([key, entry]) => [key, canonicalConfig(entry)]),
39
+ );
40
+ };
41
+
42
+ const configDigest = (config: unknown): string =>
43
+ createHash("sha256").update(JSON.stringify(canonicalConfig(config))).digest("hex");
21
44
 
22
45
  const createBuildManifestCollector = (): BuildManifestCollector => ({ schemas: [] });
23
46
 
@@ -25,6 +48,7 @@ export {
25
48
  BUILD_MANIFEST_FILENAME,
26
49
  BUILD_MANIFEST_FORMAT_VERSION,
27
50
  BUILD_MANIFEST_GENERATOR,
51
+ configDigest,
28
52
  createBuildManifestCollector,
29
53
  type BuildManifest,
30
54
  type BuildManifestCollector,
@@ -0,0 +1,99 @@
1
+ import { isPathInside, isRecord } from "@gtkx/utils";
2
+ import { lstatSync, readdirSync } from "node:fs";
3
+ import { join, relative, resolve, sep } from "node:path";
4
+ import {
5
+ BUILD_MANIFEST_FILENAME,
6
+ BUILD_MANIFEST_GENERATOR,
7
+ } from "./build-manifest.js";
8
+ import {
9
+ type OutputDirectoryTransaction,
10
+ prepareOutputDirectory,
11
+ readRegularFile,
12
+ } from "./output-directory.js";
13
+
14
+ const DEFAULT_BUILD_OUT_DIR = "dist";
15
+ const PRESERVED_BUILD_ENTRIES: ReadonlySet<string> = new Set([".git"]);
16
+
17
+ type PreparedBuildOutDir = Disposable & { commit: () => void; path: string };
18
+
19
+ const hasSymlinkComponent = (root: string, target: string): boolean => {
20
+ let current = root;
21
+
22
+ for (const segment of relative(root, target).split(sep)) {
23
+ current = join(current, segment);
24
+
25
+ if (lstatSync(current, { throwIfNoEntry: false })?.isSymbolicLink() === true) {
26
+ return true;
27
+ }
28
+ }
29
+
30
+ return false;
31
+ };
32
+
33
+ const isGtkxBuildDirectory = (path: string): boolean => {
34
+ const manifest = join(path, BUILD_MANIFEST_FILENAME);
35
+ const contents = readRegularFile(manifest);
36
+
37
+ try {
38
+ const parsed: unknown = contents === null ? null : JSON.parse(contents);
39
+
40
+ return isRecord(parsed) && parsed.generator === BUILD_MANIFEST_GENERATOR;
41
+ } catch {
42
+ return false;
43
+ }
44
+ };
45
+
46
+ const isReusableBuildDirectory = (path: string): boolean => {
47
+ const stats = lstatSync(path, { throwIfNoEntry: false });
48
+
49
+ if (stats === undefined) {
50
+ return true;
51
+ }
52
+
53
+ return stats.isDirectory() && (readdirSync(path).length === 0 || isGtkxBuildDirectory(path));
54
+ };
55
+
56
+ const buildOutputError = (root: string): Error =>
57
+ new Error(`Build output must be an empty directory or an earlier GTKX build below the project root ${root}`);
58
+
59
+ const assertSafeBuildLocation = (root: string, outDir: string): void => {
60
+ if (!isPathInside(root, outDir) || hasSymlinkComponent(root, outDir)) {
61
+ throw buildOutputError(root);
62
+ }
63
+ };
64
+
65
+ const resolveBuildOutDir = (root: string, configured?: string): string => {
66
+ const selected = configured ?? DEFAULT_BUILD_OUT_DIR;
67
+ const outDir = resolve(root, selected);
68
+
69
+ assertSafeBuildLocation(root, outDir);
70
+
71
+ if (!isReusableBuildDirectory(outDir)) {
72
+ throw buildOutputError(root);
73
+ }
74
+
75
+ return outDir;
76
+ };
77
+
78
+ const prepareBuildOutDir = (root: string, outDir: string): PreparedBuildOutDir => {
79
+ assertSafeBuildLocation(root, outDir);
80
+ const prepared = prepareOutputDirectory(root, outDir, isReusableBuildDirectory);
81
+
82
+ if (prepared.status === "unsafe") {
83
+ throw buildOutputError(root);
84
+ }
85
+
86
+ const transaction: OutputDirectoryTransaction = prepared.transaction;
87
+
88
+ return {
89
+ path: transaction.path,
90
+ commit: () => {
91
+ transaction.commit(PRESERVED_BUILD_ENTRIES, isGtkxBuildDirectory);
92
+ },
93
+ [Symbol.dispose]: () => {
94
+ transaction[Symbol.dispose]();
95
+ },
96
+ };
97
+ };
98
+
99
+ export { prepareBuildOutDir, resolveBuildOutDir };
@@ -7,13 +7,13 @@ type DocsElements = {
7
7
  };
8
8
 
9
9
  const resolveDocsElements = async (cwd: string): Promise<DocsElements> => {
10
- const { gi, reactSubexports } = resolveStore(cwd);
10
+ const { gi } = resolveStore(cwd);
11
11
 
12
12
  if (!existsSync(gi.storeDir)) {
13
13
  return { props: {}, omittedProps: {} };
14
14
  }
15
15
 
16
- const { props, omittedProps } = await readBuiltinElements(reactSubexports, gi.storeDir);
16
+ const { props, omittedProps } = await readBuiltinElements();
17
17
 
18
18
  return { props, omittedProps };
19
19
  };
@@ -14,6 +14,13 @@ const cwdArg: { cwd: StringArgDef } = {
14
14
  },
15
15
  };
16
16
 
17
+ const configArg: { config: StringArgDef } = {
18
+ config: {
19
+ type: "string",
20
+ description: "Project-relative configuration file (default: gtkx.config.*)",
21
+ },
22
+ };
23
+
17
24
  const entryArg: { entry: PositionalArgDef; cwd: StringArgDef } = {
18
25
  entry: {
19
26
  type: "positional",
@@ -57,4 +64,4 @@ const resolveGivenEntry = (cwd: string, entry: string): string => {
57
64
  const resolveEntry = (cwd: string, entry: string | undefined): string =>
58
65
  entry ? resolveGivenEntry(cwd, entry) : resolveDefaultEntry(cwd);
59
66
 
60
- export { cwdArg, entryArg, resolveCwd, resolveEntry };
67
+ export { configArg, cwdArg, entryArg, resolveCwd, resolveEntry };
@@ -1,5 +1,6 @@
1
- import { type Stats, statSync } from "node:fs";
2
- import { basename, extname, isAbsolute, join, relative, resolve } from "node:path";
1
+ import { isPathInside } from "@gtkx/utils";
2
+ import { statSync } from "node:fs";
3
+ import { basename, extname, isAbsolute, join, resolve } from "node:path";
3
4
 
4
5
  type ResolvedApplicationIcon = { kind: "file"; path: string } |
5
6
  { kind: "none" } |
@@ -10,20 +11,6 @@ const SCALABLE_DIR = "hicolor/scalable/apps";
10
11
  const SIZE_PATTERN = /^(?<width>[1-9]\d{0,3})x(?<height>[1-9]\d{0,3})$/;
11
12
  const SIZE_SEPARATORS = /[-_.]/;
12
13
 
13
- const isInside = (parent: string, candidate: string): boolean => {
14
- const rel = relative(parent, candidate);
15
-
16
- return rel.length > 0 && !rel.startsWith("..") && !isAbsolute(rel);
17
- };
18
-
19
- const getStats = (path: string): Stats | undefined => {
20
- try {
21
- return statSync(path, { throwIfNoEntry: false });
22
- } catch {
23
- return undefined;
24
- }
25
- };
26
-
27
14
  const iconExtension = (file: string): string => {
28
15
  const extension = extname(file).toLowerCase();
29
16
 
@@ -37,7 +24,7 @@ const iconExtension = (file: string): string => {
37
24
  const defaultIconFiles = (root: string, applicationId: string): string[] =>
38
25
  [...ICON_EXTENSIONS]
39
26
  .map((extension) => join(root, `${applicationId}${extension}`))
40
- .filter((path) => getStats(path)?.isFile() === true);
27
+ .filter((path) => statSync(path, { throwIfNoEntry: false })?.isFile() === true);
41
28
 
42
29
  const resolveDefaultApplicationIcon = (root: string, applicationId: string): ResolvedApplicationIcon => {
43
30
  const files = defaultIconFiles(root, applicationId);
@@ -76,11 +63,11 @@ const resolveApplicationIcon = (
76
63
 
77
64
  const path = resolve(root, configured);
78
65
 
79
- if (!isInside(root, path)) {
66
+ if (!isPathInside(root, path)) {
80
67
  throw new Error(`Cannot use "${configured}" as the application icon: it is outside ${root}`);
81
68
  }
82
69
 
83
- const stats = getStats(path);
70
+ const stats = statSync(path, { throwIfNoEntry: false });
84
71
 
85
72
  if (stats?.isDirectory() === true) {
86
73
  return { kind: "theme", path };