@hydranium/cli 1.0.0-next.10

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 (267) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +240 -0
  3. package/heap-analysis/analyze-heap.mjs +798 -0
  4. package/heap-analysis/heap/core/aggregate.mjs +152 -0
  5. package/heap-analysis/heap/core/bucket-label.mjs +23 -0
  6. package/heap-analysis/heap/core/cutpoints.mjs +51 -0
  7. package/heap-analysis/heap/core/diff.mjs +56 -0
  8. package/heap-analysis/heap/core/format.mjs +47 -0
  9. package/heap-analysis/heap/core/holders.mjs +80 -0
  10. package/heap-analysis/heap/core/load.mjs +46 -0
  11. package/heap-analysis/heap/core/report.mjs +68 -0
  12. package/heap-analysis/heap/core/strings.mjs +80 -0
  13. package/heap-analysis/heap/langium/concepts.mjs +230 -0
  14. package/heap-analysis/heap/langium/glsp.classifier.mjs +80 -0
  15. package/heap-analysis/heap/langium/langium.classifier.mjs +203 -0
  16. package/heap-analysis/heap/renderer/renderer.mjs +122 -0
  17. package/lib/cli-version.d.ts +21 -0
  18. package/lib/cli-version.d.ts.map +1 -0
  19. package/lib/cli-version.js +30 -0
  20. package/lib/cli-version.js.map +1 -0
  21. package/lib/cli.d.ts +11 -0
  22. package/lib/cli.d.ts.map +1 -0
  23. package/lib/cli.js +135 -0
  24. package/lib/cli.js.map +1 -0
  25. package/lib/commands/analyze-heap-args.d.ts +46 -0
  26. package/lib/commands/analyze-heap-args.d.ts.map +1 -0
  27. package/lib/commands/analyze-heap-args.js +174 -0
  28. package/lib/commands/analyze-heap-args.js.map +1 -0
  29. package/lib/commands/analyze-heap.d.ts +37 -0
  30. package/lib/commands/analyze-heap.d.ts.map +1 -0
  31. package/lib/commands/analyze-heap.js +63 -0
  32. package/lib/commands/analyze-heap.js.map +1 -0
  33. package/lib/commands/ast-ground-truth-args.d.ts +23 -0
  34. package/lib/commands/ast-ground-truth-args.d.ts.map +1 -0
  35. package/lib/commands/ast-ground-truth-args.js +47 -0
  36. package/lib/commands/ast-ground-truth-args.js.map +1 -0
  37. package/lib/commands/ast-ground-truth-driver.d.ts +10 -0
  38. package/lib/commands/ast-ground-truth-driver.d.ts.map +1 -0
  39. package/lib/commands/ast-ground-truth-driver.js +72 -0
  40. package/lib/commands/ast-ground-truth-driver.js.map +1 -0
  41. package/lib/commands/ast-ground-truth.d.ts +31 -0
  42. package/lib/commands/ast-ground-truth.d.ts.map +1 -0
  43. package/lib/commands/ast-ground-truth.js +36 -0
  44. package/lib/commands/ast-ground-truth.js.map +1 -0
  45. package/lib/commands/generate-transfer-model-args.d.ts +38 -0
  46. package/lib/commands/generate-transfer-model-args.d.ts.map +1 -0
  47. package/lib/commands/generate-transfer-model-args.js +183 -0
  48. package/lib/commands/generate-transfer-model-args.js.map +1 -0
  49. package/lib/commands/generate-transfer-model-config.d.ts +32 -0
  50. package/lib/commands/generate-transfer-model-config.d.ts.map +1 -0
  51. package/lib/commands/generate-transfer-model-config.js +122 -0
  52. package/lib/commands/generate-transfer-model-config.js.map +1 -0
  53. package/lib/commands/generate-transfer-model.d.ts +110 -0
  54. package/lib/commands/generate-transfer-model.d.ts.map +1 -0
  55. package/lib/commands/generate-transfer-model.js +587 -0
  56. package/lib/commands/generate-transfer-model.js.map +1 -0
  57. package/lib/commands/harness-args.d.ts +198 -0
  58. package/lib/commands/harness-args.d.ts.map +1 -0
  59. package/lib/commands/harness-args.js +339 -0
  60. package/lib/commands/harness-args.js.map +1 -0
  61. package/lib/commands/headless-harness.d.ts +97 -0
  62. package/lib/commands/headless-harness.d.ts.map +1 -0
  63. package/lib/commands/headless-harness.js +121 -0
  64. package/lib/commands/headless-harness.js.map +1 -0
  65. package/lib/commands/init-args.d.ts +81 -0
  66. package/lib/commands/init-args.d.ts.map +1 -0
  67. package/lib/commands/init-args.js +250 -0
  68. package/lib/commands/init-args.js.map +1 -0
  69. package/lib/commands/init-prompt.d.ts +66 -0
  70. package/lib/commands/init-prompt.d.ts.map +1 -0
  71. package/lib/commands/init-prompt.js +71 -0
  72. package/lib/commands/init-prompt.js.map +1 -0
  73. package/lib/commands/init-templates.d.ts +42 -0
  74. package/lib/commands/init-templates.d.ts.map +1 -0
  75. package/lib/commands/init-templates.js +1902 -0
  76. package/lib/commands/init-templates.js.map +1 -0
  77. package/lib/commands/init-wizard.d.ts +65 -0
  78. package/lib/commands/init-wizard.d.ts.map +1 -0
  79. package/lib/commands/init-wizard.js +302 -0
  80. package/lib/commands/init-wizard.js.map +1 -0
  81. package/lib/commands/init-workspace.d.ts +168 -0
  82. package/lib/commands/init-workspace.d.ts.map +1 -0
  83. package/lib/commands/init-workspace.js +372 -0
  84. package/lib/commands/init-workspace.js.map +1 -0
  85. package/lib/commands/init.d.ts +273 -0
  86. package/lib/commands/init.d.ts.map +1 -0
  87. package/lib/commands/init.js +303 -0
  88. package/lib/commands/init.js.map +1 -0
  89. package/lib/commands/lint-grammar-args.d.ts +23 -0
  90. package/lib/commands/lint-grammar-args.d.ts.map +1 -0
  91. package/lib/commands/lint-grammar-args.js +63 -0
  92. package/lib/commands/lint-grammar-args.js.map +1 -0
  93. package/lib/commands/lint-grammar-driver.d.ts +10 -0
  94. package/lib/commands/lint-grammar-driver.d.ts.map +1 -0
  95. package/lib/commands/lint-grammar-driver.js +72 -0
  96. package/lib/commands/lint-grammar-driver.js.map +1 -0
  97. package/lib/commands/lint-grammar-report.d.ts +24 -0
  98. package/lib/commands/lint-grammar-report.d.ts.map +1 -0
  99. package/lib/commands/lint-grammar-report.js +65 -0
  100. package/lib/commands/lint-grammar-report.js.map +1 -0
  101. package/lib/commands/lint-grammar.d.ts +33 -0
  102. package/lib/commands/lint-grammar.d.ts.map +1 -0
  103. package/lib/commands/lint-grammar.js +42 -0
  104. package/lib/commands/lint-grammar.js.map +1 -0
  105. package/lib/commands/measure-memory-args.d.ts +23 -0
  106. package/lib/commands/measure-memory-args.d.ts.map +1 -0
  107. package/lib/commands/measure-memory-args.js +106 -0
  108. package/lib/commands/measure-memory-args.js.map +1 -0
  109. package/lib/commands/measure-memory-driver.d.ts +10 -0
  110. package/lib/commands/measure-memory-driver.d.ts.map +1 -0
  111. package/lib/commands/measure-memory-driver.js +110 -0
  112. package/lib/commands/measure-memory-driver.js.map +1 -0
  113. package/lib/commands/measure-memory.d.ts +68 -0
  114. package/lib/commands/measure-memory.d.ts.map +1 -0
  115. package/lib/commands/measure-memory.js +100 -0
  116. package/lib/commands/measure-memory.js.map +1 -0
  117. package/lib/commands/model-docs-args.d.ts +23 -0
  118. package/lib/commands/model-docs-args.d.ts.map +1 -0
  119. package/lib/commands/model-docs-args.js +53 -0
  120. package/lib/commands/model-docs-args.js.map +1 -0
  121. package/lib/commands/model-docs-driver.d.ts +10 -0
  122. package/lib/commands/model-docs-driver.d.ts.map +1 -0
  123. package/lib/commands/model-docs-driver.js +61 -0
  124. package/lib/commands/model-docs-driver.js.map +1 -0
  125. package/lib/commands/model-docs-report.d.ts +18 -0
  126. package/lib/commands/model-docs-report.d.ts.map +1 -0
  127. package/lib/commands/model-docs-report.js +123 -0
  128. package/lib/commands/model-docs-report.js.map +1 -0
  129. package/lib/commands/model-docs.d.ts +30 -0
  130. package/lib/commands/model-docs.d.ts.map +1 -0
  131. package/lib/commands/model-docs.js +37 -0
  132. package/lib/commands/model-docs.js.map +1 -0
  133. package/lib/commands/projects-args.d.ts +25 -0
  134. package/lib/commands/projects-args.d.ts.map +1 -0
  135. package/lib/commands/projects-args.js +49 -0
  136. package/lib/commands/projects-args.js.map +1 -0
  137. package/lib/commands/projects.d.ts +45 -0
  138. package/lib/commands/projects.d.ts.map +1 -0
  139. package/lib/commands/projects.js +39 -0
  140. package/lib/commands/projects.js.map +1 -0
  141. package/lib/commands/query-args.d.ts +25 -0
  142. package/lib/commands/query-args.d.ts.map +1 -0
  143. package/lib/commands/query-args.js +46 -0
  144. package/lib/commands/query-args.js.map +1 -0
  145. package/lib/commands/query.d.ts +32 -0
  146. package/lib/commands/query.d.ts.map +1 -0
  147. package/lib/commands/query.js +29 -0
  148. package/lib/commands/query.js.map +1 -0
  149. package/lib/commands/reflect-args.d.ts +23 -0
  150. package/lib/commands/reflect-args.d.ts.map +1 -0
  151. package/lib/commands/reflect-args.js +59 -0
  152. package/lib/commands/reflect-args.js.map +1 -0
  153. package/lib/commands/reflect-driver.d.ts +10 -0
  154. package/lib/commands/reflect-driver.d.ts.map +1 -0
  155. package/lib/commands/reflect-driver.js +66 -0
  156. package/lib/commands/reflect-driver.js.map +1 -0
  157. package/lib/commands/reflect-report.d.ts +20 -0
  158. package/lib/commands/reflect-report.d.ts.map +1 -0
  159. package/lib/commands/reflect-report.js +127 -0
  160. package/lib/commands/reflect-report.js.map +1 -0
  161. package/lib/commands/reflect.d.ts +30 -0
  162. package/lib/commands/reflect.d.ts.map +1 -0
  163. package/lib/commands/reflect.js +38 -0
  164. package/lib/commands/reflect.js.map +1 -0
  165. package/lib/commands/save-args.d.ts +26 -0
  166. package/lib/commands/save-args.d.ts.map +1 -0
  167. package/lib/commands/save-args.js +59 -0
  168. package/lib/commands/save-args.js.map +1 -0
  169. package/lib/commands/save.d.ts +38 -0
  170. package/lib/commands/save.d.ts.map +1 -0
  171. package/lib/commands/save.js +48 -0
  172. package/lib/commands/save.js.map +1 -0
  173. package/lib/commands/validate-args.d.ts +23 -0
  174. package/lib/commands/validate-args.d.ts.map +1 -0
  175. package/lib/commands/validate-args.js +60 -0
  176. package/lib/commands/validate-args.js.map +1 -0
  177. package/lib/commands/validate-driver.d.ts +10 -0
  178. package/lib/commands/validate-driver.d.ts.map +1 -0
  179. package/lib/commands/validate-driver.js +72 -0
  180. package/lib/commands/validate-driver.js.map +1 -0
  181. package/lib/commands/validate-report.d.ts +30 -0
  182. package/lib/commands/validate-report.d.ts.map +1 -0
  183. package/lib/commands/validate-report.js +87 -0
  184. package/lib/commands/validate-report.js.map +1 -0
  185. package/lib/commands/validate.d.ts +34 -0
  186. package/lib/commands/validate.d.ts.map +1 -0
  187. package/lib/commands/validate.js +41 -0
  188. package/lib/commands/validate.js.map +1 -0
  189. package/lib/commands/watch-args.d.ts +26 -0
  190. package/lib/commands/watch-args.d.ts.map +1 -0
  191. package/lib/commands/watch-args.js +59 -0
  192. package/lib/commands/watch-args.js.map +1 -0
  193. package/lib/commands/watch.d.ts +75 -0
  194. package/lib/commands/watch.d.ts.map +1 -0
  195. package/lib/commands/watch.js +112 -0
  196. package/lib/commands/watch.js.map +1 -0
  197. package/lib/index.d.ts +16 -0
  198. package/lib/index.d.ts.map +1 -0
  199. package/lib/index.js +16 -0
  200. package/lib/index.js.map +1 -0
  201. package/lib/log-level.d.ts +39 -0
  202. package/lib/log-level.d.ts.map +1 -0
  203. package/lib/log-level.js +47 -0
  204. package/lib/log-level.js.map +1 -0
  205. package/lib/package-name.d.ts +33 -0
  206. package/lib/package-name.d.ts.map +1 -0
  207. package/lib/package-name.js +37 -0
  208. package/lib/package-name.js.map +1 -0
  209. package/lib/spawn-data-server.d.ts +102 -0
  210. package/lib/spawn-data-server.d.ts.map +1 -0
  211. package/lib/spawn-data-server.js +154 -0
  212. package/lib/spawn-data-server.js.map +1 -0
  213. package/lib/testing/echo-server.d.ts +11 -0
  214. package/lib/testing/echo-server.d.ts.map +1 -0
  215. package/lib/testing/echo-server.js +79 -0
  216. package/lib/testing/echo-server.js.map +1 -0
  217. package/package.json +105 -0
  218. package/src/cli-version.ts +32 -0
  219. package/src/cli.ts +139 -0
  220. package/src/commands/analyze-heap-args.ts +184 -0
  221. package/src/commands/analyze-heap.ts +76 -0
  222. package/src/commands/ast-ground-truth-args.ts +63 -0
  223. package/src/commands/ast-ground-truth-driver.ts +81 -0
  224. package/src/commands/ast-ground-truth.ts +49 -0
  225. package/src/commands/generate-transfer-model-args.ts +203 -0
  226. package/src/commands/generate-transfer-model-config.ts +128 -0
  227. package/src/commands/generate-transfer-model.ts +716 -0
  228. package/src/commands/harness-args.ts +429 -0
  229. package/src/commands/headless-harness.ts +165 -0
  230. package/src/commands/init-args.ts +273 -0
  231. package/src/commands/init-prompt.ts +149 -0
  232. package/src/commands/init-templates.ts +2037 -0
  233. package/src/commands/init-wizard.ts +378 -0
  234. package/src/commands/init-workspace.ts +442 -0
  235. package/src/commands/init.ts +554 -0
  236. package/src/commands/lint-grammar-args.ts +79 -0
  237. package/src/commands/lint-grammar-driver.ts +83 -0
  238. package/src/commands/lint-grammar-report.ts +74 -0
  239. package/src/commands/lint-grammar.ts +57 -0
  240. package/src/commands/measure-memory-args.ts +123 -0
  241. package/src/commands/measure-memory-driver.ts +128 -0
  242. package/src/commands/measure-memory.ts +143 -0
  243. package/src/commands/model-docs-args.ts +69 -0
  244. package/src/commands/model-docs-driver.ts +68 -0
  245. package/src/commands/model-docs-report.ts +133 -0
  246. package/src/commands/model-docs.ts +48 -0
  247. package/src/commands/projects-args.ts +62 -0
  248. package/src/commands/projects.ts +76 -0
  249. package/src/commands/query-args.ts +61 -0
  250. package/src/commands/query.ts +58 -0
  251. package/src/commands/reflect-args.ts +75 -0
  252. package/src/commands/reflect-driver.ts +76 -0
  253. package/src/commands/reflect-report.ts +138 -0
  254. package/src/commands/reflect.ts +51 -0
  255. package/src/commands/save-args.ts +74 -0
  256. package/src/commands/save.ts +84 -0
  257. package/src/commands/validate-args.ts +76 -0
  258. package/src/commands/validate-driver.ts +84 -0
  259. package/src/commands/validate-report.ts +95 -0
  260. package/src/commands/validate.ts +58 -0
  261. package/src/commands/watch-args.ts +74 -0
  262. package/src/commands/watch.ts +185 -0
  263. package/src/index.ts +16 -0
  264. package/src/log-level.ts +49 -0
  265. package/src/package-name.ts +39 -0
  266. package/src/spawn-data-server.ts +247 -0
  267. package/src/testing/echo-server.ts +93 -0
