@gtkx/codegen 1.0.0 → 1.1.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 (316) 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 +48 -31
  5. package/dist/analysis/descriptor-render.js.map +1 -1
  6. package/dist/analysis/descriptor.d.ts +16 -7
  7. package/dist/analysis/descriptor.d.ts.map +1 -1
  8. package/dist/analysis/descriptor.js +15 -3
  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 +23 -0
  15. package/dist/analysis/interface-conflicts.d.ts.map +1 -0
  16. package/dist/analysis/interface-conflicts.js +144 -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.map +1 -1
  24. package/dist/analysis/ts-type.js +10 -17
  25. package/dist/analysis/ts-type.js.map +1 -1
  26. package/dist/analysis/type-key.d.ts +5 -0
  27. package/dist/analysis/type-key.d.ts.map +1 -0
  28. package/dist/analysis/type-key.js +9 -0
  29. package/dist/analysis/type-key.js.map +1 -0
  30. package/dist/compile.d.ts +12 -3
  31. package/dist/compile.d.ts.map +1 -1
  32. package/dist/compile.js +73 -24
  33. package/dist/compile.js.map +1 -1
  34. package/dist/docs/api-reference.d.ts.map +1 -1
  35. package/dist/docs/api-reference.js +10 -9
  36. package/dist/docs/api-reference.js.map +1 -1
  37. package/dist/docs/element-page.d.ts.map +1 -1
  38. package/dist/docs/element-page.js +5 -3
  39. package/dist/docs/element-page.js.map +1 -1
  40. package/dist/docs/pipeline.d.ts.map +1 -1
  41. package/dist/docs/pipeline.js +85 -18
  42. package/dist/docs/pipeline.js.map +1 -1
  43. package/dist/docs/render.d.ts +2 -1
  44. package/dist/docs/render.d.ts.map +1 -1
  45. package/dist/docs/render.js +5 -4
  46. package/dist/docs/render.js.map +1 -1
  47. package/dist/docs/symbol-page.d.ts.map +1 -1
  48. package/dist/docs/symbol-page.js +9 -6
  49. package/dist/docs/symbol-page.js.map +1 -1
  50. package/dist/fingerprint.d.ts +12 -1
  51. package/dist/fingerprint.d.ts.map +1 -1
  52. package/dist/fingerprint.js +44 -28
  53. package/dist/fingerprint.js.map +1 -1
  54. package/dist/gi.d.ts.map +1 -1
  55. package/dist/gi.js +1 -0
  56. package/dist/gi.js.map +1 -1
  57. package/dist/gir/ancestry.d.ts +7 -1
  58. package/dist/gir/ancestry.d.ts.map +1 -1
  59. package/dist/gir/ancestry.js +9 -1
  60. package/dist/gir/ancestry.js.map +1 -1
  61. package/dist/gir/annotations.d.ts +1 -2
  62. package/dist/gir/annotations.d.ts.map +1 -1
  63. package/dist/gir/annotations.js +1 -1
  64. package/dist/gir/annotations.js.map +1 -1
  65. package/dist/gir/cursor-overrides.d.ts +8 -0
  66. package/dist/gir/cursor-overrides.d.ts.map +1 -0
  67. package/dist/gir/cursor-overrides.js +6 -0
  68. package/dist/gir/cursor-overrides.js.map +1 -0
  69. package/dist/gir/emittable.d.ts +7 -0
  70. package/dist/gir/emittable.d.ts.map +1 -0
  71. package/dist/gir/emittable.js +3 -0
  72. package/dist/gir/emittable.js.map +1 -0
  73. package/dist/gir/function.d.ts.map +1 -1
  74. package/dist/gir/function.js +28 -2
  75. package/dist/gir/function.js.map +1 -1
  76. package/dist/gir/library.js +1 -1
  77. package/dist/gir/library.js.map +1 -1
  78. package/dist/gir/namespace.d.ts +5 -1
  79. package/dist/gir/namespace.d.ts.map +1 -1
  80. package/dist/gir/namespace.js +4 -2
  81. package/dist/gir/namespace.js.map +1 -1
  82. package/dist/gir/parameter.d.ts +13 -1
  83. package/dist/gir/parameter.d.ts.map +1 -1
  84. package/dist/gir/parameter.js +1 -0
  85. package/dist/gir/parameter.js.map +1 -1
  86. package/dist/gir/parse.d.ts.map +1 -1
  87. package/dist/gir/parse.js +3 -6
  88. package/dist/gir/parse.js.map +1 -1
  89. package/dist/guards.d.ts +16 -0
  90. package/dist/guards.d.ts.map +1 -0
  91. package/dist/guards.js +11 -0
  92. package/dist/guards.js.map +1 -0
  93. package/dist/internal.d.ts +2 -0
  94. package/dist/internal.d.ts.map +1 -1
  95. package/dist/internal.js +2 -0
  96. package/dist/internal.js.map +1 -1
  97. package/dist/json.d.ts +3 -0
  98. package/dist/json.d.ts.map +1 -0
  99. package/dist/json.js +11 -0
  100. package/dist/json.js.map +1 -0
  101. package/dist/khronos/modules.d.ts.map +1 -1
  102. package/dist/khronos/modules.js +22 -7
  103. package/dist/khronos/modules.js.map +1 -1
  104. package/dist/khronos/parse.d.ts.map +1 -1
  105. package/dist/khronos/parse.js +3 -4
  106. package/dist/khronos/parse.js.map +1 -1
  107. package/dist/khronos/pipeline.d.ts +1 -1
  108. package/dist/khronos/pipeline.d.ts.map +1 -1
  109. package/dist/khronos/pipeline.js.map +1 -1
  110. package/dist/khronos/plan.d.ts +1 -7
  111. package/dist/khronos/plan.d.ts.map +1 -1
  112. package/dist/khronos/plan.js +1 -1
  113. package/dist/khronos/plan.js.map +1 -1
  114. package/dist/runner.d.ts +5 -1
  115. package/dist/runner.d.ts.map +1 -1
  116. package/dist/runner.js +16 -0
  117. package/dist/runner.js.map +1 -1
  118. package/dist/staging.d.ts +5 -0
  119. package/dist/staging.d.ts.map +1 -0
  120. package/dist/staging.js +57 -0
  121. package/dist/staging.js.map +1 -0
  122. package/dist/store/compile-store.d.ts.map +1 -1
  123. package/dist/store/compile-store.js +4 -2
  124. package/dist/store/compile-store.js.map +1 -1
  125. package/dist/store/gi/callables.d.ts +7 -2
  126. package/dist/store/gi/callables.d.ts.map +1 -1
  127. package/dist/store/gi/callables.js +25 -30
  128. package/dist/store/gi/callables.js.map +1 -1
  129. package/dist/store/gi/callback.d.ts.map +1 -1
  130. package/dist/store/gi/callback.js +9 -5
  131. package/dist/store/gi/callback.js.map +1 -1
  132. package/dist/store/gi/class.d.ts.map +1 -1
  133. package/dist/store/gi/class.js +59 -38
  134. package/dist/store/gi/class.js.map +1 -1
  135. package/dist/store/gi/companion.d.ts.map +1 -1
  136. package/dist/store/gi/companion.js +6 -6
  137. package/dist/store/gi/companion.js.map +1 -1
  138. package/dist/store/gi/constant.d.ts.map +1 -1
  139. package/dist/store/gi/constant.js +10 -2
  140. package/dist/store/gi/constant.js.map +1 -1
  141. package/dist/store/gi/constructor-props.js +1 -1
  142. package/dist/store/gi/constructor-props.js.map +1 -1
  143. package/dist/store/gi/enum.d.ts.map +1 -1
  144. package/dist/store/gi/enum.js +28 -8
  145. package/dist/store/gi/enum.js.map +1 -1
  146. package/dist/store/gi/function.d.ts +1 -3
  147. package/dist/store/gi/function.d.ts.map +1 -1
  148. package/dist/store/gi/function.js +24 -18
  149. package/dist/store/gi/function.js.map +1 -1
  150. package/dist/store/gi/gtype-binding.d.ts.map +1 -1
  151. package/dist/store/gi/gtype-binding.js +4 -1
  152. package/dist/store/gi/gtype-binding.js.map +1 -1
  153. package/dist/store/gi/interface.d.ts.map +1 -1
  154. package/dist/store/gi/interface.js +32 -17
  155. package/dist/store/gi/interface.js.map +1 -1
  156. package/dist/store/gi/method.d.ts +7 -6
  157. package/dist/store/gi/method.d.ts.map +1 -1
  158. package/dist/store/gi/method.js +13 -21
  159. package/dist/store/gi/method.js.map +1 -1
  160. package/dist/store/gi/param-marshal.d.ts +3 -2
  161. package/dist/store/gi/param-marshal.d.ts.map +1 -1
  162. package/dist/store/gi/param-marshal.js +50 -5
  163. package/dist/store/gi/param-marshal.js.map +1 -1
  164. package/dist/store/gi/pipeline.d.ts.map +1 -1
  165. package/dist/store/gi/pipeline.js +21 -10
  166. package/dist/store/gi/pipeline.js.map +1 -1
  167. package/dist/store/gi/properties.d.ts +2 -1
  168. package/dist/store/gi/properties.d.ts.map +1 -1
  169. package/dist/store/gi/properties.js +6 -3
  170. package/dist/store/gi/properties.js.map +1 -1
  171. package/dist/store/gi/property-accessor.d.ts +10 -5
  172. package/dist/store/gi/property-accessor.d.ts.map +1 -1
  173. package/dist/store/gi/property-accessor.js +95 -44
  174. package/dist/store/gi/property-accessor.js.map +1 -1
  175. package/dist/store/gi/record-constructor.d.ts +8 -1
  176. package/dist/store/gi/record-constructor.d.ts.map +1 -1
  177. package/dist/store/gi/record-constructor.js +31 -10
  178. package/dist/store/gi/record-constructor.js.map +1 -1
  179. package/dist/store/gi/record-field-accessor.d.ts.map +1 -1
  180. package/dist/store/gi/record-field-accessor.js +5 -1
  181. package/dist/store/gi/record-field-accessor.js.map +1 -1
  182. package/dist/store/gi/record-layout.d.ts +6 -1
  183. package/dist/store/gi/record-layout.d.ts.map +1 -1
  184. package/dist/store/gi/record-layout.js +8 -1
  185. package/dist/store/gi/record-layout.js.map +1 -1
  186. package/dist/store/gi/record.d.ts.map +1 -1
  187. package/dist/store/gi/record.js +19 -8
  188. package/dist/store/gi/record.js.map +1 -1
  189. package/dist/store/gi/registration.js +2 -2
  190. package/dist/store/gi/registration.js.map +1 -1
  191. package/dist/store/gi/signal.d.ts +2 -1
  192. package/dist/store/gi/signal.d.ts.map +1 -1
  193. package/dist/store/gi/signal.js +30 -24
  194. package/dist/store/gi/signal.js.map +1 -1
  195. package/dist/store/gi/value-marshalable.d.ts.map +1 -1
  196. package/dist/store/gi/value-marshalable.js +2 -1
  197. package/dist/store/gi/value-marshalable.js.map +1 -1
  198. package/dist/store/gi/vtable.d.ts +4 -1
  199. package/dist/store/gi/vtable.d.ts.map +1 -1
  200. package/dist/store/gi/vtable.js +60 -17
  201. package/dist/store/gi/vtable.js.map +1 -1
  202. package/dist/store/gi-store.d.ts +1 -0
  203. package/dist/store/gi-store.d.ts.map +1 -1
  204. package/dist/store/gi-store.js +15 -20
  205. package/dist/store/gi-store.js.map +1 -1
  206. package/dist/store/jsx/generated-elements.d.ts +2 -1
  207. package/dist/store/jsx/generated-elements.d.ts.map +1 -1
  208. package/dist/store/jsx/generated-elements.js +13 -7
  209. package/dist/store/jsx/generated-elements.js.map +1 -1
  210. package/dist/store/jsx/jsx.js +3 -3
  211. package/dist/store/jsx/jsx.js.map +1 -1
  212. package/dist/store/jsx/metadata.d.ts.map +1 -1
  213. package/dist/store/jsx/metadata.js +3 -1
  214. package/dist/store/jsx/metadata.js.map +1 -1
  215. package/dist/store/jsx-store.d.ts.map +1 -1
  216. package/dist/store/jsx-store.js +3 -3
  217. package/dist/store/jsx-store.js.map +1 -1
  218. package/dist/store/resolve-store.d.ts +14 -3
  219. package/dist/store/resolve-store.d.ts.map +1 -1
  220. package/dist/store/resolve-store.js +98 -15
  221. package/dist/store/resolve-store.js.map +1 -1
  222. package/dist/store/store-fs.d.ts +9 -6
  223. package/dist/store/store-fs.d.ts.map +1 -1
  224. package/dist/store/store-fs.js +59 -25
  225. package/dist/store/store-fs.js.map +1 -1
  226. package/dist/store/store-links.d.ts +3 -0
  227. package/dist/store/store-links.d.ts.map +1 -0
  228. package/dist/store/store-links.js +39 -0
  229. package/dist/store/store-links.js.map +1 -0
  230. package/dist/writer/comparison-context.d.ts +4 -0
  231. package/dist/writer/comparison-context.d.ts.map +1 -0
  232. package/dist/writer/comparison-context.js +13 -0
  233. package/dist/writer/comparison-context.js.map +1 -0
  234. package/dist/writer/context.d.ts +3 -3
  235. package/dist/writer/context.d.ts.map +1 -1
  236. package/dist/writer/context.js +11 -6
  237. package/dist/writer/context.js.map +1 -1
  238. package/dist/writer/module.d.ts +13 -5
  239. package/dist/writer/module.d.ts.map +1 -1
  240. package/dist/writer/module.js +68 -11
  241. package/dist/writer/module.js.map +1 -1
  242. package/dist/xml.d.ts +7 -1
  243. package/dist/xml.d.ts.map +1 -1
  244. package/dist/xml.js +43 -1
  245. package/dist/xml.js.map +1 -1
  246. package/package.json +14 -3
  247. package/src/analysis/descriptor-render.ts +90 -45
  248. package/src/analysis/descriptor.ts +35 -8
  249. package/src/analysis/inheritance.ts +134 -44
  250. package/src/analysis/interface-conflicts.ts +237 -0
  251. package/src/analysis/param-capability.ts +3 -6
  252. package/src/analysis/param-structure.ts +2 -2
  253. package/src/analysis/ts-type.ts +13 -17
  254. package/src/analysis/type-key.ts +14 -0
  255. package/src/compile.ts +110 -26
  256. package/src/docs/api-reference.ts +10 -9
  257. package/src/docs/element-page.ts +5 -2
  258. package/src/docs/pipeline.ts +129 -19
  259. package/src/docs/render.ts +7 -3
  260. package/src/docs/symbol-page.ts +12 -7
  261. package/src/fingerprint.ts +86 -31
  262. package/src/gi.ts +1 -0
  263. package/src/gir/ancestry.ts +17 -1
  264. package/src/gir/annotations.ts +1 -1
  265. package/src/gir/cursor-overrides.ts +12 -0
  266. package/src/gir/emittable.ts +8 -0
  267. package/src/gir/function.ts +45 -9
  268. package/src/gir/library.ts +1 -1
  269. package/src/gir/namespace.ts +8 -2
  270. package/src/gir/parameter.ts +15 -0
  271. package/src/gir/parse.ts +5 -7
  272. package/src/guards.ts +23 -0
  273. package/src/internal.ts +2 -0
  274. package/src/json.ts +11 -0
  275. package/src/khronos/modules.ts +25 -9
  276. package/src/khronos/parse.ts +3 -4
  277. package/src/khronos/pipeline.ts +1 -1
  278. package/src/khronos/plan.ts +0 -1
  279. package/src/runner.ts +19 -1
  280. package/src/staging.ts +69 -0
  281. package/src/store/compile-store.ts +6 -4
  282. package/src/store/gi/callables.ts +41 -33
  283. package/src/store/gi/callback.ts +9 -9
  284. package/src/store/gi/class.ts +79 -48
  285. package/src/store/gi/companion.ts +7 -6
  286. package/src/store/gi/constant.ts +12 -2
  287. package/src/store/gi/constructor-props.ts +1 -1
  288. package/src/store/gi/enum.ts +35 -21
  289. package/src/store/gi/function.ts +30 -24
  290. package/src/store/gi/gtype-binding.ts +7 -2
  291. package/src/store/gi/interface.ts +37 -18
  292. package/src/store/gi/method.ts +28 -29
  293. package/src/store/gi/param-marshal.ts +77 -6
  294. package/src/store/gi/pipeline.ts +25 -15
  295. package/src/store/gi/properties.ts +8 -4
  296. package/src/store/gi/property-accessor.ts +156 -73
  297. package/src/store/gi/record-constructor.ts +50 -18
  298. package/src/store/gi/record-field-accessor.ts +8 -1
  299. package/src/store/gi/record-layout.ts +11 -1
  300. package/src/store/gi/record.ts +20 -12
  301. package/src/store/gi/registration.ts +2 -2
  302. package/src/store/gi/signal.ts +36 -38
  303. package/src/store/gi/value-marshalable.ts +3 -1
  304. package/src/store/gi/vtable.ts +97 -20
  305. package/src/store/gi-store.ts +26 -31
  306. package/src/store/jsx/generated-elements.ts +17 -8
  307. package/src/store/jsx/jsx.ts +3 -3
  308. package/src/store/jsx/metadata.ts +4 -1
  309. package/src/store/jsx-store.ts +3 -3
  310. package/src/store/resolve-store.ts +133 -18
  311. package/src/store/store-fs.ts +87 -34
  312. package/src/store/store-links.ts +49 -0
  313. package/src/writer/comparison-context.ts +18 -0
  314. package/src/writer/context.ts +12 -6
  315. package/src/writer/module.ts +101 -14
  316. package/src/xml.ts +61 -1
