@hydranium/cli 1.0.0-next.22 → 1.0.0-next.220
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 +25 -5
- package/bin/hydranium-cli.js +13 -0
- package/lib/cli-version.js.map +1 -1
- package/lib/cli.js +0 -0
- package/lib/commands/analyze-heap-args.js.map +1 -1
- package/lib/commands/analyze-heap.d.ts +3 -0
- package/lib/commands/analyze-heap.d.ts.map +1 -1
- package/lib/commands/analyze-heap.js +2 -1
- package/lib/commands/analyze-heap.js.map +1 -1
- 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 +11 -9
- package/lib/commands/ast-ground-truth-args.js.map +1 -1
- package/lib/commands/ast-ground-truth.js +1 -1
- package/lib/commands/ast-ground-truth.js.map +1 -1
- 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/harness-args.d.ts +37 -42
- package/lib/commands/harness-args.d.ts.map +1 -1
- package/lib/commands/harness-args.js +65 -25
- package/lib/commands/harness-args.js.map +1 -1
- package/lib/commands/headless-harness.d.ts +37 -20
- package/lib/commands/headless-harness.d.ts.map +1 -1
- package/lib/commands/headless-harness.js +68 -17
- package/lib/commands/headless-harness.js.map +1 -1
- package/lib/commands/init-args.js.map +1 -1
- package/lib/commands/init-prompt.d.ts.map +1 -1
- package/lib/commands/init-templates.d.ts +0 -9
- package/lib/commands/init-templates.d.ts.map +1 -1
- package/lib/commands/init-templates.js +210 -96
- package/lib/commands/init-templates.js.map +1 -1
- package/lib/commands/init-wizard.js.map +1 -1
- package/lib/commands/init-workspace.d.ts +13 -3
- package/lib/commands/init-workspace.d.ts.map +1 -1
- package/lib/commands/init-workspace.js +21 -6
- package/lib/commands/init-workspace.js.map +1 -1
- package/lib/commands/init.d.ts +10 -2
- package/lib/commands/init.d.ts.map +1 -1
- package/lib/commands/init.js +10 -11
- 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 +13 -10
- package/lib/commands/lint-grammar-args.js.map +1 -1
- package/lib/commands/lint-grammar-report.js.map +1 -1
- package/lib/commands/lint-grammar.js +1 -1
- package/lib/commands/lint-grammar.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 +20 -10
- package/lib/commands/measure-memory-args.js.map +1 -1
- package/lib/commands/measure-memory-driver.js +3 -2
- package/lib/commands/measure-memory-driver.js.map +1 -1
- package/lib/commands/measure-memory.js +1 -1
- package/lib/commands/measure-memory.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 +12 -10
- package/lib/commands/model-docs-args.js.map +1 -1
- package/lib/commands/model-docs.js +1 -1
- package/lib/commands/model-docs.js.map +1 -1
- package/lib/commands/projects-args.d.ts.map +1 -1
- package/lib/commands/projects-args.js +5 -5
- package/lib/commands/projects-args.js.map +1 -1
- package/lib/commands/projects.d.ts +3 -4
- package/lib/commands/projects.d.ts.map +1 -1
- package/lib/commands/projects.js +2 -2
- package/lib/commands/projects.js.map +1 -1
- package/lib/commands/query-args.d.ts.map +1 -1
- package/lib/commands/query-args.js +5 -5
- package/lib/commands/query-args.js.map +1 -1
- package/lib/commands/query.d.ts +3 -4
- package/lib/commands/query.d.ts.map +1 -1
- package/lib/commands/query.js +2 -2
- package/lib/commands/query.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 +13 -10
- package/lib/commands/reflect-args.js.map +1 -1
- package/lib/commands/reflect-report.js.map +1 -1
- package/lib/commands/reflect.js +1 -1
- package/lib/commands/reflect.js.map +1 -1
- package/lib/commands/save-args.d.ts.map +1 -1
- package/lib/commands/save-args.js +6 -4
- package/lib/commands/save-args.js.map +1 -1
- package/lib/commands/save.d.ts +8 -4
- package/lib/commands/save.d.ts.map +1 -1
- package/lib/commands/save.js +31 -6
- package/lib/commands/save.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 +13 -10
- package/lib/commands/validate-args.js.map +1 -1
- package/lib/commands/validate-report.js.map +1 -1
- package/lib/commands/validate.js +1 -1
- package/lib/commands/validate.js.map +1 -1
- package/lib/commands/watch-args.d.ts.map +1 -1
- package/lib/commands/watch-args.js +6 -4
- package/lib/commands/watch-args.js.map +1 -1
- package/lib/commands/watch.d.ts +3 -4
- package/lib/commands/watch.d.ts.map +1 -1
- package/lib/commands/watch.js +20 -2
- package/lib/commands/watch.js.map +1 -1
- package/lib/driver-heap.d.ts +52 -0
- package/lib/driver-heap.d.ts.map +1 -0
- package/lib/driver-heap.js +51 -0
- package/lib/driver-heap.js.map +1 -0
- 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/lib/log-level.d.ts +21 -11
- package/lib/log-level.d.ts.map +1 -1
- package/lib/log-level.js +25 -14
- package/lib/log-level.js.map +1 -1
- package/lib/spawn-data-server.d.ts +6 -1
- package/lib/spawn-data-server.d.ts.map +1 -1
- package/lib/spawn-data-server.js +2 -1
- package/lib/spawn-data-server.js.map +1 -1
- package/lib/stderr-connection-logger.d.ts +12 -0
- package/lib/stderr-connection-logger.d.ts.map +1 -0
- package/lib/stderr-connection-logger.js +16 -0
- package/lib/stderr-connection-logger.js.map +1 -0
- package/lib/testing/echo-server.js +8 -1
- package/lib/testing/echo-server.js.map +1 -1
- package/package.json +39 -39
- package/src/commands/analyze-heap.ts +4 -1
- package/src/commands/ast-ground-truth-args.ts +15 -12
- package/src/commands/ast-ground-truth.ts +1 -1
- 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/harness-args.ts +73 -28
- package/src/commands/headless-harness.ts +86 -26
- package/src/commands/init-templates.ts +216 -97
- package/src/commands/init-workspace.ts +31 -7
- package/src/commands/init.ts +21 -14
- package/src/commands/lint-grammar-args.ts +17 -13
- package/src/commands/lint-grammar.ts +1 -1
- package/src/commands/measure-memory-args.ts +25 -13
- package/src/commands/measure-memory-driver.ts +3 -2
- package/src/commands/measure-memory.ts +1 -1
- package/src/commands/model-docs-args.ts +16 -13
- package/src/commands/model-docs.ts +1 -1
- package/src/commands/projects-args.ts +5 -4
- package/src/commands/projects.ts +4 -6
- package/src/commands/query-args.ts +5 -4
- package/src/commands/query.ts +4 -6
- package/src/commands/reflect-args.ts +17 -13
- package/src/commands/reflect.ts +1 -1
- package/src/commands/save-args.ts +6 -3
- package/src/commands/save.ts +49 -10
- package/src/commands/validate-args.ts +17 -13
- package/src/commands/validate.ts +1 -1
- package/src/commands/watch-args.ts +6 -3
- package/src/commands/watch.ts +28 -7
- package/src/driver-heap.ts +65 -0
- package/src/index.ts +1 -0
- package/src/log-level.ts +42 -14
- package/src/spawn-data-server.ts +14 -2
- package/src/stderr-connection-logger.ts +18 -0
- package/src/testing/echo-server.ts +10 -2
|
@@ -82,16 +82,6 @@ export interface InitTemplate {
|
|
|
82
82
|
content: string;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
/**
|
|
86
|
-
* The version every framework package carries before the first release.
|
|
87
|
-
*
|
|
88
|
-
* A scaffold made by a CLI still at this version pins a range the registry
|
|
89
|
-
* cannot serve, so the emitted README keeps its yalc note and `init` keeps its
|
|
90
|
-
* install warning while — and only while — it holds: from a published CLI the
|
|
91
|
-
* derived pins resolve and either note would be false as printed.
|
|
92
|
-
*/
|
|
93
|
-
export const UNPUBLISHED_FRAMEWORK_VERSION = '0.0.0';
|
|
94
|
-
|
|
95
85
|
/**
|
|
96
86
|
* The version `init` pins every `@hydranium/*` dependency at: the scaffolding
|
|
97
87
|
* CLI's own.
|
|
@@ -177,7 +167,7 @@ function columnsFor(composition: InitComposition): number {
|
|
|
177
167
|
|
|
178
168
|
/**
|
|
179
169
|
* A named import on one line while it fits the column budget, else one symbol
|
|
180
|
-
* per line. Emitted files are supposed to pass a
|
|
170
|
+
* per line. Emitted files are supposed to pass a format check as-is, and the
|
|
181
171
|
* generated-module import grows by one symbol per grammar.
|
|
182
172
|
*/
|
|
183
173
|
function importList(symbols: readonly string[], from: string, columns: number, typeOnly = false): string {
|
|
@@ -228,7 +218,12 @@ const HEAD_FRAMEWORK_DEPENDENCIES: Record<InitHead, readonly string[]> = {
|
|
|
228
218
|
const HEAD_THIRD_PARTY_DEPENDENCIES: Record<InitHead, Readonly<Record<string, string>>> = {
|
|
229
219
|
lsp: {
|
|
230
220
|
langium: '4.3.1',
|
|
231
|
-
|
|
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'
|
|
232
227
|
},
|
|
233
228
|
data: {},
|
|
234
229
|
glsp: {
|
|
@@ -241,6 +236,24 @@ const HEAD_THIRD_PARTY_DEPENDENCIES: Record<InitHead, Readonly<Record<string, st
|
|
|
241
236
|
}
|
|
242
237
|
};
|
|
243
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
|
+
|
|
244
257
|
/** The dependency block for a head set, merged and sorted as npm writes it. */
|
|
245
258
|
function dependencyBlock(composition: InitComposition, indent: string): string {
|
|
246
259
|
const merged: Record<string, string> = {};
|
|
@@ -260,7 +273,7 @@ function dependencyBlock(composition: InitComposition, indent: string): string {
|
|
|
260
273
|
// npm convention, and `npm install` rewrites `package.json` at two spaces
|
|
261
274
|
// regardless — so three would not survive the adopter's first install.
|
|
262
275
|
//
|
|
263
|
-
// The KEY ORDER is
|
|
276
|
+
// The KEY ORDER is the one Oxfmt's `sortPackageJson` canonicalises to, `private`
|
|
264
277
|
// before `license` included. Any other order is rewritten by the scaffolded
|
|
265
278
|
// project's own first `format` run — a diff on a file the adopter never touched
|
|
266
279
|
// — and it makes a byte-compare against a formatted copy of this emission
|
|
@@ -291,16 +304,9 @@ const PACKAGE_JSON = `{
|
|
|
291
304
|
"version": "0.0.0",
|
|
292
305
|
__PRIVATE__ "description": "__NAME__ language server, built with the Hydranium framework.",
|
|
293
306
|
"keywords": [
|
|
294
|
-
|
|
295
|
-
"langium",
|
|
296
|
-
"language-server",
|
|
297
|
-
"__PROJECT_ID__"
|
|
298
|
-
],
|
|
307
|
+
__KEYWORDS__ ],
|
|
299
308
|
"license": "UNLICENSED",
|
|
300
309
|
"author": "",
|
|
301
|
-
"type": "module",
|
|
302
|
-
"main": "lib/index.js",
|
|
303
|
-
"types": "lib/index.d.ts",
|
|
304
310
|
"bin": {
|
|
305
311
|
__BIN__ },
|
|
306
312
|
"files": [
|
|
@@ -308,11 +314,14 @@ __BIN__ },
|
|
|
308
314
|
"src",
|
|
309
315
|
"syntaxes"
|
|
310
316
|
],
|
|
317
|
+
"type": "module",
|
|
318
|
+
"main": "lib/index.js",
|
|
319
|
+
"types": "lib/index.d.ts",
|
|
311
320
|
"scripts": {
|
|
312
321
|
"build": "npm run generate && tsc",
|
|
313
|
-
"clean": "rimraf lib syntaxes src/language-server/generated src/language-server/generated-
|
|
322
|
+
"clean": "rimraf lib syntaxes src/language-server/generated src/language-server/generated-hydranium tsconfig.tsbuildinfo",
|
|
314
323
|
"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-
|
|
324
|
+
"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
325
|
"langium:generate": "langium generate",
|
|
317
326
|
"langium:watch": "langium generate --watch",
|
|
318
327
|
__LINT__ "start": "node lib/main.js --stdio",
|
|
@@ -328,8 +337,8 @@ __DEPENDENCIES__
|
|
|
328
337
|
"@types/node": "^22.0.0",
|
|
329
338
|
"langium-cli": "4.3.0",
|
|
330
339
|
"rimraf": "^5.0.0",
|
|
331
|
-
"typescript": "^
|
|
332
|
-
"vitest": "
|
|
340
|
+
"typescript": "^7.0.2",
|
|
341
|
+
"vitest": "__VITEST__"
|
|
333
342
|
},
|
|
334
343
|
"engines": {
|
|
335
344
|
"node": ">=22.13"
|
|
@@ -359,7 +368,7 @@ function npmRun(composition: InitComposition): string {
|
|
|
359
368
|
* name. Without the second key a scaffolded project has no command line that
|
|
360
369
|
* reaches its own data head at all.
|
|
361
370
|
*
|
|
362
|
-
* Alphabetical, because `
|
|
371
|
+
* Alphabetical, because Oxfmt's `sortPackageJson` sorts `bin` and any other
|
|
363
372
|
* order is rewritten by the scaffolded project's first `format` run.
|
|
364
373
|
*/
|
|
365
374
|
function binBlock(composition: InitComposition): string {
|
|
@@ -374,15 +383,32 @@ function binBlock(composition: InitComposition): string {
|
|
|
374
383
|
function packageJson(composition: InitComposition): string {
|
|
375
384
|
const { scope } = composition.packaging;
|
|
376
385
|
const packageName = scope === undefined ? composition.projectId : `${scope}/${composition.projectId}`;
|
|
377
|
-
|
|
386
|
+
const keywords =
|
|
387
|
+
['hydranium', 'langium', 'language-server', composition.projectId]
|
|
388
|
+
.sort()
|
|
389
|
+
.map(keyword => ` ${JSON.stringify(keyword)}`)
|
|
390
|
+
.join(',\n') + '\n';
|
|
391
|
+
// Emitted only where a root lint config says the repo lints. The absence is
|
|
378
392
|
// the dangerous direction: a task runner runs a script only where one is
|
|
379
393
|
// declared, so a package with no `lint` is SKIPPED rather than reported, and
|
|
380
394
|
// that reads as a clean lint. A wrong invocation is one visible line to edit.
|
|
381
|
-
const
|
|
395
|
+
const workspace = composition.packaging.workspace;
|
|
396
|
+
const oxlintConfig =
|
|
397
|
+
workspace?.oxlintConfig === undefined
|
|
398
|
+
? undefined
|
|
399
|
+
: `${'../'.repeat(workspace.targetPath.split('/').length)}${workspace.oxlintConfig}`;
|
|
400
|
+
const lint =
|
|
401
|
+
workspace?.oxlintConfig !== undefined
|
|
402
|
+
? ` "lint": "oxlint --report-unused-disable-directives --format stylish --config ${oxlintConfig} src test --max-warnings 0",\n`
|
|
403
|
+
: workspace?.eslintConfig !== undefined
|
|
404
|
+
? ' "lint": "eslint src test --max-warnings 0",\n'
|
|
405
|
+
: '';
|
|
382
406
|
return project(PACKAGE_JSON, composition)
|
|
383
407
|
.replace('__PACKAGE_NAME__', packageName)
|
|
408
|
+
.replace('__KEYWORDS__', keywords)
|
|
384
409
|
.replace('__BIN__', binBlock(composition))
|
|
385
410
|
.replace('__LINT__', lint)
|
|
411
|
+
.replace('__VITEST__', pinsVitest(composition) ? '~4.0.18' : '^4.0.0')
|
|
386
412
|
.replace('__PRIVATE__', composition.packaging.private ? ' "private": true,\n' : '')
|
|
387
413
|
.replace('__NPM_RUN__', npmRun(composition))
|
|
388
414
|
.replace('__FRAMEWORK_PIN__', frameworkPin(composition))
|
|
@@ -439,6 +465,11 @@ const TSCONFIG_COMPILER_OPTIONS: ReadonlyArray<readonly [string, JsonValue]> = [
|
|
|
439
465
|
['outDir', 'lib'],
|
|
440
466
|
['strict', true],
|
|
441
467
|
['esModuleInterop', true],
|
|
468
|
+
// A server that renders its own user-facing messages loads a catalogue, and
|
|
469
|
+
// a catalogue is JSON. Without this the import does not resolve at all, and
|
|
470
|
+
// without the `include` glob below a composite project rejects it with
|
|
471
|
+
// TS6307 naming neither cause.
|
|
472
|
+
['resolveJsonModule', true],
|
|
442
473
|
['skipLibCheck', true],
|
|
443
474
|
['declaration', true],
|
|
444
475
|
['experimentalDecorators', true],
|
|
@@ -480,7 +511,7 @@ function tsconfigJson(composition: InitComposition): string {
|
|
|
480
511
|
];
|
|
481
512
|
const extendsLine = workspace?.baseTsconfig === undefined ? '' : ` "extends": "${workspace.baseTsconfig}",\n`;
|
|
482
513
|
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`;
|
|
514
|
+
return `{\n${extendsLine} "compilerOptions": {\n${body}\n },\n "include": ["src", "src/**/*.json"]\n}\n`;
|
|
484
515
|
}
|
|
485
516
|
|
|
486
517
|
// `isolatedModules` is what makes this check agree with the transform that
|
|
@@ -498,7 +529,7 @@ const TSCONFIG_TEST = `{
|
|
|
498
529
|
"isolatedModules": true,
|
|
499
530
|
"types": ["node"]
|
|
500
531
|
},
|
|
501
|
-
"include": ["src", "test"]
|
|
532
|
+
"include": ["src", "test", "src/**/*.json"]
|
|
502
533
|
}
|
|
503
534
|
`;
|
|
504
535
|
|
|
@@ -708,13 +739,35 @@ export type __NAME__Services = LangiumServices &
|
|
|
708
739
|
LspServerAddedServices & {
|
|
709
740
|
shared: __NAME__SharedServices;
|
|
710
741
|
};
|
|
742
|
+
|
|
743
|
+
/** What a host or a test may vary about this composition. */
|
|
744
|
+
export interface __NAME__Options {
|
|
745
|
+
/**
|
|
746
|
+
* Shared modules layered in after the framework's own bindings.
|
|
747
|
+
*
|
|
748
|
+
* The framework constructs most shared services with no options —
|
|
749
|
+
* \`DocumentBuilder: services => new HydraniumDocumentBuilder(services)\` — so
|
|
750
|
+
* rebinding the slot is the only way to boot one configured differently, and
|
|
751
|
+
* a factory that hard-codes its composition leaves a test nowhere to do it.
|
|
752
|
+
* That is what this is for: pass a module binding \`workspace.DocumentBuilder\`
|
|
753
|
+
* to exercise a builder option, or to substitute a subclass.
|
|
754
|
+
*
|
|
755
|
+
* Composed LAST, after \`__NAME__SharedModule\`, so it wins over every other
|
|
756
|
+
* tier including this file's own bindings — which is what makes it usable for
|
|
757
|
+
* a slot the adopter overrides. Production code should not reach for it.
|
|
758
|
+
*/
|
|
759
|
+
readonly extraSharedModules?: ReadonlyArray<Module<__NAME__SharedServices, DeepPartial<__NAME__SharedServices>>>;
|
|
760
|
+
}
|
|
711
761
|
${configurationRoot}
|
|
712
762
|
${sharedModule}
|
|
713
763
|
|
|
714
764
|
${languageModules}
|
|
715
765
|
|
|
716
766
|
/** Compose the Langium DI tree for __NAME__ — returns the shared + language services. */
|
|
717
|
-
export function create__NAME__Services(
|
|
767
|
+
export function create__NAME__Services(
|
|
768
|
+
context: Partial<ServerModuleContext> = EmptyFileSystem,
|
|
769
|
+
options: __NAME__Options = {}
|
|
770
|
+
): {
|
|
718
771
|
shared: __NAME__SharedServices;
|
|
719
772
|
${returnType}
|
|
720
773
|
} {
|
|
@@ -724,7 +777,8 @@ ${returnType}
|
|
|
724
777
|
sharedModules: {
|
|
725
778
|
generated: __NAME__GeneratedSharedModule,
|
|
726
779
|
adopter: __NAME__SharedModule,
|
|
727
|
-
extra: [createLspServerSharedModule(fullContext)]
|
|
780
|
+
extra: [createLspServerSharedModule(fullContext)],
|
|
781
|
+
overrides: options.extraSharedModules
|
|
728
782
|
},
|
|
729
783
|
languageModules: {
|
|
730
784
|
generated: ${primary.grammar}GeneratedModule,
|
|
@@ -791,9 +845,9 @@ const SERIALIZER = `// Concrete-syntax emitter for __GRAMMAR__ — the parser's
|
|
|
791
845
|
//
|
|
792
846
|
// Without it every structured write fails: the framework's default binding at
|
|
793
847
|
// \`services.serializer.Serializer\` THROWS, because a concrete syntax is
|
|
794
|
-
// language knowledge no framework can derive. \`
|
|
795
|
-
// the data head's \`saveModelDocument\` and a GLSP \`SaveModelAction\`
|
|
796
|
-
// it.
|
|
848
|
+
// language knowledge no framework can derive. A client session's \`update\` /
|
|
849
|
+
// \`save\`, the data head's \`saveModelDocument\` and a GLSP \`SaveModelAction\`
|
|
850
|
+
// all reach it.
|
|
797
851
|
//
|
|
798
852
|
// \`AbstractSerializer\`'s generic property walk lays out FORMAT-structured
|
|
799
853
|
// output — its YAML and JSON subclasses are what it exists for — and cannot
|
|
@@ -980,7 +1034,7 @@ import { startGlspServer } from '@hydranium/glsp-server/node';
|
|
|
980
1034
|
// \`TransferElement\` structurally, so naming the AST type here compiles fine and
|
|
981
1035
|
// silently tells every typed client that a reference is a resolvable object
|
|
982
1036
|
// rather than a name.
|
|
983
|
-
${importList(roots, './language-server/generated-
|
|
1037
|
+
${importList(roots, './language-server/generated-hydranium/transfer-model.js', columns, true)}
|
|
984
1038
|
`
|
|
985
1039
|
: '';
|
|
986
1040
|
|
|
@@ -1063,11 +1117,17 @@ void glspServer;
|
|
|
1063
1117
|
// than a library entry — import \`./index.js\` instead to compose the language.
|
|
1064
1118
|
|
|
1065
1119
|
${reflectImport}${glspImports}${importList(coreNodeSymbols, '@hydranium/core/node', columns)}
|
|
1066
|
-
${data ? "import { DataServer } from '@hydranium/data-server';\n" : ''}
|
|
1120
|
+
${data ? "import { DataServer } from '@hydranium/data-server';\n" : ''}// The framework's entry point, NOT Langium's \`@hydranium/langium/lsp\` one. It is
|
|
1121
|
+
// signature-compatible and delegates straight through; what it adds first is
|
|
1122
|
+
// \`assertLspHeadComposed\`, which fails the start if the LSP head's SHARED module
|
|
1123
|
+
// was never composed. Reaching for Langium's is the natural mistake and it is
|
|
1124
|
+
// silent: the server boots, links and completes, while echo suppression, the
|
|
1125
|
+
// didChangeContent debounce and the last-client-close rebuild are all inert.
|
|
1126
|
+
import { startLanguageServer, withHydraniumLspFeatures } from '@hydranium/core/lsp';
|
|
1067
1127
|
import { ProposedFeatures, createConnection } from 'vscode-languageserver/node';
|
|
1068
1128
|
${diagramImports}${transferImports}import { create__NAME__Services } from './language-server/__PROJECT_ID__-module.js';
|
|
1069
1129
|
${portCommands === '' ? '' : '\n' + portCommands}
|
|
1070
|
-
const connection = createConnection(ProposedFeatures.all);
|
|
1130
|
+
const connection = createConnection(withHydraniumLspFeatures(ProposedFeatures.all));
|
|
1071
1131
|
const { shared } = create__NAME__Services({ connection, ...NodeFileSystem });
|
|
1072
1132
|
startLanguageServer(shared);
|
|
1073
1133
|
${dataBlock}${glspBlock}`;
|
|
@@ -1117,7 +1177,7 @@ function dataServerMainFile(composition: InitComposition): string {
|
|
|
1117
1177
|
${importList(['NodeFileSystem', 'startStdioServer'], '@hydranium/core/node', columns)}
|
|
1118
1178
|
import { DataServer } from '@hydranium/data-server';
|
|
1119
1179
|
// The TRANSFER root${plural}, not the AST one${plural} — same reasoning as \`main.ts\`.
|
|
1120
|
-
${importList(roots, './language-server/generated-
|
|
1180
|
+
${importList(roots, './language-server/generated-hydranium/transfer-model.js', columns, true)}
|
|
1121
1181
|
import { create__NAME__Services } from './language-server/__PROJECT_ID__-module.js';
|
|
1122
1182
|
|
|
1123
1183
|
const { shared } = create__NAME__Services({ ...NodeFileSystem });
|
|
@@ -1236,8 +1296,8 @@ function serializationTest(composition: InitComposition): string {
|
|
|
1236
1296
|
const transferTypes = composition.grammars.map(grammar => `${grammar.entryRule} as Transfer${grammar.entryRule}`);
|
|
1237
1297
|
const text = `// Round-trips each grammar through its serializer: parse the source, serialize
|
|
1238
1298
|
// the model, compare the text. The serializer is the parser's inverse, so this
|
|
1239
|
-
// is the assertion that keeps \`
|
|
1240
|
-
// language server can read back.
|
|
1299
|
+
// is the assertion that keeps a client session's \`update\` / \`save\` writing
|
|
1300
|
+
// files the language server can read back.
|
|
1241
1301
|
//
|
|
1242
1302
|
// The transfer case is the one that catches the mistake worth catching.
|
|
1243
1303
|
// \`ModelService.modelToText\` short-circuits only a RAW STRING, so a typed model
|
|
@@ -1247,7 +1307,7 @@ function serializationTest(composition: InitComposition): string {
|
|
|
1247
1307
|
import { parseHelper } from '@hydranium/core/testing';
|
|
1248
1308
|
import { describe, expect, it } from 'vitest';
|
|
1249
1309
|
${importList(astTypes, '../src/language-server/ast.js', columns, true)}
|
|
1250
|
-
${importList(transferTypes, '../src/language-server/generated-
|
|
1310
|
+
${importList(transferTypes, '../src/language-server/generated-hydranium/transfer-model.js', columns, true)}
|
|
1251
1311
|
import { createServices } from '../src/services.js';
|
|
1252
1312
|
|
|
1253
1313
|
${composition.grammars.map(grammar => render(SERIALIZATION_SUITE, composition, grammar)).join('\n\n')}
|
|
@@ -1416,20 +1476,6 @@ function readme(composition: InitComposition): string {
|
|
|
1416
1476
|
'',
|
|
1417
1477
|
'## Getting started',
|
|
1418
1478
|
'',
|
|
1419
|
-
// Conditional on the pin this scaffold actually carries — see
|
|
1420
|
-
// `UNPUBLISHED_FRAMEWORK_VERSION`.
|
|
1421
|
-
...(composition.frameworkVersion === UNPUBLISHED_FRAMEWORK_VERSION
|
|
1422
|
-
? [
|
|
1423
|
-
'> **Pre-publish note.** `@hydranium/*` is not on npm yet, so the `0.0.0`',
|
|
1424
|
-
'> pins below are placeholders and `npm install` will fail with a 404 until',
|
|
1425
|
-
'> the framework is released. Until then, supply the packages from a local',
|
|
1426
|
-
'> framework checkout with [yalc](https://github.com/wclr/yalc) — a plain',
|
|
1427
|
-
'> `file:` path or `npm link` is not enough, because the framework packages',
|
|
1428
|
-
'> depend on each other by version and npm would try to fetch those from the',
|
|
1429
|
-
'> registry too.',
|
|
1430
|
-
''
|
|
1431
|
-
]
|
|
1432
|
-
: []),
|
|
1433
1479
|
'```bash',
|
|
1434
1480
|
'npm install',
|
|
1435
1481
|
'npm run langium:generate # generate the AST from the grammar',
|
|
@@ -1543,6 +1589,24 @@ function readme(composition: InitComposition): string {
|
|
|
1543
1589
|
'atomic set and depends on a single physical copy, so a floating range can',
|
|
1544
1590
|
'silently resolve a second one.',
|
|
1545
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
|
+
: []),
|
|
1546
1610
|
'## If your repo gates license headers',
|
|
1547
1611
|
'',
|
|
1548
1612
|
'The emitted `.ts` files carry no copyright header — the scaffold cannot know',
|
|
@@ -1633,8 +1697,11 @@ export class __GRAMMAR__GlspState extends FullTextHydraniumGlspState<__ENTRY_RUL
|
|
|
1633
1697
|
|
|
1634
1698
|
const storage = `// Source-model storage for the __GRAMMAR__ diagram, inheriting both framework
|
|
1635
1699
|
// defaults: \`loadSourceModel\` (open + settle + \`setSourceRoot\`) and
|
|
1636
|
-
// \`saveSourceModel\` (
|
|
1637
|
-
//
|
|
1700
|
+
// \`saveSourceModel\` (flush the store's text for the primary and every tracked
|
|
1701
|
+
// secondary through the diagram's client session).
|
|
1702
|
+
//
|
|
1703
|
+
// No serializer runs on the save path — a save persists what the diagram's
|
|
1704
|
+
// operations already wrote to the store.
|
|
1638
1705
|
//
|
|
1639
1706
|
// The subclass exists so a bespoke load or save has a stable place to land.
|
|
1640
1707
|
|
|
@@ -1798,27 +1865,28 @@ export class __GRAMMAR__DiagramConfiguration implements DiagramConfiguration {
|
|
|
1798
1865
|
// read-only is a deletion the compiler checks, while read-only → editable is
|
|
1799
1866
|
// authoring against a seam you have not used yet.
|
|
1800
1867
|
//
|
|
1801
|
-
// **It
|
|
1802
|
-
// \`
|
|
1803
|
-
//
|
|
1804
|
-
//
|
|
1805
|
-
//
|
|
1806
|
-
//
|
|
1807
|
-
//
|
|
1808
|
-
// \`HydraniumGlspRecordingCommand\` over \`state.sourceModel\` instead — the same
|
|
1809
|
-
// binding the diagram's own save action needs.
|
|
1868
|
+
// **It edits the operation's working copy.** A handler runs inside an operation,
|
|
1869
|
+
// where \`sourceRoot\` is a copy of the built root: the recording command's
|
|
1870
|
+
// runnable appends to the copy, and the operation writes the copy's text through
|
|
1871
|
+
// the grammar's \`Serializer\` once, gated on the version the copy was made at,
|
|
1872
|
+
// with undo and redo applying that one change. A command that writes the
|
|
1873
|
+
// document itself instead reads no text off the copy, which has no \`$document\`,
|
|
1874
|
+
// and the operation's own write then overwrites it.
|
|
1810
1875
|
//
|
|
1811
1876
|
// **The drop location is discarded.** \`needsClientLayout\` is \`true\` and the
|
|
1812
1877
|
// starter grammar persists no bounds, so there is nowhere to put a coordinate: the
|
|
1813
1878
|
// node is appended at the end of the document and the client places it.
|
|
1814
1879
|
//
|
|
1815
1880
|
// Like \`types.ts\`, \`gmodel-factory.ts\` and \`diagram-configuration.ts\`, this file
|
|
1816
|
-
// knows the starter grammar's
|
|
1817
|
-
//
|
|
1881
|
+
// knows the starter grammar's shape — the \`nodes\` list and the \`__NODE_RULE__\`
|
|
1882
|
+
// it holds. Replacing the grammar means replacing these four together.
|
|
1818
1883
|
|
|
1819
1884
|
import { type Command, type CreateNodeOperation, JsonCreateNodeOperationHandler, type MaybePromise } from '@eclipse-glsp/server';
|
|
1885
|
+
import { appendChild } from '@hydranium/core';
|
|
1886
|
+
import { HydraniumGlspRecordingCommand } from '@hydranium/glsp-server';
|
|
1820
1887
|
import { findNextUnique } from '@hydranium/protocol';
|
|
1821
1888
|
import { injectable } from 'inversify';
|
|
1889
|
+
import type { __NODE_RULE__ } from '../../language-server/ast.js';
|
|
1822
1890
|
import { ${upper}_NODE_TYPE } from './types.js';
|
|
1823
1891
|
import type { __GRAMMAR__GlspState } from './state.js';
|
|
1824
1892
|
|
|
@@ -1837,34 +1905,14 @@ export class __GRAMMAR__CreateNodeOperationHandler extends JsonCreateNodeOperati
|
|
|
1837
1905
|
if (!this.elementTypeIds.includes(operation.elementTypeId)) {
|
|
1838
1906
|
return undefined;
|
|
1839
1907
|
}
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
before,
|
|
1844
|
-
findNextUnique(
|
|
1908
|
+
return new HydraniumGlspRecordingCommand(this.modelState, this.label, () => {
|
|
1909
|
+
const root = this.modelState.sourceRoot;
|
|
1910
|
+
const name = findNextUnique(
|
|
1845
1911
|
NODE_NAME_STEM,
|
|
1846
|
-
|
|
1847
|
-
)
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
// has exactly one field, so there is nothing to merge a concurrent edit into.
|
|
1851
|
-
return {
|
|
1852
|
-
execute: () => state.updateSourceModel({ text: after }),
|
|
1853
|
-
undo: () => state.updateSourceModel({ text: before }),
|
|
1854
|
-
redo: () => state.updateSourceModel({ text: after })
|
|
1855
|
-
};
|
|
1856
|
-
}
|
|
1857
|
-
|
|
1858
|
-
/** The text the captured source root was parsed from — the baseline an edit appends to. */
|
|
1859
|
-
protected documentText(): string {
|
|
1860
|
-
return this.modelState.sourceRoot.$document?.textDocument.getText() ?? '';
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
/** \`text\` with one more node declaration, under exactly one trailing newline. */
|
|
1864
|
-
protected withNode(text: string, name: string): string {
|
|
1865
|
-
const body = text.trimEnd();
|
|
1866
|
-
const declaration = \`node \${name}\`;
|
|
1867
|
-
return body.length === 0 ? \`\${declaration}\\n\` : \`\${body}\\n\${declaration}\\n\`;
|
|
1912
|
+
root.nodes.map(node => node.name)
|
|
1913
|
+
);
|
|
1914
|
+
appendChild(root, 'nodes', root.nodes, { $type: '__NODE_RULE__', name } as __NODE_RULE__);
|
|
1915
|
+
});
|
|
1868
1916
|
}
|
|
1869
1917
|
}
|
|
1870
1918
|
`;
|
|
@@ -1960,6 +2008,76 @@ export class __GRAMMAR__DiagramModule extends AbstractHydraniumGlspDiagramModule
|
|
|
1960
2008
|
binding.add(__GRAMMAR__CreateNodeOperationHandler);
|
|
1961
2009
|
}
|
|
1962
2010
|
}
|
|
2011
|
+
`;
|
|
2012
|
+
|
|
2013
|
+
const diagramTest = `// The starter create-node handler, run the way a client runs it: an in-process
|
|
2014
|
+
// GLSP server opens a document, a create-node operation adds a node, and an undo
|
|
2015
|
+
// and a redo take it out and put it back. Every step must keep the nodes the
|
|
2016
|
+
// document already had, and its comments: the diagram writes the document from
|
|
2017
|
+
// its AST, which carries none.
|
|
2018
|
+
|
|
2019
|
+
import 'reflect-metadata';
|
|
2020
|
+
import { type Action, CreateNodeOperation, RedoAction, ServerModule, SetDirtyStateAction, UndoAction } from '@eclipse-glsp/server';
|
|
2021
|
+
import { initializeWorkspaceProgrammatically } from '@hydranium/core';
|
|
2022
|
+
import { makeScratchWorkspace, type ScratchWorkspace } from '@hydranium/core/testing/node';
|
|
2023
|
+
import { HydraniumGlspAppModule } from '@hydranium/glsp-server';
|
|
2024
|
+
import { type GlspHarness, makeGlspHarness } from '@hydranium/glsp-server/testing';
|
|
2025
|
+
import { waitFor } from '@hydranium/protocol/testing';
|
|
2026
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
2027
|
+
import { __GRAMMAR__DiagramModule } from '../src/glsp/__GRAMMAR_ID__/diagram-module.js';
|
|
2028
|
+
import type { __GRAMMAR__GlspState } from '../src/glsp/__GRAMMAR_ID__/state.js';
|
|
2029
|
+
import { ${upper}_DIAGRAM_TYPE, ${upper}_NODE_TYPE } from '../src/glsp/__GRAMMAR_ID__/types.js';
|
|
2030
|
+
import { createServices } from '../src/services.js';
|
|
2031
|
+
|
|
2032
|
+
let workspace: ScratchWorkspace | undefined;
|
|
2033
|
+
let diagram: GlspHarness<__GRAMMAR__GlspState> | undefined;
|
|
2034
|
+
|
|
2035
|
+
afterEach(() => {
|
|
2036
|
+
diagram?.dispose();
|
|
2037
|
+
workspace?.dispose();
|
|
2038
|
+
diagram = undefined;
|
|
2039
|
+
workspace = undefined;
|
|
2040
|
+
});
|
|
2041
|
+
|
|
2042
|
+
/** Send \`action\` and wait for the dirty state the server answers it with, which it sends once the edit is written. */
|
|
2043
|
+
async function send(harness: GlspHarness<__GRAMMAR__GlspState>, action: Action, reason: string): Promise<void> {
|
|
2044
|
+
const before = harness.actions.length;
|
|
2045
|
+
harness.dispatch(action);
|
|
2046
|
+
await waitFor(() => harness.actions.slice(before).some(sent => SetDirtyStateAction.is(sent) && sent.reason === reason), {
|
|
2047
|
+
message: \`no '\${reason}' dirty state\`
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
describe('__GRAMMAR__ diagram', () => {
|
|
2052
|
+
it('creates a node after the existing ones, keeping their comments, and undoes and redoes it', async () => {
|
|
2053
|
+
workspace = makeScratchWorkspace({ prefix: '__GRAMMAR_ID__-diagram-' });
|
|
2054
|
+
const original = '// keep this comment\\nnode first -> second\\n// about second\\nnode second';
|
|
2055
|
+
const file = workspace.write('model.__EXTENSION__', original);
|
|
2056
|
+
const { shared } = createServices();
|
|
2057
|
+
await initializeWorkspaceProgrammatically(shared, workspace.root);
|
|
2058
|
+
diagram = makeGlspHarness<__GRAMMAR__GlspState>({
|
|
2059
|
+
serverModule: new ServerModule().configureDiagramModule(new __GRAMMAR__DiagramModule()),
|
|
2060
|
+
diagramType: ${upper}_DIAGRAM_TYPE,
|
|
2061
|
+
appModules: [new HydraniumGlspAppModule({ shared })]
|
|
2062
|
+
});
|
|
2063
|
+
await diagram.start();
|
|
2064
|
+
await diagram.openDocument(file);
|
|
2065
|
+
const uri = workspace.uri('model.__EXTENSION__');
|
|
2066
|
+
const text = (): string | undefined => shared.workspace.TextDocuments.get(uri)?.getText();
|
|
2067
|
+
|
|
2068
|
+
await send(diagram, CreateNodeOperation.create(${upper}_NODE_TYPE), 'operation');
|
|
2069
|
+
const created = text();
|
|
2070
|
+
await send(diagram, UndoAction.create(), 'undo');
|
|
2071
|
+
const undone = text();
|
|
2072
|
+
await send(diagram, RedoAction.create(), 'redo');
|
|
2073
|
+
|
|
2074
|
+
expect({ created, undone, redone: text() }).toEqual({
|
|
2075
|
+
created: \`\${original}\\nnode Node\`,
|
|
2076
|
+
undone: original,
|
|
2077
|
+
redone: \`\${original}\\nnode Node\`
|
|
2078
|
+
});
|
|
2079
|
+
});
|
|
2080
|
+
});
|
|
1963
2081
|
`;
|
|
1964
2082
|
|
|
1965
2083
|
return [
|
|
@@ -1970,7 +2088,8 @@ export class __GRAMMAR__DiagramModule extends AbstractHydraniumGlspDiagramModule
|
|
|
1970
2088
|
{ path: `${dir}/gmodel-factory.ts`, content: render(factory) },
|
|
1971
2089
|
{ path: `${dir}/diagram-configuration.ts`, content: render(configuration) },
|
|
1972
2090
|
{ path: `${dir}/create-node-operation-handler.ts`, content: render(createHandler) },
|
|
1973
|
-
{ path: `${dir}/diagram-module.ts`, content: render(module) }
|
|
2091
|
+
{ path: `${dir}/diagram-module.ts`, content: render(module) },
|
|
2092
|
+
{ path: `test/${grammar.grammarId}-diagram.test.ts`, content: render(diagramTest) }
|
|
1974
2093
|
];
|
|
1975
2094
|
}
|
|
1976
2095
|
|
|
@@ -186,10 +186,14 @@ export interface WorkspaceDetection {
|
|
|
186
186
|
readonly baseTsconfig?: string;
|
|
187
187
|
/** That tsconfig's `compilerOptions`, so the emitted one can drop what it inherits. */
|
|
188
188
|
readonly baseCompilerOptions?: Readonly<Record<string, JsonValue>>;
|
|
189
|
-
/** The root
|
|
189
|
+
/** The root formatter config's `printWidth`, so emitted sources wrap where the repo wraps. */
|
|
190
190
|
readonly printWidth?: number;
|
|
191
191
|
/** Filename of the root eslint config, when there is one. Absent means the repo does not lint. */
|
|
192
192
|
readonly eslintConfig?: string;
|
|
193
|
+
/** Filename of the root Oxlint config, when present. */
|
|
194
|
+
readonly oxlintConfig?: string;
|
|
195
|
+
/** The npm version the root's `packageManager` field declares, when it names npm. */
|
|
196
|
+
readonly packageManagerNpm?: string;
|
|
193
197
|
}
|
|
194
198
|
|
|
195
199
|
/**
|
|
@@ -339,7 +343,7 @@ export function findBaseTsconfig(
|
|
|
339
343
|
}
|
|
340
344
|
|
|
341
345
|
/**
|
|
342
|
-
* The repo's
|
|
346
|
+
* The repo's formatter `printWidth`, so the emitted sources wrap where the repo
|
|
343
347
|
* wraps instead of at the scaffold's own default.
|
|
344
348
|
*
|
|
345
349
|
* **Why this is worth detecting at all.** There is no width that is stable for
|
|
@@ -360,19 +364,21 @@ export function findBaseTsconfig(
|
|
|
360
364
|
* adopter's formatter rewraps on first commit. So a heuristic that is usually
|
|
361
365
|
* right strictly improves on no detection, and can break nothing.
|
|
362
366
|
*/
|
|
363
|
-
export function
|
|
367
|
+
export function findFormatterPrintWidth(rootDir: string, probe: WorkspaceProbe): number | undefined {
|
|
364
368
|
const manifest = probe.readJson(path.join(rootDir, 'package.json'));
|
|
365
369
|
if (isJsonObject(manifest) && isJsonObject(manifest.prettier) && typeof manifest.prettier.printWidth === 'number') {
|
|
366
370
|
return manifest.prettier.printWidth;
|
|
367
371
|
}
|
|
368
372
|
const present = probe.listFiles(rootDir);
|
|
369
|
-
for (const file of present.filter(name => name === '.prettierrc' || name === '.prettierrc.json')) {
|
|
373
|
+
for (const file of present.filter(name => name === '.oxfmtrc.json' || name === '.prettierrc' || name === '.prettierrc.json')) {
|
|
370
374
|
const parsed = probe.readJson(path.join(rootDir, file));
|
|
371
375
|
if (isJsonObject(parsed) && typeof parsed.printWidth === 'number') {
|
|
372
376
|
return parsed.printWidth;
|
|
373
377
|
}
|
|
374
378
|
}
|
|
375
|
-
for (const file of present.filter(name =>
|
|
379
|
+
for (const file of present.filter(name =>
|
|
380
|
+
/^(\.prettierrc\.[cm]?[jt]s|prettier\.config\.[cm]?[jt]s|oxfmt\.config\.[cm]?[jt]s)$/.test(name)
|
|
381
|
+
)) {
|
|
376
382
|
const width = /\bprintWidth\s*:\s*(\d+)/.exec(probe.readText(path.join(rootDir, file)) ?? '');
|
|
377
383
|
if (width) {
|
|
378
384
|
return Number(width[1]);
|
|
@@ -398,6 +404,22 @@ export function findEslintConfig(rootDir: string, probe: WorkspaceProbe): string
|
|
|
398
404
|
return probe.listFiles(rootDir).find(name => /^eslint\.config\.[cm]?[jt]s$/.test(name) || /^\.eslintrc(\..+)?$/.test(name));
|
|
399
405
|
}
|
|
400
406
|
|
|
407
|
+
/** Detect Oxlint without evaluating executable configuration files. */
|
|
408
|
+
export function findOxlintConfig(rootDir: string, probe: WorkspaceProbe): string | undefined {
|
|
409
|
+
return probe.listFiles(rootDir).find(name => /^(oxlint\.config\.[cm]?[jt]s|\.oxlintrc\.jsonc?)$/.test(name));
|
|
410
|
+
}
|
|
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
|
+
|
|
401
423
|
/** POSIX-style relative path, which is what both `extends` and `--prefix` want on every platform. */
|
|
402
424
|
function posixRelative(from: string, to: string): string {
|
|
403
425
|
return path.relative(from, to).split(path.sep).join('/');
|
|
@@ -436,7 +458,9 @@ export function detectWorkspace(targetDir: string, probe: WorkspaceProbe): Works
|
|
|
436
458
|
scope: inferPackageScope(rootDir, workspaces, probe),
|
|
437
459
|
baseTsconfig: base && posixRelative(absoluteTarget, path.join(rootDir, base.file)),
|
|
438
460
|
baseCompilerOptions: base?.compilerOptions,
|
|
439
|
-
printWidth:
|
|
440
|
-
eslintConfig: findEslintConfig(rootDir, probe)
|
|
461
|
+
printWidth: findFormatterPrintWidth(rootDir, probe),
|
|
462
|
+
eslintConfig: findEslintConfig(rootDir, probe),
|
|
463
|
+
oxlintConfig: findOxlintConfig(rootDir, probe),
|
|
464
|
+
packageManagerNpm: findPackageManagerNpm(rootDir, probe)
|
|
441
465
|
};
|
|
442
466
|
}
|