@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,63 @@
|
|
|
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
|
+
import { runAstGroundTruth, type AstGroundTruthCommandOptions } from './ast-ground-truth.js';
|
|
11
|
+
import {
|
|
12
|
+
exitWithUsage,
|
|
13
|
+
helpRequested,
|
|
14
|
+
LOG_LEVEL_FLAG,
|
|
15
|
+
logLevelHelpLine,
|
|
16
|
+
logLevelOption,
|
|
17
|
+
parseHarnessArgs,
|
|
18
|
+
printHelp,
|
|
19
|
+
SERVICES_FLAG,
|
|
20
|
+
type UsageError
|
|
21
|
+
} from './harness-args.js';
|
|
22
|
+
import { OUT_FILE_FLAG } from './headless-harness.js';
|
|
23
|
+
|
|
24
|
+
const VALUE_FLAGS = [OUT_FILE_FLAG, LOG_LEVEL_FLAG] as const;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Every flag `ast-ground-truth` accepts, derived from the sets the parser is
|
|
28
|
+
* handed so the list cannot claim a flag the parser would reject. `--services`
|
|
29
|
+
* is consumed by the shared harness parser itself, so it is in neither set.
|
|
30
|
+
*/
|
|
31
|
+
export const AST_GROUND_TRUTH_FLAGS: readonly string[] = [SERVICES_FLAG, ...VALUE_FLAGS];
|
|
32
|
+
|
|
33
|
+
/** The subset that takes a value, so `--help` in a value position reads as data. */
|
|
34
|
+
export const AST_GROUND_TRUTH_VALUE_FLAGS: readonly string[] = AST_GROUND_TRUTH_FLAGS;
|
|
35
|
+
|
|
36
|
+
/** The `--help` text, as data, held to {@link AST_GROUND_TRUTH_FLAGS} by a test. */
|
|
37
|
+
export const AST_GROUND_TRUTH_HELP: readonly string[] = [
|
|
38
|
+
'Usage: hydranium-cli ast-ground-truth --services <module> <workspace> [--out-file <file>] [--log-level <lvl>]',
|
|
39
|
+
'',
|
|
40
|
+
"Tally a workspace's live-model AST nodes by `$type` — the ground truth that",
|
|
41
|
+
'`analyze-heap --validate <gt.json>` checks a heap snapshot against. `<module>`',
|
|
42
|
+
'exports a zero-arg `createServices(): { shared }` thunk. Emits the',
|
|
43
|
+
'`{ documents, totalAstNodes, byType }` JSON on stdout (or `--out-file`).',
|
|
44
|
+
'',
|
|
45
|
+
'Options:',
|
|
46
|
+
' --services <module> ESM module exporting `createServices(): { shared }` (required).',
|
|
47
|
+
' <workspace> Workspace root (path or file URI) to build (required).',
|
|
48
|
+
' --out-file <file> Write the JSON to this file instead of stdout.',
|
|
49
|
+
logLevelHelpLine(22)
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
export function parseAstGroundTruthArgs(args: string[], onError: UsageError = exitWithUsage): AstGroundTruthCommandOptions {
|
|
53
|
+
const { servicesModule, workspace, options } = parseHarnessArgs(args, 'ast-ground-truth', VALUE_FLAGS, [], { onError });
|
|
54
|
+
return { servicesModule, workspace, outFile: options[OUT_FILE_FLAG], logLevel: logLevelOption(options[LOG_LEVEL_FLAG]) };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function runAstGroundTruthCommand(args: string[]): Promise<void> {
|
|
58
|
+
if (helpRequested(args, AST_GROUND_TRUTH_VALUE_FLAGS)) {
|
|
59
|
+
printHelp(AST_GROUND_TRUTH_HELP);
|
|
60
|
+
return Promise.resolve();
|
|
61
|
+
}
|
|
62
|
+
return runAstGroundTruth(parseAstGroundTruthArgs(args));
|
|
63
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
* Spawned child of the `ast-ground-truth` subcommand. Launched by the parent with
|
|
12
|
+
* `node <this> --services <module> <workspace> [--out-file <file>]`. It
|
|
13
|
+
* dynamic-imports the head's services module, runs the framework's
|
|
14
|
+
* `collectAstGroundTruth`, and writes the `{ documents, totalAstNodes, byType }`
|
|
15
|
+
* JSON to stdout (or `--out-file`). Never imported — only spawned.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import * as fs from 'node:fs';
|
|
19
|
+
import { loadHeadlessContext, OUT_FILE_FLAG } from './headless-harness.js';
|
|
20
|
+
|
|
21
|
+
interface DriverArgs {
|
|
22
|
+
servicesModule?: string;
|
|
23
|
+
workspace?: string;
|
|
24
|
+
outFile?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseArgs(argv: string[]): DriverArgs {
|
|
28
|
+
const parsed: DriverArgs = {};
|
|
29
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
30
|
+
const flag = argv[index];
|
|
31
|
+
const next = (): string => {
|
|
32
|
+
const value = argv[index + 1];
|
|
33
|
+
if (value === undefined) {
|
|
34
|
+
console.error(`Missing value for ${flag}`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
index += 1;
|
|
38
|
+
return value;
|
|
39
|
+
};
|
|
40
|
+
switch (flag) {
|
|
41
|
+
case '--services':
|
|
42
|
+
parsed.servicesModule = next();
|
|
43
|
+
break;
|
|
44
|
+
case OUT_FILE_FLAG:
|
|
45
|
+
parsed.outFile = next();
|
|
46
|
+
break;
|
|
47
|
+
default:
|
|
48
|
+
if (flag.startsWith('--')) {
|
|
49
|
+
console.error(`Unknown option: ${flag}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
parsed.workspace = flag;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function main(argv: string[]): Promise<void> {
|
|
59
|
+
const args = parseArgs(argv);
|
|
60
|
+
if (!args.servicesModule || !args.workspace) {
|
|
61
|
+
console.error('ast-ground-truth-driver: --services <module> and a <workspace> are required.');
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
const { createServices, coreNode } = await loadHeadlessContext(args.servicesModule);
|
|
65
|
+
const result = await coreNode.collectAstGroundTruth({ createServices, workspace: args.workspace });
|
|
66
|
+
const json = JSON.stringify(result, undefined, 2);
|
|
67
|
+
if (args.outFile) {
|
|
68
|
+
fs.writeFileSync(args.outFile, `${json}\n`);
|
|
69
|
+
// Progress to stderr keeps stdout clean for callers that capture either form.
|
|
70
|
+
console.error(`Ground truth: ${result.documents} docs, ${result.totalAstNodes} AST nodes -> ${args.outFile}`);
|
|
71
|
+
} else {
|
|
72
|
+
console.log(json);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
main(process.argv.slice(2))
|
|
77
|
+
.then(() => process.exit(0))
|
|
78
|
+
.catch((error: unknown) => {
|
|
79
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { OUT_FILE_FLAG, runDriverChild, type DriverSpawnOptions } from './headless-harness.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Absolute path to the spawned driver. From `lib/commands/ast-ground-truth.js`
|
|
15
|
+
* the compiled driver sits beside it at `lib/commands/ast-ground-truth-driver.js`.
|
|
16
|
+
*/
|
|
17
|
+
const DRIVER = fileURLToPath(new URL('./ast-ground-truth-driver.js', import.meta.url));
|
|
18
|
+
|
|
19
|
+
/** Options for the {@link runAstGroundTruth} subcommand. */
|
|
20
|
+
export interface AstGroundTruthCommandOptions extends DriverSpawnOptions {
|
|
21
|
+
/** ESM module exporting a zero-arg `createServices(): { shared }` thunk. */
|
|
22
|
+
readonly servicesModule: string;
|
|
23
|
+
/** Workspace root (filesystem path or file URI) to build. */
|
|
24
|
+
readonly workspace: string;
|
|
25
|
+
/** Write the JSON to this file instead of stdout. */
|
|
26
|
+
readonly outFile?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Build the argv the driver child parses. Pure → unit-testable. */
|
|
30
|
+
export function buildGroundTruthDriverArgs(options: AstGroundTruthCommandOptions): string[] {
|
|
31
|
+
const args = ['--services', options.servicesModule, options.workspace];
|
|
32
|
+
if (options.outFile !== undefined) {
|
|
33
|
+
args.push(OUT_FILE_FLAG, options.outFile);
|
|
34
|
+
}
|
|
35
|
+
return args;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Headless live-model `$type` census — the ground truth the offline heap
|
|
40
|
+
* analyzer validates a snapshot's classification against
|
|
41
|
+
* (`analyze-heap --validate <gt.json>`). Like `measure-memory`, the binary is
|
|
42
|
+
* language-agnostic, so the head passes a `createServices` module (`--services`);
|
|
43
|
+
* the work runs in a spawned child that dynamic-imports it, builds the workspace,
|
|
44
|
+
* and emits the `{ documents, totalAstNodes, byType }` JSON on stdout (or
|
|
45
|
+
* `--out-file`). No `--expose-gc` needed — this walks the AST, it does not weigh it.
|
|
46
|
+
*/
|
|
47
|
+
export function runAstGroundTruth(options: AstGroundTruthCommandOptions): Promise<void> {
|
|
48
|
+
return runDriverChild(['--max-old-space-size=8192', DRIVER, ...buildGroundTruthDriverArgs(options)], options);
|
|
49
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import { deriveAstFileFromLangiumConfig, loadTransferModelConfig, mergeTransferModelOptions } from './generate-transfer-model-config.js';
|
|
12
|
+
import { generateTransferModel, watchTransferModel, type GenerateTransferModelOptions } from './generate-transfer-model.js';
|
|
13
|
+
import { exitWithUsage, helpRequested, printHelp, type UsageError } from './harness-args.js';
|
|
14
|
+
import { wireSigintAbort } from './watch.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Every flag `generate-transfer-model` accepts.
|
|
18
|
+
*
|
|
19
|
+
* Listed rather than derived: this command takes no `--services` and no
|
|
20
|
+
* positional, so it parses its own switch instead of going through the shared
|
|
21
|
+
* harness parser, and there is no flag set to derive the list from. A test
|
|
22
|
+
* therefore drives the parser with each name to prove the switch has a case for
|
|
23
|
+
* it, which the harness-backed commands get for free.
|
|
24
|
+
*/
|
|
25
|
+
export const GENERATE_TRANSFER_MODEL_FLAGS: readonly string[] = [
|
|
26
|
+
'--config',
|
|
27
|
+
'--langium-config',
|
|
28
|
+
'--watch',
|
|
29
|
+
'--ast-file',
|
|
30
|
+
'--augmentation-file',
|
|
31
|
+
'--out-file',
|
|
32
|
+
'--element-type-name',
|
|
33
|
+
'--terminals-name',
|
|
34
|
+
'--terminals-source-name',
|
|
35
|
+
'--skip-type-alias',
|
|
36
|
+
'--skip-terminal',
|
|
37
|
+
'--regen-command'
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The subset that takes a value, so `--help` in a value position reads as data.
|
|
42
|
+
* `--watch` is the only presence-only flag; `--regen-command` is the one where a
|
|
43
|
+
* caller plausibly writes that literal text into a generated header.
|
|
44
|
+
*/
|
|
45
|
+
export const GENERATE_TRANSFER_MODEL_VALUE_FLAGS: readonly string[] = GENERATE_TRANSFER_MODEL_FLAGS.filter(flag => flag !== '--watch');
|
|
46
|
+
|
|
47
|
+
/** The `--help` text, as data, held to {@link GENERATE_TRANSFER_MODEL_FLAGS} by a test. */
|
|
48
|
+
export const GENERATE_TRANSFER_MODEL_HELP: readonly string[] = [
|
|
49
|
+
'Usage: hydranium-cli generate-transfer-model [options]',
|
|
50
|
+
'',
|
|
51
|
+
'Generate a serializable transfer-model TypeScript file from a Langium AST. The',
|
|
52
|
+
'required inputs (--ast-file / --augmentation-file / --out-file) may instead come',
|
|
53
|
+
'from a --config JSON file, and --ast-file can be auto-discovered from a Langium',
|
|
54
|
+
"config's `out` directory. Precedence, highest first: explicit flags, then the",
|
|
55
|
+
'--config file, then the langium-config-derived --ast-file.',
|
|
56
|
+
'',
|
|
57
|
+
'Options:',
|
|
58
|
+
' --config <path> JSON config supplying any of the options below.',
|
|
59
|
+
" Relative paths resolve against the config's dir.",
|
|
60
|
+
" --langium-config <path> Derive --ast-file from this config's `out` dir",
|
|
61
|
+
' (<out>/ast.ts). Defaults to ./langium-config.json.',
|
|
62
|
+
' --watch Regenerate on AST / augmentation file changes.',
|
|
63
|
+
' --ast-file <path> Langium-generated AST source file (required).',
|
|
64
|
+
' --augmentation-file <path> Module-augmentation file (required).',
|
|
65
|
+
' --out-file <path> Destination for the generated transfer model (required).',
|
|
66
|
+
" Do NOT point this inside langium-cli's own `out`",
|
|
67
|
+
' directory: it treats that directory as exclusively',
|
|
68
|
+
' its own, so every `langium generate` reports this',
|
|
69
|
+
' file as unexpected and offers to delete it.',
|
|
70
|
+
' --element-type-name <name> Base element type name in output. Default: TransferElement.',
|
|
71
|
+
' --terminals-name <name> Terminals const name in output. Default: ModelTerminals.',
|
|
72
|
+
' --terminals-source-name <n> Source-side terminals variable name. Default: <LanguageId>Terminals.',
|
|
73
|
+
' --skip-type-alias <name> Type alias to exclude (repeatable).',
|
|
74
|
+
' --skip-terminal <name> Terminal to exclude (repeatable).',
|
|
75
|
+
' --regen-command <text> Header line shown in the generated file (regen instructions).'
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
/** The flags as read, before any config file or langium-config derivation is folded in. */
|
|
79
|
+
export interface ParsedGenerateOptions {
|
|
80
|
+
flags: Partial<GenerateTransferModelOptions>;
|
|
81
|
+
configFile?: string;
|
|
82
|
+
langiumConfigFile?: string;
|
|
83
|
+
watch: boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function parseGenerateOptions(args: string[], onError: UsageError = exitWithUsage): ParsedGenerateOptions {
|
|
87
|
+
const skipTypeAliases: string[] = [];
|
|
88
|
+
const skipTerminals: string[] = [];
|
|
89
|
+
const flags: Partial<GenerateTransferModelOptions> = {};
|
|
90
|
+
let configFile: string | undefined;
|
|
91
|
+
let langiumConfigFile: string | undefined;
|
|
92
|
+
let watch = false;
|
|
93
|
+
|
|
94
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
95
|
+
const flag = args[index];
|
|
96
|
+
const next = (): string => {
|
|
97
|
+
const value = args[index + 1];
|
|
98
|
+
if (value === undefined) {
|
|
99
|
+
onError(`Missing value for ${flag}`);
|
|
100
|
+
}
|
|
101
|
+
index += 1;
|
|
102
|
+
return value;
|
|
103
|
+
};
|
|
104
|
+
switch (flag) {
|
|
105
|
+
case '--config':
|
|
106
|
+
configFile = next();
|
|
107
|
+
break;
|
|
108
|
+
case '--langium-config':
|
|
109
|
+
langiumConfigFile = next();
|
|
110
|
+
break;
|
|
111
|
+
case '--watch':
|
|
112
|
+
watch = true;
|
|
113
|
+
break;
|
|
114
|
+
case '--ast-file':
|
|
115
|
+
flags.astFile = next();
|
|
116
|
+
break;
|
|
117
|
+
case '--augmentation-file':
|
|
118
|
+
flags.augmentationFile = next();
|
|
119
|
+
break;
|
|
120
|
+
case '--out-file':
|
|
121
|
+
flags.outFile = next();
|
|
122
|
+
break;
|
|
123
|
+
case '--element-type-name':
|
|
124
|
+
flags.elementTypeName = next();
|
|
125
|
+
break;
|
|
126
|
+
case '--terminals-name':
|
|
127
|
+
flags.terminalsName = next();
|
|
128
|
+
break;
|
|
129
|
+
case '--terminals-source-name':
|
|
130
|
+
flags.terminalsSourceName = next();
|
|
131
|
+
break;
|
|
132
|
+
case '--skip-type-alias':
|
|
133
|
+
skipTypeAliases.push(next());
|
|
134
|
+
break;
|
|
135
|
+
case '--skip-terminal':
|
|
136
|
+
skipTerminals.push(next());
|
|
137
|
+
break;
|
|
138
|
+
case '--regen-command':
|
|
139
|
+
flags.regenCommand = next();
|
|
140
|
+
break;
|
|
141
|
+
default:
|
|
142
|
+
onError(`Unknown option: ${flag}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Only record the repeatable arrays when the flag actually appeared, so an empty
|
|
147
|
+
// `--skip-*` doesn't shadow a config-file value in the merge (first-defined-wins).
|
|
148
|
+
if (skipTypeAliases.length) {
|
|
149
|
+
flags.skipTypeAliases = skipTypeAliases;
|
|
150
|
+
}
|
|
151
|
+
if (skipTerminals.length) {
|
|
152
|
+
flags.skipTerminals = skipTerminals;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { flags, configFile, langiumConfigFile, watch };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Only auto-detect the default `./langium-config.json` when it exists — an explicit
|
|
160
|
+
* `--langium-config` always wins and is returned as-is (the caller reports errors if
|
|
161
|
+
* it is unreadable).
|
|
162
|
+
*/
|
|
163
|
+
function resolveLangiumConfigPath(explicit: string | undefined): string | undefined {
|
|
164
|
+
if (explicit !== undefined) {
|
|
165
|
+
return explicit;
|
|
166
|
+
}
|
|
167
|
+
return fs.existsSync('langium-config.json') ? 'langium-config.json' : undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function runGenerateTransferModelCommand(args: string[]): void | Promise<void> {
|
|
171
|
+
if (helpRequested(args, GENERATE_TRANSFER_MODEL_VALUE_FLAGS)) {
|
|
172
|
+
printHelp(GENERATE_TRANSFER_MODEL_HELP);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const parsed = parseGenerateOptions(args);
|
|
177
|
+
|
|
178
|
+
// Assemble option sources highest-precedence first: explicit flags, then the
|
|
179
|
+
// --config file, then a langium-config-derived --ast-file. Merge validates the
|
|
180
|
+
// three required paths are present after all sources are considered.
|
|
181
|
+
const sources: Array<Partial<GenerateTransferModelOptions>> = [parsed.flags];
|
|
182
|
+
if (parsed.configFile !== undefined) {
|
|
183
|
+
sources.push(loadTransferModelConfig(parsed.configFile));
|
|
184
|
+
}
|
|
185
|
+
const langiumConfig = resolveLangiumConfigPath(parsed.langiumConfigFile);
|
|
186
|
+
if (langiumConfig !== undefined) {
|
|
187
|
+
const derivedAstFile = deriveAstFileFromLangiumConfig(langiumConfig);
|
|
188
|
+
if (derivedAstFile !== undefined) {
|
|
189
|
+
if (parsed.langiumConfigFile !== undefined) {
|
|
190
|
+
console.log(`Deriving --ast-file from ${langiumConfig}: ${derivedAstFile}`);
|
|
191
|
+
}
|
|
192
|
+
sources.push({ astFile: derivedAstFile });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const options = mergeTransferModelOptions(...sources);
|
|
197
|
+
if (parsed.watch) {
|
|
198
|
+
const controller = new AbortController();
|
|
199
|
+
const unwire = wireSigintAbort(controller);
|
|
200
|
+
return watchTransferModel(options, { signal: controller.signal }).finally(() => unwire());
|
|
201
|
+
}
|
|
202
|
+
generateTransferModel(options);
|
|
203
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
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
|
+
import * as fs from 'node:fs';
|
|
11
|
+
import * as path from 'node:path';
|
|
12
|
+
import type { GenerateTransferModelOptions } from './generate-transfer-model.js';
|
|
13
|
+
|
|
14
|
+
/** Path-valued option keys — resolved relative to a config/langium-config file's directory. */
|
|
15
|
+
const PATH_KEYS = ['astFile', 'augmentationFile', 'outFile'] as const;
|
|
16
|
+
|
|
17
|
+
/** Resolve `value` against `baseDir` when it is relative; absolute paths pass through. */
|
|
18
|
+
function resolveAgainst(baseDir: string, value: string): string {
|
|
19
|
+
return path.isAbsolute(value) ? value : path.resolve(baseDir, value);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Read a transfer-model JSON config file into a partial options object. Only the
|
|
24
|
+
* known {@link GenerateTransferModelOptions} keys are picked (unknown keys are
|
|
25
|
+
* ignored); the {@link PATH_KEYS} are resolved relative to the config file's own
|
|
26
|
+
* directory so a config can use paths relative to itself. Throws a clear error on
|
|
27
|
+
* unreadable / non-object / invalid JSON.
|
|
28
|
+
*/
|
|
29
|
+
export function loadTransferModelConfig(configPath: string): Partial<GenerateTransferModelOptions> {
|
|
30
|
+
let raw: unknown;
|
|
31
|
+
try {
|
|
32
|
+
raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
33
|
+
} catch (error: unknown) {
|
|
34
|
+
throw new Error(`Cannot read transfer-model config '${configPath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
35
|
+
}
|
|
36
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
|
|
37
|
+
throw new Error(`Transfer-model config '${configPath}' must be a JSON object.`);
|
|
38
|
+
}
|
|
39
|
+
const source = raw as Record<string, unknown>;
|
|
40
|
+
const baseDir = path.dirname(path.resolve(configPath));
|
|
41
|
+
const result: Partial<GenerateTransferModelOptions> = {};
|
|
42
|
+
const stringKeys = [
|
|
43
|
+
'astFile',
|
|
44
|
+
'augmentationFile',
|
|
45
|
+
'outFile',
|
|
46
|
+
'elementTypeName',
|
|
47
|
+
'terminalsName',
|
|
48
|
+
'terminalsSourceName',
|
|
49
|
+
'regenCommand'
|
|
50
|
+
] as const;
|
|
51
|
+
for (const key of stringKeys) {
|
|
52
|
+
const value = source[key];
|
|
53
|
+
if (typeof value === 'string') {
|
|
54
|
+
result[key] = (PATH_KEYS as readonly string[]).includes(key) ? resolveAgainst(baseDir, value) : value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const key of ['skipTypeAliases', 'skipTerminals'] as const) {
|
|
58
|
+
const value = source[key];
|
|
59
|
+
if (Array.isArray(value) && value.every(entry => typeof entry === 'string')) {
|
|
60
|
+
result[key] = value as string[];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Derive the Langium-generated AST file path from a `langium-config.json`: the
|
|
68
|
+
* generator emits `ast.ts` into the config's `out` directory, so the AST file is
|
|
69
|
+
* `<config-dir>/<out>/ast.ts`. Returns `undefined` when the config declares no
|
|
70
|
+
* `out` (nothing to derive). Throws on unreadable / invalid JSON.
|
|
71
|
+
*/
|
|
72
|
+
export function deriveAstFileFromLangiumConfig(langiumConfigPath: string): string | undefined {
|
|
73
|
+
let raw: unknown;
|
|
74
|
+
try {
|
|
75
|
+
raw = JSON.parse(fs.readFileSync(langiumConfigPath, 'utf-8'));
|
|
76
|
+
} catch (error: unknown) {
|
|
77
|
+
throw new Error(`Cannot read langium-config '${langiumConfigPath}': ${error instanceof Error ? error.message : String(error)}`);
|
|
78
|
+
}
|
|
79
|
+
const out = (raw as { out?: unknown })?.out;
|
|
80
|
+
if (typeof out !== 'string') {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
const baseDir = path.dirname(path.resolve(langiumConfigPath));
|
|
84
|
+
return path.resolve(baseDir, out, 'ast.ts');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Merge partial option sources into a complete {@link GenerateTransferModelOptions},
|
|
89
|
+
* highest precedence first: for each field the first source that defines it wins.
|
|
90
|
+
* Pure over its inputs. Throws listing every required path (`astFile` /
|
|
91
|
+
* `augmentationFile` / `outFile`) still missing after the merge.
|
|
92
|
+
*/
|
|
93
|
+
export function mergeTransferModelOptions(...sources: Array<Partial<GenerateTransferModelOptions>>): GenerateTransferModelOptions {
|
|
94
|
+
const pick = <K extends keyof GenerateTransferModelOptions>(key: K): GenerateTransferModelOptions[K] | undefined => {
|
|
95
|
+
for (const source of sources) {
|
|
96
|
+
if (source[key] !== undefined) {
|
|
97
|
+
return source[key];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return undefined;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const astFile = pick('astFile');
|
|
104
|
+
const augmentationFile = pick('augmentationFile');
|
|
105
|
+
const outFile = pick('outFile');
|
|
106
|
+
const missing = [
|
|
107
|
+
['astFile (--ast-file)', astFile],
|
|
108
|
+
['augmentationFile (--augmentation-file)', augmentationFile],
|
|
109
|
+
['outFile (--out-file)', outFile]
|
|
110
|
+
]
|
|
111
|
+
.filter(([, value]) => value === undefined)
|
|
112
|
+
.map(([label]) => label);
|
|
113
|
+
if (missing.length) {
|
|
114
|
+
throw new Error(`Missing required transfer-model option(s): ${missing.join(', ')}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
astFile: astFile!,
|
|
119
|
+
augmentationFile: augmentationFile!,
|
|
120
|
+
outFile: outFile!,
|
|
121
|
+
elementTypeName: pick('elementTypeName'),
|
|
122
|
+
terminalsName: pick('terminalsName'),
|
|
123
|
+
terminalsSourceName: pick('terminalsSourceName'),
|
|
124
|
+
skipTypeAliases: pick('skipTypeAliases'),
|
|
125
|
+
skipTerminals: pick('skipTerminals'),
|
|
126
|
+
regenCommand: pick('regenCommand')
|
|
127
|
+
};
|
|
128
|
+
}
|