@gtkx/cli 2.0.0-beta.1 → 2.0.0-beta.3

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 (238) hide show
  1. package/dist/builder.d.ts +1 -0
  2. package/dist/builder.d.ts.map +1 -1
  3. package/dist/builder.js +17 -5
  4. package/dist/builder.js.map +1 -1
  5. package/dist/cli.d.ts +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +9 -1
  8. package/dist/cli.js.map +1 -1
  9. package/dist/codegen/reference.d.ts.map +1 -1
  10. package/dist/codegen/reference.js +1 -0
  11. package/dist/codegen/reference.js.map +1 -1
  12. package/dist/codegen/run-codegen.d.ts +4 -2
  13. package/dist/codegen/run-codegen.d.ts.map +1 -1
  14. package/dist/codegen/run-codegen.js +15 -16
  15. package/dist/codegen/run-codegen.js.map +1 -1
  16. package/dist/codegen/store-resolver.d.ts +2 -1
  17. package/dist/codegen/store-resolver.d.ts.map +1 -1
  18. package/dist/codegen/store-resolver.js +4 -3
  19. package/dist/codegen/store-resolver.js.map +1 -1
  20. package/dist/commands/build.d.ts +5 -0
  21. package/dist/commands/build.d.ts.map +1 -1
  22. package/dist/commands/build.js +83 -10
  23. package/dist/commands/build.js.map +1 -1
  24. package/dist/commands/codegen.d.ts +1 -0
  25. package/dist/commands/codegen.d.ts.map +1 -1
  26. package/dist/commands/codegen.js +6 -5
  27. package/dist/commands/codegen.js.map +1 -1
  28. package/dist/commands/deploy.d.ts +1 -0
  29. package/dist/commands/deploy.d.ts.map +1 -1
  30. package/dist/commands/deploy.js +4 -2
  31. package/dist/commands/deploy.js.map +1 -1
  32. package/dist/commands/dev.d.ts +9 -0
  33. package/dist/commands/dev.d.ts.map +1 -1
  34. package/dist/commands/dev.js +29 -4
  35. package/dist/commands/dev.js.map +1 -1
  36. package/dist/commands/docs.d.ts.map +1 -1
  37. package/dist/commands/docs.js +1 -0
  38. package/dist/commands/docs.js.map +1 -1
  39. package/dist/deploy/freedesktop/metainfo.d.ts.map +1 -1
  40. package/dist/deploy/freedesktop/metainfo.js +41 -2
  41. package/dist/deploy/freedesktop/metainfo.js.map +1 -1
  42. package/dist/deploy/freedesktop/validate.d.ts.map +1 -1
  43. package/dist/deploy/freedesktop/validate.js +10 -4
  44. package/dist/deploy/freedesktop/validate.js.map +1 -1
  45. package/dist/deploy/nfpm/config.d.ts +3 -2
  46. package/dist/deploy/nfpm/config.d.ts.map +1 -1
  47. package/dist/deploy/nfpm/config.js +7 -4
  48. package/dist/deploy/nfpm/config.js.map +1 -1
  49. package/dist/deploy/nfpm/contents.d.ts.map +1 -1
  50. package/dist/deploy/nfpm/contents.js +8 -0
  51. package/dist/deploy/nfpm/contents.js.map +1 -1
  52. package/dist/deploy/node-runtime/elf.d.ts +2 -1
  53. package/dist/deploy/node-runtime/elf.d.ts.map +1 -1
  54. package/dist/deploy/node-runtime/elf.js +32 -5
  55. package/dist/deploy/node-runtime/elf.js.map +1 -1
  56. package/dist/deploy/node-runtime/index.d.ts +2 -1
  57. package/dist/deploy/node-runtime/index.d.ts.map +1 -1
  58. package/dist/deploy/node-runtime/index.js +75 -5
  59. package/dist/deploy/node-runtime/index.js.map +1 -1
  60. package/dist/deploy/notices/node-runtime.js +2 -2
  61. package/dist/deploy/notices/node-runtime.js.map +1 -1
  62. package/dist/deploy/payload/launcher.d.ts.map +1 -1
  63. package/dist/deploy/payload/launcher.js +14 -1
  64. package/dist/deploy/payload/launcher.js.map +1 -1
  65. package/dist/deploy/payload/stage.js +1 -1
  66. package/dist/deploy/payload/stage.js.map +1 -1
  67. package/dist/deploy/run-deploy.d.ts +1 -0
  68. package/dist/deploy/run-deploy.d.ts.map +1 -1
  69. package/dist/deploy/run-deploy.js +100 -14
  70. package/dist/deploy/run-deploy.js.map +1 -1
  71. package/dist/deploy/settings/build-manifest.d.ts +6 -1
  72. package/dist/deploy/settings/build-manifest.d.ts.map +1 -1
  73. package/dist/deploy/settings/build-manifest.js +15 -4
  74. package/dist/deploy/settings/build-manifest.js.map +1 -1
  75. package/dist/deploy/settings/extra-files.d.ts +1 -1
  76. package/dist/deploy/settings/extra-files.d.ts.map +1 -1
  77. package/dist/deploy/settings/extra-files.js +22 -1
  78. package/dist/deploy/settings/extra-files.js.map +1 -1
  79. package/dist/deploy/settings/index.d.ts +1 -0
  80. package/dist/deploy/settings/index.d.ts.map +1 -1
  81. package/dist/deploy/settings/index.js +2 -1
  82. package/dist/deploy/settings/index.js.map +1 -1
  83. package/dist/deploy/settings/paths.d.ts +2 -1
  84. package/dist/deploy/settings/paths.d.ts.map +1 -1
  85. package/dist/deploy/settings/paths.js +113 -5
  86. package/dist/deploy/settings/paths.js.map +1 -1
  87. package/dist/deploy/targets/flatpak-source.d.ts.map +1 -1
  88. package/dist/deploy/targets/flatpak-source.js +11 -2
  89. package/dist/deploy/targets/flatpak-source.js.map +1 -1
  90. package/dist/deploy/targets/nfpm-target.d.ts.map +1 -1
  91. package/dist/deploy/targets/nfpm-target.js +12 -7
  92. package/dist/deploy/targets/nfpm-target.js.map +1 -1
  93. package/dist/deploy/types.d.ts +1 -0
  94. package/dist/deploy/types.d.ts.map +1 -1
  95. package/dist/deploy/types.js.map +1 -1
  96. package/dist/dev/entry-env.d.ts +2 -1
  97. package/dist/dev/entry-env.d.ts.map +1 -1
  98. package/dist/dev/entry-env.js +2 -1
  99. package/dist/dev/entry-env.js.map +1 -1
  100. package/dist/dev/headless.d.ts +3 -0
  101. package/dist/dev/headless.d.ts.map +1 -0
  102. package/dist/dev/headless.js +35 -0
  103. package/dist/dev/headless.js.map +1 -0
  104. package/dist/dev/runner-deps.d.ts +1 -1
  105. package/dist/dev/runner-deps.d.ts.map +1 -1
  106. package/dist/dev/runner-deps.js +3 -3
  107. package/dist/dev/runner-deps.js.map +1 -1
  108. package/dist/dev/runner-main.d.ts.map +1 -1
  109. package/dist/dev/runner-main.js +8 -3
  110. package/dist/dev/runner-main.js.map +1 -1
  111. package/dist/dev/supervisor.d.ts +1 -0
  112. package/dist/dev/supervisor.d.ts.map +1 -1
  113. package/dist/dev/supervisor.js +4 -3
  114. package/dist/dev/supervisor.js.map +1 -1
  115. package/dist/i18n/catalogs.d.ts.map +1 -1
  116. package/dist/i18n/catalogs.js +1 -0
  117. package/dist/i18n/catalogs.js.map +1 -1
  118. package/dist/i18n/source-messages.d.ts.map +1 -1
  119. package/dist/i18n/source-messages.js +960 -8
  120. package/dist/i18n/source-messages.js.map +1 -1
  121. package/dist/internal/build-manifest.d.ts +5 -2
  122. package/dist/internal/build-manifest.d.ts.map +1 -1
  123. package/dist/internal/build-manifest.js +15 -2
  124. package/dist/internal/build-manifest.js.map +1 -1
  125. package/dist/internal/build-output.d.ts +8 -0
  126. package/dist/internal/build-output.d.ts.map +1 -0
  127. package/dist/internal/build-output.js +79 -0
  128. package/dist/internal/build-output.js.map +1 -0
  129. package/dist/internal/docs-elements.d.ts +1 -0
  130. package/dist/internal/docs-elements.d.ts.map +1 -1
  131. package/dist/internal/docs-elements.js +5 -4
  132. package/dist/internal/docs-elements.js.map +1 -1
  133. package/dist/internal/entry-arg.d.ts +4 -1
  134. package/dist/internal/entry-arg.d.ts.map +1 -1
  135. package/dist/internal/entry-arg.js +7 -1
  136. package/dist/internal/entry-arg.js.map +1 -1
  137. package/dist/internal/errors.d.ts +2 -1
  138. package/dist/internal/errors.d.ts.map +1 -1
  139. package/dist/internal/errors.js +1 -1
  140. package/dist/internal/errors.js.map +1 -1
  141. package/dist/internal/output-directory.d.ts +15 -0
  142. package/dist/internal/output-directory.d.ts.map +1 -0
  143. package/dist/internal/output-directory.js +490 -0
  144. package/dist/internal/output-directory.js.map +1 -0
  145. package/dist/internal/parent-process.d.ts +12 -0
  146. package/dist/internal/parent-process.d.ts.map +1 -0
  147. package/dist/internal/parent-process.js +74 -0
  148. package/dist/internal/parent-process.js.map +1 -0
  149. package/dist/internal/prepare-project.d.ts +2 -0
  150. package/dist/internal/prepare-project.d.ts.map +1 -1
  151. package/dist/internal/prepare-project.js +3 -3
  152. package/dist/internal/prepare-project.js.map +1 -1
  153. package/dist/internal/source-imports.js +1 -1
  154. package/dist/internal/source-imports.js.map +1 -1
  155. package/dist/mcp/client.js +2 -2
  156. package/dist/mcp/client.js.map +1 -1
  157. package/dist/vite-plugins/build-manifest.d.ts +5 -1
  158. package/dist/vite-plugins/build-manifest.d.ts.map +1 -1
  159. package/dist/vite-plugins/build-manifest.js +2 -1
  160. package/dist/vite-plugins/build-manifest.js.map +1 -1
  161. package/dist/vite-plugins/ensure-store.d.ts +1 -1
  162. package/dist/vite-plugins/ensure-store.d.ts.map +1 -1
  163. package/dist/vite-plugins/ensure-store.js +2 -2
  164. package/dist/vite-plugins/ensure-store.js.map +1 -1
  165. package/dist/vite-plugins/i18n.d.ts.map +1 -1
  166. package/dist/vite-plugins/i18n.js +58 -6
  167. package/dist/vite-plugins/i18n.js.map +1 -1
  168. package/dist/vite-plugins/index.d.ts +8 -1
  169. package/dist/vite-plugins/index.d.ts.map +1 -1
  170. package/dist/vite-plugins/index.js +6 -2
  171. package/dist/vite-plugins/index.js.map +1 -1
  172. package/dist/vite-plugins/native.d.ts.map +1 -1
  173. package/dist/vite-plugins/native.js +0 -4
  174. package/dist/vite-plugins/native.js.map +1 -1
  175. package/dist/vite-plugins/self-contained.d.ts.map +1 -1
  176. package/dist/vite-plugins/self-contained.js +159 -158
  177. package/dist/vite-plugins/self-contained.js.map +1 -1
  178. package/dist/vite-plugins/settings-worker-env.d.ts +1 -1
  179. package/dist/vite-plugins/settings-worker-env.d.ts.map +1 -1
  180. package/dist/vite-plugins/settings-worker-env.js +2 -2
  181. package/dist/vite-plugins/settings-worker-env.js.map +1 -1
  182. package/dist/vitest-plugin.d.ts +2 -1
  183. package/dist/vitest-plugin.d.ts.map +1 -1
  184. package/dist/vitest-plugin.js +6 -6
  185. package/dist/vitest-plugin.js.map +1 -1
  186. package/package.json +10 -9
  187. package/src/builder.ts +18 -5
  188. package/src/cli.ts +9 -1
  189. package/src/codegen/reference.ts +1 -0
  190. package/src/codegen/run-codegen.ts +17 -19
  191. package/src/codegen/store-resolver.ts +5 -3
  192. package/src/commands/build.ts +13 -2
  193. package/src/commands/codegen.ts +6 -5
  194. package/src/commands/deploy.ts +4 -2
  195. package/src/commands/dev.ts +38 -4
  196. package/src/commands/docs.ts +1 -0
  197. package/src/deploy/freedesktop/metainfo.ts +73 -21
  198. package/src/deploy/freedesktop/validate.ts +12 -4
  199. package/src/deploy/nfpm/config.ts +13 -4
  200. package/src/deploy/nfpm/contents.ts +8 -0
  201. package/src/deploy/node-runtime/elf.ts +43 -5
  202. package/src/deploy/node-runtime/index.ts +107 -6
  203. package/src/deploy/notices/node-runtime.ts +2 -2
  204. package/src/deploy/payload/launcher.ts +20 -1
  205. package/src/deploy/payload/stage.ts +1 -1
  206. package/src/deploy/run-deploy.ts +56 -16
  207. package/src/deploy/settings/build-manifest.ts +28 -3
  208. package/src/deploy/settings/extra-files.ts +26 -2
  209. package/src/deploy/settings/index.ts +3 -1
  210. package/src/deploy/settings/paths.ts +70 -5
  211. package/src/deploy/targets/flatpak-source.ts +13 -2
  212. package/src/deploy/targets/nfpm-target.ts +13 -6
  213. package/src/deploy/types.ts +1 -0
  214. package/src/dev/entry-env.ts +2 -1
  215. package/src/dev/headless.ts +40 -0
  216. package/src/dev/runner-deps.ts +3 -3
  217. package/src/dev/runner-main.ts +10 -3
  218. package/src/dev/supervisor.ts +6 -3
  219. package/src/i18n/catalogs.ts +1 -0
  220. package/src/i18n/source-messages.ts +1429 -28
  221. package/src/internal/build-manifest.ts +25 -1
  222. package/src/internal/build-output.ts +113 -0
  223. package/src/internal/docs-elements.ts +10 -4
  224. package/src/internal/entry-arg.ts +8 -1
  225. package/src/internal/errors.ts +1 -1
  226. package/src/internal/output-directory.ts +699 -0
  227. package/src/internal/parent-process.ts +110 -0
  228. package/src/internal/prepare-project.ts +5 -5
  229. package/src/internal/source-imports.ts +1 -1
  230. package/src/mcp/client.ts +2 -2
  231. package/src/vite-plugins/build-manifest.ts +7 -1
  232. package/src/vite-plugins/ensure-store.ts +2 -2
  233. package/src/vite-plugins/i18n.ts +83 -5
  234. package/src/vite-plugins/index.ts +14 -7
  235. package/src/vite-plugins/native.ts +0 -5
  236. package/src/vite-plugins/self-contained.ts +184 -185
  237. package/src/vite-plugins/settings-worker-env.ts +2 -2
  238. package/src/vitest-plugin.ts +6 -6
