@likec4/language-server 1.44.0 → 1.46.0

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.
Files changed (63) hide show
  1. package/dist/LikeC4LanguageServices.d.ts +4 -15
  2. package/dist/LikeC4LanguageServices.js +4 -32
  3. package/dist/Rpc.js +44 -21
  4. package/dist/ast.d.ts +10 -0
  5. package/dist/ast.js +13 -2
  6. package/dist/browser.js +2 -2
  7. package/dist/bundled.js +2 -0
  8. package/dist/bundled.mjs +3838 -4059
  9. package/dist/filesystem/ChokidarWatcher.d.ts +2 -0
  10. package/dist/filesystem/ChokidarWatcher.js +29 -18
  11. package/dist/filesystem/LikeC4FileSystem.js +4 -0
  12. package/dist/filesystem/index.d.ts +2 -0
  13. package/dist/generated/ast.d.ts +46 -9
  14. package/dist/generated/ast.js +56 -4
  15. package/dist/generated/grammar.js +1 -1
  16. package/dist/generated-lib/icons.js +1 -1
  17. package/dist/index.d.ts +3 -1
  18. package/dist/index.js +5 -3
  19. package/dist/lsp/DocumentSymbolProvider.js +12 -1
  20. package/dist/mcp/server/StdioLikeC4MCPServer.js +10 -6
  21. package/dist/mcp/server/StreamableLikeC4MCPServer.js +97 -97
  22. package/dist/mcp/server/WithMCPServer.js +4 -6
  23. package/dist/mcp/tools/read-deployment.js +18 -0
  24. package/dist/mcp/tools/read-element.js +24 -0
  25. package/dist/mcp/tools/search-element.js +5 -5
  26. package/dist/mcp/utils.js +1 -1
  27. package/dist/model/builder/buildModel.js +70 -1
  28. package/dist/model/deployments-index.js +2 -2
  29. package/dist/model/fqn-index.d.ts +1 -2
  30. package/dist/model/fqn-index.js +21 -18
  31. package/dist/model/model-builder.js +0 -2
  32. package/dist/model/model-parser.d.ts +3 -0
  33. package/dist/model/model-parser.js +41 -27
  34. package/dist/model/parser/Base.js +8 -3
  35. package/dist/model/parser/GlobalsParser.d.ts +1 -0
  36. package/dist/model/parser/ModelParser.d.ts +2 -1
  37. package/dist/model/parser/ModelParser.js +45 -1
  38. package/dist/model/parser/SpecificationParser.js +4 -0
  39. package/dist/model/parser/ViewsParser.d.ts +1 -0
  40. package/dist/model/parser/ViewsParser.js +16 -1
  41. package/dist/model-change/ModelChanges.d.ts +2 -2
  42. package/dist/model-change/ModelChanges.js +41 -11
  43. package/dist/protocol.d.ts +33 -10
  44. package/dist/protocol.js +13 -4
  45. package/dist/validation/index.d.ts +1 -1
  46. package/dist/validation/index.js +11 -1
  47. package/dist/validation/relation.d.ts +1 -0
  48. package/dist/validation/relation.js +87 -1
  49. package/dist/validation/view-checks.d.ts +4 -0
  50. package/dist/validation/view-checks.js +46 -0
  51. package/dist/view-utils/manual-layout.js +2 -4
  52. package/dist/views/LikeC4ManualLayouts.d.ts +16 -2
  53. package/dist/views/LikeC4ManualLayouts.js +100 -23
  54. package/dist/views/LikeC4Views.d.ts +26 -5
  55. package/dist/views/LikeC4Views.js +49 -33
  56. package/dist/workspace/AstNodeDescriptionProvider.js +6 -3
  57. package/dist/workspace/IndexManager.js +1 -1
  58. package/dist/workspace/LangiumDocuments.d.ts +3 -2
  59. package/dist/workspace/LangiumDocuments.js +29 -15
  60. package/dist/workspace/ProjectsManager.d.ts +45 -16
  61. package/dist/workspace/ProjectsManager.js +227 -45
  62. package/dist/workspace/WorkspaceManager.js +43 -0
  63. package/package.json +22 -21
@@ -1,10 +1,12 @@
1
- import { isLikeC4Config, validateProjectConfig, } from '@likec4/config';
1
+ import { isLikeC4Config, normalizeIncludeConfig, validateProjectConfig, } from '@likec4/config';
2
2
  import { BiMap, delay, invariant, memoizeProp, nonNullable } from '@likec4/core/utils';
