@likec4/language-server 1.58.0 → 1.59.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.
@@ -1,5 +1,5 @@
1
- import { B as LikeC4ManualLayoutsModuleContext, C as ViewLocateResult, D as LangiumDocuments, E as LikeC4WorkspaceManager, F as FileSystemModuleContext, H as Project, I as FileSystemProvider, L as FileSystemWatcher, M as NoFileSystemWatcher, N as NoLikeC4ManualLayouts, R as FileSystemWatcherModuleContext, S as LikeC4ModelLocator, T as FqnIndex, U as ProjectData, W as ProjectsManager, _ as LikeC4ViewsModuleContext, b as LayoutViewParams, d as LikeC4LanguageServices, f as DocumentParser, j as NoFileSystem, n as LikeC4AddedServices, o as LikeC4SharedServices, p as LikeC4ModelParser, r as LikeC4Services, s as createLanguageServices, t as LanguageServicesContext, u as FormatOptions, w as LikeC4ModelBuilder, x as LikeC4Views, y as WithWasmGraphviz, z as LikeC4ManualLayouts } from "../_chunks/module.mjs";
2
-
1
+ import { t as __name } from "../chunks/rolldown-runtime.mjs";
2
+ import { B as LikeC4ManualLayoutsModuleContext, C as ViewLocateResult, D as LangiumDocuments, E as LikeC4WorkspaceManager, F as FileSystemModuleContext, H as Project, I as FileSystemProvider, L as FileSystemWatcher, M as NoFileSystemWatcher, N as NoLikeC4ManualLayouts, R as FileSystemWatcherModuleContext, S as LikeC4ModelLocator, T as FqnIndex, U as ProjectData, W as ProjectsManager, _ as LikeC4ViewsModuleContext, b as LayoutViewParams, d as LikeC4LanguageServices, f as DocumentParser, j as NoFileSystem, n as LikeC4AddedServices, o as LikeC4SharedServices, p as LikeC4ModelParser, r as LikeC4Services, s as createLanguageServices, t as LanguageServicesContext, u as FormatOptions, w as LikeC4ModelBuilder, x as LikeC4Views, y as WithWasmGraphviz, z as LikeC4ManualLayouts } from "../chunks/module.mjs";
3
3
  //#region src/browser/index.d.ts