@@ -0,0 +1,110 @@
1
+ import { readFileSync } from "node:fs";
2
+
3
+ type ProcessIdentity = {
4
+ pid: number;
5
+ parentId: number;
6
+ processGroupId: number;
7
+ startTime: string;
8
+ state: string;
9
+ };
10
+
11
+ type ProcessGroupOwner = Pick<ProcessIdentity, "pid" | "startTime">;
12
+ type ProcessGroupOwnerCapture = { isCaptured: true; owner: ProcessGroupOwner | undefined } |
13
+ { isCaptured: false; error: Error };
14
+
15
+ const initialParentId = process.ppid;
16
+ const STOPPED_PROCESS_STATES: Set<string> = new Set(["Z", "X", "x"]);
17
+
18
+ const processIdentity = (pid: number): ProcessIdentity => {
19
+ const stat = readFileSync(`/proc/${String(pid)}/stat`, "utf8");
20
+ const fields = stat.slice(stat.lastIndexOf(") ") + 2).split(" ");
21
+ const state = fields[0];
22
+ const parentId = Number(fields[1]);
23
+ const processGroupId = Number(fields[2]);
24
+ const startTime = fields[19];
25
+
26
+ if (
27
+ state === undefined ||
28
+ startTime === undefined ||
29
+ !Number.isSafeInteger(parentId) ||
30
+ !Number.isSafeInteger(processGroupId)
31
+ ) {
32
+ throw new Error(`Failed to identify process ${String(pid)}`);
33
+ }
34
+
35
+ return { pid, parentId, processGroupId, startTime, state };
36
+ };
37
+
38
+ const ancestorFor = (process: ProcessIdentity, ancestorId: number): ProcessIdentity | undefined => {
39
+ let current = process;
40
+
41
+ while (current.parentId > 1) {
42
+ const parent = processIdentity(current.parentId);
43
+
44
+ if (parent.pid === ancestorId) {
45
+ return parent;
46
+ }
47
+
48
+ current = parent;
49
+ }
50
+
51
+ return undefined;
52
+ };
53
+
54
+ const processGroupOwner = (process: ProcessIdentity): ProcessGroupOwner => {
55
+ if (process.processGroupId !== process.pid || STOPPED_PROCESS_STATES.has(process.state)) {
56
+ throw new Error(`Failed to identify process group ${String(process.pid)}`);
57
+ }
58
+
59
+ return { pid: process.pid, startTime: process.startTime };
60
+ };
61
+
62
+ const processGroupOwnerFor = (process: ProcessIdentity): ProcessGroupOwner | undefined => {
63
+ if (process.processGroupId === process.pid) {
64
+ return undefined;
65
+ }
66
+
67
+ const groupLeader = processGroupOwner(processIdentity(process.processGroupId));
68
+ const owner = ancestorFor(process, groupLeader.pid);
69
+
70
+ if (owner !== undefined) {
71
+ return groupLeader;
72
+ }
73
+
74
+ const currentGroupLeader = processGroupOwner(processIdentity(groupLeader.pid));
75
+
76
+ if (currentGroupLeader.startTime !== groupLeader.startTime) {
77
+ throw new Error(`Failed to identify process group ${String(groupLeader.pid)}`);
78
+ }
79
+
80
+ return undefined;
81
+ };
82
+
83
+ const captureProcessGroupOwner = (): ProcessGroupOwnerCapture => {
84
+ try {
85
+ const initialProcess = processIdentity(process.pid);
86
+
87
+ if (initialProcess.parentId !== initialParentId) {
88
+ throw new Error("The process that launched GTKX exited during startup");
89
+ }
90
+
91
+ return { isCaptured: true, owner: processGroupOwnerFor(initialProcess) };
92
+ } catch (error) {
93
+ return {
94
+ isCaptured: false,
95
+ error: error instanceof Error ? error : new Error("Failed to identify the process that launched GTKX"),
96
+ };
97
+ }
98
+ };
99
+
100
+ const capturedProcessGroupOwner = captureProcessGroupOwner();
101
+
102
+ const getInitialProcessGroupOwner = (): ProcessGroupOwner | undefined => {
103
+ if (!capturedProcessGroupOwner.isCaptured) {
104
+ throw capturedProcessGroupOwner.error;
105
+ }
106
+
107
+ return capturedProcessGroupOwner.owner;
108
+ };
109
+
110
+ export { getInitialProcessGroupOwner, initialParentId };
@@ -2,22 +2,22 @@ import { ensureGeneratedIn } from "../codegen/run-codegen.js";
2
2
  import { type CodegenContext, resolveCodegenContext } from "../codegen/store-resolver.js";
