@gtkx/cli 0.21.0 → 1.0.0-rc.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 (346) hide show
  1. package/README.md +173 -0
  2. package/bin/gtkx-dev-runner.js +9 -0
  3. package/dist/builder.d.ts +2 -50
  4. package/dist/builder.d.ts.map +1 -1
  5. package/dist/builder.js +22 -56
  6. package/dist/builder.js.map +1 -1
  7. package/dist/cli.d.ts +1 -2
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +11 -124
  10. package/dist/cli.js.map +1 -1
  11. package/dist/codegen/freshness.d.ts +11 -0
  12. package/dist/codegen/freshness.d.ts.map +1 -0
  13. package/dist/codegen/freshness.js +42 -0
  14. package/dist/codegen/freshness.js.map +1 -0
  15. package/dist/codegen/report.d.ts +3 -0
  16. package/dist/codegen/report.d.ts.map +1 -0
  17. package/dist/codegen/report.js +16 -0
  18. package/dist/codegen/report.js.map +1 -0
  19. package/dist/codegen/run-codegen.d.ts +35 -0
  20. package/dist/codegen/run-codegen.d.ts.map +1 -0
  21. package/dist/codegen/run-codegen.js +133 -0
  22. package/dist/codegen/run-codegen.js.map +1 -0
  23. package/dist/codegen/store-resolver.d.ts +21 -0
  24. package/dist/codegen/store-resolver.d.ts.map +1 -0
  25. package/dist/codegen/store-resolver.js +52 -0
  26. package/dist/codegen/store-resolver.js.map +1 -0
  27. package/dist/commands/build.d.ts +9 -0
  28. package/dist/commands/build.d.ts.map +1 -0
  29. package/dist/commands/build.js +33 -0
  30. package/dist/commands/build.js.map +1 -0
  31. package/dist/commands/codegen.d.ts +9 -0
  32. package/dist/commands/codegen.d.ts.map +1 -0
  33. package/dist/commands/codegen.js +40 -0
  34. package/dist/commands/codegen.js.map +1 -0
  35. package/dist/commands/dev.d.ts +5 -0
  36. package/dist/commands/dev.d.ts.map +1 -0
  37. package/dist/commands/dev.js +21 -0
  38. package/dist/commands/dev.js.map +1 -0
  39. package/dist/commands/docs.d.ts +19 -0
  40. package/dist/commands/docs.d.ts.map +1 -0
  41. package/dist/commands/docs.js +59 -0
  42. package/dist/commands/docs.js.map +1 -0
  43. package/dist/dev/icon-dir.d.ts +2 -0
  44. package/dist/dev/icon-dir.d.ts.map +1 -0
  45. package/dist/dev/icon-dir.js +13 -0
  46. package/dist/dev/icon-dir.js.map +1 -0
  47. package/dist/dev/refresh-tracker.d.ts +6 -0
  48. package/dist/dev/refresh-tracker.d.ts.map +1 -0
  49. package/dist/dev/refresh-tracker.js +18 -0
  50. package/dist/dev/refresh-tracker.js.map +1 -0
  51. package/dist/dev/runner-deps.d.ts +3 -0
  52. package/dist/dev/runner-deps.d.ts.map +1 -0
  53. package/dist/dev/runner-deps.js +39 -0
  54. package/dist/dev/runner-deps.js.map +1 -0
  55. package/dist/dev/runner-main.d.ts +2 -0
  56. package/dist/dev/runner-main.d.ts.map +1 -0
  57. package/dist/dev/runner-main.js +28 -0
  58. package/dist/dev/runner-main.js.map +1 -0
  59. package/dist/dev/runner.d.ts +23 -0
  60. package/dist/dev/runner.d.ts.map +1 -0
  61. package/dist/dev/runner.js +88 -0
  62. package/dist/dev/runner.js.map +1 -0
  63. package/dist/dev/schema-dir.d.ts +2 -0
  64. package/dist/dev/schema-dir.d.ts.map +1 -0
  65. package/dist/dev/schema-dir.js +10 -0
  66. package/dist/dev/schema-dir.js.map +1 -0
  67. package/dist/dev/supervisor.d.ts +17 -0
  68. package/dist/dev/supervisor.d.ts.map +1 -0
  69. package/dist/dev/supervisor.js +142 -0
  70. package/dist/dev/supervisor.js.map +1 -0
  71. package/dist/dev/vite-dev-server.d.ts +21 -0
  72. package/dist/dev/vite-dev-server.d.ts.map +1 -0
  73. package/dist/dev/vite-dev-server.js +12 -0
  74. package/dist/dev/vite-dev-server.js.map +1 -0
  75. package/dist/internal/banner.d.ts +3 -0
  76. package/dist/internal/banner.d.ts.map +1 -0
  77. package/dist/internal/banner.js +8 -0
  78. package/dist/internal/banner.js.map +1 -0
  79. package/dist/internal/data-dir.d.ts +3 -0
  80. package/dist/internal/data-dir.d.ts.map +1 -0
  81. package/dist/internal/data-dir.js +37 -0
  82. package/dist/internal/data-dir.js.map +1 -0
  83. package/dist/internal/entry-arg.d.ts +19 -0
  84. package/dist/internal/entry-arg.d.ts.map +1 -0
  85. package/dist/internal/entry-arg.js +33 -0
  86. package/dist/internal/entry-arg.js.map +1 -0
  87. package/dist/internal/errors.d.ts +4 -0
  88. package/dist/internal/errors.d.ts.map +1 -0
  89. package/dist/internal/errors.js +27 -0
  90. package/dist/internal/errors.js.map +1 -0
  91. package/dist/internal/list-files.d.ts +6 -0
  92. package/dist/internal/list-files.d.ts.map +1 -0
  93. package/dist/internal/list-files.js +13 -0
  94. package/dist/internal/list-files.js.map +1 -0
  95. package/dist/internal/resolve-cli-tool.d.ts +2 -0
  96. package/dist/internal/resolve-cli-tool.d.ts.map +1 -0
  97. package/dist/internal/resolve-cli-tool.js +20 -0
  98. package/dist/internal/resolve-cli-tool.js.map +1 -0
  99. package/dist/internal/staging-dir.d.ts +3 -0
  100. package/dist/internal/staging-dir.d.ts.map +1 -0
  101. package/dist/internal/staging-dir.js +16 -0
  102. package/dist/internal/staging-dir.js.map +1 -0
  103. package/dist/internal/xdg-data-dirs.d.ts +2 -0
  104. package/dist/internal/xdg-data-dirs.d.ts.map +1 -0
  105. package/dist/internal/xdg-data-dirs.js +8 -0
  106. package/dist/internal/xdg-data-dirs.js.map +1 -0
  107. package/dist/mcp/client.d.ts +23 -0
  108. package/dist/mcp/client.d.ts.map +1 -0
  109. package/dist/mcp/client.js +157 -0
  110. package/dist/mcp/client.js.map +1 -0
  111. package/dist/mcp/handlers.d.ts +8 -0
  112. package/dist/mcp/handlers.d.ts.map +1 -0
  113. package/dist/mcp/handlers.js +119 -0
  114. package/dist/mcp/handlers.js.map +1 -0
  115. package/dist/mcp/index.d.ts +4 -0
  116. package/dist/mcp/index.d.ts.map +1 -0
  117. package/dist/mcp/index.js +17 -0
  118. package/dist/mcp/index.js.map +1 -0
  119. package/dist/mcp/serialize-widget.d.ts +10 -0
  120. package/dist/mcp/serialize-widget.d.ts.map +1 -0
  121. package/dist/mcp/serialize-widget.js +20 -0
  122. package/dist/mcp/serialize-widget.js.map +1 -0
  123. package/dist/mcp/testing-loader.d.ts +6 -0
  124. package/dist/mcp/testing-loader.d.ts.map +1 -0
  125. package/dist/mcp/testing-loader.js +25 -0
  126. package/dist/mcp/testing-loader.js.map +1 -0
  127. package/dist/mcp/widget-registry.d.ts +13 -0
  128. package/dist/mcp/widget-registry.d.ts.map +1 -0
  129. package/dist/mcp/widget-registry.js +38 -0
  130. package/dist/mcp/widget-registry.js.map +1 -0
  131. package/dist/refresh-runtime.d.ts +10 -20
  132. package/dist/refresh-runtime.d.ts.map +1 -1
  133. package/dist/refresh-runtime.js +12 -36
  134. package/dist/refresh-runtime.js.map +1 -1
  135. package/dist/settings/compile.d.ts +2 -0
  136. package/dist/settings/compile.d.ts.map +1 -0
  137. package/dist/settings/compile.js +19 -0
  138. package/dist/settings/compile.js.map +1 -0
  139. package/dist/settings/parser.d.ts +23 -0
  140. package/dist/settings/parser.d.ts.map +1 -0
  141. package/dist/settings/parser.js +117 -0
  142. package/dist/settings/parser.js.map +1 -0
  143. package/dist/settings/render.d.ts +4 -0
  144. package/dist/settings/render.d.ts.map +1 -0
  145. package/dist/settings/render.js +148 -0
  146. package/dist/settings/render.js.map +1 -0
  147. package/dist/settings/schema.d.ts +13 -0
  148. package/dist/settings/schema.d.ts.map +1 -0
  149. package/dist/settings/schema.js +103 -0
  150. package/dist/settings/schema.js.map +1 -0
  151. package/dist/vite-plugins/asset-extensions.d.ts +4 -0
  152. package/dist/vite-plugins/asset-extensions.d.ts.map +1 -0
  153. package/dist/vite-plugins/asset-extensions.js +27 -0
  154. package/dist/vite-plugins/asset-extensions.js.map +1 -0
  155. package/dist/vite-plugins/built-url.d.ts +3 -0
  156. package/dist/vite-plugins/built-url.d.ts.map +1 -0
  157. package/dist/{vite-plugin-gtkx-built-url.js → vite-plugins/built-url.js} +4 -17
  158. package/dist/vite-plugins/built-url.js.map +1 -0
  159. package/dist/vite-plugins/css.d.ts +3 -0
  160. package/dist/vite-plugins/css.d.ts.map +1 -0
  161. package/dist/vite-plugins/css.js +30 -0
  162. package/dist/vite-plugins/css.js.map +1 -0
  163. package/dist/vite-plugins/fast-refresh/refresh-filter.d.ts +7 -0
  164. package/dist/vite-plugins/fast-refresh/refresh-filter.d.ts.map +1 -0
  165. package/dist/vite-plugins/fast-refresh/refresh-filter.js +15 -0
  166. package/dist/vite-plugins/fast-refresh/refresh-filter.js.map +1 -0
  167. package/dist/vite-plugins/fast-refresh/swc-refresh.d.ts +5 -0
  168. package/dist/vite-plugins/fast-refresh/swc-refresh.d.ts.map +1 -0
  169. package/dist/vite-plugins/fast-refresh/swc-refresh.js +66 -0
  170. package/dist/vite-plugins/fast-refresh/swc-refresh.js.map +1 -0
  171. package/dist/vite-plugins/icons.d.ts +3 -0
  172. package/dist/vite-plugins/icons.d.ts.map +1 -0
  173. package/dist/vite-plugins/icons.js +47 -0
  174. package/dist/vite-plugins/icons.js.map +1 -0
  175. package/dist/vite-plugins/index.d.ts +3 -0
  176. package/dist/vite-plugins/index.d.ts.map +1 -0
  177. package/dist/vite-plugins/index.js +21 -0
  178. package/dist/vite-plugins/index.js.map +1 -0
  179. package/dist/vite-plugins/native.d.ts +3 -0
  180. package/dist/vite-plugins/native.d.ts.map +1 -0
  181. package/dist/vite-plugins/native.js +60 -0
  182. package/dist/vite-plugins/native.js.map +1 -0
  183. package/dist/vite-plugins/react-compiler.d.ts +4 -0
  184. package/dist/vite-plugins/react-compiler.d.ts.map +1 -0
  185. package/dist/vite-plugins/react-compiler.js +56 -0
  186. package/dist/vite-plugins/react-compiler.js.map +1 -0
  187. package/dist/vite-plugins/react-dom-prebundle.d.ts +3 -0
  188. package/dist/vite-plugins/react-dom-prebundle.d.ts.map +1 -0
  189. package/dist/vite-plugins/react-dom-prebundle.js +11 -0
  190. package/dist/vite-plugins/react-dom-prebundle.js.map +1 -0
  191. package/dist/vite-plugins/resource-init-module.d.ts +5 -0
  192. package/dist/vite-plugins/resource-init-module.d.ts.map +1 -0
  193. package/dist/vite-plugins/resource-init-module.js +50 -0
  194. package/dist/vite-plugins/resource-init-module.js.map +1 -0
  195. package/dist/vite-plugins/resource-shared.d.ts +6 -0
  196. package/dist/vite-plugins/resource-shared.d.ts.map +1 -0
  197. package/dist/vite-plugins/resource-shared.js +21 -0
  198. package/dist/vite-plugins/resource-shared.js.map +1 -0
  199. package/dist/vite-plugins/resources.d.ts +4 -0
  200. package/dist/vite-plugins/resources.d.ts.map +1 -0
  201. package/dist/vite-plugins/resources.js +220 -0
  202. package/dist/vite-plugins/resources.js.map +1 -0
  203. package/dist/vite-plugins/settings-worker-env.d.ts +3 -0
  204. package/dist/vite-plugins/settings-worker-env.d.ts.map +1 -0
  205. package/dist/vite-plugins/settings-worker-env.js +21 -0
  206. package/dist/vite-plugins/settings-worker-env.js.map +1 -0
  207. package/dist/vite-plugins/settings.d.ts +3 -0
  208. package/dist/vite-plugins/settings.d.ts.map +1 -0
  209. package/dist/vite-plugins/settings.js +190 -0
  210. package/dist/vite-plugins/settings.js.map +1 -0
  211. package/dist/vite-plugins/undeclared-library.d.ts +3 -0
  212. package/dist/vite-plugins/undeclared-library.d.ts.map +1 -0
  213. package/dist/vite-plugins/undeclared-library.js +47 -0
  214. package/dist/vite-plugins/undeclared-library.js.map +1 -0
  215. package/dist/vite-plugins/virtual-module.d.ts +22 -0
  216. package/dist/vite-plugins/virtual-module.d.ts.map +1 -0
  217. package/dist/vite-plugins/virtual-module.js +18 -0
  218. package/dist/vite-plugins/virtual-module.js.map +1 -0
  219. package/dist/vitest-plugin.d.ts +10 -0
  220. package/dist/vitest-plugin.d.ts.map +1 -0
  221. package/dist/vitest-plugin.js +12 -0
  222. package/dist/vitest-plugin.js.map +1 -0
  223. package/env.d.ts +131 -8
  224. package/package.json +32 -38
  225. package/src/babel-modules.d.ts +13 -0
  226. package/src/builder.ts +28 -84
  227. package/src/cli.ts +22 -0
  228. package/src/codegen/freshness.ts +52 -0
  229. package/src/codegen/report.ts +20 -0
  230. package/src/codegen/run-codegen.ts +185 -0
  231. package/src/codegen/store-resolver.ts +75 -0
  232. package/src/commands/build.ts +37 -0
  233. package/src/commands/codegen.ts +44 -0
  234. package/src/commands/dev.ts +25 -0
  235. package/src/commands/docs.ts +66 -0
  236. package/src/dev/icon-dir.ts +11 -0
  237. package/src/dev/refresh-tracker.ts +21 -0
  238. package/src/dev/runner-deps.ts +42 -0
  239. package/src/dev/runner-main.ts +33 -0
  240. package/src/dev/runner.ts +126 -0
  241. package/src/dev/schema-dir.ts +11 -0
  242. package/src/dev/supervisor.ts +178 -0
  243. package/src/dev/vite-dev-server.ts +34 -0
  244. package/src/internal/banner.ts +9 -0
  245. package/src/internal/data-dir.ts +39 -0
  246. package/src/internal/entry-arg.ts +38 -0
  247. package/src/internal/errors.ts +28 -0
  248. package/src/internal/list-files.ts +17 -0
  249. package/src/internal/resolve-cli-tool.ts +21 -0
  250. package/src/internal/staging-dir.ts +16 -0
  251. package/src/internal/xdg-data-dirs.ts +7 -0
  252. package/src/mcp/client.ts +177 -0
  253. package/src/mcp/handlers.ts +156 -0
  254. package/src/mcp/index.ts +22 -0
  255. package/src/mcp/serialize-widget.ts +34 -0
  256. package/src/mcp/testing-loader.ts +32 -0
  257. package/src/mcp/widget-registry.ts +43 -0
  258. package/src/refresh-runtime.ts +12 -41
  259. package/src/settings/compile.ts +19 -0
  260. package/src/settings/parser.ts +154 -0
  261. package/src/settings/render.ts +167 -0
  262. package/src/settings/schema.ts +115 -0
  263. package/src/vite-plugins/asset-extensions.ts +28 -0
  264. package/src/{vite-plugin-gtkx-built-url.ts → vite-plugins/built-url.ts} +3 -16
  265. package/src/vite-plugins/css.ts +38 -0
  266. package/src/vite-plugins/fast-refresh/refresh-filter.ts +18 -0
  267. package/src/vite-plugins/fast-refresh/swc-refresh.ts +79 -0
  268. package/src/vite-plugins/icons.ts +58 -0
  269. package/src/vite-plugins/index.ts +22 -0
  270. package/src/vite-plugins/native.ts +74 -0
  271. package/src/vite-plugins/react-compiler.ts +70 -0
  272. package/src/vite-plugins/react-dom-prebundle.ts +14 -0
  273. package/src/vite-plugins/resource-init-module.ts +54 -0
  274. package/src/vite-plugins/resource-shared.ts +27 -0
  275. package/src/vite-plugins/resources.ts +272 -0
  276. package/src/vite-plugins/settings-worker-env.ts +25 -0
  277. package/src/vite-plugins/settings.ts +221 -0
  278. package/src/vite-plugins/undeclared-library.ts +69 -0
  279. package/src/vite-plugins/virtual-module.ts +37 -0
  280. package/src/vitest-plugin.ts +14 -0
  281. package/dist/create.d.ts +0 -55
  282. package/dist/create.d.ts.map +0 -1
  283. package/dist/create.js +0 -236
  284. package/dist/create.js.map +0 -1
  285. package/dist/dev-server.d.ts +0 -37
  286. package/dist/dev-server.d.ts.map +0 -1
  287. package/dist/dev-server.js +0 -130
  288. package/dist/dev-server.js.map +0 -1
  289. package/dist/index.d.ts +0 -4
  290. package/dist/index.d.ts.map +0 -1
  291. package/dist/index.js +0 -4
  292. package/dist/index.js.map +0 -1
  293. package/dist/mcp-client.d.ts +0 -31
  294. package/dist/mcp-client.d.ts.map +0 -1
  295. package/dist/mcp-client.js +0 -430
  296. package/dist/mcp-client.js.map +0 -1
  297. package/dist/templates.d.ts +0 -9
  298. package/dist/templates.d.ts.map +0 -1
  299. package/dist/templates.js +0 -16
  300. package/dist/templates.js.map +0 -1
  301. package/dist/vite-plugin-gtkx-assets.d.ts +0 -20
  302. package/dist/vite-plugin-gtkx-assets.d.ts.map +0 -1
  303. package/dist/vite-plugin-gtkx-assets.js +0 -57
  304. package/dist/vite-plugin-gtkx-assets.js.map +0 -1
  305. package/dist/vite-plugin-gtkx-built-url.d.ts +0 -18
  306. package/dist/vite-plugin-gtkx-built-url.d.ts.map +0 -1
  307. package/dist/vite-plugin-gtkx-built-url.js.map +0 -1
  308. package/dist/vite-plugin-gtkx-gsettings.d.ts +0 -28
  309. package/dist/vite-plugin-gtkx-gsettings.d.ts.map +0 -1
  310. package/dist/vite-plugin-gtkx-gsettings.js +0 -147
  311. package/dist/vite-plugin-gtkx-gsettings.js.map +0 -1
  312. package/dist/vite-plugin-gtkx-native.d.ts +0 -14
  313. package/dist/vite-plugin-gtkx-native.d.ts.map +0 -1
  314. package/dist/vite-plugin-gtkx-native.js +0 -53
  315. package/dist/vite-plugin-gtkx-native.js.map +0 -1
  316. package/dist/vite-plugin-gtkx-refresh.d.ts +0 -8
  317. package/dist/vite-plugin-gtkx-refresh.d.ts.map +0 -1
  318. package/dist/vite-plugin-gtkx-refresh.js +0 -37
  319. package/dist/vite-plugin-gtkx-refresh.js.map +0 -1
  320. package/dist/vite-plugin-swc-ssr-refresh.d.ts +0 -8
  321. package/dist/vite-plugin-swc-ssr-refresh.d.ts.map +0 -1
  322. package/dist/vite-plugin-swc-ssr-refresh.js +0 -46
  323. package/dist/vite-plugin-swc-ssr-refresh.js.map +0 -1
  324. package/src/cli.tsx +0 -154
  325. package/src/create.ts +0 -326
  326. package/src/dev-server.tsx +0 -162
  327. package/src/index.ts +0 -3
  328. package/src/mcp-client.ts +0 -518
  329. package/src/templates.ts +0 -26
  330. package/src/vite-plugin-gtkx-assets.ts +0 -69
  331. package/src/vite-plugin-gtkx-gsettings.ts +0 -168
  332. package/src/vite-plugin-gtkx-native.ts +0 -64
  333. package/src/vite-plugin-gtkx-refresh.ts +0 -54
  334. package/src/vite-plugin-swc-ssr-refresh.ts +0 -61
  335. package/templates/claude/EXAMPLES.md.ejs +0 -744
  336. package/templates/claude/SKILL.md.ejs +0 -93
  337. package/templates/claude/WIDGETS.md.ejs +0 -820
  338. package/templates/config/vitest.config.ts.ejs +0 -11
  339. package/templates/gitignore.ejs +0 -4
  340. package/templates/package.json.ejs +0 -16
  341. package/templates/src/app.tsx.ejs +0 -32
  342. package/templates/src/dev.tsx.ejs +0 -5
  343. package/templates/src/gtkx-env.d.ts.ejs +0 -1
  344. package/templates/src/index.tsx.ejs +0 -5
  345. package/templates/tests/app.test.tsx.ejs +0 -16
  346. package/templates/tsconfig.json.ejs +0 -14