@@ -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,25 @@ 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
53
  requiresEnvReference?: boolean;
45
54
  };
46
55
 
47
56
  const STORE_DIR_MODE = 0o755;
57
+ const FAILED_STORE_SUFFIX = ".failed";
48
58
 
49
59
  const subpathExport = (stem: string): { types: string; default: string } => ({
50
60
  types: `./${stem}.d.ts`,
51
61
  default: `./${stem}.js`,
52
62
  });
53
63
 
64
+ const namespaceBarrel = (directory: string): { fileName: string; source: string } => ({
65
+ fileName: `${directory}/index.ts`,
66
+ source: `export * from "./${directory}.js";\n`,
67
+ });
68
+
54
69
  const buildManifest = (input: ManifestInput): Manifest => {
55
70
  const manifest: Manifest = {
56
71
  name: input.name,
@@ -69,29 +84,36 @@ const buildManifest = (input: ManifestInput): Manifest => {
69
84
 
70
85
  const writeStore = (params: WriteStoreParams): void => {
71
86
  const tmp = createTempStore(params.storeDir);
87
+ const keepAt = `${params.storeDir}${FAILED_STORE_SUFFIX}`;
72
88
 
73
89
  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 });
90
+ buildTempStore(tmp, params);
91
+ swapStore(tmp, params.storeDir);
92
+ ensureStoreLink(params);
93
+ } catch (error) {
94
+ throw keepFailedProject({ projectDir: tmp, keepAt, error });
95
+ }
96
+
97
+ rmSync(keepAt, { recursive: true, force: true });
98
+ };
99
+
100
+ const buildTempStore = (tmp: string, params: WriteStoreParams): void => {
101
+ for (const file of params.files) {
102
+ writeSourceFile(tmp, file.fileName, file.source);
103
+ }
104
+
105
+ compileStore({
106
+ storeDir: tmp,
107
+ files: params.files,
108
+ packageName: params.manifest.name,
109
+ ...(params.requiresEnvReference !== undefined && { requiresEnvReference: params.requiresEnvReference }),
110
+ });
111
+
112
+ writePackageJson(tmp, params.manifest);
113
+ const rawFiles = params.rawFiles ?? [];
114
+
115
+ for (const raw of rawFiles) {
116
+ writeFileSync(join(tmp, raw.relativePath), raw.content);
95
117
  }
96
118
  };