3
3
  import { resolveCwd, resolveEntry } from "./entry-arg.js";
4
4
 
5
- type ProjectArgs = { entry?: string; cwd?: string };
6
- type PreparedProject = { cwd: string; entry: string };
5
+ type ProjectArgs = { entry?: string; cwd?: string; config?: string };
6
+ type PreparedProject = { cwd: string; entry: string; configFile: string };
7
7
  type ResolvedProject = PreparedProject & { context: CodegenContext };
8
8
 
9
9
  const resolveProject = async (args: ProjectArgs, mode: string): Promise<ResolvedProject> => {
10
10
  const cwd = resolveCwd(args);
11
- const context = await resolveCodegenContext(cwd, mode);
11
+ const context = await resolveCodegenContext(cwd, mode, args.config);
12
12
 
13
- return { cwd, entry: resolveEntry(cwd, args.entry), context };
13
+ return { cwd, entry: resolveEntry(cwd, args.entry), configFile: context.configFile, context };
14
14
  };
15
15
 
16
16
  const prepareProject = async (args: ProjectArgs, mode: string): Promise<PreparedProject> => {
17
17
  const { cwd, entry, context } = await resolveProject(args, mode);
18
18
  await ensureGeneratedIn(context, { shouldAnnounce: true, mode });
19
19
 
20
- return { cwd, entry };
20
+ return { cwd, entry, configFile: context.configFile };
21
21
  };
