@hydranium/cli 1.0.0-next.210 → 1.0.0-next.213
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/README.md +8 -0
- package/lib/commands/ast-ground-truth-args.d.ts +3 -2
- package/lib/commands/ast-ground-truth-args.d.ts.map +1 -1
- package/lib/commands/ast-ground-truth-args.js +9 -7
- package/lib/commands/ast-ground-truth-args.js.map +1 -1
- package/lib/commands/harness-args.d.ts +18 -7
- package/lib/commands/harness-args.d.ts.map +1 -1
- package/lib/commands/harness-args.js +31 -13
- package/lib/commands/harness-args.js.map +1 -1
- package/lib/commands/headless-harness.d.ts +5 -2
- package/lib/commands/headless-harness.d.ts.map +1 -1
- package/lib/commands/headless-harness.js +50 -7
- package/lib/commands/headless-harness.js.map +1 -1
- package/lib/commands/init-templates.d.ts.map +1 -1
- package/lib/commands/init-templates.js +42 -2
- package/lib/commands/init-templates.js.map +1 -1
- package/lib/commands/init-workspace.d.ts +6 -0
- package/lib/commands/init-workspace.d.ts.map +1 -1
- package/lib/commands/init-workspace.js +11 -1
- package/lib/commands/init-workspace.js.map +1 -1
- package/lib/commands/init.d.ts +6 -0
- package/lib/commands/init.d.ts.map +1 -1
- package/lib/commands/init.js +4 -2
- package/lib/commands/init.js.map +1 -1
- package/lib/commands/lint-grammar-args.d.ts +3 -2
- package/lib/commands/lint-grammar-args.d.ts.map +1 -1
- package/lib/commands/lint-grammar-args.js +10 -7
- package/lib/commands/lint-grammar-args.js.map +1 -1
- package/lib/commands/measure-memory-args.d.ts +3 -2
- package/lib/commands/measure-memory-args.d.ts.map +1 -1
- package/lib/commands/measure-memory-args.js +10 -7
- package/lib/commands/measure-memory-args.js.map +1 -1
- package/lib/commands/model-docs-args.d.ts +4 -3
- package/lib/commands/model-docs-args.d.ts.map +1 -1
- package/lib/commands/model-docs-args.js +10 -8
- package/lib/commands/model-docs-args.js.map +1 -1
- package/lib/commands/reflect-args.d.ts +3 -2
- package/lib/commands/reflect-args.d.ts.map +1 -1
- package/lib/commands/reflect-args.js +10 -7
- package/lib/commands/reflect-args.js.map +1 -1
- package/lib/commands/validate-args.d.ts +3 -2
- package/lib/commands/validate-args.d.ts.map +1 -1
- package/lib/commands/validate-args.js +10 -7
- package/lib/commands/validate-args.js.map +1 -1
- package/package.json +14 -14
- package/src/commands/ast-ground-truth-args.ts +10 -7
- package/src/commands/harness-args.ts +36 -14
- package/src/commands/headless-harness.ts +54 -7
- package/src/commands/init-templates.ts +44 -2
- package/src/commands/init-workspace.ts +15 -1
- package/src/commands/init.ts +10 -2
- package/src/commands/lint-grammar-args.ts +11 -7
- package/src/commands/measure-memory-args.ts +11 -7
- package/src/commands/model-docs-args.ts +11 -8
- package/src/commands/reflect-args.ts +11 -7
- package/src/commands/validate-args.ts +11 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-docs-args.js","sourceRoot":"","sources":["../../src/commands/model-docs-args.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EACJ,aAAa,EACb,aAAa,EACb,eAAe,EACf,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,SAAS,
|
|
1
|
+
{"version":3,"file":"model-docs-args.js","sourceRoot":"","sources":["../../src/commands/model-docs-args.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EACJ,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,eAAe,EACf,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,SAAS,EAEX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAgC,MAAM,iBAAiB,CAAC;AAE7E,MAAM,WAAW,GAAG,CAAC,aAAa,EAAE,GAAG,eAAe,CAAU,CAAC;AAEjE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAsB,CAAC,GAAG,mBAAmB,EAAE,GAAG,WAAW,CAAC,CAAC;AAE5F,oFAAoF;AACpF,MAAM,CAAC,MAAM,sBAAsB,GAAsB,gBAAgB,CAAC;AAE1E,8EAA8E;AAC9E,MAAM,CAAC,MAAM,eAAe,GAAsB;IAC/C,oKAAoK;IACpK,EAAE;IACF,gFAAgF;IAChF,mFAAmF;IACnF,gFAAgF;IAChF,kFAAkF;IAClF,4EAA4E;IAC5E,uEAAuE;IACvE,EAAE;IACF,UAAU;IACV,yFAAyF;IACzF,GAAG,eAAe,CAAC,EAAE,CAAC;IACtB,oFAAoF;IACpF,kFAAkF;IAClF,kFAAkF;IAClF,GAAG,YAAY,CAAC,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,UAAU,kBAAkB,CAAC,IAAc,EAAE,OAAO,GAAe,aAAa;IACnF,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,gBAAgB,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,EAAE,EAAE;QAChG,gBAAgB,EAAE,KAAK;QACvB,OAAO;KACT,CAAC,CAAC;IACH,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;AAC/F,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAAc;IAC/C,IAAI,aAAa,CAAC,IAAI,EAAE,sBAAsB,CAAC,EAAE,CAAC;QAC/C,SAAS,CAAC,eAAe,CAAC,CAAC;QAC3B,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC5B,CAAC;IACD,OAAO,YAAY,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -10,8 +10,9 @@ import { type UsageError } from './harness-args.js';
|
|
|
10
10
|
import { type ReflectCommandOptions } from './reflect.js';
|
|
11
11
|
/**
|
|
12
12
|
* Every flag `reflect` accepts, derived from the sets the parser is handed so
|
|
13
|
-
* the list cannot claim a flag the parser would reject.
|
|
14
|
-
* by the shared harness parser itself,
|
|
13
|
+
* the list cannot claim a flag the parser would reject.
|
|
14
|
+
* {@link HARNESS_VALUE_FLAGS} are consumed by the shared harness parser itself,
|
|
15
|
+
* so they are in neither set.
|
|
15
16
|
*/
|
|
16
17
|
export declare const REFLECT_FLAGS: readonly string[];
|
|
17
18
|
/** The subset that takes a value, so `--help` in a value position reads as data. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reflect-args.d.ts","sourceRoot":"","sources":["../../src/commands/reflect-args.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,
|
|
1
|
+
{"version":3,"file":"reflect-args.d.ts","sourceRoot":"","sources":["../../src/commands/reflect-args.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAUJ,KAAK,UAAU,EACjB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAc,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAKtE;;;;;GAKG;AACH,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAA4D,CAAC;AAExG,oFAAoF;AACpF,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAA6C,CAAC;AAE/F,2EAA2E;AAC3E,eAAO,MAAM,YAAY,EAAE,SAAS,MAAM,EAiBzC,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,UAA0B,GAAG,qBAAqB,CAY3G;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAM/D"}
|
|
@@ -6,22 +6,23 @@
|
|
|
6
6
|
*
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
|
-
import { exitWithUsage, helpRequested, LOG_VALUE_FLAGS, logHelpLines, logOptions, parseHarnessArgs, printHelp
|
|
9
|
+
import { exitWithUsage, HARNESS_VALUE_FLAGS, helpRequested, importHelpLines, LOG_VALUE_FLAGS, logHelpLines, logOptions, parseHarnessArgs, printHelp } from './harness-args.js';
|
|
10
10
|
import { OUT_FILE_FLAG } from './headless-harness.js';
|
|
11
11
|
import { runReflect } from './reflect.js';
|
|
12
12
|
const VALUE_FLAGS = [OUT_FILE_FLAG, ...LOG_VALUE_FLAGS];
|
|
13
13
|
const BOOL_FLAGS = ['--json'];
|
|
14
14
|
/**
|
|
15
15
|
* Every flag `reflect` accepts, derived from the sets the parser is handed so
|
|
16
|
-
* the list cannot claim a flag the parser would reject.
|
|
17
|
-
* by the shared harness parser itself,
|
|
16
|
+
* the list cannot claim a flag the parser would reject.
|
|
17
|
+
* {@link HARNESS_VALUE_FLAGS} are consumed by the shared harness parser itself,
|
|
18
|
+
* so they are in neither set.
|
|
18
19
|
*/
|
|
19
|
-
export const REFLECT_FLAGS = [
|
|
20
|
+
export const REFLECT_FLAGS = [...HARNESS_VALUE_FLAGS, ...VALUE_FLAGS, ...BOOL_FLAGS];
|
|
20
21
|
/** The subset that takes a value, so `--help` in a value position reads as data. */
|
|
21
|
-
export const REFLECT_VALUE_FLAGS = [
|
|
22
|
+
export const REFLECT_VALUE_FLAGS = [...HARNESS_VALUE_FLAGS, ...VALUE_FLAGS];
|
|
22
23
|
/** The `--help` text, as data, held to {@link REFLECT_FLAGS} by a test. */
|
|
23
24
|
export const REFLECT_HELP = [
|
|
24
|
-
'Usage: hydranium-cli reflect --services <module> [--json] [--out-file <file>] [--log-level <lvl>] [--log-file <file>] [--log-file-level <lvl>]',
|
|
25
|
+
'Usage: hydranium-cli reflect --services <module> [--import <specifier>]... [--json] [--out-file <file>] [--log-level <lvl>] [--log-file <file>] [--log-file-level <lvl>]',
|
|
25
26
|
'',
|
|
26
27
|
"Dump a head's grammar/AST reflection — the type hierarchy, each language's",
|
|
27
28
|
'terminals and entry rule, and every cross-reference target. Read-only: no',
|
|
@@ -31,6 +32,7 @@ export const REFLECT_HELP = [
|
|
|
31
32
|
'',
|
|
32
33
|
'Options:',
|
|
33
34
|
' --services <module> ESM module exporting `createServices(): { shared }` (required).',
|
|
35
|
+
...importHelpLines(22),
|
|
34
36
|
' --json Emit the raw JSON reflection instead of the Markdown report.',
|
|
35
37
|
' --out-file <file> Write the report to this file instead of stdout. Written',
|
|
36
38
|
' only once the report exists, unlike a shell redirection,',
|
|
@@ -38,12 +40,13 @@ export const REFLECT_HELP = [
|
|
|
38
40
|
...logHelpLines(22)
|
|
39
41
|
];
|
|
40
42
|
export function parseReflectArgs(args, onError = exitWithUsage) {
|
|
41
|
-
const { servicesModule, options } = parseHarnessArgs(args, 'reflect', VALUE_FLAGS, BOOL_FLAGS, {
|
|
43
|
+
const { servicesModule, imports, options } = parseHarnessArgs(args, 'reflect', VALUE_FLAGS, BOOL_FLAGS, {
|
|
42
44
|
requireWorkspace: false,
|
|
43
45
|
onError
|
|
44
46
|
});
|
|
45
47
|
return {
|
|
46
48
|
servicesModule,
|
|
49
|
+
imports,
|
|
47
50
|
json: options['--json'] === 'true',
|
|
48
51
|
outFile: options[OUT_FILE_FLAG],
|
|
49
52
|
...logOptions(options)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reflect-args.js","sourceRoot":"","sources":["../../src/commands/reflect-args.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EACJ,aAAa,EACb,aAAa,EACb,eAAe,EACf,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,SAAS,
|
|
1
|
+
{"version":3,"file":"reflect-args.js","sourceRoot":"","sources":["../../src/commands/reflect-args.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EACJ,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,eAAe,EACf,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,SAAS,EAEX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,UAAU,EAA8B,MAAM,cAAc,CAAC;AAEtE,MAAM,WAAW,GAAG,CAAC,aAAa,EAAE,GAAG,eAAe,CAAU,CAAC;AACjE,MAAM,UAAU,GAAG,CAAC,QAAQ,CAAU,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAsB,CAAC,GAAG,mBAAmB,EAAE,GAAG,WAAW,EAAE,GAAG,UAAU,CAAC,CAAC;AAExG,oFAAoF;AACpF,MAAM,CAAC,MAAM,mBAAmB,GAAsB,CAAC,GAAG,mBAAmB,EAAE,GAAG,WAAW,CAAC,CAAC;AAE/F,2EAA2E;AAC3E,MAAM,CAAC,MAAM,YAAY,GAAsB;IAC5C,0KAA0K;IAC1K,EAAE;IACF,4EAA4E;IAC5E,2EAA2E;IAC3E,0EAA0E;IAC1E,6EAA6E;IAC7E,mFAAmF;IACnF,EAAE;IACF,UAAU;IACV,yFAAyF;IACzF,GAAG,eAAe,CAAC,EAAE,CAAC;IACtB,sFAAsF;IACtF,kFAAkF;IAClF,kFAAkF;IAClF,kFAAkF;IAClF,GAAG,YAAY,CAAC,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,UAAU,gBAAgB,CAAC,IAAc,EAAE,OAAO,GAAe,aAAa;IACjF,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE;QACrG,gBAAgB,EAAE,KAAK;QACvB,OAAO;KACT,CAAC,CAAC;IACH,OAAO;QACJ,cAAc;QACd,OAAO;QACP,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,KAAK,MAAM;QAClC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC;QAC/B,GAAG,UAAU,CAAC,OAAO,CAAC;KACxB,CAAC;AACL,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAc;IAC7C,IAAI,aAAa,CAAC,IAAI,EAAE,mBAAmB,CAAC,EAAE,CAAC;QAC5C,SAAS,CAAC,YAAY,CAAC,CAAC;QACxB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC5B,CAAC;IACD,OAAO,UAAU,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -10,8 +10,9 @@ import { type UsageError } from './harness-args.js';
|
|
|
10
10
|
import { type ValidateCommandOptions } from './validate.js';
|
|
11
11
|
/**
|
|
12
12
|
* Every flag `validate` accepts, derived from the sets the parser is handed so
|
|
13
|
-
* the list cannot claim a flag the parser would reject.
|
|
14
|
-
* by the shared harness parser itself,
|
|
13
|
+
* the list cannot claim a flag the parser would reject.
|
|
14
|
+
* {@link HARNESS_VALUE_FLAGS} are consumed by the shared harness parser itself,
|
|
15
|
+
* so they are in neither set.
|
|
15
16
|
*/
|
|
16
17
|
export declare const VALIDATE_FLAGS: readonly string[];
|
|
17
18
|
/** The subset that takes a value, so `--help` in a value position reads as data. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-args.d.ts","sourceRoot":"","sources":["../../src/commands/validate-args.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,
|
|
1
|
+
{"version":3,"file":"validate-args.d.ts","sourceRoot":"","sources":["../../src/commands/validate-args.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAUJ,KAAK,UAAU,EACjB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAe,KAAK,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAKzE;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAA4D,CAAC;AAEzG,oFAAoF;AACpF,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAA6C,CAAC;AAEhG,4EAA4E;AAC5E,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAmB1C,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,UAA0B,GAAG,sBAAsB,CAW7G;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAMhE"}
|
|
@@ -6,22 +6,23 @@
|
|
|
6
6
|
*
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
|
-
import { exitWithUsage, helpRequested, LOG_VALUE_FLAGS, logHelpLines, logOptions, parseHarnessArgs, printHelp
|
|
9
|
+
import { exitWithUsage, HARNESS_VALUE_FLAGS, helpRequested, importHelpLines, LOG_VALUE_FLAGS, logHelpLines, logOptions, parseHarnessArgs, printHelp } from './harness-args.js';
|
|
10
10
|
import { OUT_FILE_FLAG } from './headless-harness.js';
|
|
11
11
|
import { runValidate } from './validate.js';
|
|
12
12
|
const VALUE_FLAGS = [OUT_FILE_FLAG, ...LOG_VALUE_FLAGS];
|
|
13
13
|
const BOOL_FLAGS = ['--strict', '--json'];
|
|
14
14
|
/**
|
|
15
15
|
* Every flag `validate` accepts, derived from the sets the parser is handed so
|
|
16
|
-
* the list cannot claim a flag the parser would reject.
|
|
17
|
-
* by the shared harness parser itself,
|
|
16
|
+
* the list cannot claim a flag the parser would reject.
|
|
17
|
+
* {@link HARNESS_VALUE_FLAGS} are consumed by the shared harness parser itself,
|
|
18
|
+
* so they are in neither set.
|
|
18
19
|
*/
|
|
19
|
-
export const VALIDATE_FLAGS = [
|
|
20
|
+
export const VALIDATE_FLAGS = [...HARNESS_VALUE_FLAGS, ...VALUE_FLAGS, ...BOOL_FLAGS];
|
|
20
21
|
/** The subset that takes a value, so `--help` in a value position reads as data. */
|
|
21
|
-
export const VALIDATE_VALUE_FLAGS = [
|
|
22
|
+
export const VALIDATE_VALUE_FLAGS = [...HARNESS_VALUE_FLAGS, ...VALUE_FLAGS];
|
|
22
23
|
/** The `--help` text, as data, held to {@link VALIDATE_FLAGS} by a test. */
|
|
23
24
|
export const VALIDATE_HELP = [
|
|
24
|
-
'Usage: hydranium-cli validate --services <module> <workspace> [--strict] [--json] [--out-file <file>] [--log-level <lvl>] [--log-file <file>] [--log-file-level <lvl>]',
|
|
25
|
+
'Usage: hydranium-cli validate --services <module> [--import <specifier>]... <workspace> [--strict] [--json] [--out-file <file>] [--log-level <lvl>] [--log-file <file>] [--log-file-level <lvl>]',
|
|
25
26
|
'',
|
|
26
27
|
'Build a workspace headlessly and report its validation diagnostics — a CI gate:',
|
|
27
28
|
'the process exits non-zero when any error is found. `<module>` is an ESM module',
|
|
@@ -30,6 +31,7 @@ export const VALIDATE_HELP = [
|
|
|
30
31
|
'',
|
|
31
32
|
'Options:',
|
|
32
33
|
' --services <module> ESM module exporting `createServices(): { shared }` (required).',
|
|
34
|
+
...importHelpLines(22),
|
|
33
35
|
' <workspace> Workspace root (path or file URI) to validate (required).',
|
|
34
36
|
' --strict Also fail (non-zero exit) on warnings, not only errors.',
|
|
35
37
|
' --json Emit the raw JSON result instead of the human report.',
|
|
@@ -40,9 +42,10 @@ export const VALIDATE_HELP = [
|
|
|
40
42
|
...logHelpLines(22)
|
|
41
43
|
];
|
|
42
44
|
export function parseValidateArgs(args, onError = exitWithUsage) {
|
|
43
|
-
const { servicesModule, workspace, options } = parseHarnessArgs(args, 'validate', VALUE_FLAGS, BOOL_FLAGS, { onError });
|
|
45
|
+
const { servicesModule, imports, workspace, options } = parseHarnessArgs(args, 'validate', VALUE_FLAGS, BOOL_FLAGS, { onError });
|
|
44
46
|
return {
|
|
45
47
|
servicesModule,
|
|
48
|
+
imports,
|
|
46
49
|
workspace,
|
|
47
50
|
strict: options['--strict'] === 'true',
|
|
48
51
|
json: options['--json'] === 'true',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-args.js","sourceRoot":"","sources":["../../src/commands/validate-args.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EACJ,aAAa,EACb,aAAa,EACb,eAAe,EACf,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,SAAS,
|
|
1
|
+
{"version":3,"file":"validate-args.js","sourceRoot":"","sources":["../../src/commands/validate-args.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EACJ,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,eAAe,EACf,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,SAAS,EAEX,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,WAAW,EAA+B,MAAM,eAAe,CAAC;AAEzE,MAAM,WAAW,GAAG,CAAC,aAAa,EAAE,GAAG,eAAe,CAAU,CAAC;AACjE,MAAM,UAAU,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAU,CAAC;AAEnD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,cAAc,GAAsB,CAAC,GAAG,mBAAmB,EAAE,GAAG,WAAW,EAAE,GAAG,UAAU,CAAC,CAAC;AAEzG,oFAAoF;AACpF,MAAM,CAAC,MAAM,oBAAoB,GAAsB,CAAC,GAAG,mBAAmB,EAAE,GAAG,WAAW,CAAC,CAAC;AAEhG,4EAA4E;AAC5E,MAAM,CAAC,MAAM,aAAa,GAAsB;IAC7C,kMAAkM;IAClM,EAAE;IACF,iFAAiF;IACjF,iFAAiF;IACjF,mFAAmF;IACnF,4CAA4C;IAC5C,EAAE;IACF,UAAU;IACV,yFAAyF;IACzF,GAAG,eAAe,CAAC,EAAE,CAAC;IACtB,mFAAmF;IACnF,iFAAiF;IACjF,+EAA+E;IAC/E,mFAAmF;IACnF,mFAAmF;IACnF,mFAAmF;IACnF,gFAAgF;IAChF,GAAG,YAAY,CAAC,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,UAAU,iBAAiB,CAAC,IAAc,EAAE,OAAO,GAAe,aAAa;IAClF,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACjI,OAAO;QACJ,cAAc;QACd,OAAO;QACP,SAAS;QACT,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,MAAM;QACtC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,KAAK,MAAM;QAClC,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC;QAC/B,GAAG,UAAU,CAAC,OAAO,CAAC;KACxB,CAAC;AACL,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,IAAc;IAC9C,IAAI,aAAa,CAAC,IAAI,EAAE,oBAAoB,CAAC,EAAE,CAAC;QAC7C,SAAS,CAAC,aAAa,CAAC,CAAC;QACzB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC5B,CAAC;IACD,OAAO,WAAW,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC;AAC/C,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydranium/cli",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.213",
|
|
4
4
|
"description": "Build-time codegen tools + data-server subcommands for the hydranium framework.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -69,23 +69,23 @@
|
|
|
69
69
|
"ts-morph": "^25.0.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@hydranium/core": "1.0.0-next.
|
|
73
|
-
"@hydranium/data-server": "1.0.0-next.
|
|
74
|
-
"@hydranium/langium": "1.0.0-next.
|
|
75
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
72
|
+
"@hydranium/core": "1.0.0-next.213",
|
|
73
|
+
"@hydranium/data-server": "1.0.0-next.213",
|
|
74
|
+
"@hydranium/langium": "1.0.0-next.213",
|
|
75
|
+
"@hydranium/protocol": "1.0.0-next.213",
|
|
76
76
|
"rimraf": "^5.0.0",
|
|
77
77
|
"typescript": "^7.0.2",
|
|
78
|
-
"vscode-jsonrpc": "9.0.
|
|
79
|
-
"vscode-languageserver": "
|
|
78
|
+
"vscode-jsonrpc": "9.0.0",
|
|
79
|
+
"vscode-languageserver": "10.0.1"
|
|
80
80
|
},
|
|
81
81
|
"peerDependencies": {
|
|
82
|
-
"@hydranium/core": "1.0.0-next.
|
|
83
|
-
"@hydranium/data-server": "1.0.0-next.
|
|
84
|
-
"@hydranium/langium": "1.0.0-next.
|
|
85
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
82
|
+
"@hydranium/core": "1.0.0-next.213",
|
|
83
|
+
"@hydranium/data-server": "1.0.0-next.213",
|
|
84
|
+
"@hydranium/langium": "1.0.0-next.213",
|
|
85
|
+
"@hydranium/protocol": "1.0.0-next.213",
|
|
86
86
|
"@memlab/core": "^2.0.3",
|
|
87
87
|
"@memlab/heap-analysis": "^2.0.3",
|
|
88
|
-
"vscode-jsonrpc": "9.0.
|
|
88
|
+
"vscode-jsonrpc": "^9.0.0"
|
|
89
89
|
},
|
|
90
90
|
"peerDependenciesMeta": {
|
|
91
91
|
"@memlab/core": {
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
},
|
|
101
101
|
"//exports": "Two keys, and the SHORT list is the point: `files` ships the whole compiled tree, so without a map every `lib/commands/*.js`, every internal helper and `lib/testing/echo-server.js` is deep-importable the moment this publishes, and semver then applies to all of it. `.` is the library barrel (the data-server subcommands an adopter calls as functions). `./lib/cli.js` is the BINARY ARTEFACT, and it is declared because a consumer that spawns the CLI resolves it BY SPECIFIER — `createRequire(import.meta.url).resolve('@hydranium/cli/lib/cli.js')` — to get the path npx would run; `bin` gives a shim on PATH, not a path, so dropping this key breaks that resolution and the failure surfaces as \"hydranium-cli is not built\", naming the wrong cause. It is declared in the `/lib/` spelling ONLY: that spelling already resolves under both of this repo's module resolvers, so the twin rule (which exists so a BARE subpath is reachable from node10) has nothing to add here and a bare twin would only be a second name for one artefact. `heap-analysis/` needs no key even though `files` ships it — `analyze-heap` reaches the analyzer through a `new URL(..., import.meta.url)` file URL, which `exports` does not gate, and the package's own tests reach it relatively. `./testing` is deliberately absent: `echo-server` is a spawnable stdio fixture, not a reusable export.",
|
|
102
102
|
"//memlab": "@memlab/core and @memlab/heap-analysis are OPTIONAL PEER dependencies, not optionalDependencies, and the distinction is the whole point: npm installs optionalDependencies BY DEFAULT, both memlab packages pin puppeteer exactly, and puppeteer's postinstall downloads a Chrome binary — so every adopter of this CLI paid ~86 MB and a browser download for the one subcommand almost none of them run, and the download can fail and be walked past as a warning. An optional peer is not auto-installed, so `analyze-heap` documents `npm install @memlab/core @memlab/heap-analysis` as the opt-in and every other subcommand is unaffected. Taken pre-publish deliberately: after publish the install shape is what adopters already have.",
|
|
103
|
-
"//peerDependencies": "`vscode-jsonrpc`
|
|
104
|
-
"//prepack": "The publish guard,
|
|
103
|
+
"//peerDependencies": "`vscode-jsonrpc` takes a caret, so the framework shares the transport the LSP connection resolves, which an `init` scaffold pins: each `vscode-languageserver-protocol` release pins its transport exactly, so an exact peer would name a copy a fresh install rarely holds. The caret excludes 8.x, so a tree whose top holds GLSP's copy fails the peer with a named ERESOLVE warning, an error under `--strict-peer-deps`, rather than resolving silently. No peer reaches a NESTED copy, such as GLSP's or one the LSP packages nest; docs/adopting/requirements.md has the chain. The wire compares message types and `ResponseError`s by copy identity, so the framework sends typed messages by method name and recognises errors with `isResponseError` rather than `instanceof`.",
|
|
104
|
+
"//prepack": "The publish guard, a `prepack` rather than a `prepare`: `prepack` runs only when a tarball is made (`npm pack`, `npm publish`) and never on install, so it cannot break the install it has no business touching. What it defends against: `files` lists `lib`, `lib` is gitignored, and a `files` entry matching nothing is skipped SILENTLY — so `npm publish` from an unbuilt tree emits a tarball of `src` and nothing else, with no error.",
|
|
105
105
|
"//sideEffects": "The listed modules RUN on load rather than exporting anything: `cli.js` is the `bin`, each `*-driver.js` is spawned as its own process and calls `main(process.argv)` at top level, and `echo-server.js` starts a server. Their whole purpose is the effect, so a bundler must never treat them as prunable. Everything else here is library code with no module-level state beyond `new URL(..., import.meta.url)` constants."
|
|
106
106
|
}
|
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
import { runAstGroundTruth, type AstGroundTruthCommandOptions } from './ast-ground-truth.js';
|
|
11
11
|
import {
|
|
12
12
|
exitWithUsage,
|
|
13
|
+
HARNESS_VALUE_FLAGS,
|
|
13
14
|
helpRequested,
|
|
15
|
+
importHelpLines,
|
|
14
16
|
LOG_VALUE_FLAGS,
|
|
15
17
|
logHelpLines,
|
|
16
18
|
logOptions,
|
|
17
19
|
parseHarnessArgs,
|
|
18
20
|
printHelp,
|
|
19
|
-
SERVICES_FLAG,
|
|
20
21
|
type UsageError
|
|
21
22
|
} from './harness-args.js';
|
|
22
23
|
import { OUT_FILE_FLAG } from './headless-harness.js';
|
|
@@ -25,17 +26,18 @@ const VALUE_FLAGS = [OUT_FILE_FLAG, ...LOG_VALUE_FLAGS] as const;
|
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* 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.
|
|
29
|
-
*
|
|
29
|
+
* handed so the list cannot claim a flag the parser would reject.
|
|
30
|
+
* {@link HARNESS_VALUE_FLAGS} are consumed by the shared harness parser itself,
|
|
31
|
+
* so they are in neither set.
|
|
30
32
|
*/
|
|
31
|
-
export const AST_GROUND_TRUTH_FLAGS: readonly string[] = [
|
|
33
|
+
export const AST_GROUND_TRUTH_FLAGS: readonly string[] = [...HARNESS_VALUE_FLAGS, ...VALUE_FLAGS];
|
|
32
34
|
|
|
33
35
|
/** The subset that takes a value, so `--help` in a value position reads as data. */
|
|
34
36
|
export const AST_GROUND_TRUTH_VALUE_FLAGS: readonly string[] = AST_GROUND_TRUTH_FLAGS;
|
|
35
37
|
|
|
36
38
|
/** The `--help` text, as data, held to {@link AST_GROUND_TRUTH_FLAGS} by a test. */
|
|
37
39
|
export const AST_GROUND_TRUTH_HELP: readonly string[] = [
|
|
38
|
-
'Usage: hydranium-cli ast-ground-truth --services <module> <workspace> [--out-file <file>] [--log-level <lvl>] [--log-file <file>] [--log-file-level <lvl>]',
|
|
40
|
+
'Usage: hydranium-cli ast-ground-truth --services <module> [--import <specifier>]... <workspace> [--out-file <file>] [--log-level <lvl>] [--log-file <file>] [--log-file-level <lvl>]',
|
|
39
41
|
'',
|
|
40
42
|
"Tally a workspace's live-model AST nodes by `$type` — the ground truth that",
|
|
41
43
|
'`analyze-heap --validate <gt.json>` checks a heap snapshot against. `<module>`',
|
|
@@ -44,14 +46,15 @@ export const AST_GROUND_TRUTH_HELP: readonly string[] = [
|
|
|
44
46
|
'',
|
|
45
47
|
'Options:',
|
|
46
48
|
' --services <module> ESM module exporting `createServices(): { shared }` (required).',
|
|
49
|
+
...importHelpLines(22),
|
|
47
50
|
' <workspace> Workspace root (path or file URI) to build (required).',
|
|
48
51
|
' --out-file <file> Write the JSON to this file instead of stdout.',
|
|
49
52
|
...logHelpLines(22)
|
|
50
53
|
];
|
|
51
54
|
|
|
52
55
|
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], ...logOptions(options) };
|
|
56
|
+
const { servicesModule, imports, workspace, options } = parseHarnessArgs(args, 'ast-ground-truth', VALUE_FLAGS, [], { onError });
|
|
57
|
+
return { servicesModule, imports, workspace, outFile: options[OUT_FILE_FLAG], ...logOptions(options) };
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
export function runAstGroundTruthCommand(args: string[]): Promise<void> {
|
|
@@ -56,9 +56,18 @@ export function exitWithUsage(message: string): never {
|
|
|
56
56
|
process.exit(2);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
/**
|
|
59
|
+
/** Names the head's services module; every harness subcommand requires it. */
|
|
60
60
|
export const SERVICES_FLAG = '--services';
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Registers a loader in the driver child that imports the head. The child is a
|
|
64
|
+
* fresh `node`, so a loader registered on the CLI's own process never reaches it.
|
|
65
|
+
*/
|
|
66
|
+
export const IMPORT_FLAG = '--import';
|
|
67
|
+
|
|
68
|
+
/** The value flags {@link parseHarnessArgs} consumes itself, before any the subcommand declares. */
|
|
69
|
+
export const HARNESS_VALUE_FLAGS = [SERVICES_FLAG, IMPORT_FLAG] as const;
|
|
70
|
+
|
|
62
71
|
/**
|
|
63
72
|
* The one spelling of the log threshold, shared by both subcommand families.
|
|
64
73
|
*
|
|
@@ -88,15 +97,22 @@ export const SERVER_SPAWN_VALUE_FLAGS: readonly string[] = ['--server', '--cwd',
|
|
|
88
97
|
* next line, at the column.
|
|
89
98
|
*/
|
|
90
99
|
export function logHelpLines(padTo: number, head = 'the head this boots'): string[] {
|
|
91
|
-
const line = (flag: string, text: string): string[] =>
|
|
92
|
-
flag.length < padTo - 1 ? [` ${flag.padEnd(padTo)}${text}`] : [` ${flag}`, `${' '.repeat(padTo + 2)}${text}`];
|
|
93
100
|
return [
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
helpLine(`${LOG_LEVEL_FLAG} <lvl>`, `Log threshold for ${head} (off|error|warn|info|debug|trace).`, padTo),
|
|
102
|
+
helpLine(`${LOG_FILE_FLAG} <file>`, `Also write ${head}'s log to <file>; {workspace} expands to the workspace.`, padTo),
|
|
103
|
+
helpLine(`${LOG_FILE_LEVEL_FLAG} <lvl>`, `Threshold for that file, which the log-level setting does not change.`, padTo)
|
|
97
104
|
].flat();
|
|
98
105
|
}
|
|
99
106
|
|
|
107
|
+
/** The `--help` lines every `--services` subcommand describes {@link IMPORT_FLAG} with, aligned as {@link logHelpLines}. */
|
|
108
|
+
export function importHelpLines(padTo: number): string[] {
|
|
109
|
+
return helpLine(`${IMPORT_FLAG} <specifier>`, 'Loader to register before importing <module>, e.g. tsx (repeatable).', padTo);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function helpLine(flag: string, text: string, padTo: number): string[] {
|
|
113
|
+
return flag.length < padTo - 1 ? [` ${flag.padEnd(padTo)}${text}`] : [` ${flag}`, `${' '.repeat(padTo + 2)}${text}`];
|
|
114
|
+
}
|
|
115
|
+
|
|
100
116
|
/**
|
|
101
117
|
* Narrow the raw {@link LOG_VALUE_FLAGS} values a parser collected, leaving
|
|
102
118
|
* absent flags absent.
|
|
@@ -158,6 +174,8 @@ export function printHelp(lines: readonly string[]): void {
|
|
|
158
174
|
/** What {@link parseHarnessArgs} recovered from an argv. */
|
|
159
175
|
export interface ParsedHarnessArgs {
|
|
160
176
|
servicesModule: string;
|
|
177
|
+
/** The {@link IMPORT_FLAG} values in the order given, absent when there were none. */
|
|
178
|
+
imports?: string[];
|
|
161
179
|
/**
|
|
162
180
|
* An absolute directory path, resolved by {@link resolveWorkspaceArgument} and
|
|
163
181
|
* checked to exist. `''` when the subcommand declared `requireWorkspace: false`.
|
|
@@ -187,12 +205,12 @@ export interface HarnessArgsConfig {
|
|
|
187
205
|
|
|
188
206
|
/**
|
|
189
207
|
* Shared parser for the headless-harness subcommands. Consumes the required
|
|
190
|
-
* `--services <module>` value flag
|
|
191
|
-
* the `valueFlags` (each takes a value)
|
|
192
|
-
* as `'true'`) the subcommand
|
|
193
|
-
* `--services`/`<workspace>` are usage
|
|
194
|
-
*
|
|
195
|
-
* be left to the run.
|
|
208
|
+
* `--services <module>` value flag, the repeatable `--import <specifier>` and a
|
|
209
|
+
* single positional `<workspace>`, plus the `valueFlags` (each takes a value)
|
|
210
|
+
* and `boolFlags` (presence-only, recorded as `'true'`) the subcommand
|
|
211
|
+
* recognises. Unknown flags or a missing `--services`/`<workspace>` are usage
|
|
212
|
+
* errors, and so is a `<workspace>` that reaches no directory — see
|
|
213
|
+
* {@link resolveWorkspaceArgument} for why that cannot be left to the run.
|
|
196
214
|
*/
|
|
197
215
|
export function parseHarnessArgs(
|
|
198
216
|
args: string[],
|
|
@@ -209,6 +227,7 @@ export function parseHarnessArgs(
|
|
|
209
227
|
const options: Record<string, string | undefined> = {};
|
|
210
228
|
const values: Record<string, string[]> = {};
|
|
211
229
|
let servicesModule: string | undefined;
|
|
230
|
+
let imports: string[] | undefined;
|
|
212
231
|
let workspace: string | undefined;
|
|
213
232
|
for (let index = 0; index < args.length; index += 1) {
|
|
214
233
|
const flag = args[index];
|
|
@@ -220,8 +239,10 @@ export function parseHarnessArgs(
|
|
|
220
239
|
index += 1;
|
|
221
240
|
return value;
|
|
222
241
|
};
|
|
223
|
-
if (flag ===
|
|
242
|
+
if (flag === SERVICES_FLAG) {
|
|
224
243
|
servicesModule = next();
|
|
244
|
+
} else if (flag === IMPORT_FLAG) {
|
|
245
|
+
(imports ??= []).push(next());
|
|
225
246
|
} else if (boolSet.has(flag)) {
|
|
226
247
|
options[flag] = 'true';
|
|
227
248
|
} else if (repeatableSet.has(flag)) {
|
|
@@ -237,7 +258,8 @@ export function parseHarnessArgs(
|
|
|
237
258
|
}
|
|
238
259
|
}
|
|
239
260
|
return {
|
|
240
|
-
servicesModule: assertRequired(servicesModule,
|
|
261
|
+
servicesModule: assertRequired(servicesModule, SERVICES_FLAG, commandName, onError),
|
|
262
|
+
imports,
|
|
241
263
|
workspace: requireWorkspace
|
|
242
264
|
? resolveWorkspaceArgument(assertRequired(workspace, '<workspace>', commandName, onError), commandName, onError)
|
|
243
265
|
: (workspace ?? ''),
|
|
@@ -15,7 +15,7 @@ import * as path from 'node:path';
|
|
|
15
15
|
import { pathToFileURL } from 'node:url';
|
|
16
16
|
import { driverHeapArgs, type HeapReading } from '../driver-heap.js';
|
|
17
17
|
import { logEnv, type LogOptions } from '../log-level.js';
|
|
18
|
-
import { SERVICES_FLAG } from './harness-args.js';
|
|
18
|
+
import { IMPORT_FLAG, SERVICES_FLAG } from './harness-args.js';
|
|
19
19
|
|
|
20
20
|
/** The flag every report-producing subcommand names its destination file with. */
|
|
21
21
|
export const OUT_FILE_FLAG = '--out-file';
|
|
@@ -43,8 +43,9 @@ export interface HeadlessContext {
|
|
|
43
43
|
* its `createServices` thunk AND the `@hydranium/core/node` harness, both
|
|
44
44
|
* resolved from the HEAD's location rather than the CLI's.
|
|
45
45
|
*
|
|
46
|
-
* The module is an ESM file
|
|
47
|
-
*
|
|
46
|
+
* The module is an ESM file exporting a ZERO-ARG `createServices(): { shared }`:
|
|
47
|
+
* a compiled `lib/*.js`, or the TypeScript source when the child was started
|
|
48
|
+
* with a loader through {@link IMPORT_FLAG}. `hydranium-cli` is language-agnostic
|
|
48
49
|
* and cannot statically import a head's `create<Lang>Services`, so the dynamic
|
|
49
50
|
* import is the seam that keeps the binary head-neutral. Resolving the harness
|
|
50
51
|
* from the head's graph (via `createRequire` rooted at the head module) rather
|
|
@@ -65,12 +66,17 @@ export async function loadHeadlessContext(servicesModule: string): Promise<Headl
|
|
|
65
66
|
if (stats === undefined || !stats.isFile()) {
|
|
66
67
|
throw new Error(
|
|
67
68
|
`${SERVICES_FLAG} must name an existing ESM file; '${servicesModule}' resolved to ${modulePath}, ` +
|
|
68
|
-
`which is not one. Pass the head's
|
|
69
|
-
'not
|
|
69
|
+
`which is not one. Pass the path of the head's entry, e.g. \`${SERVICES_FLAG} ./lib/services.js\` — ` +
|
|
70
|
+
'not a package name.'
|
|
70
71
|
);
|
|
71
72
|
}
|
|
72
73
|
const moduleUrl = pathToFileURL(modulePath).href;
|
|
73
|
-
|
|
74
|
+
let imported: Record<string, unknown>;
|
|
75
|
+
try {
|
|
76
|
+
imported = (await import(moduleUrl)) as Record<string, unknown>;
|
|
77
|
+
} catch (err: unknown) {
|
|
78
|
+
throw withLoaderHint(err, modulePath);
|
|
79
|
+
}
|
|
74
80
|
const createServices = imported.createServices;
|
|
75
81
|
if (typeof createServices !== 'function') {
|
|
76
82
|
throw new Error(
|
|
@@ -85,6 +91,32 @@ export async function loadHeadlessContext(servicesModule: string): Promise<Headl
|
|
|
85
91
|
return { createServices: createServices as ServicesFactory, coreNode };
|
|
86
92
|
}
|
|
87
93
|
|
|
94
|
+
/** The codes Node fails a TypeScript entry with when no loader handled it, depending on its version. */
|
|
95
|
+
const UNLOADED_TYPESCRIPT_CODES: ReadonlySet<string> = new Set([
|
|
96
|
+
'ERR_UNKNOWN_FILE_EXTENSION',
|
|
97
|
+
'ERR_MODULE_NOT_FOUND',
|
|
98
|
+
'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX'
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Append a loader hint to a TypeScript entry's import failure, unless the child
|
|
103
|
+
* was started with {@link IMPORT_FLAG}, where the hint would only repeat the
|
|
104
|
+
* caller's own command. Appended rather than substituted: a loader set through
|
|
105
|
+
* `NODE_OPTIONS` does not show in `execArgv`, and under it the failure can be a
|
|
106
|
+
* missing module that only Node's own message names.
|
|
107
|
+
*/
|
|
108
|
+
function withLoaderHint(err: unknown, modulePath: string): unknown {
|
|
109
|
+
if (!(err instanceof Error) || !/\.[cm]?ts$/.test(modulePath) || !('code' in err) || typeof err.code !== 'string') {
|
|
110
|
+
return err;
|
|
111
|
+
}
|
|
112
|
+
if (!UNLOADED_TYPESCRIPT_CODES.has(err.code) || process.execArgv.some(arg => arg.startsWith(IMPORT_FLAG))) {
|
|
113
|
+
return err;
|
|
114
|
+
}
|
|
115
|
+
return new Error(`${err.message}\nA TypeScript head needs a loader registered with \`${IMPORT_FLAG}\`, e.g. \`${IMPORT_FLAG} tsx\`.`, {
|
|
116
|
+
cause: err
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
88
120
|
/**
|
|
89
121
|
* Child-side: deliver a finished report — to `outFile` when one was named, else
|
|
90
122
|
* to stdout.
|
|
@@ -137,6 +169,8 @@ export type SpawnDriverChild = (execArgs: string[], env?: Record<string, string>
|
|
|
137
169
|
* variable still wins where a caller set one.
|
|
138
170
|
*/
|
|
139
171
|
export interface DriverSpawnOptions extends LogOptions {
|
|
172
|
+
/** Specifiers the driver child registers with Node's `--import`, in order, before it imports the head. */
|
|
173
|
+
readonly imports?: readonly string[];
|
|
140
174
|
/** Test-only: capture the node argv and env instead of spawning the real child. */
|
|
141
175
|
readonly __spawnForTest?: SpawnDriverChild;
|
|
142
176
|
/**
|
|
@@ -171,8 +205,21 @@ export interface DriverSpawnOptions extends LogOptions {
|
|
|
171
205
|
*/
|
|
172
206
|
export async function runDriverChild(execArgs: string[], options: DriverSpawnOptions): Promise<void> {
|
|
173
207
|
const spawnChild = options.__spawnForTest ?? spawnNodeChild;
|
|
174
|
-
const
|
|
208
|
+
const importArgs = (options.imports ?? []).map(specifier => `${IMPORT_FLAG}=${loaderSpecifier(specifier)}`);
|
|
209
|
+
const code = await spawnChild([...driverHeapArgs(options.__heapReadingForTest), ...importArgs, ...execArgs], logEnv(options));
|
|
175
210
|
if (code) {
|
|
176
211
|
process.exitCode = code;
|
|
177
212
|
}
|
|
178
213
|
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* A path, absolute or `./`/`../` relative with either separator, becomes a file
|
|
217
|
+
* URL resolved against the caller's directory. Node reads `--import` as a URL or
|
|
218
|
+
* package specifier, so it would take a Windows `C:\…` for the URL scheme `c:`
|
|
219
|
+
* and a `.\…` for a package name. Any other specifier passes through as Node
|
|
220
|
+
* reads it.
|
|
221
|
+
*/
|
|
222
|
+
function loaderSpecifier(specifier: string): string {
|
|
223
|
+
const isPath = path.isAbsolute(specifier) || /^\.\.?[\\/]/.test(specifier);
|
|
224
|
+
return isPath ? pathToFileURL(path.resolve(specifier)).href : specifier;
|
|
225
|
+
}
|
|
@@ -218,7 +218,12 @@ const HEAD_FRAMEWORK_DEPENDENCIES: Record<InitHead, readonly string[]> = {
|
|
|
218
218
|
const HEAD_THIRD_PARTY_DEPENDENCIES: Record<InitHead, Readonly<Record<string, string>>> = {
|
|
219
219
|
lsp: {
|
|
220
220
|
langium: '4.3.1',
|
|
221
|
-
|
|
221
|
+
// Exact, as are the protocol and transport it pins, so Langium and the
|
|
222
|
+
// framework share their copies; across copies, an error a handler throws
|
|
223
|
+
// or returns loses its code, and a later patch can pin another protocol.
|
|
224
|
+
'vscode-languageserver': '10.0.1',
|
|
225
|
+
'vscode-jsonrpc': '9.0.0',
|
|
226
|
+
'vscode-languageserver-protocol': '3.18.1'
|
|
222
227
|
},
|
|
223
228
|
data: {},
|
|
224
229
|
glsp: {
|
|
@@ -231,6 +236,24 @@ const HEAD_THIRD_PARTY_DEPENDENCIES: Record<InitHead, Readonly<Record<string, st
|
|
|
231
236
|
}
|
|
232
237
|
};
|
|
233
238
|
|
|
239
|
+
/** Whether an npm version is 11.6.0 or later, the first that installs `vitest` 4.1's peer set; `11.6.0-rc.1` is earlier. */
|
|
240
|
+
function installsVitest41(npmVersion: string | undefined): boolean {
|
|
241
|
+
const [release, prerelease] = (npmVersion ?? '0.0.0').split('-', 2);
|
|
242
|
+
const [major, minor, patch] = release.split('.').map(Number);
|
|
243
|
+
return major > 11 || (major === 11 && (minor > 6 || (minor === 6 && (patch > 0 || prerelease === undefined))));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Whether the scaffold holds `vitest` below 4.1, whose peer set crashes npm
|
|
248
|
+
* before 11.6 (every npm a Node 22 release bundles) on the first install. Only a
|
|
249
|
+
* workspace root declaring a newer npm lifts it, and only when `init` runs under
|
|
250
|
+
* an npm known to be newer: npm does not enforce `packageManager`.
|
|
251
|
+
*/
|
|
252
|
+
function pinsVitest(composition: InitComposition): boolean {
|
|
253
|
+
const workspace = composition.packaging.workspace;
|
|
254
|
+
return !(installsVitest41(workspace?.packageManagerNpm) && installsVitest41(workspace?.npmVersion));
|
|
255
|
+
}
|
|
256
|
+
|
|
234
257
|
/** The dependency block for a head set, merged and sorted as npm writes it. */
|
|
235
258
|
function dependencyBlock(composition: InitComposition, indent: string): string {
|
|
236
259
|
const merged: Record<string, string> = {};
|
|
@@ -315,7 +338,7 @@ __DEPENDENCIES__
|
|
|
315
338
|
"langium-cli": "4.3.0",
|
|
316
339
|
"rimraf": "^5.0.0",
|
|
317
340
|
"typescript": "^7.0.2",
|
|
318
|
-
"vitest": "
|
|
341
|
+
"vitest": "__VITEST__"
|
|
319
342
|
},
|
|
320
343
|
"engines": {
|
|
321
344
|
"node": ">=22.13"
|
|
@@ -385,6 +408,7 @@ function packageJson(composition: InitComposition): string {
|
|
|
385
408
|
.replace('__KEYWORDS__', keywords)
|
|
386
409
|
.replace('__BIN__', binBlock(composition))
|
|
387
410
|
.replace('__LINT__', lint)
|
|
411
|
+
.replace('__VITEST__', pinsVitest(composition) ? '~4.0.18' : '^4.0.0')
|
|
388
412
|
.replace('__PRIVATE__', composition.packaging.private ? ' "private": true,\n' : '')
|
|
389
413
|
.replace('__NPM_RUN__', npmRun(composition))
|
|
390
414
|
.replace('__FRAMEWORK_PIN__', frameworkPin(composition))
|
|
@@ -1565,6 +1589,24 @@ function readme(composition: InitComposition): string {
|
|
|
1565
1589
|
'atomic set and depends on a single physical copy, so a floating range can',
|
|
1566
1590
|
'silently resolve a second one.',
|
|
1567
1591
|
'',
|
|
1592
|
+
'`vscode-languageserver` is pinned to `10.0.1`, and',
|
|
1593
|
+
'`vscode-languageserver-protocol` and `vscode-jsonrpc` to the `3.18.1` and',
|
|
1594
|
+
'`9.0.0` it pins, so Langium, the LSP connection and the framework share one',
|
|
1595
|
+
'copy of each. With two, an error a Langium handler returns, such as a request',
|
|
1596
|
+
'for a document the server does not have, reaches the client as a result, and a',
|
|
1597
|
+
'framework error your own LSP handler throws loses its code. The Langium 4.4',
|
|
1598
|
+
'upgrade',
|
|
1599
|
+
'([eclipse-emfcloud/hydranium#142](https://github.com/eclipse-emfcloud/hydranium/issues/142))',
|
|
1600
|
+
'replaces these pins.',
|
|
1601
|
+
'',
|
|
1602
|
+
...(pinsVitest(composition)
|
|
1603
|
+
? [
|
|
1604
|
+
'`vitest` is held below 4.1, whose peer set crashes the npm every Node 22',
|
|
1605
|
+
'release bundles. Raise it once every npm that installs this project is',
|
|
1606
|
+
'11.6 or later.',
|
|
1607
|
+
''
|
|
1608
|
+
]
|
|
1609
|
+
: []),
|
|
1568
1610
|
'## If your repo gates license headers',
|
|
1569
1611
|
'',
|
|
1570
1612
|
'The emitted `.ts` files carry no copyright header — the scaffold cannot know',
|
|
@@ -192,6 +192,8 @@ export interface WorkspaceDetection {
|
|
|
192
192
|
readonly eslintConfig?: string;
|
|
193
193
|
/** Filename of the root Oxlint config, when present. */
|
|
194
194
|
readonly oxlintConfig?: string;
|
|
195
|
+
/** The npm version the root's `packageManager` field declares, when it names npm. */
|
|
196
|
+
readonly packageManagerNpm?: string;
|
|
195
197
|
}
|
|
196
198
|
|
|
197
199
|
/**
|
|
@@ -407,6 +409,17 @@ export function findOxlintConfig(rootDir: string, probe: WorkspaceProbe): string
|
|
|
407
409
|
return probe.listFiles(rootDir).find(name => /^(oxlint\.config\.[cm]?[jt]s|\.oxlintrc\.jsonc?)$/.test(name));
|
|
408
410
|
}
|
|
409
411
|
|
|
412
|
+
/** The npm version the root's `packageManager` field declares (`npm@11.15.0` gives `11.15.0`), when it names npm. */
|
|
413
|
+
export function findPackageManagerNpm(rootDir: string, probe: WorkspaceProbe): string | undefined {
|
|
414
|
+
const manifest = probe.readJson(path.join(rootDir, 'package.json'));
|
|
415
|
+
return parseNpmVersion(isJsonObject(manifest) && typeof manifest.packageManager === 'string' ? manifest.packageManager : undefined);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** The npm version, prerelease included, of a `packageManager` field (`npm@…`) or a user agent (`npm/…`); undefined for another tool. */
|
|
419
|
+
export function parseNpmVersion(spec: string | undefined): string | undefined {
|
|
420
|
+
return /^npm[@/](\d+\.\d+\.\d+(?:-[\w.]+)?)/.exec(spec ?? '')?.[1];
|
|
421
|
+
}
|
|
422
|
+
|
|
410
423
|
/** POSIX-style relative path, which is what both `extends` and `--prefix` want on every platform. */
|
|
411
424
|
function posixRelative(from: string, to: string): string {
|
|
412
425
|
return path.relative(from, to).split(path.sep).join('/');
|
|
@@ -447,6 +460,7 @@ export function detectWorkspace(targetDir: string, probe: WorkspaceProbe): Works
|
|
|
447
460
|
baseCompilerOptions: base?.compilerOptions,
|
|
448
461
|
printWidth: findFormatterPrintWidth(rootDir, probe),
|
|
449
462
|
eslintConfig: findEslintConfig(rootDir, probe),
|
|
450
|
-
oxlintConfig: findOxlintConfig(rootDir, probe)
|
|
463
|
+
oxlintConfig: findOxlintConfig(rootDir, probe),
|
|
464
|
+
packageManagerNpm: findPackageManagerNpm(rootDir, probe)
|
|
451
465
|
};
|
|
452
466
|
}
|