@@ -0,0 +1,21 @@
1
+ export type RefreshTracker = {
2
+ performRefresh(): void;
3
+ isRefreshing(): boolean;
4
+ };
5
+
6
+ export const createRefreshTracker = (performRefresh: () => void): RefreshTracker => {
7
+ let refreshing = false;
8
+ return {
9
+ performRefresh: () => {
10
+ refreshing = true;
11
+ try {
12
+ performRefresh();
13
+ } finally {
14
+ setTimeout(() => {
15
+ refreshing = false;
16
+ }, 0);
17
+ }
18
+ },
19
+ isRefreshing: () => refreshing,
20
+ };
21
+ };
@@ -0,0 +1,42 @@
1
+ import { loadConfig } from "@gtkx/config";
2
+ import * as Gio from "@gtkx/gi/gio";
3
+ import type * as Gtk from "@gtkx/gi/gtk";
4
+ import { quitApplication } from "@gtkx/runtime";
5
+ import { info, installGracefulShutdown } from "@gtkx/utils";
6
+ import { createServer } from "vite";
7
+ import { startMcpClient, stopMcpClient } from "../mcp/index.js";
8
+ import { setTestingModuleLoader } from "../mcp/testing-loader.js";
9
+ import { isRefreshBoundary, performRefresh } from "../refresh-runtime.js";
10
+ import { gtkxFastRefresh } from "../vite-plugins/fast-refresh/swc-refresh.js";
11
+ import { gtkxVitePlugins } from "../vite-plugins/index.js";
12
+ import { gtkxReactDomPrebundle } from "../vite-plugins/react-dom-prebundle.js";
13
+ import type { DevRunnerDeps } from "./runner.js";
14
+
15
+ const DEV_MODE = "development";
16
+
17
+ export const defaultDevRunnerDeps = (): DevRunnerDeps => ({
18
+ createServer,
19
+ getApplicationId: () => Gio.Application.getDefault()?.applicationId ?? null,
20
+ getConfiguredApplicationId: async (root: string) =>
21
+ (await loadConfig(root, { mode: DEV_MODE })).config.applicationId,
22
+ startMcpClient: (applicationId, loadAppModule) => {
23
+ setTestingModuleLoader(() => loadAppModule("@gtkx/testing") as Promise<typeof import("@gtkx/testing")>);
24
+ return startMcpClient(applicationId);
25
+ },
26
+ stopMcpClient,
27
+ watchApplicationShutdown: (onShutdown) => {
28
+ Gio.Application.getDefault()?.on("shutdown", onShutdown);
29
+ },
30
+ installShutdownHandlers: (onSignal) => {
31
+ installGracefulShutdown({ onSignal });
32
+ },
33
+ quitDefaultApplication: () => {
34
+ const application = Gio.Application.getDefault();
35
+ if (application) quitApplication(application as Gtk.Application);
36
+ },
37
+ performRefresh,
38
+ isRefreshBoundary,
39
+ plugins: () => [...gtkxVitePlugins(DEV_MODE), ...gtkxFastRefresh(), gtkxReactDomPrebundle()],
40
+ log: info,
41
+ exit: (code: number): never => process.exit(code),
42
+ });
@@ -0,0 +1,33 @@
1
+ import { resolve } from "node:path";
2
+ import { error } from "@gtkx/utils";
3
+ import { resolveDataDir } from "../internal/data-dir.js";
4
+ import { prepareDevIconDir } from "./icon-dir.js";
5
+ import { createDevRunner } from "./runner.js";
6
+ import { prepareDevSchemaDir } from "./schema-dir.js";
7
+
8
+ const ENTRY_ARG_INDEX = 2;
9
+
10
+ export const main = async (): Promise<void> => {
11
+ if (process.channel) {
12
+ process.once("disconnect", () => {
13
+ process.exit(0);
14
+ });
15
+ }
16
+
17
+ const cwd = process.cwd();
18
+ const entryArg = process.argv[ENTRY_ARG_INDEX];
19
+
20
+ if (!entryArg) {
21
+ error("Missing entry argument");
22
+ process.exit(1);
23
+ }
24
+
25
+ const dataDir = resolveDataDir(cwd);
26
+ prepareDevSchemaDir(cwd, dataDir);
27
+ prepareDevIconDir(cwd, dataDir);
28
+
29
+ const entryPath = resolve(cwd, entryArg);
30
+ const { defaultDevRunnerDeps } = await import("./runner-deps.js");
31
+ const runner = createDevRunner(defaultDevRunnerDeps());
32
+ await runner.run(entryPath);
33
+ };
@@ -0,0 +1,126 @@
1
+ import { error } from "@gtkx/utils";
2
+ import type { InlineConfig, Plugin } from "vite";
3
+ import { createRefreshTracker } from "./refresh-tracker.js";
4
+ import { RESTART_EXIT_CODE } from "./supervisor.js";
5
+ import { createDevServerConfig, type DevServer } from "./vite-dev-server.js";
6
+
7
+ export type { DevServer } from "./vite-dev-server.js";
8
+
9
+ export type DevRunnerDeps = {
10
+ createServer(config: InlineConfig): Promise<DevServer>;
11
+ getApplicationId(): string | null;
12
+ getConfiguredApplicationId(root: string): Promise<string | undefined>;
13
+ startMcpClient(
14
+ applicationId: string,
15
+ loadAppModule: (id: string) => Promise<Record<string, unknown>>,
16
+ ): Promise<unknown>;
17
+ stopMcpClient(): void;
18
+ watchApplicationShutdown(onShutdown: () => void): void;
19
+ installShutdownHandlers(onSignal: () => void | Promise<void>): void;
20
+ quitDefaultApplication(): void;
21
+ performRefresh(): void;
22
+ isRefreshBoundary(module: Record<string, unknown>): boolean;
23
+ plugins(): Plugin[];
24
+ log(message: string): void;
25
+ exit(code: number): never;
26
+ };
27
+
28
+ type DevRunner = {
29
+ run(entryPath: string): Promise<void>;
30
+ };
31
+
32
+ const requestRestart = async (server: DevServer, deps: DevRunnerDeps): Promise<never> => {
33
+ deps.log("Full restart (process restart)");
34
+ await server.close();
35
+ return deps.exit(RESTART_EXIT_CODE);
36
+ };
37
+
38
+ const handleFileChange = async (server: DevServer, deps: DevRunnerDeps, changedPath: string): Promise<void> => {
39
+ const module = server.moduleGraph.getModuleById(changedPath);
40
+ if (!module) return;
41
+
42
+ deps.log(`File changed: ${changedPath}`);
43
+
44
+ const loadedExports = module.ssrModule;
45
+ if (loadedExports && !deps.isRefreshBoundary(loadedExports)) {
46
+ await requestRestart(server, deps);
47
+ return;
48
+ }
49
+
50
+ server.moduleGraph.invalidateModule(module);
51
+ for (const importer of module.importers) {
52
+ server.moduleGraph.invalidateModule(importer);
53
+ }
54
+
55
+ const newMod = await server.ssrLoadModule(changedPath);
56
+ if (deps.isRefreshBoundary(newMod)) {
57
+ deps.log("Running Fast Refresh...");
58
+ deps.performRefresh();
59
+ deps.log("Fast Refresh complete");
60
+ return;
61
+ }
62
+
63
+ await requestRestart(server, deps);
64
+ };
65
+
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()));
70
+
71
+ const refreshTracker = createRefreshTracker(deps.performRefresh);
72
+ const refreshTrackingDeps: DevRunnerDeps = { ...deps, performRefresh: refreshTracker.performRefresh };
73
+
74
+ let isShuttingDown = false;
75
+ const shutdown = async (quitApplication: () => void): Promise<void> => {
76
+ if (isShuttingDown) return;
77
+ isShuttingDown = true;
78
+ quitApplication();
79
+ deps.stopMcpClient();
80
+ await server.close();
81
+ };
82
+
83
+ deps.installShutdownHandlers(async () => {
84
+ if (isShuttingDown) return;
85
+ deps.log("Received shutdown signal - stopping dev runner...");
86
+ await shutdown(() => deps.quitDefaultApplication());
87
+ });
88
+
89
+ server.watcher.on("change", (changedPath) => {
90
+ if (isShuttingDown) return;
91
+ handleFileChange(server, refreshTrackingDeps, changedPath).catch((cause) => {
92
+ error("Hot reload failed:", cause);
93
+ });
94
+ });
95
+
96
+ deps.log(`Loading entry: ${entryPath}`);
97
+ await server.ssrLoadModule(entryPath);
98
+
99
+ deps.watchApplicationShutdown(() => {
100
+ if (isShuttingDown) return;
101
+ if (refreshTracker.isRefreshing()) {
102
+ deps.log("Application unmounted during Fast Refresh - restarting dev runner...");
103
+ return deps.exit(RESTART_EXIT_CODE);
104
+ }
105
+ 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();
116
+ if (liveApplicationId) {
117
+ const applicationId = (await deps.getConfiguredApplicationId(root)) ?? liveApplicationId;
118
+ deps.log(`Connected application ID: ${applicationId}`);
119
+ await deps.startMcpClient(applicationId, (id) => server.ssrLoadModule(id));
120
+ } else {
121
+ deps.log("Entry did not mount an application - MCP client not started.");
122
+ }
123
+
124
+ deps.log("HMR enabled - watching for changes...");
125
+ },
126
+ });
@@ -0,0 +1,11 @@
1
+ import { prependSchemaDir, stageAndCompileProjectSchemas } from "../settings/schema.js";
2
+
3
+ export const prepareDevSchemaDir = (root: string, dataDir: string | null): string | null => {
4
+ const dir = stageAndCompileProjectSchemas(root, dataDir);
5
+ if (dir === null) return null;
6
+
7
+ process.env.GTKX_DEV_SCHEMA_DIR = dir;
8
+ process.env.GSETTINGS_SCHEMA_DIR = prependSchemaDir(dir, process.env.GSETTINGS_SCHEMA_DIR);
9
+
10
+ return dir;
11
+ };
@@ -0,0 +1,178 @@
1
+ import { fork as nodeFork } from "node:child_process";
2
+ import { type FSWatcher, watch as watchFs } from "node:fs";
3
+ import { basename, dirname } from "node:path";
4
+ 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
+
13
+ export type SupervisedChild = {
14
+ killed: boolean;
15
+ pid?: number | undefined;
16
+ exitCode: number | null;
17
+ kill(signal?: number | NodeJS.Signals): boolean;
18
+ on(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): void;
19
+ once(event: "exit", listener: (code: number | null, signal: NodeJS.Signals | null) => void): void;
20
+ };
21
+
22
+ export type ForkRunner = (modulePath: string, args: string[], cwd: string) => SupervisedChild;
23
+
24
+ export const defaultForkRunner: ForkRunner = (modulePath, args, cwd) =>
25
+ nodeFork(modulePath, [...args], { cwd, stdio: "inherit", detached: true });
26
+
27
+ export type DevWatch = {
28
+ paths: string[];
29
+ regenerate: () => Promise<void>;
30
+ };
31
+
32
+ type SupervisorState = {
33
+ runnerPath: string;
34
+ entryPath: string;
35
+ cwd: string;
36
+ watch: DevWatch | undefined;
37
+ watchers: FSWatcher[];
38
+ fork: ForkRunner;
39
+ child: SupervisedChild | null;
40
+ shuttingDown: boolean;
41
+ restarting: boolean;
42
+ capturedChildExit: number | undefined;
43
+ };
44
+
45
+ const forwardSignal = (child: SupervisedChild, signal: NodeJS.Signals): void => {
46
+ if (!child.killed) {
47
+ child.kill(signal);
48
+ }
49
+ };
50
+
51
+ const forceKillChild = (child: SupervisedChild | null): void => {
52
+ if (!child?.pid || child.exitCode !== null || child.killed) return;
53
+ try {
54
+ process.kill(child.pid, "SIGKILL");
55
+ } catch {}
56
+ };
57
+
58
+ const launch = (state: SupervisorState): void => {
59
+ const child = state.fork(state.runnerPath, [state.entryPath], state.cwd);
60
+ state.child = child;
61
+ 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));
78
+ });
79
+ };
80
+
81
+ const restart = async (state: SupervisorState): Promise<void> => {
82
+ if (state.restarting || state.shuttingDown || state.watch === undefined) return;
83
+ state.restarting = true;
84
+ info("gtkx.config.ts changed; regenerating bindings...");
85
+ 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);
89
+ state.restarting = false;
90
+ return;
91
+ }
92
+ if (state.shuttingDown) {
93
+ state.restarting = false;
94
+ return;
95
+ }
96
+ const current = state.child;
97
+ if (current === null) {
98
+ state.restarting = false;
99
+ launch(state);
100
+ return;
101
+ }
102
+ current.once("exit", () => {
103
+ state.restarting = false;
104
+ if (!state.shuttingDown) {
105
+ info("Restarting dev runner...");
106
+ launch(state);
107
+ }
108
+ });
109
+ forwardSignal(current, "SIGTERM");
110
+ };
111
+
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) {
118
+ const directory = dirname(path);
119
+ const names = namesByDirectory.get(directory) ?? new Set<string>();
120
+ names.add(basename(path));
121
+ namesByDirectory.set(directory, names);
122
+ }
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);
132
+ }
133
+ };
134
+
135
+ const installShutdown = (state: SupervisorState): void => {
136
+ 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),
149
+ forceKillAfterMs: FORCE_KILL_TIMEOUT_MS,
150
+ exitCode: (signal, graceful) => state.capturedChildExit ?? (graceful ? 0 : exitCodeForSignal(signal)),
151
+ });
152
+ };
153
+
154
+ export const runDevSupervisor = async (
155
+ entryPath: string,
156
+ cwd: string,
157
+ watch?: DevWatch,
158
+ fork: ForkRunner = defaultForkRunner,
159
+ ): Promise<never> => {
160
+ const state: SupervisorState = {
161
+ runnerPath: fileURLToPath(DEV_RUNNER_URL),
162
+ entryPath,
163
+ cwd,
164
+ watch,
165
+ watchers: [],
166
+ fork,
167
+ child: null,
168
+ shuttingDown: false,
169
+ restarting: false,
170
+ capturedChildExit: undefined,
171
+ };
172
+
173
+ installConfigWatchers(state);
174
+ installShutdown(state);
175
+ launch(state);
176
+
177
+ return new Promise<never>(() => {});
178
+ };
@@ -0,0 +1,34 @@
1
+ import type { InlineConfig, Plugin } from "vite";
2
+
3
+ type DevServerModule = object;
4
+
5
+ type DevServerChangedModule = DevServerModule & {
6
+ importers: Iterable<DevServerModule>;
7
+ ssrModule?: Record<string, unknown> | null;
8
+ };
9
+
10
+ type DevServerModuleGraph = {
11
+ getModuleById(id: string): DevServerChangedModule | undefined;
12
+ invalidateModule(module: DevServerModule): void;
13
+ };
14
+
15
+ export type DevServer = {
16
+ close(): Promise<void>;
17
+ moduleGraph: DevServerModuleGraph;
18
+ ssrLoadModule(id: string): Promise<Record<string, unknown>>;
19
+ watcher: {
20
+ on(event: "change", listener: (changedPath: string) => void): void;
21
+ };
22
+ };
23
+
24
+ export const createDevServerConfig = (root: string, plugins: Plugin[]): InlineConfig => ({
25
+ root,
26
+ appType: "custom",
27
+ plugins,
28
+ server: { middlewareMode: true },
29
+ optimizeDeps: { noDiscovery: true, include: [] },
30
+ ssr: {
31
+ external: true,
32
+ noExternal: [/^@gtkx\/(?!(?:native|gi|gl|runtime|utils|css)(?:\/|$))/, /[/\\]\.gtkx[/\\]/],
33
+ },
34
+ });
@@ -0,0 +1,9 @@
1
+ import type { Rollup } from "vite";
2
+
3
+ export const prependBanner = (options: Rollup.OutputOptions, banner: string): Rollup.OutputOptions => {
4
+ const existing = options.banner;
5
+ if (typeof existing === "function") {
6
+ return { ...options, banner: async (chunk) => `${banner}\n${await existing(chunk)}` };
7
+ }
8
+ return { ...options, banner: existing ? `${banner}\n${existing}` : banner };
9
+ };
@@ -0,0 +1,39 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ export const DATA_IMPORT_PREFIX = "#data";
5
+
6
+ const DATA_IMPORT_KEY: string = `${DATA_IMPORT_PREFIX}/*`;
7
+
8
+ const CONDITION_PRIORITY = ["default", "import", "node"] as const;
9
+
10
+ const targetString = (entry: unknown): string | null => {
11
+ if (typeof entry === "string") return entry;
12
+ if (entry !== null && typeof entry === "object" && !Array.isArray(entry)) {
13
+ const conditions = entry as Record<string, unknown>;
14
+ for (const condition of CONDITION_PRIORITY) {
15
+ const value = conditions[condition];
16
+ if (typeof value === "string") return value;
17
+ }
18
+ }
19
+ return null;
20
+ };
21
+
22
+ const DATA_TARGET_PATTERN = /^\.?\/?(.+?)\/\*$/;
23
+
24
+ const directoryFromTarget = (target: string): string | null => {
25
+ const match = DATA_TARGET_PATTERN.exec(target);
26
+ return match?.[1] ?? null;
27
+ };
28
+
29
+ export const resolveDataDir = (root: string): string | null => {
30
+ let manifest: { imports?: Record<string, unknown> };
31
+ try {
32
+ manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf-8"));
33
+ } catch {
34
+ return null;
35
+ }
36
+ const target = targetString(manifest.imports?.[DATA_IMPORT_KEY]);
37
+ if (target === null) return null;
38
+ return directoryFromTarget(target);
39
+ };
@@ -0,0 +1,38 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import type { PositionalArgDef, StringArgDef } from "citty";
4
+
5
+ const DEFAULT_ENTRY_BASE = "src/index";
6
+ const DEFAULT_ENTRY_EXTENSIONS = [".tsx", ".jsx", ".ts", ".js"];
7
+ const DEFAULT_ENTRY = `${DEFAULT_ENTRY_BASE}{${DEFAULT_ENTRY_EXTENSIONS.join(",")}}`;
8
+
9
+ export const cwdArg: { cwd: StringArgDef } = {
10
+ cwd: {
11
+ type: "string",
12
+ description: "Project root (default: current working directory)",
13
+ },
14
+ };
15
+
16
+ export const entryArg: { entry: PositionalArgDef; cwd: StringArgDef } = {
17
+ entry: {
18
+ type: "positional",
19
+ description: `Entry file (default: ${DEFAULT_ENTRY})`,
20
+ required: false,
21
+ },
22
+ ...cwdArg,
23
+ };
24
+
25
+ export const resolveCwd = (args: { cwd?: string }): string => (args.cwd ? resolve(args.cwd) : process.cwd());
26
+
27
+ const resolveDefaultEntry = (cwd: string): string => {
28
+ for (const extension of DEFAULT_ENTRY_EXTENSIONS) {
29
+ const candidate = resolve(cwd, `${DEFAULT_ENTRY_BASE}${extension}`);
30
+ if (existsSync(candidate)) return candidate;
31
+ }
32
+ return resolve(cwd, `${DEFAULT_ENTRY_BASE}${DEFAULT_ENTRY_EXTENSIONS[0]}`);
33
+ };
34
+
35
+ export const resolveEntry = (args: { entry?: string; cwd?: string }): { cwd: string; entry: string } => {
36
+ const cwd = resolveCwd(args);
37
+ return { cwd, entry: args.entry ? resolve(cwd, args.entry) : resolveDefaultEntry(cwd) };
38
+ };
@@ -0,0 +1,28 @@
1
+ import { logger } from "@gtkx/utils";
2
+ import type { ArgsDef, CommandDef } from "citty";
3
+
4
+ const ERROR_EXIT_CODE = 1;
5
+
6
+ export const printError = (cause: unknown): never => {
7
+ const message = cause instanceof Error ? cause.message : String(cause);
8
+ logger.error(message);
9
+ if (cause instanceof Error && cause.stack !== undefined) {
10
+ logger.debug(cause.stack);
11
+ }
12
+ process.exit(ERROR_EXIT_CODE);
13
+ };
14
+
15
+ export const withErrorBoundary = <T extends ArgsDef>(command: CommandDef<T>): CommandDef<T> => {
16
+ const run = command.run;
17
+ if (run === undefined) return command;
18
+ return {
19
+ ...command,
20
+ run: async (context) => {
21
+ try {
22
+ return await run(context);
23
+ } catch (cause) {
24
+ printError(cause);
25
+ }
26
+ },
27
+ };
28
+ };
@@ -0,0 +1,17 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import { join, relative } from "node:path";
3
+
4
+ export type ListedFile = {
5
+ absPath: string;
6
+ rel: string;
7
+ };
8
+
9
+ export const listFilesRecursive = (dir: string, predicate?: (name: string) => boolean): ListedFile[] => {
10
+ if (!existsSync(dir)) return [];
11
+ return readdirSync(dir, { recursive: true, withFileTypes: true })
12
+ .filter((entry) => entry.isFile() && (predicate === undefined || predicate(entry.name)))
13
+ .map((entry) => {
14
+ const absPath = join(entry.parentPath, entry.name);
15
+ return { absPath, rel: relative(dir, absPath) };
16
+ });
17
+ };
@@ -0,0 +1,21 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+ import { delimiter, join } from "node:path";
3
+
4
+ export const resolveCliTool = (() => {
5
+ const cache = new Map<string, string>();
6
+
7
+ return (executable: string): string => {
8
+ const cached = cache.get(executable);
9
+ if (cached) return cached;
10
+
11
+ const dirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
12
+ for (const dir of dirs) {
13
+ const candidate = join(dir, executable);
14
+ if (existsSync(candidate) && statSync(candidate).isFile()) {
15
+ cache.set(executable, candidate);
16
+ return candidate;
17
+ }
18
+ }
19
+ throw new Error(`${executable} not found in PATH`);
20
+ };
21
+ })();
@@ -0,0 +1,16 @@
1
+ import { mkdtempSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+
5
+ export const removeTempDir = (dir: string): void => {
6
+ rmSync(dir, { recursive: true, force: true, maxRetries: 5 });
7
+ };
8
+
9
+ export const withStagingDir = <T>(prefix: string, produce: (dir: string) => T): T => {
10
+ const dir = mkdtempSync(join(tmpdir(), `gtkx-${prefix}-`));
11
+ try {
12
+ return produce(dir);
13
+ } finally {
14
+ removeTempDir(dir);
15
+ }
16
+ };
@@ -0,0 +1,7 @@
1
+ const XDG_DATA_DIRS_DEFAULT = "/usr/local/share:/usr/share";
2
+
3
+ export const prependXdgDataDir = (dir: string, existing: string | undefined): string => {
4
+ const base = existing === undefined || existing.length === 0 ? XDG_DATA_DIRS_DEFAULT : existing;
5
+ if (base.split(":").includes(dir)) return base;
6
+ return `${dir}:${base}`;
7
+ };