@gtkx/cli 1.5.0 → 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/README.md +5 -5
  2. package/dist/cli.js +4 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/codegen/freshness.d.ts.map +1 -1
  5. package/dist/codegen/freshness.js +45 -15
  6. package/dist/codegen/freshness.js.map +1 -1
  7. package/dist/codegen/reference.d.ts +17 -0
  8. package/dist/codegen/reference.d.ts.map +1 -0
  9. package/dist/codegen/reference.js +31 -0
  10. package/dist/codegen/reference.js.map +1 -0
  11. package/dist/codegen/report.d.ts.map +1 -1
  12. package/dist/codegen/report.js +18 -15
  13. package/dist/codegen/report.js.map +1 -1
  14. package/dist/codegen/run-codegen.d.ts +5 -5
  15. package/dist/codegen/run-codegen.d.ts.map +1 -1
  16. package/dist/codegen/run-codegen.js +24 -59
  17. package/dist/codegen/run-codegen.js.map +1 -1
  18. package/dist/codegen/store-resolver.d.ts +0 -1
  19. package/dist/codegen/store-resolver.d.ts.map +1 -1
  20. package/dist/codegen/store-resolver.js +8 -17
  21. package/dist/codegen/store-resolver.js.map +1 -1
  22. package/dist/commands/docs.d.ts.map +1 -1
  23. package/dist/commands/docs.js +6 -22
  24. package/dist/commands/docs.js.map +1 -1
  25. package/dist/commands/mcp.d.ts +13 -0
  26. package/dist/commands/mcp.d.ts.map +1 -0
  27. package/dist/commands/mcp.js +68 -0
  28. package/dist/commands/mcp.js.map +1 -0
  29. package/dist/deploy/freedesktop/copyright.d.ts.map +1 -1
  30. package/dist/deploy/freedesktop/copyright.js +2 -13
  31. package/dist/deploy/freedesktop/copyright.js.map +1 -1
  32. package/dist/deploy/freedesktop/xml.d.ts.map +1 -1
  33. package/dist/deploy/freedesktop/xml.js +21 -25
  34. package/dist/deploy/freedesktop/xml.js.map +1 -1
  35. package/dist/deploy/node-runtime/license.d.ts.map +1 -1
  36. package/dist/deploy/node-runtime/license.js +2 -9
  37. package/dist/deploy/node-runtime/license.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/notices/packages.d.ts.map +1 -1
  44. package/dist/deploy/notices/packages.js +3 -10
  45. package/dist/deploy/notices/packages.js.map +1 -1
  46. package/dist/deploy/payload/copy-tree.d.ts.map +1 -1
  47. package/dist/deploy/payload/copy-tree.js +3 -3
  48. package/dist/deploy/payload/copy-tree.js.map +1 -1
  49. package/dist/deploy/payload/stage.d.ts.map +1 -1
  50. package/dist/deploy/payload/stage.js +1 -3
  51. package/dist/deploy/payload/stage.js.map +1 -1
  52. package/dist/deploy/settings/build-manifest.d.ts.map +1 -1
  53. package/dist/deploy/settings/build-manifest.js +4 -15
  54. package/dist/deploy/settings/build-manifest.js.map +1 -1
  55. package/dist/deploy/settings/libraries.d.ts.map +1 -1
  56. package/dist/deploy/settings/libraries.js +2 -21
  57. package/dist/deploy/settings/libraries.js.map +1 -1
  58. package/dist/deploy/settings/paths.d.ts +1 -2
  59. package/dist/deploy/settings/paths.d.ts.map +1 -1
  60. package/dist/deploy/settings/paths.js +5 -15
  61. package/dist/deploy/settings/paths.js.map +1 -1
  62. package/dist/deploy/settings/screenshots.d.ts.map +1 -1
  63. package/dist/deploy/settings/screenshots.js +6 -31
  64. package/dist/deploy/settings/screenshots.js.map +1 -1
  65. package/dist/deploy/targets/flatpak-pnpm.js +4 -4
  66. package/dist/deploy/targets/flatpak-pnpm.js.map +1 -1
  67. package/dist/deploy/targets/flatpak-source.d.ts.map +1 -1
  68. package/dist/deploy/targets/flatpak-source.js +4 -4
  69. package/dist/deploy/targets/flatpak-source.js.map +1 -1
  70. package/dist/deploy/targets/flatpak-sources.js +1 -1
  71. package/dist/deploy/targets/flatpak-sources.js.map +1 -1
  72. package/dist/deploy/tools.d.ts.map +1 -1
  73. package/dist/deploy/tools.js +1 -3
  74. package/dist/deploy/tools.js.map +1 -1
  75. package/dist/dev/runner-main.js +1 -1
  76. package/dist/dev/runner-main.js.map +1 -1
  77. package/dist/dev/schema-dir.d.ts +1 -1
  78. package/dist/dev/schema-dir.d.ts.map +1 -1
  79. package/dist/dev/schema-dir.js +2 -4
  80. package/dist/dev/schema-dir.js.map +1 -1
  81. package/dist/i18n/catalogs.d.ts.map +1 -1
  82. package/dist/i18n/catalogs.js +2 -1
  83. package/dist/i18n/catalogs.js.map +1 -1
  84. package/dist/i18n/metadata-templates.d.ts.map +1 -1
  85. package/dist/i18n/metadata-templates.js +2 -1
  86. package/dist/i18n/metadata-templates.js.map +1 -1
  87. package/dist/i18n/source-messages.d.ts +3 -21
  88. package/dist/i18n/source-messages.d.ts.map +1 -1
  89. package/dist/i18n/source-messages.js +66 -85
  90. package/dist/i18n/source-messages.js.map +1 -1
  91. package/dist/i18n/types.d.ts +6 -7
  92. package/dist/i18n/types.d.ts.map +1 -1
  93. package/dist/i18n/types.js +83 -96
  94. package/dist/i18n/types.js.map +1 -1
  95. package/dist/internal/agent-rules.d.ts +7 -0
  96. package/dist/internal/agent-rules.d.ts.map +1 -0
  97. package/dist/internal/agent-rules.js +106 -0
  98. package/dist/internal/agent-rules.js.map +1 -0
  99. package/dist/internal/docs-elements.d.ts +8 -0
  100. package/dist/internal/docs-elements.d.ts.map +1 -0
  101. package/dist/internal/docs-elements.js +12 -0
  102. package/dist/internal/docs-elements.js.map +1 -0
  103. package/dist/internal/icon-path.d.ts.map +1 -1
  104. package/dist/internal/icon-path.js +5 -16
  105. package/dist/internal/icon-path.js.map +1 -1
  106. package/dist/internal/source-imports.d.ts.map +1 -1
  107. package/dist/internal/source-imports.js +10 -42
  108. package/dist/internal/source-imports.js.map +1 -1
  109. package/dist/mcp/client-config.d.ts +22 -0
  110. package/dist/mcp/client-config.d.ts.map +1 -0
  111. package/dist/mcp/client-config.js +94 -0
  112. package/dist/mcp/client-config.js.map +1 -0
  113. package/dist/settings/render.d.ts +0 -1
  114. package/dist/settings/render.d.ts.map +1 -1
  115. package/dist/settings/render.js +1 -11
  116. package/dist/settings/render.js.map +1 -1
  117. package/dist/settings/schema.d.ts +2 -2
  118. package/dist/settings/schema.d.ts.map +1 -1
  119. package/dist/settings/schema.js +21 -62
  120. package/dist/settings/schema.js.map +1 -1
  121. package/dist/vite-plugins/asset-imports.d.ts +1 -2
  122. package/dist/vite-plugins/asset-imports.d.ts.map +1 -1
  123. package/dist/vite-plugins/asset-imports.js +19 -32
  124. package/dist/vite-plugins/asset-imports.js.map +1 -1
  125. package/dist/vite-plugins/asset-specifier.d.ts +1 -3
  126. package/dist/vite-plugins/asset-specifier.d.ts.map +1 -1
  127. package/dist/vite-plugins/asset-specifier.js +1 -4
  128. package/dist/vite-plugins/asset-specifier.js.map +1 -1
  129. package/dist/vite-plugins/i18n.d.ts.map +1 -1
  130. package/dist/vite-plugins/i18n.js +2 -1
  131. package/dist/vite-plugins/i18n.js.map +1 -1
  132. package/dist/vite-plugins/index.js +2 -2
  133. package/dist/vite-plugins/index.js.map +1 -1
  134. package/dist/vite-plugins/resources.d.ts.map +1 -1
  135. package/dist/vite-plugins/resources.js +31 -96
  136. package/dist/vite-plugins/resources.js.map +1 -1
  137. package/dist/vite-plugins/settings-worker-env.d.ts.map +1 -1
  138. package/dist/vite-plugins/settings-worker-env.js +1 -3
  139. package/dist/vite-plugins/settings-worker-env.js.map +1 -1
  140. package/dist/vite-plugins/settings.d.ts +1 -2
  141. package/dist/vite-plugins/settings.d.ts.map +1 -1
  142. package/dist/vite-plugins/settings.js +8 -13
  143. package/dist/vite-plugins/settings.js.map +1 -1
  144. package/dist/vite-plugins/undeclared-library.d.ts.map +1 -1
  145. package/dist/vite-plugins/undeclared-library.js +10 -13
  146. package/dist/vite-plugins/undeclared-library.js.map +1 -1
  147. package/dist/vite-plugins/worker.d.ts.map +1 -1
  148. package/dist/vite-plugins/worker.js +0 -1
  149. package/dist/vite-plugins/worker.js.map +1 -1
  150. package/package.json +21 -14
  151. package/src/cli.ts +5 -0
  152. package/src/codegen/freshness.ts +66 -16
  153. package/src/codegen/reference.ts +52 -0
  154. package/src/codegen/report.ts +16 -17
  155. package/src/codegen/run-codegen.ts +31 -91
  156. package/src/codegen/store-resolver.ts +8 -24
  157. package/src/commands/docs.ts +6 -36
  158. package/src/commands/mcp.ts +81 -0
  159. package/src/deploy/freedesktop/copyright.ts +2 -14
  160. package/src/deploy/freedesktop/xml.ts +24 -32
  161. package/src/deploy/node-runtime/license.ts +2 -8
  162. package/src/deploy/notices/gtkx.ts +1 -1
  163. package/src/deploy/notices/libraries.ts +2 -1
  164. package/src/deploy/notices/packages.ts +3 -11
  165. package/src/deploy/payload/copy-tree.ts +3 -4
  166. package/src/deploy/payload/stage.ts +2 -6
  167. package/src/deploy/settings/build-manifest.ts +4 -16
  168. package/src/deploy/settings/libraries.ts +1 -25
  169. package/src/deploy/settings/paths.ts +6 -16
  170. package/src/deploy/settings/screenshots.ts +8 -46
  171. package/src/deploy/targets/flatpak-pnpm.ts +4 -4
  172. package/src/deploy/targets/flatpak-source.ts +4 -4
  173. package/src/deploy/targets/flatpak-sources.ts +1 -1
  174. package/src/deploy/tools.ts +1 -3
  175. package/src/dev/runner-main.ts +1 -1
  176. package/src/dev/schema-dir.ts +2 -4
  177. package/src/i18n/catalogs.ts +2 -1
  178. package/src/i18n/metadata-templates.ts +2 -1
  179. package/src/i18n/source-messages.ts +77 -115
  180. package/src/i18n/types.ts +97 -131
  181. package/src/internal/agent-rules.ts +135 -0
  182. package/src/internal/docs-elements.ts +21 -0
  183. package/src/internal/icon-path.ts +6 -19
  184. package/src/internal/source-imports.ts +14 -50
  185. package/src/mcp/client-config.ts +130 -0
  186. package/src/settings/render.ts +1 -13
  187. package/src/settings/schema.ts +19 -81
  188. package/src/vite-plugins/asset-imports.ts +20 -44
  189. package/src/vite-plugins/asset-specifier.ts +0 -5
  190. package/src/vite-plugins/i18n.ts +2 -1
  191. package/src/vite-plugins/index.ts +2 -2
  192. package/src/vite-plugins/resources.ts +36 -139
  193. package/src/vite-plugins/settings-worker-env.ts +1 -3
  194. package/src/vite-plugins/settings.ts +11 -19
  195. package/src/vite-plugins/undeclared-library.ts +14 -20
  196. package/src/vite-plugins/worker.ts +0 -1
  197. package/dist/i18n/binding-extractor.d.ts +0 -30
  198. package/dist/i18n/binding-extractor.d.ts.map +0 -1
  199. package/dist/i18n/binding-extractor.js +0 -1324
  200. package/dist/i18n/binding-extractor.js.map +0 -1
  201. package/dist/i18n/extractor-plugin.d.ts +0 -8
  202. package/dist/i18n/extractor-plugin.d.ts.map +0 -1
  203. package/dist/i18n/extractor-plugin.js +0 -247
  204. package/dist/i18n/extractor-plugin.js.map +0 -1
  205. package/dist/internal/data-dir.d.ts +0 -4
  206. package/dist/internal/data-dir.d.ts.map +0 -1
  207. package/dist/internal/data-dir.js +0 -52
  208. package/dist/internal/data-dir.js.map +0 -1
  209. package/src/i18n/binding-extractor.ts +0 -1969
  210. package/src/i18n/extractor-plugin.ts +0 -413
  211. package/src/internal/data-dir.ts +0 -70
