@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.
- package/LICENSE +21 -0
- package/README.md +240 -0
- package/heap-analysis/analyze-heap.mjs +798 -0
- package/heap-analysis/heap/core/aggregate.mjs +152 -0
- package/heap-analysis/heap/core/bucket-label.mjs +23 -0
- package/heap-analysis/heap/core/cutpoints.mjs +51 -0
- package/heap-analysis/heap/core/diff.mjs +56 -0
- package/heap-analysis/heap/core/format.mjs +47 -0
- package/heap-analysis/heap/core/holders.mjs +80 -0
- package/heap-analysis/heap/core/load.mjs +46 -0
- package/heap-analysis/heap/core/report.mjs +68 -0
- package/heap-analysis/heap/core/strings.mjs +80 -0
- package/heap-analysis/heap/langium/concepts.mjs +230 -0
- package/heap-analysis/heap/langium/glsp.classifier.mjs +80 -0
- package/heap-analysis/heap/langium/langium.classifier.mjs +203 -0
- package/heap-analysis/heap/renderer/renderer.mjs +122 -0
- package/lib/cli-version.d.ts +21 -0
- package/lib/cli-version.d.ts.map +1 -0
- package/lib/cli-version.js +30 -0
- package/lib/cli-version.js.map +1 -0
- package/lib/cli.d.ts +11 -0
- package/lib/cli.d.ts.map +1 -0
- package/lib/cli.js +135 -0
- package/lib/cli.js.map +1 -0
- package/lib/commands/analyze-heap-args.d.ts +46 -0
- package/lib/commands/analyze-heap-args.d.ts.map +1 -0
- package/lib/commands/analyze-heap-args.js +174 -0
- package/lib/commands/analyze-heap-args.js.map +1 -0
- package/lib/commands/analyze-heap.d.ts +37 -0
- package/lib/commands/analyze-heap.d.ts.map +1 -0
- package/lib/commands/analyze-heap.js +63 -0
- package/lib/commands/analyze-heap.js.map +1 -0
- package/lib/commands/ast-ground-truth-args.d.ts +23 -0
- package/lib/commands/ast-ground-truth-args.d.ts.map +1 -0
- package/lib/commands/ast-ground-truth-args.js +47 -0
- package/lib/commands/ast-ground-truth-args.js.map +1 -0
- package/lib/commands/ast-ground-truth-driver.d.ts +10 -0
- package/lib/commands/ast-ground-truth-driver.d.ts.map +1 -0
- package/lib/commands/ast-ground-truth-driver.js +72 -0
- package/lib/commands/ast-ground-truth-driver.js.map +1 -0
- package/lib/commands/ast-ground-truth.d.ts +31 -0
- package/lib/commands/ast-ground-truth.d.ts.map +1 -0
- package/lib/commands/ast-ground-truth.js +36 -0
- package/lib/commands/ast-ground-truth.js.map +1 -0
- package/lib/commands/generate-transfer-model-args.d.ts +38 -0
- package/lib/commands/generate-transfer-model-args.d.ts.map +1 -0
- package/lib/commands/generate-transfer-model-args.js +183 -0
- package/lib/commands/generate-transfer-model-args.js.map +1 -0
- package/lib/commands/generate-transfer-model-config.d.ts +32 -0
- package/lib/commands/generate-transfer-model-config.d.ts.map +1 -0
- package/lib/commands/generate-transfer-model-config.js +122 -0
- package/lib/commands/generate-transfer-model-config.js.map +1 -0
- package/lib/commands/generate-transfer-model.d.ts +110 -0
- package/lib/commands/generate-transfer-model.d.ts.map +1 -0
- package/lib/commands/generate-transfer-model.js +587 -0
- package/lib/commands/generate-transfer-model.js.map +1 -0
- package/lib/commands/harness-args.d.ts +198 -0
- package/lib/commands/harness-args.d.ts.map +1 -0
- package/lib/commands/harness-args.js +339 -0
- package/lib/commands/harness-args.js.map +1 -0
- package/lib/commands/headless-harness.d.ts +97 -0
- package/lib/commands/headless-harness.d.ts.map +1 -0
- package/lib/commands/headless-harness.js +121 -0
- package/lib/commands/headless-harness.js.map +1 -0
- package/lib/commands/init-args.d.ts +81 -0
- package/lib/commands/init-args.d.ts.map +1 -0
- package/lib/commands/init-args.js +250 -0
- package/lib/commands/init-args.js.map +1 -0
- package/lib/commands/init-prompt.d.ts +66 -0
- package/lib/commands/init-prompt.d.ts.map +1 -0
- package/lib/commands/init-prompt.js +71 -0
- package/lib/commands/init-prompt.js.map +1 -0
- package/lib/commands/init-templates.d.ts +42 -0
- package/lib/commands/init-templates.d.ts.map +1 -0
- package/lib/commands/init-templates.js +1902 -0
- package/lib/commands/init-templates.js.map +1 -0
- package/lib/commands/init-wizard.d.ts +65 -0
- package/lib/commands/init-wizard.d.ts.map +1 -0
- package/lib/commands/init-wizard.js +302 -0
- package/lib/commands/init-wizard.js.map +1 -0
- package/lib/commands/init-workspace.d.ts +168 -0
- package/lib/commands/init-workspace.d.ts.map +1 -0
- package/lib/commands/init-workspace.js +372 -0
- package/lib/commands/init-workspace.js.map +1 -0
- package/lib/commands/init.d.ts +273 -0
- package/lib/commands/init.d.ts.map +1 -0
- package/lib/commands/init.js +303 -0
- package/lib/commands/init.js.map +1 -0
- package/lib/commands/lint-grammar-args.d.ts +23 -0
- package/lib/commands/lint-grammar-args.d.ts.map +1 -0
- package/lib/commands/lint-grammar-args.js +63 -0
- package/lib/commands/lint-grammar-args.js.map +1 -0
- package/lib/commands/lint-grammar-driver.d.ts +10 -0
- package/lib/commands/lint-grammar-driver.d.ts.map +1 -0
- package/lib/commands/lint-grammar-driver.js +72 -0
- package/lib/commands/lint-grammar-driver.js.map +1 -0
- package/lib/commands/lint-grammar-report.d.ts +24 -0
- package/lib/commands/lint-grammar-report.d.ts.map +1 -0
- package/lib/commands/lint-grammar-report.js +65 -0
- package/lib/commands/lint-grammar-report.js.map +1 -0
- package/lib/commands/lint-grammar.d.ts +33 -0
- package/lib/commands/lint-grammar.d.ts.map +1 -0
- package/lib/commands/lint-grammar.js +42 -0
- package/lib/commands/lint-grammar.js.map +1 -0
- package/lib/commands/measure-memory-args.d.ts +23 -0
- package/lib/commands/measure-memory-args.d.ts.map +1 -0
- package/lib/commands/measure-memory-args.js +106 -0
- package/lib/commands/measure-memory-args.js.map +1 -0
- package/lib/commands/measure-memory-driver.d.ts +10 -0
- package/lib/commands/measure-memory-driver.d.ts.map +1 -0
- package/lib/commands/measure-memory-driver.js +110 -0
- package/lib/commands/measure-memory-driver.js.map +1 -0
- package/lib/commands/measure-memory.d.ts +68 -0
- package/lib/commands/measure-memory.d.ts.map +1 -0
- package/lib/commands/measure-memory.js +100 -0
- package/lib/commands/measure-memory.js.map +1 -0
- package/lib/commands/model-docs-args.d.ts +23 -0
- package/lib/commands/model-docs-args.d.ts.map +1 -0
- package/lib/commands/model-docs-args.js +53 -0
- package/lib/commands/model-docs-args.js.map +1 -0
- package/lib/commands/model-docs-driver.d.ts +10 -0
- package/lib/commands/model-docs-driver.d.ts.map +1 -0
- package/lib/commands/model-docs-driver.js +61 -0
- package/lib/commands/model-docs-driver.js.map +1 -0
- package/lib/commands/model-docs-report.d.ts +18 -0
- package/lib/commands/model-docs-report.d.ts.map +1 -0
- package/lib/commands/model-docs-report.js +123 -0
- package/lib/commands/model-docs-report.js.map +1 -0
- package/lib/commands/model-docs.d.ts +30 -0
- package/lib/commands/model-docs.d.ts.map +1 -0
- package/lib/commands/model-docs.js +37 -0
- package/lib/commands/model-docs.js.map +1 -0
- package/lib/commands/projects-args.d.ts +25 -0
- package/lib/commands/projects-args.d.ts.map +1 -0
- package/lib/commands/projects-args.js +49 -0
- package/lib/commands/projects-args.js.map +1 -0
- package/lib/commands/projects.d.ts +45 -0
- package/lib/commands/projects.d.ts.map +1 -0
- package/lib/commands/projects.js +39 -0
- package/lib/commands/projects.js.map +1 -0
- package/lib/commands/query-args.d.ts +25 -0
- package/lib/commands/query-args.d.ts.map +1 -0
- package/lib/commands/query-args.js +46 -0
- package/lib/commands/query-args.js.map +1 -0
- package/lib/commands/query.d.ts +32 -0
- package/lib/commands/query.d.ts.map +1 -0
- package/lib/commands/query.js +29 -0
- package/lib/commands/query.js.map +1 -0
- package/lib/commands/reflect-args.d.ts +23 -0
- package/lib/commands/reflect-args.d.ts.map +1 -0
- package/lib/commands/reflect-args.js +59 -0
- package/lib/commands/reflect-args.js.map +1 -0
- package/lib/commands/reflect-driver.d.ts +10 -0
- package/lib/commands/reflect-driver.d.ts.map +1 -0
- package/lib/commands/reflect-driver.js +66 -0
- package/lib/commands/reflect-driver.js.map +1 -0
- package/lib/commands/reflect-report.d.ts +20 -0
- package/lib/commands/reflect-report.d.ts.map +1 -0
- package/lib/commands/reflect-report.js +127 -0
- package/lib/commands/reflect-report.js.map +1 -0
- package/lib/commands/reflect.d.ts +30 -0
- package/lib/commands/reflect.d.ts.map +1 -0
- package/lib/commands/reflect.js +38 -0
- package/lib/commands/reflect.js.map +1 -0
- package/lib/commands/save-args.d.ts +26 -0
- package/lib/commands/save-args.d.ts.map +1 -0
- package/lib/commands/save-args.js +59 -0
- package/lib/commands/save-args.js.map +1 -0
- package/lib/commands/save.d.ts +38 -0
- package/lib/commands/save.d.ts.map +1 -0
- package/lib/commands/save.js +48 -0
- package/lib/commands/save.js.map +1 -0
- package/lib/commands/validate-args.d.ts +23 -0
- package/lib/commands/validate-args.d.ts.map +1 -0
- package/lib/commands/validate-args.js +60 -0
- package/lib/commands/validate-args.js.map +1 -0
- package/lib/commands/validate-driver.d.ts +10 -0
- package/lib/commands/validate-driver.d.ts.map +1 -0
- package/lib/commands/validate-driver.js +72 -0
- package/lib/commands/validate-driver.js.map +1 -0
- package/lib/commands/validate-report.d.ts +30 -0
- package/lib/commands/validate-report.d.ts.map +1 -0
- package/lib/commands/validate-report.js +87 -0
- package/lib/commands/validate-report.js.map +1 -0
- package/lib/commands/validate.d.ts +34 -0
- package/lib/commands/validate.d.ts.map +1 -0
- package/lib/commands/validate.js +41 -0
- package/lib/commands/validate.js.map +1 -0
- package/lib/commands/watch-args.d.ts +26 -0
- package/lib/commands/watch-args.d.ts.map +1 -0
- package/lib/commands/watch-args.js +59 -0
- package/lib/commands/watch-args.js.map +1 -0
- package/lib/commands/watch.d.ts +75 -0
- package/lib/commands/watch.d.ts.map +1 -0
- package/lib/commands/watch.js +112 -0
- package/lib/commands/watch.js.map +1 -0
- package/lib/index.d.ts +16 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +16 -0
- package/lib/index.js.map +1 -0
- package/lib/log-level.d.ts +39 -0
- package/lib/log-level.d.ts.map +1 -0
- package/lib/log-level.js +47 -0
- package/lib/log-level.js.map +1 -0
- package/lib/package-name.d.ts +33 -0
- package/lib/package-name.d.ts.map +1 -0
- package/lib/package-name.js +37 -0
- package/lib/package-name.js.map +1 -0
- package/lib/spawn-data-server.d.ts +102 -0
- package/lib/spawn-data-server.d.ts.map +1 -0
- package/lib/spawn-data-server.js +154 -0
- package/lib/spawn-data-server.js.map +1 -0
- package/lib/testing/echo-server.d.ts +11 -0
- package/lib/testing/echo-server.d.ts.map +1 -0
- package/lib/testing/echo-server.js +79 -0
- package/lib/testing/echo-server.js.map +1 -0
- package/package.json +105 -0
- package/src/cli-version.ts +32 -0
- package/src/cli.ts +139 -0
- package/src/commands/analyze-heap-args.ts +184 -0
- package/src/commands/analyze-heap.ts +76 -0
- package/src/commands/ast-ground-truth-args.ts +63 -0
- package/src/commands/ast-ground-truth-driver.ts +81 -0
- package/src/commands/ast-ground-truth.ts +49 -0
- package/src/commands/generate-transfer-model-args.ts +203 -0
- package/src/commands/generate-transfer-model-config.ts +128 -0
- package/src/commands/generate-transfer-model.ts +716 -0
- package/src/commands/harness-args.ts +429 -0
- package/src/commands/headless-harness.ts +165 -0
- package/src/commands/init-args.ts +273 -0
- package/src/commands/init-prompt.ts +149 -0
- package/src/commands/init-templates.ts +2037 -0
- package/src/commands/init-wizard.ts +378 -0
- package/src/commands/init-workspace.ts +442 -0
- package/src/commands/init.ts +554 -0
- package/src/commands/lint-grammar-args.ts +79 -0
- package/src/commands/lint-grammar-driver.ts +83 -0
- package/src/commands/lint-grammar-report.ts +74 -0
- package/src/commands/lint-grammar.ts +57 -0
- package/src/commands/measure-memory-args.ts +123 -0
- package/src/commands/measure-memory-driver.ts +128 -0
- package/src/commands/measure-memory.ts +143 -0
- package/src/commands/model-docs-args.ts +69 -0
- package/src/commands/model-docs-driver.ts +68 -0
- package/src/commands/model-docs-report.ts +133 -0
- package/src/commands/model-docs.ts +48 -0
- package/src/commands/projects-args.ts +62 -0
- package/src/commands/projects.ts +76 -0
- package/src/commands/query-args.ts +61 -0
- package/src/commands/query.ts +58 -0
- package/src/commands/reflect-args.ts +75 -0
- package/src/commands/reflect-driver.ts +76 -0
- package/src/commands/reflect-report.ts +138 -0
- package/src/commands/reflect.ts +51 -0
- package/src/commands/save-args.ts +74 -0
- package/src/commands/save.ts +84 -0
- package/src/commands/validate-args.ts +76 -0
- package/src/commands/validate-driver.ts +84 -0
- package/src/commands/validate-report.ts +95 -0
- package/src/commands/validate.ts +58 -0
- package/src/commands/watch-args.ts +74 -0
- package/src/commands/watch.ts +185 -0
- package/src/index.ts +16 -0
- package/src/log-level.ts +49 -0
- package/src/package-name.ts +39 -0
- package/src/spawn-data-server.ts +247 -0
- 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
|
+
}
|