@hydranium/cli 1.0.0-next.4 → 1.0.0-next.42
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/lib/commands/generate-ast-builder.d.ts +49 -0
- package/lib/commands/generate-ast-builder.d.ts.map +1 -0
- package/lib/commands/generate-ast-builder.js +200 -0
- package/lib/commands/generate-ast-builder.js.map +1 -0
- package/lib/commands/generate-transfer-model-args.d.ts.map +1 -1
- package/lib/commands/generate-transfer-model-args.js +11 -1
- package/lib/commands/generate-transfer-model-args.js.map +1 -1
- package/lib/commands/generate-transfer-model-config.d.ts.map +1 -1
- package/lib/commands/generate-transfer-model-config.js +3 -1
- package/lib/commands/generate-transfer-model-config.js.map +1 -1
- package/lib/commands/generate-transfer-model.d.ts +9 -0
- package/lib/commands/generate-transfer-model.d.ts.map +1 -1
- package/lib/commands/generate-transfer-model.js +16 -3
- package/lib/commands/generate-transfer-model.js.map +1 -1
- package/lib/commands/init-templates.d.ts.map +1 -1
- package/lib/commands/init-templates.js +44 -10
- package/lib/commands/init-templates.js.map +1 -1
- package/lib/commands/watch.d.ts.map +1 -1
- package/lib/commands/watch.js +15 -0
- package/lib/commands/watch.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/package.json +11 -10
- package/src/commands/generate-ast-builder.ts +246 -0
- package/src/commands/generate-transfer-model-args.ts +11 -1
- package/src/commands/generate-transfer-model-config.ts +3 -1
- package/src/commands/generate-transfer-model.ts +25 -3
- package/src/commands/init-templates.ts +44 -10
- package/src/commands/watch.ts +21 -1
- package/src/index.ts +1 -0
|
@@ -29,6 +29,7 @@ export const GENERATE_TRANSFER_MODEL_FLAGS: readonly string[] = [
|
|
|
29
29
|
'--ast-file',
|
|
30
30
|
'--augmentation-file',
|
|
31
31
|
'--out-file',
|
|
32
|
+
'--ast-builder-file',
|
|
32
33
|
'--element-type-name',
|
|
33
34
|
'--terminals-name',
|
|
34
35
|
'--terminals-source-name',
|
|
@@ -48,7 +49,8 @@ export const GENERATE_TRANSFER_MODEL_VALUE_FLAGS: readonly string[] = GENERATE_T
|
|
|
48
49
|
export const GENERATE_TRANSFER_MODEL_HELP: readonly string[] = [
|
|
49
50
|
'Usage: hydranium-cli generate-transfer-model [options]',
|
|
50
51
|
'',
|
|
51
|
-
'Generate a serializable transfer-model TypeScript file from a Langium AST
|
|
52
|
+
'Generate a serializable transfer-model TypeScript file from a Langium AST, and',
|
|
53
|
+
'optionally an AST-node builder module bound to the same reflection. The',
|
|
52
54
|
'required inputs (--ast-file / --augmentation-file / --out-file) may instead come',
|
|
53
55
|
'from a --config JSON file, and --ast-file can be auto-discovered from a Langium',
|
|
54
56
|
"config's `out` directory. Precedence, highest first: explicit flags, then the",
|
|
@@ -67,6 +69,11 @@ export const GENERATE_TRANSFER_MODEL_HELP: readonly string[] = [
|
|
|
67
69
|
' directory: it treats that directory as exclusively',
|
|
68
70
|
' its own, so every `langium generate` reports this',
|
|
69
71
|
' file as unexpected and offers to delete it.',
|
|
72
|
+
' --ast-builder-file <path> Also emit an AST-node builder module here: one',
|
|
73
|
+
' makeAstNodeBuilder binding per grammar, plus one',
|
|
74
|
+
' spanning all of them. Omit if nothing constructs',
|
|
75
|
+
' AST nodes. Same restriction as --out-file: keep it',
|
|
76
|
+
" out of langium-cli's own `out` directory.",
|
|
70
77
|
' --element-type-name <name> Base element type name in output. Default: TransferElement.',
|
|
71
78
|
' --terminals-name <name> Terminals const name in output. Default: ModelTerminals.',
|
|
72
79
|
' --terminals-source-name <n> Source-side terminals variable name. Default: <LanguageId>Terminals.',
|
|
@@ -120,6 +127,9 @@ export function parseGenerateOptions(args: string[], onError: UsageError = exitW
|
|
|
120
127
|
case '--out-file':
|
|
121
128
|
flags.outFile = next();
|
|
122
129
|
break;
|
|
130
|
+
case '--ast-builder-file':
|
|
131
|
+
flags.astBuilderFile = next();
|
|
132
|
+
break;
|
|
123
133
|
case '--element-type-name':
|
|
124
134
|
flags.elementTypeName = next();
|
|
125
135
|
break;
|
|
@@ -12,7 +12,7 @@ import * as path from 'node:path';
|
|
|
12
12
|
import type { GenerateTransferModelOptions } from './generate-transfer-model.js';
|
|
13
13
|
|
|
14
14
|
/** Path-valued option keys — resolved relative to a config/langium-config file's directory. */
|
|
15
|
-
const PATH_KEYS = ['astFile', 'augmentationFile', 'outFile'] as const;
|
|
15
|
+
const PATH_KEYS = ['astFile', 'augmentationFile', 'outFile', 'astBuilderFile'] as const;
|
|
16
16
|
|
|
17
17
|
/** Resolve `value` against `baseDir` when it is relative; absolute paths pass through. */
|
|
18
18
|
function resolveAgainst(baseDir: string, value: string): string {
|
|
@@ -43,6 +43,7 @@ export function loadTransferModelConfig(configPath: string): Partial<GenerateTra
|
|
|
43
43
|
'astFile',
|
|
44
44
|
'augmentationFile',
|
|
45
45
|
'outFile',
|
|
46
|
+
'astBuilderFile',
|
|
46
47
|
'elementTypeName',
|
|
47
48
|
'terminalsName',
|
|
48
49
|
'terminalsSourceName',
|
|
@@ -118,6 +119,7 @@ export function mergeTransferModelOptions(...sources: Array<Partial<GenerateTran
|
|
|
118
119
|
astFile: astFile!,
|
|
119
120
|
augmentationFile: augmentationFile!,
|
|
120
121
|
outFile: outFile!,
|
|
122
|
+
astBuilderFile: pick('astBuilderFile'),
|
|
121
123
|
elementTypeName: pick('elementTypeName'),
|
|
122
124
|
terminalsName: pick('terminalsName'),
|
|
123
125
|
terminalsSourceName: pick('terminalsSourceName'),
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import * as fs from 'fs';
|
|
11
11
|
import * as path from 'path';
|
|
12
|
+
import { emitAstBuilder } from './generate-ast-builder.js';
|
|
12
13
|
import {
|
|
13
14
|
type Node,
|
|
14
15
|
type ObjectLiteralExpression,
|
|
@@ -35,6 +36,15 @@ export interface GenerateTransferModelOptions {
|
|
|
35
36
|
augmentationFile: string;
|
|
36
37
|
/** Destination path for the generated transfer model. */
|
|
37
38
|
outFile: string;
|
|
39
|
+
/**
|
|
40
|
+
* Destination for a generated AST-node builder module. Omitted by consumers
|
|
41
|
+
* that construct no AST nodes; the transfer model is emitted either way.
|
|
42
|
+
*
|
|
43
|
+
* Emitted from this command rather than its own because the AST source is
|
|
44
|
+
* already parsed here, and because a consumer that regenerates one artefact
|
|
45
|
+
* and not the other has the two disagreeing about the same grammar.
|
|
46
|
+
*/
|
|
47
|
+
astBuilderFile?: string;
|
|
38
48
|
/** Name used for the base element type in the output. Defaults to `TransferElement`. */
|
|
39
49
|
elementTypeName?: string;
|
|
40
50
|
/** Name used for the terminal-patterns const in the output. Defaults to `ModelTerminals`. */
|
|
@@ -396,10 +406,22 @@ export function generateTransferModel(options: GenerateTransferModelOptions): vo
|
|
|
396
406
|
fs.mkdirSync(path.dirname(options.outFile), { recursive: true });
|
|
397
407
|
if (fs.existsSync(options.outFile) && fs.readFileSync(options.outFile, 'utf-8') === content) {
|
|
398
408
|
console.log('Transfer model is up to date.');
|
|
399
|
-
|
|
409
|
+
} else {
|
|
410
|
+
fs.writeFileSync(options.outFile, content, 'utf-8');
|
|
411
|
+
console.log(`Generated: ${options.outFile}`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (options.astBuilderFile !== undefined) {
|
|
415
|
+
const emitted = emitAstBuilder(astSource, {
|
|
416
|
+
outFile: options.astBuilderFile,
|
|
417
|
+
astFile: options.astFile,
|
|
418
|
+
languageId,
|
|
419
|
+
regenCommand
|
|
420
|
+
});
|
|
421
|
+
if (!emitted) {
|
|
422
|
+
console.warn(`Warning: no '${languageId}AstType' alias in ${options.astFile}; skipping the AST builder.`);
|
|
423
|
+
}
|
|
400
424
|
}
|
|
401
|
-
fs.writeFileSync(options.outFile, content, 'utf-8');
|
|
402
|
-
console.log(`Generated: ${options.outFile}`);
|
|
403
425
|
}
|
|
404
426
|
|
|
405
427
|
// ---------------------------------------------------------------------------
|
|
@@ -310,9 +310,9 @@ __BIN__ },
|
|
|
310
310
|
],
|
|
311
311
|
"scripts": {
|
|
312
312
|
"build": "npm run generate && tsc",
|
|
313
|
-
"clean": "rimraf lib syntaxes src/language-server/generated src/language-server/generated-
|
|
313
|
+
"clean": "rimraf lib syntaxes src/language-server/generated src/language-server/generated-hydranium tsconfig.tsbuildinfo",
|
|
314
314
|
"generate": "npm run langium:generate && npm run generate:transfer-model",
|
|
315
|
-
"generate:transfer-model": "hydranium-cli generate-transfer-model --ast-file src/language-server/generated/ast.ts --augmentation-file src/language-server/ast.ts --out-file src/language-server/generated-
|
|
315
|
+
"generate:transfer-model": "hydranium-cli generate-transfer-model --ast-file src/language-server/generated/ast.ts --augmentation-file src/language-server/ast.ts --out-file src/language-server/generated-hydranium/transfer-model.ts --ast-builder-file src/language-server/generated-hydranium/ast-builder.ts --element-type-name __NAME__Element --terminals-name __NAME__Terminals --regen-command \\"Run: __NPM_RUN__ generate:transfer-model\\"",
|
|
316
316
|
"langium:generate": "langium generate",
|
|
317
317
|
"langium:watch": "langium generate --watch",
|
|
318
318
|
__LINT__ "start": "node lib/main.js --stdio",
|
|
@@ -439,6 +439,11 @@ const TSCONFIG_COMPILER_OPTIONS: ReadonlyArray<readonly [string, JsonValue]> = [
|
|
|
439
439
|
['outDir', 'lib'],
|
|
440
440
|
['strict', true],
|
|
441
441
|
['esModuleInterop', true],
|
|
442
|
+
// A server that renders its own user-facing messages loads a catalogue, and
|
|
443
|
+
// a catalogue is JSON. Without this the import does not resolve at all, and
|
|
444
|
+
// without the `include` glob below a composite project rejects it with
|
|
445
|
+
// TS6307 naming neither cause.
|
|
446
|
+
['resolveJsonModule', true],
|
|
442
447
|
['skipLibCheck', true],
|
|
443
448
|
['declaration', true],
|
|
444
449
|
['experimentalDecorators', true],
|
|
@@ -480,7 +485,7 @@ function tsconfigJson(composition: InitComposition): string {
|
|
|
480
485
|
];
|
|
481
486
|
const extendsLine = workspace?.baseTsconfig === undefined ? '' : ` "extends": "${workspace.baseTsconfig}",\n`;
|
|
482
487
|
const body = options.map(([key, value]) => ` "${key}": ${JSON.stringify(value)}`).join(',\n');
|
|
483
|
-
return `{\n${extendsLine} "compilerOptions": {\n${body}\n },\n "include": ["src"]\n}\n`;
|
|
488
|
+
return `{\n${extendsLine} "compilerOptions": {\n${body}\n },\n "include": ["src", "src/**/*.json"]\n}\n`;
|
|
484
489
|
}
|
|
485
490
|
|
|
486
491
|
// `isolatedModules` is what makes this check agree with the transform that
|
|
@@ -498,7 +503,7 @@ const TSCONFIG_TEST = `{
|
|
|
498
503
|
"isolatedModules": true,
|
|
499
504
|
"types": ["node"]
|
|
500
505
|
},
|
|
501
|
-
"include": ["src", "test"]
|
|
506
|
+
"include": ["src", "test", "src/**/*.json"]
|
|
502
507
|
}
|
|
503
508
|
`;
|
|
504
509
|
|
|
@@ -708,13 +713,35 @@ export type __NAME__Services = LangiumServices &
|
|
|
708
713
|
LspServerAddedServices & {
|
|
709
714
|
shared: __NAME__SharedServices;
|
|
710
715
|
};
|
|
716
|
+
|
|
717
|
+
/** What a host or a test may vary about this composition. */
|
|
718
|
+
export interface __NAME__Options {
|
|
719
|
+
/**
|
|
720
|
+
* Shared modules layered in after the framework's own bindings.
|
|
721
|
+
*
|
|
722
|
+
* The framework constructs most shared services with no options —
|
|
723
|
+
* \`DocumentBuilder: services => new HydraniumDocumentBuilder(services)\` — so
|
|
724
|
+
* rebinding the slot is the only way to boot one configured differently, and
|
|
725
|
+
* a factory that hard-codes its composition leaves a test nowhere to do it.
|
|
726
|
+
* That is what this is for: pass a module binding \`workspace.DocumentBuilder\`
|
|
727
|
+
* to exercise a builder option, or to substitute a subclass.
|
|
728
|
+
*
|
|
729
|
+
* Composed LAST, after \`__NAME__SharedModule\`, so it wins over every other
|
|
730
|
+
* tier including this file's own bindings — which is what makes it usable for
|
|
731
|
+
* a slot the adopter overrides. Production code should not reach for it.
|
|
732
|
+
*/
|
|
733
|
+
readonly extraSharedModules?: ReadonlyArray<Module<__NAME__SharedServices, DeepPartial<__NAME__SharedServices>>>;
|
|
734
|
+
}
|
|
711
735
|
${configurationRoot}
|
|
712
736
|
${sharedModule}
|
|
713
737
|
|
|
714
738
|
${languageModules}
|
|
715
739
|
|
|
716
740
|
/** Compose the Langium DI tree for __NAME__ — returns the shared + language services. */
|
|
717
|
-
export function create__NAME__Services(
|
|
741
|
+
export function create__NAME__Services(
|
|
742
|
+
context: Partial<ServerModuleContext> = EmptyFileSystem,
|
|
743
|
+
options: __NAME__Options = {}
|
|
744
|
+
): {
|
|
718
745
|
shared: __NAME__SharedServices;
|
|
719
746
|
${returnType}
|
|
720
747
|
} {
|
|
@@ -724,7 +751,8 @@ ${returnType}
|
|
|
724
751
|
sharedModules: {
|
|
725
752
|
generated: __NAME__GeneratedSharedModule,
|
|
726
753
|
adopter: __NAME__SharedModule,
|
|
727
|
-
extra: [createLspServerSharedModule(fullContext)]
|
|
754
|
+
extra: [createLspServerSharedModule(fullContext)],
|
|
755
|
+
overrides: options.extraSharedModules
|
|
728
756
|
},
|
|
729
757
|
languageModules: {
|
|
730
758
|
generated: ${primary.grammar}GeneratedModule,
|
|
@@ -980,7 +1008,7 @@ import { startGlspServer } from '@hydranium/glsp-server/node';
|
|
|
980
1008
|
// \`TransferElement\` structurally, so naming the AST type here compiles fine and
|
|
981
1009
|
// silently tells every typed client that a reference is a resolvable object
|
|
982
1010
|
// rather than a name.
|
|
983
|
-
${importList(roots, './language-server/generated-
|
|
1011
|
+
${importList(roots, './language-server/generated-hydranium/transfer-model.js', columns, true)}
|
|
984
1012
|
`
|
|
985
1013
|
: '';
|
|
986
1014
|
|
|
@@ -1063,7 +1091,13 @@ void glspServer;
|
|
|
1063
1091
|
// than a library entry — import \`./index.js\` instead to compose the language.
|
|
1064
1092
|
|
|
1065
1093
|
${reflectImport}${glspImports}${importList(coreNodeSymbols, '@hydranium/core/node', columns)}
|
|
1066
|
-
${data ? "import { DataServer } from '@hydranium/data-server';\n" : ''}
|
|
1094
|
+
${data ? "import { DataServer } from '@hydranium/data-server';\n" : ''}// The framework's entry point, NOT Langium's \`@hydranium/langium/lsp\` one. It is
|
|
1095
|
+
// signature-compatible and delegates straight through; what it adds first is
|
|
1096
|
+
// \`assertLspHeadComposed\`, which fails the start if the LSP head's SHARED module
|
|
1097
|
+
// was never composed. Reaching for Langium's is the natural mistake and it is
|
|
1098
|
+
// silent: the server boots, links and completes, while echo suppression, the
|
|
1099
|
+
// didChangeContent debounce and the last-client-close rebuild are all inert.
|
|
1100
|
+
import { startLanguageServer } from '@hydranium/core/lsp';
|
|
1067
1101
|
import { ProposedFeatures, createConnection } from 'vscode-languageserver/node';
|
|
1068
1102
|
${diagramImports}${transferImports}import { create__NAME__Services } from './language-server/__PROJECT_ID__-module.js';
|
|
1069
1103
|
${portCommands === '' ? '' : '\n' + portCommands}
|
|
@@ -1117,7 +1151,7 @@ function dataServerMainFile(composition: InitComposition): string {
|
|
|
1117
1151
|
${importList(['NodeFileSystem', 'startStdioServer'], '@hydranium/core/node', columns)}
|
|
1118
1152
|
import { DataServer } from '@hydranium/data-server';
|
|
1119
1153
|
// The TRANSFER root${plural}, not the AST one${plural} — same reasoning as \`main.ts\`.
|
|
1120
|
-
${importList(roots, './language-server/generated-
|
|
1154
|
+
${importList(roots, './language-server/generated-hydranium/transfer-model.js', columns, true)}
|
|
1121
1155
|
import { create__NAME__Services } from './language-server/__PROJECT_ID__-module.js';
|
|
1122
1156
|
|
|
1123
1157
|
const { shared } = create__NAME__Services({ ...NodeFileSystem });
|
|
@@ -1247,7 +1281,7 @@ function serializationTest(composition: InitComposition): string {
|
|
|
1247
1281
|
import { parseHelper } from '@hydranium/core/testing';
|
|
1248
1282
|
import { describe, expect, it } from 'vitest';
|
|
1249
1283
|
${importList(astTypes, '../src/language-server/ast.js', columns, true)}
|
|
1250
|
-
${importList(transferTypes, '../src/language-server/generated-
|
|
1284
|
+
${importList(transferTypes, '../src/language-server/generated-hydranium/transfer-model.js', columns, true)}
|
|
1251
1285
|
import { createServices } from '../src/services.js';
|
|
1252
1286
|
|
|
1253
1287
|
${composition.grammars.map(grammar => render(SERIALIZATION_SUITE, composition, grammar)).join('\n\n')}
|
package/src/commands/watch.ts
CHANGED
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
10
|
import type { LogThreshold, TransferElement } from '@hydranium/protocol';
|
|
11
|
-
import type {
|
|
11
|
+
import type {
|
|
12
|
+
DataClientProtocol,
|
|
13
|
+
DataServerProtocol,
|
|
14
|
+
TransferDocumentDeletedEvent,
|
|
15
|
+
TransferDocumentUpdatedEvent
|
|
16
|
+
} from '@hydranium/protocol/data';
|
|
12
17
|
import { logLevelEnv } from '../log-level.js';
|
|
13
18
|
import { spawnDataServer } from '../spawn-data-server.js';
|
|
14
19
|
|
|
@@ -132,6 +137,21 @@ export async function runWatch(options: WatchCommandOptions): Promise<void> {
|
|
|
132
137
|
onDocumentSaved(): void {
|
|
133
138
|
// Persistence is out of band for the per-URI update view.
|
|
134
139
|
},
|
|
140
|
+
onDocumentDeleted(event: TransferDocumentDeletedEvent): void {
|
|
141
|
+
// In band, unlike the two neighbours: this is the end of the stream
|
|
142
|
+
// the view exists to show, and a consumer that never hears it waits
|
|
143
|
+
// forever for an update that cannot come. The line carries no
|
|
144
|
+
// `document`, so a reader must switch on shape rather than assume one.
|
|
145
|
+
if (event.uri !== options.uri) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
write(`${JSON.stringify(event)}\n`);
|
|
149
|
+
},
|
|
150
|
+
onDocumentsBuilt(): void {
|
|
151
|
+
// Out of band for a per-URI view by construction: this notification
|
|
152
|
+
// carries only documents nobody watches, and this command watches the
|
|
153
|
+
// one URI it was given.
|
|
154
|
+
},
|
|
135
155
|
onProjectsChanged(): void {
|
|
136
156
|
// Project lifecycle is out of band for the per-URI update view.
|
|
137
157
|
}
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* SPDX-License-Identifier: MIT
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
|
+
export * from './commands/generate-ast-builder.js';
|
|
10
11
|
export * from './commands/generate-transfer-model.js';
|
|
11
12
|
export * from './commands/projects.js';
|
|
12
13
|
export * from './commands/query.js';
|