@gtkx/cli 1.0.0-rc.1 → 1.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/dist/builder.d.ts +3 -2
  2. package/dist/builder.d.ts.map +1 -1
  3. package/dist/builder.js +2 -1
  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 +21 -6
  8. package/dist/cli.js.map +1 -1
  9. package/dist/codegen/freshness.d.ts +5 -5
  10. package/dist/codegen/freshness.d.ts.map +1 -1
  11. package/dist/codegen/freshness.js +26 -23
  12. package/dist/codegen/freshness.js.map +1 -1
  13. package/dist/codegen/report.d.ts +2 -1
  14. package/dist/codegen/report.d.ts.map +1 -1
  15. package/dist/codegen/report.js +4 -2
  16. package/dist/codegen/report.js.map +1 -1
  17. package/dist/codegen/run-codegen.d.ts +8 -8
  18. package/dist/codegen/run-codegen.d.ts.map +1 -1
  19. package/dist/codegen/run-codegen.js +72 -43
  20. package/dist/codegen/run-codegen.js.map +1 -1
  21. package/dist/codegen/store-resolver.d.ts +8 -5
  22. package/dist/codegen/store-resolver.d.ts.map +1 -1
  23. package/dist/codegen/store-resolver.js +26 -6
  24. package/dist/codegen/store-resolver.js.map +1 -1
  25. package/dist/commands/build.d.ts +4 -3
  26. package/dist/commands/build.d.ts.map +1 -1
  27. package/dist/commands/build.js +2 -1
  28. package/dist/commands/build.js.map +1 -1
  29. package/dist/commands/codegen.d.ts +2 -1
  30. package/dist/commands/codegen.d.ts.map +1 -1
  31. package/dist/commands/codegen.js +6 -4
  32. package/dist/commands/codegen.js.map +1 -1
  33. package/dist/commands/dev.d.ts +2 -1
  34. package/dist/commands/dev.d.ts.map +1 -1
  35. package/dist/commands/dev.js +2 -1
  36. package/dist/commands/dev.js.map +1 -1
  37. package/dist/commands/docs.d.ts +2 -1
  38. package/dist/commands/docs.d.ts.map +1 -1
  39. package/dist/commands/docs.js +22 -7
  40. package/dist/commands/docs.js.map +1 -1
  41. package/dist/dev/icon-dir.d.ts +2 -1
  42. package/dist/dev/icon-dir.d.ts.map +1 -1
  43. package/dist/dev/icon-dir.js +6 -3
  44. package/dist/dev/icon-dir.js.map +1 -1
  45. package/dist/dev/refresh-tracker.d.ts +5 -4
  46. package/dist/dev/refresh-tracker.d.ts.map +1 -1
  47. package/dist/dev/refresh-tracker.js +6 -5
  48. package/dist/dev/refresh-tracker.js.map +1 -1
  49. package/dist/dev/runner-deps.d.ts +2 -1
  50. package/dist/dev/runner-deps.d.ts.map +1 -1
  51. package/dist/dev/runner-deps.js +20 -4
  52. package/dist/dev/runner-deps.js.map +1 -1
  53. package/dist/dev/runner-main.d.ts +2 -1
  54. package/dist/dev/runner-main.d.ts.map +1 -1
  55. package/dist/dev/runner-main.js +3 -2
  56. package/dist/dev/runner-main.js.map +1 -1
  57. package/dist/dev/runner.d.ts +6 -5
  58. package/dist/dev/runner.d.ts.map +1 -1
  59. package/dist/dev/runner.js +69 -38
  60. package/dist/dev/runner.js.map +1 -1
  61. package/dist/dev/schema-dir.d.ts +2 -1
  62. package/dist/dev/schema-dir.d.ts.map +1 -1
  63. package/dist/dev/schema-dir.js +4 -2
  64. package/dist/dev/schema-dir.js.map +1 -1
  65. package/dist/dev/supervisor.d.ts +7 -6
  66. package/dist/dev/supervisor.d.ts.map +1 -1
  67. package/dist/dev/supervisor.js +107 -67
  68. package/dist/dev/supervisor.js.map +1 -1
  69. package/dist/dev/vite-dev-server.d.ts +3 -3
  70. package/dist/dev/vite-dev-server.d.ts.map +1 -1
  71. package/dist/dev/vite-dev-server.js +2 -1
  72. package/dist/dev/vite-dev-server.js.map +1 -1
  73. package/dist/internal/banner.d.ts +2 -1
  74. package/dist/internal/banner.d.ts.map +1 -1
  75. package/dist/internal/banner.js +2 -1
  76. package/dist/internal/banner.js.map +1 -1
  77. package/dist/internal/data-dir.d.ts +3 -2
  78. package/dist/internal/data-dir.d.ts.map +1 -1
  79. package/dist/internal/data-dir.js +29 -14
  80. package/dist/internal/data-dir.js.map +1 -1
  81. package/dist/internal/entry-arg.d.ts +5 -4
  82. package/dist/internal/entry-arg.d.ts.map +1 -1
  83. package/dist/internal/entry-arg.js +10 -7
  84. package/dist/internal/entry-arg.js.map +1 -1
  85. package/dist/internal/errors.d.ts +3 -2
  86. package/dist/internal/errors.d.ts.map +1 -1
  87. package/dist/internal/errors.js +7 -5
  88. package/dist/internal/errors.js.map +1 -1
  89. package/dist/internal/list-files.d.ts +3 -2
  90. package/dist/internal/list-files.d.ts.map +1 -1
  91. package/dist/internal/list-files.js +5 -3
  92. package/dist/internal/list-files.js.map +1 -1
  93. package/dist/internal/resolve-cli-tool.d.ts +2 -1
  94. package/dist/internal/resolve-cli-tool.d.ts.map +1 -1
  95. package/dist/internal/resolve-cli-tool.js +17 -10
  96. package/dist/internal/resolve-cli-tool.js.map +1 -1
  97. package/dist/internal/staging-dir.d.ts +3 -2
  98. package/dist/internal/staging-dir.d.ts.map +1 -1
  99. package/dist/internal/staging-dir.js +3 -2
  100. package/dist/internal/staging-dir.js.map +1 -1
  101. package/dist/internal/xdg-data-dirs.d.ts +2 -1
  102. package/dist/internal/xdg-data-dirs.d.ts.map +1 -1
  103. package/dist/internal/xdg-data-dirs.js +4 -2
  104. package/dist/internal/xdg-data-dirs.js.map +1 -1
  105. package/dist/mcp/client.d.ts +9 -4
  106. package/dist/mcp/client.d.ts.map +1 -1
  107. package/dist/mcp/client.js +106 -81
  108. package/dist/mcp/client.js.map +1 -1
  109. package/dist/mcp/handlers.d.ts +3 -2
  110. package/dist/mcp/handlers.d.ts.map +1 -1
  111. package/dist/mcp/handlers.js +89 -61
  112. package/dist/mcp/handlers.js.map +1 -1
  113. package/dist/mcp/index.d.ts +2 -2
  114. package/dist/mcp/index.d.ts.map +1 -1
  115. package/dist/mcp/index.js +30 -13
  116. package/dist/mcp/index.js.map +1 -1
  117. package/dist/mcp/serialize-widget.d.ts +3 -3
  118. package/dist/mcp/serialize-widget.d.ts.map +1 -1
  119. package/dist/mcp/serialize-widget.js +9 -6
  120. package/dist/mcp/serialize-widget.js.map +1 -1
  121. package/dist/mcp/testing-loader.d.ts +3 -4
  122. package/dist/mcp/testing-loader.d.ts.map +1 -1
  123. package/dist/mcp/testing-loader.js +43 -21
  124. package/dist/mcp/testing-loader.js.map +1 -1
  125. package/dist/mcp/widget-registry.d.ts +3 -2
  126. package/dist/mcp/widget-registry.d.ts.map +1 -1
  127. package/dist/mcp/widget-registry.js +4 -3
  128. package/dist/mcp/widget-registry.js.map +1 -1
  129. package/dist/refresh-globals.d.ts +2 -0
  130. package/dist/refresh-globals.d.ts.map +1 -0
  131. package/dist/refresh-globals.js +8 -0
  132. package/dist/refresh-globals.js.map +1 -0
  133. package/dist/refresh-runtime.d.ts +5 -11
  134. package/dist/refresh-runtime.d.ts.map +1 -1
  135. package/dist/refresh-runtime.js +16 -14
  136. package/dist/refresh-runtime.js.map +1 -1
  137. package/dist/settings/compile.d.ts +2 -1
  138. package/dist/settings/compile.d.ts.map +1 -1
  139. package/dist/settings/compile.js +6 -4
  140. package/dist/settings/compile.js.map +1 -1
  141. package/dist/settings/parser.d.ts +6 -5
  142. package/dist/settings/parser.d.ts.map +1 -1
  143. package/dist/settings/parser.js +26 -22
  144. package/dist/settings/parser.js.map +1 -1
  145. package/dist/settings/render.d.ts +3 -2
  146. package/dist/settings/render.d.ts.map +1 -1
  147. package/dist/settings/render.js +38 -64
  148. package/dist/settings/render.js.map +1 -1
  149. package/dist/settings/schema.d.ts +8 -8
  150. package/dist/settings/schema.d.ts.map +1 -1
  151. package/dist/settings/schema.js +61 -43
  152. package/dist/settings/schema.js.map +1 -1
  153. package/dist/vite-plugins/asset-extensions.d.ts +4 -3
  154. package/dist/vite-plugins/asset-extensions.d.ts.map +1 -1
  155. package/dist/vite-plugins/asset-extensions.js +4 -3
  156. package/dist/vite-plugins/asset-extensions.js.map +1 -1
  157. package/dist/vite-plugins/built-url.d.ts +2 -1
  158. package/dist/vite-plugins/built-url.d.ts.map +1 -1
  159. package/dist/vite-plugins/built-url.js +19 -13
  160. package/dist/vite-plugins/built-url.js.map +1 -1
  161. package/dist/vite-plugins/css.d.ts +2 -1
  162. package/dist/vite-plugins/css.d.ts.map +1 -1
  163. package/dist/vite-plugins/css.js +23 -15
  164. package/dist/vite-plugins/css.js.map +1 -1
  165. package/dist/vite-plugins/fast-refresh/refresh-filter.d.ts +5 -4
  166. package/dist/vite-plugins/fast-refresh/refresh-filter.d.ts.map +1 -1
  167. package/dist/vite-plugins/fast-refresh/refresh-filter.js +11 -7
  168. package/dist/vite-plugins/fast-refresh/refresh-filter.js.map +1 -1
  169. package/dist/vite-plugins/fast-refresh/swc-refresh.d.ts +4 -3
  170. package/dist/vite-plugins/fast-refresh/swc-refresh.d.ts.map +1 -1
  171. package/dist/vite-plugins/fast-refresh/swc-refresh.js +45 -41
  172. package/dist/vite-plugins/fast-refresh/swc-refresh.js.map +1 -1
  173. package/dist/vite-plugins/icons.d.ts +2 -1
  174. package/dist/vite-plugins/icons.d.ts.map +1 -1
  175. package/dist/vite-plugins/icons.js +24 -20
  176. package/dist/vite-plugins/icons.js.map +1 -1
  177. package/dist/vite-plugins/index.d.ts +2 -1
  178. package/dist/vite-plugins/index.d.ts.map +1 -1
  179. package/dist/vite-plugins/index.js +3 -2
  180. package/dist/vite-plugins/index.js.map +1 -1
  181. package/dist/vite-plugins/native.d.ts +2 -1
  182. package/dist/vite-plugins/native.d.ts.map +1 -1
  183. package/dist/vite-plugins/native.js +6 -4
  184. package/dist/vite-plugins/native.js.map +1 -1
  185. package/dist/vite-plugins/react-compiler.d.ts +3 -2
  186. package/dist/vite-plugins/react-compiler.d.ts.map +1 -1
  187. package/dist/vite-plugins/react-compiler.js +26 -20
  188. package/dist/vite-plugins/react-compiler.js.map +1 -1
  189. package/dist/vite-plugins/react-dom-prebundle.d.ts +2 -1
  190. package/dist/vite-plugins/react-dom-prebundle.d.ts.map +1 -1
  191. package/dist/vite-plugins/react-dom-prebundle.js +2 -1
  192. package/dist/vite-plugins/react-dom-prebundle.js.map +1 -1
  193. package/dist/vite-plugins/resource-init-module.d.ts +2 -1
  194. package/dist/vite-plugins/resource-init-module.d.ts.map +1 -1
  195. package/dist/vite-plugins/resource-init-module.js +39 -38
  196. package/dist/vite-plugins/resource-init-module.js.map +1 -1
  197. package/dist/vite-plugins/resource-shared.d.ts +7 -5
  198. package/dist/vite-plugins/resource-shared.d.ts.map +1 -1
  199. package/dist/vite-plugins/resource-shared.js +17 -10
  200. package/dist/vite-plugins/resource-shared.js.map +1 -1
  201. package/dist/vite-plugins/resources.d.ts +3 -2
  202. package/dist/vite-plugins/resources.d.ts.map +1 -1
  203. package/dist/vite-plugins/resources.js +94 -54
  204. package/dist/vite-plugins/resources.js.map +1 -1
  205. package/dist/vite-plugins/settings-worker-env.d.ts +2 -1
  206. package/dist/vite-plugins/settings-worker-env.d.ts.map +1 -1
  207. package/dist/vite-plugins/settings-worker-env.js +4 -2
  208. package/dist/vite-plugins/settings-worker-env.js.map +1 -1
  209. package/dist/vite-plugins/settings.d.ts +2 -1
  210. package/dist/vite-plugins/settings.d.ts.map +1 -1
  211. package/dist/vite-plugins/settings.js +56 -32
  212. package/dist/vite-plugins/settings.js.map +1 -1
  213. package/dist/vite-plugins/strip-query.d.ts +3 -0
  214. package/dist/vite-plugins/strip-query.d.ts.map +1 -0
  215. package/dist/vite-plugins/strip-query.js +6 -0
  216. package/dist/vite-plugins/strip-query.js.map +1 -0
  217. package/dist/vite-plugins/undeclared-library.d.ts +2 -1
  218. package/dist/vite-plugins/undeclared-library.d.ts.map +1 -1
  219. package/dist/vite-plugins/undeclared-library.js +8 -5
  220. package/dist/vite-plugins/undeclared-library.js.map +1 -1
  221. package/dist/vite-plugins/virtual-module.d.ts +2 -2
  222. package/dist/vite-plugins/virtual-module.d.ts.map +1 -1
  223. package/dist/vite-plugins/virtual-module.js +5 -3
  224. package/dist/vite-plugins/virtual-module.js.map +1 -1
  225. package/dist/vitest-plugin.d.ts.map +1 -1
  226. package/dist/vitest-plugin.js.map +1 -1
  227. package/env.d.ts +21 -61
  228. package/package.json +12 -14
  229. package/src/babel-modules.d.ts +0 -2
  230. package/src/builder.ts +7 -6
  231. package/src/cli.ts +26 -6
  232. package/src/codegen/freshness.ts +38 -25
  233. package/src/codegen/report.ts +10 -2
  234. package/src/codegen/run-codegen.ts +135 -60
  235. package/src/codegen/store-resolver.ts +56 -15
  236. package/src/commands/build.ts +3 -2
  237. package/src/commands/codegen.ts +9 -4
  238. package/src/commands/dev.ts +3 -4
  239. package/src/commands/docs.ts +36 -7
  240. package/src/dev/icon-dir.ts +13 -3
  241. package/src/dev/refresh-tracker.ts +12 -8
  242. package/src/dev/runner-deps.ts +30 -7
  243. package/src/dev/runner-main.ts +4 -3
  244. package/src/dev/runner.ts +100 -41
  245. package/src/dev/schema-dir.ts +7 -2
  246. package/src/dev/supervisor.ts +169 -71
  247. package/src/dev/vite-dev-server.ts +4 -2
  248. package/src/internal/banner.ts +5 -1
  249. package/src/internal/data-dir.ts +47 -16
  250. package/src/internal/entry-arg.ts +16 -8
  251. package/src/internal/errors.ts +15 -7
  252. package/src/internal/list-files.ts +10 -4
  253. package/src/internal/resolve-cli-tool.ts +28 -12
  254. package/src/internal/staging-dir.ts +5 -2
  255. package/src/internal/xdg-data-dirs.ts +8 -2
  256. package/src/mcp/client.ts +143 -85
  257. package/src/mcp/handlers.ts +126 -69
  258. package/src/mcp/index.ts +38 -14
  259. package/src/mcp/serialize-widget.ts +15 -8
  260. package/src/mcp/testing-loader.ts +64 -23
  261. package/src/mcp/widget-registry.ts +11 -5
  262. package/src/refresh-globals.ts +10 -0
  263. package/src/refresh-runtime.ts +20 -16
  264. package/src/settings/compile.ts +7 -4
  265. package/src/settings/parser.ts +69 -30
  266. package/src/settings/render.ts +66 -66
  267. package/src/settings/schema.ts +100 -46
  268. package/src/vite-plugins/asset-extensions.ts +4 -3
  269. package/src/vite-plugins/built-url.ts +30 -15
  270. package/src/vite-plugins/css.ts +35 -20
  271. package/src/vite-plugins/fast-refresh/refresh-filter.ts +18 -10
  272. package/src/vite-plugins/fast-refresh/swc-refresh.ts +60 -47
  273. package/src/vite-plugins/icons.ts +40 -24
  274. package/src/vite-plugins/index.ts +6 -3
  275. package/src/vite-plugins/native.ts +14 -5
  276. package/src/vite-plugins/react-compiler.ts +44 -30
  277. package/src/vite-plugins/react-dom-prebundle.ts +4 -1
  278. package/src/vite-plugins/resource-init-module.ts +41 -38
  279. package/src/vite-plugins/resource-shared.ts +27 -14
  280. package/src/vite-plugins/resources.ts +153 -59
  281. package/src/vite-plugins/settings-worker-env.ts +7 -2
  282. package/src/vite-plugins/settings.ts +107 -42
  283. package/src/vite-plugins/strip-query.ts +7 -0
  284. package/src/vite-plugins/undeclared-library.ts +21 -12
  285. package/src/vite-plugins/virtual-module.ts +9 -2
  286. package/src/vitest-plugin.ts +1 -2
