@hydranium/cli 1.0.0-next.37 → 1.0.0-next.370

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 (339) hide show
  1. package/README.md +102 -204
  2. package/bin/hydranium-cli.js +13 -0
  3. package/heap-analysis/heap/core/load.mjs +2 -2
  4. package/heap-analysis/heap/langium/langium.classifier.mjs +22 -11
  5. package/lib/cli-version.js.map +1 -1
  6. package/lib/cli.js +7 -2
  7. package/lib/cli.js.map +1 -1
  8. package/lib/commands/analyze-heap-args.d.ts.map +1 -1
  9. package/lib/commands/analyze-heap-args.js +1 -2
  10. package/lib/commands/analyze-heap-args.js.map +1 -1
  11. package/lib/commands/analyze-heap.d.ts +3 -0
  12. package/lib/commands/analyze-heap.d.ts.map +1 -1
  13. package/lib/commands/analyze-heap.js +2 -1
  14. package/lib/commands/analyze-heap.js.map +1 -1
  15. package/lib/commands/ast-ground-truth-args.d.ts +3 -2
  16. package/lib/commands/ast-ground-truth-args.d.ts.map +1 -1
  17. package/lib/commands/ast-ground-truth-args.js +11 -9
  18. package/lib/commands/ast-ground-truth-args.js.map +1 -1
  19. package/lib/commands/ast-ground-truth-driver.js +4 -2
  20. package/lib/commands/ast-ground-truth-driver.js.map +1 -1
  21. package/lib/commands/ast-ground-truth.js +1 -1
  22. package/lib/commands/ast-ground-truth.js.map +1 -1
  23. package/lib/commands/client-session.d.ts +17 -0
  24. package/lib/commands/client-session.d.ts.map +1 -0
  25. package/lib/commands/client-session.js +27 -0
  26. package/lib/commands/client-session.js.map +1 -0
  27. package/lib/commands/create-args.d.ts +17 -0
  28. package/lib/commands/create-args.d.ts.map +1 -0
  29. package/lib/commands/create-args.js +53 -0
  30. package/lib/commands/create-args.js.map +1 -0
  31. package/lib/commands/create.d.ts +38 -0
  32. package/lib/commands/create.d.ts.map +1 -0
  33. package/lib/commands/create.js +43 -0
  34. package/lib/commands/create.js.map +1 -0
  35. package/lib/commands/delete-args.d.ts +17 -0
  36. package/lib/commands/delete-args.d.ts.map +1 -0
  37. package/lib/commands/delete-args.js +49 -0
  38. package/lib/commands/delete-args.js.map +1 -0
  39. package/lib/commands/delete.d.ts +30 -0
  40. package/lib/commands/delete.d.ts.map +1 -0
  41. package/lib/commands/delete.js +32 -0
  42. package/lib/commands/delete.js.map +1 -0
  43. package/lib/commands/generate-ast-builder.d.ts.map +1 -1
  44. package/lib/commands/generate-ast-builder.js +1 -10
  45. package/lib/commands/generate-ast-builder.js.map +1 -1
  46. package/lib/commands/generate-languages.d.ts +35 -0
  47. package/lib/commands/generate-languages.d.ts.map +1 -0
  48. package/lib/commands/generate-languages.js +105 -0
  49. package/lib/commands/generate-languages.js.map +1 -0
  50. package/lib/commands/generate-transfer-builder.d.ts +33 -0
  51. package/lib/commands/generate-transfer-builder.d.ts.map +1 -0
  52. package/lib/commands/generate-transfer-builder.js +86 -0
  53. package/lib/commands/generate-transfer-builder.js.map +1 -0
  54. package/lib/commands/generate-transfer-model-args.d.ts.map +1 -1
  55. package/lib/commands/generate-transfer-model-args.js +20 -5
  56. package/lib/commands/generate-transfer-model-args.js.map +1 -1
  57. package/lib/commands/generate-transfer-model-config.d.ts.map +1 -1
  58. package/lib/commands/generate-transfer-model-config.js +8 -3
  59. package/lib/commands/generate-transfer-model-config.js.map +1 -1
  60. package/lib/commands/generate-transfer-model.d.ts +40 -9
  61. package/lib/commands/generate-transfer-model.d.ts.map +1 -1
  62. package/lib/commands/generate-transfer-model.js +219 -67
  63. package/lib/commands/generate-transfer-model.js.map +1 -1
  64. package/lib/commands/generated-module-path.d.ts +16 -0
  65. package/lib/commands/generated-module-path.d.ts.map +1 -0
  66. package/lib/commands/generated-module-path.js +21 -0
  67. package/lib/commands/generated-module-path.js.map +1 -0
  68. package/lib/commands/harness-args.d.ts +44 -42
  69. package/lib/commands/harness-args.d.ts.map +1 -1
  70. package/lib/commands/harness-args.js +109 -27
  71. package/lib/commands/harness-args.js.map +1 -1
  72. package/lib/commands/headless-harness.d.ts +37 -20
  73. package/lib/commands/headless-harness.d.ts.map +1 -1
  74. package/lib/commands/headless-harness.js +71 -19
  75. package/lib/commands/headless-harness.js.map +1 -1
  76. package/lib/commands/init-args.d.ts +2 -0
  77. package/lib/commands/init-args.d.ts.map +1 -1
  78. package/lib/commands/init-args.js +74 -31
  79. package/lib/commands/init-args.js.map +1 -1
  80. package/lib/commands/init-hosts.d.ts +24 -0
  81. package/lib/commands/init-hosts.d.ts.map +1 -0
  82. package/lib/commands/init-hosts.js +37 -0
  83. package/lib/commands/init-hosts.js.map +1 -0
  84. package/lib/commands/init-prompt.d.ts +3 -3
  85. package/lib/commands/init-prompt.d.ts.map +1 -1
  86. package/lib/commands/init-prompt.js +4 -5
  87. package/lib/commands/init-prompt.js.map +1 -1
  88. package/lib/commands/init-templates.d.ts +26 -14
  89. package/lib/commands/init-templates.d.ts.map +1 -1
  90. package/lib/commands/init-templates.js +2989 -743
  91. package/lib/commands/init-templates.js.map +1 -1
  92. package/lib/commands/init-wizard.d.ts +3 -0
  93. package/lib/commands/init-wizard.d.ts.map +1 -1
  94. package/lib/commands/init-wizard.js +46 -31
  95. package/lib/commands/init-wizard.js.map +1 -1
  96. package/lib/commands/init-workspace.d.ts +13 -11
  97. package/lib/commands/init-workspace.d.ts.map +1 -1
  98. package/lib/commands/init-workspace.js +21 -7
  99. package/lib/commands/init-workspace.js.map +1 -1
  100. package/lib/commands/init.d.ts +58 -22
  101. package/lib/commands/init.d.ts.map +1 -1
  102. package/lib/commands/init.js +206 -48
  103. package/lib/commands/init.js.map +1 -1
  104. package/lib/commands/kebab.d.ts +15 -0
  105. package/lib/commands/kebab.d.ts.map +1 -0
  106. package/lib/commands/kebab.js +17 -0
  107. package/lib/commands/kebab.js.map +1 -0
  108. package/lib/commands/lint-grammar-args.d.ts +3 -2
  109. package/lib/commands/lint-grammar-args.d.ts.map +1 -1
  110. package/lib/commands/lint-grammar-args.js +13 -10
  111. package/lib/commands/lint-grammar-args.js.map +1 -1
  112. package/lib/commands/lint-grammar-driver.js +2 -1
  113. package/lib/commands/lint-grammar-driver.js.map +1 -1
  114. package/lib/commands/lint-grammar-report.js.map +1 -1
  115. package/lib/commands/lint-grammar.js +1 -1
  116. package/lib/commands/lint-grammar.js.map +1 -1
  117. package/lib/commands/measure-memory-args.d.ts +3 -2
  118. package/lib/commands/measure-memory-args.d.ts.map +1 -1
  119. package/lib/commands/measure-memory-args.js +22 -11
  120. package/lib/commands/measure-memory-args.js.map +1 -1
  121. package/lib/commands/measure-memory-driver.js +5 -3
  122. package/lib/commands/measure-memory-driver.js.map +1 -1
  123. package/lib/commands/measure-memory.js +1 -1
  124. package/lib/commands/measure-memory.js.map +1 -1
  125. package/lib/commands/model-docs-args.d.ts +4 -3
  126. package/lib/commands/model-docs-args.d.ts.map +1 -1
  127. package/lib/commands/model-docs-args.js +12 -10
  128. package/lib/commands/model-docs-args.js.map +1 -1
  129. package/lib/commands/model-docs-driver.js +2 -1
  130. package/lib/commands/model-docs-driver.js.map +1 -1
  131. package/lib/commands/model-docs.js +1 -1
  132. package/lib/commands/model-docs.js.map +1 -1
  133. package/lib/commands/projects-args.d.ts.map +1 -1
  134. package/lib/commands/projects-args.js +5 -5
  135. package/lib/commands/projects-args.js.map +1 -1
  136. package/lib/commands/projects.d.ts +3 -4
  137. package/lib/commands/projects.d.ts.map +1 -1
  138. package/lib/commands/projects.js +2 -2
  139. package/lib/commands/projects.js.map +1 -1
  140. package/lib/commands/query-args.d.ts.map +1 -1
  141. package/lib/commands/query-args.js +6 -5
  142. package/lib/commands/query-args.js.map +1 -1
  143. package/lib/commands/query.d.ts +6 -5
  144. package/lib/commands/query.d.ts.map +1 -1
  145. package/lib/commands/query.js +5 -4
  146. package/lib/commands/query.js.map +1 -1
  147. package/lib/commands/reflect-args.d.ts +3 -2
  148. package/lib/commands/reflect-args.d.ts.map +1 -1
  149. package/lib/commands/reflect-args.js +13 -10
  150. package/lib/commands/reflect-args.js.map +1 -1
  151. package/lib/commands/reflect-driver.js +2 -1
  152. package/lib/commands/reflect-driver.js.map +1 -1
  153. package/lib/commands/reflect-report.js.map +1 -1
  154. package/lib/commands/reflect.js +1 -1
  155. package/lib/commands/reflect.js.map +1 -1
  156. package/lib/commands/save-args.d.ts.map +1 -1
  157. package/lib/commands/save-args.js +7 -4
  158. package/lib/commands/save-args.js.map +1 -1
  159. package/lib/commands/save.d.ts +8 -4
  160. package/lib/commands/save.d.ts.map +1 -1
  161. package/lib/commands/save.js +12 -23
  162. package/lib/commands/save.js.map +1 -1
  163. package/lib/commands/validate-args.d.ts +3 -2
  164. package/lib/commands/validate-args.d.ts.map +1 -1
  165. package/lib/commands/validate-args.js +13 -10
  166. package/lib/commands/validate-args.js.map +1 -1
  167. package/lib/commands/validate-driver.js +2 -1
  168. package/lib/commands/validate-driver.js.map +1 -1
  169. package/lib/commands/validate-report.js.map +1 -1
  170. package/lib/commands/validate.js +1 -1
  171. package/lib/commands/validate.js.map +1 -1
  172. package/lib/commands/watch-args.d.ts.map +1 -1
  173. package/lib/commands/watch-args.js +6 -4
  174. package/lib/commands/watch-args.js.map +1 -1
  175. package/lib/commands/watch.d.ts +3 -4
  176. package/lib/commands/watch.d.ts.map +1 -1
  177. package/lib/commands/watch.js +20 -2
  178. package/lib/commands/watch.js.map +1 -1
  179. package/lib/driver-heap.d.ts +52 -0
  180. package/lib/driver-heap.d.ts.map +1 -0
  181. package/lib/driver-heap.js +51 -0
  182. package/lib/driver-heap.js.map +1 -0
  183. package/lib/index.d.ts +2 -0
  184. package/lib/index.d.ts.map +1 -1
  185. package/lib/index.js +2 -0
  186. package/lib/index.js.map +1 -1
  187. package/lib/log-level.d.ts +21 -11
  188. package/lib/log-level.d.ts.map +1 -1
  189. package/lib/log-level.js +25 -14
  190. package/lib/log-level.js.map +1 -1
  191. package/lib/spawn-data-server.d.ts +9 -4
  192. package/lib/spawn-data-server.d.ts.map +1 -1
  193. package/lib/spawn-data-server.js +5 -4
  194. package/lib/spawn-data-server.js.map +1 -1
  195. package/lib/stderr-connection-logger.d.ts +12 -0
  196. package/lib/stderr-connection-logger.d.ts.map +1 -0
  197. package/lib/stderr-connection-logger.js +16 -0
  198. package/lib/stderr-connection-logger.js.map +1 -0
  199. package/lib/testing/echo-server.js +9 -1
  200. package/lib/testing/echo-server.js.map +1 -1
  201. package/package.json +91 -39
  202. package/src/cli.ts +7 -2
  203. package/src/commands/analyze-heap-args.ts +1 -2
  204. package/src/commands/analyze-heap.ts +4 -1
  205. package/src/commands/ast-ground-truth-args.ts +15 -12
  206. package/src/commands/ast-ground-truth-driver.ts +4 -2
  207. package/src/commands/ast-ground-truth.ts +1 -1
  208. package/src/commands/client-session.ts +33 -0
  209. package/src/commands/create-args.ts +68 -0
  210. package/src/commands/create.ts +83 -0
  211. package/src/commands/delete-args.ts +64 -0
  212. package/src/commands/delete.ts +61 -0
  213. package/src/commands/generate-ast-builder.ts +1 -11
  214. package/src/commands/generate-languages.ts +132 -0
  215. package/src/commands/generate-transfer-builder.ts +101 -0
  216. package/src/commands/generate-transfer-model-args.ts +20 -5
  217. package/src/commands/generate-transfer-model-config.ts +8 -3
  218. package/src/commands/generate-transfer-model.ts +291 -65
  219. package/src/commands/generated-module-path.ts +22 -0
  220. package/src/commands/harness-args.ts +99 -31
  221. package/src/commands/headless-harness.ts +89 -28
  222. package/src/commands/init-args.ts +76 -31
  223. package/src/commands/init-hosts.ts +51 -0
  224. package/src/commands/init-prompt.ts +5 -6
  225. package/src/commands/init-templates.ts +3161 -756
  226. package/src/commands/init-wizard.ts +55 -31
  227. package/src/commands/init-workspace.ts +31 -16
  228. package/src/commands/init.ts +266 -63
  229. package/src/commands/kebab.ts +17 -0
  230. package/src/commands/lint-grammar-args.ts +17 -13
  231. package/src/commands/lint-grammar-driver.ts +2 -1
  232. package/src/commands/lint-grammar.ts +1 -1
  233. package/src/commands/measure-memory-args.ts +27 -14
  234. package/src/commands/measure-memory-driver.ts +5 -3
  235. package/src/commands/measure-memory.ts +1 -1
  236. package/src/commands/model-docs-args.ts +16 -13
  237. package/src/commands/model-docs-driver.ts +2 -1
  238. package/src/commands/model-docs.ts +1 -1
  239. package/src/commands/projects-args.ts +5 -4
  240. package/src/commands/projects.ts +4 -6
  241. package/src/commands/query-args.ts +6 -4
  242. package/src/commands/query.ts +9 -9
  243. package/src/commands/reflect-args.ts +17 -13
  244. package/src/commands/reflect-driver.ts +2 -1
  245. package/src/commands/reflect.ts +1 -1
  246. package/src/commands/save-args.ts +7 -3
  247. package/src/commands/save.ts +24 -27
  248. package/src/commands/validate-args.ts +17 -13
  249. package/src/commands/validate-driver.ts +2 -1
  250. package/src/commands/validate.ts +1 -1
  251. package/src/commands/watch-args.ts +6 -3
  252. package/src/commands/watch.ts +28 -7
  253. package/src/driver-heap.ts +65 -0
  254. package/src/index.ts +2 -0
  255. package/src/log-level.ts +42 -14
  256. package/src/spawn-data-server.ts +17 -5
  257. package/src/stderr-connection-logger.ts +18 -0
  258. package/src/testing/echo-server.ts +11 -2
  259. package/templates/client/gitignore +3 -0
  260. package/templates/client/src/__GRAMMAR_ID__-diagram-module.ts +82 -0
  261. package/templates/editorconfig +15 -0
  262. package/templates/gitignore +1 -0
  263. package/templates/host-browser/esbuild.mjs +82 -0
  264. package/templates/host-browser/gitignore +4 -0
  265. package/templates/host-browser/hydranium-mark.svg +40 -0
  266. package/templates/host-browser/index.html +287 -0
  267. package/templates/host-browser/playwright.config.mts +24 -0
  268. package/templates/host-browser/src/data-status.ts +15 -0
  269. package/templates/host-browser/src/diagram-save.ts +60 -0
  270. package/templates/host-browser/src/diagram.ts +118 -0
  271. package/templates/host-browser/src/editor.ts +102 -0
  272. package/templates/host-browser/src/log.ts +14 -0
  273. package/templates/host-browser/src/monaco-editor-core-worker.d.ts +7 -0
  274. package/templates/host-browser/src/monaco-editor-worker.ts +10 -0
  275. package/templates/host-browser/src/posix-path-shim.ts +23 -0
  276. package/templates/host-browser/src/shown-file.ts +61 -0
  277. package/templates/host-browser/src/workspace.ts +5 -0
  278. package/templates/host-browser/tsconfig.e2e.json +16 -0
  279. package/templates/host-browser/tsconfig.page.json +16 -0
  280. package/templates/host-browser/tsconfig.worker.json +16 -0
  281. package/templates/host-theia-browser/esbuild.mjs +24 -0
  282. package/templates/host-theia-browser/gitignore +9 -0
  283. package/templates/host-theia-browser/playwright.config.mts +42 -0
  284. package/templates/host-theia-browser/start.mjs +50 -0
  285. package/templates/host-theia-browser/tsconfig.e2e.json +16 -0
  286. package/templates/host-theia-electron/esbuild.mjs +27 -0
  287. package/templates/host-theia-electron/gitignore +8 -0
  288. package/templates/host-theia-electron/playwright.config.mts +12 -0
  289. package/templates/host-theia-electron/start.mjs +47 -0
  290. package/templates/host-theia-electron/tsconfig.e2e.json +16 -0
  291. package/templates/host-vscode/esbuild.mjs +38 -0
  292. package/templates/host-vscode/gitignore +7 -0
  293. package/templates/host-vscode/playwright.config.mts +14 -0
  294. package/templates/host-vscode/src/__GRAMMAR_ID__-diagram-editor.ts +19 -0
  295. package/templates/host-vscode/src/show-model-command.ts +24 -0
  296. package/templates/host-vscode/src/webview/__GRAMMAR_ID__-diagram.ts +18 -0
  297. package/templates/host-vscode/src/webview/diagram.css +40 -0
  298. package/templates/host-vscode/start.mjs +22 -0
  299. package/templates/host-vscode/test/install-vscode.mts +21 -0
  300. package/templates/host-vscode/test/vscode-version.mts +10 -0
  301. package/templates/host-vscode/tsconfig.e2e.json +17 -0
  302. package/templates/host-vscode/tsconfig.json +19 -0
  303. package/templates/host-vscode/tsconfig.webview.json +17 -0
  304. package/templates/oxlintrc.json +4 -0
  305. package/templates/protocol/gitignore +3 -0
  306. package/templates/protocol/src/__GRAMMAR_ID__-diagram-types.ts +47 -0
  307. package/templates/server/gitignore +6 -0
  308. package/templates/server/src/glsp/__GRAMMAR_ID__/create-node-operation-handler.ts +62 -0
  309. package/templates/server/src/glsp/__GRAMMAR_ID__/delete-operation-handler.ts +63 -0
  310. package/templates/server/src/glsp/__GRAMMAR_ID__/gmodel-factory.ts +144 -0
  311. package/templates/server/src/glsp/__GRAMMAR_ID__/state.ts +18 -0
  312. package/templates/server/src/glsp/__GRAMMAR_ID__/storage.ts +16 -0
  313. package/templates/server/src/glsp/__GRAMMAR_ID__/submission-handler.ts +24 -0
  314. package/templates/server/src/grammar/common.langium +12 -0
  315. package/templates/server/src/language-server/__GRAMMAR_ID__-serializer.ts +53 -0
  316. package/templates/server/src/language-server/__GRAMMAR_ID__-validation.ts +30 -0
  317. package/templates/server/src/language-server/ast.ts +23 -0
  318. package/templates/server/src/node/services.ts +16 -0
  319. package/templates/server/test/__GRAMMAR_ID__-diagram.test.ts +388 -0
  320. package/templates/server/test/tsconfig.json +4 -0
  321. package/templates/server/tsconfig.test.json +10 -0
  322. package/templates/server/vitest.config.ts +14 -0
  323. package/templates/theia/gitignore +3 -0
  324. package/templates/theia/src/browser/__GRAMMAR_ID__-diagram-frontend-module.ts +65 -0
  325. package/templates/theia/src/browser/__PROJECT_ID__-connection-preload-module.ts +15 -0
  326. package/templates/theia/src/browser/__PROJECT_ID__-data-connection.ts +15 -0
  327. package/templates/theia/src/browser/__PROJECT_ID__-data-port.ts +11 -0
  328. package/templates/theia/src/browser/__PROJECT_ID__-glsp-client-contribution.ts +13 -0
  329. package/templates/theia/src/browser/__PROJECT_ID__-show-model-command.ts +51 -0
  330. package/templates/theia/src/common/__PROJECT_ID__-glsp-contribution.ts +3 -0
  331. package/templates/theia/src/node/__PROJECT_ID__-connection-backend-module.ts +12 -0
  332. package/templates/theia/src/node/__PROJECT_ID__-data-backend-module.ts +15 -0
  333. package/templates/theia/src/node/__PROJECT_ID__-glsp-backend-module.ts +16 -0
  334. package/templates/theia/style/diagram.css +20 -0
  335. package/templates/theia/tsconfig.json +24 -0
  336. package/templates/vscode-servers/gitignore +4 -0
  337. package/templates/vscode-servers/tsconfig.json +19 -0
  338. package/templates/workspace/nodes.__EXTENSION__ +5 -0
  339. package/templates/workspace/references.__EXTENSION__ +2 -0
