@likec4/language-server 1.38.1 → 1.39.1

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 (61) hide show
  1. package/dist/LikeC4LanguageServices.d.ts +7 -0
  2. package/dist/LikeC4LanguageServices.js +25 -4
  3. package/dist/Rpc.js +9 -2
  4. package/dist/ast.d.ts +1 -1
  5. package/dist/ast.js +2 -1
  6. package/dist/bundled.d.ts +8 -0
  7. package/dist/bundled.js +40 -0
  8. package/dist/bundled.mjs +3629 -3523
  9. package/dist/filesystem/ChokidarWatcher.js +12 -9
  10. package/dist/filesystem/LikeC4FileSystem.d.ts +0 -2
  11. package/dist/filesystem/LikeC4FileSystem.js +7 -5
  12. package/dist/filesystem/index.d.ts +7 -0
  13. package/dist/filesystem/index.js +3 -0
  14. package/dist/generated/ast.d.ts +1 -0
  15. package/dist/generated/ast.js +2 -1
  16. package/dist/generated/grammar.js +1 -1
  17. package/dist/generated-lib/icons.js +1 -1
  18. package/dist/index.d.ts +1 -0
  19. package/dist/logger.js +1 -1
  20. package/dist/mcp/MCPServerFactory.js +6 -5
  21. package/dist/mcp/server/StreamableLikeC4MCPServer.d.ts +2 -2
  22. package/dist/mcp/server/StreamableLikeC4MCPServer.js +97 -100
  23. package/dist/mcp/server/WithMCPServer.d.ts +3 -1
  24. package/dist/mcp/server/WithMCPServer.js +6 -5
  25. package/dist/mcp/tools/search-element.js +26 -11
  26. package/dist/mcp/utils.js +2 -2
  27. package/dist/model/builder/MergedSpecification.d.ts +2 -2
  28. package/dist/model/builder/MergedSpecification.js +4 -7
  29. package/dist/model/builder/assignTagColors.js +1 -1
  30. package/dist/model/builder/buildModel.d.ts +3 -8
  31. package/dist/model/builder/buildModel.js +14 -11
  32. package/dist/model/model-builder.d.ts +1 -1
  33. package/dist/model/model-locator.js +2 -1
  34. package/dist/model/model-parser.d.ts +19 -46
  35. package/dist/model/model-parser.js +13 -3
  36. package/dist/model/parser/Base.d.ts +4 -7
  37. package/dist/model/parser/Base.js +19 -0
  38. package/dist/model/parser/DeploymentModelParser.d.ts +2 -5
  39. package/dist/model/parser/DeploymentViewParser.d.ts +2 -5
  40. package/dist/model/parser/FqnRefParser.d.ts +2 -5
  41. package/dist/model/parser/GlobalsParser.d.ts +2 -5
  42. package/dist/model/parser/ImportsParser.d.ts +2 -5
  43. package/dist/model/parser/ModelParser.d.ts +2 -5
  44. package/dist/model/parser/PredicatesParser.d.ts +2 -5
  45. package/dist/model/parser/SpecificationParser.d.ts +2 -5
  46. package/dist/model/parser/ViewsParser.d.ts +2 -5
  47. package/dist/protocol.d.ts +16 -2
  48. package/dist/protocol.js +4 -0
  49. package/dist/test/testServices.d.ts +5 -1
  50. package/dist/test/testServices.js +18 -3
  51. package/dist/utils/disposable.d.ts +1 -1
  52. package/dist/utils/stringHash.js +1 -1
  53. package/dist/view-utils/resolve-relative-paths.js +1 -1
  54. package/dist/workspace/ProjectsManager.d.ts +24 -10
  55. package/dist/workspace/ProjectsManager.js +72 -27
  56. package/dist/workspace/WorkspaceManager.js +5 -4
  57. package/package.json +20 -26
  58. package/dist/config/index.d.ts +0 -1
  59. package/dist/config/index.js +0 -1
  60. package/dist/config/schema.d.ts +0 -10
  61. package/dist/config/schema.js +0 -39
@@ -28,11 +28,7 @@ export declare function DeploymentViewParser<TBase extends WithExpressionV2 & Wi
28
28
  isValid: import("../../validation").IsValidFn;
29
29
  readonly services: import("../..").LikeC4Services;
30
30
  readonly doc: import("../../ast").ParsedLikeC4LangiumDocument;
