@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
package/src/builder.ts CHANGED
@@ -10,6 +10,7 @@ import { gtkxWorker } from "./vite-plugins/worker.js";
10
10
 
11
11
  type BuildOptions = {
12
12
  entry: string;
13
+ shouldPreserveI18nMetadata?: boolean | undefined;
13
14
  vite?: InlineConfig | undefined;
14
15
  };
15
16
 
@@ -32,14 +33,14 @@ const buildDefaults: InlineConfig = {
32
33
  };
33
34
 
34
35
  const build = async (options: BuildOptions): Promise<string> => {
35
- const { entry, vite: viteConfig } = options;
36
+ const { entry, shouldPreserveI18nMetadata = true, vite: viteConfig } = options;
36
37
  const root = viteConfig?.root ?? process.cwd();
37
38
  const assetsDir = viteConfig?.build?.assetsDir ?? DEFAULT_ASSETS_DIR;
38
39
  const buildManifest = createBuildManifestCollector();
39
40
 
40
41
  const forced: InlineConfig = {
41
42
  plugins: [
42
- ...gtkxVitePlugins(BUILD_MODE, undefined, buildManifest),
43
+ ...gtkxVitePlugins(BUILD_MODE, entry, buildManifest, shouldPreserveI18nMetadata),
43
44
  gtkxWorker(),
44
45
  gtkxNative(root),
45
46
  gtkxBuildManifest(root, buildManifest),
package/src/cli.ts CHANGED
@@ -38,6 +38,11 @@ const main = defineCommand({
38
38
 
39
39
  return withErrorBoundary(docs);
40
40
  },
41
+ mcp: async () => {
42
+ const { mcp } = await import("./commands/mcp.js");
43
+
44
+ return withErrorBoundary(mcp);
45
+ },
41
46
  create: async () => {
42
47
  const { scaffoldCommand } = await import("create-gtkx");
43
48
 
@@ -1,5 +1,6 @@
1
1
  import type { Config } from "@gtkx/config";
2
2
  import { resolveGirPath, resolveLibraries } from "@gtkx/codegen";
3
+ import { resolveFuture } from "@gtkx/config/internal";
3
4
  import { existsSync } from "node:fs";
4
5
  import { join } from "node:path";
5
6
  import { type CodegenStore, resolveCodegenStore } from "./store-resolver.js";
@@ -14,7 +15,7 @@ const REACT_GENERATED_MODULES: string[] = ["index.js", "metadata.js", join("gtk"
14
15
 
15
16
  const resolveCodegenInputs = (cwd: string, config: Config): CodegenInputs => {
16
17
  const girPath = resolveGirPath(config.girPath);
17
- const libraries = resolveLibraries(config.libraries, girPath);
18
+ const libraries = resolveLibraries(config.libraries, girPath, resolveFuture(config.future).isAdwaitaDefault);
18
19
  const store = resolveCodegenStore(cwd);
19
20
 
20
21
  return { girPath, libraries, store };
@@ -0,0 +1,59 @@
1
+ import type { Config } from "@gtkx/config";
2
+ import { mergeOmittedProps } from "@gtkx/codegen";
3
+ import { writeDocs } from "@gtkx/codegen/internal";
4
+ import { isAgentReferenceEnabled, resolveOmittedProps } from "@gtkx/config/internal";
5
+ import { join } from "node:path";
6
+ import { resolveDocsElements } from "../internal/docs-elements.js";
7
+
8
+ type WriteReferenceOptions = {
9
+ root: string;
10
+ config: Config;
11
+ girPath: string[];
12
+ libraries: string[];
13
+ isForced?: boolean;
14
+ };
15
+
16
+ type ReferenceResult = {
17
+ isRegenerated: boolean;
18
+ elements: number;
19
+ namespaces: number;
20
+ };
21
+
22
+ const REFERENCE_PATH = ".gtkx/reference";
23
+ const SKIPPED: ReferenceResult = { isRegenerated: false, elements: 0, namespaces: 0 };
24
+
25
+ const resolveReferenceDir = (root: string): string => join(root, ".gtkx", "reference");
26
+
27
+ const writeReference = async (options: WriteReferenceOptions): Promise<ReferenceResult> => {
28
+ const { root, config, girPath, libraries } = options;
29
+
30
+ if (!isAgentReferenceEnabled(config) || girPath.length === 0 || libraries.length === 0) {
31
+ return SKIPPED;
32
+ }
33
+
34
+ const builtin = await resolveDocsElements(root);
35
+
36
+ const { isRegenerated, namespaces } = writeDocs({
37
+ libraries,
38
+ girPath,
39
+ outDir: resolveReferenceDir(root),
40
+ basePath: REFERENCE_PATH,
41
+ linkStyle: "file",
42
+ props: builtin.props,
43
+ omittedProps: mergeOmittedProps(builtin.omittedProps, resolveOmittedProps(config.elements)),
44
+ isForced: options.isForced === true,
45
+ isByteArrayTyped: config.future?.v2ByteArrays === true,
46
+ isValueUnwrapped: config.future?.v2ValueReturns === true,
47
+ isFinishTrimmed: config.future?.v2FinishResults === true,
48
+ isInoutInPlace: config.future?.v2InoutReturns === true,
49
+ isTreeShaken: config.future?.v2TreeShaking === true,
50
+ });
51
+
52
+ return {
53
+ isRegenerated,
54
+ elements: namespaces.reduce((total, namespace) => total + namespace.elements.length, 0),
55
+ namespaces: namespaces.length,
56
+ };
57
+ };
58
+
59
+ export { REFERENCE_PATH, writeReference, type ReferenceResult };
@@ -1,8 +1,9 @@
1
1
  import type { RunCodegenResult } from "./run-codegen.js";
2
+ import { REFERENCE_PATH } from "./reference.js";
2
3
 
3
4
  const PREFIX = "codegen: ";
4
5
 
5
- const formatCodegenResult = (result: RunCodegenResult, totalMs: number): string[] => {
6
+ const contextDetails = (result: RunCodegenResult): string[] => {
6
7
  const details: string[] = [];
7
8
 
8
9
  if (result.configFile) {
@@ -21,10 +22,24 @@ const formatCodegenResult = (result: RunCodegenResult, totalMs: number): string[
21
22
  details.push(`future=${result.future.join(", ")}`);
22
23
  }
23
24
 
24
- details.push(
25
+ return details;
26
+ };
27
+
28
+ const referenceDetails = (result: RunCodegenResult): string[] => {
29
+ if (result.reference?.isRegenerated !== true) {
30
+ return [];
31
+ }
32
+
33
+ return [`${String(result.reference.elements)} reference pages in ${REFERENCE_PATH}`];
34
+ };
35
+
36
+ const formatCodegenResult = (result: RunCodegenResult, totalMs: number): string[] => {
37
+ const details = [
38
+ ...contextDetails(result),
25
39
  `${String(result.namespaces)} namespaces, ${String(result.intrinsicElements)} intrinsic elements ` +
26
40
  `in ${String(result.duration)}ms (total ${String(totalMs)}ms)`,
27
- );
41
+ ...referenceDetails(result),
42
+ ];
28
43
 
29
44
  return details.map((detail) => PREFIX + detail);
30
45
  };
@@ -1,18 +1,33 @@
1
1
  import { runCodegen as runCodegenCore } from "@gtkx/codegen";
2
- import { getShadowingStorePaths, sweepProjectStaging } from "@gtkx/codegen/internal";
2
+ import {
3
+ getShadowingStorePaths,
4
+ LIBRARIES_WILDCARD,
5
+ redundantLibraries,
6
+ sweepProjectStaging,
7
+ } from "@gtkx/codegen/internal";
3
8
  import { type Config, loadConfig } from "@gtkx/config";
4
9
  import {
10
+ isAgentRulesEnabled,
11
+ type ResolvedFuture,
5
12
  resolveElementComponents,
6
13
  resolveElementProps,
14
+ resolveFuture,
7
15
  resolveLazyElements,
8
16
  resolveOmittedProps,
17
+ unknownFutureKeys,
9
18
  } from "@gtkx/config/internal";
10
19
  import { info, warn } from "@gtkx/utils";
11
- import { rmSync } from "node:fs";
12
- import { resolve } from "node:path";
20
+ import { existsSync, rmSync } from "node:fs";
21
+ import { join, resolve } from "node:path";
22
+ import { resolveCatalogProject, synchronizeCatalogs } from "../i18n/catalogs.js";
23
+ import { extractSourceCatalog } from "../i18n/source-messages.js";
24
+ import { emitI18nTypes } from "../i18n/types.js";
25
+ import { upsertAgentRules } from "../internal/agent-rules.js";
13
26
  import { resolveDataDir } from "../internal/data-dir.js";
27
+ import { discoverSourceFiles } from "../internal/source-imports.js";
14
28
  import { emitSchemaEnv } from "../settings/schema.js";
15
29
  import { type CodegenInputs, isCodegenStale, resolveCodegenInputs } from "./freshness.js";
30
+ import { type ReferenceResult, writeReference } from "./reference.js";
16
31
  import { type CodegenContext, type CodegenStore, resolveCodegenContext } from "./store-resolver.js";
17
32
 
18
33
  type RunCodegenOptions = {
@@ -21,6 +36,7 @@ type RunCodegenOptions = {
21
36
  isForced?: boolean;
22
37
  inputs?: CodegenInputs;
23
38
  resolved?: LoadedConfig;
39
+ shouldPreserveI18nMetadata?: boolean | undefined;
24
40
  };
25
41
 
26
42
  type LoadedConfig = {
@@ -38,6 +54,7 @@ type RunCodegenResult = {
38
54
  configFile?: string | undefined;
39
55
  libraries?: string[] | undefined;
40
56
  future?: string[] | undefined;
57
+ reference?: ReferenceResult | undefined;
41
58
  };
42
59
 
43
60
  type CodegenOptionsInput = {
@@ -45,20 +62,23 @@ type CodegenOptionsInput = {
45
62
  libraries: string[];
46
63
  girPath: string[];
47
64
  elements: Config["elements"];
48
- isByteArrayTyped: boolean;
49
- isValueUnwrapped: boolean;
50
- isFinishTrimmed: boolean;
51
- isInoutInPlace: boolean;
65
+ future: ResolvedFuture;
52
66
  };
53
67
 
54
68
  type PreparedCodegen = CodegenInputs & { isForced: boolean };
55
- type EnsureGeneratedOptions = { shouldAnnounce?: boolean; mode?: string };
69
+
70
+ type EnsureGeneratedOptions = {
71
+ shouldAnnounce?: boolean;
72
+ mode?: string;
73
+ shouldPreserveI18nMetadata?: boolean | undefined;
74
+ };
56
75
 
57
76
  type RunOptionsInput = {
58
77
  root: string;
59
78
  mode: string | undefined;
60
79
  inputs: CodegenInputs | null;
61
80
  resolved: LoadedConfig;
81
+ shouldPreserveI18nMetadata: boolean | undefined;
62
82
  };
63
83
 
64
84
  const GIR_PATH_MISSING_MESSAGE =
@@ -77,13 +97,14 @@ const removeShadowingStores = (cwd: string): void => {
77
97
  removeStores(getShadowingStorePaths(cwd));
78
98
  };
79
99
 
80
- const codegenOptions = ({ store, libraries, girPath, elements, ...future }: CodegenOptionsInput) => ({
100
+ const codegenOptions = ({ store, libraries, girPath, elements, future }: CodegenOptionsInput) => ({
81
101
  libraries,
82
102
  girPath,
83
103
  isByteArrayTyped: future.isByteArrayTyped,
84
104
  isValueUnwrapped: future.isValueUnwrapped,
85
105
  isFinishTrimmed: future.isFinishTrimmed,
86
106
  isInoutInPlace: future.isInoutInPlace,
107
+ isTreeShaken: future.isTreeShaken,
87
108
  gi: {
88
109
  storeDir: store.giStoreDir,
89
110
  linkDir: store.giLinkDir,
@@ -104,16 +125,19 @@ const codegenOptions = ({ store, libraries, girPath, elements, ...future }: Code
104
125
  userOmittedProps: resolveOmittedProps(elements),
105
126
  });
106
127
 
107
- const enabledFutureFlags = (config: Config): string[] =>
108
- Object.entries(config.future ?? {})
109
- .filter(([, value]) => value === true)
128
+ const enabledFutureFlags = (config: Config): string[] => {
129
+ const unknown: Set<string> = new Set(unknownFutureKeys(config.future));
130
+
131
+ return Object.entries(config.future ?? {})
132
+ .filter(([name, value]) => value === true && !unknown.has(name))
110
133
  .map(([name]) => name);
134
+ };
111
135
 
112
- const disabledCodegenResult = (configFile: string): RunCodegenResult => ({
136
+ const disabledCodegenResult = (configFile: string, notices: string[]): RunCodegenResult => ({
113
137
  isRegenerated: false,
114
138
  namespaces: 0,
115
139
  intrinsicElements: 0,
116
- notices: [],
140
+ notices,
117
141
  duration: 0,
118
142
  girPath: [],
119
143
  configFile,
@@ -126,6 +150,46 @@ const warnNotices = (notices: string[]): void => {
126
150
  }
127
151
  };
128
152
 
153
+ const unknownFutureNotice = (keys: string[]): string[] => {
154
+ if (keys.length === 0) {
155
+ return [];
156
+ }
157
+
158
+ return [
159
+ `gtkx.config.ts: \`future\` has no ${keys.join(", ")} flag, so the key is ignored. Check it against ` +
160
+ "the flags the configuration guide lists.",
161
+ ];
162
+ };
163
+
164
+ const redundantLibraryNotice = (entries: string[]): string[] => {
165
+ if (entries.length === 0) {
166
+ return [];
167
+ }
168
+
169
+ return [
170
+ `gtkx.config.ts: \`future.v2DefaultLibraries\` binds ${entries.join(", ")} on its own, so naming them ` +
171
+ "in `libraries` changes nothing. Drop them; naming them becomes a configuration error in GTKX 2.0.",
172
+ ];
173
+ };
174
+
175
+ const wildcardLibrariesNotice = (libraries: Config["libraries"]): string[] => {
176
+ if (libraries !== LIBRARIES_WILDCARD) {
177
+ return [];
178
+ }
179
+
180
+ return [
181
+ 'gtkx.config.ts: `libraries: "*"` binds whatever the build host happens to have installed, so the ' +
182
+ "store changes with the machine. Instead, list the libraries the project needs; the wildcard is " +
183
+ "removed in GTKX 2.0.",
184
+ ];
185
+ };
186
+
187
+ const configNotices = (config: Config, future: ResolvedFuture): string[] => [
188
+ ...unknownFutureNotice(unknownFutureKeys(config.future)),
189
+ ...wildcardLibrariesNotice(config.libraries),
190
+ ...redundantLibraryNotice(redundantLibraries(config.libraries, future.isAdwaitaDefault)),
191
+ ];
192
+
129
193
  const regeneratedStorePaths = (store: CodegenStore): string[] => {
130
194
  if (store.react === null) {
131
195
  return [store.giStoreDir, store.giLinkDir];
@@ -156,12 +220,16 @@ const prepareCodegen = (options: RunCodegenOptions, cwd: string, config: Config)
156
220
  const runCodegen = async (options: RunCodegenOptions = {}): Promise<RunCodegenResult> => {
157
221
  const cwd = options.cwd ?? process.cwd();
158
222
  const { config, configFile } = await resolveLoadedConfig(options, cwd);
159
- syncSchemaEnv(cwd, config.future?.v2ResourceImports === true);
223
+ const future = resolveFuture(config.future);
224
+ const configured = configNotices(config, future);
225
+ warnNotices(configured);
226
+ await syncI18n(cwd, config.applicationId, options.shouldPreserveI18nMetadata);
227
+ syncSchemaEnv(cwd, future.isResourceImported);
160
228
 
161
229
  if (config.codegen === false) {
162
230
  removeShadowingStores(cwd);
163
231
 
164
- return disabledCodegenResult(configFile);
232
+ return disabledCodegenResult(configFile, configured);
165
233
  }
166
234
 
167
235
  const { girPath, libraries, store, isForced } = prepareCodegen(options, cwd, config);
@@ -171,34 +239,55 @@ const runCodegen = async (options: RunCodegenOptions = {}): Promise<RunCodegenRe
171
239
  }
172
240
 
173
241
  const result = await runCodegenCore({
174
- ...codegenOptions({
175
- store,
176
- libraries,
177
- girPath,
178
- elements: config.elements,
179
- isByteArrayTyped: config.future?.v2ByteArrays === true,
180
- isValueUnwrapped: config.future?.v2ValueReturns === true,
181
- isFinishTrimmed: config.future?.v2FinishResults === true,
182
- isInoutInPlace: config.future?.v2InoutReturns === true,
183
- }),
242
+ ...codegenOptions({ store, libraries, girPath, elements: config.elements, future }),
184
243
  isForced,
185
244
  });
186
245
 
187
246
  warnNotices(result.notices);
247
+ const notices = [...configured, ...result.notices];
248
+ const reference = await writeReference({ root: cwd, config, girPath, libraries, isForced });
249
+ syncAgentRules(cwd, config);
188
250
 
189
251
  return {
190
252
  isRegenerated: result.isRegenerated,
191
253
  namespaces: result.namespaces,
192
254
  intrinsicElements: result.intrinsicElements,
193
- notices: result.notices,
255
+ notices,
194
256
  duration: result.duration,
195
257
  girPath,
196
258
  configFile,
197
259
  libraries,
198
260
  future: enabledFutureFlags(config),
261
+ reference,
199
262
  };
200
263
  };
201
264
 
265
+ const syncAgentRules = (root: string, config: Config): void => {
266
+ if (isAgentRulesEnabled(config)) {
267
+ upsertAgentRules(root);
268
+ }
269
+ };
270
+
271
+ const syncI18n = async (
272
+ root: string,
273
+ applicationId: string,
274
+ shouldPreserveMetadataMessages = true,
275
+ ): Promise<void> => {
276
+ const project = resolveCatalogProject(root, applicationId);
277
+
278
+ if (project === null) {
279
+ emitI18nTypes(root, [], false);
280
+
281
+ return;
282
+ }
283
+
284
+ const srcDir = join(root, "src");
285
+ const sourceFiles = discoverSourceFiles(existsSync(srcDir) ? srcDir : root);
286
+ const catalog = await extractSourceCatalog(project, sourceFiles, shouldPreserveMetadataMessages);
287
+ synchronizeCatalogs(project);
288
+ emitI18nTypes(root, catalog.messages);
289
+ };
290
+
202
291
  const isCodegenDisabled = async (cwd: string, mode?: string): Promise<boolean> => {
203
292
  try {
204
293
  const { config } = await loadConfig(cwd, { mode });
@@ -232,12 +321,18 @@ const maybeAnnounceStale = (shouldAnnounce: boolean | undefined, inputs: Codegen
232
321
  }
233
322
  };
234
323
 
235
- const getRunOptions = ({ root, mode, inputs, resolved }: RunOptionsInput): RunCodegenOptions => {
324
+ const getRunOptions = ({
325
+ root,
326
+ mode,
327
+ inputs,
328
+ resolved,
329
+ shouldPreserveI18nMetadata,
330
+ }: RunOptionsInput): RunCodegenOptions => {
236
331
  if (inputs === null) {
237
- return { cwd: root, mode, resolved };
332
+ return { cwd: root, mode, resolved, shouldPreserveI18nMetadata };
238
333
  }
239
334
 
240
- return { cwd: root, mode, inputs, resolved };
335
+ return { cwd: root, mode, inputs, resolved, shouldPreserveI18nMetadata };
241
336
  };
242
337
 
243
338
  const isPreflightSkipped = (options: EnsureGeneratedOptions): boolean =>
@@ -250,6 +345,7 @@ const generate = async (context: CodegenContext, options: EnsureGeneratedOptions
250
345
  cwd: context.root,
251
346
  mode: options.mode,
252
347
  resolved: { config: context.config, configFile: context.configFile },
348
+ shouldPreserveI18nMetadata: options.shouldPreserveI18nMetadata,
253
349
  });
254
350
 
255
351
  return result.isRegenerated;
@@ -258,7 +354,14 @@ const generate = async (context: CodegenContext, options: EnsureGeneratedOptions
258
354
  const inputs = resolveInputsOrNull(context.root, context.config);
259
355
  maybeAnnounceStale(options.shouldAnnounce, inputs);
260
356
  const resolved = { config: context.config, configFile: context.configFile };
261
- const result = await runCodegen(getRunOptions({ root: context.root, mode: options.mode, inputs, resolved }));
357
+
358
+ const result = await runCodegen(getRunOptions({
359
+ root: context.root,
360
+ mode: options.mode,
361
+ inputs,
362
+ resolved,
363
+ shouldPreserveI18nMetadata: options.shouldPreserveI18nMetadata,
364
+ }));
262
365
 
263
366
  return result.isRegenerated;
264
367
  };
@@ -12,7 +12,7 @@ const FORCED_WHILE_DISABLED_MESSAGE =
12
12
  const codegen = defineCommand({
13
13
  meta: {
14
14
  name: "codegen",
15
- description: "Generate TypeScript bindings for the GIR libraries declared in gtkx.config.ts",
15
+ description: "Generate project bindings, translation catalogs, and TypeScript declarations",
16
16
  },
17
17
  args: {
18
18
  force: {
@@ -1,19 +1,11 @@
1
- import {
2
- type ElementProps,
3
- mergeOmittedProps,
4
- type OmittedProps,
5
- readBuiltinElements,
6
- resolveGirPath,
7
- resolveLibraries,
8
- resolveStore,
9
- } from "@gtkx/codegen";
1
+ import { mergeOmittedProps, resolveGirPath, resolveLibraries } from "@gtkx/codegen";
10
2
  import { writeDocs } from "@gtkx/codegen/internal";
11
3
  import { loadConfig } from "@gtkx/config";
12
- import { resolveOmittedProps } from "@gtkx/config/internal";
4
+ import { resolveFuture, resolveOmittedProps } from "@gtkx/config/internal";
13
5
  import { info } from "@gtkx/utils";
14
6
  import { defineCommand } from "citty";
15
- import { existsSync } from "node:fs";
16
7
  import { isAbsolute, relative, resolve, sep } from "node:path";
8
+ import { resolveDocsElements } from "../internal/docs-elements.js";
17
9
  import { cwdArg, resolveCwd } from "../internal/entry-arg.js";
18
10
 
19
11
  const docs = defineCommand({
@@ -65,7 +57,8 @@ const docs = defineCommand({
65
57
  );
66
58
  }
67
59
 
68
- const libraries = resolveLibraries(config.libraries, girPath);
60
+ const future = resolveFuture(config.future);
61
+ const libraries = resolveLibraries(config.libraries, girPath, future.isAdwaitaDefault);
69
62
  const startedAt = Date.now();
70
63
  const outDir = resolveOutDir(cwd, args.out);
71
64
  const builtin = await resolveDocsElements(cwd);
@@ -78,10 +71,11 @@ const docs = defineCommand({
78
71
  props: builtin.props,
79
72
  omittedProps: mergeOmittedProps(builtin.omittedProps, resolveOmittedProps(config.elements)),
80
73
  isForced: args.force,
81
- isByteArrayTyped: config.future?.v2ByteArrays === true,
82
- isValueUnwrapped: config.future?.v2ValueReturns === true,
83
- isFinishTrimmed: config.future?.v2FinishResults === true,
84
- isInoutInPlace: config.future?.v2InoutReturns === true,
74
+ isByteArrayTyped: future.isByteArrayTyped,
75
+ isValueUnwrapped: future.isValueUnwrapped,
76
+ isFinishTrimmed: future.isFinishTrimmed,
77
+ isInoutInPlace: future.isInoutInPlace,
78
+ isTreeShaken: future.isTreeShaken,
85
79
  });
86
80
 
87
81
  if (!isRegenerated) {
@@ -130,16 +124,4 @@ const resolveOutDir = (cwd: string, out: string): string => {
130
124
  );
131
125
  };
132
126
 
133
- const resolveDocsElements = async (cwd: string): Promise<{ props: ElementProps; omittedProps: OmittedProps }> => {
134
- const { gi, reactSubexports } = resolveStore(cwd);
135
-
136
- if (!existsSync(gi.storeDir)) {
137
- return { props: {}, omittedProps: {} };
138
- }
139
-
140
- const { props, omittedProps } = await readBuiltinElements(reactSubexports, gi.storeDir);
141
-
142
- return { props, omittedProps };
143
- };
144
-
145
127
  export { docs };
@@ -0,0 +1,81 @@
1
+ import { info } from "@gtkx/utils";
2
+ import { defineCommand } from "citty";
3
+ import { cwdArg, resolveCwd } from "../internal/entry-arg.js";
4
+ import { type ClientName, type ClientResult, CLIENTS, isClientName, writeClientConfig } from "../mcp/client-config.js";
5
+
6
+ const clientList = CLIENTS.map((client) => client.name).join(", ");
7
+
8
+ const init = defineCommand({
9
+ meta: {
10
+ name: "init",
11
+ description: "Write the gtkx MCP server into an editor's configuration file",
12
+ },
13
+ args: {
14
+ client: {
15
+ type: "string",
16
+ description: `Which editor to configure: one of ${clientList}`,
17
+ required: true,
18
+ },
19
+ ...cwdArg,
20
+ },
21
+ run({ args }) {
22
+ const cwd = resolveCwd(args);
23
+ const name = args.client;
24
+
25
+ if (!isClientName(name)) {
26
+ throw new Error(`Unknown --client "${name}". Pass one of ${clientList}.`);
27
+ }
28
+
29
+ report(name, writeClientConfig(cwd, name));
30
+ },
31
+ });
32
+
33
+ const mcp = defineCommand({
34
+ meta: {
35
+ name: "mcp",
36
+ description: "Run the MCP server that exposes this project's running app and generated API reference",
37
+ },
38
+ args: {
39
+ tools: {
40
+ type: "string",
41
+ description:
42
+ "Comma-separated tool name patterns to register, `*` matching any run of characters and a " +
43
+ "leading `!` excluding. Overrides `mcp.tools` in gtkx.config.ts",
44
+ },
45
+ "read-only": {
46
+ type: "boolean",
47
+ description: "Register only the tools that read state, leaving out the ones that drive the app",
48
+ },
49
+ ...cwdArg,
50
+ },
51
+ subCommands: { init },
52
+ async run({ args }) {
53
+ const { runMcpServer } = await import("@gtkx/mcp/server");
54
+
55
+ await runMcpServer({
56
+ cwd: resolveCwd(args),
57
+ ...(args.tools !== undefined && { tools: splitPatterns(args.tools) }),
58
+ ...(args["read-only"] !== undefined && { isReadOnly: args["read-only"] }),
59
+ });
60
+ },
61
+ });
62
+
63
+ const splitPatterns = (value: string): string[] =>
64
+ value.split(",").map((pattern) => pattern.trim()).filter((pattern) => pattern.length > 0);
65
+
66
+ const report = (name: ClientName, result: ClientResult): void => {
67
+ if (result.kind === "manual") {
68
+ info(`mcp: ${name} keeps its servers in ${result.path}, which is outside this project. Add:`);
69
+
70
+ for (const line of result.snippet.split("\n")) {
71
+ info(`mcp: ${line}`);
72
+ }
73
+
74
+ return;
75
+ }
76
+
77
+ info(`mcp: ${result.isCreated ? "created" : "updated"} ${result.path} for ${name}`);
78
+ info("mcp: restart the editor for it to pick the server up");
79
+ };
80
+
81
+ export { mcp };