@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,138 @@
|
|
|
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
|
+
// Type-only import: the CLI is head-neutral and carries no runtime `@hydranium/core`
|
|
11
|
+
// dependency (the head's copy is resolved at run time via `loadHeadlessContext`).
|
|
12
|
+
import type { GrammarReflection, TypeReflection } from '@hydranium/core/node';
|
|
13
|
+
|
|
14
|
+
/** Escape a value for a Markdown table cell (only `|` and newlines can break a row). */
|
|
15
|
+
function cell(value: string): string {
|
|
16
|
+
return value.replace(/\|/g, '\\|').replace(/\r?\n/g, ' ');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Wrap a value in inline-code backticks, escaping any embedded backtick run. */
|
|
20
|
+
function code(value: string): string {
|
|
21
|
+
return `\`${value}\``;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The Markdown for each language's grammar surface — extensions, entry rule, terminals table. */
|
|
25
|
+
function languageSection(reflection: GrammarReflection): string[] {
|
|
26
|
+
const lines: string[] = ['## Languages', ''];
|
|
27
|
+
for (const language of reflection.languages) {
|
|
28
|
+
lines.push(`### ${language.languageId}`, '');
|
|
29
|
+
const extensions = language.fileExtensions.length ? language.fileExtensions.map(code).join(', ') : '_none_';
|
|
30
|
+
lines.push(`- File extensions: ${extensions}`);
|
|
31
|
+
lines.push(`- Entry rule: ${language.entryRule ? code(language.entryRule) : '_none_'}`);
|
|
32
|
+
lines.push(`- Terminals: ${language.terminals.length}`, '');
|
|
33
|
+
if (language.terminals.length) {
|
|
34
|
+
lines.push('| Terminal | Pattern | Hidden |', '| --- | --- | --- |');
|
|
35
|
+
for (const terminal of language.terminals) {
|
|
36
|
+
const pattern = terminal.pattern !== undefined ? code(terminal.pattern) : '';
|
|
37
|
+
lines.push(`| ${cell(terminal.name)} | ${cell(pattern)} | ${terminal.hidden ? 'yes' : ''} |`);
|
|
38
|
+
}
|
|
39
|
+
lines.push('');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return lines;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A depth-first hierarchy tree from the roots (types with no super types) down
|
|
47
|
+
* through `directSubTypes`. A type reachable from several supertypes appears under
|
|
48
|
+
* each; a cycle (should not occur in a grammar) is broken by not re-descending a
|
|
49
|
+
* type already on the current path.
|
|
50
|
+
*/
|
|
51
|
+
function hierarchySection(reflection: GrammarReflection): string[] {
|
|
52
|
+
const byName = new Map(reflection.types.map(type => [type.name, type]));
|
|
53
|
+
const roots = reflection.types.filter(type => type.superTypes.length === 0);
|
|
54
|
+
const lines: string[] = ['## Type hierarchy', ''];
|
|
55
|
+
const walk = (type: TypeReflection, depth: number, path: ReadonlySet<string>): void => {
|
|
56
|
+
lines.push(`${' '.repeat(depth)}- ${type.name}`);
|
|
57
|
+
const nextPath = new Set(path).add(type.name);
|
|
58
|
+
for (const childName of type.directSubTypes) {
|
|
59
|
+
const child = byName.get(childName);
|
|
60
|
+
if (child && !nextPath.has(childName)) {
|
|
61
|
+
walk(child, depth + 1, nextPath);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
for (const root of roots) {
|
|
66
|
+
walk(root, 0, new Set());
|
|
67
|
+
}
|
|
68
|
+
lines.push('');
|
|
69
|
+
return lines;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The `Type.property → TargetType` list of every cross-reference in the grammar. */
|
|
73
|
+
function crossReferenceSection(reflection: GrammarReflection): string[] {
|
|
74
|
+
const lines: string[] = ['## Cross-reference targets', ''];
|
|
75
|
+
let hasCrossReferences = false;
|
|
76
|
+
for (const type of reflection.types) {
|
|
77
|
+
for (const property of type.properties) {
|
|
78
|
+
if (property.referenceType !== undefined) {
|
|
79
|
+
lines.push(`- ${code(`${type.name}.${property.name}`)} → ${code(property.referenceType)}`);
|
|
80
|
+
hasCrossReferences = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!hasCrossReferences) {
|
|
85
|
+
lines.push('_No cross-references._');
|
|
86
|
+
}
|
|
87
|
+
lines.push('');
|
|
88
|
+
return lines;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Per-type detail: super types plus a property table (name, reference target, array, default). */
|
|
92
|
+
function typeDetailSection(reflection: GrammarReflection): string[] {
|
|
93
|
+
const lines: string[] = [`## Types (${reflection.types.length})`, ''];
|
|
94
|
+
for (const type of reflection.types) {
|
|
95
|
+
lines.push(`### ${type.name}`, '');
|
|
96
|
+
if (type.superTypes.length) {
|
|
97
|
+
lines.push(`- Super types: ${type.superTypes.map(code).join(', ')}`);
|
|
98
|
+
}
|
|
99
|
+
if (type.directSubTypes.length) {
|
|
100
|
+
lines.push(`- Sub types: ${type.directSubTypes.map(code).join(', ')}`);
|
|
101
|
+
}
|
|
102
|
+
lines.push('');
|
|
103
|
+
if (type.properties.length) {
|
|
104
|
+
lines.push('| Property | Reference target | Array | Default |', '| --- | --- | --- | --- |');
|
|
105
|
+
for (const property of type.properties) {
|
|
106
|
+
const reference = property.referenceType !== undefined ? code(property.referenceType) : '';
|
|
107
|
+
lines.push(
|
|
108
|
+
`| ${cell(property.name)} | ${cell(reference)} | ${property.array ? 'yes' : ''} | ${property.hasDefault ? 'yes' : ''} |`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
lines.push('_No properties._');
|
|
113
|
+
}
|
|
114
|
+
lines.push('');
|
|
115
|
+
}
|
|
116
|
+
return lines;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Render a grammar reflection for the terminal. `json` emits the raw
|
|
121
|
+
* {@link GrammarReflection} (the machine-readable contract); the default is a
|
|
122
|
+
* Markdown reference — a per-language grammar surface (terminals, entry rule), the
|
|
123
|
+
* type-hierarchy tree, the cross-reference target list, and a per-type property
|
|
124
|
+
* table — suitable for piping into a `.md` file.
|
|
125
|
+
*/
|
|
126
|
+
export function formatReflectionReport(reflection: GrammarReflection, options: { json?: boolean } = {}): string {
|
|
127
|
+
if (options.json) {
|
|
128
|
+
return JSON.stringify(reflection, undefined, 2);
|
|
129
|
+
}
|
|
130
|
+
return [
|
|
131
|
+
'# Grammar reflection',
|
|
132
|
+
'',
|
|
133
|
+
...languageSection(reflection),
|
|
134
|
+
...hierarchySection(reflection),
|
|
135
|
+
...crossReferenceSection(reflection),
|
|
136
|
+
...typeDetailSection(reflection)
|
|
137
|
+
].join('\n');
|
|
138
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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/reflect.js` the
|
|
15
|
+
* compiled driver sits beside it at `lib/commands/reflect-driver.js`.
|
|
16
|
+
*/
|
|
17
|
+
const DRIVER = fileURLToPath(new URL('./reflect-driver.js', import.meta.url));
|
|
18
|
+
|
|
19
|
+
/** Options for the {@link runReflect} subcommand. */
|
|
20
|
+
export interface ReflectCommandOptions extends DriverSpawnOptions {
|
|
21
|
+
/** ESM module exporting a zero-arg `createServices(): { shared }` thunk. */
|
|
22
|
+
readonly servicesModule: string;
|
|
23
|
+
/** Emit the raw JSON reflection instead of the human markdown report. */
|
|
24
|
+
readonly json?: boolean;
|
|
25
|
+
/** Write the report 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 buildReflectDriverArgs(options: ReflectCommandOptions): string[] {
|
|
31
|
+
const args = ['--services', options.servicesModule];
|
|
32
|
+
if (options.json) {
|
|
33
|
+
args.push('--json');
|
|
34
|
+
}
|
|
35
|
+
if (options.outFile !== undefined) {
|
|
36
|
+
args.push(OUT_FILE_FLAG, options.outFile);
|
|
37
|
+
}
|
|
38
|
+
return args;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Headless grammar/AST reflection — dumps a head's type hierarchy, terminals and
|
|
43
|
+
* cross-reference targets. Read-only: no workspace is built (the reflection is
|
|
44
|
+
* available as soon as the head's services register a language), so unlike the
|
|
45
|
+
* sibling harnesses this subcommand takes only `--services`. The work runs in a
|
|
46
|
+
* spawned child that dynamic-imports the head's `createServices` module, keeping
|
|
47
|
+
* the binary language-agnostic.
|
|
48
|
+
*/
|
|
49
|
+
export function runReflect(options: ReflectCommandOptions): Promise<void> {
|
|
50
|
+
return runDriverChild(['--max-old-space-size=8192', DRIVER, ...buildReflectDriverArgs(options)], options);
|
|
51
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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 {
|
|
11
|
+
assertRequired,
|
|
12
|
+
exitWithUsage,
|
|
13
|
+
helpRequested,
|
|
14
|
+
parseFlagOptions,
|
|
15
|
+
parseServerSpawnOptions,
|
|
16
|
+
printHelp,
|
|
17
|
+
SERVER_SPAWN_VALUE_FLAGS,
|
|
18
|
+
type UsageError
|
|
19
|
+
} from './harness-args.js';
|
|
20
|
+
import { runSave, type SaveCommandOptions } from './save.js';
|
|
21
|
+
|
|
22
|
+
const OWN_FLAGS = ['--uri', '--content', '--client-id'] as const;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Every flag `save` accepts, derived from its own list plus the shared spawn
|
|
26
|
+
* flags, so the list cannot claim a flag the parser would reject.
|
|
27
|
+
*/
|
|
28
|
+
export const SAVE_FLAGS: readonly string[] = [...SERVER_SPAWN_VALUE_FLAGS, ...OWN_FLAGS];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The subset that takes a value, so `--help` in a value position reads as data.
|
|
32
|
+
* Every flag in this family takes one, so the two lists coincide — and `--content`
|
|
33
|
+
* is the one where a caller plausibly writes that literal text.
|
|
34
|
+
*/
|
|
35
|
+
export const SAVE_VALUE_FLAGS: readonly string[] = SAVE_FLAGS;
|
|
36
|
+
|
|
37
|
+
/** The `--help` text, as data, held to {@link SAVE_FLAGS} by a test. */
|
|
38
|
+
export const SAVE_HELP: readonly string[] = [
|
|
39
|
+
'Usage: hydranium-cli save --server "<cmd>..." --uri <uri> --content <text|@file> [--client-id <id>] [--cwd <dir>] [--log-level <level>]',
|
|
40
|
+
'',
|
|
41
|
+
'Update the document at `<uri>` with `<content>` and persist via saveModelDocument.',
|
|
42
|
+
'Output is the post-save envelope as a single JSON line.',
|
|
43
|
+
'',
|
|
44
|
+
'Options:',
|
|
45
|
+
' --server "<cmd>" Command-line for the data-server subprocess (required).',
|
|
46
|
+
' --uri <uri> Document URI (required).',
|
|
47
|
+
' --content <text> Content to write (required). Prefix `@` to read from a file',
|
|
48
|
+
' (escape with `\\@` for literal `@`-leading content).',
|
|
49
|
+
' --client-id <id> clientId for authorship attribution. Default: hydranium-cli.',
|
|
50
|
+
' --cwd <dir> Working directory for the spawned child. Default: cwd.',
|
|
51
|
+
' --log-level <lvl> Log threshold for the spawned server (off|error|warn|info|debug|trace).'
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
export function parseSaveArgs(args: string[], onError: UsageError = exitWithUsage): SaveCommandOptions {
|
|
55
|
+
const { serverCommand, serverArgs, cwd, logLevel, extra } = parseServerSpawnOptions(args, 'save', onError);
|
|
56
|
+
const { uri, content, clientId } = parseFlagOptions(extra, 'save', OWN_FLAGS, onError);
|
|
57
|
+
return {
|
|
58
|
+
serverCommand,
|
|
59
|
+
serverArgs,
|
|
60
|
+
cwd,
|
|
61
|
+
logLevel,
|
|
62
|
+
uri: assertRequired(uri, '--uri', 'save', onError),
|
|
63
|
+
content: assertRequired(content, '--content', 'save', onError),
|
|
64
|
+
clientId
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function runSaveCommand(args: string[]): Promise<void> {
|
|
69
|
+
if (helpRequested(args, SAVE_VALUE_FLAGS)) {
|
|
70
|
+
printHelp(SAVE_HELP);
|
|
71
|
+
return Promise.resolve();
|
|
72
|
+
}
|
|
73
|
+
return runSave(parseSaveArgs(args));
|
|
74
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
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 type { LogThreshold, TransferElement } from '@hydranium/protocol';
|
|
11
|
+
import type { DataServerProtocol } from '@hydranium/protocol/data';
|
|
12
|
+
import * as fs from 'node:fs/promises';
|
|
13
|
+
import { logLevelEnv } from '../log-level.js';
|
|
14
|
+
import { withDataServer } from '../spawn-data-server.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Options for the {@link runSave} subcommand. Updates the document at
|
|
18
|
+
* `uri` with `content` (literal text or `@<path>` for a file reference)
|
|
19
|
+
* and persists it via the data-server's `saveModelDocument` RPC. Writes
|
|
20
|
+
* the post-save envelope as a single JSON line — the same shape
|
|
21
|
+
* `query` emits, so a save's output is consumable by the same `jq`
|
|
22
|
+
* pipelines.
|
|
23
|
+
*
|
|
24
|
+
* `clientId` defaults to `'hydranium-cli'` — adopters wanting a richer
|
|
25
|
+
* identity (per-user, per-script) override.
|
|
26
|
+
*/
|
|
27
|
+
export interface SaveCommandOptions {
|
|
28
|
+
readonly serverCommand: string;
|
|
29
|
+
readonly serverArgs?: readonly string[];
|
|
30
|
+
readonly cwd?: string;
|
|
31
|
+
/** Log threshold for the spawned server, set on its `HYDRANIUM_LOG_LEVEL` env. */
|
|
32
|
+
readonly logLevel?: LogThreshold;
|
|
33
|
+
readonly uri: string;
|
|
34
|
+
/** Literal content text, or `@<path>` to read from a file. */
|
|
35
|
+
readonly content: string;
|
|
36
|
+
readonly clientId?: string;
|
|
37
|
+
readonly write?: (line: string) => void;
|
|
38
|
+
readonly __proxyForTest?: DataServerProtocol<TransferElement>;
|
|
39
|
+
/** Test-only: override the file-reader so unit tests can stub `@<path>` expansion. */
|
|
40
|
+
readonly __readFileForTest?: (path: string) => Promise<string>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function runSave(options: SaveCommandOptions): Promise<void> {
|
|
44
|
+
const write = options.write ?? ((line: string) => process.stdout.write(line));
|
|
45
|
+
const clientId = options.clientId ?? 'hydranium-cli';
|
|
46
|
+
const model = await resolveContent(options.content, options.__readFileForTest);
|
|
47
|
+
|
|
48
|
+
if (options.__proxyForTest) {
|
|
49
|
+
const doc = await options.__proxyForTest.saveModelDocument({ uri: options.uri, clientId, model });
|
|
50
|
+
write(`${JSON.stringify(doc)}\n`);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
await withDataServer(
|
|
55
|
+
{
|
|
56
|
+
command: options.serverCommand,
|
|
57
|
+
args: options.serverArgs,
|
|
58
|
+
cwd: options.cwd,
|
|
59
|
+
env: options.logLevel ? logLevelEnv(options.logLevel) : undefined
|
|
60
|
+
},
|
|
61
|
+
async server => {
|
|
62
|
+
const doc = await server.saveModelDocument({ uri: options.uri, clientId, model });
|
|
63
|
+
write(`${JSON.stringify(doc)}\n`);
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Resolve the `--content` argument to its textual form. The `@<path>`
|
|
70
|
+
* prefix reads from a file (parses as UTF-8); anything else is taken
|
|
71
|
+
* literally. The leading `@` can be escaped as `\@` for content that
|
|
72
|
+
* legitimately starts with `@`.
|
|
73
|
+
*/
|
|
74
|
+
async function resolveContent(content: string, readFile?: (path: string) => Promise<string>): Promise<string> {
|
|
75
|
+
if (content.startsWith('@')) {
|
|
76
|
+
const path = content.slice(1);
|
|
77
|
+
const reader = readFile ?? ((target: string) => fs.readFile(target, 'utf8'));
|
|
78
|
+
return reader(path);
|
|
79
|
+
}
|
|
80
|
+
if (content.startsWith('\\@')) {
|
|
81
|
+
return content.slice(1);
|
|
82
|
+
}
|
|
83
|
+
return content;
|
|
84
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
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 {
|
|
11
|
+
exitWithUsage,
|
|
12
|
+
helpRequested,
|
|
13
|
+
LOG_LEVEL_FLAG,
|
|
14
|
+
logLevelHelpLine,
|
|
15
|
+
logLevelOption,
|
|
16
|
+
parseHarnessArgs,
|
|
17
|
+
printHelp,
|
|
18
|
+
SERVICES_FLAG,
|
|
19
|
+
type UsageError
|
|
20
|
+
} from './harness-args.js';
|
|
21
|
+
import { OUT_FILE_FLAG } from './headless-harness.js';
|
|
22
|
+
import { runValidate, type ValidateCommandOptions } from './validate.js';
|
|
23
|
+
|
|
24
|
+
const VALUE_FLAGS = [OUT_FILE_FLAG, LOG_LEVEL_FLAG] as const;
|
|
25
|
+
const BOOL_FLAGS = ['--strict', '--json'] as const;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Every flag `validate` accepts, derived from the sets the parser is handed so
|
|
29
|
+
* the list cannot claim a flag the parser would reject. `--services` is consumed
|
|
30
|
+
* by the shared harness parser itself, so it is in neither set.
|
|
31
|
+
*/
|
|
32
|
+
export const VALIDATE_FLAGS: readonly string[] = [SERVICES_FLAG, ...VALUE_FLAGS, ...BOOL_FLAGS];
|
|
33
|
+
|
|
34
|
+
/** The subset that takes a value, so `--help` in a value position reads as data. */
|
|
35
|
+
export const VALIDATE_VALUE_FLAGS: readonly string[] = [SERVICES_FLAG, ...VALUE_FLAGS];
|
|
36
|
+
|
|
37
|
+
/** The `--help` text, as data, held to {@link VALIDATE_FLAGS} by a test. */
|
|
38
|
+
export const VALIDATE_HELP: readonly string[] = [
|
|
39
|
+
'Usage: hydranium-cli validate --services <module> <workspace> [--strict] [--json] [--out-file <file>] [--log-level <lvl>]',
|
|
40
|
+
'',
|
|
41
|
+
'Build a workspace headlessly and report its validation diagnostics — a CI gate:',
|
|
42
|
+
'the process exits non-zero when any error is found. `<module>` is an ESM module',
|
|
43
|
+
'exporting a zero-arg `createServices(): { shared }` thunk (the head wires its own',
|
|
44
|
+
'filesystem inside). Needs @hydranium/core.',
|
|
45
|
+
'',
|
|
46
|
+
'Options:',
|
|
47
|
+
' --services <module> ESM module exporting `createServices(): { shared }` (required).',
|
|
48
|
+
' <workspace> Workspace root (path or file URI) to validate (required).',
|
|
49
|
+
' --strict Also fail (non-zero exit) on warnings, not only errors.',
|
|
50
|
+
' --json Emit the raw JSON result instead of the human report.',
|
|
51
|
+
' --out-file <file> Write the report to this file instead of stdout. The gate',
|
|
52
|
+
' still decides the exit code, and the file is written only',
|
|
53
|
+
' once the report exists, unlike a shell redirection, which',
|
|
54
|
+
' truncates the file before the workspace is even built.',
|
|
55
|
+
logLevelHelpLine(22)
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
export function parseValidateArgs(args: string[], onError: UsageError = exitWithUsage): ValidateCommandOptions {
|
|
59
|
+
const { servicesModule, workspace, options } = parseHarnessArgs(args, 'validate', VALUE_FLAGS, BOOL_FLAGS, { onError });
|
|
60
|
+
return {
|
|
61
|
+
servicesModule,
|
|
62
|
+
workspace,
|
|
63
|
+
strict: options['--strict'] === 'true',
|
|
64
|
+
json: options['--json'] === 'true',
|
|
65
|
+
outFile: options[OUT_FILE_FLAG],
|
|
66
|
+
logLevel: logLevelOption(options[LOG_LEVEL_FLAG])
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function runValidateCommand(args: string[]): Promise<void> {
|
|
71
|
+
if (helpRequested(args, VALIDATE_VALUE_FLAGS)) {
|
|
72
|
+
printHelp(VALIDATE_HELP);
|
|
73
|
+
return Promise.resolve();
|
|
74
|
+
}
|
|
75
|
+
return runValidate(parseValidateArgs(args));
|
|
76
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
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 `validate` subcommand. Launched by the parent with
|
|
12
|
+
* `node <this> --services <module> <workspace> [--json] [--strict]
|
|
13
|
+
* [--out-file <file>]`. It dynamic-imports the head's services module, runs the
|
|
14
|
+
* framework's `validateWorkspace`, delivers the report (human or `--json`) to
|
|
15
|
+
* stdout or the named file, and exits with the gate code (non-zero on errors;
|
|
16
|
+
* `--strict` also fails on warnings). Never imported — only spawned.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { emitReport, loadHeadlessContext, OUT_FILE_FLAG } from './headless-harness.js';
|
|
20
|
+
import { formatValidationReport, validationExitCode } from './validate-report.js';
|
|
21
|
+
|
|
22
|
+
interface DriverArgs {
|
|
23
|
+
servicesModule?: string;
|
|
24
|
+
workspace?: string;
|
|
25
|
+
json: boolean;
|
|
26
|
+
strict: boolean;
|
|
27
|
+
outFile?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseArgs(argv: string[]): DriverArgs {
|
|
31
|
+
const parsed: DriverArgs = { json: false, strict: false };
|
|
32
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
33
|
+
const flag = argv[index];
|
|
34
|
+
const next = (): string => {
|
|
35
|
+
const value = argv[index + 1];
|
|
36
|
+
if (value === undefined) {
|
|
37
|
+
console.error(`Missing value for ${flag}`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
index += 1;
|
|
41
|
+
return value;
|
|
42
|
+
};
|
|
43
|
+
switch (flag) {
|
|
44
|
+
case '--services':
|
|
45
|
+
parsed.servicesModule = next();
|
|
46
|
+
break;
|
|
47
|
+
case '--json':
|
|
48
|
+
parsed.json = true;
|
|
49
|
+
break;
|
|
50
|
+
case '--strict':
|
|
51
|
+
parsed.strict = true;
|
|
52
|
+
break;
|
|
53
|
+
case OUT_FILE_FLAG:
|
|
54
|
+
parsed.outFile = next();
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
if (flag.startsWith('--')) {
|
|
58
|
+
console.error(`Unknown option: ${flag}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
parsed.workspace = flag;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return parsed;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function main(argv: string[]): Promise<number> {
|
|
68
|
+
const args = parseArgs(argv);
|
|
69
|
+
if (!args.servicesModule || !args.workspace) {
|
|
70
|
+
console.error('validate-driver: --services <module> and a <workspace> are required.');
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
const { createServices, coreNode } = await loadHeadlessContext(args.servicesModule);
|
|
74
|
+
const result = await coreNode.validateWorkspace({ createServices, workspace: args.workspace });
|
|
75
|
+
emitReport(formatValidationReport(result, { json: args.json }), args.outFile);
|
|
76
|
+
return validationExitCode(result, args.strict);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main(process.argv.slice(2))
|
|
80
|
+
.then(code => process.exit(code))
|
|
81
|
+
.catch((error: unknown) => {
|
|
82
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
});
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
// Type-only import: the CLI is head-neutral and carries no runtime `@hydranium/core`
|
|
11
|
+
// dependency (the head's copy is resolved at run time via `loadHeadlessContext`).
|
|
12
|
+
import type { WorkspaceValidationResult } from '@hydranium/core/node';
|
|
13
|
+
|
|
14
|
+
/** `s` when `count !== 1`, else empty. */
|
|
15
|
+
function plural(count: number): string {
|
|
16
|
+
return count === 1 ? '' : 's';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** `<n> file` / `<n> files`. */
|
|
20
|
+
function fileCount(documents: number): string {
|
|
21
|
+
return `${documents} file${plural(documents)}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The exit code for a validation run: non-zero blocks the CI gate.
|
|
26
|
+
* Errors always fail; `strict` additionally fails on warnings. Info/hint
|
|
27
|
+
* findings are advisory and never affect the exit code.
|
|
28
|
+
*/
|
|
29
|
+
export function validationExitCode(result: WorkspaceValidationResult, strict = false): number {
|
|
30
|
+
if (result.counts.error > 0) {
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
if (strict && result.counts.warning > 0) {
|
|
34
|
+
return 1;
|
|
35
|
+
}
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** One-line severity tally over the file count, or the clean-workspace message. */
|
|
40
|
+
function summaryLine(result: WorkspaceValidationResult): string {
|
|
41
|
+
const { error, warning, info, hint } = result.counts;
|
|
42
|
+
if (error + warning + info + hint === 0) {
|
|
43
|
+
return `No problems found in ${fileCount(result.documents)}.`;
|
|
44
|
+
}
|
|
45
|
+
const parts: string[] = [];
|
|
46
|
+
if (error) {
|
|
47
|
+
parts.push(`${error} error${plural(error)}`);
|
|
48
|
+
}
|
|
49
|
+
if (warning) {
|
|
50
|
+
parts.push(`${warning} warning${plural(warning)}`);
|
|
51
|
+
}
|
|
52
|
+
if (info) {
|
|
53
|
+
parts.push(`${info} info`);
|
|
54
|
+
}
|
|
55
|
+
if (hint) {
|
|
56
|
+
parts.push(`${hint} hint${plural(hint)}`);
|
|
57
|
+
}
|
|
58
|
+
return `${parts.join(', ')} in ${fileCount(result.documents)}.`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Render a validation result for the terminal. `json` emits the raw
|
|
63
|
+
* {@link WorkspaceValidationResult} (the machine-readable CI contract); the
|
|
64
|
+
* default is a human report that groups findings by document — one header per
|
|
65
|
+
* file followed by `line:col severity message (code)` lines — and closes
|
|
66
|
+
* with the {@link summaryLine} tally.
|
|
67
|
+
*
|
|
68
|
+
* Findings arrive already grouped contiguously by document (the framework
|
|
69
|
+
* collector iterates per document), so a header is emitted whenever the URI
|
|
70
|
+
* changes rather than pre-bucketing.
|
|
71
|
+
*/
|
|
72
|
+
export function formatValidationReport(result: WorkspaceValidationResult, options: { json?: boolean } = {}): string {
|
|
73
|
+
if (options.json) {
|
|
74
|
+
return JSON.stringify(result, undefined, 2);
|
|
75
|
+
}
|
|
76
|
+
const lines: string[] = [];
|
|
77
|
+
let currentUri: string | undefined;
|
|
78
|
+
for (const finding of result.findings) {
|
|
79
|
+
if (finding.uri !== currentUri) {
|
|
80
|
+
if (currentUri !== undefined) {
|
|
81
|
+
lines.push('');
|
|
82
|
+
}
|
|
83
|
+
lines.push(finding.uri);
|
|
84
|
+
currentUri = finding.uri;
|
|
85
|
+
}
|
|
86
|
+
const location = `${finding.line + 1}:${finding.character + 1}`;
|
|
87
|
+
const code = finding.code !== undefined ? ` (${finding.code})` : '';
|
|
88
|
+
lines.push(` ${location.padEnd(7)} ${finding.severity.padEnd(7)} ${finding.message}${code}`);
|
|
89
|
+
}
|
|
90
|
+
if (result.findings.length > 0) {
|
|
91
|
+
lines.push('');
|
|
92
|
+
}
|
|
93
|
+
lines.push(summaryLine(result));
|
|
94
|
+
return lines.join('\n');
|
|
95
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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/validate.js` the
|
|
15
|
+
* compiled driver sits beside it at `lib/commands/validate-driver.js`.
|
|
16
|
+
*/
|
|
17
|
+
const DRIVER = fileURLToPath(new URL('./validate-driver.js', import.meta.url));
|
|
18
|
+
|
|
19
|
+
/** Options for the {@link runValidate} subcommand. */
|
|
20
|
+
export interface ValidateCommandOptions 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 and validate. */
|
|
24
|
+
readonly workspace: string;
|
|
25
|
+
/** Emit the raw JSON result instead of the human report. */
|
|
26
|
+
readonly json?: boolean;
|
|
27
|
+
/** Also fail (non-zero exit) on warnings, not only errors. */
|
|
28
|
+
readonly strict?: boolean;
|
|
29
|
+
/** Write the report to this file instead of stdout; the gate still sets the exit code. */
|
|
30
|
+
readonly outFile?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Build the argv the driver child parses. Pure → unit-testable. */
|
|
34
|
+
export function buildValidateDriverArgs(options: ValidateCommandOptions): string[] {
|
|
35
|
+
const args = ['--services', options.servicesModule, options.workspace];
|
|
36
|
+
if (options.json) {
|
|
37
|
+
args.push('--json');
|
|
38
|
+
}
|
|
39
|
+
if (options.strict) {
|
|
40
|
+
args.push('--strict');
|
|
41
|
+
}
|
|
42
|
+
if (options.outFile !== undefined) {
|
|
43
|
+
args.push(OUT_FILE_FLAG, options.outFile);
|
|
44
|
+
}
|
|
45
|
+
return args;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Headless workspace validation — a CI gate that builds a workspace, runs the
|
|
50
|
+
* language's validation checks, prints the diagnostics, and exits non-zero when
|
|
51
|
+
* any error is found (`--strict` also fails on warnings). Like the other headless
|
|
52
|
+
* harnesses the binary is language-agnostic, so the head passes a `createServices`
|
|
53
|
+
* module (`--services`); the work runs in a spawned child that dynamic-imports it.
|
|
54
|
+
* The child's exit code propagates to this process so shells and CI see the gate.
|
|
55
|
+
*/
|
|
56
|
+
export function runValidate(options: ValidateCommandOptions): Promise<void> {
|
|
57
|
+
return runDriverChild(['--max-old-space-size=8192', DRIVER, ...buildValidateDriverArgs(options)], options);
|
|
58
|
+
}
|