22
22
 
23
23
  export { prepareProject, resolveProject };
@@ -33,7 +33,7 @@ const discoverSourceFiles = (dir: string): string[] =>
33
33
  sortStringsBy(
34
34
  globSync("**/{*,.*}", {
35
35
  cwd: dir,
36
- exclude: ["**/{build,coverage,dist,node_modules,out-tsc}/**"],
36
+ exclude: ["**/{build,coverage,dist,node_modules,out-tsc}/**", "**/.gtkx-output-*/**"],
37
37
  withFileTypes: true,
38
38
  })
39
39
  .filter((entry) => entry.isFile())
package/src/mcp/client.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  import * as Gio from "@gtkx/gi/gio";
2
2
  import * as Gtk from "@gtkx/gi/gtk";
3
3
  import {
4
- DEFAULT_SOCKET_PATH,
5
4
  isConnectionClosedError,
6
5
  type JSONRPCRequest,
7
6
  ProtocolConnection,
7
+ resolveMcpSocketPath,
8
8
  type Result,
9
9
  } from "@gtkx/mcp/internal";
10
10
  import { error, errorMessage, info, normalizeError } from "@gtkx/utils";
@@ -65,7 +65,7 @@ class McpClient {
65
65
  private registry = new WidgetRegistry();
66
66
 
67
67
  constructor(options: McpClientOptions) {
68
- this.socketPath = options.socketPath ?? DEFAULT_SOCKET_PATH;
68
+ this.socketPath = resolveMcpSocketPath(options.socketPath);
69
69
  this.applicationId = options.applicationId;
70
70
  }
71
71
 
@@ -21,6 +21,11 @@ type ManifestState = {
21
21
  outDir: string;
22
22
  };
23
23
 
24
+ type BuildConfigIdentity = {
25
+ configFile: string;
26
+ configDigest: string;
27
+ };
28
+
24
29
  const PACKAGE_MANIFEST_FILENAME = "package.json";
25
30
  const DEFAULT_OUT_DIR = "dist";
26
31
  const JSON_INDENT = 4;
@@ -73,7 +78,7 @@ const packagesFor = (outDir: string, ids: string[]): RecordedPackage[] => {
73
78
 
74
79
  const renderManifest = (manifest: BuildManifest): string => `${JSON.stringify(manifest, null, JSON_INDENT)}\n`;
75
80
 
76
- function gtkxBuildManifest(root: string, collector: BuildManifestCollector): Plugin {
81
+ function gtkxBuildManifest(root: string, collector: BuildManifestCollector, identity: BuildConfigIdentity): Plugin {
77
82
  const state: ManifestState = { outDir: join(root, DEFAULT_OUT_DIR) };
78
83
 
79
84
  return {
@@ -90,6 +95,7 @@ function gtkxBuildManifest(root: string, collector: BuildManifestCollector): Plu
90
95
  const manifest: BuildManifest = {
91
96
  generator: BUILD_MANIFEST_GENERATOR,
92
97
  formatVersion: BUILD_MANIFEST_FORMAT_VERSION,
98
+ ...identity,
93
99
  schemas: collector.schemas,
94
100
  packages: packagesFor(state.outDir, ids),
95
101
  };
@@ -3,11 +3,11 @@ import { ensureGenerated } from "../codegen/run-codegen.js";
3
3
 
4
4
  const TEST_MODE = "test";
5
5
 
6
- const gtkxEnsureStore = (): Plugin => ({
6
+ const gtkxEnsureStore = (configFile?: string): Plugin => ({
7
7
  name: "gtkx:ensure-store",
8
8
 
9
9
  async config() {
10
- await ensureGenerated(process.cwd(), { shouldAnnounce: true, mode: TEST_MODE });
10
+ await ensureGenerated(process.cwd(), { shouldAnnounce: true, mode: TEST_MODE, configFile });
11
11
  },
12
12
  });
13
13
 
@@ -1,7 +1,7 @@
1
1
  import type { ConfigLoader } from "@gtkx/config";
2
2
  import type { Plugin, ResolvedConfig } from "vite";
3
3
  import { createConfigLoader } from "@gtkx/config/internal";
4
- import { toPosixPath } from "@gtkx/utils";
4
+ import { isPathWithin, logger, toPosixPath } from "@gtkx/utils";
5
5
  import { existsSync, mkdirSync } from "node:fs";
6
6
  import { createRequire } from "node:module";
7
7
  import { dirname, join, posix, relative, resolve } from "node:path";
@@ -13,7 +13,8 @@ import {
13
13
  synchronizeCatalogs,
14
14
  } from "../i18n/catalogs.js";
15
15
  import { extractSourceCatalog } from "../i18n/source-messages.js";
16
- import { discoverSourceFiles } from "../internal/source-imports.js";
16
+ import { emitI18nTypes } from "../i18n/types.js";
17
+ import { discoverSourceFiles, sourceLanguage } from "../internal/source-imports.js";
17
18
  import { stripQuery } from "./strip-query.js";
18
19
 
19
20
  type I18nState = {
@@ -21,6 +22,8 @@ type I18nState = {
21
22
  i18nRoot: string;
22
23
  outDir: string;
23
24
  project: CatalogProject | null;
25
+ extraction: Promise<void>;
26
+ hotUpdateTimestamp: number | null;
24
27
  };
25
28
 
26
29
  const BOOTSTRAP_SPECIFIER = "@gtkx/i18n/bootstrap";
@@ -94,6 +97,7 @@ const buildCatalogs = (state: I18nState): void => {
94
97
  const extractProjectMessages = async (
95
98
  state: I18nState,
96
99
  shouldPreserveMetadataMessages: boolean,
100
+ shouldSynchronizeCatalogs: boolean,
97
101
  ): Promise<void> => {
98
102
  const project = state.project;
99
103
 
@@ -104,7 +108,64 @@ const extractProjectMessages = async (
104
108
  const srcDir = join(project.root, "src");
105
109
  const sourceFiles = discoverSourceFiles(existsSync(srcDir) ? srcDir : project.root);
106
110
  await extractSourceCatalog(project, sourceFiles, shouldPreserveMetadataMessages);
107
- synchronizeCatalogs(project);
111
+
112
+ if (shouldSynchronizeCatalogs) {
113
+ synchronizeCatalogs(project);
114
+ }
115
+
116
+ await emitI18nTypes(project.root);
117
+ };
118
+
119
+ const settleExtraction = async (pending: Promise<void>): Promise<void> => {
120
+ try {
121
+ await pending;
122
+ } catch {
123
+ return;
124
+ }
125
+ };
126
+
127
+ const queueExtraction = (
128
+ state: I18nState,
129
+ shouldPreserveMetadataMessages: boolean,
130
+ shouldSynchronizeCatalogs: boolean,
131
+ ): Promise<void> => {
132
+ const prior = state.extraction;
133
+ const pending = (async (): Promise<void> => {
134
+ await settleExtraction(prior);
135
+ await extractProjectMessages(
136
+ state,
137
+ shouldPreserveMetadataMessages,
138
+ shouldSynchronizeCatalogs,
139
+ );
140
+ })();
141
+
142
+ state.extraction = pending;
143
+
144
+ return pending;
145
+ };
146
+
147
+ const isProjectSource = (state: I18nState, path: string): boolean => {
148
+ const project = state.project;
149
+
150
+ if (project === null || sourceLanguage(path) === undefined) {
151
+ return false;
152
+ }
153
+
154
+ const srcDir = join(project.root, "src");
155
+ const sourceRoot = existsSync(srcDir) ? srcDir : project.root;
156
+
157
+ return isPathWithin(sourceRoot, path);
158
+ };
159
+
160
+ const refreshProjectMessages = async (
161
+ state: I18nState,
162
+ shouldPreserveMetadataMessages: boolean,
163
+ ): Promise<void> => {
164
+ try {
165
+ await queueExtraction(state, shouldPreserveMetadataMessages, false);
166
+ } catch (error) {
167
+ logger.error(error instanceof Error ? error.message : String(error));
168
+ }
108
169
  };
109
170
 
110
171
  const gtkxI18n = (
@@ -112,7 +173,14 @@ const gtkxI18n = (
112
173
  loadConfig: ConfigLoader = createConfigLoader(),
113
174
  shouldPreserveMetadataMessages = true,
114
175
  ): Plugin => {
115
- const state: I18nState = { entryPath, i18nRoot: "", outDir: "", project: null };
176
+ const state: I18nState = {
177
+ entryPath,
178
+ extraction: Promise.resolve(),
179
+ hotUpdateTimestamp: null,
180
+ i18nRoot: "",
181
+ outDir: "",
182
+ project: null,
183
+ };
116
184
 
117
185
  return {
118
186
  name: "gtkx:i18n",
@@ -120,7 +188,17 @@ const gtkxI18n = (
120
188
 
121
189
  configResolved: (config) => applyResolvedConfig(state, config, loadConfig),
122
190
 
123
- buildStart: () => extractProjectMessages(state, shouldPreserveMetadataMessages),
191
+ buildStart: () => queueExtraction(state, shouldPreserveMetadataMessages, true),
192
+
193
+ hotUpdate(options) {
194
+ if (!isProjectSource(state, options.file) || state.hotUpdateTimestamp === options.timestamp) {
195
+ return;
196
+ }
197
+
198
+ state.hotUpdateTimestamp = options.timestamp;
199
+
200
+ return refreshProjectMessages(state, shouldPreserveMetadataMessages);
201
+ },
124
202
 
125
203
  transform(code, id) {
126
204
  if (isI18nLocaleModule(state, id)) {
@@ -13,13 +13,20 @@ import { gtkxSettings } from "./settings.js";
13
13
  import { gtkxStoreLinks } from "./store-links.js";
14
14
  import { gtkxUndeclaredLibrary } from "./undeclared-library.js";
15
15
 
16
- const gtkxVitePlugins = (
17
- mode?: string,
18
- entryPath?: string,
19
- buildManifest?: BuildManifestCollector,
20
- shouldPreserveI18nMetadata = true,
21
- ): Plugin[] => {
22
- const loadConfig = createConfigLoader(mode === undefined ? {} : { mode });
16
+ type GtkxVitePluginOptions = {
17
+ buildManifest?: BuildManifestCollector | undefined;
18
+ configFile?: string | undefined;
19
+ entryPath?: string | undefined;
20
+ mode?: string | undefined;
21
+ shouldPreserveI18nMetadata?: boolean | undefined;
22
+ };
23
+
24
+ const gtkxVitePlugins = (options: GtkxVitePluginOptions = {}): Plugin[] => {
25
+ const { buildManifest, configFile, entryPath, mode, shouldPreserveI18nMetadata = true } = options;
26
+ const loadConfig = createConfigLoader({
27
+ ...(mode !== undefined && { mode }),
28
+ ...(configFile !== undefined && { configFile }),
29
+ });
23
30
 
24
31
  return [
25
32
  createConfigPlugin({ name: "gtkx:config", loadConfig }),
@@ -5,7 +5,6 @@ import { arch, platform } from "node:os";
5
5
  import { dirname, join } from "node:path";
6
6
  import { stripQuery } from "./strip-query.js";
7
7
 
8
- const EMITTED_BINDING_SPECIFIER = "./gtkx.node";
9
8
  const BINDING_FILENAME = "gtkx.node";
10
9
 
11
10
  function resolveBinaryPath(projectRequire: ReturnType<typeof createRequire>, currentArch: string): string {
@@ -97,10 +96,6 @@ function gtkxNative(root: string): Plugin {
97
96
  });
98
97
  },
99
98
 
100
- resolveId(id) {
101
- return id === EMITTED_BINDING_SPECIFIER ? { id, external: true } : null;
102
- },
103
-
104
99
  transform(code, id) {
105
100
  return transformLoader(code, id, loaderPath(), bindingReferenceId);
106
101
  },