@gtkx/codegen 1.0.0 → 1.2.0

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 (347) hide show
  1. package/README.md +1 -1
  2. package/dist/analysis/descriptor-render.d.ts +12 -15
  3. package/dist/analysis/descriptor-render.d.ts.map +1 -1
  4. package/dist/analysis/descriptor-render.js +59 -38
  5. package/dist/analysis/descriptor-render.js.map +1 -1
  6. package/dist/analysis/descriptor.d.ts +25 -10
  7. package/dist/analysis/descriptor.d.ts.map +1 -1
  8. package/dist/analysis/descriptor.js +37 -9
  9. package/dist/analysis/descriptor.js.map +1 -1
  10. package/dist/analysis/inheritance.d.ts +4 -2
  11. package/dist/analysis/inheritance.d.ts.map +1 -1
  12. package/dist/analysis/inheritance.js +80 -38
  13. package/dist/analysis/inheritance.js.map +1 -1
  14. package/dist/analysis/interface-conflicts.d.ts +25 -0
  15. package/dist/analysis/interface-conflicts.d.ts.map +1 -0
  16. package/dist/analysis/interface-conflicts.js +139 -0
  17. package/dist/analysis/interface-conflicts.js.map +1 -0
  18. package/dist/analysis/param-capability.d.ts.map +1 -1
  19. package/dist/analysis/param-capability.js +3 -4
  20. package/dist/analysis/param-capability.js.map +1 -1
  21. package/dist/analysis/param-structure.js +2 -2
  22. package/dist/analysis/param-structure.js.map +1 -1
  23. package/dist/analysis/ts-type.d.ts +3 -2
  24. package/dist/analysis/ts-type.d.ts.map +1 -1
  25. package/dist/analysis/ts-type.js +26 -26
  26. package/dist/analysis/ts-type.js.map +1 -1
  27. package/dist/analysis/type-key.d.ts +5 -0
  28. package/dist/analysis/type-key.d.ts.map +1 -0
  29. package/dist/analysis/type-key.js +9 -0
  30. package/dist/analysis/type-key.js.map +1 -0
  31. package/dist/analysis/type-shape.d.ts +3 -2
  32. package/dist/analysis/type-shape.d.ts.map +1 -1
  33. package/dist/analysis/type-shape.js +2 -1
  34. package/dist/analysis/type-shape.js.map +1 -1
  35. package/dist/compile.d.ts +14 -6
  36. package/dist/compile.d.ts.map +1 -1
  37. package/dist/compile.js +125 -24
  38. package/dist/compile.js.map +1 -1
  39. package/dist/docs/api-reference.d.ts +2 -0
  40. package/dist/docs/api-reference.d.ts.map +1 -1
  41. package/dist/docs/api-reference.js +11 -10
  42. package/dist/docs/api-reference.js.map +1 -1
  43. package/dist/docs/element-page.d.ts.map +1 -1
  44. package/dist/docs/element-page.js +5 -3
  45. package/dist/docs/element-page.js.map +1 -1
  46. package/dist/docs/pipeline.d.ts +1 -0
  47. package/dist/docs/pipeline.d.ts.map +1 -1
  48. package/dist/docs/pipeline.js +93 -19
  49. package/dist/docs/pipeline.js.map +1 -1
  50. package/dist/docs/render.d.ts +2 -1
  51. package/dist/docs/render.d.ts.map +1 -1
  52. package/dist/docs/render.js +5 -4
  53. package/dist/docs/render.js.map +1 -1
  54. package/dist/docs/symbol-page.d.ts.map +1 -1
  55. package/dist/docs/symbol-page.js +10 -9
  56. package/dist/docs/symbol-page.js.map +1 -1
  57. package/dist/fingerprint.d.ts +20 -3
  58. package/dist/fingerprint.d.ts.map +1 -1
  59. package/dist/fingerprint.js +56 -37
  60. package/dist/fingerprint.js.map +1 -1
  61. package/dist/gi.d.ts +1 -0
  62. package/dist/gi.d.ts.map +1 -1
  63. package/dist/gi.js +8 -2
  64. package/dist/gi.js.map +1 -1
  65. package/dist/gir/ancestry.d.ts +7 -1
  66. package/dist/gir/ancestry.d.ts.map +1 -1
  67. package/dist/gir/ancestry.js +9 -1
  68. package/dist/gir/ancestry.js.map +1 -1
  69. package/dist/gir/annotations.d.ts +1 -2
  70. package/dist/gir/annotations.d.ts.map +1 -1
  71. package/dist/gir/annotations.js +1 -1
  72. package/dist/gir/annotations.js.map +1 -1
  73. package/dist/gir/cursor-overrides.d.ts +8 -0
  74. package/dist/gir/cursor-overrides.d.ts.map +1 -0
  75. package/dist/gir/cursor-overrides.js +6 -0
  76. package/dist/gir/cursor-overrides.js.map +1 -0
  77. package/dist/gir/emittable.d.ts +7 -0
  78. package/dist/gir/emittable.d.ts.map +1 -0
  79. package/dist/gir/emittable.js +3 -0
  80. package/dist/gir/emittable.js.map +1 -0
  81. package/dist/gir/function.d.ts.map +1 -1
  82. package/dist/gir/function.js +28 -2
  83. package/dist/gir/function.js.map +1 -1
  84. package/dist/gir/gir-path.d.ts +1 -1
  85. package/dist/gir/gir-path.js +1 -1
  86. package/dist/gir/gir-path.js.map +1 -1
  87. package/dist/gir/library.d.ts +5 -1
  88. package/dist/gir/library.d.ts.map +1 -1
  89. package/dist/gir/library.js +9 -2
  90. package/dist/gir/library.js.map +1 -1
  91. package/dist/gir/namespace.d.ts +5 -1
  92. package/dist/gir/namespace.d.ts.map +1 -1
  93. package/dist/gir/namespace.js +4 -2
  94. package/dist/gir/namespace.js.map +1 -1
  95. package/dist/gir/parameter.d.ts +13 -1
  96. package/dist/gir/parameter.d.ts.map +1 -1
  97. package/dist/gir/parameter.js +1 -0
  98. package/dist/gir/parameter.js.map +1 -1
  99. package/dist/gir/parse.d.ts.map +1 -1
  100. package/dist/gir/parse.js +6 -6
  101. package/dist/gir/parse.js.map +1 -1
  102. package/dist/gir/primitives.js +1 -1
  103. package/dist/gir/primitives.js.map +1 -1
  104. package/dist/guards.d.ts +16 -0
  105. package/dist/guards.d.ts.map +1 -0
  106. package/dist/guards.js +11 -0
  107. package/dist/guards.js.map +1 -0
  108. package/dist/internal.d.ts +2 -0
  109. package/dist/internal.d.ts.map +1 -1
  110. package/dist/internal.js +2 -0
  111. package/dist/internal.js.map +1 -1
  112. package/dist/json.d.ts +3 -0
  113. package/dist/json.d.ts.map +1 -0
  114. package/dist/json.js +11 -0
  115. package/dist/json.js.map +1 -0
  116. package/dist/khronos/args.d.ts.map +1 -1
  117. package/dist/khronos/args.js +8 -6
  118. package/dist/khronos/args.js.map +1 -1
  119. package/dist/khronos/modules.d.ts.map +1 -1
  120. package/dist/khronos/modules.js +22 -7
  121. package/dist/khronos/modules.js.map +1 -1
  122. package/dist/khronos/parse.d.ts.map +1 -1
  123. package/dist/khronos/parse.js +3 -4
  124. package/dist/khronos/parse.js.map +1 -1
  125. package/dist/khronos/pipeline.d.ts +1 -1
  126. package/dist/khronos/pipeline.d.ts.map +1 -1
  127. package/dist/khronos/pipeline.js.map +1 -1
  128. package/dist/khronos/plan.d.ts +1 -7
  129. package/dist/khronos/plan.d.ts.map +1 -1
  130. package/dist/khronos/plan.js +1 -1
  131. package/dist/khronos/plan.js.map +1 -1
  132. package/dist/khronos/render.d.ts.map +1 -1
  133. package/dist/khronos/render.js +8 -4
  134. package/dist/khronos/render.js.map +1 -1
  135. package/dist/runner.d.ts +7 -1
  136. package/dist/runner.d.ts.map +1 -1
  137. package/dist/runner.js +21 -3
  138. package/dist/runner.js.map +1 -1
  139. package/dist/staging.d.ts +5 -0
  140. package/dist/staging.d.ts.map +1 -0
  141. package/dist/staging.js +57 -0
  142. package/dist/staging.js.map +1 -0
  143. package/dist/store/compile-store.d.ts +0 -1
  144. package/dist/store/compile-store.d.ts.map +1 -1
  145. package/dist/store/compile-store.js +7 -32
  146. package/dist/store/compile-store.js.map +1 -1
  147. package/dist/store/gi/callables.d.ts +6 -3
  148. package/dist/store/gi/callables.d.ts.map +1 -1
  149. package/dist/store/gi/callables.js +40 -30
  150. package/dist/store/gi/callables.js.map +1 -1
  151. package/dist/store/gi/callback.d.ts.map +1 -1
  152. package/dist/store/gi/callback.js +9 -5
  153. package/dist/store/gi/callback.js.map +1 -1
  154. package/dist/store/gi/class.d.ts.map +1 -1
  155. package/dist/store/gi/class.js +104 -44
  156. package/dist/store/gi/class.js.map +1 -1
  157. package/dist/store/gi/companion.d.ts.map +1 -1
  158. package/dist/store/gi/companion.js +6 -6
  159. package/dist/store/gi/companion.js.map +1 -1
  160. package/dist/store/gi/constant.d.ts.map +1 -1
  161. package/dist/store/gi/constant.js +11 -3
  162. package/dist/store/gi/constant.js.map +1 -1
  163. package/dist/store/gi/constructor-props.js +1 -1
  164. package/dist/store/gi/constructor-props.js.map +1 -1
  165. package/dist/store/gi/enum.d.ts.map +1 -1
  166. package/dist/store/gi/enum.js +29 -12
  167. package/dist/store/gi/enum.js.map +1 -1
  168. package/dist/store/gi/function.d.ts +1 -3
  169. package/dist/store/gi/function.d.ts.map +1 -1
  170. package/dist/store/gi/function.js +24 -18
  171. package/dist/store/gi/function.js.map +1 -1
  172. package/dist/store/gi/gtype-binding.d.ts.map +1 -1
  173. package/dist/store/gi/gtype-binding.js +4 -1
  174. package/dist/store/gi/gtype-binding.js.map +1 -1
  175. package/dist/store/gi/interface.d.ts.map +1 -1
  176. package/dist/store/gi/interface.js +34 -19
  177. package/dist/store/gi/interface.js.map +1 -1
  178. package/dist/store/gi/method.d.ts +7 -6
  179. package/dist/store/gi/method.d.ts.map +1 -1
  180. package/dist/store/gi/method.js +13 -24
  181. package/dist/store/gi/method.js.map +1 -1
  182. package/dist/store/gi/param-marshal.d.ts +3 -2
  183. package/dist/store/gi/param-marshal.d.ts.map +1 -1
  184. package/dist/store/gi/param-marshal.js +50 -5
  185. package/dist/store/gi/param-marshal.js.map +1 -1
  186. package/dist/store/gi/pipeline.d.ts.map +1 -1
  187. package/dist/store/gi/pipeline.js +21 -10
  188. package/dist/store/gi/pipeline.js.map +1 -1
  189. package/dist/store/gi/properties.d.ts +2 -1
  190. package/dist/store/gi/properties.d.ts.map +1 -1
  191. package/dist/store/gi/properties.js +6 -3
  192. package/dist/store/gi/properties.js.map +1 -1
  193. package/dist/store/gi/property-accessor.d.ts +10 -8
  194. package/dist/store/gi/property-accessor.d.ts.map +1 -1
  195. package/dist/store/gi/property-accessor.js +49 -91
  196. package/dist/store/gi/property-accessor.js.map +1 -1
  197. package/dist/store/gi/record-constructor.d.ts +8 -1
  198. package/dist/store/gi/record-constructor.d.ts.map +1 -1
  199. package/dist/store/gi/record-constructor.js +34 -13
  200. package/dist/store/gi/record-constructor.js.map +1 -1
  201. package/dist/store/gi/record-field-accessor.d.ts.map +1 -1
  202. package/dist/store/gi/record-field-accessor.js +20 -4
  203. package/dist/store/gi/record-field-accessor.js.map +1 -1
  204. package/dist/store/gi/record-layout.d.ts +6 -1
  205. package/dist/store/gi/record-layout.d.ts.map +1 -1
  206. package/dist/store/gi/record-layout.js +8 -1
  207. package/dist/store/gi/record-layout.js.map +1 -1
  208. package/dist/store/gi/record.d.ts.map +1 -1
  209. package/dist/store/gi/record.js +19 -8
  210. package/dist/store/gi/record.js.map +1 -1
  211. package/dist/store/gi/registration.js +2 -2
  212. package/dist/store/gi/registration.js.map +1 -1
  213. package/dist/store/gi/return-wrap.js +1 -1
  214. package/dist/store/gi/return-wrap.js.map +1 -1
  215. package/dist/store/gi/signal.d.ts +2 -1
  216. package/dist/store/gi/signal.d.ts.map +1 -1
  217. package/dist/store/gi/signal.js +55 -28
  218. package/dist/store/gi/signal.js.map +1 -1
  219. package/dist/store/gi/value-marshalable.d.ts.map +1 -1
  220. package/dist/store/gi/value-marshalable.js +2 -1
  221. package/dist/store/gi/value-marshalable.js.map +1 -1
  222. package/dist/store/gi/vtable.d.ts +4 -1
  223. package/dist/store/gi/vtable.d.ts.map +1 -1
  224. package/dist/store/gi/vtable.js +60 -17
  225. package/dist/store/gi/vtable.js.map +1 -1
  226. package/dist/store/gi-store.d.ts +1 -0
  227. package/dist/store/gi-store.d.ts.map +1 -1
  228. package/dist/store/gi-store.js +15 -20
  229. package/dist/store/gi-store.js.map +1 -1
  230. package/dist/store/jsx/generated-elements.d.ts +2 -1
  231. package/dist/store/jsx/generated-elements.d.ts.map +1 -1
  232. package/dist/store/jsx/generated-elements.js +13 -7
  233. package/dist/store/jsx/generated-elements.js.map +1 -1
  234. package/dist/store/jsx/jsx.js +3 -3
  235. package/dist/store/jsx/jsx.js.map +1 -1
  236. package/dist/store/jsx/metadata.d.ts.map +1 -1
  237. package/dist/store/jsx/metadata.js +3 -1
  238. package/dist/store/jsx/metadata.js.map +1 -1
  239. package/dist/store/jsx-store.d.ts.map +1 -1
  240. package/dist/store/jsx-store.js +3 -4
  241. package/dist/store/jsx-store.js.map +1 -1
  242. package/dist/store/resolve-store.d.ts +14 -3
  243. package/dist/store/resolve-store.d.ts.map +1 -1
  244. package/dist/store/resolve-store.js +98 -15
  245. package/dist/store/resolve-store.js.map +1 -1
  246. package/dist/store/store-fs.d.ts +9 -7
  247. package/dist/store/store-fs.d.ts.map +1 -1
  248. package/dist/store/store-fs.js +54 -25
  249. package/dist/store/store-fs.js.map +1 -1
  250. package/dist/store/store-links.d.ts +3 -0
  251. package/dist/store/store-links.d.ts.map +1 -0
  252. package/dist/store/store-links.js +39 -0
  253. package/dist/store/store-links.js.map +1 -0
  254. package/dist/writer/comparison-context.d.ts +4 -0
  255. package/dist/writer/comparison-context.d.ts.map +1 -0
  256. package/dist/writer/comparison-context.js +13 -0
  257. package/dist/writer/comparison-context.js.map +1 -0
  258. package/dist/writer/context.d.ts +3 -3
  259. package/dist/writer/context.d.ts.map +1 -1
  260. package/dist/writer/context.js +11 -6
  261. package/dist/writer/context.js.map +1 -1
  262. package/dist/writer/module.d.ts +13 -5
  263. package/dist/writer/module.d.ts.map +1 -1
  264. package/dist/writer/module.js +68 -11
  265. package/dist/writer/module.js.map +1 -1
  266. package/dist/xml.d.ts +8 -1
  267. package/dist/xml.d.ts.map +1 -1
  268. package/dist/xml.js +70 -1
  269. package/dist/xml.js.map +1 -1
  270. package/overrides/gobject/object.ts.ejs +2 -0
  271. package/package.json +14 -3
  272. package/src/analysis/descriptor-render.ts +106 -52
  273. package/src/analysis/descriptor.ts +76 -15
  274. package/src/analysis/inheritance.ts +134 -44
  275. package/src/analysis/interface-conflicts.ts +235 -0
  276. package/src/analysis/param-capability.ts +3 -6
  277. package/src/analysis/param-structure.ts +2 -2
  278. package/src/analysis/ts-type.ts +43 -28
  279. package/src/analysis/type-key.ts +14 -0
  280. package/src/analysis/type-shape.ts +5 -2
  281. package/src/compile.ts +192 -27
  282. package/src/docs/api-reference.ts +13 -10
  283. package/src/docs/element-page.ts +5 -2
  284. package/src/docs/pipeline.ts +140 -24
  285. package/src/docs/render.ts +7 -3
  286. package/src/docs/symbol-page.ts +12 -11
  287. package/src/fingerprint.ts +95 -47
  288. package/src/gi.ts +10 -2
  289. package/src/gir/ancestry.ts +17 -1
  290. package/src/gir/annotations.ts +1 -1
  291. package/src/gir/cursor-overrides.ts +12 -0
  292. package/src/gir/emittable.ts +8 -0
  293. package/src/gir/function.ts +45 -9
  294. package/src/gir/gir-path.ts +1 -1
  295. package/src/gir/library.ts +10 -2
  296. package/src/gir/namespace.ts +8 -2
  297. package/src/gir/parameter.ts +15 -0
  298. package/src/gir/parse.ts +10 -7
  299. package/src/gir/primitives.ts +1 -1
  300. package/src/guards.ts +23 -0
  301. package/src/internal.ts +2 -0
  302. package/src/json.ts +11 -0
  303. package/src/khronos/args.ts +9 -6
  304. package/src/khronos/modules.ts +25 -9
  305. package/src/khronos/parse.ts +3 -4
  306. package/src/khronos/pipeline.ts +1 -1
  307. package/src/khronos/plan.ts +0 -1
  308. package/src/khronos/render.ts +8 -6
  309. package/src/runner.ts +26 -4
  310. package/src/staging.ts +67 -0
  311. package/src/store/compile-store.ts +7 -38
  312. package/src/store/gi/callables.ts +60 -33
  313. package/src/store/gi/callback.ts +9 -9
  314. package/src/store/gi/class.ts +140 -53
  315. package/src/store/gi/companion.ts +7 -6
  316. package/src/store/gi/constant.ts +13 -3
  317. package/src/store/gi/constructor-props.ts +1 -1
  318. package/src/store/gi/enum.ts +36 -26
  319. package/src/store/gi/function.ts +30 -24
  320. package/src/store/gi/gtype-binding.ts +7 -2
  321. package/src/store/gi/interface.ts +39 -20
  322. package/src/store/gi/method.ts +26 -32
  323. package/src/store/gi/param-marshal.ts +77 -6
  324. package/src/store/gi/pipeline.ts +25 -15
  325. package/src/store/gi/properties.ts +8 -4
  326. package/src/store/gi/property-accessor.ts +73 -161
  327. package/src/store/gi/record-constructor.ts +53 -21
  328. package/src/store/gi/record-field-accessor.ts +26 -4
  329. package/src/store/gi/record-layout.ts +11 -1
  330. package/src/store/gi/record.ts +20 -12
  331. package/src/store/gi/registration.ts +2 -2
  332. package/src/store/gi/return-wrap.ts +1 -1
  333. package/src/store/gi/signal.ts +66 -42
  334. package/src/store/gi/value-marshalable.ts +3 -1
  335. package/src/store/gi/vtable.ts +97 -20
  336. package/src/store/gi-store.ts +26 -31
  337. package/src/store/jsx/generated-elements.ts +17 -8
  338. package/src/store/jsx/jsx.ts +3 -3
  339. package/src/store/jsx/metadata.ts +4 -1
  340. package/src/store/jsx-store.ts +3 -4
  341. package/src/store/resolve-store.ts +133 -18
  342. package/src/store/store-fs.ts +82 -35
  343. package/src/store/store-links.ts +49 -0
  344. package/src/writer/comparison-context.ts +18 -0
  345. package/src/writer/context.ts +12 -6
  346. package/src/writer/module.ts +101 -14
  347. package/src/xml.ts +103 -1