@@ -1,26 +1,44 @@
1
1
  import { loadConfig } from "@gtkx/config";
2
2
  import * as Gio from "@gtkx/gi/gio";
3
- import type * as Gtk from "@gtkx/gi/gtk";
4
3
  import { quitApplication } from "@gtkx/runtime";
5
4
  import { info, installGracefulShutdown } from "@gtkx/utils";
6
5
  import { createServer } from "vite";
6
+ import type { DevRunnerDeps } from "./runner.js";
7
7
  import { startMcpClient, stopMcpClient } from "../mcp/index.js";
8
8
  import { setTestingModuleLoader } from "../mcp/testing-loader.js";
9
9
  import { isRefreshBoundary, performRefresh } from "../refresh-runtime.js";
10
10
  import { gtkxFastRefresh } from "../vite-plugins/fast-refresh/swc-refresh.js";
11
11
  import { gtkxVitePlugins } from "../vite-plugins/index.js";
12
12
  import { gtkxReactDomPrebundle } from "../vite-plugins/react-dom-prebundle.js";
13
- import type { DevRunnerDeps } from "./runner.js";
14
13
 
15
14
  const DEV_MODE = "development";
15
+ const APPLICATION_POLL_INTERVAL_MS = 50;
16
+
17
+ const currentApplicationId = (): string | null => Gio.Application.getDefault()?.applicationId ?? null;
18
+
19
+ const waitForApplicationId = async (timeoutMs: number): Promise<string | null> => {
20
+ const deadline = Date.now() + timeoutMs;
21
+ let applicationId = currentApplicationId();
22
+
23
+ while (applicationId === null && Date.now() < deadline) {
24
+ await new Promise((resolve) => setTimeout(resolve, APPLICATION_POLL_INTERVAL_MS));
25
+ applicationId = currentApplicationId();
26
+ }
27
+
28
+ return applicationId;
29
+ };
16
30
 