@@ -2,21 +2,23 @@ import { runCodegen as runCodegenCore } from "@gtkx/codegen";
2
2
  import { getShadowingStorePaths, sweepProjectStaging } from "@gtkx/codegen/internal";
3
3
  import { type Config, loadConfig } from "@gtkx/config";
4
4
  import {
5
+ isAgentRulesEnabled,
5
6
  resolveElementComponents,
6
7
  resolveElementProps,
7
8
  resolveLazyElements,
8
9
  resolveOmittedProps,
9
10
  } from "@gtkx/config/internal";
10
- import { info, warn } from "@gtkx/utils";
11
+ import { info } from "@gtkx/utils";
11
12
  import { existsSync, rmSync } from "node:fs";
12
13
  import { join, resolve } from "node:path";
13
14
  import { resolveCatalogProject, synchronizeCatalogs } from "../i18n/catalogs.js";
14
15
  import { extractSourceCatalog } from "../i18n/source-messages.js";
15
- import { emitI18nTypes } from "../i18n/types.js";
16
- import { resolveDataDir } from "../internal/data-dir.js";
16
+ import { clearI18nTypes, emitI18nTypes } from "../i18n/types.js";
17
+ import { upsertAgentRules } from "../internal/agent-rules.js";
17
18
  import { discoverSourceFiles } from "../internal/source-imports.js";
