@likec4/language-server 1.32.1 → 1.33.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 (51) hide show
  1. package/dist/ast.d.ts +6 -5
  2. package/dist/ast.js +3 -0
  3. package/dist/bundled.mjs +3347 -2559
  4. package/dist/config/schema.d.ts +2 -2
  5. package/dist/config/schema.js +8 -11
  6. package/dist/formatting/LikeC4Formatter.js +2 -2
  7. package/dist/generated/ast.d.ts +22 -11
  8. package/dist/generated/ast.js +19 -5
  9. package/dist/generated/grammar.js +1 -1
  10. package/dist/lsp/SemanticTokenProvider.js +1 -1
  11. package/dist/mcp/LikeC4MCPServerFactory.d.ts +4 -0
  12. package/dist/mcp/LikeC4MCPServerFactory.js +6 -0
  13. package/dist/mcp/LikeC4MCPTools.js +5 -2
  14. package/dist/mcp/sseserver/MCPServer.d.ts +4 -2
  15. package/dist/mcp/sseserver/MCPServer.js +12 -6
  16. package/dist/mcp/sseserver/with-mcp-server.js +22 -6
  17. package/dist/model/builder/MergedSpecification.js +8 -4
  18. package/dist/model/index.d.ts +1 -0
  19. package/dist/model/index.js +1 -0
  20. package/dist/model/model-builder.js +20 -19
  21. package/dist/model/model-parser.d.ts +126 -0
  22. package/dist/model/parser/Base.d.ts +30 -2
  23. package/dist/model/parser/Base.js +54 -3
  24. package/dist/model/parser/DeploymentModelParser.d.ts +14 -0
  25. package/dist/model/parser/DeploymentModelParser.js +28 -23
  26. package/dist/model/parser/DeploymentViewParser.d.ts +14 -0
  27. package/dist/model/parser/DeploymentViewParser.js +15 -6
  28. package/dist/model/parser/FqnRefParser.d.ts +14 -0
  29. package/dist/model/parser/FqnRefParser.js +8 -6
  30. package/dist/model/parser/GlobalsParser.d.ts +14 -0
  31. package/dist/model/parser/ImportsParser.d.ts +14 -0
  32. package/dist/model/parser/ModelParser.d.ts +14 -0
  33. package/dist/model/parser/ModelParser.js +27 -17
  34. package/dist/model/parser/PredicatesParser.d.ts +14 -0
  35. package/dist/model/parser/SpecificationParser.d.ts +14 -0
  36. package/dist/model/parser/SpecificationParser.js +16 -11
  37. package/dist/model/parser/ValueConverter.d.ts +4 -0
  38. package/dist/model/parser/ValueConverter.js +12 -0
  39. package/dist/model/parser/ViewsParser.d.ts +14 -0
  40. package/dist/model/parser/ViewsParser.js +21 -7
  41. package/dist/module.d.ts +6 -3
  42. package/dist/module.js +9 -5
  43. package/dist/utils/index.d.ts +5 -0
  44. package/dist/utils/index.js +19 -0
  45. package/dist/views/configurable-layouter.d.ts +2 -2
  46. package/dist/views/configurable-layouter.js +23 -27
  47. package/dist/views/likec4-views.d.ts +2 -3
  48. package/dist/views/likec4-views.js +28 -50
  49. package/dist/workspace/ProjectsManager.d.ts +0 -1
  50. package/dist/workspace/ProjectsManager.js +15 -4
  51. package/package.json +24 -23
@@ -0,0 +1,12 @@
1
+ import { DefaultValueConverter, ValueConverter } from "langium";
2
+ export class LikeC4ValueConverter extends DefaultValueConverter {
3
+ runConverter(rule, input, cstNode) {
4
+ if (rule.name === "MarkdownString") {
5
+ if (input.startsWith('"""') && input.endsWith('"""') || input.startsWith(`'''`) && input.endsWith(`'''`)) {
6
+ input = input.slice(3, -3);
7
+ }
8
+ return ValueConverter.convertString(input);
9
+ }
10
+ return super.runConverter(rule, input, cstNode);
11
+ }
12
+ }
@@ -58,6 +58,7 @@ export declare function ViewsParser<TBase extends WithPredicates & WithDeploymen
58
58
  getMetadata(metadataAstNode: ast.MetadataProperty | undefined): {
59
59
  [key: string]: string;
60
60
  } | undefined;