17
- export const defaultDevRunnerDeps = (): DevRunnerDeps => ({
31
+ const defaultDevRunnerDeps = (): DevRunnerDeps => ({
18
32
  createServer,
19
- getApplicationId: () => Gio.Application.getDefault()?.applicationId ?? null,
20
- getConfiguredApplicationId: async (root: string) =>
21
- (await loadConfig(root, { mode: DEV_MODE })).config.applicationId,
33
+ waitForApplicationId,
34
+ getConfiguredApplicationId: async (root: string) => {
35
+ const loaded = await loadConfig(root, { mode: DEV_MODE });
36
+
37
+ return loaded.config.applicationId;
38
+ },
22
39
  startMcpClient: (applicationId, loadAppModule) => {
23
40
  setTestingModuleLoader(() => loadAppModule("@gtkx/testing") as Promise<typeof import("@gtkx/testing")>);
41
+
24
42
  return startMcpClient(applicationId);
25
43
  },
26
44
  stopMcpClient,
@@ -32,7 +50,10 @@ export const defaultDevRunnerDeps = (): DevRunnerDeps => ({
32
50
  },
33
51
  quitDefaultApplication: () => {
34
52
  const application = Gio.Application.getDefault();
35
- if (application) quitApplication(application as Gtk.Application);
53
+
54
+ if (application) {
55
+ quitApplication(application);
56
+ }
36
57
  },
37
58
  performRefresh,
38
59
  isRefreshBoundary,
@@ -40,3 +61,5 @@ export const defaultDevRunnerDeps = (): DevRunnerDeps => ({
40
61
  log: info,
41
62
  exit: (code: number): never => process.exit(code),
42
63
  });
64
+
65
+ export { defaultDevRunnerDeps };
@@ -1,5 +1,5 @@
1
- import { resolve } from "node:path";
2
1
  import { error } from "@gtkx/utils";
2
+ import { resolve } from "node:path";
3
3
  import { resolveDataDir } from "../internal/data-dir.js";
4
4
  import { prepareDevIconDir } from "./icon-dir.js";
5
5
  import { createDevRunner } from "./runner.js";
@@ -7,7 +7,7 @@ import { prepareDevSchemaDir } from "./schema-dir.js";
7
7
 
8
8
  const ENTRY_ARG_INDEX = 2;
9
9
 
10
- export const main = async (): Promise<void> => {
10
+ const main = async (): Promise<void> => {
11
11
  if (process.channel) {
12
12
  process.once("disconnect", () => {
13
13
  process.exit(0);
@@ -25,9 +25,10 @@ export const main = async (): Promise<void> => {
25
25
  const dataDir = resolveDataDir(cwd);
26
26
  prepareDevSchemaDir(cwd, dataDir);
27
27
  prepareDevIconDir(cwd, dataDir);
28
-
29
28
  const entryPath = resolve(cwd, entryArg);
30
29
  const { defaultDevRunnerDeps } = await import("./runner-deps.js");
31
30
  const runner = createDevRunner(defaultDevRunnerDeps());
32
31
  await runner.run(entryPath);
33
32
  };
33
+
34
+ export { main };
package/src/dev/runner.ts CHANGED
@@ -1,14 +1,12 @@
1
- import { error } from "@gtkx/utils";
2
1
  import type { InlineConfig, Plugin } from "vite";
3
- import { createRefreshTracker } from "./refresh-tracker.js";
2
+ import { error } from "@gtkx/utils";
3
+ import { createRefreshTracker, type RefreshTracker } from "./refresh-tracker.js";
4
4
  import { RESTART_EXIT_CODE } from "./supervisor.js";
5
5
  import { createDevServerConfig, type DevServer } from "./vite-dev-server.js";
6
6
 
7
- export type { DevServer } from "./vite-dev-server.js";
8
-
9
- export type DevRunnerDeps = {
7
+ type DevRunnerDeps = {
10
8
  createServer(config: InlineConfig): Promise<DevServer>;
11
- getApplicationId(): string | null;
9
+ waitForApplicationId(timeoutMs: number): Promise<string | null>;
12
10
  getConfiguredApplicationId(root: string): Promise<string | undefined>;
13
11
  startMcpClient(
14
12
  applicationId: string,
@@ -18,7 +16,7 @@ export type DevRunnerDeps = {
18
16
  watchApplicationShutdown(onShutdown: () => void): void;
19
17
  installShutdownHandlers(onSignal: () => void | Promise<void>): void;
20
18
  quitDefaultApplication(): void;
21
- performRefresh(): void;
19
+ performRefresh: () => void;
22
20
  isRefreshBoundary(module: Record<string, unknown>): boolean;
23
21
  plugins(): Plugin[];
24
22
  log(message: string): void;
@@ -29,91 +27,149 @@ type DevRunner = {
29
27
  run(entryPath: string): Promise<void>;
30
28
  };
31
29
 
30
+ type ShutdownController = {
31
+ isShuttingDown: () => boolean;
32
+ shutdown: (quitApplication: () => void) => Promise<void>;
33
+ };
34
+
35
+ const APPLICATION_MOUNT_TIMEOUT_MS = 10_000;
36
+
32
37
  const requestRestart = async (server: DevServer, deps: DevRunnerDeps): Promise<never> => {
33
38
  deps.log("Full restart (process restart)");
34
39
  await server.close();
40
+
35
41
  return deps.exit(RESTART_EXIT_CODE);
36
42
  };
37
43
 
38
44
  const handleFileChange = async (server: DevServer, deps: DevRunnerDeps, changedPath: string): Promise<void> => {
39
45
  const module = server.moduleGraph.getModuleById(changedPath);
40
- if (!module) return;
41
46
 
42
- deps.log(`File changed: ${changedPath}`);
47
+ if (!module) {
48
+ return;
49
+ }
43
50
 
51
+ deps.log(`File changed: ${changedPath}`);
44
52
  const loadedExports = module.ssrModule;
53
+
45
54
  if (loadedExports && !deps.isRefreshBoundary(loadedExports)) {
46
55
  await requestRestart(server, deps);
56
+
47
57
  return;
48
58
  }
49
59
 
50
60
  server.moduleGraph.invalidateModule(module);
61
+
51
62
  for (const importer of module.importers) {
52
63
  server.moduleGraph.invalidateModule(importer);
53
64
  }
54
65
 
55
66
  const newMod = await server.ssrLoadModule(changedPath);
67
+
56
68
  if (deps.isRefreshBoundary(newMod)) {
57
69
  deps.log("Running Fast Refresh...");
58
70
  deps.performRefresh();
59
71
  deps.log("Fast Refresh complete");
72
+
60
73
  return;
61
74
  }
62
75
 
63
76
  await requestRestart(server, deps);
64
77
  };
65
78
 
66
- export const createDevRunner = (deps: DevRunnerDeps): DevRunner => ({
67
- async run(entryPath: string): Promise<void> {
68
- const root = process.cwd();
69
- const server = await deps.createServer(createDevServerConfig(root, deps.plugins()));
79
+ const createShutdownController = (server: DevServer, deps: DevRunnerDeps): ShutdownController => {
80
+ let isShuttingDown = false;
70
81
 
71
- const refreshTracker = createRefreshTracker(deps.performRefresh);
72
- const refreshTrackingDeps: DevRunnerDeps = { ...deps, performRefresh: refreshTracker.performRefresh };
82
+ return {
83
+ isShuttingDown: () => isShuttingDown,
84
+ shutdown: async (quitApplication: () => void): Promise<void> => {
85
+ if (isShuttingDown) {
86
+ return;
87
+ }
73
88
 
74
- let isShuttingDown = false;
75
- const shutdown = async (quitApplication: () => void): Promise<void> => {
76
- if (isShuttingDown) return;
77
89
  isShuttingDown = true;
78
90
  quitApplication();
79
91
  deps.stopMcpClient();
80
92
  await server.close();
93
+ },
94
+ };
95
+ };
96
+
97
+ const reloadChangedFile = async (server: DevServer, deps: DevRunnerDeps, changedPath: string): Promise<void> => {
98
+ try {
99
+ await handleFileChange(server, deps, changedPath);
100
+ } catch (error_) {
101
+ error("Hot reload failed:", error_);
102
+ }
103
+ };
104
+
105
+ const onFileChange =
106
+ (server: DevServer, deps: DevRunnerDeps, controller: ShutdownController): ((changedPath: string) => void) =>
107
+ (changedPath) => {
108
+ if (controller.isShuttingDown()) {
109
+ return;
110
+ }
111
+
112
+ void reloadChangedFile(server, deps, changedPath);
81
113
  };
82
114
 
83
- deps.installShutdownHandlers(async () => {
84
- if (isShuttingDown) return;
115
+ const onShutdownSignal =
116
+ (deps: DevRunnerDeps, controller: ShutdownController): (() => Promise<void>) =>
117
+ async () => {
118
+ if (controller.isShuttingDown()) {
119
+ return;
120
+ }
121
+
85
122
  deps.log("Received shutdown signal - stopping dev runner...");
86
- await shutdown(() => deps.quitDefaultApplication());
87
- });
88
123
 
89
- server.watcher.on("change", (changedPath) => {
90
- if (isShuttingDown) return;
91
- handleFileChange(server, refreshTrackingDeps, changedPath).catch((cause) => {
92
- error("Hot reload failed:", cause);
124
+ await controller.shutdown(() => {
125
+ deps.quitDefaultApplication();
93
126
  });
94
- });
127
+ };
95
128
 
96
- deps.log(`Loading entry: ${entryPath}`);
97
- await server.ssrLoadModule(entryPath);
129
+ const closeAndExit = async (deps: DevRunnerDeps, controller: ShutdownController): Promise<never> => {
130
+ try {
131
+ await controller.shutdown((): void => undefined);
132
+ } catch (error_) {
133
+ error("Error closing server:", error_);
134
+
135
+ return deps.exit(1);
136
+ }
137
+
138
+ return deps.exit(0);
139
+ };
140
+
141
+ const onApplicationShutdown =
142
+ (deps: DevRunnerDeps, refreshTracker: RefreshTracker, controller: ShutdownController): (() => void) =>
143
+ () => {
144
+ if (controller.isShuttingDown()) {
145
+ return;
146
+ }
98
147
 
99
- deps.watchApplicationShutdown(() => {
100
- if (isShuttingDown) return;
101
148
  if (refreshTracker.isRefreshing()) {
102
149
  deps.log("Application unmounted during Fast Refresh - restarting dev runner...");
150
+
103
151
  return deps.exit(RESTART_EXIT_CODE);
104
152
  }
153
+
105
154
  deps.log("Application quit - stopping dev runner...");
106
- shutdown(() => {}).then(
107
- () => deps.exit(0),
108
- (cause: unknown) => {
109
- error("Error closing server:", cause);
110
- return deps.exit(1);
111
- },
112
- );
113
- });
114
-
115
- const liveApplicationId = deps.getApplicationId();
155
+ void closeAndExit(deps, controller);
156
+ };
157
+
158
+ const createDevRunner = (deps: DevRunnerDeps): DevRunner => ({
159
+ async run(entryPath: string): Promise<void> {
160
+ const root = process.cwd();
161
+ const server = await deps.createServer(createDevServerConfig(root, deps.plugins()));
162
+ const refreshTracker = createRefreshTracker(deps.performRefresh);
163
+ const refreshTrackingDeps: DevRunnerDeps = { ...deps, performRefresh: refreshTracker.performRefresh };
164
+ const controller = createShutdownController(server, deps);
165
+ deps.installShutdownHandlers(onShutdownSignal(deps, controller));
166
+ server.watcher.on("change", onFileChange(server, refreshTrackingDeps, controller));
167
+ deps.log(`Loading entry: ${entryPath}`);
168
+ await server.ssrLoadModule(entryPath);
169
+ const liveApplicationId = await deps.waitForApplicationId(APPLICATION_MOUNT_TIMEOUT_MS);
170
+
116
171
  if (liveApplicationId) {
172
+ deps.watchApplicationShutdown(onApplicationShutdown(deps, refreshTracker, controller));
117
173
  const applicationId = (await deps.getConfiguredApplicationId(root)) ?? liveApplicationId;
118
174
  deps.log(`Connected application ID: ${applicationId}`);
119
175
  await deps.startMcpClient(applicationId, (id) => server.ssrLoadModule(id));
@@ -124,3 +180,6 @@ export const createDevRunner = (deps: DevRunnerDeps): DevRunner => ({
124
180
  deps.log("HMR enabled - watching for changes...");
125
181
  },
126
182
  });
183
+
184
+ export type { DevServer } from "./vite-dev-server.js";
185
+ export { createDevRunner, type DevRunnerDeps };
@@ -1,11 +1,16 @@
1
1
  import { prependSchemaDir, stageAndCompileProjectSchemas } from "../settings/schema.js";
2
2
 
3
- export const prepareDevSchemaDir = (root: string, dataDir: string | null): string | null => {
3
+ const prepareDevSchemaDir = (root: string, dataDir: string | null): string | null => {
4
4
  const dir = stageAndCompileProjectSchemas(root, dataDir);
5
- if (dir === null) return null;
5
+
6
+ if (dir === null) {
7
+ return null;
8
+ }
6
9
 
7
10
  process.env.GTKX_DEV_SCHEMA_DIR = dir;
8
11
  process.env.GSETTINGS_SCHEMA_DIR = prependSchemaDir(dir, process.env.GSETTINGS_SCHEMA_DIR);
9
12
 
10
13
  return dir;
11
14
  };
15
+
16
+ export { prepareDevSchemaDir };
@@ -1,16 +1,10 @@
1
+ import { error, exitCodeForSignal, info, installGracefulShutdown } from "@gtkx/utils";
1
2
  import { fork as nodeFork } from "node:child_process";
2
3
  import { type FSWatcher, watch as watchFs } from "node:fs";
3
4
  import { basename, dirname } from "node:path";
4
5
  import { fileURLToPath } from "node:url";
5
- import { error, exitCodeForSignal, info, installGracefulShutdown } from "@gtkx/utils";
6
-
7
- const DEV_RUNNER_URL = new URL("../../bin/gtkx-dev-runner.js", import.meta.url);
8
- const FORCE_KILL_TIMEOUT_MS = 5000;
9
- const CONFIG_DEBOUNCE_MS = 150;
10
-
11
- export const RESTART_EXIT_CODE = 75;
12
6
 
13
- export type SupervisedChild = {
7
+ type SupervisedChild = {
14
8
  killed: boolean;
15
9
  pid?: number | undefined;
16
10
  exitCode: number | null;
@@ -19,12 +13,9 @@ export type SupervisedChild = {
19
13
  once(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): void;
20
14
  };
21
15
 
22
- export type ForkRunner = (modulePath: string, args: string[], cwd: string) => SupervisedChild;
16
+ type ForkRunner = (modulePath: string, args: string[], cwd: string) => SupervisedChild;
23
17
 
24
- export const defaultForkRunner: ForkRunner = (modulePath, args, cwd) =>
25
- nodeFork(modulePath, [...args], { cwd, stdio: "inherit", detached: true });
26
-
27
- export type DevWatch = {
18
+ type DevWatch = {
28
19
  paths: string[];
29
20
  regenerate: () => Promise<void>;
30
21
  };
@@ -42,116 +33,221 @@ type SupervisorState = {
42
33
  capturedChildExit: number | undefined;
43
34
  };
44
35
 
36
+ type DebounceTimer = { handle: NodeJS.Timeout | null };
37
+
38
+ const DEV_RUNNER_URL = new URL("../../bin/gtkx-dev-runner.js", import.meta.url);
39
+ const FORCE_KILL_TIMEOUT_MS = 5000;
40
+ const CONFIG_DEBOUNCE_MS = 150;
41
+ const RESTART_EXIT_CODE = 75;
42
+
43
+ const defaultForkRunner: ForkRunner = (modulePath, args, cwd) =>
44
+ nodeFork(modulePath, [...args], { cwd, stdio: "inherit", detached: true });
45
+
45
46
  const forwardSignal = (child: SupervisedChild, signal: NodeJS.Signals): void => {
46
47
  if (!child.killed) {
47
48
  child.kill(signal);
48
49
  }
49
50
  };
50
51
 
51
- const forceKillChild = (child: SupervisedChild | null): void => {
52
- if (!child?.pid || child.exitCode !== null || child.killed) return;
52
+ const didForceKillChild = (child: SupervisedChild | null): boolean => {
53
+ if (!child?.pid || child.exitCode !== null || child.killed) {
54
+ return false;
55
+ }
56
+
53
57
  try {
54
- process.kill(child.pid, "SIGKILL");
55
- } catch {}
58
+ return process.kill(child.pid, "SIGKILL");
59
+ } catch {
60
+ return false;
61
+ }
62
+ };
63
+
64
+ const captureShutdownExit = (state: SupervisorState, code: number | null, signal: NodeJS.Signals | null): void => {
65
+ if (code !== null) {
66
+ state.capturedChildExit = code;
67
+ } else if (signal) {
68
+ state.capturedChildExit = exitCodeForSignal(signal);
69
+ }
70
+ };
71
+
72
+ const handleChildExit = (state: SupervisorState, code: number | null, signal: NodeJS.Signals | null): void => {
73
+ state.child = null;
74
+
75
+ if (state.restarting) {
76
+ return;
77
+ }
78
+
79
+ if (state.shuttingDown) {
80
+ captureShutdownExit(state, code, signal);
81
+
82
+ return;
83
+ }
84
+
85
+ if (code === RESTART_EXIT_CODE) {
86
+ info("Restarting dev runner...");
87
+ launch(state);
88
+
89
+ return;
90
+ }
91
+
92
+ process.exit(code ?? exitCodeForSignal(signal));
56
93
  };
57
94
 
58
95
  const launch = (state: SupervisorState): void => {
59
96
  const child = state.fork(state.runnerPath, [state.entryPath], state.cwd);
60
97
  state.child = child;
98
+
61
99
  child.on("exit", (code, signal) => {
62
- state.child = null;
63
- if (state.restarting) return;
64
- if (state.shuttingDown) {
65
- if (code !== null) {
66
- state.capturedChildExit = code;
67
- } else if (signal) {
68
- state.capturedChildExit = exitCodeForSignal(signal);
69
- }
70
- return;
71
- }
72
- if (code === RESTART_EXIT_CODE) {
73
- info("Restarting dev runner...");
74
- launch(state);
75
- return;
76
- }
77
- process.exit(code ?? exitCodeForSignal(signal));
100
+ handleChildExit(state, code, signal);
78
101
  });
79
102
  };
80
103
 
104
+ const isRestartBlocked = (state: SupervisorState): boolean => state.restarting || state.shuttingDown;
105
+
106
+ const relaunchAfterExit = (state: SupervisorState): void => {
107
+ state.restarting = false;
108
+
109
+ if (state.shuttingDown) {
110
+ return;
111
+ }
112
+
113
+ info("Restarting dev runner...");
114
+ launch(state);
115
+ };
116
+
81
117
  const restart = async (state: SupervisorState): Promise<void> => {
82
- if (state.restarting || state.shuttingDown || state.watch === undefined) return;
118
+ const watch = state.watch;
119
+
120
+ if (watch === undefined || isRestartBlocked(state)) {
121
+ return;
122
+ }
123
+
83
124
  state.restarting = true;
84
125
  info("gtkx.config.ts changed; regenerating bindings...");
126
+
85
127
  try {
86
- await state.watch.regenerate();
87
- } catch (cause) {
88
- error("Codegen failed; keeping the current dev runner. Fix the error and save again.", cause);
128
+ await watch.regenerate();
129
+ } catch (error_) {
130
+ error("Codegen failed; keeping the current dev runner. Fix the error and save again.", error_);
89
131
  state.restarting = false;
132
+
90
133
  return;
91
134
  }
135
+
92
136
  if (state.shuttingDown) {
93
137
  state.restarting = false;
138
+
94
139
  return;
95
140
  }
141
+
96
142
  const current = state.child;
143
+
97
144
  if (current === null) {
98
145
  state.restarting = false;
99
146
  launch(state);
147
+
100
148
  return;
101
149
  }
150
+
102
151
  current.once("exit", () => {
103
- state.restarting = false;
104
- if (!state.shuttingDown) {
105
- info("Restarting dev runner...");
106
- launch(state);
107
- }
152
+ relaunchAfterExit(state);
108
153
  });
154
+
109
155
  forwardSignal(current, "SIGTERM");
110
156
  };
111
157
 
112
- const installConfigWatchers = (state: SupervisorState): void => {
113
- const watch = state.watch;
114
- if (watch === undefined || watch.paths.length === 0) return;
115
- let timer: NodeJS.Timeout | null = null;
116
- const namesByDirectory = new Map<string, Set<string>>();
117
- for (const path of watch.paths) {
158
+ const scheduleRestart = (state: SupervisorState, timer: DebounceTimer): void => {
159
+ if (timer.handle !== null) {
160
+ clearTimeout(timer.handle);
161
+ }
162
+
163
+ timer.handle = setTimeout(() => void restart(state), CONFIG_DEBOUNCE_MS);
164
+ };
165
+
166
+ const isWatchedChange = (state: SupervisorState, names: Set<string>, filename: string | Buffer | null): boolean => {
167
+ if (filename === null || state.shuttingDown) {
168
+ return false;
169
+ }
170
+
171
+ return names.has(basename(filename.toString()));
172
+ };
173
+
174
+ const groupWatchNamesByDirectory = (paths: string[]): Map<string, Set<string>> => {
175
+ const namesByDirectory: Map<string, Set<string>> = new Map();
176
+
177
+ for (const path of paths) {
118
178
  const directory = dirname(path);
119
179
  const names = namesByDirectory.get(directory) ?? new Set<string>();
120
180
  names.add(basename(path));
121
181
  namesByDirectory.set(directory, names);
122
182
  }
123
- for (const [directory, names] of namesByDirectory) {
124
- const watcher = watchFs(directory, (_event, filename) => {
125
- if (state.shuttingDown) return;
126
- if (filename === null || !names.has(basename(filename.toString()))) return;
127
- if (timer !== null) clearTimeout(timer);
128
- timer = setTimeout(() => void restart(state), CONFIG_DEBOUNCE_MS);
129
- });
130
- watcher.on("error", () => {});
131
- state.watchers.push(watcher);
183
+
184
+ return namesByDirectory;
185
+ };
186
+
187
+ const watchConfigDirectory = (
188
+ state: SupervisorState,
189
+ directory: string,
190
+ names: Set<string>,
191
+ timer: DebounceTimer,
192
+ ): void => {
193
+ const watcher = watchFs(directory, (_event, filename) => {
194
+ if (isWatchedChange(state, names, filename)) {
195
+ scheduleRestart(state, timer);
196
+ }
197
+ });
198
+
199
+ watcher.on("error", (): void => undefined);
200
+ state.watchers.push(watcher);
201
+ };
202
+
203
+ const installConfigWatchers = (state: SupervisorState): void => {
204
+ const watch = state.watch;
205
+
206
+ if (watch === undefined || watch.paths.length === 0) {
207
+ return;
208
+ }
209
+
210
+ const timer: DebounceTimer = { handle: null };
211
+
212
+ for (const [directory, names] of groupWatchNamesByDirectory(watch.paths)) {
213
+ watchConfigDirectory(state, directory, names, timer);
132
214
  }
133
215
  };
134
216
 
217
+ const closeWatchers = (state: SupervisorState): void => {
218
+ for (const watcher of state.watchers) {
219
+ watcher.close();
220
+ }
221
+ };
222
+
223
+ const shutdownOnSignal = (state: SupervisorState, signal: NodeJS.Signals): Promise<void> =>
224
+ new Promise<void>((resolve) => {
225
+ state.shuttingDown = true;
226
+ closeWatchers(state);
227
+
228
+ if (!state.child) {
229
+ resolve();
230
+
231
+ return;
232
+ }
233
+
234
+ state.child.once("exit", () => {
235
+ resolve();
236
+ });
237
+
238
+ forwardSignal(state.child, signal);
239
+ });
240
+
135
241
  const installShutdown = (state: SupervisorState): void => {
136
242
  installGracefulShutdown({
137
- onSignal: (signal) =>
138
- new Promise<void>((resolve) => {
139
- state.shuttingDown = true;
140
- for (const watcher of state.watchers) watcher.close();
141
- if (!state.child) {
142
- resolve();
143
- return;
144
- }
145
- state.child.once("exit", () => resolve());
146
- forwardSignal(state.child, signal);
147
- }),
148
- onForce: () => forceKillChild(state.child),
243
+ onSignal: (signal) => shutdownOnSignal(state, signal),
244
+ onForce: () => didForceKillChild(state.child),
149
245
  forceKillAfterMs: FORCE_KILL_TIMEOUT_MS,
150
246
  exitCode: (signal, graceful) => state.capturedChildExit ?? (graceful ? 0 : exitCodeForSignal(signal)),
151
247
  });
152
248
  };
153
249
 
154
- export const runDevSupervisor = async (
250
+ const runDevSupervisor = async (
155
251
  entryPath: string,
156
252
  cwd: string,
157
253
  watch?: DevWatch,
@@ -174,5 +270,7 @@ export const runDevSupervisor = async (
174
270
  installShutdown(state);
175
271
  launch(state);
176
272
 
177
- return new Promise<never>(() => {});
273
+ return new Promise<never>((): void => undefined);
178
274
  };
275
+
276
+ export { RESTART_EXIT_CODE, defaultForkRunner, runDevSupervisor, type SupervisedChild, type ForkRunner, type DevWatch };
@@ -12,7 +12,7 @@ type DevServerModuleGraph = {
12
12
  invalidateModule(module: DevServerModule): void;
13
13
  };
14
14
 
15
- export type DevServer = {
15
+ type DevServer = {
16
16
  close(): Promise<void>;
17
17
  moduleGraph: DevServerModuleGraph;
18
18
  ssrLoadModule(id: string): Promise<Record<string, unknown>>;
@@ -21,7 +21,7 @@ export type DevServer = {
21
21
  };
22
22
  };
23
23
 
24
- export const createDevServerConfig = (root: string, plugins: Plugin[]): InlineConfig => ({
24
+ const createDevServerConfig = (root: string, plugins: Plugin[]): InlineConfig => ({
25
25
  root,
26
26
  appType: "custom",
27
27
  plugins,
@@ -32,3 +32,5 @@ export const createDevServerConfig = (root: string, plugins: Plugin[]): InlineCo
32
32
  noExternal: [/^@gtkx\/(?!(?:native|gi|gl|runtime|utils|css)(?:\/|$))/, /[/\\]\.gtkx[/\\]/],
33
33
  },
34
34
  });
35
+
36
+ export { createDevServerConfig, type DevServer };