@@ -10,15 +10,19 @@
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
12
  import { emitAstBuilder } from './generate-ast-builder.js';
13
+ import { emitLanguages } from './generate-languages.js';
14
+ import { emitTransferBuilder } from './generate-transfer-builder.js';
13
15
  import {
14
16
  type Node,
15
17
  type ObjectLiteralExpression,
16
18
  Project,
17
19
  SyntaxKind,
20
+ ts,
18
21
  type ClassDeclaration,
19
22
  type InterfaceDeclaration,
20
23
  type SourceFile
21
24
  } from 'ts-morph';
25
+ import { describeError } from '@hydranium/protocol';
22
26
 
23
27
  /**
24
28
  * Options for the transfer-model generator.
@@ -45,6 +49,28 @@ export interface GenerateTransferModelOptions {
45
49
  * and not the other has the two disagreeing about the same grammar.
46
50
  */
47
51
  astBuilderFile?: string;
52
+ /**
53
+ * Destination for a generated test module binding `makeTransferNodeBuilder`
54
+ * from `@hydranium/protocol/testing` to the transfer model, as `transferNode`,
55
+ * the way {@link astBuilderFile} binds `astNode`. Belongs in a test tree:
56
+ * production code has no use for a builder that fills grammar defaults.
57
+ *
58
+ * When it sits in another package than {@link outFile}, it imports the model
59
+ * by that package's name, so the package's main entry must re-export the
60
+ * generated model, and with it each type's constant: an interface the entry
61
+ * narrows hides the generated constant of the same name unless the entry
62
+ * re-exports that constant too, and the builder keeps the generated
63
+ * interface unless the entry declares `<LanguageId>TransferTypes` with the
64
+ * narrowed entry.
65
+ */
66
+ transferBuilderFile?: string;
67
+ /**
68
+ * Destination for a generated module stating each grammar's language id and
69
+ * file extensions, read from the `module.ts` beside {@link astFile}. Lets a
70
+ * package without Langium, such as one shared with clients, name the ids
71
+ * without copying them by hand.
72
+ */
73
+ languagesFile?: string;
48
74
  /** Name used for the base element type in the output. Defaults to `TransferElement`. */