@@ -0,0 +1,273 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, 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
+ /**
11
+ * `init`'s argv parser.
12
+ *
13
+ * Its own module rather than a function in the CLI entry point, because the
14
+ * entry point runs `main()` on import: a test that wants to read an argv the
15
+ * way the command does would otherwise have to execute the whole binary. That
16
+ * matters more here than for the other subcommands, since the wizard's entire
17
+ * contract is that its composed argv means exactly what a typed one would — a
18
+ * claim only a test that round-trips through THIS function can check.
19
+ *
20
+ * The parsing is hand-written rather than declared to `commander` because the
21
+ * grammar-scoped flags are ORDER-dependent across different option names:
22
+ * `--extensions` applies to the `--grammar` it follows, so
23
+ * `--grammar A --extensions x --grammar B` and
24
+ * `--grammar A --grammar B --extensions x` mean different things. A declarative
25
+ * option model collects `{grammar: [A, B], extensions: [x]}` for both and the
26
+ * association is gone.
27
+ */
28
+
29
+ import { assertRequired, exitWithUsage, helpRequested, printHelp, type UsageError } from './harness-args.js';
30
+ import { isNonEmptyDir, runInit, type InitHead } from './init.js';
31
+ import { createClackPrompt } from './init-prompt.js';
32
+ import { runInitWizard } from './init-wizard.js';
33
+ import { createNodeWorkspaceProbe, detectWorkspace } from './init-workspace.js';
34
+
35
+ /** One grammar as the command line collected it, before any derivation. */
36
+ export interface ParsedInitGrammar {
37
+ name: string;
38
+ extensions?: string[];
39
+ languageId?: string;
40
+ diagram?: boolean;
41
+ }
42
+
43
+ /** `init`'s argv, parsed but not validated — a missing `--name` is the wizard's cue, not an error. */
44
+ export interface ParsedInitArgs {
45
+ targetDir?: string;
46
+ name?: string;
47
+ force: boolean;
48
+ heads?: InitHead[];
49
+ monorepo: boolean;
50
+ scope?: string;
51
+ public: boolean;
52
+ grammars: ParsedInitGrammar[];
53
+ }
54
+
55
+ /**
56
+ * Every flag `init` accepts, as data.
57
+ *
58
+ * The single source the parser's own switch and the `--help` text both read,
59
+ * with a test asserting they agree. A flag added to one and not the other is
60
+ * the silent drift this list exists to make impossible.
61
+ */
62
+ export const INIT_FLAGS: readonly string[] = [
63
+ '--name',
64
+ '--heads',
65
+ '--force',
66
+ '--monorepo',
67
+ '--scope',
68
+ '--public',
69
+ '--grammar',
70
+ '--extensions',
71
+ '--language-id',
72
+ '--diagram'
73
+ ];
74
+
75
+ /**
76
+ * The subset that takes a value, so `--help` in a value position reads as data.
77
+ * The rest are presence-only.
78
+ */
79
+ export const INIT_VALUE_FLAGS: readonly string[] = ['--name', '--heads', '--scope', '--grammar', '--extensions', '--language-id'];
80
+
81
+ /**
82
+ * Parse `init`'s flags without deciding whether the result is complete.
83
+ *
84
+ * Shared by the wizard and the flag path so both read an argv with ONE parser:
85
+ * the wizard's contract is that its composed argv means exactly what a typed
86
+ * one would, which only holds if the same code reads both.
87
+ *
88
+ * `onError` receives a usage message. It defaults to reporting and exiting,
89
+ * which is what a command line wants; a caller that would rather handle the
90
+ * problem passes its own and the function throws instead of ending the process.
91
+ */
92
+ export function parseInitArgs(args: string[], onError: UsageError = exitWithUsage): ParsedInitArgs {
93
+ let targetDir: string | undefined;
94
+ let name: string | undefined;
95
+ let force = false;
96
+ let heads: InitHead[] | undefined;
97
+ let monorepo = false;
98
+ let scope: string | undefined;
99
+ let isPublic = false;
100
+ const grammars: ParsedInitGrammar[] = [];
101
+ for (let index = 0; index < args.length; index += 1) {
102
+ const flag = args[index];
103
+ const next = (): string => {
104
+ const value = args[index + 1];
105
+ if (value === undefined) {
106
+ onError(`Missing value for ${flag}`);
107
+ }
108
+ index += 1;
109
+ return value;
110
+ };
111
+ // A grammar-scoped flag modifies the grammar it follows, so with no
112
+ // `--grammar` yet there is nothing for it to mean. Refusing loudly beats
113
+ // silently attaching it to a grammar the user never named.
114
+ const scoped = (): ParsedInitGrammar => {
115
+ const current = grammars[grammars.length - 1];
116
+ if (current === undefined) {
117
+ onError(`${flag} must follow a --grammar (hydranium-cli init --help)`);
118
+ }
119
+ return current;
120
+ };
121
+ switch (flag) {
122
+ case '--name':
123
+ name = next();
124
+ break;
125
+ case '--grammar':
126
+ grammars.push({ name: next() });
127
+ break;
128
+ case '--extensions': {
129
+ const grammar = scoped();
130
+ grammar.extensions = [...(grammar.extensions ?? []), ...next().split(',')];
131
+ break;
132
+ }
133
+ case '--language-id':
134
+ scoped().languageId = next();
135
+ break;
136
+ case '--diagram':
137
+ scoped().diagram = true;
138
+ break;
139
+ case '--heads': {
140
+ // Validated in `resolveInitComposition`, which owns the rule that
141
+ // `lsp` is mandatory; the cast only gets the strings that far.
142
+ heads = next().split(',') as InitHead[];
143
+ break;
144
+ }
145
+ case '--force':
146
+ force = true;
147
+ break;
148
+ case '--monorepo':
149
+ monorepo = true;
150
+ break;
151
+ case '--scope':
152
+ scope = next();
153
+ break;
154
+ case '--public':
155
+ isPublic = true;
156
+ break;
157
+ default:
158
+ // A grammar takes N extensions, so the flag is plural. Naming the
159
+ // singular spelling beats a bare "unknown option" for what is
160
+ // otherwise a silent typo.
161
+ if (flag === '--extension') {
162
+ onError('Unknown option: --extension — did you mean --extensions? (hydranium-cli init --help)');
163
+ } else if (flag.startsWith('--')) {
164
+ onError(`Unknown option: ${flag} (hydranium-cli init --help)`);
165
+ } else if (targetDir === undefined) {
166
+ targetDir = flag;
167
+ } else {
168
+ onError(`Unexpected argument: ${flag} (hydranium-cli init --help)`);
169
+ }
170
+ }
171
+ }
172
+ return { targetDir, name, force, heads, monorepo, scope, public: isPublic, grammars };
173
+ }
174
+
175
+ /**
176
+ * The `init --help` text, as data.
177
+ *
178
+ * Exported so a test can assert it documents every flag {@link INIT_FLAGS}
179
+ * names. Help that drifts from the parser is worse than none: it is read as
180
+ * authoritative, and a flag missing from it is a capability nobody finds.
181
+ */
182
+ export const INIT_HELP: readonly string[] = [
183
+ 'Usage: hydranium-cli init <target-dir> --name <Name> [--heads <list>] [--force]',
184
+ ' [--monorepo] [--scope <@scope>] [--public]',
185
+ ' ( --grammar <Name> [--extensions <list>]',
186
+ ' [--language-id <id>] [--diagram] )...',
187
+ '',
188
+ 'Scaffold a new Hydranium language project end-to-end: a starter grammar per',
189
+ '--grammar, the `create<Name>Services` DI wiring, an LSP + data-server launch,',
190
+ 'langium-config, package.json, and build scripts. Does NOT run `npm install` or',
191
+ '`langium generate` — those are printed as next steps. Refuses a non-empty',
192
+ 'directory without --force.',
193
+ '',
194
+ 'Run it with no --name on a terminal to be prompted instead. The wizard echoes',
195
+ 'the command it composed before running it, so every answer is reproducible as',
196
+ 'flags. Without a terminal a missing --name stays an error, so CI never hangs.',
197
+ '',
198
+ 'Project options (position-free):',
199
+ ' <target-dir> Directory to scaffold into (required).',
200
+ ' --name <Name> PascalCase PROJECT name; drives the services and the',
201
+ ' Langium projectName, i.e. the shared generated symbols',
202
+ ' (<Name>AstReflection, <Name>GeneratedSharedModule) (required).',
203
+ ' --heads <list> Comma-separated protocol heads: lsp, data, glsp.',
204
+ ' `lsp` is mandatory — it owns the workspace, the build',
205
+ ' pipeline and the shared tier the others read through.',
206
+ ' The emitted dependencies, main.ts and module bindings',
207
+ ' all follow this set. Default: lsp,data.',
208
+ ' --force Scaffold into a non-empty directory anyway.',
209
+ ' --monorepo Scaffold a member of the surrounding npm workspace:',
210
+ ' tsconfig extends the root config that actually carries',
211
+ ' compilerOptions, .gitignore is left to the root, and the',
212
+ ' scripts address the package by --prefix. Errors when no',
213
+ ' ancestor declares `workspaces`. Never writes outside the',
214
+ ' target — the root `workspaces` entry is printed, not added.',
215
+ ' --scope <@scope> npm scope for the package name, e.g. @acme. Not inferred:',
216
+ ' a root manifest is usually named for the repo, not the',
217
+ ' scope. The wizard offers the siblings’ scope as a default.',
218
+ ' --public Omit "private": true, so the package can be published.',
219
+ ' The scaffold emits it by default, because the manifest',
220
+ ' it also emits declares license "UNLICENSED": a package',
221
+ ' granting no rights on a public registry contradicts',
222
+ ' itself. Pass this once the project has picked a licence.',
223
+ '',
224
+ 'Grammar options — each applies to the PRECEDING --grammar:',
225
+ ' --grammar <Name> PascalCase GRAMMAR name; drives the `grammar X` declaration',
226
+ ' and the per-language generated symbols',
227
+ ' (<Grammar>GeneratedModule, <Grammar>LanguageMetaData).',
228
+ ' Repeatable — pass it once per grammar. Default: --name.',
229
+ ' --extensions <list> Comma-separated file extensions for that grammar, leading',
230
+ ' dot optional. Repeatable; accumulates. Default: the kebab',
231
+ ' grammar name.',
232
+ ' --language-id <id> Override that grammar’s derived routing key',
233
+ ' (default: <project-id>-<grammar-id>).',
234
+ ' --diagram Scaffold a GLSP diagram for that grammar. Requires',
235
+ ' `glsp` in --heads. Derived when there is exactly one',
236
+ ' grammar; required when there are several, because a',
237
+ ' diagram type binds exactly one grammar.',
238
+ '',
239
+ 'Example — three grammars, one with a differing extension, three heads:',
240
+ ' hydranium-cli init ./order-flow --name OrderFlow --heads lsp,data,glsp \\',
241
+ ' --grammar Domain --grammar Process --diagram \\',
242
+ ' --grammar Layout --extensions diagram'
243
+ ];
244
+
245
+ export async function runInitCommand(args: string[]): Promise<void> {
246
+ if (helpRequested(args, INIT_VALUE_FLAGS)) {
247
+ printHelp(INIT_HELP);
248
+ return;
249
+ }
250
+ const parsed = parseInitArgs(args);
251
+ // The wizard's entry point: no `--name` on an interactive terminal. Safe
252
+ // because that combination has no non-wizard meaning, and gated on a TTY
253
+ // because a non-interactive caller must keep failing fast — a prompt in CI
254
+ // hangs the job instead of reporting the missing flag.
255
+ if (parsed.name === undefined && process.stdin.isTTY && process.stdout.isTTY) {
256
+ const prompt = await createClackPrompt();
257
+ const argv = await runInitWizard(prompt, targetDir => detectWorkspace(targetDir, createNodeWorkspaceProbe()), {
258
+ targetDir: parsed.targetDir,
259
+ isOccupied: isNonEmptyDir
260
+ });
261
+ return runInitCommand(argv);
262
+ }
263
+ runInit({
264
+ targetDir: assertRequired(parsed.targetDir, '<target-dir>', 'init'),
265
+ name: assertRequired(parsed.name, '--name', 'init'),
266
+ grammars: parsed.grammars,
267
+ heads: parsed.heads,
268
+ force: parsed.force,
269
+ monorepo: parsed.monorepo,
270
+ scope: parsed.scope,
271
+ public: parsed.public
272
+ });
273
+ }
@@ -0,0 +1,149 @@
1
+ /********************************************************************************
2
+ * Copyright (c) 2026 CrossBreeze, 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
+ /**
11
+ * The wizard's question/answer port, and its `@clack/prompts` implementation.
12
+ *
13
+ * Semantic rather than line-oriented — `text` / `select` / `multiselect` /
14
+ * `confirm` instead of one `ask(string)` — for two reasons. It lets the real
15
+ * implementation render arrow-key lists and checkboxes, which a readline loop
16
+ * cannot; and it keeps the flow in `runInitWizard` testable against a
17
+ * scripted port, so the wizard's logic is exercised without a TTY, without
18
+ * ANSI parsing, and without the prompt library in the assertion path.
19
+ *
20
+ * Cancellation is an exception rather than a sentinel value: clack signals
21
+ * Ctrl-C by RETURNING a cancel symbol, so a caller that forgets to check it
22
+ * carries on with a symbol where a string should be. Throwing puts the check in
23
+ * one place and makes forgetting it impossible.
24
+ */
25
+
26
+ // Type-only, so it is erased at compile time and the dynamic `import()` in
27
+ // `createClackPrompt` stays the only thing that actually loads the library.
28
+ import type { Option as ClackOption } from '@clack/prompts';
29
+ import type { InitHead } from './init.js';
30
+
31
+ /** One choice in a {@link PromptPort.select} or {@link PromptPort.multiselect} list. */
32
+ export interface PromptChoice<Value extends string> {
33
+ readonly value: Value;
34
+ readonly label: string;
35
+ /** Dimmed trailing text, for the rule or consequence behind the choice. */
36
+ readonly hint?: string;
37
+ }
38
+
39
+ /** A free-text question. `validate` returns a message to re-ask with, or `undefined` to accept. */
40
+ export interface PromptTextOptions {
41
+ readonly message: string;
42
+ readonly initialValue?: string;
43
+ readonly placeholder?: string;
44
+ readonly validate?: (value: string) => string | undefined;
45
+ }
46
+
47
+ /** Thrown when the user cancels — Ctrl-C, or an EOF on the input stream. */
48
+ export class InitWizardCancelled extends Error {
49
+ constructor() {
50
+ super('Scaffolding cancelled.');
51
+ this.name = 'InitWizardCancelled';
52
+ }
53
+ }
54
+
55
+ /** The prompts the wizard needs, and nothing more. */
56
+ export interface PromptPort {
57
+ /** Open the session with a title. */
58
+ readonly intro: (title: string) => void;
59
+ /** Show a block of context that is not a question. */
60
+ readonly note: (message: string, title?: string) => void;
61
+ /** Ask for a line of text. */
62
+ readonly text: (options: PromptTextOptions) => Promise<string>;
63
+ /** Ask for exactly one of several choices. */
64
+ readonly select: <Value extends string>(options: {
65
+ readonly message: string;
66
+ readonly choices: ReadonlyArray<PromptChoice<Value>>;
67
+ readonly initialValue?: Value;
68
+ }) => Promise<Value>;
69
+ /** Ask for any number of several choices, including none. */
70
+ readonly multiselect: <Value extends string>(options: {
71
+ readonly message: string;
72
+ readonly choices: ReadonlyArray<PromptChoice<Value>>;
73
+ readonly initialValues?: readonly Value[];
74
+ }) => Promise<Value[]>;
75
+ /** Ask a yes/no question. */
76
+ readonly confirm: (options: { readonly message: string; readonly initialValue?: boolean }) => Promise<boolean>;
77
+ /** Close the session with a closing line. */
78
+ readonly outro: (message: string) => void;
79
+ }
80
+
81
+ /** The optional heads, as a checkbox list. `lsp` is not offered — see `askHeads`. */
82
+ export const OPTIONAL_HEAD_CHOICES: ReadonlyArray<PromptChoice<Exclude<InitHead, 'lsp'>>> = [
83
+ { value: 'data', label: 'data', hint: 'typed JSON-RPC for forms, trees and code-gen' },
84
+ { value: 'glsp', label: 'glsp', hint: 'graphical editing; adds a scaffolded diagram' }
85
+ ];
86
+
87
+ /**
88
+ * The real prompt, backed by `@clack/prompts`.
89
+ *
90
+ * Imported dynamically so the library loads only when someone actually reaches
91
+ * the interactive path: every non-interactive `init`, and all twelve other
92
+ * subcommands, keep their previous startup cost.
93
+ */
94
+ export async function createClackPrompt(): Promise<PromptPort> {
95
+ const clack = await import('@clack/prompts');
96
+
97
+ /** Unwrap a clack answer, turning its cancel sentinel into a throw. */
98
+ const unwrap = <Value>(answer: Value | symbol): Value => {
99
+ if (clack.isCancel(answer)) {
100
+ throw new InitWizardCancelled();
101
+ }
102
+ return answer;
103
+ };
104
+
105
+ // `Option<Value>` is a conditional type over the value, which stays deferred
106
+ // under our own generic parameter and so refuses a structurally identical
107
+ // literal. The cast asserts the shape the branch resolves to for `string`.
108
+ const toOptions = <Value extends string>(choices: ReadonlyArray<PromptChoice<Value>>): Array<ClackOption<Value>> =>
109
+ choices.map(choice => ({
110
+ value: choice.value,
111
+ label: choice.label,
112
+ ...(choice.hint === undefined ? {} : { hint: choice.hint })
113
+ })) as Array<ClackOption<Value>>;
114
+
115
+ return {
116
+ intro: title => clack.intro(title),
117
+ note: (message, title) => clack.note(message, title),
118
+ text: async options =>
119
+ unwrap(
120
+ await clack.text({
121
+ message: options.message,
122
+ initialValue: options.initialValue,
123
+ placeholder: options.placeholder,
124
+ validate: value => options.validate?.((value ?? '').trim())
125
+ })
126
+ ).trim(),
127
+ select: async options =>
128
+ unwrap(
129
+ await clack.select({
130
+ message: options.message,
131
+ options: toOptions(options.choices),
132
+ initialValue: options.initialValue
133
+ })
134
+ ),
135
+ multiselect: async options =>
136
+ unwrap(
137
+ await clack.multiselect({
138
+ message: options.message,
139
+ options: toOptions(options.choices),
140
+ initialValues: options.initialValues ? [...options.initialValues] : [],
141
+ // Legal and meaningful: an `lsp`-only project is a plain LSP
142
+ // server, so an empty selection must not be rejected.
143
+ required: false
144
+ })
145
+ ),
146
+ confirm: async options => unwrap(await clack.confirm({ message: options.message, initialValue: options.initialValue })),
147
+ outro: message => clack.outro(message)
148
+ };
149
+ }