61
+ parseMarkdownOrString(markdownOrString: ast.MarkdownOrString | undefined): c4.MarkdownOrString | undefined;
61
62
  convertTags<E extends {
62
63
  tags?: ast.Tags;
63
64
  }>(withTags?: E | undefined): any;
@@ -70,6 +71,19 @@ export declare function ViewsParser<TBase extends WithPredicates & WithDeploymen
70
71
  parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
71
72
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
72
73
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
74
+ parseTitleDescriptionTechnology(inlineProps: {
75
+ title?: string | undefined;
76
+ description?: string | undefined;
77
+ technology?: string | undefined;
78
+ }, bodyProps: {
79
+ title?: ast.MarkdownOrString | undefined;
80
+ description?: ast.MarkdownOrString | undefined;
81
+ technology?: ast.MarkdownOrString | undefined;
82
+ }): {
83
+ title?: string;
84
+ description?: c4.MarkdownOrString;
85
+ technology?: string;
86
+ };
73
87
  parseDeploymentView(astNode: ast.DeploymentView): import("../../ast").ParsedAstDeploymentView;
74
88
  parseDeploymentViewRule(astRule: ast.DeploymentViewRule): c4.DeploymentViewRule;
75
89
  parseDeploymentViewRulePredicate(astRule: ast.DeploymentViewRulePredicate): c4.DeploymentViewPredicate;
@@ -1,8 +1,9 @@
1
1
  import * as c4 from "@likec4/core";
2
2
  import { invariant, isNonEmptyArray, nonexhaustive } from "@likec4/core";
3
- import { isArray, isDefined, isNonNullish, isTruthy } from "remeda";
3
+ import { filter, isArray, isDefined, isNonNullish, isTruthy, mapToObj, pipe } from "remeda";
4
4
  import {
5
5
  ast,
6
+ parseMarkdownAsString,
6
7
  toAutoLayout,
7
8
  toColor,
8
9
  ViewOps
@@ -71,8 +72,15 @@ export function ViewsParser(B) {
71
72
  viewOf ?? ""
72
73
  );
73
74
  }
74
- const title = toSingleLine(body.props.find((p) => p.key === "title")?.value) ?? null;
75
- const description = removeIndent(body.props.find((p) => p.key === "description")?.value) ?? null;
75
+ const { title = null, description = null } = this.parseTitleDescriptionTechnology(
76
+ {},
77
+ pipe(
78
+ body.props,
79
+ filter((p) => this.isValid(p)),
80
+ filter(ast.isViewStringProperty),
81
+ mapToObj((p) => [p.key, p.value])
82
+ )
83
+ );
76
84
  const tags = this.convertTags(body);
77
85
  const links = this.convertLinks(body);
78
86
  const manualLayout = parseViewManualLayout(astNode);
@@ -191,7 +199,7 @@ export function ViewsParser(B) {
191
199
  (e) => c4.ModelExpression.isFqnExpr(e)
192
200
  );
193
201
  const style = this.parseStyleProps(astRule.props.filter(ast.isStyleProperty));
194
- const notation = removeIndent(astRule.props.find(ast.isNotationProperty)?.value);
202
+ const notation = removeIndent(parseMarkdownAsString(astRule.props.find(ast.isNotationProperty)?.value));
195
203
  return {
196
204
  targets,
197
205
  style,
@@ -216,8 +224,14 @@ export function ViewsParser(B) {
216
224
  astPath
217
225
  );
218
226
  }
219
- const title = toSingleLine(props.find((p) => p.key === "title")?.value) ?? null;
220
- const description = removeIndent(props.find((p) => p.key === "description")?.value) ?? null;
227
+ const { title = null, description = null } = this.parseTitleDescriptionTechnology(
228
+ {},
229
+ pipe(
230
+ props,
231
+ filter(ast.isViewStringProperty),
232
+ mapToObj((p) => [p.key, p.value])
233
+ )
234
+ );
221
235
  const tags = this.convertTags(body);
222
236
  const links = this.convertLinks(body);
