@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,9 +1,13 @@
1
1
  import type { Rollup } from "vite";
2
2
 
3
- export const prependBanner = (options: Rollup.OutputOptions, banner: string): Rollup.OutputOptions => {
3
+ const prependBanner = (options: Rollup.OutputOptions, banner: string): Rollup.OutputOptions => {
4
4
  const existing = options.banner;
5
+
5
6
  if (typeof existing === "function") {
6
7
  return { ...options, banner: async (chunk) => `${banner}\n${await existing(chunk)}` };
7
8
  }
9
+
8
10
  return { ...options, banner: existing ? `${banner}\n${existing}` : banner };
9
11
  };
12
+
13
+ export { prependBanner };
@@ -1,39 +1,70 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
- export const DATA_IMPORT_PREFIX = "#data";
4
+ const DATA_IMPORT_PREFIX = "#data";
5
+ const DATA_IMPORT_KEY = `${DATA_IMPORT_PREFIX}/*`;
6
+ const CONDITION_PRIORITY = ["default", "import", "node"] as const;
7
+ const DATA_TARGET_PATTERN = /^\.?\/?(.+?)\/\*$/;
5
8
 
6
- const DATA_IMPORT_KEY: string = `${DATA_IMPORT_PREFIX}/*`;
9
+ const isConditionMap = (entry: unknown): entry is Record<string, unknown> =>
10
+ entry !== null && typeof entry === "object" && !Array.isArray(entry);
7
11
 
8
- const CONDITION_PRIORITY = ["default", "import", "node"] as const;
12
+ const conditionTarget = (conditions: Record<string, unknown>): string | null => {
13
+ for (const condition of CONDITION_PRIORITY) {
14
+ const value = conditions[condition];
9
15
 
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;
16
+ if (typeof value === "string") {
17
+ return value;
17
18
  }
18
19
  }
20
+
19
21
  return null;
20
22
  };
21
23
 
22
- const DATA_TARGET_PATTERN = /^\.?\/?(.+?)\/\*$/;
24
+ const targetString = (entry: unknown): string | null => {
25
+ if (typeof entry === "string") {
26
+ return entry;
27
+ }
28
+
29
+ if (isConditionMap(entry)) {
30
+ return conditionTarget(entry);
31
+ }
32
+
33
+ return null;
34
+ };
23
35
 
24
36
  const directoryFromTarget = (target: string): string | null => {
25
37
  const match = DATA_TARGET_PATTERN.exec(target);
38
+
26
39
  return match?.[1] ?? null;
27
40
  };
28
41
 
29
- export const resolveDataDir = (root: string): string | null => {
30
- let manifest: { imports?: Record<string, unknown> };
42
+ const resolveDataDir = (root: string): string | null => {
43
+ let manifest: unknown;
44
+
31
45
  try {
32
- manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf-8"));
46
+ manifest = JSON.parse(readFileSync(join(root, "package.json"), "utf8"));
33
47
  } catch {
34
48
  return null;
35
49
  }
36
- const target = targetString(manifest.imports?.[DATA_IMPORT_KEY]);
37
- if (target === null) return null;
50
+
51
+ if (!isConditionMap(manifest)) {
52
+ return null;
53
+ }
54
+
55
+ const imports = manifest.imports;
56
+
57
+ if (!isConditionMap(imports)) {
58
+ return null;
59
+ }
60
+
61
+ const target = targetString(imports[DATA_IMPORT_KEY]);
62
+
63
+ if (target === null) {
64
+ return null;
65
+ }
66
+
38
67
  return directoryFromTarget(target);
39
68
  };
69
+
70
+ export { DATA_IMPORT_PREFIX, resolveDataDir };
@@ -1,19 +1,20 @@
1
+ import type { PositionalArgDef, StringArgDef } from "citty";
1
2
  import { existsSync } from "node:fs";
2
3
  import { resolve } from "node:path";
3
- import type { PositionalArgDef, StringArgDef } from "citty";
4
4
 
5
5
  const DEFAULT_ENTRY_BASE = "src/index";
6
- const DEFAULT_ENTRY_EXTENSIONS = [".tsx", ".jsx", ".ts", ".js"];
6
+ const DEFAULT_ENTRY_EXTENSION = ".tsx";
7
+ const DEFAULT_ENTRY_EXTENSIONS = [DEFAULT_ENTRY_EXTENSION, ".jsx", ".ts", ".js"];
7
8
  const DEFAULT_ENTRY = `${DEFAULT_ENTRY_BASE}{${DEFAULT_ENTRY_EXTENSIONS.join(",")}}`;
8
9
 
9
- export const cwdArg: { cwd: StringArgDef } = {
10
+ const cwdArg: { cwd: StringArgDef } = {
10
11
  cwd: {
11
12
  type: "string",
12
13
  description: "Project root (default: current working directory)",
13
14
  },
14
15
  };
15
16
 
16
- export const entryArg: { entry: PositionalArgDef; cwd: StringArgDef } = {
17
+ const entryArg: { entry: PositionalArgDef; cwd: StringArgDef } = {
17
18
  entry: {
18
19
  type: "positional",
19
20
  description: `Entry file (default: ${DEFAULT_ENTRY})`,
@@ -22,17 +23,24 @@ export const entryArg: { entry: PositionalArgDef; cwd: StringArgDef } = {
22
23
  ...cwdArg,
23
24
  };
24
25
 
25
- export const resolveCwd = (args: { cwd?: string }): string => (args.cwd ? resolve(args.cwd) : process.cwd());
26
+ const resolveCwd = (args: { cwd?: string }): string => (args.cwd ? resolve(args.cwd) : process.cwd());
26
27
 
27
28
  const resolveDefaultEntry = (cwd: string): string => {
28
29
  for (const extension of DEFAULT_ENTRY_EXTENSIONS) {
29
30
  const candidate = resolve(cwd, `${DEFAULT_ENTRY_BASE}${extension}`);
30
- if (existsSync(candidate)) return candidate;
31
+
32
+ if (existsSync(candidate)) {
33
+ return candidate;
34
+ }
31
35
  }
32
- return resolve(cwd, `${DEFAULT_ENTRY_BASE}${DEFAULT_ENTRY_EXTENSIONS[0]}`);
36
+
37
+ return resolve(cwd, `${DEFAULT_ENTRY_BASE}${DEFAULT_ENTRY_EXTENSION}`);
33
38
  };
34
39
 
35
- export const resolveEntry = (args: { entry?: string; cwd?: string }): { cwd: string; entry: string } => {
40
+ const resolveEntry = (args: { entry?: string; cwd?: string }): { cwd: string; entry: string } => {
36
41
  const cwd = resolveCwd(args);
42
+
37
43
  return { cwd, entry: args.entry ? resolve(cwd, args.entry) : resolveDefaultEntry(cwd) };
38
44
  };
45
+
46
+ export { cwdArg, entryArg, resolveCwd, resolveEntry };
@@ -1,28 +1,36 @@
1
- import { logger } from "@gtkx/utils";
2
1
  import type { ArgsDef, CommandDef } from "citty";
2
+ import { logger } from "@gtkx/utils";
3
3
 
4
4
  const ERROR_EXIT_CODE = 1;
5
5
 
6
- export const printError = (cause: unknown): never => {
6
+ const printError = (cause: unknown): never => {
7
7
  const message = cause instanceof Error ? cause.message : String(cause);
8
8
  logger.error(message);
9
+
9
10
  if (cause instanceof Error && cause.stack !== undefined) {
10
11
  logger.debug(cause.stack);
11
12
  }
13
+
12
14
  process.exit(ERROR_EXIT_CODE);
13
15
  };
14
16
 
15
- export const withErrorBoundary = <T extends ArgsDef>(command: CommandDef<T>): CommandDef<T> => {
17
+ const withErrorBoundary = <T extends ArgsDef>(command: CommandDef<T>): CommandDef<T> => {
16
18
  const run = command.run;
17
- if (run === undefined) return command;
19
+
20
+ if (run === undefined) {
21
+ return command;
22
+ }
23
+
18
24
  return {
19
25
  ...command,
20
- run: async (context) => {
26
+ run: async (context): Promise<unknown> => {
21
27
  try {
22
28
  return await run(context);
23
- } catch (cause) {
24
- printError(cause);
29
+ } catch (error) {
30
+ return printError(error);
25
31
  }
26
32
  },
27
33
  };
28
34
  };
35
+
36
+ export { printError, withErrorBoundary };
@@ -1,17 +1,23 @@
1
1
  import { existsSync, readdirSync } from "node:fs";
2
2
  import { join, relative } from "node:path";
3
3
 
4
- export type ListedFile = {
4
+ type ListedFile = {
5
5
  absPath: string;
6
6
  rel: string;
7
7
  };
8
8
 
9
- export const listFilesRecursive = (dir: string, predicate?: (name: string) => boolean): ListedFile[] => {
10
- if (!existsSync(dir)) return [];
9
+ const listFilesRecursive = (dir: string, shouldInclude?: (name: string) => boolean): ListedFile[] => {
10
+ if (!existsSync(dir)) {
11
+ return [];
12
+ }
13
+
11
14
  return readdirSync(dir, { recursive: true, withFileTypes: true })
12
- .filter((entry) => entry.isFile() && (predicate === undefined || predicate(entry.name)))
15
+ .filter((entry) => entry.isFile() && (shouldInclude === undefined || shouldInclude(entry.name)))
13
16
  .map((entry) => {
14
17
  const absPath = join(entry.parentPath, entry.name);
18
+
15
19
  return { absPath, rel: relative(dir, absPath) };
16
20
  });
17
21
  };
22
+
23
+ export { listFilesRecursive, type ListedFile };
@@ -1,21 +1,37 @@
1
1
  import { existsSync, statSync } from "node:fs";
2
2
  import { delimiter, join } from "node:path";
3
3
 
4
- export const resolveCliTool = (() => {
5
- const cache = new Map<string, string>();
4
+ const resolveCliTool = (() => {
5
+ const cache: Map<string, string> = new Map();
6
6
 
7
7
  return (executable: string): string => {
8
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
- }
9
+
10
+ if (cached) {
11
+ return cached;
18
12
  }
19
- throw new Error(`${executable} not found in PATH`);
13
+
14
+ const resolved = findInPath(executable);
15
+ cache.set(executable, resolved);
16
+
17
+ return resolved;
20
18
  };
21
19
  })();
20
+
21
+ const isExecutableFile = (candidate: string): boolean => existsSync(candidate) && statSync(candidate).isFile();
22
+
23
+ const findInPath = (executable: string): string => {
24
+ const dirs = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
25
+
26
+ for (const dir of dirs) {
27
+ const candidate = join(dir, executable);
28
+
29
+ if (isExecutableFile(candidate)) {
30
+ return candidate;
31
+ }
32
+ }
33
+
34
+ throw new Error(`${executable} not found in PATH`);
35
+ };
36
+
37
+ export { resolveCliTool };
@@ -2,15 +2,18 @@ import { mkdtempSync, rmSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
 
5
- export const removeTempDir = (dir: string): void => {
5
+ const removeTempDir = (dir: string): void => {
6
6
  rmSync(dir, { recursive: true, force: true, maxRetries: 5 });
7
7
  };
8
8
 
9
- export const withStagingDir = <T>(prefix: string, produce: (dir: string) => T): T => {
9
+ const withStagingDir = <T>(prefix: string, produce: (dir: string) => T): T => {
10
10
  const dir = mkdtempSync(join(tmpdir(), `gtkx-${prefix}-`));
11
+
11
12
  try {
12
13
  return produce(dir);
13
14
  } finally {
14
15
  removeTempDir(dir);
15
16
  }
16
17
  };
18
+
19
+ export { removeTempDir, withStagingDir };
@@ -1,7 +1,13 @@
1
1
  const XDG_DATA_DIRS_DEFAULT = "/usr/local/share:/usr/share";
2
2
 
3
- export const prependXdgDataDir = (dir: string, existing: string | undefined): string => {
3
+ const prependXdgDataDir = (dir: string, existing: string | undefined): string => {
4
4
  const base = existing === undefined || existing.length === 0 ? XDG_DATA_DIRS_DEFAULT : existing;
5
- if (base.split(":").includes(dir)) return base;
5
+
6
+ if (base.split(":").includes(dir)) {
7
+ return base;
8
+ }
9
+
6
10
  return `${dir}:${base}`;
7
11
  };
12
+
13
+ export { prependXdgDataDir };
package/src/mcp/client.ts CHANGED
@@ -1,20 +1,58 @@
1
- import * as net from "node:net";
2
1
  import * as Gio from "@gtkx/gi/gio";
3
2
  import * as Gtk from "@gtkx/gi/gtk";
4
3
  import { DEFAULT_SOCKET_PATH, ErrorCode, ProtocolConnection, ProtocolError, type Request } from "@gtkx/mcp/internal";
5
4
  import { error, errorMessage, info, normalizeError, warn } from "@gtkx/utils";
5
+ import * as net from "node:net";
6
6
  import { dispatch } from "./handlers.js";
7
7
  import { WidgetRegistry } from "./widget-registry.js";
8
8
 
9
- export type McpClientOptions = {
9
+ type McpClientOptions = {
10
10
  socketPath?: string;
11
11
  applicationId: string;
12
12
  };
13
13
 
14
+ type ConnectCallbacks = {
15
+ onSuccess?: (() => void) | undefined;
16
+ onError?: ((error: Error) => void) | undefined;
17
+ };
18
+
19
+ type ConnectSettler = {
20
+ succeed: () => void;
21
+ fail: (error: Error) => void;
22
+ };
23
+
14
24
  const RECONNECT_DELAY_MS = 2000;
15
- const REGISTER_TIMEOUT_MS = 30000;
25
+ const REGISTER_TIMEOUT_MS = 30_000;
26
+ const DISCONNECT_ERROR_CODES: Set<string> = new Set(["ENOENT", "ECONNREFUSED", "EPIPE", "ECONNRESET"]);
27
+
28
+ const toResponseError = (error: unknown): { code: number; message: string; data?: unknown } =>
29
+ error instanceof ProtocolError
30
+ ? error.toErrorObject()
31
+ : { code: ErrorCode.INTERNAL_ERROR, message: errorMessage(error) };
32
+
33
+ const connectSettler = (callbacks: ConnectCallbacks): ConnectSettler => {
34
+ let isSettled = false;
35
+
36
+ const settle = (notify: () => void): void => {
37
+ if (isSettled) {
38
+ return;
39
+ }
40
+
41
+ isSettled = true;
42
+ notify();
43
+ };
44
+
45
+ return {
46
+ succeed: () => {
47
+ settle(() => callbacks.onSuccess?.());
48
+ },
49
+ fail: (failure) => {
50
+ settle(() => callbacks.onError?.(failure));
51
+ },
52
+ };
53
+ };
16
54
 
17
- export class McpClient {
55
+ class McpClient {
18
56
  private socket: net.Socket | null = null;
19
57
  private connection: ProtocolConnection | null = null;
20
58
  private socketPath: string;
@@ -30,92 +68,61 @@ export class McpClient {
30
68
  this.applicationId = options.applicationId;
31
69
  }
32
70
 
33
- async connect(): Promise<void> {
34
- return new Promise<void>((resolve, reject) => {
35
- this.pendingConnectReject = reject;
36
- this.attemptConnect(
37
- () => {
38
- this.pendingConnectReject = null;
39
- resolve();
40
- },
41
- (error) => {
42
- this.pendingConnectReject = null;
43
- reject(error);
44
- },
45
- );
46
- });
71
+ private handleClose(): void {
72
+ if (this.hasConnected) {
73
+ info("Disconnected from MCP server");
74
+ this.hasConnected = false;
75
+ }
76
+
77
+ this.socket = null;
78
+ this.connection = null;
79
+ this.scheduleReconnect();
47
80
  }
48
81
 
49
- disconnect(): void {
50
- this.isStopping = true;
51
- if (this.pendingConnectReject) {
52
- this.pendingConnectReject(new Error("Client disconnected before connection registered"));
53
- this.pendingConnectReject = null;
54
- }
55
- if (this.reconnectTimer) {
56
- clearTimeout(this.reconnectTimer);
57
- this.reconnectTimer = null;
58
- }
59
- this.connection?.rejectPending(new Error("Client disconnected"));
60
- if (this.socket) {
61
- this.connection?.write({ id: crypto.randomUUID(), method: "app.unregister" });
62
- this.socket.destroy();
63
- this.socket = null;
82
+ private handleSocketError(socketError: Error): void {
83
+ const code = (socketError as NodeJS.ErrnoException).code;
84
+
85
+ if (code !== undefined && DISCONNECT_ERROR_CODES.has(code)) {
86
+ this.scheduleReconnect();
87
+ } else {
88
+ error("Socket error:", socketError.message);
64
89
  }
65
- this.connection = null;
66
- this.hasConnected = false;
67
90
  }
68
91
 
69
- private attemptConnect(onSuccess?: () => void, onError?: (error: Error) => void): void {
70
- let settled = false;
92
+ private async registerWithServer(settle: ConnectSettler): Promise<void> {
93
+ try {
94
+ await this.register();
95
+ info("Registered with MCP server");
96
+ settle.succeed();
97
+ } catch (registerError) {
98
+ error("Failed to register with MCP server:", errorMessage(registerError));
99
+ settle.fail(normalizeError(registerError));
100
+ }
101
+ }
71
102
 
72
- const settle = <T extends unknown[]>(callback: ((...args: T) => void) | undefined, ...args: T) => {
73
- if (settled) return;
74
- settled = true;
75
- callback?.(...args);
76
- };
103
+ private attemptConnect(callbacks: ConnectCallbacks = {}): void {
104
+ const settle = connectSettler(callbacks);
77
105
 
78
106
  const socket = net.createConnection(this.socketPath, () => {
79
107
  info(`Connected to MCP server at ${this.socketPath}`);
80
108
  this.hasConnected = true;
81
- this.register()
82
- .then(() => {
83
- info("Registered with MCP server");
84
- settle(onSuccess);
85
- })
86
- .catch((cause) => {
87
- error("Failed to register with MCP server:", cause.message);
88
- settle(onError, normalizeError(cause));
89
- });
109
+ void this.registerWithServer(settle);
90
110
  });
91
111
 
92
112
  const connection = ProtocolConnection.fromSocket(socket, {
93
113
  onClose: () => {
94
- if (this.hasConnected) {
95
- info("Disconnected from MCP server");
96
- this.hasConnected = false;
97
- }
98
- this.socket = null;
99
- this.connection = null;
100
- this.scheduleReconnect();
114
+ this.handleClose();
101
115
  },
102
116
  onError: (socketError) => {
103
- const code = (socketError as NodeJS.ErrnoException).code;
104
- const isDisconnectError =
105
- code === "ENOENT" || code === "ECONNREFUSED" || code === "EPIPE" || code === "ECONNRESET";
106
- if (isDisconnectError) {
107
- this.scheduleReconnect();
108
- } else {
109
- error("Socket error:", socketError.message);
110
- }
111
- settle(onError, socketError);
117
+ this.handleSocketError(socketError);
118
+ settle.fail(socketError);
112
119
  },
113
120
  });
121
+
114
122
  connection.on("request", (request) => {
115
- this.handleRequest(request).catch((cause) => {
116
- error("Error handling request:", cause);
117
- });
123
+ void this.handleRequest(request);
118
124
  });
125
+
119
126
  connection.on("invalid", ({ error: parseError }) => {
120
127
  warn(`Received invalid JSON from MCP server: ${parseError.message}`);
121
128
  });
@@ -125,7 +132,10 @@ export class McpClient {
125
132
  }
126
133
 
127
134
  private scheduleReconnect(): void {
128
- if (this.reconnectTimer || this.isStopping) return;
135
+ if (this.reconnectTimer || this.isStopping) {
136
+ return;
137
+ }
138
+
129
139
  this.reconnectTimer = setTimeout(() => {
130
140
  this.reconnectTimer = null;
131
141
  this.attemptConnect();
@@ -136,6 +146,7 @@ export class McpClient {
136
146
  if (!this.connection) {
137
147
  return Promise.reject(new Error("Connection not initialized"));
138
148
  }
149
+
139
150
  return this.connection.send(
140
151
  "app.register",
141
152
  {
@@ -147,31 +158,78 @@ export class McpClient {
147
158
  );
148
159
  }
149
160
 
150
- private async handleRequest(request: Request): Promise<void> {
161
+ private async respondToRequest(request: Request): Promise<void> {
151
162
  const { id, method, params } = request;
152
163
  const connection = this.connection;
153
- if (!connection) return;
164
+
165
+ if (!connection) {
166
+ return;
167
+ }
154
168
 
155
169
  try {
156
170
  const defaultApp = Gio.Application.getDefault();
171
+
157
172
  if (!(defaultApp instanceof Gtk.Application)) {
158
173
  throw new TypeError("Application not initialized");
159
174
  }
175
+
160
176
  this.registry.refresh();
161
177
  const result = await dispatch(method, params, { app: defaultApp, registry: this.registry });
162
178
  connection.write({ id, result });
163
- } catch (error) {
164
- if (error instanceof ProtocolError) {
165
- connection.write({ id, error: error.toErrorObject() });
166
- } else {
167
- connection.write({
168
- id,
169
- error: {
170
- code: ErrorCode.INTERNAL_ERROR,
171
- message: errorMessage(error),
172
- },
173
- });
174
- }
179
+ } catch (dispatchError) {
180
+ connection.write({ id, error: toResponseError(dispatchError) });
175
181
  }
176
182
  }
183
+
184
+ private async handleRequest(request: Request): Promise<void> {
185
+ try {
186
+ await this.respondToRequest(request);
187
+ } catch (requestError) {
188
+ error("Error handling request:", requestError);
189
+ }
190
+ }
191
+
192
+ async connect(): Promise<void> {
193
+ return new Promise<void>((resolve, reject) => {
194
+ this.pendingConnectReject = reject;
195
+
196
+ this.attemptConnect({
197
+ onSuccess: () => {
198
+ this.pendingConnectReject = null;
199
+ resolve();
200
+ },
201
+ onError: (connectError) => {
202
+ this.pendingConnectReject = null;
203
+ reject(connectError);
204
+ },
205
+ });
206
+ });
207
+ }
208
+
209
+ disconnect(): void {
210
+ this.isStopping = true;
211
+
212
+ if (this.pendingConnectReject) {
213
+ this.pendingConnectReject(new Error("Client disconnected before connection registered"));
214
+ this.pendingConnectReject = null;
215
+ }
216
+
217
+ if (this.reconnectTimer) {
218
+ clearTimeout(this.reconnectTimer);
219
+ this.reconnectTimer = null;
220
+ }
221
+
222
+ this.connection?.rejectPending(new Error("Client disconnected"));
223
+
224
+ if (this.socket) {
225
+ this.connection?.write({ id: crypto.randomUUID(), method: "app.unregister" });
226
+ this.socket.destroy();
227
+ this.socket = null;
228
+ }
229
+
230
+ this.connection = null;
231
+ this.hasConnected = false;
232
+ }
177
233
  }
234
+
235
+ export { McpClient, type McpClientOptions };