@@ -1,7 +1,8 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
1
  import { join } from "node:path";
3
2
  import type { GlibNamedClass } from "./intrinsic-elements.js";
4
3
  import { namespaceDirectory } from "../../gir/namespace.js";
4
+ import { arrayGuard, hasFields, isBoolean, isString } from "../../guards.js";
5
+ import { readJsonFile } from "../../json.js";
5
6
 
6
7
  /** One element the `@gtkx/jsx` store binds. */
7
8
  type GeneratedElement = {
@@ -33,22 +34,30 @@ const collectGeneratedElements = (intrinsicElements: GlibNamedClass[]): Generate
33
34
  const renderGeneratedElements = (elements: GeneratedElement[]): string =>
34
35
  `${JSON.stringify(elements, null, 2)}\n`;
35
36
 
37
+ const isGeneratedElement = (value: unknown): value is GeneratedElement =>
38
+ hasFields<GeneratedElement>(value, {
39
+ namespace: isString,
40
+ directory: isString,
41
+ glibName: isString,
42
+ isMountable: isBoolean,
43
+ });
44
+
45
+ const isGeneratedInventory = (value: unknown): value is GeneratedElement[] =>
46
+ arrayGuard(isGeneratedElement)(value);
47
+
36
48
  /**
37
49
  * Reads the inventory of elements the `@gtkx/jsx` store binds, written into the store by codegen. Answers
38
50
  * "what does this project's JSX layer cover" without loading the store, which resolves `virtual:` specifiers
39
51
  * that only exist inside a Vite or Vitest build.
40
52
  *
41
53
  * @param jsxStoreDir The jsx store directory, as given by `resolveStore(projectRoot).jsx.storeDir`.
42
- * @returns Every bound element, sorted by GLib type name, or an empty array when the store has no inventory.
54
+ * @returns Every bound element, sorted by GLib type name, or an empty array when the store holds no inventory
55
+ * codegen wrote, which covers an absent, unreadable, unparseable and structurally foreign store file alike.
43
56
  */
44
57
  const readGeneratedElements = (jsxStoreDir: string): GeneratedElement[] => {
45
- const path = join(jsxStoreDir, ELEMENTS_FILENAME);
46
-
47
- if (!existsSync(path)) {
48
- return [];
49
- }
58
+ const parsed = readJsonFile(join(jsxStoreDir, ELEMENTS_FILENAME));
50
59
 
51
- return JSON.parse(readFileSync(path, "utf8")) as GeneratedElement[];
60
+ return isGeneratedInventory(parsed) ? parsed : [];
52
61
  };
53
62
 
54
63
  export {
@@ -1,4 +1,4 @@
1
- import { sourceStringLiteral } from "@gtkx/utils";
1
+ import { sanitizeTypeIdentifier, sourceStringLiteral } from "@gtkx/utils";
2
2
  import type { GirClass } from "../../gir/class.js";
3
3
  import type { Library } from "../../gir/library.js";
4
4
  import type { GirNamespace } from "../../gir/namespace.js";
@@ -285,7 +285,7 @@ const renderInterfacePropsBlock = (
285
285
 
286
286
  const extendsClause = prerequisiteExtends.length === 0 ? "" : ` extends ${prerequisiteExtends.join(", ")}`;
287
287
  addGiNamespace(imports, iface.namespace.name, giNamespaceAlias(iface.namespace.name));
288
- const selfDefault = `${giNamespaceAlias(iface.namespace.name)}.${iface.klass.name}`;
288
+ const selfDefault = `${giNamespaceAlias(iface.namespace.name)}.${sanitizeTypeIdentifier(iface.klass.name)}`;
289
289
  const signature = `export interface ${glib}Props<Self = ${selfDefault}>${extendsClause}`;
290
290
  const block = `${getDoc(iface.klass)}${renderBlock(signature, ownerLines.join("\n"))}`;
291
291
 
@@ -324,7 +324,7 @@ const renderPropBlock = (
324
324
  const ownerLines = dedupePropLines(["ref?: Ref<Self | null> | undefined;", ...propLines]);
325
325
  const extendsList = resolveElementExtends(library, entry, context);
326
326
  const extendsClause = extendsList.length === 0 ? "" : ` extends ${extendsList.join(", ")}`;
327
- const selfDefault = `${giNamespaceAlias(entry.namespace.name)}.${entry.klass.name}`;
327
+ const selfDefault = `${giNamespaceAlias(entry.namespace.name)}.${sanitizeTypeIdentifier(entry.klass.name)}`;
328
328
 
329
329
  const block = `${getDoc(entry.klass)}${renderBlock(
330
330
  `export interface ${entry.glibName}Props<Self = ${selfDefault}>${extendsClause}`,
@@ -66,12 +66,15 @@ const generateMetadata = (library: Library): string => {
66
66
 
67
67
  const collectIntrinsicElements = (library: Library): IntrinsicElementEntry[] => {
68
68
  const entries: IntrinsicElementEntry[] = [];
69
+ const seen: Set<string> = new Set();
69
70
 
70
71
  for (const { glibName, klass, namespace } of iterateClassesWithGlibName(library)) {
71
- if (!isIntrinsicElementClass(klass, namespace, library)) {
72
+ if (!isIntrinsicElementClass(klass, namespace, library) || seen.has(glibName)) {
72
73
  continue;
73
74
  }
74
75
 
76
+ seen.add(glibName);
77
+
75
78
  const sources: GirClass[] = [
76
79
  klass,
77
80
  ...implementedInterfaces(klass, namespace, library).map((entry) => entry.klass),
@@ -1,6 +1,6 @@
1
1
  import type { JsxNamespaceFile } from "./jsx/pipeline.js";
2
2
  import type { RawFile } from "./store-fs.js";
3
- import { buildManifest, type StoreOptions, subpathExport, writeStore } from "./store-fs.js";
3
+ import { buildManifest, namespaceBarrel, type StoreOptions, subpathExport, writeStore } from "./store-fs.js";
4
4
 
5
5
  const writeJsxStore = (
6
6
  options: StoreOptions,
@@ -15,8 +15,8 @@ const writeJsxStore = (
15
15
  const files = [{ fileName: "metadata.ts", source: metadata }];
16
16
 
17
17
  for (const { directory, source } of namespaces) {
18
- files.push({ fileName: `${directory}/${directory}.tsx`, source });
19
- exportsMap[`./${directory}`] = subpathExport(`${directory}/${directory}`);
18
+ files.push({ fileName: `${directory}/${directory}.tsx`, source }, namespaceBarrel(directory));
19
+ exportsMap[`./${directory}`] = subpathExport(`${directory}/index`);
20
20
  }
21
21
 
22
22
  writeStore({
@@ -30,7 +30,6 @@ const writeJsxStore = (
30
30
  peerDependencies: { "@gtkx/gi": "*", "@gtkx/react": "*", react: "*" },
31
31
  }),
32
32
  rawFiles,
33
- requiresEnvReference: true,
34
33
  });
35
34
  };
36
35
 
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync, realpathSync } from "node:fs";
2
- import { dirname, join, parse } from "node:path";
2
+ import { dirname, join, parse, relative, sep } from "node:path";
3
3
  import type { StoreOptions } from "./store-fs.js";
4
+ import { sweepStagingDirs } from "../staging.js";
4
5
 
5
6
  /**
6
7
  * Where a project's generated stores live, ready to spread into `runCodegen`. `jsx` is null when the
@@ -15,10 +16,20 @@ type ResolvedStore = {
15
16
  reactSubexports: string[];
16
17
  };
17
18
 
18
- type ResolvedPackage = { dir: string; version: string };
19
+ type ResolvedPackage = { dir: string; nodeModules: string; version: string };
20
+ type StoreConsumer = { name: string; nodeModules: string };
19
21
 
20
22
  const STORE_DIR = ".gtkx";
21
23
  const SCOPE = "@gtkx";
24
+ const STORE_NAMES: string[] = ["gi", "jsx"];
25
+
26
+ const STORE_CONSUMERS: string[] = [
27
+ "@gtkx/cli",
28
+ "@gtkx/components",
29
+ "@gtkx/css",
30
+ "@gtkx/react",
31
+ "@gtkx/testing",
32
+ ];
22
33
 
23
34
  const readManifest = (path: string): { version?: string; exports?: Record<string, unknown> } =>
24
35
  JSON.parse(readFileSync(path, "utf8")) as { version?: string; exports?: Record<string, unknown> };
@@ -28,14 +39,14 @@ const subexportNames = (packageDir: string): string[] =>
28
39
  .filter((key) => key.startsWith("./") && key !== "./package.json")
29
40
  .map((key) => key.slice(2));
30
41
 
31
- const loadPackage = (manifest: string): ResolvedPackage | null => {
42
+ const loadPackage = (manifest: string, nodeModules: string): ResolvedPackage | null => {
32
43
  if (!existsSync(manifest)) {
33
44
  return null;
34
45
  }
35
46
 
36
47
  const real = realpathSync(manifest);
37
48
 
38
- return { dir: dirname(real), version: readManifest(real).version ?? "0.0.0" };
49
+ return { dir: dirname(real), nodeModules, version: readManifest(real).version ?? "0.0.0" };
39
50
  };
40
51
 
41
52
  const nodeModulesChain = function* (projectRoot: string): Generator<string> {
@@ -52,49 +63,153 @@ const nodeModulesChain = function* (projectRoot: string): Generator<string> {
52
63
 
53
64
  const resolvePackage = (projectRoot: string, packageName: string): ResolvedPackage | null => {
54
65
  for (const nodeModules of nodeModulesChain(projectRoot)) {
55
- const found = loadPackage(join(nodeModules, packageName, "package.json"));
66
+ const found = loadPackage(join(nodeModules, packageName, "package.json"), nodeModules);
56
67
 
57
68
  if (found !== null) {
58
69
  return found;
59
70
  }
60
71
  }
61
72
 
62
- return loadPackage(join(projectRoot, "packages", packageName.replace(/^@[^/]+\//, ""), "package.json"));
73
+ const unscoped = packageName.replace(/^@[^/]+\//, "");
74
+
75
+ return loadPackage(join(projectRoot, "packages", unscoped, "package.json"), join(projectRoot, "node_modules"));
76
+ };
77
+
78
+ const isWithin = (parent: string, child: string): boolean => {
79
+ const path = relative(parent, child);
80
+
81
+ return path === "" || (path !== ".." && !path.startsWith(`..${sep}`));
63
82
  };
64
83
 
84
+ const canImport = (fromNodeModules: string, targetNodeModules: string): boolean =>
85
+ isWithin(dirname(targetNodeModules), dirname(fromNodeModules));
86
+
65
87
  const storeOptions = (nodeModules: string, name: string, version: string): StoreOptions => ({
66
88
  storeDir: join(nodeModules, STORE_DIR, name),
67
89
  linkDir: join(nodeModules, SCOPE, name),
68
90
  version,
69
91
  });
70
92
 
93
+ const resolveRuntime = (projectRoot: string): ResolvedPackage => {
94
+ const runtime = resolvePackage(projectRoot, "@gtkx/runtime");
95
+
96
+ if (runtime === null) {
97
+ throw new Error(`Cannot resolve @gtkx/runtime from ${projectRoot}; is it installed?`);
98
+ }
99
+
100
+ return runtime;
101
+ };
102
+
103
+ const storeNodeModules = (runtime: ResolvedPackage, react: ResolvedPackage | null): string => {
104
+ if (react !== null && canImport(react.nodeModules, runtime.nodeModules)) {
105
+ return react.nodeModules;
106
+ }
107
+
108
+ return runtime.nodeModules;
109
+ };
110
+
111
+ const findUnreachableConsumer = (projectRoot: string, nodeModules: string): StoreConsumer | null => {
112
+ for (const name of STORE_CONSUMERS) {
113
+ const consumer = resolvePackage(projectRoot, name);
114
+
115
+ if (consumer !== null && !canImport(consumer.nodeModules, nodeModules)) {
116
+ return { name, nodeModules: consumer.nodeModules };
117
+ }
118
+ }
119
+
120
+ return null;
121
+ };
122
+
123
+ const unreachableConsumerMessage = (consumer: StoreConsumer, nodeModules: string): string =>
124
+ `Cannot write the generated store to ${nodeModules}: ${consumer.name} is installed in ` +
125
+ `${consumer.nodeModules}, above it, so that copy can never import the generated @gtkx/gi. Install every ` +
126
+ "@gtkx package in one node_modules, then run gtkx codegen again.";
127
+
128
+ const checkConsumers = (projectRoot: string, nodeModules: string): void => {
129
+ const consumer = findUnreachableConsumer(projectRoot, nodeModules);
130
+
131
+ if (consumer !== null) {
132
+ throw new Error(unreachableConsumerMessage(consumer, nodeModules));
133
+ }
134
+ };
135
+
136
+ const jsxOptions = (react: ResolvedPackage | null, nodeModules: string): StoreOptions | null =>
137
+ react === null ? null : storeOptions(nodeModules, "jsx", react.version);
138
+
139
+ const getReactSubexports = (react: ResolvedPackage | null): string[] =>
140
+ react === null ? [] : subexportNames(react.dir);
141
+
142
+ const storePaths = (nodeModules: string): string[] =>
143
+ STORE_NAMES.flatMap((name) => [join(nodeModules, STORE_DIR, name), join(nodeModules, SCOPE, name)]);
144
+
145
+ const findStoreNodeModules = (projectRoot: string): string | null => {
146
+ const runtime = resolvePackage(projectRoot, "@gtkx/runtime");
147
+
148
+ if (runtime === null) {
149
+ return null;
150
+ }
151
+
152
+ return storeNodeModules(runtime, resolvePackage(projectRoot, "@gtkx/react"));
153
+ };
154
+
155
+ const getShadowingStorePaths = (projectRoot: string): string[] => {
156
+ const nodeModules = join(projectRoot, "node_modules");
157
+ const anchored = findStoreNodeModules(projectRoot);
158
+
159
+ if (anchored === null || anchored === nodeModules) {
160
+ return [];
161
+ }
162
+
163
+ return storePaths(nodeModules);
164
+ };
165
+
166
+ const stagingRoots = (projectRoot: string): string[] => {
167
+ const nodeModules = join(projectRoot, "node_modules");
168
+ const anchored = findStoreNodeModules(projectRoot);
169
+
170
+ return anchored === null || anchored === nodeModules ? [nodeModules] : [nodeModules, anchored];
171
+ };
172
+
173
+ const sweepProjectStaging = (projectRoot: string): void => {
174
+ for (const nodeModules of stagingRoots(projectRoot)) {
175
+ for (const name of STORE_NAMES) {
176
+ sweepStagingDirs(join(nodeModules, STORE_DIR, name));
177
+ }
178
+ }
179
+ };
180
+
71
181
  /**
72
182
  * Resolves where a project's `@gtkx/gi` and `@gtkx/jsx` stores belong, from the project root alone. The
73
183
  * result supplies every `runCodegen` input except `libraries` and `girPath`, so a caller that has
74
184
  * already resolved those can spread it straight into the call.
75
185
  *
186
+ * Both stores go in one `node_modules`, found by walking the project's `node_modules` chain upwards:
187
+ * the one `@gtkx/react` resolves from, or `@gtkx/runtime`'s when no React is installed. That is the
188
+ * project's own directory when it installs those packages itself, and the workspace root when npm or
189
+ * yarn hoisted them there. Keeping the pair together is what lets the jsx store import the gi store, and
190
+ * anchoring them where the `@gtkx` packages sit is what lets `@gtkx/react`, the CLI, and the project's
191
+ * sources all reach the bindings. A package manager that hoists therefore gives every project sharing
192
+ * that `node_modules` one shared store.
193
+ *
76
194
  * Store versions come from the installed `@gtkx/runtime` and `@gtkx/react`, so a dependency upgrade
77
195
  * invalidates the stores. Pass explicit `gi` or `jsx` options to `runCodegen` to override any of it.
78
196
  *
79
- * @param projectRoot Directory holding the project's `package.json` and `node_modules`.
197
+ * @param projectRoot Directory holding the project's `package.json`, whose `node_modules` chain is walked.
80
198
  * @returns The store locations and the React subexports that shape the jsx store.
81
- * @throws If `@gtkx/runtime` cannot be resolved from the project.
199
+ * @throws If `@gtkx/runtime` cannot be resolved from the project, or a `@gtkx` package that imports the
200
+ * bindings is installed above the `node_modules` the stores would go in, since it could not reach them.
82
201
  */
83
202
  const resolveStore = (projectRoot: string): ResolvedStore => {
84
- const runtime = resolvePackage(projectRoot, "@gtkx/runtime");
85
-
86
- if (runtime === null) {
87
- throw new Error(`Cannot resolve @gtkx/runtime from ${projectRoot}; is it installed?`);
88
- }
89
-
203
+ const runtime = resolveRuntime(projectRoot);
90
204
  const react = resolvePackage(projectRoot, "@gtkx/react");
91
- const nodeModules = join(projectRoot, "node_modules");
205
+ const nodeModules = storeNodeModules(runtime, react);
206
+ checkConsumers(projectRoot, nodeModules);
92
207
 
93
208
  return {
94
209
  gi: storeOptions(nodeModules, "gi", runtime.version),
95
- jsx: react === null ? null : storeOptions(nodeModules, "jsx", react.version),
96
- reactSubexports: react === null ? [] : subexportNames(react.dir),
210
+ jsx: jsxOptions(react, nodeModules),
211
+ reactSubexports: getReactSubexports(react),
97
212
  };
98
213
  };
99
214
 
100
- export { resolveStore, type ResolvedStore };
215
+ export { getShadowingStorePaths, resolveStore, sweepProjectStaging, type ResolvedStore };
@@ -1,5 +1,17 @@
1
- import { chmodSync, existsSync, mkdirSync, mkdtempSync, renameSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
1
+ import { errorMessage } from "@gtkx/utils";
2
+ import {
3
+ chmodSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ realpathSync,
7
+ renameSync,
8
+ rmSync,
9
+ symlinkSync,
10
+ writeFileSync,
11
+ } from "node:fs";
2
12
  import { dirname, join, relative } from "node:path";
13
+ import { keepFailedProject, type SourceModule } from "../compile.js";
14
+ import { createStagingDir } from "../staging.js";
3
15
  import { compileStore } from "./compile-store.js";
4
16
 
5
17
  /** Where one generated store is written and how it is reached. */
@@ -12,10 +24,7 @@ type StoreOptions = {
12
24
  version: string;
13
25
  };
14
26
 
15
- type StoreFile = {
16
- fileName: string;
17
- source: string;
18
- };
27
+ type StoreLink = Pick<StoreOptions, "storeDir" | "linkDir">;
19
28
 
20
29
  type Manifest = {
21
30
  name: string;
@@ -38,19 +47,24 @@ type RawFile = { relativePath: string; content: string };
38
47
  type WriteStoreParams = {
39
48
  storeDir: string;
40
49
  linkDir: string;
41
- files: StoreFile[];
50
+ files: SourceModule[];
42
51
  manifest: Manifest;
43
52
  rawFiles?: RawFile[];
44
- requiresEnvReference?: boolean;
45
53
  };
46
54
 
47
55
  const STORE_DIR_MODE = 0o755;
56
+ const FAILED_STORE_SUFFIX = ".failed";
48
57
 
49
58
  const subpathExport = (stem: string): { types: string; default: string } => ({
50
59
  types: `./${stem}.d.ts`,
51
60
  default: `./${stem}.js`,
52
61
  });
53
62
 
63
+ const namespaceBarrel = (directory: string): { fileName: string; source: string } => ({
64
+ fileName: `${directory}/index.ts`,
65
+ source: `export * from "./${directory}.js";\n`,
66
+ });
67
+
54
68
  const buildManifest = (input: ManifestInput): Manifest => {
55
69
  const manifest: Manifest = {
56
70
  name: input.name,
@@ -69,29 +83,31 @@ const buildManifest = (input: ManifestInput): Manifest => {
69
83
 
70
84
  const writeStore = (params: WriteStoreParams): void => {
71
85
  const tmp = createTempStore(params.storeDir);
86
+ const keepAt = `${params.storeDir}${FAILED_STORE_SUFFIX}`;
72
87
 
73
88
  try {
74
- for (const file of params.files) {
75
- writeSourceFile(tmp, file.fileName, file.source);
76
- }
77
-
78
- compileStore({
79
- storeDir: tmp,
80
- files: params.files,
81
- packageName: params.manifest.name,
82
- ...(params.requiresEnvReference !== undefined && { requiresEnvReference: params.requiresEnvReference }),
83
- });
84
-
85
- writePackageJson(tmp, params.manifest);
86
- const rawFiles = params.rawFiles ?? [];
87
-
88
- for (const raw of rawFiles) {
89
- writeFileSync(join(tmp, raw.relativePath), raw.content);
90
- }
91
-
92
- swapStore(tmp, params.storeDir, params.linkDir);
93
- } finally {
94
- rmSync(tmp, { recursive: true, force: true });
89
+ buildTempStore(tmp, params);
90
+ swapStore(tmp, params.storeDir);
91
+ ensureStoreLink(params);
92
+ } catch (error) {
93
+ throw keepFailedProject({ projectDir: tmp, keepAt, error });
94
+ }
95
+
96
+ rmSync(keepAt, { recursive: true, force: true });
97
+ };
98
+
99
+ const buildTempStore = (tmp: string, params: WriteStoreParams): void => {
100
+ writePackageJson(tmp, params.manifest);
101
+
102
+ for (const file of params.files) {
103
+ writeSourceFile(tmp, file.fileName, file.source);
104
+ }
105
+
106
+ compileStore({ storeDir: tmp, files: params.files, packageName: params.manifest.name });
107
+ const rawFiles = params.rawFiles ?? [];
108
+
109
+ for (const raw of rawFiles) {
110
+ writeFileSync(join(tmp, raw.relativePath), raw.content);
95
111
  }
96
112
  };
97
113
 
@@ -111,7 +127,7 @@ const writePackageJson = (storeDir: string, manifest: Manifest): void => {
111
127
  writeFileSync(join(storeDir, "package.json"), `${JSON.stringify(manifest, null, 2)}\n`);
112
128
  };
113
129
 
114
- const swapStore = (tmp: string, storeDir: string, visibleLink: string): void => {
130
+ const swapStore = (tmp: string, storeDir: string): void => {
115
131
  const previous = `${storeDir}.old`;
116
132
  rmSync(previous, { recursive: true, force: true });
117
133
 
@@ -121,15 +137,46 @@ const swapStore = (tmp: string, storeDir: string, visibleLink: string): void =>
121
137
 
122
138
  renameSync(tmp, storeDir);
123
139
  rmSync(previous, { recursive: true, force: true });
124
- symlinkRelative(visibleLink, storeDir);
125
140
  };
126
141
 
142
+ const isStoreLinked = (link: StoreLink): boolean => {
143
+ try {
144
+ return realpathSync(link.linkDir) === realpathSync(link.storeDir);
145
+ } catch {
146
+ return false;
147
+ }
148
+ };
149
+
150
+ const ensureStoreLink = (link: StoreLink): void => {
151
+ if (!existsSync(join(link.storeDir, "package.json")) || isStoreLinked(link)) {
152
+ return;
153
+ }
154
+
155
+ symlinkRelative(link.linkDir, link.storeDir);
156
+ };
157
+
158
+ const storeWriteMessage = (storeDir: string, error: unknown): string =>
159
+ `Cannot write the generated store to ${storeDir}: ${errorMessage(error)}. ` +
160
+ "Codegen writes the store into the node_modules the @gtkx packages resolve from, " +
161
+ `so ${dirname(storeDir)} has to be writable.`;
162
+
127
163
  const createTempStore = (storeDir: string): string => {
128
- mkdirSync(dirname(storeDir), { recursive: true });
129
- const tmp = mkdtempSync(`${storeDir}.tmp-`);
130
- chmodSync(tmp, STORE_DIR_MODE);
164
+ try {
165
+ const tmp = createStagingDir(storeDir);
166
+ chmodSync(tmp, STORE_DIR_MODE);
131
167
 
132
- return tmp;
168
+ return tmp;
169
+ } catch (error) {
170
+ throw new Error(storeWriteMessage(storeDir, error), { cause: error });
171
+ }
133
172
  };
134
173
 
135
- export { subpathExport, buildManifest, writeStore, type StoreOptions, type RawFile };
174
+ export {
175
+ subpathExport,
176
+ buildManifest,
177
+ ensureStoreLink,
178
+ namespaceBarrel,
179
+ writeStore,
180
+ type StoreOptions,
181
+ type RawFile,
182
+ };
@@ -0,0 +1,49 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { type ResolvedStore, resolveStore } from "./resolve-store.js";
4
+ import { ensureStoreLink } from "./store-fs.js";
5
+
6
+ const resolvedStores: Map<string, ResolvedStore> = new Map();
7
+
8
+ const resolveStoreOrNull = (projectRoot: string): ResolvedStore | null => {
9
+ try {
10
+ return resolveStore(projectRoot);
11
+ } catch {
12
+ return null;
13
+ }
14
+ };
15
+
16
+ const isStoreDirPresent = (store: ResolvedStore): boolean => existsSync(join(store.gi.storeDir, "package.json"));
17
+
18
+ const storeFor = (projectRoot: string): ResolvedStore | null => {
19
+ const cached = resolvedStores.get(projectRoot);
20
+
21
+ if (cached !== undefined && isStoreDirPresent(cached)) {
22
+ return cached;
23
+ }
24
+
25
+ const resolved = resolveStoreOrNull(projectRoot);
26
+ resolvedStores.delete(projectRoot);
27
+
28
+ if (resolved !== null) {
29
+ resolvedStores.set(projectRoot, resolved);
30
+ }
31
+
32
+ return resolved;
33
+ };
34
+
35
+ const ensureStoreLinks = (projectRoot: string): void => {
36
+ const store = storeFor(projectRoot);
37
+
38
+ if (store === null) {
39
+ return;
40
+ }
41
+
42
+ for (const unit of [store.gi, store.jsx]) {
43
+ if (unit !== null) {
44
+ ensureStoreLink(unit);
45
+ }
46
+ }
47
+ };
48
+
49
+ export { ensureStoreLinks };
@@ -0,0 +1,18 @@
1
+ import { ModuleContext } from "./context.js";
2
+
3
+ const COMPARISON_CONTEXTS: WeakMap<ModuleContext, ModuleContext> = new WeakMap();
4
+
5
+ const comparisonContextFor = (context: ModuleContext): ModuleContext => {
6
+ const existing = COMPARISON_CONTEXTS.get(context);
7
+
8
+ if (existing !== undefined) {
9
+ return existing;
10
+ }
11
+
12
+ const created = new ModuleContext(context.namespace, context.library);
13
+ COMPARISON_CONTEXTS.set(context, created);
14
+
15
+ return created;
16
+ };
17
+
18
+ export { comparisonContextFor };
@@ -1,6 +1,6 @@
1
1
  import type { Library } from "../gir/library.js";
2
2
  import type { GirNamespace } from "../gir/namespace.js";
3
- import { type DeclaredType, ModuleBuilder } from "./module.js";
3
+ import { type Declaration, ModuleBuilder } from "./module.js";
4
4
 
5
5
  class ModuleContext {
6
6
  public module: ModuleBuilder = new ModuleBuilder();
@@ -55,16 +55,22 @@ class ModuleContext {
55
55
  return namespaceName;
56
56
  }
57
57
 
58
- declaredType(owner: string, name: string = owner): DeclaredType {
59
- return { name, owner: `${this.namespace.name}.${owner}` };
58
+ declare(declaration: Declaration): void {
59
+ const { name, code, owner } = declaration;
60
+
61
+ this.module.appendDeclaration({
62
+ name,
63
+ code,
64
+ owner: owner === undefined ? undefined : `${this.namespace.name}.${owner}`,
65
+ });
60
66
  }
61
67
 
62
- qualify(namespaceName: string, typeName: string): string {
68
+ qualify(namespaceName: string, name: string): string {
63
69
  if (namespaceName === this.namespace.name) {
64
- return typeName;
70
+ return name;
65
71
  }
66
72
 
67
- return `${this.addCrossNamespaceImport(namespaceName)}.${typeName}`;
73
+ return `${this.addCrossNamespaceImport(namespaceName)}.${name}`;
68
74
  }
69
75
  }
70
76