@likec4/language-server 1.38.0 → 1.39.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 +8 -0
  2. package/dist/LikeC4LanguageServices.js +25 -4
  3. package/dist/Rpc.js +11 -4
  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 +3612 -3512
  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 +3 -3
  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/LangiumDocuments.d.ts +4 -1
  55. package/dist/workspace/LangiumDocuments.js +15 -0
  56. package/dist/workspace/ProjectsManager.d.ts +25 -11
  57. package/dist/workspace/ProjectsManager.js +85 -44
  58. package/dist/workspace/WorkspaceManager.js +5 -4
  59. package/package.json +22 -28
  60. package/dist/config/index.d.ts +0 -1
  61. package/dist/config/index.js +0 -1
  62. package/dist/config/schema.d.ts +0 -10
  63. package/dist/config/schema.js +0 -39
@@ -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 {
@@ -10,23 +11,25 @@ import PQueue from "p-queue";
10
11
  import picomatch from "picomatch";
11
12
  import { hasAtLeast, isNullish, isTruthy, map, pickBy, pipe, prop, sortBy } from "remeda";
12
13
  import {
13
- hasProtocol,
14
14
  joinRelativeURL,
15
- normalizeURL,
16
15
  parseFilename,
17
16
  withoutProtocol,
18
- withProtocol,
19
17
  withTrailingSlash
20
18
  } from "ufo";
21
- import { parseConfigJson, validateConfig } from "../config/index.js";
22
19
  import { logger as mainLogger } from "../logger.js";
23
20
  const logger = mainLogger.getChild("ProjectsManager");
24
- export function ProjectFolder(folder) {
25
- if (URI.isUri(folder)) {
26
- folder = folder.toString();
21
+ function normalizeUri(uri) {
22
+ if (URI.isUri(uri)) {
23
+ return uri.toString();
24
+ } else if (typeof uri === "string") {
25
+ return uri.startsWith("file://") ? uri : URI.file(uri).toString();
26
+ } else {
27
+ return uri.uri.toString();
27
28
  }
28
- folder = hasProtocol(folder) ? folder : withProtocol(folder, "file://");
29
- return withTrailingSlash(normalizeURL(folder));
29
+ }
30
+ export function ProjectFolder(folder) {
31
+ folder = normalizeUri(folder);
32
+ return withTrailingSlash(folder);
30
33
  }
31
34
  export class ProjectsManager {
32
35
  constructor(services) {
@@ -38,11 +41,19 @@ export class ProjectsManager {
38
41
  * that are not part of a specific project.
39
42
  */
40
43
  static DefaultProjectId = "default";
41
- static ConfigFileNames = [
42
- ".likec4rc",
43
- ".likec4.config.json",
44
- "likec4.config.json"
45
- ];
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;
46
57
  /**
47
58
  * The mapping between project config files and project IDs.
48
59
  */
@@ -54,36 +65,56 @@ export class ProjectsManager {
54
65
  */
55
66
  _projects = [];
56
67
  excludedDocuments = /* @__PURE__ */ new WeakMap();
57
- defaultGlobalProject = {
58
- id: ProjectsManager.DefaultProjectId,
59
- config: {
60
- name: ProjectsManager.DefaultProjectId,
61
- exclude: ["**/node_modules/**"]
62
- },
63
- exclude: picomatch("**/node_modules/**", { dot: true })
64
- };
68
+ get defaultGlobalProject() {
69
+ return ProjectsManager.DefaultProject;
70
+ }
65
71
  reloadProjectsLimiter = new PQueue({
66
72
  concurrency: 1,
67
73
  timeout: 2e4
68
74
  });
69
75
  /**
70
76
  * Returns:
77
+ * - configured default project ID if set
71
78
  * - the default project ID if there are no projects.
72
79
  * - the ID of the only project
73
80
  * - undefined if there are multiple projects.
74
81
  */
75
82
  get defaultProjectId() {
83
+ if (this.#defaultProjectId) {
84
+ return this.#defaultProjectId;
85
+ }
76
86
  if (this._projects.length > 1) {
77
87
  return void 0;
78
88
  }
79
89
  return this._projects[0]?.id ?? ProjectsManager.DefaultProjectId;
80
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
+ }
81
104
  get all() {
82
105
  if (hasAtLeast(this._projects, 1)) {
83
- return [
106
+ const ids = [
84
107
  ...map(this._projects, prop("id")),
85
108
  ProjectsManager.DefaultProjectId
86
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;
87
118
  }
88
119
  return [ProjectsManager.DefaultProjectId];
89
120
  }
@@ -113,6 +144,11 @@ export class ProjectsManager {
113
144
  config
114
145
  };
115
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
+ */
116
152
  ensureProjectId(projectId) {
117
153
  if (projectId === ProjectsManager.DefaultProjectId) {
118
154
  return this.defaultProjectId ?? ProjectsManager.DefaultProjectId;
@@ -129,9 +165,12 @@ export class ProjectsManager {
129
165
  hasMultipleProjects() {
130
166
  return this._projects.length > 1;
131
167
  }
168
+ /**
169
+ * Checks if the specified document should be excluded from processing.
170
+ */
132
171
  checkIfExcluded(document) {
133
172
  if (typeof document === "string" || URI.isUri(document)) {
134
- let docUriAsString = typeof document === "string" ? document : document.toString();
173
+ let docUriAsString = normalizeUri(document);
135
174
  const project = this.findProjectForDocument(docUriAsString);
136
175
  return project.exclude ? project.exclude(withoutProtocol(docUriAsString)) : false;
137
176
  }
@@ -149,7 +188,7 @@ export class ProjectsManager {
149
188
  */
150
189
  isConfigFile(entry) {
151
190
  const filename = parseFilename(entry.toString(), { strict: false })?.toLowerCase();
152
- const isConfigFile = !!filename && ProjectsManager.ConfigFileNames.includes(filename);
191
+ const isConfigFile = !!filename && isLikeC4Config(filename);
153
192
  if (isConfigFile) {
154
193
  if (this.defaultGlobalProject.exclude(entry.path)) {
155
194
  logger.debug`exclude config file ${entry.path}`;
@@ -176,22 +215,25 @@ export class ProjectsManager {
176
215
 
177
216
  ${loggable(error)}`
178
217
  );
179
- 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 });
180
219
  return void 0;
181
220
  }
182
221
  }
183
222
  return void 0;
184
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
+ */
185
228
  async registerProject(opts) {
186
229
  if (URI.isUri(opts)) {
187
230
  const configFile = opts;
188
- const cfg = await this.services.workspace.FileSystemProvider.readFile(configFile);
189
- const config2 = parseConfigJson(cfg);
231
+ const config2 = await this.services.workspace.FileSystemProvider.loadProjectConfig(configFile);
190
232
  const path = joinRelativeURL(configFile.path, "..");
191
233
  const folderUri2 = configFile.with({ path });
192
234
  return await this.registerProject({ config: config2, folderUri: folderUri2 });
193
235
  }
194
- const config = pickBy(validateConfig(opts.config), isTruthy);
236
+ const config = pickBy(validateProjectConfig(opts.config), isTruthy);
195
237
  const { folderUri } = opts;
196
238
  const folder = ProjectFolder(folderUri);
197
239
  let project = this._projects.find((p) => p.folder === folder);
@@ -244,14 +286,7 @@ ${loggable(error)}`
244
286
  return project;
245
287
  }
246
288
  belongsTo(document) {
247
- let documentUri;
248
- if (typeof document === "string") {
249
- documentUri = hasProtocol(document) ? document : withProtocol(document, "file://");
250
- } else if (URI.isUri(document)) {
251
- documentUri = document.toString();
252
- } else {
253
- documentUri = document.uri.toString();
254
- }
289
+ const documentUri = normalizeUri(document);
255
290
  return this.findProjectForDocument(documentUri).id;
256
291
  }
257
292
  async reloadProjects(token) {
@@ -282,7 +317,7 @@ ${loggable(error)}`
282
317
  }
283
318
  }
284
319
  } catch (error) {
285
- logger.error("Failed to load config file", { error });
320
+ logger.error("Failed to scanProjectFiles, {folder}", { folder: folder.uri, error });
286
321
  }
287
322
  }
288
323
  if (configFiles.length === 0 && this._projects.length !== 0) {
@@ -292,15 +327,13 @@ ${loggable(error)}`
292
327
  this.projectIdToFolder.clear();
293
328
  for (const entry of configFiles) {
294
329
  try {
295
- await this.registerProject(entry.uri);
330
+ await this.loadConfigFile(entry);
296
331
  } catch (error) {
297
- logger.error("Failed to load config file", { error });
332
+ logger.error("Failed to load config file {uri}", { uri: entry.uri.toString(), error });
298
333
  }
299
334
  }
300
335
  this.resetProjectIds();
301
- const docs = this.services.workspace.LangiumDocuments.all.map((d) => d.uri).toArray();
302
- logger.info("invalidate and rebuild documents {docs}", { docs: docs.length });
303
- await this.services.workspace.DocumentBuilder.update(docs, []);
336
+ await this.rebuidDocuments();
304
337
  });
305
338
  }
306
339
  uniqueProjectId(name) {
@@ -312,10 +345,18 @@ ${loggable(error)}`
312
345
  return id;
313
346
  }
314
347
  resetProjectIds() {
348
+ if (this.#defaultProjectId && !this.projectIdToFolder.has(this.#defaultProjectId)) {
349
+ this.#defaultProjectId = void 0;
350
+ }
315
351
  this.mappingsToProject.clear();
316
352
  this.excludedDocuments = /* @__PURE__ */ new WeakMap();
317
353
  this.services.workspace.LangiumDocuments.resetProjectIds();
318
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
+ }
319
360
  findProjectForDocument(documentUri) {
320
361
  return this.mappingsToProject.get(documentUri, () => {
321
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);
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.38.0",
4
+ "version": "1.39.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.3"
23
+ "node": ">=20.19.4"
24
24
  },
25
25
  "engineStrict": true,
26
26
  "type": "module",
@@ -42,14 +42,6 @@
42
42
  "default": "./dist/browser.js"
43
43
  }
44
44
  },
45
- "./config": {
46
- "sources": "./src/config/index.ts",
47
- "default": {
48
- "types": "./dist/config/index.d.ts",
49
- "import": "./dist/config/index.js",
50
- "default": "./dist/config/index.js"
51
- }
52
- },
53
45
  "./likec4lib": {
54
46
  "sources": "./src/likec4lib.ts",
55
47
  "default": {
@@ -89,26 +81,29 @@
89
81
  "access": "public"
90
82
  },
91
83
  "dependencies": {
92
- "@hpcc-js/wasm-graphviz": "1.11.0"
84
+ "@hpcc-js/wasm-graphviz": "1.12.0",
85
+ "bundle-n-require": "^1.1.2",
86
+ "esbuild": "0.25.9"
93
87
  },
94
88
  "devDependencies": {
95
- "@types/chroma-js": "^3.1.1",
96
- "@types/natural-compare-lite": "^1.4.2",
97
- "@types/vscode": "^1.84.0",
98
- "@modelcontextprotocol/sdk": "^1.17.2",
89
+ "@hono/node-server": "^1.19.1",
90
+ "@modelcontextprotocol/sdk": "^1.17.4",
99
91
  "@msgpack/msgpack": "^3.1.2",
100
92
  "@smithy/util-base64": "^4.0.0",
101
- "@hono/node-server": "^1.14.4",
102
- "@types/node": "~20.19.2",
93
+ "@types/chroma-js": "^3.1.1",
94
+ "@types/natural-compare-lite": "^1.4.2",
95
+ "@types/node": "~20.19.11",
103
96
  "@types/picomatch": "^4.0.2",
97
+ "@types/vscode": "^1.84.0",
104
98
  "@types/which": "^3.0.4",
105
99
  "chokidar": "^4.0.3",
106
100
  "chroma-js": "^3.1.2",
107
101
  "defu": "^6.1.4",
108
102
  "esm-env": "^1.2.2",
109
- "hono": "^4.9.0",
110
103
  "fast-equals": "^5.2.2",
111
104
  "fdir": "6.4.6",
105
+ "fetch-to-node": "^2.1.0",
106
+ "hono": "^4.9.6",
112
107
  "indent-string": "^5.0.0",
113
108
  "json5": "^2.2.3",
114
109
  "langium": "3.5.0",
@@ -119,16 +114,14 @@
119
114
  "p-timeout": "6.1.4",
120
115
  "picomatch": "^4.0.3",
121
116
  "pretty-ms": "^9.2.0",
122
- "fetch-to-node": "^2.1.0",
123
117
  "remeda": "^2.23.1",
124
118
  "strip-indent": "^4.0.0",
125
119
  "tsx": "4.20.3",
126
- "turbo": "2.5.5",
120
+ "turbo": "2.5.6",
127
121
  "type-fest": "^4.41.0",
128
122
  "typescript": "5.9.2",
129
123
  "ufo": "1.6.1",
130
124
  "unbuild": "3.5.0",
131
- "valibot": "^1.1.0",
132
125
  "vitest": "3.2.4",
133
126
  "vscode-jsonrpc": "8.2.1",
134
127
  "vscode-languageserver": "9.0.1",
@@ -136,18 +129,19 @@
136
129
  "vscode-languageserver-types": "3.17.5",
137
130
  "vscode-uri": "3.1.0",
138
131
  "which": "^5.0.0",
139
- "zod": "3.25.67",
140
- "@likec4/log": "1.38.0",
141
- "@likec4/icons": "1.38.0",
142
- "@likec4/core": "1.38.0",
143
- "@likec4/tsconfig": "1.38.0",
144
- "@likec4/layouts": "1.38.0"
132
+ "zod": "3.25.76",
133
+ "@likec4/core": "1.39.0",
134
+ "@likec4/config": "1.39.0",
135
+ "@likec4/layouts": "1.39.0",
136
+ "@likec4/log": "1.39.0",
137
+ "@likec4/icons": "1.39.0",
138
+ "@likec4/tsconfig": "1.39.0"
145
139
  },
146
140
  "scripts": {
147
141
  "typecheck": "tsc -b --verbose",
148
142
  "build": "unbuild",
149
143
  "pack": "pnpm pack",
150
- "pregenerate": "rm -f src/generated/*",
144
+ "pregenerate": "rm -f src/generated/* || true",
151
145
  "watch:langium": "langium generate --watch",
152
146
  "watch:ts": "tsc --watch",
153
147
  "generate": "langium generate && tsx scripts/generate-icons.ts",
@@ -1 +0,0 @@
1
- export { parseConfigJson, ProjectConfig, validateConfig } from './schema';
@@ -1 +0,0 @@
1
- export { parseConfigJson, ProjectConfig, validateConfig } from "./schema.js";
@@ -1,10 +0,0 @@
1
- import * as v from 'valibot';
2
- export declare const ProjectConfig: v.ObjectSchema<{
3
- readonly name: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, "Project name cannot be empty">, v.ExcludesAction<string, "default", "Project name cannot be \"default\"">, v.ExcludesAction<string, ".", "Project name cannot contain \".\", try to use A-z, 0-9, _ and -">, v.ExcludesAction<string, "@", "Project name cannot contain \"@\", try to use A-z, 0-9, _ and -">, v.ExcludesAction<string, "#", "Project name cannot contain \"#\", try to use A-z, 0-9, _ and -">, v.DescriptionAction<string, "Project name, must be unique in the workspace">]>;
4
- readonly title: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, "Project title cannot be empty if specified">, v.DescriptionAction<string, "A human readable title for the project">]>, undefined>;
5
- readonly contactPerson: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.NonEmptyAction<string, "Contact person cannot be empty if specified">, v.DescriptionAction<string, "A person who has been involved in creating or maintaining this project">]>, undefined>;
6
- readonly exclude: v.OptionalSchema<v.SchemaWithPipe<readonly [v.ArraySchema<v.StringSchema<undefined>, undefined>, v.DescriptionAction<string[], "List of file patterns to exclude from the project, default is [\"**/node_modules/**\"]">]>, undefined>;
7
- }, undefined>;
8
- export type ProjectConfig = v.InferOutput<typeof ProjectConfig>;
9
- export declare function parseConfigJson(config: string): ProjectConfig;
10
- export declare function validateConfig(config: unknown): ProjectConfig;
@@ -1,39 +0,0 @@
1
- import JSON5 from "json5";
2
- import * as v from "valibot";
3
- export const ProjectConfig = v.object({
4
- name: v.pipe(
5
- v.string(),
6
- v.nonEmpty("Project name cannot be empty"),
7
- v.excludes("default", 'Project name cannot be "default"'),
8
- v.excludes(".", 'Project name cannot contain ".", try to use A-z, 0-9, _ and -'),
9
- v.excludes("@", 'Project name cannot contain "@", try to use A-z, 0-9, _ and -'),
10
- v.excludes("#", 'Project name cannot contain "#", try to use A-z, 0-9, _ and -'),
11
- v.description("Project name, must be unique in the workspace")
12
- ),
13
- title: v.optional(
14
- v.pipe(
15
- v.string(),
16
- v.nonEmpty("Project title cannot be empty if specified"),
17
- v.description("A human readable title for the project")
18
- )
19
- ),
20
- contactPerson: v.optional(
21
- v.pipe(
22
- v.string(),
23
- v.nonEmpty("Contact person cannot be empty if specified"),
24
- v.description("A person who has been involved in creating or maintaining this project")
25
- )
26
- ),
27
- exclude: v.optional(
28
- v.pipe(
29
- v.array(v.string()),
30
- v.description('List of file patterns to exclude from the project, default is ["**/node_modules/**"]')
31
- )
32
- )
33
- });
34
- export function parseConfigJson(config) {
35
- return validateConfig(JSON5.parse(config));
36
- }
37
- export function validateConfig(config) {
38
- return v.parse(ProjectConfig, config);
39
- }