3
+ import { wrapError } from '@likec4/log';
3
4
  import { deepEqual } from 'fast-equals';
4
5
  import { interruptAndCheck, URI, WorkspaceCache, } from 'langium';
5
6
  import picomatch from 'picomatch';
6
7
  import { hasAtLeast, isNullish, map, pipe, prop, sortBy } from 'remeda';
7
8
  import { joinRelativeURL, parseFilename, withoutProtocol, withTrailingSlash, } from 'ufo';
9
+ import { isLikeC4Builtin } from '../likec4lib';
8
10
  import { logger as mainLogger } from '../logger';
9
11
  const logger = mainLogger.getChild('ProjectsManager');
10
12
  function normalizeUri(uri) {
@@ -30,6 +32,7 @@ const DefaultProject = {
30
32
  exclude: ['**/node_modules/**'],
31
33
  },
32
34
  exclude: picomatch('**/node_modules/**', { dot: true }),
35
+ includeConfig: { paths: [], maxDepth: 3, fileThreshold: 30 },
33
36
  };
34
37
  export class ProjectsManager {
35
38
  services;
@@ -43,6 +46,10 @@ export class ProjectsManager {
43
46
  * (it is used in CLI and Vite plugin)
44
47
  */
45
48
  #defaultProjectId = undefined;
49
+ /**
50
+ * Cached default project.
51
+ */
52
+ #defaultProject = undefined;
46
53
  /**
47
54
  * The mapping between project config files and project IDs.
48
55
  */
@@ -78,6 +85,7 @@ export class ProjectsManager {
78
85
  if (id === this.#defaultProjectId) {
79
86
  return;
80
87
  }
88
+ this.#defaultProject = undefined;
81
89
  if (!id || id === ProjectsManager.DefaultProjectId) {
82
90
  logger.debug `reset default project ID`;
83
91
  this.#defaultProjectId = undefined;
@@ -87,6 +95,25 @@ export class ProjectsManager {
87
95
  logger.debug `set default project ID to ${id}`;
88
96
  this.#defaultProjectId = id;
89
97
  }
98
+ get default() {
99
+ if (!this.#defaultProject) {
100
+ const id = this.defaultProjectId ?? ProjectsManager.DefaultProjectId;
101
+ let project = this.#projects.find(p => p.id === id);
102
+ if (!project) {
103
+ const folderUri = this.getWorkspaceFolder();
104
+ project = {
105
+ id,
106
+ config: DefaultProject.config,
107
+ folder: ProjectFolder(folderUri),
108
+ folderUri,
109
+ exclude: DefaultProject.exclude,
110
+ includeConfig: DefaultProject.includeConfig,
111
+ };
112
+ }
113
+ this.#defaultProject = project;
114
+ }
115
+ return this.#defaultProject;
116
+ }
90
117
  get all() {
91
118
  if (hasAtLeast(this.#projects, 1)) {
92
119
  const ids = [
@@ -114,7 +141,7 @@ export class ProjectsManager {
114
141
  }
115
142
  catch (error) {
116
143
  logger.warn('Failed to get workspace URI, using default folder', { error });
117
- folderUri = URI.file('');
144
+ folderUri = URI.file('/');
118
145
  // ignore - workspace not initialized
119
146
  }
120
147
  return {
@@ -123,11 +150,12 @@ export class ProjectsManager {
123
150
  folderUri,
124
151
  };
125
152
  }
126
- const { config, folderUri, } = nonNullable(this.#projects.find(p => p.id === id), `Project "${id}" not found`);
153
+ const project = nonNullable(this.#projects.find(p => p.id === id), `Project "${id}" not found`);
127
154
  return {
128
155
  id,
129
- folderUri,
130
- config,
156
+ folderUri: project.folderUri,
157
+ config: project.config,
158
+ ...(project.includePaths && { includePaths: project.includePaths.map(p => p.uri) }),
131
159
  };
132
160
  }
133
161
  /**
@@ -188,42 +216,31 @@ export class ProjectsManager {
188
216
  return isConfigFile;
189
217
  }
190
218
  /**
191
- * Checks if the provided file system entry is a valid project config file.
192
- *
193
- * @param entry The file system entry to check
219
+ * Registers likec4 project by config file.
194
220
  */
195
221
  async registerConfigFile(configFile) {
196
- if (this.isConfigFile(configFile)) {
197
- try {
198
- return await this.registerProject(configFile);
199
- }
200
- catch (error) {
201
- this.services.lsp.Connection?.window.showErrorMessage(`LikeC4: Failed to register project at ${configFile.fsPath}`);
202
- logger.warn('Failed to register project at {uri}', { uri: configFile.fsPath, error });
203
- return undefined;
204
- }
222
+ if (DefaultProject.exclude(configFile.path)) {
223
+ throw new Error(`Path to ${configFile.fsPath} is excluded by: ${DefaultProject.config.exclude.join(', ')}`);
205
224
  }
206
- return undefined;
207
- }
208
- /**
209
- * Registers (or reloads) likec4 project by config file or config object.
210
- * If there is some project registered at same folder, it will be reloaded.
211
- */
212
- async registerProject(opts) {
213
- if (URI.isUri(opts)) {
214
- const configFile = opts;
225
+ if (!this.isConfigFile(configFile)) {
226
+ throw new Error(`${configFile.fsPath} is not a valid LikeC4 config filename.`);
227
+ }
228
+ try {
215
229
  const config = await this.services.workspace.FileSystemProvider.loadProjectConfig(configFile);
216
230
  const path = joinRelativeURL(configFile.path, '..');
217
231
  const folderUri = configFile.with({ path });
218
- return this._registerProject({ config, folderUri });
232
+ return await this.registerProject({ config, folderUri });
233
+ }
234
+ catch (error) {
235
+ this.services.lsp.Connection?.window.showErrorMessage(`LikeC4: Failed to register project at ${configFile.fsPath}`);
236
+ throw wrapError(error, `Failed to register project config ${configFile.fsPath}:\n`);
219
237
  }
220
- return this._registerProject(opts);
221
238
  }
222
239
  /**
223
240
  * Registers (or reloads) likec4 project by config file or config object.
224
241
  * If there is some project registered at same folder, it will be reloaded.
225
242
  */
226
- _registerProject(opts) {
243
+ async registerProject(opts) {
227
244
  const config = validateProjectConfig(opts.config);
228
245
  const folder = ProjectFolder(opts.folderUri);
229
246
  let project = this.#projects.find(p => p.folder === folder);
@@ -237,11 +254,13 @@ export class ProjectsManager {
237
254
  logger.warn `Project "${config.name}" already exists, generating unique ID`;
238
255
  }
239
256
  id = this.uniqueProjectId(config.name);
257
+ const includeConfig = normalizeIncludeConfig(config.include);
240
258
  project = {
241
259
  id,
242
260
  config,
243
261
  folder,
244
262
  folderUri: URI.parse(folder),
263
+ includeConfig,
245
264
  };
246
265
  // if there is any project within subfolder or parent folder
247
266
  // we need to reset assigned to documents project IDs
@@ -264,22 +283,83 @@ export class ProjectsManager {
264
283
  logger.info `update project ${project.id} on config change`;
265
284
  }
266
285
  project.config = config;
286
+ const includeConfig = normalizeIncludeConfig(config.include);
287
+ project.includeConfig = includeConfig;
267
288
  }
289
+ // Reset cached default project
290
+ this.#defaultProject = undefined;
268
291
  if (isNullish(config.exclude)) {
269
292
  project.exclude = DefaultProject.exclude;
270
293
  }
271
294
  else if (hasAtLeast(config.exclude, 1)) {
272
295
  project.exclude = picomatch(config.exclude, { dot: true });
273
296
  }
297
+ // Resolve include paths relative to project folder
298
+ if (project.includeConfig.paths && hasAtLeast(project.includeConfig.paths, 1)) {
299
+ project.includePaths = project.includeConfig.paths.map(includePath => {
300
+ const resolvedPath = joinRelativeURL(project.folderUri.path, includePath);
301
+ const uri = project.folderUri.with({ path: resolvedPath });
302
+ return {
303
+ uri,
304
+ folder: ProjectFolder(uri),
305
+ };
306
+ });
307
+ logger.debug `project ${project.id} include paths: ${project.includePaths.map(p => p.uri.fsPath).join(', ')}`;
308
+ // Check for overlapping include paths with other projects
309
+ for (const includePath of project.includePaths) {
310
+ // Check if this include path overlaps with another project's folder
311
+ for (const otherProject of this.#projects) {
312
+ if (otherProject.id === project.id)
313
+ continue;
314
+ if (includePath.folder.startsWith(otherProject.folder) || otherProject.folder.startsWith(includePath.folder)) {
315
+ logger.warn('Project "{projectId}" include path "{includePath}" overlaps with project "{otherProjectId}" folder. ' +
316
+ 'Files in overlapping areas will only belong to one project.', { projectId: project.id, includePath: includePath.folder, otherProjectId: otherProject.id });
317
+ }
318
+ // Check if this include path overlaps with another project's include paths
319
+ if (otherProject.includePaths) {
320
+ for (const otherIncludePath of otherProject.includePaths) {
321
+ if (includePath.folder.startsWith(otherIncludePath.folder)
322
+ || otherIncludePath.folder.startsWith(includePath.folder)) {
323
+ logger.warn('Project "{projectId}" include path "{includePath}" overlaps with project "{otherProjectId}" ' +
324
+ 'include path "{otherIncludePath}". Files in overlapping areas will only belong to one project.', {
325
+ projectId: project.id,
326
+ includePath: includePath.folder,
327
+ otherProjectId: otherProject.id,
328
+ otherIncludePath: otherIncludePath.folder,
329
+ });
330
+ }
331
+ }
332
+ }
333
+ }
334
+ }
335
+ }
336
+ else {
337
+ delete project.includePaths;
338
+ }
274
339
  this.#projectIdToFolder.set(project.id, folder);
275
- if (mustReset) {
276
- this.resetProjectIds();
340
+ // Reset assigned project IDs if no projects reload is active
341
+ if (mustReset && !this.#activeReload) {
342
+ await this.rebuidProject(project.id).catch(error => {
343
+ logger.warn('Failed to rebuild project {projectId} after config change', {
344
+ projectId: project.id,
345
+ error,
346
+ });
347
+ });
277
348
  }
278
349
  return project;
279
350
  }
351
+ /**
352
+ * Determines which project the given document belongs to.
353
+ * If the document does not belong to any project, returns the default project ID.
354
+ */
280
355
  belongsTo(document) {
281
- const documentUri = normalizeUri(document);
282
- return this.findProjectForDocument(documentUri).id;
356
+ if (URI.isUri(document) || typeof document === 'string') {
357
+ const documentUri = normalizeUri(document);
358
+ return this.findProjectForDocument(documentUri).id;
359
+ }
360
+ return this.documentBelongsTo.get(document, () => {
361
+ return this.findProjectForDocument(normalizeUri(document.uri));
362
+ }).id;
283
363
  }
284
364
  #activeReload = null;
285
365
  async reloadProjects() {
@@ -306,13 +386,16 @@ export class ProjectsManager {
306
386
  return;
307
387
  }
308
388
  await this.services.workspace.WorkspaceLock.write(async (cancelToken) => {
389
+ logger.debug `start reload projects`;
309
390
  const configFiles = [];
310
391
  for (const folder of folders) {
311
392
  try {
393
+ logger.debug `scan projects in folder ${folder.uri}`;
312
394
  const files = await this.services.workspace.FileSystemProvider.scanProjectFiles(URI.parse(folder.uri));
313
395
  for (const file of files) {
314
396
  if (file.isFile && this.isConfigFile(file.uri)) {
315
- configFiles.push(file);
397
+ logger.debug `found config ${file.uri.fsPath}`;
398
+ configFiles.push(file.uri);
316
399
  }
317
400
  }
318
401
  }
@@ -324,19 +407,18 @@ export class ProjectsManager {
324
407
  logger.warning('No config files found, but some projects were registered before');
325
408
  }
326
409
  await interruptAndCheck(cancelToken);
327
- logger.debug `start reload projects`;
328
410
  this.#projects = [];
329
411
  this.#projectIdToFolder.clear();
330
- for (const entry of configFiles) {
412
+ for (const uri of configFiles) {
331
413
  try {
332
- await this.registerConfigFile(entry.uri);
414
+ await this.registerConfigFile(uri);
333
415
  }
334
416
  catch (error) {
335
- logger.error('Failed to load config file {uri}', { uri: entry.uri.toString(), error });
417
+ logger.error('Failed to load config file {uri}', { uri: uri.fsPath, error });
336
418
  }
337
419
  }
338
- this.resetProjectIds();
339
- await this.rebuidDocuments(cancelToken);
420
+ this.reset();
421
+ await this.services.workspace.WorkspaceManager.rebuildAll(cancelToken);
340
422
  });
341
423
  }
342
424
  uniqueProjectId(name) {
@@ -347,22 +429,86 @@ export class ProjectsManager {
347
429
  }
348
430
  return id;
349
431
  }
350
- resetProjectIds() {
432
+ reset() {
433
+ this.#defaultProject = undefined;
351
434
  if (this.#defaultProjectId && !this.#projectIdToFolder.has(this.#defaultProjectId)) {
352
435
  this.#defaultProjectId = undefined;
353
436
  }
437
+ this.services.workspace.LangiumDocuments.all.forEach(doc => {
438
+ if (isLikeC4Builtin(doc.uri)) {
439
+ return;
440
+ }
441
+ // Remove assigned project ID to force re-calculation
442
+ delete doc.likec4ProjectId;
443
+ });
444
+ this.documentBelongsTo.clear();
354
445
  this.mappingsToProject.clear();
355
446
  this.#excludedDocuments = new WeakMap();
356
- this.services.workspace.LangiumDocuments.resetProjectIds();
357
447
  }
358
- async rebuidDocuments(cancelToken) {
359
- await this.services.workspace.WorkspaceManager.rebuildAll(cancelToken);
448
+ async rebuidProject(projectId, cancelToken) {
449
+ if (!cancelToken) {
450
+ return await this.services.workspace.WorkspaceLock.write(async (ct) => {
451
+ await this.rebuidProject(projectId, ct);
452
+ });
453
+ }
454
+ logger.info `rebuild project ${projectId}`;
455
+ // reset default project cache
456
+ this.#defaultProject = undefined;
457
+ const project = this.#projects.find(p => p.id === projectId) ?? this.default;
458
+ if (project.id !== projectId) {
459
+ logger.warn `Project ${projectId} not found, rebuilding default project ${project.id}`;
460
+ }
461
+ const log = logger.getChild(project.id);
462
+ const folder = project.folder;
463
+ const includePathStrings = project.includePaths?.map(p => p.folder) ?? [];
464
+ const docs = this.services.workspace.LangiumDocuments
465
+ .all
466
+ .filter(doc => {
467
+ if (project.exclude?.(doc.uri.path)) {
468
+ return false;
469
+ }
470
+ const docUriStr = doc.uri.toString();
471
+ if (docUriStr.startsWith(folder)) {
472
+ return true;
473
+ }
474
+ if (includePathStrings.some(includePath => docUriStr.startsWith(includePath))) {
475
+ return true;
476
+ }
477
+ const docdir = withTrailingSlash(joinRelativeURL(docUriStr, '..'));
478
+ return docdir.startsWith(folder) || folder.startsWith(docdir);
479
+ })
480
+ .map(d => d.uri)
481
+ .toArray();
482
+ if (docs.length > 0) {
483
+ this.reset();
484
+ log.info('rebuild documents: {docs}', {
485
+ docs: docs.length,
486
+ });
487
+ await this.services.workspace.DocumentBuilder
488
+ .update(docs, [], cancelToken)
489
+ .catch(error => {
490
+ log.warn('Failed to rebuild project', {
491
+ error,
492
+ });
493
+ });
494
+ }
360
495
  }
361
496
  findProjectForDocument(documentUri) {
362
497
  return this.mappingsToProject.get(documentUri, () => {
363
498
  const project = this.#projects.find(({ folder }) => documentUri.startsWith(folder));
499
+ if (project) {
500
+ return project;
501
+ }
502
+ const projectWithInclude = this.#projects.find(({ includePaths }) => {
503
+ if (!includePaths)
504
+ return false;
505
+ return includePaths.some(includePath => documentUri.startsWith(includePath.folder));
506
+ });
507
+ if (projectWithInclude) {
508
+ return projectWithInclude;
509
+ }
364
510
  // If the document is not part of any project, assign it to the global project ID
365
- return project ?? DefaultProject;
511
+ return this.default;
366
512
  });
367
513
  }
368
514
  // The mapping between document URIs and their corresponding project ID
@@ -370,4 +516,40 @@ export class ProjectsManager {
370
516
  get mappingsToProject() {
371
517
  return memoizeProp(this, '_mappingsToProject', () => new WorkspaceCache(this.services));
372
518
  }
519
+ /**
520
+ * The mapping between documents and projects they belong to.
521
+ * Lazy-created due to initialization order of the LanguageServer
522
+ */
523
+ get documentBelongsTo() {
524
+ return memoizeProp(this, '_documentBelongsTo', () => new WorkspaceCache(this.services));
525
+ }
526
+ /**
527
+ * Returns all include paths from all projects.
528
+ * Used by WorkspaceManager to scan additional directories for C4 files.
529
+ */
530
+ getAllIncludePaths() {
531
+ const result = [];
532
+ for (const project of this.#projects) {
533
+ if (project.includePaths) {
534
+ for (const includePath of project.includePaths) {
535
+ result.push({
536
+ projectId: project.id,
537
+ includePath: includePath.uri,
538
+ includeConfig: project.includeConfig,
539
+ });
540
+ }
541
+ }
542
+ }
543
+ return result;
544
+ }
545
+ getWorkspaceFolder() {
546
+ try {
547
+ return this.services.workspace.WorkspaceManager.workspaceUri;
548
+ }
549
+ catch (error) {
550
+ logger.warn('Failed to get workspace URI, using default folder', { error });
551
+ return URI.file('/');
552
+ // ignore - workspace not initialized
553
+ }
554
+ }
373
555
  }
@@ -54,6 +54,44 @@ export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
54
54
  */
55
55
  async loadAdditionalDocuments(folders, collector) {
56
56
  collector(this.documentFactory.fromString(BuiltIn.Content, URI.parse(BuiltIn.Uri)));
57
+ // Load documents from project include paths
58
+ const includePaths = this.services.workspace.ProjectsManager.getAllIncludePaths();
59
+ let totalFilesLoaded = 0;
60
+ for (const { projectId, includePath, includeConfig } of includePaths) {
61
+ try {
62
+ logger.debug `scanning include path ${includePath.fsPath} for project ${projectId}`;
63
+ const files = await this.fileSystemProvider.readDirectory(includePath, {
64
+ recursive: true,
65
+ maxDepth: includeConfig.maxDepth,
66
+ });
67
+ let filesLoadedFromPath = 0;
68
+ for (const file of files) {
69
+ if (file.isFile && !this.services.workspace.ProjectsManager.isExcluded(file.uri)) {
70
+ const doc = await this.documentFactory.fromUri(file.uri);
71
+ collector(doc);
72
+ filesLoadedFromPath++;
73
+ totalFilesLoaded++;
74
+ }
75
+ }
76
+ logger.debug `loaded ${filesLoadedFromPath} files from include path ${includePath.fsPath}`;
77
+ }
78
+ catch (error) {
79
+ logWarnError(error);
80
+ }
81
+ }
82
+ // Warn if total files loaded exceeds threshold across all include paths
83
+ if (includePaths.length > 0 && totalFilesLoaded > 0) {
84
+ // Get the minimum threshold from all projects
85
+ const minThreshold = Math.min(...includePaths.map(p => p.includeConfig.fileThreshold));
86
+ if (totalFilesLoaded > minThreshold) {
87
+ logger.warn(`Loaded ${totalFilesLoaded} files from include paths (threshold: ${minThreshold}). ` +
88
+ 'Large include directories may slow workspace initialization. ' +
89
+ 'Consider adjusting "include.fileThreshold" or "include.maxDepth" in your project configuration.');
90
+ }
91
+ else {
92
+ logger.info `loaded ${totalFilesLoaded} total files from ${includePaths.length} include paths`;
93
+ }
94
+ }
57
95
  await super.loadAdditionalDocuments(folders, collector);
58
96
  }
59
97
  /**
@@ -75,6 +113,11 @@ export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
75
113
  return null;
76
114
  }
77
115
  async rebuildAll(cancelToken) {
116
+ if (!cancelToken) {
117
+ return await this.services.workspace.WorkspaceLock.write(async (ct) => {
118
+ await this.rebuildAll(ct);
119
+ });
120
+ }
78
121
  const docs = this.services.workspace.LangiumDocuments.all.map(d => d.uri).toArray();
79
122
  logger.info('invalidate and rebuild all {docs} documents', { docs: docs.length });
80
123
  this.services.workspace.Cache.clear();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@likec4/language-server",
3
3
  "description": "LikeC4 Language Server",
4
- "version": "1.44.0",
4
+ "version": "1.46.0",
5
5
  "license": "MIT",
6
6
  "bugs": "https://github.com/likec4/likec4/issues",
7
7
  "homepage": "https://likec4.dev",
@@ -76,48 +76,49 @@
76
76
  "access": "public"
77
77
  },
78
78
  "dependencies": {
79
- "@hpcc-js/wasm-graphviz": "1.15.0",
79
+ "@hpcc-js/wasm-graphviz": "1.16.0",
80
80
  "bundle-require": "^5.1.0",
81
- "esbuild": "0.25.11"
81
+ "esbuild": "0.27.0"
82
82
  },
83
83
  "devDependencies": {
84
- "@hono/node-server": "^1.19.5",
85
- "@modelcontextprotocol/sdk": "^1.20.1",
84
+ "@hono/node-server": "^1.19.6",
85
+ "@modelcontextprotocol/sdk": "^1.22.0",
86
86
  "@msgpack/msgpack": "^3.1.2",
87
87
  "@smithy/util-base64": "^4.3.0",
88
88
  "@types/natural-compare-lite": "^1.4.2",
89
- "@types/node": "~20.19.22",
89
+ "@types/node": "~22.19.1",
90
90
  "@types/picomatch": "^4.0.2",
91
- "@types/vscode": "^1.84.0",
91
+ "@types/vscode": "^1.106.1",
92
92
  "@types/which": "^3.0.4",
93
93
  "chokidar": "^4.0.3",
94
94
  "defu": "^6.1.4",
95
95
  "esm-env": "^1.2.2",
96
- "fast-equals": "^5.3.2",
96
+ "fast-equals": "^5.3.3",
97
97
  "fdir": "6.4.0",
98
98
  "fetch-to-node": "^2.1.0",
99
- "hono": "^4.10.2",
99
+ "hono": "^4.10.7",
100
+ "immer": "^10.2.0",
100
101
  "indent-string": "^5.0.0",
101
102
  "json5": "^2.2.3",
102
103
  "langium": "3.5.0",
103
104
  "langium-cli": "3.5.2",
104
105
  "nano-spawn": "^1.0.3",
105
106
  "natural-compare-lite": "^1.4.0",
106
- "oxlint": "1.28.0",
107
+ "oxlint": "1.31.0",
107
108
  "p-debounce": "4.0.0",
108
109
  "p-queue": "8.1.1",
109
110
  "p-timeout": "6.1.4",
110
111
  "picomatch": "^4.0.3",
111
- "pretty-ms": "^9.2.0",
112
+ "pretty-ms": "^9.3.0",
112
113
  "remeda": "^2.32.0",
113
114
  "strip-indent": "^4.1.1",
114
- "tsx": "4.20.6",
115
- "turbo": "2.6.0",
115
+ "tsx": "4.21.0",
116
+ "turbo": "2.6.3",
116
117
  "type-fest": "^4.41.0",
117
118
  "typescript": "5.9.3",
118
119
  "ufo": "1.6.1",
119
120
  "unbuild": "3.5.0",
120
- "vitest": "4.0.8",
121
+ "vitest": "4.0.15",
121
122
  "vscode-jsonrpc": "8.2.1",
122
123
  "vscode-languageserver": "9.0.1",
123
124
  "vscode-languageserver-protocol": "3.17.5",
@@ -125,13 +126,13 @@
125
126
  "vscode-uri": "3.1.0",
126
127
  "which": "^5.0.0",
127
128
  "zod": "^3.25.76",
128
- "@likec4/core": "1.44.0",
129
- "@likec4/config": "1.44.0",
130
- "@likec4/layouts": "1.44.0",
131
- "@likec4/log": "1.44.0",
132
- "@likec4/icons": "1.44.0",
133
- "@likec4/tsconfig": "1.44.0",
134
- "@likec4/devops": "1.42.0"
129
+ "@likec4/config": "1.46.0",
130
+ "@likec4/core": "1.46.0",
131
+ "@likec4/devops": "1.42.0",
132
+ "@likec4/icons": "1.46.0",
133
+ "@likec4/layouts": "1.46.0",
134
+ "@likec4/log": "1.46.0",
135
+ "@likec4/tsconfig": "1.46.0"
135
136
  },
136
137
  "scripts": {
137
138
  "typecheck": "tsc -b --verbose",