31
- get project(): {
32
- id: c4.ProjectId;
33
- folderUri: c4;
34
- config: Readonly<import("../../config").ProjectConfig>;
35
- };
31
+ get project(): import("../../workspace").Project;
36
32
  resolveFqn(node: ast.FqnReferenceable): c4.Fqn;
37
33
  getAstNodePath(node: c4): any;
38
34
  getMetadata(metadataAstNode: ast.MetadataProperty | undefined): {
@@ -48,6 +44,7 @@ export declare function DeploymentViewParser<TBase extends WithExpressionV2 & Wi
48
44
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
49
45
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
50
46
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
47
+ parseImageAlias(value: string): string | undefined;
51
48
  parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
52
49
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
53
50
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
@@ -23,11 +23,7 @@ export declare function ExpressionV2Parser<TBase extends Base>(B: TBase): {
23
23
  isValid: import("../../validation").IsValidFn;
24
24
  readonly services: import("../..").LikeC4Services;
25
25
  readonly doc: import("../../ast").ParsedLikeC4LangiumDocument;
26
- get project(): {
27
- id: c4.ProjectId;
28
- folderUri: c4;
29
- config: Readonly<import("../../config").ProjectConfig>;
30
- };
26
+ get project(): import("../../workspace").Project;
31
27
  resolveFqn(node: ast.FqnReferenceable): c4.Fqn;
32
28
  getAstNodePath(node: c4): any;
33
29
  getMetadata(metadataAstNode: ast.MetadataProperty | undefined): {
@@ -43,6 +39,7 @@ export declare function ExpressionV2Parser<TBase extends Base>(B: TBase): {
43
39
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
44
40
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
45
41
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
42
+ parseImageAlias(value: string): string | undefined;
46
43
  parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
47
44
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
48
45
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
@@ -52,11 +52,7 @@ export declare function GlobalsParser<TBase extends WithViewsParser>(B: TBase):
52
52
  isValid: import("../../validation").IsValidFn;
53
53
  readonly services: import("../..").LikeC4Services;
54
54
  readonly doc: import("../../ast").ParsedLikeC4LangiumDocument;
55
- get project(): {
56
- id: c4.ProjectId;
57
- folderUri: c4;
58
- config: Readonly<import("../../config").ProjectConfig>;
59
- };
55
+ get project(): import("../../workspace").Project;
60
56
  resolveFqn(node: ast.FqnReferenceable): c4.Fqn;
61
57
  getAstNodePath(node: c4): any;
62
58
  getMetadata(metadataAstNode: ast.MetadataProperty | undefined): {
@@ -72,6 +68,7 @@ export declare function GlobalsParser<TBase extends WithViewsParser>(B: TBase):
72
68
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
73
69
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
74
70
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
71
+ parseImageAlias(value: string): string | undefined;
75
72
  parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
76
73
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
77
74
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
@@ -7,11 +7,7 @@ export declare function ImportsParser<TBase extends Base>(B: TBase): {
7
7
  isValid: import("../../validation").IsValidFn;
8
8
  readonly services: import("../..").LikeC4Services;
9
9
  readonly doc: import("../../ast").ParsedLikeC4LangiumDocument;
10
- get project(): {
11
- id: ProjectId;
12
- folderUri: ProjectId;
13
- config: Readonly<import("../../config").ProjectConfig>;
14
- };
10
+ get project(): import("../../workspace").Project;
15
11
  resolveFqn(node: ast.FqnReferenceable): ProjectId;
16
12
  getAstNodePath(node: ProjectId): any;
17
13
  getMetadata(metadataAstNode: ast.MetadataProperty | undefined): {
@@ -27,6 +23,7 @@ export declare function ImportsParser<TBase extends Base>(B: TBase): {
27
23
  convertLinks(source?: ast.LinkProperty["$container"]): ProjectId[] | undefined;
28
24
  parseLinks(source?: ast.LinkProperty["$container"]): ProjectId[] | undefined;
29
25
  parseIconProperty(prop: ast.IconProperty | undefined): ProjectId | undefined;
26
+ parseImageAlias(value: string): string | undefined;
30
27
  parseColorLiteral(astNode: ast.ColorLiteral): ProjectId | undefined;
31
28
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
32
29
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
@@ -29,11 +29,7 @@ export declare function ModelParser<TBase extends WithExpressionV2>(B: TBase): {
29
29
  isValid: import("../../validation").IsValidFn;
30
30
  readonly services: import("../..").LikeC4Services;
31
31
  readonly doc: import("../../ast").ParsedLikeC4LangiumDocument;
32
- get project(): {
33
- id: c4.ProjectId;
34
- folderUri: c4;
35
- config: Readonly<import("../../config").ProjectConfig>;
36
- };
32
+ get project(): import("../../workspace").Project;
37
33
  resolveFqn(node: ast.FqnReferenceable): c4.Fqn;
38
34
  getAstNodePath(node: c4): any;
39
35
  getMetadata(metadataAstNode: ast.MetadataProperty | undefined): {
@@ -49,6 +45,7 @@ export declare function ModelParser<TBase extends WithExpressionV2>(B: TBase): {
49
45
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
50
46
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
51
47
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
48
+ parseImageAlias(value: string): string | undefined;
52
49
  parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
53
50
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
54
51
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
@@ -34,11 +34,7 @@ export declare function PredicatesParser<TBase extends WithExpressionV2>(B: TBas
34
34
  isValid: import("../../validation").IsValidFn;
35
35
  readonly services: import("../..").LikeC4Services;
36
36
  readonly doc: import("../../ast").ParsedLikeC4LangiumDocument;
37
- get project(): {
38
- id: c4.ProjectId;
39
- folderUri: c4;
40
- config: Readonly<import("../../config").ProjectConfig>;
41
- };
37
+ get project(): import("../../workspace").Project;
42
38
  resolveFqn(node: ast.FqnReferenceable): c4.Fqn;
43
39
  getAstNodePath(node: c4): any;
44
40
  getMetadata(metadataAstNode: ast.MetadataProperty | undefined): {
@@ -54,6 +50,7 @@ export declare function PredicatesParser<TBase extends WithExpressionV2>(B: TBas
54
50
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
55
51
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
56
52
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
53
+ parseImageAlias(value: string): string | undefined;
57
54
  parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
58
55
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
59
56
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
@@ -13,11 +13,7 @@ export declare function SpecificationParser<TBase extends Base>(B: TBase): {
13
13
  isValid: import("../../validation").IsValidFn;
14
14
  readonly services: import("../..").LikeC4Services;
15
15
  readonly doc: import("../../ast").ParsedLikeC4LangiumDocument;
16
- get project(): {
17
- id: c4.ProjectId;
18
- folderUri: c4;
19
- config: Readonly<import("../../config").ProjectConfig>;
20
- };
16
+ get project(): import("../../workspace").Project;
21
17
  resolveFqn(node: ast.FqnReferenceable): c4.Fqn;
22
18
  getAstNodePath(node: c4): any;
23
19
  getMetadata(metadataAstNode: ast.MetadataProperty | undefined): {
@@ -33,6 +29,7 @@ export declare function SpecificationParser<TBase extends Base>(B: TBase): {
33
29
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
34
30
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
35
31
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
32
+ parseImageAlias(value: string): string | undefined;
36
33
  parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
37
34
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
38
35
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
@@ -50,11 +50,7 @@ export declare function ViewsParser<TBase extends WithPredicates & WithDeploymen
50
50
  isValid: import("../../validation").IsValidFn;
51
51
  readonly services: import("../..").LikeC4Services;
52
52
  readonly doc: import("../../ast").ParsedLikeC4LangiumDocument;
53
- get project(): {
54
- id: c4.ProjectId;
55
- folderUri: c4;
56
- config: Readonly<import("../../config").ProjectConfig>;
57
- };
53
+ get project(): import("../../workspace").Project;
58
54
  resolveFqn(node: ast.FqnReferenceable): c4.Fqn;
59
55
  getAstNodePath(node: c4): any;
60
56
  getMetadata(metadataAstNode: ast.MetadataProperty | undefined): {
@@ -70,6 +66,7 @@ export declare function ViewsParser<TBase extends WithPredicates & WithDeploymen
70
66
  convertLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
71
67
  parseLinks(source?: ast.LinkProperty["$container"]): c4.Link[] | undefined;
72
68
  parseIconProperty(prop: ast.IconProperty | undefined): c4.IconUrl | undefined;
69
+ parseImageAlias(value: string): string | undefined;
73
70
  parseColorLiteral(astNode: ast.ColorLiteral): c4.ColorLiteral | undefined;
74
71
  parseElementStyle(elementProps: Array<ast.ElementProperty> | ast.ElementStyleProperty | undefined): import("../../ast").ParsedElementStyle;
75
72
  parseStyleProps(styleProps: Array<ast.StyleProperty> | undefined): import("../../ast").ParsedElementStyle;
@@ -1,7 +1,7 @@
1
+ import type { LikeC4ProjectJsonConfig } from '@likec4/config';
1
2
  import type { ComputedLikeC4ModelData, ComputedView, DeploymentFqn, DiagramView, Fqn, LayoutedLikeC4ModelData, NonEmptyArray, ProjectId, RelationId, ViewChange, ViewId } from '@likec4/core';
2
3
  import { NotificationType, RequestType, RequestType0 } from 'vscode-jsonrpc';
3
4
  import type { DiagnosticSeverity, DocumentUri, Location, Position, Range, URI } from 'vscode-languageserver-types';
4
- import type { ProjectConfig } from './config';
5
5
  export declare namespace DidChangeModelNotification {
6
6
  const type: NotificationType<string>;
7
7
  type Type = typeof type;
@@ -139,7 +139,7 @@ export declare namespace FetchProjects {
139
139
  projects: {
140
140
  [projectId: ProjectId]: {
141
141
  folder: URI;
142
- config: ProjectConfig;
142
+ config: LikeC4ProjectJsonConfig;
143
143
  docs: NonEmptyArray<DocumentUri>;
144
144
  };
145
145
  };
@@ -147,6 +147,20 @@ export declare namespace FetchProjects {
147
147
  const req: RequestType0<Res, void>;
148
148
  type Req = typeof req;
149
149
  }
150
+ /**
151
+ * Request from the client to register a project.
152
+ */
153
+ export declare namespace RegisterProject {
154
+ type Params = {
155
+ folderUri: URI;
156
+ config: LikeC4ProjectJsonConfig;
157
+ };
158
+ type Res = {
159
+ id: ProjectId;
160
+ };
161
+ const req: RequestType<Params, Res, void>;
162
+ type Req = typeof req;
163
+ }
150
164
  /**
151
165
  * Request to build documents.
152
166
  */
package/dist/protocol.js CHANGED
@@ -39,6 +39,10 @@ export var FetchProjects;
39
39
  ((FetchProjects2) => {
40
40
  FetchProjects2.req = new RequestType0("likec4/fetch-projects");
41
41
  })(FetchProjects || (FetchProjects = {}));
42
+ export var RegisterProject;
43
+ ((RegisterProject2) => {
44
+ RegisterProject2.req = new RequestType("likec4/register-project");
45
+ })(RegisterProject || (RegisterProject = {}));
42
46
  export var BuildDocuments;
43
47
  ((BuildDocuments2) => {
44
48
  BuildDocuments2.Req = new RequestType("likec4/build");
@@ -1,8 +1,12 @@
1
+ import type { LikeC4ProjectJsonConfig } from '@likec4/config';
1
2
  import type { ComputedLikeC4ModelData } from '@likec4/core';
2
3
  import type { LiteralUnion } from 'type-fest';
3
4
  import { URI } from 'vscode-uri';
4
5
  import type { LikeC4LangiumDocument } from '../ast';
5
- export declare function createTestServices(workspace?: string): {
6
+ export declare function createTestServices(options?: {
7
+ workspace?: string;
8
+ projectConfig?: Partial<LikeC4ProjectJsonConfig>;
9
+ }): {
6
10
  services: any;
7
11
  addDocument: (input: string, uri?: string) => Promise<LikeC4LangiumDocument>;
8
12
  parse: (input: string, uri?: string) => Promise<LikeC4LangiumDocument>;
@@ -6,7 +6,9 @@ import { DiagnosticSeverity } from "vscode-languageserver-types";
6
6
  import { URI, Utils } from "vscode-uri";
7
7
  import { NoopFileSystem } from "../filesystem/index.js";
8
8
  import { createLanguageServices } from "../module.js";
9
- export function createTestServices(workspace = "file:///test/workspace") {
9
+ export function createTestServices(options) {
10
+ const workspace = options?.workspace ?? "file:///test/workspace";
11
+ const projectConfig = options?.projectConfig;
10
12
  const services = createLanguageServices(NoopFileSystem).likec4;
11
13
  const metaData = services.LanguageMetaData;
12
14
  const langiumDocuments = services.shared.workspace.LangiumDocuments;
@@ -15,7 +17,7 @@ export function createTestServices(workspace = "file:///test/workspace") {
15
17
  const workspaceUri = URI.parse(workspace);
16
18
  const formatter = services.lsp.Formatter;
17
19
  const workspaceFolder = {
18
- name: "test",
20
+ name: projectConfig?.name || "test-project",
19
21
  uri: workspaceUri.toString()
20
22
  };
21
23
  let isInitialized = false;
@@ -34,6 +36,19 @@ export function createTestServices(workspace = "file:///test/workspace") {
34
36
  workspaceFolders: [workspaceFolder]
35
37
  });
36
38
  await services.shared.workspace.WorkspaceManager.initializeWorkspace([workspaceFolder]);
39
+ if (projectConfig) {
40
+ const projectFolderUri = Utils.resolvePath(workspaceUri, "src");
41
+ services.shared.workspace.ProjectsManager.registerProject({
42
+ config: {
43
+ name: projectConfig?.name || "test-project",
44
+ title: projectConfig?.title || "Test Project",
45
+ contactPerson: projectConfig?.contactPerson || "Unknown",
46
+ imageAliases: projectConfig?.imageAliases || {},
47
+ exclude: projectConfig?.exclude || ["node_modules"]
48
+ },
49
+ folderUri: projectFolderUri
50
+ });
51
+ }
37
52
  });
38
53
  }
39
54
  const addDocument = async (input, uri) => {
@@ -139,7 +154,7 @@ export async function createMultiProjectTestServices(data) {
139
154
  services,
140
155
  addDocument,
141
156
  validateAll
142
- } = createTestServices(workspace);
157
+ } = createTestServices({ workspace });
143
158
  const projects = {};
144
159
  for (const [name, files] of entries(data)) {
145
160
  const folderUri = UriUtils.joinPath(URI.parse(workspace), "src", name);
@@ -1,4 +1,4 @@
1
- import { Disposable } from 'langium';
1
+ import type { Disposable } from 'langium';
2
2
  export declare abstract class ADisposable implements Disposable {
3
3
  protected toDispose: Disposable[];
4
4
  protected isDisposed: boolean;
@@ -1,4 +1,4 @@
1
- import { stringHash as hash } from "@likec4/core";
1
+ import { stringHash as hash } from "@likec4/core/utils";
2
2
  export function stringHash(...str) {
3
3
  var s = str.length > 1 ? str.join(":::") : str[0];
4
4
  return hash(s);
@@ -1,4 +1,4 @@
1
- import { compareNatural, invariant } from "@likec4/core";
1
+ import { compareNatural, invariant } from "@likec4/core/utils";
2
2
  import { filter, hasAtLeast, isTruthy, map, pipe, unique } from "remeda";
3
3
  import { parsePath } from "ufo";
4
4
  function commonAncestorPath(views, sep = "/") {
@@ -1,8 +1,7 @@
1
+ import { type LikeC4ProjectConfig } from '@likec4/config';
1
2
  import type { NonEmptyReadonlyArray, ProjectId } from '@likec4/core';
2
3
  import { type Cancellation, type FileSystemNode, type LangiumDocument, URI, WorkspaceCache } from 'langium';
3
- import picomatch from 'picomatch';
4
4
  import type { Tagged } from 'type-fest';
5
- import { ProjectConfig } from '../config';
6
5
  import type { LikeC4SharedServices } from '../module';
7
6
  /**
8
7
  * A tagged string that represents a project folder URI
@@ -12,24 +11,25 @@ export type ProjectFolder = Tagged<string, 'ProjectFolder'>;
12
11
  export declare function ProjectFolder(folder: URI | string): ProjectFolder;
13
12
  interface ProjectData {
14
13
  id: ProjectId;
15
- config: ProjectConfig;
14
+ config: LikeC4ProjectConfig;
16
15
  folder: ProjectFolder;
17
16
  folderUri: URI;
18
- exclude?: picomatch.Matcher;
17
+ exclude?: (path: string) => boolean;
19
18
  }
20
19
  export interface Project {
21
20
  id: ProjectId;
22
21
  folderUri: URI;
23
- config: ProjectConfig;
22
+ config: LikeC4ProjectConfig;
24
23
  }
25
24
  export declare class ProjectsManager {
25
+ #private;
26
26
  protected services: LikeC4SharedServices;
27
27
  /**
28
28
  * The global project ID used for all documents
29
29
  * that are not part of a specific project.
30
30
  */
31
31
  static readonly DefaultProjectId: ProjectId;
32
- static readonly ConfigFileNames: string[];
32
+ private static DefaultProject;
33
33
  /**
34
34
  * The mapping between project config files and project IDs.
35
35
  */
@@ -41,20 +41,30 @@ export declare class ProjectsManager {
41
41
  */
42
42
  private _projects;
43
43
  private excludedDocuments;
44
- private defaultGlobalProject;
44
+ private get defaultGlobalProject();
45
45
  private reloadProjectsLimiter;
46
46
  constructor(services: LikeC4SharedServices);
47
47
  /**
48
48
  * Returns:
49
+ * - configured default project ID if set
49
50
  * - the default project ID if there are no projects.
50
51
  * - the ID of the only project
51
52
  * - undefined if there are multiple projects.
52
53
  */
53
54
  get defaultProjectId(): ProjectId | undefined;
55
+ set defaultProjectId(id: ProjectId | undefined);
54
56
  get all(): NonEmptyReadonlyArray<ProjectId>;
55
57
  getProject(arg: ProjectId | LangiumDocument): Project;
58
+ /**
59
+ * Validates and ensures the project ID.
60
+ * If no project ID is specified, returns default project ID
61
+ * If there are multiple projects and default project is not set, throws an error
62
+ */
56
63
  ensureProjectId(projectId?: ProjectId | undefined): ProjectId;
57
64
  hasMultipleProjects(): boolean;
65
+ /**
66
+ * Checks if the specified document should be excluded from processing.
67
+ */
58
68
  checkIfExcluded(document: LangiumDocument | URI | string): boolean;
59
69
  /**
60
70
  * Checks if it is a config file and it is not excluded by default exclude pattern
@@ -68,15 +78,19 @@ export declare class ProjectsManager {
68
78
  * @param entry The file system entry to check
69
79
  */
70
80
  loadConfigFile(entry: FileSystemNode): Promise<ProjectData | undefined>;
71
- registerProject(configFile: URI): Promise<ProjectData>;
72
- registerProject(opts: {
73
- config: ProjectConfig;
81
+ /**
82
+ * Registers (or reloads) likec4 project by config file or config object.
83
+ * If there is some project registered at same folder, it will be reloaded.
84
+ */
85
+ registerProject(opts: URI | {
86
+ config: LikeC4ProjectConfig;
74
87
  folderUri: URI | string;
75
88
  }): Promise<ProjectData>;
76
89
  belongsTo(document: LangiumDocument | URI | string): ProjectId;
77
90
  reloadProjects(token?: Cancellation.CancellationToken): Promise<void>;
78
91
  protected uniqueProjectId(name: string): ProjectId;
79
92
  protected resetProjectIds(): void;
93
+ protected rebuidDocuments(): Promise<void>;
80
94
  protected findProjectForDocument(documentUri: string): any;
81
95
  protected get mappingsToProject(): WorkspaceCache<string, Pick<ProjectData, 'id' | 'config' | 'exclude'>>;
82
96
  }
@@ -1,4 +1,5 @@
1
- import { BiMap, delay, invariant, memoizeProp, nonNullable } from "@likec4/core";
1
+ import { isLikeC4Config, validateProjectConfig } from "@likec4/config";
2
+ import { BiMap, delay, invariant, memoizeProp, nonNullable } from "@likec4/core/utils";
2
3
  import { loggable } from "@likec4/log";
3
4
  import { deepEqual } from "fast-equals";
4
5
  import {
@@ -15,7 +16,6 @@ import {
15
16
  withoutProtocol,
16
17
  withTrailingSlash
17
18
  } from "ufo";
18
- import { parseConfigJson, validateConfig } from "../config/index.js";
19
19
  import { logger as mainLogger } from "../logger.js";
20
20
  const logger = mainLogger.getChild("ProjectsManager");
21
21
  function normalizeUri(uri) {
@@ -41,11 +41,19 @@ export class ProjectsManager {
41
41
  * that are not part of a specific project.
42
42
  */
43
43
  static DefaultProjectId = "default";
44
- static ConfigFileNames = [
45
- ".likec4rc",
46
- ".likec4.config.json",
47
- "likec4.config.json"
48
- ];
44
+ static DefaultProject = {
45
+ id: ProjectsManager.DefaultProjectId,
46
+ config: {
47
+ name: ProjectsManager.DefaultProjectId,
48
+ exclude: ["**/node_modules/**"]
49
+ },
50
+ exclude: picomatch("**/node_modules/**", { dot: true })
51
+ };
52
+ /**
53
+ * Configured default project ID.
54
+ * (it is used in CLI and Vite plugin)
55
+ */
56
+ #defaultProjectId = void 0;
49
57
  /**
50
58
  * The mapping between project config files and project IDs.
51
59
  */
@@ -57,36 +65,56 @@ export class ProjectsManager {
57
65
  */
58
66
  _projects = [];
59
67
  excludedDocuments = /* @__PURE__ */ new WeakMap();
60
- defaultGlobalProject = {
61
- id: ProjectsManager.DefaultProjectId,
62
- config: {
63
- name: ProjectsManager.DefaultProjectId,
64
- exclude: ["**/node_modules/**"]
65
- },
66
- exclude: picomatch("**/node_modules/**", { dot: true })
67
- };
68
+ get defaultGlobalProject() {
69
+ return ProjectsManager.DefaultProject;
70
+ }
68
71
  reloadProjectsLimiter = new PQueue({
69
72
  concurrency: 1,
70
73
  timeout: 2e4
71
74
  });
72
75
  /**
73
76
  * Returns:
77
+ * - configured default project ID if set
74
78
  * - the default project ID if there are no projects.
75
79
  * - the ID of the only project
76
80
  * - undefined if there are multiple projects.
77
81
  */
78
82
  get defaultProjectId() {
83
+ if (this.#defaultProjectId) {
84
+ return this.#defaultProjectId;
85
+ }
79
86
  if (this._projects.length > 1) {
80
87
  return void 0;
81
88
  }
82
89
  return this._projects[0]?.id ?? ProjectsManager.DefaultProjectId;
83
90
  }
91
+ set defaultProjectId(id) {
92
+ if (id === this.#defaultProjectId) {
93
+ return;
94
+ }
95
+ if (!id || id === ProjectsManager.DefaultProjectId) {
96
+ logger.debug`reset default project ID`;
97
+ this.#defaultProjectId = void 0;
98
+ return;
99
+ }
100
+ invariant(this._projects.find((p) => p.id === id), `Project "${id}" not found`);
101
+ logger.debug`set default project ID to ${id}`;
102
+ this.#defaultProjectId = id;
103
+ }
84
104
  get all() {
85
105
  if (hasAtLeast(this._projects, 1)) {
86
- return [
106
+ const ids = [
87
107
  ...map(this._projects, prop("id")),
88
108
  ProjectsManager.DefaultProjectId
89
109
  ];
110
+ if (this.#defaultProjectId) {
111
+ const idx = ids.findIndex((p) => p === this.#defaultProjectId);
112
+ if (idx > 0) {
113
+ const [defaultProject] = ids.splice(idx, 1);
114
+ return [defaultProject, ...ids];
115
+ }
116
+ }
117
+ return ids;
90
118
  }
91
119
  return [ProjectsManager.DefaultProjectId];
92
120
  }
@@ -116,6 +144,11 @@ export class ProjectsManager {
116
144
  config
117
145
  };
118
146
  }
147
+ /**
148
+ * Validates and ensures the project ID.
149
+ * If no project ID is specified, returns default project ID
150
+ * If there are multiple projects and default project is not set, throws an error
151
+ */
119
152
  ensureProjectId(projectId) {
120
153
  if (projectId === ProjectsManager.DefaultProjectId) {
121
154
  return this.defaultProjectId ?? ProjectsManager.DefaultProjectId;
@@ -132,6 +165,9 @@ export class ProjectsManager {
132
165
  hasMultipleProjects() {
133
166
  return this._projects.length > 1;
134
167
  }
168
+ /**
169
+ * Checks if the specified document should be excluded from processing.
170
+ */
135
171
  checkIfExcluded(document) {
136
172
  if (typeof document === "string" || URI.isUri(document)) {
137
173
  let docUriAsString = normalizeUri(document);
@@ -152,7 +188,7 @@ export class ProjectsManager {
152
188
  */
153
189
  isConfigFile(entry) {
154
190
  const filename = parseFilename(entry.toString(), { strict: false })?.toLowerCase();
155
- const isConfigFile = !!filename && ProjectsManager.ConfigFileNames.includes(filename);
191
+ const isConfigFile = !!filename && isLikeC4Config(filename);
156
192
  if (isConfigFile) {
157
193
  if (this.defaultGlobalProject.exclude(entry.path)) {
158
194
  logger.debug`exclude config file ${entry.path}`;
@@ -179,22 +215,25 @@ export class ProjectsManager {
179
215
 
180
216
  ${loggable(error)}`
181
217
  );
182
- logger.error("Failed to register project at {uri}", { uri: entry.uri.toString(), error });
218
+ logger.warn("Failed to register project at {uri}", { uri: entry.uri.toString(), error });
183
219
  return void 0;
184
220
  }
185
221
  }
186
222
  return void 0;
187
223
  }
224
+ /**
225
+ * Registers (or reloads) likec4 project by config file or config object.
226
+ * If there is some project registered at same folder, it will be reloaded.
227
+ */
188
228
  async registerProject(opts) {
189
229
  if (URI.isUri(opts)) {
190
230
  const configFile = opts;
191
- const cfg = await this.services.workspace.FileSystemProvider.readFile(configFile);
192
- const config2 = parseConfigJson(cfg);
231
+ const config2 = await this.services.workspace.FileSystemProvider.loadProjectConfig(configFile);
193
232
  const path = joinRelativeURL(configFile.path, "..");
194
233
  const folderUri2 = configFile.with({ path });
195
234
  return await this.registerProject({ config: config2, folderUri: folderUri2 });
196
235
  }
197
- const config = pickBy(validateConfig(opts.config), isTruthy);
236
+ const config = pickBy(validateProjectConfig(opts.config), isTruthy);
198
237
  const { folderUri } = opts;
199
238
  const folder = ProjectFolder(folderUri);
200
239
  let project = this._projects.find((p) => p.folder === folder);
@@ -278,7 +317,7 @@ ${loggable(error)}`
278
317
  }
279
318
  }
280
319
  } catch (error) {
281
- logger.error("Failed to load config file", { error });
320
+ logger.error("Failed to scanProjectFiles, {folder}", { folder: folder.uri, error });
282
321
  }
283
322
  }
284
323
  if (configFiles.length === 0 && this._projects.length !== 0) {
@@ -288,15 +327,13 @@ ${loggable(error)}`
288
327
  this.projectIdToFolder.clear();
289
328
  for (const entry of configFiles) {
290
329
  try {
291
- await this.registerProject(entry.uri);
330
+ await this.loadConfigFile(entry);
292
331
  } catch (error) {
293
- logger.error("Failed to load config file", { error });
332
+ logger.error("Failed to load config file {uri}", { uri: entry.uri.toString(), error });
294
333
  }
295
334
  }
296
335
  this.resetProjectIds();
297
- const docs = this.services.workspace.LangiumDocuments.all.map((d) => d.uri).toArray();
298
- logger.info("invalidate and rebuild documents {docs}", { docs: docs.length });
299
- await this.services.workspace.DocumentBuilder.update(docs, []);
336
+ await this.rebuidDocuments();
300
337
  });
301
338
  }
302
339
  uniqueProjectId(name) {
@@ -308,10 +345,18 @@ ${loggable(error)}`
308
345
  return id;
309
346
  }
310
347
  resetProjectIds() {
348
+ if (this.#defaultProjectId && !this.projectIdToFolder.has(this.#defaultProjectId)) {
349
+ this.#defaultProjectId = void 0;
350
+ }
311
351
  this.mappingsToProject.clear();
312
352
  this.excludedDocuments = /* @__PURE__ */ new WeakMap();
313
353
  this.services.workspace.LangiumDocuments.resetProjectIds();
314
354
  }
355
+ async rebuidDocuments() {
356
+ const docs = this.services.workspace.LangiumDocuments.all.map((d) => d.uri).toArray();
357
+ logger.info("invalidate and rebuild all {docs} documents", { docs: docs.length });
358
+ await this.services.workspace.DocumentBuilder.update(docs, []);
359
+ }
315
360
  findProjectForDocument(documentUri) {
316
361
  return this.mappingsToProject.get(documentUri, () => {
317
362
  const project = this._projects.find(({ folder }) => documentUri.startsWith(folder));
@@ -1,8 +1,9 @@
1
- import { hasAtLeast, invariant } from "@likec4/core";
1
+ import { invariant } from "@likec4/core";
2
2
  import { DefaultWorkspaceManager } from "langium";
3
+ import { hasAtLeast } from "remeda";
3
4
  import { URI } from "vscode-uri";
4
5
  import * as BuiltIn from "../likec4lib.js";
5
- import { logError } from "../logger.js";
6
+ import { logWarnError } from "../logger.js";
6
7
  export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
7
8
  constructor(services) {
8
9
  super(services);
@@ -29,7 +30,7 @@ export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
29
30
  configFiles.push(...found);
30
31
  this.services.workspace.FileSystemWatcher.watch(uri.fsPath);
31
32
  } catch (error) {
32
- logError(error);
33
+ logWarnError(error);
33
34
  }
34
35
  }
35
36
  const projects = this.services.workspace.ProjectsManager;
@@ -37,7 +38,7 @@ export class LikeC4WorkspaceManager extends DefaultWorkspaceManager {
37
38
  try {
38
39
  await projects.loadConfigFile(entry);
39
40
  } catch (error) {
40
- logError(error);
41
+ logWarnError(error);
41
42
  }
42
43
  }
43
44
  return await super.performStartup(folders);