223
237
  ViewOps.writeId(astNode, id);
@@ -338,7 +352,7 @@ export function ViewsParser(B) {
338
352
  case ast.isNotationProperty(prop):
339
353
  case ast.isNotesProperty(prop): {
340
354
  if (isDefined(prop.value)) {
341
- step[prop.key] = removeIndent(prop.value) ?? "";
355
+ step[prop.key] = removeIndent(parseMarkdownAsString(prop.value)) ?? "";
342
356
  }
343
357
  break;
344
358
  }
package/dist/module.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { GraphvizLayouter } from '@likec4/layouts';
1
+ import { QueueGraphvizLayoter } from '@likec4/layouts';
2
2
  import { type Module, WorkspaceCache } from 'langium';
3
3
  import { type DefaultSharedModuleContext, type LangiumServices, type LangiumSharedServices, type PartialLangiumServices } from 'langium/lsp';
4
4
  import { LikeC4DocumentationProvider } from './documentation';
@@ -6,7 +6,7 @@ import { type LikeC4LanguageServices } from './LikeC4LanguageServices';
6
6
  import { LikeC4CodeLensProvider, LikeC4CompletionProvider, LikeC4DocumentHighlightProvider, LikeC4DocumentLinkProvider, LikeC4DocumentSymbolProvider, LikeC4HoverProvider, LikeC4SemanticTokenProvider } from './lsp';
7
7
  import { type LikeC4MCPServer, type LikeC4MCPServerFactory } from './mcp/LikeC4MCPServerFactory';
8
8
  import { type LikeC4MCPTools } from './mcp/LikeC4MCPTools';
9
- import { type LikeC4ModelBuilder, DeploymentsIndex, FqnIndex, LikeC4ModelLocator, LikeC4ModelParser } from './model';
9
+ import { type LikeC4ModelBuilder, DeploymentsIndex, FqnIndex, LikeC4ModelLocator, LikeC4ModelParser, LikeC4ValueConverter } from './model';
10
10
  import { LikeC4ModelChanges } from './model-change/ModelChanges';
11
11
  import { LikeC4NameProvider, LikeC4ScopeComputation, LikeC4ScopeProvider } from './references';
12
12
  import { Rpc } from './Rpc';
@@ -43,7 +43,7 @@ export interface LikeC4AddedServices {
43
43
  likec4: {
44
44
  LanguageServices: LikeC4LanguageServices;
45
45
  Views: LikeC4Views;
46
- Layouter: GraphvizLayouter;
46
+ Layouter: QueueGraphvizLayoter;
47
47
  DeploymentsIndex: DeploymentsIndex;
48
48
  FqnIndex: FqnIndex;
49
49
  ModelParser: LikeC4ModelParser;
@@ -66,6 +66,9 @@ export interface LikeC4AddedServices {
66
66
  ScopeProvider: LikeC4ScopeProvider;
67
67
  };
68
68
  shared?: LikeC4SharedServices;
69
+ parser: {
70
+ ValueConverter: LikeC4ValueConverter;
71
+ };
69
72
  }
70
73
  export type LikeC4Services = LangiumServices & LikeC4AddedServices;
71
74
  export declare const LikeC4Module: Module<LikeC4Services, PartialLangiumServices & LikeC4AddedServices>;
package/dist/module.js CHANGED
@@ -1,4 +1,4 @@
1
- import { GraphvizLayouter, GraphvizWasmAdapter } from "@likec4/layouts";
1
+ import { GraphvizWasmAdapter, QueueGraphvizLayoter } from "@likec4/layouts";
2
2
  import {
3
3
  DocumentState,
4
4
  EmptyFileSystem,
@@ -16,7 +16,6 @@ import {
16
16
  LikeC4GeneratedSharedModule
17
17
  } from "./generated/module.js";
18
18
  import { DefaultLikeC4LanguageServices } from "./LikeC4LanguageServices.js";
19
- import { logger } from "./logger.js";
20
19
  import {
21
20
  LikeC4CodeLensProvider,
22
21
  LikeC4CompletionProvider,
@@ -36,7 +35,8 @@ import {
36
35
  DeploymentsIndex,
37
36
  FqnIndex,
38
37
  LikeC4ModelLocator,
39
- LikeC4ModelParser
38
+ LikeC4ModelParser,
39
+ LikeC4ValueConverter
40
40
  } from "./model/index.js";
41
41
  import { LikeC4ModelChanges } from "./model-change/ModelChanges.js";
42
42
  import {
@@ -87,8 +87,9 @@ export const LikeC4Module = {
87
87
  likec4: {
88
88
  LanguageServices: bind(DefaultLikeC4LanguageServices),
89
89
  Layouter: (_services) => {
90
- logger.debug("Creating GraphvizLayouter with GraphvizWasmAdapter");
91
- return new GraphvizLayouter(new GraphvizWasmAdapter());
90
+ return new QueueGraphvizLayoter({
91
+ graphviz: new GraphvizWasmAdapter()
92
+ });
92
93
  },
93
94
  Views: bind(DefaultLikeC4Views),
94
95
  DeploymentsIndex: bind(DeploymentsIndex),
@@ -116,6 +117,9 @@ export const LikeC4Module = {
116
117
  NameProvider: bind(LikeC4NameProvider),
117
118
  ScopeComputation: bind(LikeC4ScopeComputation),
118
119
  ScopeProvider: bind(LikeC4ScopeProvider)
120
+ },
121
+ parser: {
122
+ ValueConverter: bind(LikeC4ValueConverter)
119
123
  }
120
124
  };
121
125
  export function createCustomLanguageServices(context, module, module2, module3) {
@@ -4,3 +4,8 @@ export * from './fqnRef';
4
4
  export * from './projectId';
5
5
  export * from './stringHash';
6
6
  export declare function safeCall<T>(fn: () => T): T | undefined;
7
+ export declare function performanceNow(): number;
8
+ export declare function performanceMark(): {
9
+ readonly ms: number;
10
+ readonly pretty: string;
11
+ };
@@ -1,3 +1,4 @@
1
+ import prettyMs from "pretty-ms";
1
2
  import { logger } from "../logger.js";
2
3
  export * from "./disposable.js";
3
4
  export * from "./elementRef.js";
@@ -12,3 +13,21 @@ export function safeCall(fn) {
12
13
  return void 0;
13
14
  }
14
15
  }
16
+ export function performanceNow() {
17
+ try {
18
+ return performance.now();
19
+ } catch (e) {
20
+ return Date.now();
21
+ }
22
+ }
23
+ export function performanceMark() {
24
+ const t0 = performanceNow();
25
+ return {
26
+ get ms() {
27
+ return performanceNow() - t0;
28
+ },
29
+ get pretty() {
30
+ return prettyMs(performanceNow() - t0);
31
+ }
32
+ };
33
+ }
@@ -1,7 +1,7 @@
1
- import { GraphvizLayouter } from '@likec4/layouts';
1
+ import { QueueGraphvizLayoter } from '@likec4/layouts';
2
2
  import type { LikeC4Services } from '../module';
3
3
  export declare const ConfigurableLayouter: {
4
4
  likec4: {
5
- Layouter(services: LikeC4Services): GraphvizLayouter;
5
+ Layouter(services: LikeC4Services): QueueGraphvizLayoter;
6
6
  };
7
7
  };
@@ -1,4 +1,4 @@
1
- import { GraphvizLayouter, GraphvizWasmAdapter } from "@likec4/layouts";
1
+ import { GraphvizWasmAdapter, QueueGraphvizLayoter } from "@likec4/layouts";
2
2
  import { GraphvizBinaryAdapter } from "@likec4/layouts/graphviz/binary";
3
3
  import { isEmpty } from "remeda";
4
4
  import which from "which";
@@ -15,39 +15,35 @@ export const ConfigurableLayouter = {
15
15
  likec4: {
16
16
  Layouter(services) {
17
17
  logger.debug("Creating ConfigurableLayouter");
18
- const wasmAdapter = new GraphvizWasmAdapter();
19
- const layouter = new GraphvizLayouter(wasmAdapter);
20
- const langId = services.LanguageMetaData.languageId;
18
+ const layouter = new QueueGraphvizLayoter();
21
19
  services.shared.workspace.ConfigurationProvider.onConfigurationSectionUpdate((update) => {
22
20
  logger.debug("Configuration update: {update}", { update });
23
- if (update.section === langId) {
24
- try {
25
- const { mode, path } = update.configuration.graphviz ?? {
26
- mode: "wasm",
27
- path: ""
28
- };
29
- if (mode === "wasm") {
30
- layouter.changePort(wasmAdapter);
31
- logger.info("use graphviz wasm");
32
- return;
33
- }
21
+ if (update.section !== services.LanguageMetaData.languageId) {
22
+ logger.debug(`Ignoring configuration update as it is not for ${services.LanguageMetaData.languageId}`);
23
+ return;
24
+ }
25
+ try {
26
+ const { mode, path } = update.configuration.graphviz ?? {
27
+ mode: "wasm",
28
+ path: ""
29
+ };
30
+ if (mode !== "wasm") {
34
31
  let binaryPath = isEmpty(path) ? graphvizBinPath() : path;
35
- if (binaryPath === null) {
36
- layouter.changePort(wasmAdapter);
37
- logger.warn(`No Graphviz binaries found on PATH, use graphviz wasm`);
38
- services.shared.lsp.Connection?.window.showWarningMessage(
39
- "No Graphviz binaries found on PATH, set path to binaries in settings."
40
- );
32
+ if (!isEmpty(binaryPath)) {
33
+ layouter.changePort(new GraphvizBinaryAdapter(binaryPath));
34
+ logger.info`use graphviz binary: ${binaryPath}`;
41
35
  return;
42
36
  }
43
- layouter.changePort(new GraphvizBinaryAdapter(binaryPath));
44
- logger.info`use graphviz binary: ${binaryPath}`;
45
- } catch (error) {
46
- logger.error("Failed to update configuration", { error });
37
+ logger.warn(`No Graphviz binaries found on PATH, use graphviz wasm`);
38
+ services.shared.lsp.Connection?.window.showWarningMessage(
39
+ "No Graphviz binaries found on PATH, set path to binaries in settings."
40
+ );
47
41
  }
48
- return;
42
+ layouter.changePort(new GraphvizWasmAdapter());
43
+ logger.info("use graphviz wasm");
44
+ } catch (error) {
45
+ logger.error("Failed to update configuration", { error });
49
46
  }
50
- logger.warn("Unexpected configuration update: {update}", { update });
51
47
  });
52
48
  return layouter;
53
49
  }
@@ -1,5 +1,5 @@
1
1
  import type { ComputedView, DiagramView, ProjectId, ViewId } from '@likec4/core';
2
- import { GraphvizLayouter } from '@likec4/layouts';
2
+ import { type QueueGraphvizLayoter, GraphvizLayouter } from '@likec4/layouts';
3
3
  import { CancellationToken } from 'vscode-jsonrpc';
4
4
  import type { LikeC4Services } from '../module';
5
5
  export type GraphvizOut = {
@@ -24,9 +24,8 @@ export declare class DefaultLikeC4Views implements LikeC4Views {
24
24
  private cache;
25
25
  private viewsWithReportedErrors;
26
26
  private ModelBuilder;
27
- private queue;
28
27
  constructor(services: LikeC4Services);
29
- get layouter(): GraphvizLayouter;
28
+ get layouter(): QueueGraphvizLayoter;
30
29
  computedViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<ComputedView[]>;
31
30
  layoutAllViews(projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<Array<Readonly<GraphvizOut>>>;
32
31
  layoutView(viewId: ViewId, projectId?: ProjectId | undefined, cancelToken?: CancellationToken): Promise<GraphvizOut | null>;
@@ -1,9 +1,9 @@
1
1
  import { loggable } from "@likec4/log";
2
- import PQueue from "p-queue";
3
- import prettyMs from "pretty-ms";
4
2
  import { values } from "remeda";
5
3
  import { CancellationToken } from "vscode-jsonrpc";
6
4
  import { logError, logger as rootLogger, logWarnError } from "../logger.js";
5
+ import { performanceMark } from "../utils/index.js";
6
+ const viewsLogger = rootLogger.getChild("views");
7
7
  export class DefaultLikeC4Views {
8
8
  constructor(services) {
9
9
  this.services = services;
@@ -12,7 +12,6 @@ export class DefaultLikeC4Views {
12
12
  cache = /* @__PURE__ */ new WeakMap();
13
13
  viewsWithReportedErrors = /* @__PURE__ */ new Set();
14
14
  ModelBuilder;
15
- queue = new PQueue({ concurrency: 2, timeout: 2e4, throwOnTimeout: true });
16
15
  get layouter() {
17
16
  return this.services.likec4.Layouter;
18
17
  }
@@ -26,16 +25,10 @@ export class DefaultLikeC4Views {
26
25
  if (views.length === 0) {
27
26
  return [];
28
27
  }
29
- const logger = rootLogger.getChild(["views", projectId ?? ""]);
28
+ const m0 = performanceMark();
29
+ const logger = projectId ? viewsLogger.getChild(projectId) : viewsLogger;
30
30
  logger.debug`layoutAll: ${views.length} views`;
31
- if (this.queue.pending + this.queue.size > 0) {
32
- logger.debug`wait for previous layouts to finish`;
33
- await this.queue.onIdle();
34
- }
35
- if (this.layouter.port.concurrency !== this.queue.concurrency) {
36
- this.queue.concurrency = this.layouter.port.concurrency;
37
- logger.debug`set queue concurrency to ${this.layouter.port.concurrency}`;
38
- }
31
+ const tasks = [];
39
32
  const specification = likeC4Model.$data.specification;
40
33
  const results = [];
41
34
  for (const view of views) {
@@ -45,40 +38,35 @@ export class DefaultLikeC4Views {
45
38
  results.push(cached);
46
39
  continue;
47
40
  }
48
- if (this.queue.pending > this.queue.concurrency + 4) {
49
- await this.queue.onSizeLessThan(this.queue.concurrency + 1);
50
- }
51
- this.queue.add(async () => {
52
- logger.debug`layouting view ${view.id}...`;
53
- return await this.layouter.layout({
54
- view,
55
- specification
56
- });
57
- }).then((result) => {
58
- if (!result) {
59
- throw new Error(`Layout queue returned null for view ${view.id}`);
41
+ tasks.push({
42
+ view,
43
+ specification
44
+ });
45
+ }
46
+ if (tasks.length > 0) {
47
+ await this.layouter.batchLayout({
48
+ batch: tasks,
49
+ onSuccess: (task, result) => {
50
+ this.viewsWithReportedErrors.delete(task.view.id);
51
+ this.cache.set(task.view, result);
52
+ results.push(result);
53
+ },
54
+ onError: (task, error) => {
55
+ logger.warn(`Fail layout view ${task.view.id}`, { error });
60
56
  }
61
- this.viewsWithReportedErrors.delete(view.id);
62
- logger.debug`done layout view ${view.id}`;
63
- this.cache.set(view, result);
64
- results.push(result);
65
- }).catch((e) => {
66
- logger.error(`Fail layout view ${view.id}`, { e });
67
- this.cache.delete(view);
68
57
  });
69
58
  }
70
- await this.queue.onIdle();
71
59
  if (results.length !== views.length) {
72
- logger.warn`layouted ${results.length} of ${views.length} views`;
60
+ logger.warn`layouted ${results.length} of ${views.length} views in ${m0.pretty}`;
73
61
  } else if (results.length > 0) {
74
- logger.debug`layouted all ${results.length} views`;
62
+ logger.debug`layouted all ${results.length} views in ${m0.pretty}`;
75
63
  }
76
64
  return results;
77
65
  }
78
66
  async layoutView(viewId, projectId, cancelToken = CancellationToken.None) {
79
67
  const model = await this.ModelBuilder.buildLikeC4Model(projectId, cancelToken);
80
68
  const view = model.findView(viewId)?.$view;
81
- const logger = rootLogger.getChild(["views", projectId ?? ""]);
69
+ const logger = projectId ? viewsLogger.getChild(projectId) : viewsLogger;
82
70
  if (!view) {
83
71
  logger.warn`layoutView ${viewId} not found`;
84
72
  return null;
@@ -89,24 +77,14 @@ export class DefaultLikeC4Views {
89
77
  return await Promise.resolve(cached);
90
78
  }
91
79
  try {
92
- const start = performance.now();
93
- if (this.queue.pending + this.queue.size > 0) {
94
- logger.debug`wait for previous layouts to finish`;
95
- await this.queue.onIdle();
96
- }
97
- const result = await this.queue.add(async () => {
98
- logger.debug`layouting view ${view.id}...`;
99
- return await this.layouter.layout({
100
- view,
101
- specification: model.$data.specification
102
- });
80
+ const m0 = performanceMark();
81
+ const result = await this.layouter.layout({
82
+ view,
83
+ specification: model.$data.specification
103
84
  });
104
- if (!result) {
105
- throw new Error(`Failed to layout view ${viewId}`);
106
- }
107
85
  this.viewsWithReportedErrors.delete(viewId);
108
86
  this.cache.set(view, result);
109
- logger.debug(`layout {viewId} ready in ${prettyMs(performance.now() - start)}`, { viewId });
87
+ logger.debug(`layout {viewId} ready in ${m0.pretty}`, { viewId });
110
88
  return result;
111
89
  } catch (e) {
112
90
  if (!this.viewsWithReportedErrors.has(viewId)) {
@@ -50,7 +50,6 @@ export declare class ProjectsManager {
50
50
  * Checks if the provided file system entry is a valid project config file.
51
51
  *
52
52
  * @param entry The file system entry to check
53
- * @returns {boolean} Returns true if the entry is a valid config file, false otherwise.
54
53
  */
55
54
  loadConfigFile(entry: FileSystemNode): Promise<Project | undefined>;
56
55
  registerProject(configFile: URI): Promise<Project>;
@@ -1,4 +1,5 @@
1
1
  import { BiMap, invariant, nonNullable } from "@likec4/core";
2
+ import { loggable } from "@likec4/log";
2
3
  import { URI, WorkspaceCache } from "langium";
3
4
  import picomatch from "picomatch/posix";
4
5
  import { hasAtLeast, isNullish, map, pipe, prop, sortBy } from "remeda";
@@ -9,7 +10,7 @@ import {
9
10
  withoutProtocol,
10
11
  withProtocol
11
12
  } from "ufo";
12
- import { parseConfigJson } from "../config/index.js";
13
+ import { parseConfigJson, validateConfig } from "../config/index.js";
13
14
  import { logger as mainLogger } from "../logger.js";
14
15
  const logger = mainLogger.getChild("ProjectsManager");
15
16
  export class ProjectsManager {
@@ -115,14 +116,23 @@ export class ProjectsManager {
115
116
  * Checks if the provided file system entry is a valid project config file.
116
117
  *
117
118
  * @param entry The file system entry to check
118
- * @returns {boolean} Returns true if the entry is a valid config file, false otherwise.
119
119
  */
120
120
  async loadConfigFile(entry) {
121
121
  if (entry.isDirectory) {
122
122
  return void 0;
123
123
  }
124
124
  if (this.isConfigFile(entry)) {
125
- return await this.registerProject(entry.uri);
125
+ try {
126
+ return await this.registerProject(entry.uri);
127
+ } catch (error) {
128
+ this.services.lsp.Connection?.window.showErrorMessage(
129
+ `LikeC4: Failed to register project at ${entry.uri.toString()}
130
+
131
+ ${loggable(error)}`
132
+ );
133
+ logger.error("Failed to register project at {uri}", { uri: entry.uri.toString(), error });
134
+ return void 0;
135
+ }
126
136
  }
127
137
  return void 0;
128
138
  }
@@ -135,7 +145,8 @@ export class ProjectsManager {
135
145
  const folderUri2 = configFile.with({ path });
136
146
  return this.registerProject({ config: config2, folderUri: folderUri2 });
137
147
  }
138
- const { config, folderUri } = opts;
148
+ const config = validateConfig(opts.config);
149
+ const { folderUri } = opts;
139
150
  let id = config.name;
140
151
  let i = 1;
141
152
  while (this.projectIdToFolder.has(id)) {
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.32.1",
4
+ "version": "1.33.0",
5
5
  "license": "MIT",
6
6
  "bugs": "https://github.com/likec4/likec4/issues",
7
7
  "homepage": "https://likec4.dev",
@@ -20,7 +20,7 @@
20
20
  "directory": "packages/language-server"
21
21
  },
22
22
  "engines": {
23
- "node": ">=20.19.1"
23
+ "node": ">=20.19.3"
24
24
  },
25
25
  "engineStrict": true,
26
26
  "type": "module",
@@ -93,50 +93,50 @@
93
93
  },
94
94
  "devDependencies": {
95
95
  "@types/chroma-js": "^3.1.1",
96
- "@modelcontextprotocol/sdk": "^1.12.1",
97
- "@msgpack/msgpack": "^3.1.1",
96
+ "@modelcontextprotocol/sdk": "^1.13.1",
97
+ "@msgpack/msgpack": "^3.1.2",
98
98
  "@smithy/util-base64": "^4.0.0",
99
- "@types/express": "^5.0.2",
100
- "@types/node": "~20.19.0",
99
+ "@types/express": "^5.0.3",
100
+ "@types/node": "~20.19.1",
101
101
  "@types/picomatch": "^4.0.0",
102
102
  "@types/which": "^3.0.4",
103
103
  "chroma-js": "^3.1.2",
104
104
  "esm-env": "^1.2.2",
105
105
  "express": "^5.1.0",
106
106
  "fast-equals": "^5.2.2",
107
- "fdir": "^6.4.4",
107
+ "fdir": "6.4.6",
108
108
  "indent-string": "^5.0.0",
109
109
  "json5": "^2.2.3",
110
110
  "langium": "3.5.0",
111
111
  "langium-cli": "3.5.0",
112
112
  "natural-compare-lite": "^1.4.0",
113
- "p-debounce": "^4.0.0",
114
- "p-queue": "^8.1.0",
115
- "p-timeout": "^6.1.4",
113
+ "p-debounce": "4.0.0",
114
+ "p-queue": "8.1.0",
115
+ "p-timeout": "6.1.4",
116
116
  "picomatch": "^4.0.2",
117
117
  "pretty-ms": "^9.2.0",
118
- "remeda": "^2.23.0",
118
+ "remeda": "^2.23.1",
119
119
  "strip-indent": "^4.0.0",
120
- "tsx": "~4.20.1",
121
- "turbo": "^2.5.4",
120
+ "tsx": "4.19.4",
121
+ "turbo": "2.5.4",
122
122
  "type-fest": "^4.41.0",
123
- "typescript": "^5.8.3",
123
+ "typescript": "5.8.3",
124
124
  "ufo": "^1.6.1",
125
- "unbuild": "^3.5.0",
126
- "valibot": "^1.0.0",
127
- "vitest": "3.2.3",
125
+ "unbuild": "3.5.0",
126
+ "valibot": "^1.1.0",
127
+ "vitest": "3.2.4",
128
128
  "vscode-jsonrpc": "8.2.0",
129
129
  "vscode-languageserver": "9.0.1",
130
130
  "vscode-languageserver-protocol": "3.17.5",
131
131
  "vscode-languageserver-types": "3.17.5",
132
132
  "vscode-uri": "3.1.0",
133
133
  "which": "^5.0.0",
134
- "zod": "^3.24.3",
135
- "@likec4/core": "1.32.1",
136
- "@likec4/icons": "1.32.1",
137
- "@likec4/layouts": "1.32.1",
138
- "@likec4/tsconfig": "1.32.1",
139
- "@likec4/log": "1.32.1"
134
+ "zod": "3.25.67",
135
+ "@likec4/core": "1.33.0",
136
+ "@likec4/layouts": "1.33.0",
137
+ "@likec4/log": "1.33.0",
138
+ "@likec4/tsconfig": "1.33.0",
139
+ "@likec4/icons": "1.33.0"
140
140
  },
141
141
  "scripts": {
142
142
  "typecheck": "tsc -b --verbose",
@@ -148,6 +148,7 @@
148
148
  "generate": "langium generate && tsx scripts/generate-icons.ts",
149
149
  "dev": "run-p \"watch:*\"",
150
150
  "lint": "run -T eslint src/ --fix",
151
+ "lint:package": "pnpx publint ./package.tgz",
151
152
  "clean": "pnpm rimraf dist contrib lib src/generated src/generated-lib",
152
153
  "test": "vitest run --no-isolate",
153
154
  "test-dbg": "vitest run --no-isolate -t formating",