4
4
  /**
5
5
  * Starts the LikeC4 language server in the browser, connected via the given port (e.g. a worker).
@@ -11,4 +11,4 @@ declare function startLanguageServer(port: MessagePort | DedicatedWorkerGlobalSc
11
11
  likec4: LikeC4Services;
12
12
  };
13
13
  //#endregion
14
- export { DocumentParser, FileSystemModuleContext, FileSystemProvider, FileSystemWatcher, FileSystemWatcherModuleContext, FormatOptions, FqnIndex, LangiumDocuments, LanguageServicesContext, LayoutViewParams, LikeC4AddedServices, LikeC4LanguageServices, LikeC4ManualLayouts, LikeC4ManualLayoutsModuleContext, LikeC4ModelBuilder, LikeC4ModelLocator, LikeC4ModelParser, LikeC4Services, LikeC4SharedServices, LikeC4Views, LikeC4ViewsModuleContext, LikeC4WorkspaceManager, NoFileSystem, NoFileSystemWatcher, NoLikeC4ManualLayouts, Project, ProjectData, ProjectsManager, ViewLocateResult, WithWasmGraphviz, createLanguageServices, startLanguageServer };
14
+ export { type DocumentParser, type FileSystemModuleContext, type FileSystemProvider, type FileSystemWatcher, type FileSystemWatcherModuleContext, type FormatOptions, type FqnIndex, type LangiumDocuments, type LanguageServicesContext, type LayoutViewParams, type LikeC4AddedServices, type LikeC4LanguageServices, type LikeC4ManualLayouts, type LikeC4ManualLayoutsModuleContext, type LikeC4ModelBuilder, type LikeC4ModelLocator, type LikeC4ModelParser, type LikeC4Services, type LikeC4SharedServices, type LikeC4Views, type LikeC4ViewsModuleContext, type LikeC4WorkspaceManager, NoFileSystem, NoFileSystemWatcher, NoLikeC4ManualLayouts, type Project, type ProjectData, type ProjectsManager, type ViewLocateResult, type WithWasmGraphviz, createLanguageServices, startLanguageServer };
@@ -1 +1,32 @@
1
- import{ai as e,oi as t,si as n}from"../_chunks/utils.mjs";import{t as r}from"../_chunks/module.mjs";import"../_chunks/common-exports.mjs";import{configureLogger as i,getConsoleSink as a,getTextFormatter as o}from"@likec4/log";import{startLanguageServer as s}from"langium/lsp";import{BrowserMessageReader as c,BrowserMessageWriter as l,createConnection as u}from"vscode-languageserver/browser";function startLanguageServer(e){let t=u(new c(e),new l(e));i({sinks:{console:a({formatter:o({format:({level:e,category:t,message:n})=>`${e} ${t} ${n}`})})},loggers:[{category:`likec4`,sinks:[`console`],lowestLevel:`debug`}]});let n=r({connection:t});return s(n.shared),n}export{e as NoFileSystem,t as NoFileSystemWatcher,n as NoLikeC4ManualLayouts,r as createLanguageServices,startLanguageServer};
1
+ import { t as __name } from "../chunks/rolldown-runtime.mjs";
2
+ import { Lr as NoFileSystem, Rr as NoFileSystemWatcher, zr as NoLikeC4ManualLayouts } from "../chunks/utils.mjs";
3
+ import { t as createLanguageServices } from "../chunks/module.mjs";
4
+ import { configureLogger, getConsoleSink, getTextFormatter } from "@likec4/log";
5
+ import { startLanguageServer as startLanguageServer$1 } from "langium/lsp";
6
+ import { BrowserMessageReader, BrowserMessageWriter, createConnection } from "vscode-languageserver/browser.js";
7
+
8
+ //#region src/browser/index.ts
9
+ /**
10
+ * Starts the LikeC4 language server in the browser, connected via the given port (e.g. a worker).
11
+ * @param port - MessagePort or DedicatedWorkerGlobalScope for LSP communication.
12
+ * @returns Shared and LikeC4-specific services after the connection is initialized.
13
+ */
14
+ function startLanguageServer(port) {
15
+ const connection = createConnection(new BrowserMessageReader(port), new BrowserMessageWriter(port));
16
+ configureLogger({
17
+ sinks: { console: getConsoleSink({ formatter: getTextFormatter({ format: ({ level, category, message }) => {
18
+ return `${level} ${category} ${message}`;
19
+ } }) }) },
20
+ loggers: [{
21
+ category: "likec4",
22
+ sinks: ["console"],
23
+ lowestLevel: "debug"
24
+ }]
25
+ });
26
+ const services = createLanguageServices({ connection });
27
+ startLanguageServer$1(services.shared);
28
+ return services;
29
+ }
30
+
31
+ //#endregion
32
+ export { NoFileSystem, NoFileSystemWatcher, NoLikeC4ManualLayouts, createLanguageServices, startLanguageServer };
@@ -1 +1,40 @@
1
- import{startLanguageServer as e}from"./index.mjs";function errToString(e){switch(!0){case e instanceof Error:return e.message;case typeof e==`object`&&!!e:try{return JSON.stringify(e)}catch{return`[unserializable value]`}case typeof e==`string`:return e;case e==null:return``;case typeof e==`number`||typeof e==`boolean`:return String(e);case typeof e==`symbol`:return e.toString();default:return`unknown`}}const log=(e,t)=>{let n=t==null?e:`${e} ${errToString(t)}`;try{console.error(`[LikeC4 LSP worker]`,n)}catch{}};self.onerror=e=>(log(`Uncaught error`,e.message??e.error),!1),self.onunhandledrejection=e=>{log(`Unhandled rejection`,e.reason)};try{e(self)}catch(e){throw log(`Failed to start language server`,e),e}
1
+ import { t as __name } from "../chunks/rolldown-runtime.mjs";
2
+ import { startLanguageServer } from "./index.mjs";
3
+
4
+ //#region src/browser/worker.ts
5
+ function errToString(err) {
6
+ switch (true) {
7
+ case err instanceof Error: return err.message;
8
+ case typeof err === "object" && err !== null: try {
9
+ return JSON.stringify(err);
10
+ } catch {
11
+ return "[unserializable value]";
12
+ }
13
+ case typeof err === "string": return err;
14
+ case err === null || err === void 0: return "";
15
+ case typeof err === "number" || typeof err === "boolean": return String(err);
16
+ case typeof err === "symbol": return err.toString();
17
+ default: return "unknown";
18
+ }
19
+ }
20
+ const log = (msg, err) => {
21
+ const line = err === void 0 || err === null ? msg : `${msg} ${errToString(err)}`;
22
+ try {
23
+ console.error("[LikeC4 LSP worker]", line);
24
+ } catch {}
25
+ };
26
+ self.onerror = (event) => {
27
+ log("Uncaught error", event.message ?? event.error);
28
+ return false;
29
+ };
30
+ self.onunhandledrejection = (event) => {
31
+ log("Unhandled rejection", event.reason);
32
+ };
33
+ try {
34
+ startLanguageServer(self);
35
+ } catch (err) {
36
+ log("Failed to start language server", err);
37
+ throw err;
38
+ }
39
+
40
+ //#endregion
@@ -0,0 +1,537 @@
1
+ import { t as __name } from "./rolldown-runtime.mjs";
2
+ import { t as LibIcons } from "./icons.mjs";
3
+ import { isLikeC4Builtin } from "../likec4lib.mjs";
4
+ import { Fr as compareByUri, Hr as logger$1, Ir as compareByUriDeepFirst, Pr as ADisposable, Rr as NoFileSystemWatcher, a as LikeC4LanguageMetaData, n as safeCall } from "./utils.mjs";
5
+ import { compareNaturalHierarchically, onNextTick, stringHash } from "@likec4/core/utils";
6
+ import { isLikeC4Config, loadConfig } from "@likec4/config/node";
7
+ import { fdir } from "fdir";
8
+ import { Disposable, SimpleCache, URI, UriUtils } from "langium";
9
+ import { NodeFileSystemProvider } from "langium/node";
10
+ import { mkdirSync, statSync } from "node:fs";
11
+ import { unlink, writeFile } from "node:fs/promises";
12
+ import { basename, dirname } from "node:path";
13
+ import { indexBy, prop } from "remeda";
14
+ import { exact } from "@likec4/core";
15
+ import chokidar from "chokidar";
16
+ import PQueue from "p-queue";
17
+ import JSON5 from "json5";
18
+ import pLimit from "p-limit";
19
+ import { Position, Range } from "vscode-languageserver-types";
20
+
21
+ //#region src/filesystem/LikeC4ManualLayouts.ts
22
+ const layoutsLogger = logger$1.getChild("manual-layouts");
23
+ const extension = ".likec4.snap";
24
+ /**
25
+ * @todo sync with vscode extension watchers
26
+ * (search for ".likec4.snap" references)
27
+ */
28
+ const isManualLayoutFile = (path) => path !== extension && path.endsWith(extension);
29
+ function fileName(view) {
30
+ return `${view}${extension}`;
31
+ }
32
+ function viewIdFromURI(uri) {
33
+ const fileName = UriUtils.basename(uri);
34
+ if (!isManualLayoutFile(fileName)) return null;
35
+ return fileName.slice(0, -12);
36
+ }
37
+ function getManualLayoutsOutDir(project) {
38
+ return UriUtils.resolvePath(project.folderUri, project.config.manualLayouts?.outDir ?? ".likec4");
39
+ }
40
+ const WithLikeC4ManualLayouts = { manualLayouts: (services) => new DefaultLikeC4ManualLayouts(services) };
41
+ const RELATIVE_PATH_PREFIX = "file://./";
42
+ var DefaultLikeC4ManualLayouts = class extends ADisposable {
43
+ services;
44
+ cache;
45
+ listeners = [];
46
+ #limit = pLimit(1);
47
+ constructor(services) {
48
+ super();
49
+ this.services = services;
50
+ this.cache = new SimpleCache();
51
+ this.onDispose(Disposable.create(() => {
52
+ this.listeners.length = 0;
53
+ this.cache.clear();
54
+ }));
55
+ onNextTick(() => {
56
+ this.onDispose(services.workspace.ProjectsManager.onProjectsUpdate(() => {
57
+ this.clearCaches();
58
+ }));
59
+ });
60
+ }
61
+ async handleFileSystemUpdate(event) {
62
+ const uri = event.update ?? event.delete;
63
+ let viewId = viewIdFromURI(uri) ?? void 0;
64
+ const projectId = this.services.workspace.ProjectsManager.ownerProjectId(uri);
65
+ this.cache.delete(projectId);
66
+ if ("delete" in event) {
67
+ this.triggerUpdate(exact({
68
+ removed: uri,
69
+ projectId,
70
+ viewId
71
+ }));
72
+ return;
73
+ }
74
+ const project = this.services.workspace.ProjectsManager.getProject(projectId);
75
+ if (!viewId) {
76
+ const snapshot = await this.readSnapshot(uri, project);
77
+ if (snapshot) viewId = snapshot.id;
78
+ else {
79
+ layoutsLogger.error(`Snapshot ${uri.fsPath} does not exist or is invalid`);
80
+ viewId = "index";
81
+ }
82
+ }
83
+ this.triggerUpdate({
84
+ updated: uri,
85
+ projectId,
86
+ viewId
87
+ });
88
+ }
89
+ onManualLayoutUpdate(listener) {
90
+ this.listeners.push(listener);
91
+ return { dispose: () => {
92
+ const index = this.listeners.indexOf(listener);
93
+ if (index !== -1) this.listeners.splice(index, 1);
94
+ } };
95
+ }
96
+ async readManualLayouts(project) {
97
+ const logger = layoutsLogger.getChild(project.id);
98
+ const fs = this.services.workspace.FileSystemProvider;
99
+ const outDir = getManualLayoutsOutDir(project);
100
+ const manualLayouts = [];
101
+ let hash = `${outDir.toString()}`;
102
+ try {
103
+ const files = await fs.scanDirectory(outDir, isManualLayoutFile);
104
+ if (files.length === 0) return null;
105
+ files.sort((a, b) => a.uri.path.localeCompare(b.uri.path));
106
+ for (const file of files) try {
107
+ const content = await fs.readFile(file.uri);
108
+ const parsed = JSON5.parse(content);
109
+ hash = stringHash(hash + file.uri.toString() + content);
110
+ const resolved = this.resolveIconPathsAfterRead(parsed, project.folderUri);
111
+ manualLayouts.push({
112
+ ...resolved,
113
+ _layout: "manual"
114
+ });
115
+ } catch (err) {
116
+ logger.warn(`Failed to read view snapshot ${file.uri.fsPath}`, { err });
117
+ }
118
+ if (manualLayouts.length) logger.trace`read manual layouts for ${project.id}, found ${manualLayouts.length}`;
119
+ } catch (err) {
120
+ logger.warn(`Failed to read manual layouts for ${project.folderUri.fsPath}`, { err });
121
+ }
122
+ if (manualLayouts.length === 0) return null;
123
+ const views = indexBy(manualLayouts, prop("id"));
124
+ return {
125
+ hash,
126
+ views
127
+ };
128
+ }
129
+ async readSnapshot(uri, project) {
130
+ const fs = this.services.workspace.FileSystemProvider;
131
+ try {
132
+ const content = await fs.readFile(uri);
133
+ const parsed = JSON5.parse(content);
134
+ if (!project) {
135
+ const projectId = this.services.workspace.ProjectsManager.ownerProjectId(uri);
136
+ project = this.services.workspace.ProjectsManager.getProject(projectId);
137
+ }
138
+ return {
139
+ ...this.resolveIconPathsAfterRead(parsed, project.folderUri),
140
+ _layout: "manual"
141
+ };
142
+ } catch (err) {
143
+ layoutsLogger.warn(`Failed to read view snapshot ${uri.fsPath}`, { err });
144
+ return null;
145
+ }
146
+ }
147
+ async read(project) {
148
+ return await this.#limit(async () => {
149
+ const cached = this.cache.get(project.id);
150
+ if (cached !== void 0) {
151
+ layoutsLogger.trace`cache hit project ${project.id}`;
152
+ return cached;
153
+ }
154
+ const result = await this.readManualLayouts(project);
155
+ this.cache.set(project.id, result);
156
+ return result;
157
+ });
158
+ }
159
+ async write(project, layouted) {
160
+ this.cache.delete(project.id);
161
+ const logger = layoutsLogger.getChild(project.id);
162
+ const outDir = getManualLayoutsOutDir(project);
163
+ const file = UriUtils.joinPath(outDir, fileName(layouted.id));
164
+ if ("manualLayout" in layouted) {
165
+ const { manualLayout: _, ...rest } = layouted;
166
+ layouted = rest;
167
+ }
168
+ const content = JSON5.stringify(this.normalizeIconPathsForWrite(layouted, project.folderUri), {
169
+ space: 2,
170
+ quote: "'"
171
+ });
172
+ const location = {
173
+ uri: file.toString(),
174
+ range: Range.create(Position.create(0, 0), Position.create(content.split("\n").length - 1, 1))
175
+ };
176
+ logger.debug`write snapshot of ${layouted.id} in project ${project.id} to ${file.fsPath}`;
177
+ const fs = this.services.workspace.FileSystemProvider;
178
+ try {
179
+ await fs.writeFile(file, content + "\n");
180
+ this.triggerUpdate({
181
+ updated: file,
182
+ projectId: project.id,
183
+ viewId: layouted.id
184
+ });
185
+ } catch (err) {
186
+ logger.warn(`Failed to write snapshot ${layouted.id} to ${file.fsPath}`, { err });
187
+ }
188
+ return location;
189
+ }
190
+ async remove(project, view) {
191
+ this.cache.delete(project.id);
192
+ const logger = layoutsLogger.getChild(project.id);
193
+ const outDir = getManualLayoutsOutDir(project);
194
+ const file = UriUtils.joinPath(outDir, fileName(view));
195
+ logger.debug`delete snapshot of ${view} in project ${project.id}. File: ${file.fsPath}`;
196
+ const location = {
197
+ uri: file.toString(),
198
+ range: Range.create(0, 0, 0, 0)
199
+ };
200
+ try {
201
+ if (!await this.services.workspace.FileSystemProvider.deleteFile(file)) {
202
+ logger.warn`Snapshot ${view} did not exist at ${file.fsPath}`;
203
+ return null;
204
+ }
205
+ this.triggerUpdate({
206
+ removed: file,
207
+ projectId: project.id,
208
+ viewId: view
209
+ });
210
+ } catch (err) {
211
+ logger.warn(`Failed to delete snapshot ${view} from ${file.fsPath}`, { err });
212
+ }
213
+ return location;
214
+ }
215
+ clearCaches() {
216
+ layoutsLogger.trace`clear caches`;
217
+ this.cache.clear();
218
+ }
219
+ triggerUpdate(event) {
220
+ const listeners = [...this.listeners];
221
+ for (const listener of listeners) safeCall(() => listener(event));
222
+ }
223
+ /**
224
+ * When we save snapshot - it may contain fullpath to icons on the machine it was created,
225
+ * that is wrong when opened on another.
226
+ *
227
+ * Prepares a snapshot for writing by converting absolute icon paths to relative paths.
228
+ * Absolute paths starting with 'file://' are converted to relative paths prefixed with 'file://./'
229
+ */
230
+ normalizeIconPathsForWrite(layouted, projectUri) {
231
+ const nodes = layouted.nodes.map((node) => {
232
+ if (!node.icon || typeof node.icon !== "string") return node;
233
+ if (node.icon.startsWith("file://")) {
234
+ const iconUri = URI.parse(node.icon);
235
+ const relativePath = UriUtils.relative(projectUri, iconUri);
236
+ if (relativePath.startsWith("..")) return node;
237
+ return {
238
+ ...node,
239
+ icon: `${RELATIVE_PATH_PREFIX}${relativePath}`
240
+ };
241
+ }
242
+ return node;
243
+ });
244
+ return {
245
+ ...layouted,
246
+ nodes
247
+ };
248
+ }
249
+ /**
250
+ * Postprocesses a snapshot after reading by converting relative icon paths back to absolute paths.
251
+ * Relative paths prefixed with 'file://./' are converted to absolute paths based on project folder.
252
+ */
253
+ resolveIconPathsAfterRead(layouted, projectUri) {
254
+ const nodes = layouted.nodes.map((node) => {
255
+ if (!node.icon || typeof node.icon !== "string") return node;
256
+ if (node.icon.startsWith(RELATIVE_PATH_PREFIX)) {
257
+ const relativePath = node.icon.substring(9);
258
+ const absoluteUri = UriUtils.joinPath(projectUri, relativePath);
259
+ return {
260
+ ...node,
261
+ icon: absoluteUri.toString()
262
+ };
263
+ }
264
+ return node;
265
+ });
266
+ return {
267
+ ...layouted,
268
+ nodes
269
+ };
270
+ }
271
+ };
272
+
273
+ //#endregion
274
+ //#region src/filesystem/utils.ts
275
+ function hasLikeC4Ext(path) {
276
+ return LikeC4LanguageMetaData.fileExtensions.some((ext) => path !== ext && path.endsWith(ext));
277
+ }
278
+ const nodeModulesOrRepo = [
279
+ "node_modules",
280
+ ".git",
281
+ ".svn",
282
+ ".yarn",
283
+ ".pnpm"
284
+ ];
285
+ /**
286
+ * Check if the given directory name is a node_modules or repository directory
287
+ */
288
+ function isNodeModulesOrRepo(basename) {
289
+ return nodeModulesOrRepo.includes(basename);
290
+ }
291
+ function insideNodeModulesOrRepo(path) {
292
+ for (const dir of nodeModulesOrRepo) if (path.includes(dir)) return true;
293
+ return false;
294
+ }
295
+
296
+ //#endregion
297
+ //#region src/filesystem/ChokidarWatcher.ts
298
+ const logger = logger$1.getChild("chokidar");
299
+ const WithChokidarWatcher = { fileSystemWatcher: (services) => new ChokidarFileSystemWatcher(services) };
300
+ const isAnyLikeC4File = (path) => {
301
+ const filename = basename(path);
302
+ return hasLikeC4Ext(filename) || isLikeC4Config(filename) || isManualLayoutFile(filename);
303
+ };
304
+ /**
305
+ * A no-op file system watcher.
306
+ */
307
+ var ChokidarFileSystemWatcher = class {
308
+ services;
309
+ watcher;
310
+ queue = new PQueue({
311
+ concurrency: 1,
312
+ timeout: 5e3
313
+ });
314
+ constructor(services) {
315
+ this.services = services;
316
+ logger.trace`ChokidarFileSystemWatcher created`;
317
+ }
318
+ watch(folder) {
319
+ if (this.watcher) {
320
+ logger.debug`add watching folder: ${folder}`;
321
+ this.watcher.add(folder);
322
+ return;
323
+ }
324
+ this.watcher = this.createWatcher(folder);
325
+ }
326
+ async dispose() {
327
+ if (this.watcher) {
328
+ const watcher = this.watcher;
329
+ this.watcher = void 0;
330
+ try {
331
+ await watcher.close();
332
+ } catch {}
333
+ }
334
+ }
335
+ createWatcher(folder) {
336
+ logger.debug`create watcher for folder: ${folder}`;
337
+ let watcher = chokidar.watch(folder, {
338
+ ignored: [
339
+ (path) => insideNodeModulesOrRepo(path),
340
+ (path, stats) => !!stats && stats.isFile() && !isAnyLikeC4File(path),
341
+ (path) => this.services.workspace.ProjectsManager.isExcluded(URI.file(path))
342
+ ],
343
+ followSymlinks: true,
344
+ ignoreInitial: true
345
+ });
346
+ const onAddOrChange = (path, stats) => {
347
+ if (stats?.isDirectory()) return;
348
+ this.enqueueFileOp("addOrChange: " + path, async () => {
349
+ await this.onAddOrChange(path);
350
+ });
351
+ };
352
+ const onRemove = (path, stats) => {
353
+ if (stats?.isDirectory()) return;
354
+ this.enqueueFileOp("remove: " + path, async () => {
355
+ await this.onRemove(path);
356
+ });
357
+ };
358
+ watcher.on("add", onAddOrChange).on("change", onAddOrChange).on("unlink", onRemove).on("unlinkDir", (path) => {
359
+ this.enqueueFileOp("removeDir: " + path, async () => {
360
+ await this.onRemoveDir(path);
361
+ });
362
+ });
363
+ return watcher;
364
+ }
365
+ enqueueFileOp(fileop, fn) {
366
+ this.queue.add(async () => {
367
+ try {
368
+ await fn();
369
+ } catch (error) {
370
+ logger.warn(`Failed on {fileop}`, {
371
+ fileop,
372
+ error
373
+ });
374
+ }
375
+ }).catch((error) => {
376
+ logger.error(`Error on {fileop}`, {
377
+ fileop,
378
+ error
379
+ });
380
+ });
381
+ }
382
+ async onAddOrChange(path) {
383
+ const workspace = this.services.workspace;
384
+ const filename = basename(path);
385
+ const uri = URI.file(path);
386
+ switch (true) {
387
+ case isLikeC4Config(filename):
388
+ logger.debug`project config changed: ${path}`;
389
+ workspace.ManualLayouts.clearCaches();
390
+ await workspace.ProjectsManager.registerConfigFile(uri);
391
+ break;
392
+ case isManualLayoutFile(filename):
393
+ logger.debug`manual layout file changed: ${path}`;
394
+ await workspace.ManualLayouts.handleFileSystemUpdate({ update: uri });
395
+ break;
396
+ case hasLikeC4Ext(filename):
397
+ logger.debug`file changed: ${path}`;
398
+ await workspace.DocumentBuilder.update([uri], []);
399
+ break;
400
+ default: logger.warn`Unknown file change: ${path}`;
401
+ }
402
+ }
403
+ async onRemove(path) {
404
+ const workspace = this.services.workspace;
405
+ const filename = basename(path);
406
+ const uri = URI.file(path);
407
+ switch (true) {
408
+ case isLikeC4Config(filename):
409
+ logger.debug`project file removed: ${path}`;
410
+ workspace.ManualLayouts.clearCaches();
411
+ await workspace.ProjectsManager.reloadProjects();
412
+ break;
413
+ case hasLikeC4Ext(filename):
414
+ logger.debug`file removed: ${path}`;
415
+ await workspace.DocumentBuilder.update([], [uri]);
416
+ break;
417
+ case isManualLayoutFile(filename):
418
+ logger.debug`manual layout file removed: ${path}`;
419
+ await workspace.ManualLayouts.handleFileSystemUpdate({ delete: uri });
420
+ break;
421
+ default: logger.warn`Unknown file removal: ${path}`;
422
+ }
423
+ }
424
+ async onRemoveDir(path) {
425
+ logger.debug`directory removed: ${path}`;
426
+ const workspace = this.services.workspace;
427
+ if (workspace.ProjectsManager.findOverlaped(path).length > 0) {
428
+ workspace.ManualLayouts.clearCaches();
429
+ await workspace.ProjectsManager.reloadProjects();
430
+ }
431
+ }
432
+ };
433
+
434
+ //#endregion
435
+ //#region src/filesystem/LikeC4FileSystem.ts
436
+ function isLikeC4ConfigFile(path, isDirectory = false) {
437
+ return !isDirectory && isLikeC4Config(basename(path));
438
+ }
439
+ function isLikeC4File(path, isDirectory = false) {
440
+ return !isDirectory && hasLikeC4Ext(basename(path));
441
+ }
442
+ const WithFileSystem = (enableWatcher = false) => ({
443
+ fileSystemProvider: () => new SymLinkTraversingFileSystemProvider(),
444
+ ...enableWatcher ? WithChokidarWatcher : NoFileSystemWatcher
445
+ });
446
+ /**
447
+ * A file system provider that follows symbolic links.
448
+ * @see https://github.com/likec4/likec4/pull/1213
449
+ */
450
+ var SymLinkTraversingFileSystemProvider = class extends NodeFileSystemProvider {
451
+ #logger = logger$1.getChild("filesystem");
452
+ async readFile(uri) {
453
+ if (isLikeC4Builtin(uri)) return Promise.resolve(LibIcons);
454
+ try {
455
+ return await super.readFile(uri);
456
+ } catch (error) {
457
+ this.#logger.warn(`Failed to read file ${uri.fsPath}`, { error });
458
+ return "";
459
+ }
460
+ }
461
+ async readDirectory(folderPath, opts) {
462
+ const recursive = opts?.recursive ?? true;
463
+ const maxDepth = opts?.maxDepth ?? Infinity;
464
+ const entries = [];
465
+ try {
466
+ let crawler = new fdir().withSymlinks({ resolvePaths: false }).exclude(isNodeModulesOrRepo).withFullPaths().filter(isLikeC4File);
467
+ if (!recursive) crawler = crawler.withMaxDepth(1);
468
+ else if (maxDepth !== Infinity) crawler = crawler.withMaxDepth(maxDepth);
469
+ const crawled = await crawler.crawl(folderPath.fsPath).withPromise();
470
+ for (const path of crawled) entries.push({
471
+ isFile: true,
472
+ isDirectory: false,
473
+ uri: URI.file(path)
474
+ });
475
+ return entries.sort(compareByUri);
476
+ } catch (error) {
477
+ this.#logger.warn(`Failed to read directory ${folderPath.fsPath}`, { error });
478
+ return [];
479
+ }
480
+ }
481
+ async scanProjectFiles(folderUri) {
482
+ const files = await this.scanDirectory(folderUri, isLikeC4ConfigFile);
483
+ if (files.length <= 1) return files;
484
+ return files.sort(compareByUriDeepFirst);
485
+ }
486
+ async scanDirectory(directory, filter) {
487
+ const entries = [];
488
+ try {
489
+ const crawled = await new fdir().withSymlinks({ resolvePaths: false }).exclude(isNodeModulesOrRepo).withFullPaths().filter(filter).crawl(directory.fsPath).withPromise();
490
+ for (const path of crawled) entries.push({
491
+ isFile: true,
492
+ isDirectory: false,
493
+ uri: URI.file(path)
494
+ });
495
+ } catch (error) {
496
+ this.#logger.warn(`Failed to scan directory {path}`, {
497
+ path: directory.fsPath,
498
+ error
499
+ });
500
+ }
501
+ return entries;
502
+ }
503
+ async loadProjectConfig(filepath) {
504
+ return await loadConfig(filepath);
505
+ }
506
+ async writeFile(uri, content) {
507
+ const dir = dirname(uri.fsPath);
508
+ const exists = statSync(dir, { throwIfNoEntry: false });
509
+ if (exists?.isFile()) throw new Error(`Cannot create directory ${dir} because a file with the same name exists.`);
510
+ if (!exists) {
511
+ this.#logger.debug("creating directory {path}", { path: dir });
512
+ mkdirSync(dir, { recursive: true });
513
+ }
514
+ this.#logger.debug("writing file {path}", { path: uri.fsPath });
515
+ return await writeFile(uri.fsPath, content, { encoding: "utf-8" });
516
+ }
517
+ async deleteFile(uri) {
518
+ try {
519
+ const path = uri.fsPath;
520
+ const exists = statSync(path, { throwIfNoEntry: false });
521
+ if (exists?.isFile() || exists?.isSymbolicLink()) {
522
+ await unlink(path);
523
+ this.#logger.debug("deleted file {path}", { path });
524
+ return true;
525
+ } else {
526
+ this.#logger.warn("deleteFile failed: {path} does not exist, or is not a file", { path });
527
+ return false;
528
+ }
529
+ } catch (error) {
530
+ this.#logger.warn(`Failed to delete file ${uri.fsPath}`, { error });
531
+ }
532
+ return false;
533
+ }
534
+ };
535
+
536
+ //#endregion
537
+ export { WithChokidarWatcher as n, WithLikeC4ManualLayouts as r, WithFileSystem as t };
@@ -1,3 +1,4 @@
1
+ import { t as __name } from "./rolldown-runtime.mjs";
1
2
  //#region src/generated-lib/icons.d.ts