18
19
  import { emitSchemaEnv } from "../settings/schema.js";
19
20
  import { type CodegenInputs, isCodegenStale, resolveCodegenInputs } from "./freshness.js";
21
+ import { type ReferenceResult, writeReference } from "./reference.js";
20
22
  import { type CodegenContext, type CodegenStore, resolveCodegenContext } from "./store-resolver.js";
21
23
 
22
24
  type RunCodegenOptions = {
@@ -37,12 +39,11 @@ type RunCodegenResult = {
37
39
  isRegenerated: boolean;
38
40
  namespaces: number;
39
41
  intrinsicElements: number;
40
- notices: string[];
41
42
  duration: number;
42
- girPath?: string[] | undefined;
43
- configFile?: string | undefined;
44
- libraries?: string[] | undefined;
45
- future?: string[] | undefined;
43
+ girPath: string[];
44
+ configFile: string;
45
+ libraries: string[];
46
+ reference?: ReferenceResult | undefined;
46
47
  };
47
48
 
48
49
  type CodegenOptionsInput = {
@@ -50,10 +51,6 @@ type CodegenOptionsInput = {
50
51
  libraries: string[];
51
52
  girPath: string[];
52
53
  elements: Config["elements"];
53
- isByteArrayTyped: boolean;
54
- isValueUnwrapped: boolean;
55
- isFinishTrimmed: boolean;
56
- isInoutInPlace: boolean;
57
54
  };
58
55
 
59
56
  type PreparedCodegen = CodegenInputs & { isForced: boolean };
@@ -64,14 +61,6 @@ type EnsureGeneratedOptions = {
64
61
  shouldPreserveI18nMetadata?: boolean | undefined;
65
62
  };
66
63
 
67
- type RunOptionsInput = {
68
- root: string;
69
- mode: string | undefined;
70
- inputs: CodegenInputs | null;
71
- resolved: LoadedConfig;
72
- shouldPreserveI18nMetadata: boolean | undefined;
73
- };
74
-
75
64
  const GIR_PATH_MISSING_MESSAGE =
76
65
  "No GIR search paths available. Install gobject-introspection " +
77
66
  "(Linux: `sudo dnf install gobject-introspection-devel` or `sudo apt install libgirepository1.0-dev`), " +
@@ -88,13 +77,9 @@ const removeShadowingStores = (cwd: string): void => {
88
77
  removeStores(getShadowingStorePaths(cwd));
89
78
  };
90
79
 
91
- const codegenOptions = ({ store, libraries, girPath, elements, ...future }: CodegenOptionsInput) => ({
80
+ const codegenOptions = ({ store, libraries, girPath, elements }: CodegenOptionsInput) => ({
92
81
  libraries,
93
82
  girPath,
94
- isByteArrayTyped: future.isByteArrayTyped,
95
- isValueUnwrapped: future.isValueUnwrapped,
96
- isFinishTrimmed: future.isFinishTrimmed,
97
- isInoutInPlace: future.isInoutInPlace,
98
83
  gi: {
99
84
  storeDir: store.giStoreDir,
100
85
  linkDir: store.giLinkDir,
@@ -108,50 +93,30 @@ const codegenOptions = ({ store, libraries, girPath, elements, ...future }: Code
108
93
  linkDir: store.jsxLinkDir,
109
94
  version: store.react.version,
110
95
  },
111
- reactSubexports: store.react?.subexports ?? [],
112
96
  userComponents: resolveElementComponents(elements),
113
97
  userProps: resolveElementProps(elements),
114
98
  userLazyElements: resolveLazyElements(elements),
115
99
  userOmittedProps: resolveOmittedProps(elements),
116
100
  });
117
101
 
118
- const enabledFutureFlags = (config: Config): string[] =>
119
- Object.entries(config.future ?? {})
120
- .filter(([, value]) => value === true)
121
- .map(([name]) => name);
122
-
123
102
  const disabledCodegenResult = (configFile: string): RunCodegenResult => ({
124
103
  isRegenerated: false,
125
104
  namespaces: 0,
126
105
  intrinsicElements: 0,
127
- notices: [],
128
106
  duration: 0,
129
107
  girPath: [],
130
108
  configFile,
131
109
  libraries: [],
132
110
  });
133
111
 
134
- const warnNotices = (notices: string[]): void => {
135
- for (const notice of notices) {
136
- warn(notice);
137
- }
138
- };
139
-
140
- const regeneratedStorePaths = (store: CodegenStore): string[] => {
141
- if (store.react === null) {
142
- return [store.giStoreDir, store.giLinkDir];
143
- }
144
-
145
- return [store.giStoreDir, store.giLinkDir, store.jsxStoreDir, store.jsxLinkDir];
146
- };
147
-
148
112
  const clearGeneratedStores = (store: CodegenStore): void => {
149
- removeStores(regeneratedStorePaths(store));
113
+ removeStores(
114
+ store.react === null
115
+ ? [store.giStoreDir, store.giLinkDir]
116
+ : [store.giStoreDir, store.giLinkDir, store.jsxStoreDir, store.jsxLinkDir],
117
+ );
150
118
  };
151
119
 
152
- const resolveLoadedConfig = async (options: RunCodegenOptions, cwd: string): Promise<LoadedConfig> =>
153
- options.resolved ?? (await loadConfig(cwd, { mode: options.mode }));
154
-
155
120
  const prepareCodegen = (options: RunCodegenOptions, cwd: string, config: Config): PreparedCodegen => {
156
121
  const { girPath, libraries, store } = options.inputs ?? resolveCodegenInputs(cwd, config);
157
122
 
@@ -166,9 +131,9 @@ const prepareCodegen = (options: RunCodegenOptions, cwd: string, config: Config)
166
131
 
167
132
  const runCodegen = async (options: RunCodegenOptions = {}): Promise<RunCodegenResult> => {
168
133
  const cwd = options.cwd ?? process.cwd();
169
- const { config, configFile } = await resolveLoadedConfig(options, cwd);
134
+ const { config, configFile } = options.resolved ?? (await loadConfig(cwd, { mode: options.mode }));
170
135
  await syncI18n(cwd, config.applicationId, options.shouldPreserveI18nMetadata);
171
- syncSchemaEnv(cwd, config.future?.v2ResourceImports === true);
136
+ emitSchemaEnv(cwd);
172
137
 
173
138
  if (config.codegen === false) {
174
139
  removeShadowingStores(cwd);
@@ -183,31 +148,25 @@ const runCodegen = async (options: RunCodegenOptions = {}): Promise<RunCodegenRe
183
148
  }
184
149
 
185
150
  const result = await runCodegenCore({
186
- ...codegenOptions({
187
- store,
188
- libraries,
189
- girPath,
190
- elements: config.elements,
191
- isByteArrayTyped: config.future?.v2ByteArrays === true,
192
- isValueUnwrapped: config.future?.v2ValueReturns === true,
193
- isFinishTrimmed: config.future?.v2FinishResults === true,
194
- isInoutInPlace: config.future?.v2InoutReturns === true,
195
- }),
151
+ ...codegenOptions({ store, libraries, girPath, elements: config.elements }),
196
152
  isForced,
197
153
  });
198
154
 
199
- warnNotices(result.notices);
155
+ const reference = await writeReference({ root: cwd, config, girPath, libraries, isForced });
156
+
157
+ if (isAgentRulesEnabled(config)) {
158
+ upsertAgentRules(cwd);
159
+ }
200
160
 
201
161
  return {
202
162
  isRegenerated: result.isRegenerated,
203
163
  namespaces: result.namespaces,
204
164
  intrinsicElements: result.intrinsicElements,
205
- notices: result.notices,
206
165
  duration: result.duration,
207
166
  girPath,
208
167
  configFile,
209
168
  libraries,
210
- future: enabledFutureFlags(config),
169
+ reference,
211
170
  };
212
171
  };
213
172
 
@@ -219,16 +178,16 @@ const syncI18n = async (
219
178
  const project = resolveCatalogProject(root, applicationId);
220
179
 
221
180
  if (project === null) {
222
- emitI18nTypes(root, [], false);
181
+ clearI18nTypes(root);
223
182
 
224
183
  return;
225
184
  }
226
185
 
227
186
  const srcDir = join(root, "src");
228
187
  const sourceFiles = discoverSourceFiles(existsSync(srcDir) ? srcDir : root);
229
- const catalog = await extractSourceCatalog(project, sourceFiles, shouldPreserveMetadataMessages);
188
+ await extractSourceCatalog(project, sourceFiles, shouldPreserveMetadataMessages);
230
189
  synchronizeCatalogs(project);
231
- emitI18nTypes(root, catalog.messages);
190
+ await emitI18nTypes(root);
232
191
  };
233
192
 
234
193
  const isCodegenDisabled = async (cwd: string, mode?: string): Promise<boolean> => {
@@ -241,11 +200,6 @@ const isCodegenDisabled = async (cwd: string, mode?: string): Promise<boolean> =
241
200
  }
242
201
  };
243
202
 
244
- const syncSchemaEnv = (cwd: string, isV2ResourceImports = false): void => {
245
- const dataDir = isV2ResourceImports ? null : resolveDataDir(cwd);
246
- emitSchemaEnv(cwd, dataDir, isV2ResourceImports);
247
- };
248
-
249
203
  const resolveInputsOrNull = (cwd: string, config: Config): CodegenInputs | null => {
250
204
  try {
251
205
  return resolveCodegenInputs(cwd, config);
@@ -264,20 +218,6 @@ const maybeAnnounceStale = (shouldAnnounce: boolean | undefined, inputs: Codegen
264
218
  }
265
219
  };
266
220
 
267
- const getRunOptions = ({
268
- root,
269
- mode,
270
- inputs,
271
- resolved,
272
- shouldPreserveI18nMetadata,
273
- }: RunOptionsInput): RunCodegenOptions => {
274
- if (inputs === null) {
275
- return { cwd: root, mode, resolved, shouldPreserveI18nMetadata };
276
- }
277
-
278
- return { cwd: root, mode, inputs, resolved, shouldPreserveI18nMetadata };
279
- };
280
-
281
221
  const isPreflightSkipped = (options: EnsureGeneratedOptions): boolean =>
282
222
  options.shouldAnnounce === true && process.env.GTKX_DISABLE_PREFLIGHT === "1";
283
223
 
@@ -298,13 +238,13 @@ const generate = async (context: CodegenContext, options: EnsureGeneratedOptions
298
238
  maybeAnnounceStale(options.shouldAnnounce, inputs);
299
239
  const resolved = { config: context.config, configFile: context.configFile };
300
240
 
301
- const result = await runCodegen(getRunOptions({
302
- root: context.root,
241
+ const result = await runCodegen({
242
+ cwd: context.root,
303
243
  mode: options.mode,
304
- inputs,
305
244
  resolved,
306
245
  shouldPreserveI18nMetadata: options.shouldPreserveI18nMetadata,
307
- }));
246
+ ...(inputs !== null && { inputs }),
247
+ });
308
248
 
309
249
  return result.isRegenerated;
310
250
  };
@@ -1,6 +1,6 @@
1
1
  import { resolveStore } from "@gtkx/codegen";
2
2
  import { type Config, loadConfig } from "@gtkx/config";
3
- import { existsSync, readFileSync, realpathSync } from "node:fs";
3
+ import { existsSync } from "node:fs";
4
4
  import { createRequire } from "node:module";
5
5
  import { dirname, join } from "node:path";
6
6
  import { pathToFileURL } from "node:url";
@@ -16,39 +16,23 @@ type CodegenStore = {
16
16
 
17
17
  type CodegenReactPackage = {
18
18
  version: string;
19
- subexports: string[];
20
19
  };
21
20
 
22
- type ResolvedPackage = { dir: string; version: string };
23
-
24
21
  type CodegenContext = {
25
22
  root: string;
26
23
  config: Config;
27
24
  configFile: string;
28
25
  };
29
26
 
30
- const readVersion = (packageJsonPath: string): string => {
31
- const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8")) as { version?: string };
32
-
33
- return parsed.version ?? "0.0.0";
34
- };
35
-
36
- const resolvePackage = (require: NodeJS.Require, dir: string, packageName: string): ResolvedPackage | null => {
27
+ const hasPackage = (require: NodeJS.Require, dir: string, packageName: string): boolean => {
37
28
  try {
38
- const real = realpathSync(require.resolve(`${packageName}/package.json`));
29
+ require.resolve(`${packageName}/package.json`);
39
30
 
40
- return { dir: dirname(real), version: readVersion(real) };
31
+ return true;
41
32
  } catch {
42
33
  const unscoped = packageName.replace(/^@[^/]+\//, "");
43
- const workspacePkg = join(dir, "packages", unscoped, "package.json");
44
-
45
- if (!existsSync(workspacePkg)) {
46
- return null;
47
- }
48
-
49
- const real = realpathSync(workspacePkg);
50
34
 
51
- return { dir: dirname(real), version: readVersion(real) };
35
+ return existsSync(join(dir, "packages", unscoped, "package.json"));
52
36
  }
53
37
  };
54
38
 
@@ -57,12 +41,12 @@ const siblingStore = (giDir: string): string => join(dirname(giDir), "jsx");
57
41
  const resolveCodegenStore = (dir: string): CodegenStore => {
58
42
  const require = createRequire(pathToFileURL(join(dir, "__gtkx_resolver__.js")).href);
59
43
 
60
- if (resolvePackage(require, dir, "@gtkx/native") === null) {
44
+ if (!hasPackage(require, dir, "@gtkx/native")) {
61
45
  throw new Error("Cannot resolve @gtkx/native from the project; is it installed?");
62
46
  }
63
47
 
64
48
  const store = resolveStore(dir);
65
- const hasReactRuntime = resolvePackage(require, dir, "react") !== null;
49
+ const hasReactRuntime = hasPackage(require, dir, "react");
66
50
 
67
51
  return {
68
52
  giStoreDir: store.gi.storeDir,
@@ -72,7 +56,7 @@ const resolveCodegenStore = (dir: string): CodegenStore => {
72
56
  runtimeVersion: store.gi.version,
73
57
  react:
74
58
  hasReactRuntime && store.jsx !== null
75
- ? { version: store.jsx.version, subexports: store.reactSubexports }
59
+ ? { version: store.jsx.version }
76
60
  : null,
77
61
  };
78
62
  };
@@ -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
4
  import { resolveOmittedProps } from "@gtkx/config/internal";
13
- import { info } from "@gtkx/utils";
5
+ import { info, isPathInside } from "@gtkx/utils";
14
6
  import { defineCommand } from "citty";
15
- import { existsSync } from "node:fs";
16
- import { isAbsolute, relative, resolve, sep } from "node:path";
7
+ import { resolve } 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,7 @@ const docs = defineCommand({
65
57
  );
66
58
  }
67
59
 
68
- const libraries = resolveLibraries(config.libraries, girPath);
60
+ const libraries = resolveLibraries(config.libraries);
69
61
  const startedAt = Date.now();
70
62
  const outDir = resolveOutDir(cwd, args.out);
71
63
  const builtin = await resolveDocsElements(cwd);
@@ -78,10 +70,6 @@ const docs = defineCommand({
78
70
  props: builtin.props,
79
71
  omittedProps: mergeOmittedProps(builtin.omittedProps, resolveOmittedProps(config.elements)),
80
72
  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,
85
73
  });
86
74
 
87
75
  if (!isRegenerated) {
@@ -99,12 +87,6 @@ const docs = defineCommand({
99
87
  },
100
88
  });
101
89
 
102
- const isBelow = (parent: string, child: string): boolean => {
103
- const path = relative(parent, child);
104
-
105
- return path !== "" && path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path);
106
- };
107
-
108
90
  const outDirReason = (cwd: string, out: string, outDir: string): string => {
109
91
  if (out.trim() === "") {
110
92
  return "it was empty, which names the project root itself";
@@ -120,7 +102,7 @@ const outDirReason = (cwd: string, out: string, outDir: string): string => {
120
102
  const resolveOutDir = (cwd: string, out: string): string => {
121
103
  const outDir = out.trim() === "" ? cwd : resolve(cwd, out);
122
104
 
123
- if (isBelow(cwd, outDir)) {
105
+ if (isPathInside(cwd, outDir)) {
124
106
  return outDir;
125
107
  }
126
108
 
@@ -130,16 +112,4 @@ const resolveOutDir = (cwd: string, out: string): string => {
130
112
  );
131
113
  };
132
114
 
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
115
  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 };
@@ -1,6 +1,6 @@
1
- import { readFileSync } from "node:fs";
2
1
  import type { DeploySettings, Notice, NoticeSection } from "../types.js";
3
2
  import { BUNDLE_FILENAME } from "../../vite-plugins/esm-extension.js";
3
+ import { readLicenseText } from "../notices/text.js";
4
4
 
5
5
  type FileStanza = {
6
6
  settings: DeploySettings;
@@ -30,18 +30,6 @@ const foldedField = (name: string, lines: string[]): string[] => {
30
30
  return first === undefined ? [] : [`${name}: ${first}`, ...rest.map((line) => indentLine(line))];
31
31
  };
32
32
 
33
- const licenseText = (licenseFile: string | null): string | null => {
34
- if (licenseFile === null) {
35
- return null;
36
- }
37
-
38
- try {
39
- return readFileSync(licenseFile, "utf8").trimEnd();
40
- } catch {
41
- return null;
42
- }
43
- };
44
-
45
33
  const shortName = (license: string): string =>
46
34
  license
47
35
  .replaceAll(SPDX_GROUPING, " ")
@@ -159,7 +147,7 @@ const applicationStanza = (settings: DeploySettings, text: string | null): strin
159
147
  ];
160
148
 
161
149
  const renderCopyright = (settings: DeploySettings, sections: NoticeSection[]): string => {
162
- const own = licenseText(settings.paths.licenseFile);
150
+ const own = settings.paths.licenseFile === null ? null : readLicenseText(settings.paths.licenseFile);
163
151
  const reproduced: Set<string> = new Set(own === null ? [] : [own]);
164
152
 
165
153
  const stanzas = filePatterns(sections).flatMap((files) =>
@@ -1,3 +1,5 @@
1
+ import XMLBuilder from "fast-xml-builder";
2
+
1
3
  type XmlNode = {
2
4
  tag: string;
3
5
  attributes?: Record<string, string>;
@@ -5,23 +7,26 @@ type XmlNode = {
5
7
  text?: string;
6
8
  };
7
9
 
8
- const XML_DECLARATION = '<?xml version="1.0" encoding="UTF-8"?>';
9
- const INDENT = " ".repeat(4);
10
+ type OrderedNode = Record<string, unknown>;
10
11
 
11
- const ESCAPES: Record<string, string> = {
12
- '"': "&quot;",
13
- "&": "&amp;",
14
- "<": "&lt;",
15
- ">": "&gt;",
16
- };
12
+ const XML_ENCODING = ["UTF", "8"].join("-");
17
13
 
18
- const escapeXml = (value: string): string =>
19
- value.replaceAll(/["&<>]/g, (character) => ESCAPES[character] ?? character);
14
+ const builder = new XMLBuilder({
15
+ format: true,
16
+ ignoreAttributes: false,
17
+ indentBy: " ".repeat(4),
18
+ preserveOrder: true,
19
+ suppressEmptyNode: true,
20
+ });
20
21
 
21
- const renderAttributes = (attributes: Record<string, string> | undefined): string =>
22
- Object.entries(attributes ?? {})
23
- .map(([key, value]) => ` ${key}="${escapeXml(value)}"`)
24
- .join("");
22
+ const orderedNode = (node: XmlNode): OrderedNode => ({
23
+ [node.tag]: node.text === undefined
24
+ ? (node.children ?? []).map((child) => orderedNode(child))
25
+ : [{ "#text": node.text }],
26
+ ...(node.attributes && {
27
+ ":@": Object.fromEntries(Object.entries(node.attributes).map(([key, value]) => [`@_${key}`, value])),
28
+ }),
29
+ });
25
30
 
26
31
  const element = (tag: string, attributes: Record<string, string>, children: XmlNode[]): XmlNode => ({
27
32
  tag,
@@ -31,23 +36,10 @@ const element = (tag: string, attributes: Record<string, string>, children: XmlN
31
36
 
32
37
  const text = (tag: string, value: string): XmlNode => ({ tag, text: value });
33
38
 
34
- const renderNode = (node: XmlNode, depth: number): string[] => {
35
- const padding = INDENT.repeat(depth);
36
- const open = `${padding}<${node.tag}${renderAttributes(node.attributes)}`;
37
-
38
- if (node.text !== undefined) {
39
- return [`${open}>${escapeXml(node.text)}</${node.tag}>`];
40
- }
41
-
42
- const children = node.children ?? [];
43
-
44
- if (children.length === 0) {
45
- return [`${open}/>`];
46
- }
47
-
48
- return [`${open}>`, ...children.flatMap((child) => renderNode(child, depth + 1)), `${padding}</${node.tag}>`];
49
- };
50
-
51
- const renderDocument = (root: XmlNode): string => [XML_DECLARATION, ...renderNode(root, 0), ""].join("\n");
39
+ const renderDocument = (root: XmlNode): string =>
40
+ `${builder.build([
41
+ { "?xml": [{ "#text": "" }], ":@": { "@_version": "1.0", "@_encoding": XML_ENCODING } },
42
+ orderedNode(root),
43
+ ])}\n`;
52
44
 
53
45
  export { element, renderDocument, text, type XmlNode };
@@ -1,5 +1,5 @@
1
- import { readFileSync } from "node:fs";
2
1
  import { dirname, join } from "node:path";
2
+ import { readLicenseText } from "../notices/text.js";
3
3
 
4
4
  const LICENSE_FILENAME = "LICENSE";
5
5
  const NODE_MARKER = "Node.js";
@@ -10,13 +10,7 @@ const licenseCandidates = (binary: string): string[] => {
10
10
  return [join(dir, LICENSE_FILENAME), join(dir, "..", LICENSE_FILENAME)];
11
11
  };
12
12
 
13
- const isNodeLicense = (path: string): boolean => {
14
- try {
15
- return readFileSync(path, "utf8").includes(NODE_MARKER);
16
- } catch {
17
- return false;
18
- }
19
- };
13
+ const isNodeLicense = (path: string): boolean => readLicenseText(path)?.includes(NODE_MARKER) === true;
20
14
 
21
15
  const licenseBesideNode = (binary: string): string | null =>
22
16
  licenseCandidates(binary).find((candidate) => isNodeLicense(candidate)) ?? null;
@@ -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",