97
119
 
@@ -111,7 +133,7 @@ const writePackageJson = (storeDir: string, manifest: Manifest): void => {
111
133
  writeFileSync(join(storeDir, "package.json"), `${JSON.stringify(manifest, null, 2)}\n`);
112
134
  };
113
135
 
114
- const swapStore = (tmp: string, storeDir: string, visibleLink: string): void => {
136
+ const swapStore = (tmp: string, storeDir: string): void => {
115
137
  const previous = `${storeDir}.old`;
116
138
  rmSync(previous, { recursive: true, force: true });
117
139
 
@@ -121,15 +143,46 @@ const swapStore = (tmp: string, storeDir: string, visibleLink: string): void =>
121
143
 
122
144
  renameSync(tmp, storeDir);
123
145
  rmSync(previous, { recursive: true, force: true });
124
- symlinkRelative(visibleLink, storeDir);
125
146
  };
126
147
 
148
+ const isStoreLinked = (link: StoreLink): boolean => {
149
+ try {
150
+ return realpathSync(link.linkDir) === realpathSync(link.storeDir);
151
+ } catch {
152
+ return false;
153
+ }
154
+ };
155
+
156
+ const ensureStoreLink = (link: StoreLink): void => {
157
+ if (!existsSync(join(link.storeDir, "package.json")) || isStoreLinked(link)) {
158
+ return;
159
+ }
160
+
161
+ symlinkRelative(link.linkDir, link.storeDir);
162
+ };
163
+
164
+ const storeWriteMessage = (storeDir: string, error: unknown): string =>
165
+ `Cannot write the generated store to ${storeDir}: ${errorMessage(error)}. ` +
166
+ "Codegen writes the store into the node_modules the @gtkx packages resolve from, " +
167
+ `so ${dirname(storeDir)} has to be writable.`;
168
+
127
169
  const createTempStore = (storeDir: string): string => {
128
- mkdirSync(dirname(storeDir), { recursive: true });
129
- const tmp = mkdtempSync(`${storeDir}.tmp-`);
130
- chmodSync(tmp, STORE_DIR_MODE);
170
+ try {
171
+ const tmp = createStagingDir(storeDir);
172
+ chmodSync(tmp, STORE_DIR_MODE);
131
173
 
132
- return tmp;
174
+ return tmp;
175
+ } catch (error) {
176
+ throw new Error(storeWriteMessage(storeDir, error), { cause: error });
177
+ }
133
178
  };
134
179
 
135
- export { subpathExport, buildManifest, writeStore, type StoreOptions, type RawFile };
180
+ export {
181
+ subpathExport,
182
+ buildManifest,
183
+ ensureStoreLink,
184
+ namespaceBarrel,
185
+ writeStore,
186
+ type StoreOptions,
187
+ type RawFile,
188
+ };
@@ -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
 
@@ -1,30 +1,109 @@
1
1
  import { ImportsBuilder } from "./imports.js";
2
2
 
3
- type DeclaredType = {
3
+ type Declaration = {
4
4
  name: string;
5
- owner: string;
5
+ code: string;
6
+ owner?: string | undefined;
6
7
  };
7
8
 
9
+ type ExportedDeclaration = {
10
+ name: string;
11
+ spaces: string[];
12
+ };
13
+
14
+ const EXPORTED_DECLARATION = /^export (?:abstract )?(class|const|enum|function|interface|type) ([A-Za-z_$][\w$]*)/mu;
15
+
16
+ const SPACES_BY_KEYWORD: Map<string, string[]> = new Map([
17
+ ["class", ["value", "type"]],
18
+ ["const", ["value"]],
19
+ ["enum", ["value", "type"]],
20
+ ["function", ["value"]],
21
+ ["interface", []],
22
+ ["type", ["type"]],
23
+ ]);
24
+
25
+ const getExportedDeclaration = (code: string): ExportedDeclaration | undefined => {
26
+ const match = EXPORTED_DECLARATION.exec(code);
27
+ const spaces = SPACES_BY_KEYWORD.get(match?.[1] ?? "");
28
+ const name = match?.[2];
29
+
30
+ return spaces === undefined || name === undefined ? undefined : { name, spaces };
31
+ };
32
+
33
+ const exportedNameText = (exported: ExportedDeclaration | undefined): string =>
34
+ exported === undefined ? "no name at all" : `'${exported.name}'`;
35
+
8
36
  class ModuleBuilder {
9
37
  private bindings: string[] = [];
10
38
  private bindingNames: Set<string> = new Set();
11
39
  private hoistedDescriptors: Map<string, string> = new Map();
40
+ private claimedSpaces: Set<string> = new Set();
12
41
  private declarations: string[] = [];
42
+ private declaredNames: Set<string> = new Set();
13
43
  private declaredTypes: Map<string, string> = new Map();
14
44
  private registrations: string[] = [];
45
+ private requiredNames: Set<string> = new Set();
15
46
  public imports: ImportsBuilder = new ImportsBuilder();
16
47
 
17
- private claimTypeName(declared: DeclaredType): void {
18
- const owner = this.declaredTypes.get(declared.name);
48
+ private claimSpace(name: string, space: string): void {
49
+ const key = `${space} ${name}`;
50
+
51
+ if (this.claimedSpaces.has(key)) {
52
+ throw new Error(
53
+ `The generated module declares '${name}' twice in its ${space} space. ` +
54
+ "A module may declare each exported name once per declaration space.",
55
+ );
56
+ }
57
+
58
+ this.claimedSpaces.add(key);
59
+ }
60
+
61
+ private claimExportedName(declaration: Declaration): void {
62
+ const exported = getExportedDeclaration(declaration.code);
63
+ const spaces = exported?.name === declaration.name ? exported.spaces : undefined;
64
+
65
+ if (spaces === undefined) {
66
+ throw new Error(
67
+ `The declaration recorded as '${declaration.name}' exports ${exportedNameText(exported)}. ` +
68
+ "A declaration may only be recorded under the name its own code exports.",
69
+ );
70
+ }
71
+
72
+ for (const space of spaces) {
73
+ this.claimSpace(declaration.name, space);
74
+ }
75
+ }
76
+
77
+ private claimTypeName(declaration: Declaration): void {
78
+ const { name, owner } = declaration;
79
+
80
+ if (owner === undefined) {
81
+ return;
82
+ }
83
+
84
+ const previous = this.declaredTypes.get(name);
19
85
 
20
- if (owner !== undefined && owner !== declared.owner) {
86
+ if (previous !== undefined && previous !== owner) {
21
87
  throw new Error(
22
- `The generated type '${declared.name}' is declared for both ${owner} and ${declared.owner}. ` +
88
+ `The generated type '${name}' is declared for both ${previous} and ${owner}. ` +
23
89
  "Rename one of them, or drop the namespace from the configuration.",
24
90
  );
25
91
  }
26
92
 
27
- this.declaredTypes.set(declared.name, declared.owner);
93
+ this.declaredTypes.set(name, owner);
94
+ }
95
+
96
+ private verifyRegistrations(): void {
97
+ const missing = [...this.requiredNames].filter((name) => !this.declaredNames.has(name));
98
+
99
+ if (missing.length === 0) {
100
+ return;
101
+ }
102
+
103
+ throw new Error(
104
+ `The generated module registers ${missing.join(", ")}, which it never declares. ` +
105
+ "A registration may only name a symbol whose declaration the same module emits.",
106
+ );
28
107
  }
29
108
 
30
109
  appendBinding(code: string, name?: string): void {
@@ -53,19 +132,27 @@ class ModuleBuilder {
53
132
  return name;
54
133
  }
55
134
 
56
- appendDeclaration(code: string, declared?: DeclaredType): void {
57
- if (declared !== undefined) {
58
- this.claimTypeName(declared);
59
- }
135
+ appendDeclaration(declaration: Declaration): void {
136
+ this.claimExportedName(declaration);
137
+ this.claimTypeName(declaration);
138
+ this.declaredNames.add(declaration.name);
139
+ this.declarations.push(declaration.code);
140
+ }
60
141
 
61
- this.declarations.push(code);
142
+ hasExports(): boolean {
143
+ return this.declaredNames.size > 0;
62
144
  }
63
145
 
64
- appendRegistration(code: string): void {
146
+ appendRegistration(code: string, requires: string[] = []): void {
147
+ for (const name of requires) {
148
+ this.requiredNames.add(name);
149
+ }
150
+
65
151
  this.registrations.push(code);
66
152
  }
67
153
 
68
154
  toSource(): string {
155
+ this.verifyRegistrations();
69
156
  const sections: string[] = [];
70
157
  const importsBlock = this.imports.toSource();
71
158
 
@@ -89,4 +176,4 @@ class ModuleBuilder {
89
176
  }
90
177
  }
91
178
 
92
- export { type DeclaredType, ModuleBuilder };
179
+ export { type Declaration, ModuleBuilder };