2
3
  declare const groupedIcons: {
3
4
  readonly aws: readonly ["activate", "alexa-for-business", "amplify", "apache-mxnet-on-aws", "api-gateway", "app-config", "app-flow", "app-mesh", "app-runner", "app-stream", "app-sync", "application-auto-scaling", "application-composer", "application-cost-profiler", "application-discovery-service", "application-migration-service", "artifact", "athena", "audit-manager", "augmented-ai-a2i", "aurora", "auto-scaling", "backint-agent", "backup", "batch", "billing-conductor", "bottlerocket", "braket", "budgets", "certificate-manager", "chatbot", "chime-sdk", "chime-voice-connector", "chime", "clean-rooms", "client-vpn", "cloud-control-api", "cloud-development-kit", "cloud-directory", "cloud-formation", "cloud-front", "cloud-hsm", "cloud-map", "cloud-search", "cloud-shell", "cloud-trail", "cloud-wan", "cloud-watch", "cloud9", "code-artifact", "code-build", "code-catalyst", "code-commit", "code-deploy", "code-guru", "code-pipeline", "code-star", "code-whisperer", "cognito", "command-line-interface", "comprehend-medical", "comprehend", "compute-optimizer", "config", "connect", "console-mobile-application", "control-tower", "corretto", "cost-and-usage-report", "cost-explorer", "data-exchange", "data-pipeline", "data-sync", "data-zone", "database-migration-service", "deep-composer", "deep-learning-amis", "deep-learning-containers", "deep-lens", "deep-racer", "detective", "dev-ops-guru", "device-farm", "direct-connect", "directory-service", "distro-for-open-telemetry", "document-db", "dynamo-db", "ec2-auto-scaling", "ec2-image-builder", "ec2", "ecs-anywhere", "efs", "eks-anywhere", "eks-cloud", "eks-distro", "elasti-cache", "elastic-beanstalk", "elastic-block-store", "elastic-container-registry", "elastic-container-service", "elastic-disaster-recovery", "elastic-fabric-adapter", "elastic-inference", "elastic-kubernetes-service", "elastic-load-balancing", "elastic-transcoder", "elemental-appliances-software", "elemental-conductor", "elemental-delta", "elemental-link", "elemental-live", "elemental-media-connect", "elemental-media-convert", "elemental-media-live", "elemental-media-package", "elemental-media-store", "elemental-media-tailor", "elemental-server", "emr", "event-bridge", "express-workflows", "fargate", "fault-injection-simulator", "file-cache", "fin-space", "firewall-manager", "forecast", "fraud-detector", "free-rtos", "fsx-for-lustre", "fsx-for-net-app-ontap", "fsx-for-open-zfs", "fsx-for-wfs", "fsx", "game-kit", "game-lift", "game-sparks", "genomics-cli", "global-accelerator", "glue-data-brew", "glue-elastic-views", "glue", "ground-station", "guard-duty", "health-lake", "honeycode", "iam-identity-center", "identity-and-access-management", "inspector", "interactive-video-service", "io-t-1-click", "io-t-analytics", "io-t-button", "io-t-core", "io-t-device-defender", "io-t-device-management", "io-t-edu-kit", "io-t-events", "io-t-express-link", "io-t-fleet-wise", "io-t-greengrass", "io-t-robo-runner", "io-t-site-wise", "io-t-things-graph", "io-t-twin-maker", "iq", "kendra", "key-management-service", "keyspaces", "kinesis-data-analytics", "kinesis-data-streams", "kinesis-firehose", "kinesis-video-streams", "kinesis", "lake-formation", "lambda", "launch-wizard", "lex", "license-manager", "lightsail", "local-zones", "location-service", "lookout-for-equipment", "lookout-for-metrics", "lookout-for-vision", "lumberyard", "macie", "mainframe-modernization", "managed-blockchain", "managed-grafana", "managed-service-for-prometheus", "managed-services", "managed-streaming-for-apache-kafka", "managed-workflows-for-apache-airflow", "management-console", "marketplace-dark", "marketplace-light", "memory-db-for-redis", "migration-evaluator", "migration-hub", "monitron", "mq", "neptune", "network-firewall", "neuron", "nice-dcv", "nice-engin-frame", "nimble-studio", "nitro-enclaves", "omics", "open-3d-engine", "open-search-service", "ops-works", "organizations", "outposts-family", "outposts-rack", "outposts-servers", "panorama", "parallel-cluster", "personal-health-dashboard", "personalize", "pinpoint-apis", "pinpoint", "polly", "private-5g", "private-certificate-authority", "private-link", "professional-services", "proton", "quantum-ledger-database", "quick-sight", "rds-on-vmware", "rds", "re-post", "red-hat-open-shift-service-on-aws", "redshift", "rekognition", "reserved-instance-reporting", "resilience-hub", "resource-access-manager", "resource-explorer", "robo-maker", "route-53", "s3-on-outposts", "sage-maker-ground-truth", "sage-maker-studio-lab", "sage-maker", "savings-plans", "secrets-manager", "security-hub", "security-lake", "server-migration-service", "serverless-application-repository", "service-catalog", "service-management-connector", "shield", "signer", "sim-space-weaver", "simple-email-service", "simple-notification-service", "simple-queue-service", "simple-storage-service-glacier", "simple-storage-service", "site-to-site-vpn", "snowball-edge", "snowball", "snowcone", "snowmobile", "step-functions", "storage-gateway", "sumerian", "supply-chain", "support", "systems-manager", "tensor-flow-on-aws", "textract", "thinkbox-deadline", "thinkbox-frost", "thinkbox-krakatoa", "thinkbox-sequoia", "thinkbox-stoke", "thinkbox-xmesh", "timestream", "tools-and-sdks", "torch-serve", "training-certification", "transcribe", "transfer-family", "transit-gateway", "translate", "trusted-advisor", "verified-access", "verified-permissions", "virtual-private-cloud", "vmware-cloud-on-aws", "vpc-lattice", "waf", "wavelength", "well-architected-tool", "wickr", "work-docs-sdk", "work-docs", "work-link", "work-mail", "work-spaces-family", "x-ray"];