49
75
  elementTypeName?: string;
50
76
  /** Name used for the terminal-patterns const in the output. Defaults to `ModelTerminals`. */
@@ -86,6 +112,15 @@ interface AugmentedProperty {
86
112
 
87
113
  interface ReflectionEntry {
88
114
  superTypes: string[];
115
+ properties: ReflectionProperty[];
116
+ }
117
+
118
+ /** One grammar property of a reflection entry; `defaultValue` keeps the source text of the literal. */
119
+ interface ReflectionProperty {
120
+ name: string;
121
+ defaultValue?: string;
122
+ optional: boolean;
123
+ referenceType?: string;
89
124
  }
90
125
 
91
126
  const DEFAULT_ELEMENT_TYPE_NAME = 'TransferElement';
@@ -98,38 +133,67 @@ const DEFAULT_REGEN_COMMAND = 'Run the hydranium-cli generate-transfer-model com
98
133
  */
99
134
  const NAMESPACED_TERMINALS_NAME = 'Terminals';
100
135
 
136
+ /** How often `--watch` polls its input files. */
137
+ const WATCH_POLL_INTERVAL_MS = 250;
138
+
101
139
  /**
102
- * Generate once, then re-generate whenever the AST or augmentation file changes,
140
+ * Generate once, then re-generate whenever an input file changes (the AST, the
141
+ * augmentation file, and `module.ts` when a languages file is written),
103
142
  * until `signal` aborts (the CLI wires this to SIGINT). Regeneration is debounced
104
143
  * so an editor's multi-event save fires a single run, and a generation error is
105
144
  * logged without tearing down the watch. Resolves when the signal aborts.
145
+ *
146
+ * Polled by path rather than watched through OS events: langium-cli removes
147
+ * its output directory and writes the files back, which an event watch, Node's
148
+ * and chokidar's alike, stops hearing after the first run. A run that finds an
149
+ * input missing is skipped, since the file's return is a change of its own, so
150
+ * nothing generates from a half-written output.
106
151
  */
107
152
  export function watchTransferModel(options: GenerateTransferModelOptions, context: { signal: AbortSignal }): Promise<void> {
108
153
  const runSafely = (): void => {
109
154
  try {
110
155
  generateTransferModel(options);
111
156
  } catch (error: unknown) {
112
- console.error(`Transfer-model generation failed: ${error instanceof Error ? error.message : String(error)}`);
157
+ console.error(`Transfer-model generation failed: ${describeError(error)}`);
113
158
  }
114
159
  };
115
160
  runSafely();
116
- console.log(`Watching ${options.astFile} and ${options.augmentationFile} for changes. Press Ctrl-C to stop.`);
161
+ // A grammar's language id and extensions reach only `module.ts`, so the
162
+ // languages module would go stale without it.
163
+ const watched = [
164
+ options.astFile,
165
+ options.augmentationFile,
166
+ ...(options.languagesFile !== undefined ? [languagesModuleFile(options)] : [])
167
+ ];
117
168
 
118
169
  let timer: NodeJS.Timeout | undefined;
119
- const scheduleRerun = (): void => {
170
+ const rerun = (): void => {
171
+ if (watched.every(file => fs.existsSync(file))) {
172
+ runSafely();
173
+ }
174
+ };
175
+ // The poll reports every stat it takes; a replaced file has a new inode, a
176
+ // rewritten one a new mtime, a removed or returning one zeroed stats.
177
+ const onPoll = (current: fs.Stats, previous: fs.Stats): void => {
178
+ if (current.ino === previous.ino && current.mtimeMs === previous.mtimeMs && current.size === previous.size) {
179
+ return;
180
+ }
120
181
  if (timer) {
121
182
  clearTimeout(timer);
122
183
  }
123
- timer = setTimeout(runSafely, 150);
184
+ timer = setTimeout(rerun, 150);
124
185
  };
125
- const watchers = [options.astFile, options.augmentationFile].map(file => fs.watch(file, scheduleRerun));
186
+ for (const file of watched) {
187
+ fs.watchFile(file, { interval: WATCH_POLL_INTERVAL_MS }, onPoll);
188
+ }
189
+ console.log(`Watching ${watched.join(', ')} for changes. Press Ctrl-C to stop.`);
126
190
 
127
191
  return new Promise<void>(resolve => {
128
192
  context.signal.addEventListener(
129
193
  'abort',
130
194
  () => {
131
- for (const watcher of watchers) {
132
- watcher.close();
195
+ for (const file of watched) {
196
+ fs.unwatchFile(file, onPoll);
133
197
  }
134
198
  if (timer) {
135
199
  clearTimeout(timer);
@@ -141,6 +205,11 @@ export function watchTransferModel(options: GenerateTransferModelOptions, contex
141
205
  });
142
206
  }
143
207
 
208
+ /** The langium-cli `module.ts` beside the AST file, which the languages module is read from. */
209
+ function languagesModuleFile(options: GenerateTransferModelOptions): string {
210
+ return path.join(path.dirname(options.astFile), 'module.ts');
211
+ }
212
+
144
213
  /** Run the transfer-model generator. */
145
214
  export function generateTransferModel(options: GenerateTransferModelOptions): void {
146
215
  const elementTypeName = options.elementTypeName ?? DEFAULT_ELEMENT_TYPE_NAME;
@@ -178,15 +247,19 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
178
247
  astInterfaces.set(iface.getName(), iface);
179
248
  }
180
249
 
181
- const typeAliases: Array<{ name: string; definition: string }> = [];
250
+ const typeAliases: Array<{ name: string; definition: string; members: string[] }> = [];
182
251
  for (const alias of astSource.getTypeAliases()) {
183
252
  const name = alias.getName();
184
253
  if (skipTypeAliases.has(name)) {
185
254
  continue;
186
255
  }
187
- const typeText = alias.getTypeNode()?.getText();
188
- if (typeText) {
189
- typeAliases.push({ name, definition: mapType(resolveNamespacedStringUnion(astSource, typeText), elementTypeName) });
256
+ const typeNode = alias.getTypeNode();
257
+ if (typeNode) {
258
+ typeAliases.push({
259
+ name,
260
+ definition: mapType(resolveNamespacedStringUnion(astSource, typeNode.getText()), elementTypeName),
261
+ members: unionMembers(typeNode).map(member => mapType(member, elementTypeName))
262
+ });
190
263
  }
191
264
  }
192
265
 
@@ -203,7 +276,6 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
203
276
  // 7. Classify interfaces: abstract (union $type) vs concrete (literal $type).
204
277
  const interfaceNames: string[] = [];
205
278
  const abstractTypes = new Set<string>();
206
- const concreteTypes = new Set<string>();
207
279
 
208
280
  for (const name of Object.keys(reflectionTypes)) {
209
281
  const iface = astInterfaces.get(name);
@@ -215,8 +287,6 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
215
287
  const typeText = iface.getProperty('$type')?.getTypeNode()?.getText() ?? '';
216
288
  if (typeText.includes('|')) {
217
289
  abstractTypes.add(name);
218
- } else {
219
- concreteTypes.add(name);
220
290
  }
221
291
  }
222
292
 
@@ -258,12 +328,13 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
258
328
  out.push('}');
259
329
  out.push('');
260
330
 
261
- // Type constants for concrete types.
331
+ // Langium's per-type constant under the same name, for every reflection type
332
+ // the model emits, abstract ones and unions included.
333
+ const reflectedTypes = Object.keys(reflectionTypes).filter(name => astInterfaces.has(name) || typeAliasNames.has(name));
262
334
  out.push('// --- Type Constants ---');
263
- for (const name of sorted) {
264
- if (concreteTypes.has(name)) {
265
- out.push(`export const ${name}Type = '${name}';`);
266
- }
335
+ for (const name of reflectedTypes) {
336
+ const keys = reflectionTypes[name].properties.map(property => `${property.name}: '${property.name}'`);
337
+ out.push(`export const ${name} = { ${[`$type: '${name}'`, ...keys].join(', ')} } as const;`);
267
338
  }
268
339
  out.push('');
269
340
 
@@ -274,7 +345,7 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
274
345
  out.push(`export type ${name} = ${definition};`);
275
346
  const literals = extractStringLiterals(definition);
276
347
  if (literals) {
277
- const items = literals.map(l => `'${l}'`).join(', ');
348
+ const items = literals.map(quote).join(', ');
278
349
  out.push(`export const ${name}Values = [${items}] as const;`);
279
350
  }
280
351
  }
@@ -305,7 +376,7 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
305
376
  const typeValue = iface.getProperty('$type')?.getTypeNode()?.getText() ?? `'${name}'`;
306
377
  out.push(` readonly $type: ${typeValue};`);
307
378
  } else {
308
- out.push(` readonly $type: typeof ${name}Type;`);
379
+ out.push(` readonly $type: '${name}';`);
309
380
  }
310
381
 
311
382
  const emitted = new Set<string>(['$type']);
@@ -367,24 +438,24 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
367
438
  const checks = members.map(m => `item.$type === '${m}'`).join(' || ');
368
439
  out.push(` return is${elementTypeName}(item) && (${checks});`);
369
440
  } else {
370
- out.push(` return is${elementTypeName}(item) && item.$type === ${name}Type;`);
441
+ out.push(` return is${elementTypeName}(item) && item.$type === '${name}';`);
371
442
  }
372
443
  out.push('}');
373
444
  out.push('');
374
445
  }
375
446
 
376
- for (const { name, definition } of typeAliases) {
447
+ for (const { name, definition, members } of typeAliases) {
377
448
  const literals = extractStringLiterals(definition);
378
449
  if (literals) {
379
- const checks = literals.map(l => `item === '${l}'`).join(' || ');
450
+ const checks = literals.map(l => `item === ${quote(l)}`).join(' || ') || 'false';
380
451
  out.push(`export function is${name}(item: unknown): item is ${name} {`);
381
452
  out.push(` return ${checks};`);
382
453
  out.push('}');
383
454
  out.push('');
384
455
  } else {
385
- // Union (or single) of named members and/or Langium data-type primitives. Each member maps
386
- // to its own check (typeof / instanceof / named-guard) via typeAliasMemberGuard.
387
- const members = definition.split('|').map(s => s.trim());
456
+ // Union (or single) of named members, Langium data-type primitives and the string
457
+ // literals a data-type rule's keywords add beside them. Each member maps to its own
458
+ // check via typeAliasMemberGuard.
388
459
  const checks = members.map(typeAliasMemberGuard).join(' || ');
389
460
  out.push(`export function is${name}(item: unknown): item is ${name} {`);
390
461
  out.push(` return ${checks};`);
@@ -393,6 +464,9 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
393
464
  }
394
465
  }
395
466
 
467
+ out.push(...renderTypeMap(languageId, reflectedTypes));
468
+ out.push(...renderReflection(languageId, elementTypeName, reflectedTypes, reflectionTypes));
469
+
396
470
  // Coverage check: warn on reflection types not emitted.
397
471
  const processedSet = new Set(sorted);
398
472
  for (const name of Object.keys(reflectionTypes)) {
@@ -422,6 +496,107 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
422
496
  console.warn(`Warning: no '${languageId}AstType' alias in ${options.astFile}; skipping the AST builder.`);
423
497
  }
424
498
  }
499
+
500
+ if (options.transferBuilderFile !== undefined) {
501
+ emitTransferBuilder({ outFile: options.transferBuilderFile, transferModelFile: options.outFile, languageId, regenCommand });
502
+ }
503
+
504
+ if (options.languagesFile !== undefined) {
505
+ const moduleSource = project.addSourceFileAtPath(languagesModuleFile(options));
506
+ emitLanguages(moduleSource, { outFile: options.languagesFile, regenCommand });
507
+ }
508
+ }
509
+
510
+ /**
511
+ * `<LanguageId>TransferTypes`, each `$type` to its interface, so an encoder or a
512
+ * builder can type a node by the grammar the way Langium's `<LanguageId>AstType`
513
+ * does on the AST side.
514
+ */
515
+ function renderTypeMap(languageId: string, types: readonly string[]): string[] {
516
+ return ['// --- Type Map ---', `export type ${languageId}TransferTypes = {`, ...types.map(name => ` ${name}: ${name};`), '};', ''];
517
+ }
518
+
519
+ /**
520
+ * `<LanguageId>TransferReflection`, the grammar metadata with the method names
521
+ * Langium's `AstReflection` uses. A plain object with no framework import, so
522
+ * the file stays self-contained, and nothing references it but its importers:
523
+ * the `is` guards test `$type` directly, so a guard never pulls it in. `types`
524
+ * keeps its literal type, which is how the transfer-node builder tells which
525
+ * fields the grammar defaults.
526
+ */
527
+ function renderReflection(
528
+ languageId: string,
529
+ elementTypeName: string,
530
+ types: readonly string[],
531
+ reflectionTypes: Record<string, ReflectionEntry>
532
+ ): string[] {
533
+ const metaData = `${languageId}TransferTypeMetaData`;
534
+ // Index signatures, not `Readonly<Record<…>>`: a grammar type may be named
535
+ // `Record` or `Readonly`, which Langium does not reserve as it does `Object`.
536
+ const out = [
537
+ '// --- Reflection ---',
538
+ `export interface ${metaData} {`,
539
+ ' readonly name: string;',
540
+ ' readonly properties: {',
541
+ ' readonly [name: string]: { readonly name: string; readonly defaultValue?: unknown; readonly optional?: boolean; readonly referenceType?: string };',
542
+ ' };',
543
+ ' readonly superTypes: readonly string[];',
544
+ '}',
545
+ '',
546
+ `export const ${languageId}TransferReflection = {`,
547
+ ' types: {'
548
+ ];
549
+ for (const name of types) {
550
+ const { properties, superTypes } = reflectionTypes[name];
551
+ const propertyEntries = properties.map(property => {
552
+ const fields = [`name: '${property.name}'`];
553
+ if (property.defaultValue !== undefined) {
554
+ fields.push(`defaultValue: ${property.defaultValue}`);
555
+ }
556
+ if (property.optional) {
557
+ fields.push('optional: true');
558
+ }
559
+ if (property.referenceType !== undefined) {
560
+ fields.push(`referenceType: '${property.referenceType}'`);
561
+ }
562
+ return `${property.name}: { ${fields.join(', ')} }`;
563
+ });
564
+ const supers = superTypes.map(superType => `'${superType}'`).join(', ');
565
+ const propertiesText = propertyEntries.length > 0 ? `{ ${propertyEntries.join(', ')} }` : '{}';
566
+ out.push(` ${name}: { name: '${name}', properties: ${propertiesText}, superTypes: [${supers}] },`);
567
+ }
568
+ out.push(
569
+ ` } as const satisfies { readonly [type: string]: ${metaData} },`,
570
+ '',
571
+ ' getAllTypes(): string[] {',
572
+ ' return Object.keys(this.types);',
573
+ ' },',
574
+ '',
575
+ // An own-property check, not `types[type] ?? …`: a name the object
576
+ // prototype carries, `toString` say, would read the inherited member and
577
+ // make `isSubtype` throw. `hasOwnProperty.call` rather than `Object.hasOwn`
578
+ // because the adopter's protocol package may target a lib without it.
579
+ ` getTypeMetaData(type: string): ${metaData} {`,
580
+ ' return Object.prototype.hasOwnProperty.call(this.types, type)',
581
+ ` ? (this.types as { readonly [type: string]: ${metaData} })[type]`,
582
+ ' : { name: type, properties: {}, superTypes: [] };',
583
+ ' },',
584
+ '',
585
+ ' isSubtype(subtype: string, supertype: string): boolean {',
586
+ ' return subtype === supertype || this.getTypeMetaData(subtype).superTypes.some(type => this.isSubtype(type, supertype));',
587
+ ' },',
588
+ '',
589
+ ' getAllSubTypes(type: string): string[] {',
590
+ ' return this.getAllTypes().filter(candidate => this.isSubtype(candidate, type));',
591
+ ' },',
592
+ '',
593
+ ' isInstance(item: unknown, type: string): boolean {',
594
+ ` return is${elementTypeName}(item) && this.isSubtype(item.$type, type);`,
595
+ ' }',
596
+ '};',
597
+ ''
598
+ );
599
+ return out;
425
600
  }
426
601
 
427
602
  // ---------------------------------------------------------------------------
@@ -462,10 +637,11 @@ function findAstReflectionClass(source: SourceFile): ClassDeclaration {
462
637
  }
463
638
 
464
639
  /**
465
- * Extract `superTypes` for each entry of the reflection class's `types` property
466
- * initializer. We rely on the Langium convention that `X.$type === 'X'`, so a
467
- * supertype expression resolves to the literal type-name string by stripping
468
- * `.$type`.
640
+ * Extract `superTypes` and the grammar properties for each entry of the
641
+ * reflection class's `types` property initializer. We rely on the Langium
642
+ * convention that `X.$type === 'X'` and `X.p === 'p'`, so a type expression
643
+ * resolves to the literal type-name string by stripping `.$type`, and a
644
+ * property's name is its key.
469
645
  */
470
646
  function parseReflectionTypes(reflectionClass: ClassDeclaration): Record<string, ReflectionEntry> {
471
647
  const result: Record<string, ReflectionEntry> = {};
@@ -487,29 +663,35 @@ function parseReflectionTypes(reflectionClass: ClassDeclaration): Record<string,
487
663
  const value = entry.getLastChildByKind(SyntaxKind.ObjectLiteralExpression);
488
664
  const supersProp = value?.getProperty('superTypes');
489
665
  const supersInit = supersProp?.getFirstChildByKind(SyntaxKind.ArrayLiteralExpression);
490
- const superTypes: string[] = [];
491
- if (supersInit) {
492
- for (const element of supersInit.getElements()) {
493
- const text = element.getText();
494
- const dotMatch = /^([A-Za-z_$][\w$]*)\.\$type$/.exec(text);
495
- if (dotMatch) {
496
- superTypes.push(dotMatch[1]);
497
- continue;
498
- }
499
- // Bare identifier or quoted string fall-back.
500
- const stringMatch = /^['"](.+)['"]$/.exec(text);
501
- if (stringMatch) {
502
- superTypes.push(stringMatch[1]);
503
- } else {
504
- superTypes.push(text);
505
- }
506
- }
507
- }
508
- result[name] = { superTypes };
666
+ const superTypes = supersInit?.getElements().map(element => typeNameOf(element.getText())) ?? [];
667
+ result[name] = { superTypes, properties: parseReflectionProperties(value) };
509
668
  }
510
669
  return result;
511
670
  }
512
671
 
672
+ function parseReflectionProperties(entry: ObjectLiteralExpression | undefined): ReflectionProperty[] {
673
+ const propertiesInit = entry?.getProperty('properties')?.getFirstChildByKind(SyntaxKind.ObjectLiteralExpression);
674
+ const properties: ReflectionProperty[] = [];
675
+ for (const property of propertiesInit?.getChildrenOfKind(SyntaxKind.PropertyAssignment) ?? []) {
676
+ const meta = property.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);
677
+ const field = (key: string): string | undefined =>
678
+ meta?.getProperty(key)?.asKind(SyntaxKind.PropertyAssignment)?.getInitializer()?.getText();
679
+ const referenceType = field('referenceType');
680
+ properties.push({
681
+ name: property.getName(),
682
+ defaultValue: field('defaultValue'),
683
+ optional: field('optional') === 'true',
684
+ referenceType: referenceType === undefined ? undefined : typeNameOf(referenceType)
685
+ });
686
+ }
687
+ return properties;
688
+ }
689
+
690
+ /** `X.$type` or a quoted `'X'` to `X`; any other text passes through. */
691
+ function typeNameOf(text: string): string {
692
+ return /^([A-Za-z_$][\w$]*)\.\$type$/.exec(text)?.[1] ?? /^['"](.+)['"]$/.exec(text)?.[1] ?? text;
693
+ }
694
+
513
695
  /**
514
696
  * Collect terminal regex literals from the AST file.
515
697
  *
@@ -647,12 +829,16 @@ function mapType(text: string, elementTypeName: string): string {
647
829
 
648
830
  /**
649
831
  * Type-guard expression (against an `item: unknown`) for one member of a non-literal type-alias
650
- * union. Covers the full set of Langium data-type primitives: the `typeof`-checkable ones
651
- * (`string`/`number`/`boolean`/`bigint`) use `typeof`; `Date` — the one Langium primitive `typeof`
652
- * reports as `'object'` — uses `instanceof`; any other member is a named type whose generated
653
- * `is<Member>` guard is called.
832
+ * union. A string literal is compared as written, so the escapes Langium wrote stay valid.
833
+ * Covers the full set of Langium data-type primitives. The `typeof`-checkable ones
834
+ * (`string`/`number`/`boolean`/`bigint`) use `typeof`, and `Date`, the one Langium primitive
835
+ * `typeof` reports as `'object'`, uses `instanceof`. Any other member is a named type whose
836
+ * generated `is<Member>` guard is called.
654
837
  */
655
838
  export function typeAliasMemberGuard(member: string): string {
839
+ if (member.startsWith("'") || member.startsWith('"')) {
840
+ return `item === ${member}`;
841
+ }
656
842
  switch (member) {
657
843
  case 'string':
658
844
  case 'number':
@@ -676,10 +862,11 @@ export function typeAliasMemberGuard(member: string): string {
676
862
  * name.
677
863
  *
678
864
  * Each referenced member is inlined and de-duplicated, preserving first-seen
679
- * order. The definition is returned UNCHANGED when it holds no
680
- * namespace-qualified reference (the single-language case) or when any referenced
681
- * member is not a pure string-literal union — passing it through rather than
682
- * emitting a half-resolved union.
865
+ * order; when no member holds a literal, the union is `never`. The definition
866
+ * is returned UNCHANGED when it holds no namespace-qualified reference (the
867
+ * single-language case) or when any referenced member is not a pure
868
+ * string-literal union — passing it through rather than emitting a
869
+ * half-resolved union.
683
870
  */
684
871
  export function resolveNamespacedStringUnion(astSource: SourceFile, definition: string): string {
685
872
  const references = [...definition.matchAll(/\b([A-Za-z_]\w*)\.([A-Za-z_]\w*)\b/g)];
@@ -701,16 +888,55 @@ export function resolveNamespacedStringUnion(astSource: SourceFile, definition:
701
888
  }
702
889
  }
703
890
  }
704
- return literals.map(literal => `'${literal}'`).join(' | ');
891
+ return literals.length > 0 ? literals.map(quote).join(' | ') : 'never';
892
+ }
893
+
894
+ /**
895
+ * The members of a type-alias union as TypeScript parses them. If a data-type rule groups
896
+ * alternatives in parentheses, the alias Langium generates contains the same group, as in
897
+ * `'a' | ('b' | Keyword)`, so each group is flattened into its members. Splitting the text on
898
+ * `|` breaks such a group and any keyword that contains `|`, such as `'||'`, and the
899
+ * resulting guard does not compile.
900
+ */
901
+ function unionMembers(typeNode: Node): string[] {
902
+ if (typeNode.isKind(SyntaxKind.UnionType)) {
903
+ return typeNode.getTypeNodes().flatMap(member => unionMembers(member));
904
+ }
905
+ if (typeNode.isKind(SyntaxKind.ParenthesizedType)) {
906
+ return unionMembers(typeNode.getTypeNode());
907
+ }
908
+ return [typeNode.getText()];
705
909
  }
706
910
 
707
- /** Extract string literals from a union definition, or undefined if not pure literals. */
911
+ /**
912
+ * The values of a union of string literals, or undefined when the definition holds anything
913
+ * else. The TypeScript scanner decodes each literal, so a keyword with a quote in it reads the
914
+ * same whichever quotes and escapes Langium wrote it with. Langium writes the keyword names of
915
+ * a language without keywords as `never`, which adds no value.
916
+ */
708
917
  function extractStringLiterals(definition: string): string[] | undefined {
709
- const stringLiteral = '(?:\'[^\']*\'|"[^"]*")';
710
- if (!new RegExp(`^\\|?\\s*${stringLiteral}(\\s*\\|\\s*${stringLiteral})*$`).test(definition.trim())) {
711
- return undefined;
918
+ const scanner = ts.createScanner(ts.ScriptTarget.Latest, true, ts.LanguageVariant.Standard, definition);
919
+ const literals: string[] = [];
920
+ let members = 0;
921
+ for (let token = scanner.scan(); token !== ts.SyntaxKind.EndOfFileToken; token = scanner.scan()) {
922
+ if (token === ts.SyntaxKind.StringLiteral) {
923
+ literals.push(scanner.getTokenValue());
924
+ members++;
925
+ } else if (token === ts.SyntaxKind.NeverKeyword) {
926
+ members++;
927
+ } else if (token !== ts.SyntaxKind.BarToken) {
928
+ return undefined;
929
+ }
712
930
  }
713
- return [...definition.matchAll(/'([^']*)'|"([^"]*)"/g)].map(m => m[1] ?? m[2]);
931
+ return members > 0 ? literals : undefined;
932
+ }
933
+
934
+ /**
935
+ * `value` as a string literal in the single quotes the generated file uses. A keyword can
936
+ * contain a quote or a backslash, and either one ends or escapes the literal unless escaped.
937
+ */
938
+ function quote(value: string): string {
939
+ return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'`;
714
940
  }
715
941
 
716
942
  function topologicalSort(names: string[], getDeps: (n: string) => string[]): string[] {
@@ -0,0 +1,22 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 EclipseSource and others.
3
+ *
4
+ * This program and the accompanying materials are made available under the
5
+ * terms of the MIT License which is available in the project root.
6
+ *
7
+ * SPDX-License-Identifier: MIT
8
+ ********************************************************************************/
9
+
10
+ import * as path from 'path';
11
+
12
+ /**
13
+ * Module specifier for `targetFile` as seen from the generated module
14
+ * `outFile`'s own directory. Emitted with a `.js` extension and posix
15
+ * separators because the output is ESM TypeScript, where the specifier names
16
+ * the compiled sibling.
17
+ */
18
+ export function relativeImportPath(outFile: string, targetFile: string): string {
19
+ const relative = path.relative(path.dirname(path.resolve(outFile)), path.resolve(targetFile));
20
+ const posix = relative.split(path.sep).join('/').replace(/\.ts$/, '.js');
21
+ return posix.startsWith('.') ? posix : `./${posix}`;
22
+ }