@domainlang/language 0.5.2 → 0.7.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.
- package/README.md +1 -1
- package/out/domain-lang-module.js +5 -1
- package/out/domain-lang-module.js.map +1 -1
- package/out/generated/ast.d.ts +24 -0
- package/out/generated/ast.js.map +1 -1
- package/out/generated/grammar.js +22 -32
- package/out/generated/grammar.js.map +1 -1
- package/out/index.d.ts +2 -5
- package/out/index.js +10 -6
- package/out/index.js.map +1 -1
- package/out/lsp/domain-lang-code-actions.js +14 -8
- package/out/lsp/domain-lang-code-actions.js.map +1 -1
- package/out/lsp/domain-lang-completion.d.ts +3 -0
- package/out/lsp/domain-lang-completion.js +41 -13
- package/out/lsp/domain-lang-completion.js.map +1 -1
- package/out/lsp/domain-lang-formatter.js +24 -18
- package/out/lsp/domain-lang-formatter.js.map +1 -1
- package/out/lsp/domain-lang-index-manager.d.ts +170 -0
- package/out/lsp/domain-lang-index-manager.js +389 -0
- package/out/lsp/domain-lang-index-manager.js.map +1 -0
- package/out/lsp/domain-lang-scope-provider.d.ts +67 -0
- package/out/lsp/domain-lang-scope-provider.js +95 -0
- package/out/lsp/domain-lang-scope-provider.js.map +1 -0
- package/out/lsp/domain-lang-scope.js +31 -17
- package/out/lsp/domain-lang-scope.js.map +1 -1
- package/out/lsp/domain-lang-workspace-manager.d.ts +76 -9
- package/out/lsp/domain-lang-workspace-manager.js +176 -54
- package/out/lsp/domain-lang-workspace-manager.js.map +1 -1
- package/out/lsp/hover/domain-lang-hover.d.ts +45 -1
- package/out/lsp/hover/domain-lang-hover.js +308 -232
- package/out/lsp/hover/domain-lang-hover.js.map +1 -1
- package/out/lsp/hover/domain-lang-keywords.d.ts +3 -7
- package/out/lsp/hover/domain-lang-keywords.js +115 -38
- package/out/lsp/hover/domain-lang-keywords.js.map +1 -1
- package/out/lsp/manifest-diagnostics.js +95 -50
- package/out/lsp/manifest-diagnostics.js.map +1 -1
- package/out/main.js +204 -17
- package/out/main.js.map +1 -1
- package/out/services/import-resolver.d.ts +39 -2
- package/out/services/import-resolver.js +77 -12
- package/out/services/import-resolver.js.map +1 -1
- package/out/services/types.d.ts +2 -2
- package/out/services/workspace-manager.d.ts +33 -31
- package/out/services/workspace-manager.js +92 -148
- package/out/services/workspace-manager.js.map +1 -1
- package/out/utils/document-utils.d.ts +41 -0
- package/out/utils/document-utils.js +64 -0
- package/out/utils/document-utils.js.map +1 -0
- package/out/utils/import-utils.d.ts +0 -17
- package/out/utils/import-utils.js +2 -32
- package/out/utils/import-utils.js.map +1 -1
- package/out/utils/manifest-utils.d.ts +56 -0
- package/out/utils/manifest-utils.js +119 -0
- package/out/utils/manifest-utils.js.map +1 -0
- package/out/validation/constants.d.ts +13 -0
- package/out/validation/constants.js +18 -0
- package/out/validation/constants.js.map +1 -1
- package/out/validation/import.d.ts +12 -2
- package/out/validation/import.js +95 -22
- package/out/validation/import.js.map +1 -1
- package/out/validation/maps.js +51 -2
- package/out/validation/maps.js.map +1 -1
- package/package.json +1 -1
- package/src/domain-lang-module.ts +6 -1
- package/src/domain-lang.langium +37 -13
- package/src/generated/ast.ts +24 -0
- package/src/generated/grammar.ts +22 -32
- package/src/index.ts +12 -6
- package/src/lsp/domain-lang-code-actions.ts +13 -8
- package/src/lsp/domain-lang-completion.ts +61 -13
- package/src/lsp/domain-lang-formatter.ts +28 -23
- package/src/lsp/domain-lang-index-manager.ts +447 -0
- package/src/lsp/domain-lang-scope-provider.ts +134 -0
- package/src/lsp/domain-lang-scope.ts +29 -17
- package/src/lsp/domain-lang-workspace-manager.ts +201 -53
- package/src/lsp/hover/domain-lang-hover.ts +332 -226
- package/src/lsp/hover/domain-lang-keywords.ts +129 -43
- package/src/lsp/manifest-diagnostics.ts +100 -59
- package/src/main.ts +258 -16
- package/src/services/import-resolver.ts +91 -12
- package/src/services/types.ts +2 -2
- package/src/services/workspace-manager.ts +101 -175
- package/src/utils/document-utils.ts +80 -0
- package/src/utils/import-utils.ts +2 -40
- package/src/utils/manifest-utils.ts +132 -0
- package/src/validation/constants.ts +24 -0
- package/src/validation/import.ts +107 -24
- package/src/validation/maps.ts +59 -2
- package/out/lsp/hover/ddd-pattern-explanations.d.ts +0 -50
- package/out/lsp/hover/ddd-pattern-explanations.js +0 -196
- package/out/lsp/hover/ddd-pattern-explanations.js.map +0 -1
- package/out/services/dependency-analyzer.d.ts +0 -58
- package/out/services/dependency-analyzer.js +0 -254
- package/out/services/dependency-analyzer.js.map +0 -1
- package/out/services/dependency-resolver.d.ts +0 -146
- package/out/services/dependency-resolver.js +0 -452
- package/out/services/dependency-resolver.js.map +0 -1
- package/out/services/git-url-resolver.browser.d.ts +0 -10
- package/out/services/git-url-resolver.browser.js +0 -19
- package/out/services/git-url-resolver.browser.js.map +0 -1
- package/out/services/git-url-resolver.d.ts +0 -158
- package/out/services/git-url-resolver.js +0 -416
- package/out/services/git-url-resolver.js.map +0 -1
- package/out/services/governance-validator.d.ts +0 -44
- package/out/services/governance-validator.js +0 -153
- package/out/services/governance-validator.js.map +0 -1
- package/out/services/semver.d.ts +0 -98
- package/out/services/semver.js +0 -195
- package/out/services/semver.js.map +0 -1
- package/src/lsp/hover/ddd-pattern-explanations.ts +0 -237
- package/src/services/dependency-analyzer.ts +0 -321
- package/src/services/dependency-resolver.ts +0 -551
- package/src/services/git-url-resolver.browser.ts +0 -26
- package/src/services/git-url-resolver.ts +0 -517
- package/src/services/governance-validator.ts +0 -177
- package/src/services/semver.ts +0 -213
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import YAML from 'yaml';
|
|
4
|
-
import { DependencyResolver } from './dependency-resolver.js';
|
|
5
|
-
import { GitUrlResolver } from './git-url-resolver.js';
|
|
6
4
|
import { getGlobalOptimizer } from './performance-optimizer.js';
|
|
5
|
+
import { fileExists as checkFileExists, findWorkspaceRoot as findWorkspaceRootUtil } from '../utils/manifest-utils.js';
|
|
7
6
|
import type {
|
|
8
7
|
LockFile,
|
|
9
8
|
LockedDependency,
|
|
@@ -22,8 +21,6 @@ const DEFAULT_LOCK_FILES = [
|
|
|
22
21
|
'model.lock'
|
|
23
22
|
] as const;
|
|
24
23
|
|
|
25
|
-
const JSON_SPACE = 2;
|
|
26
|
-
|
|
27
24
|
interface ManifestCache {
|
|
28
25
|
readonly manifest: ModelManifest;
|
|
29
26
|
readonly path: string;
|
|
@@ -36,31 +33,37 @@ interface LoadedLockFile {
|
|
|
36
33
|
}
|
|
37
34
|
|
|
38
35
|
/**
|
|
39
|
-
* Coordinates workspace discovery
|
|
36
|
+
* Coordinates workspace discovery and manifest/lock file reading.
|
|
37
|
+
*
|
|
38
|
+
* This is a read-only service for the LSP - it does NOT:
|
|
39
|
+
* - Generate lock files (use CLI: `dlang install`)
|
|
40
|
+
* - Download packages (use CLI: `dlang install`)
|
|
41
|
+
* - Make network requests
|
|
42
|
+
*
|
|
43
|
+
* The LSP uses this to:
|
|
44
|
+
* - Find the workspace root (where model.yaml is)
|
|
45
|
+
* - Read manifest configuration (path aliases, dependencies)
|
|
46
|
+
* - Read lock file (to resolve cached package locations)
|
|
40
47
|
*/
|
|
41
48
|
export class WorkspaceManager {
|
|
42
49
|
private readonly manifestFiles: readonly string[];
|
|
43
50
|
private readonly lockFiles: readonly string[];
|
|
44
51
|
private workspaceRoot: string | undefined;
|
|
45
52
|
private lockFile: LockFile | undefined;
|
|
46
|
-
private gitResolver: GitUrlResolver | undefined;
|
|
47
|
-
private dependencyResolver: DependencyResolver | undefined;
|
|
48
53
|
private initializePromise: Promise<void> | undefined;
|
|
49
54
|
private manifestCache: ManifestCache | undefined;
|
|
50
55
|
|
|
51
|
-
constructor(
|
|
56
|
+
constructor(options: WorkspaceManagerOptions = {}) {
|
|
52
57
|
this.manifestFiles = options.manifestFiles ?? [...DEFAULT_MANIFEST_FILES];
|
|
53
58
|
this.lockFiles = options.lockFiles ?? [...DEFAULT_LOCK_FILES];
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
/**
|
|
57
|
-
* Finds the workspace root
|
|
62
|
+
* Finds the workspace root and loads any existing lock file.
|
|
58
63
|
* Repeated calls await the same initialization work.
|
|
59
64
|
*/
|
|
60
65
|
async initialize(startPath: string): Promise<void> {
|
|
61
|
-
|
|
62
|
-
this.initializePromise = this.performInitialization(startPath);
|
|
63
|
-
}
|
|
66
|
+
this.initializePromise ??= this.performInitialization(startPath);
|
|
64
67
|
await this.initializePromise;
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -75,6 +78,17 @@ export class WorkspaceManager {
|
|
|
75
78
|
return this.workspaceRoot;
|
|
76
79
|
}
|
|
77
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Returns the project-local package cache directory.
|
|
83
|
+
* Per PRS-010: .dlang/packages/
|
|
84
|
+
*/
|
|
85
|
+
getCacheDir(): string {
|
|
86
|
+
if (!this.workspaceRoot) {
|
|
87
|
+
throw new Error('WorkspaceManager not initialized. Call initialize() first.');
|
|
88
|
+
}
|
|
89
|
+
return path.join(this.workspaceRoot, '.dlang', 'packages');
|
|
90
|
+
}
|
|
91
|
+
|
|
78
92
|
/**
|
|
79
93
|
* Resolves the manifest file path within the workspace, if present.
|
|
80
94
|
*/
|
|
@@ -87,7 +101,7 @@ export class WorkspaceManager {
|
|
|
87
101
|
|
|
88
102
|
for (const manifest of this.manifestFiles) {
|
|
89
103
|
const candidate = path.join(root, manifest);
|
|
90
|
-
if (await
|
|
104
|
+
if (await checkFileExists(candidate)) {
|
|
91
105
|
return candidate;
|
|
92
106
|
}
|
|
93
107
|
}
|
|
@@ -105,41 +119,8 @@ export class WorkspaceManager {
|
|
|
105
119
|
}
|
|
106
120
|
|
|
107
121
|
/**
|
|
108
|
-
*
|
|
109
|
-
|
|
110
|
-
async ensureLockFile(): Promise<LockFile> {
|
|
111
|
-
await this.ensureInitialized();
|
|
112
|
-
|
|
113
|
-
if (!this.lockFile) {
|
|
114
|
-
// Try loading from cache first
|
|
115
|
-
const optimizer = getGlobalOptimizer();
|
|
116
|
-
const cached = await optimizer.getCachedLockFile(this.workspaceRoot || '');
|
|
117
|
-
|
|
118
|
-
if (cached) {
|
|
119
|
-
this.lockFile = cached;
|
|
120
|
-
} else {
|
|
121
|
-
if (this.options.allowNetwork === false) {
|
|
122
|
-
throw new Error(
|
|
123
|
-
'Lock file (model.lock) not found and network access is disabled.\n' +
|
|
124
|
-
'Hint: Run \'dlang install\' to generate the lock file.'
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
await this.generateLockFile();
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (!this.lockFile) {
|
|
132
|
-
throw new Error(
|
|
133
|
-
'Unable to resolve workspace lock file.\n' +
|
|
134
|
-
'Hint: Ensure model.yaml exists and run \'dlang install\' to generate model.lock.'
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return this.lockFile;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Gets the currently cached lock file without refreshing from disk.
|
|
122
|
+
* Gets the currently cached lock file.
|
|
123
|
+
* Returns undefined if no lock file exists (run `dlang install` to create one).
|
|
143
124
|
*/
|
|
144
125
|
async getLockFile(): Promise<LockFile | undefined> {
|
|
145
126
|
await this.ensureInitialized();
|
|
@@ -147,12 +128,16 @@ export class WorkspaceManager {
|
|
|
147
128
|
}
|
|
148
129
|
|
|
149
130
|
/**
|
|
150
|
-
* Reloads the lock file from disk
|
|
131
|
+
* Reloads the lock file from disk.
|
|
151
132
|
*/
|
|
152
133
|
async refreshLockFile(): Promise<LockFile | undefined> {
|
|
153
134
|
await this.ensureInitialized();
|
|
154
135
|
const loaded = await this.loadLockFileFromDisk();
|
|
155
|
-
|
|
136
|
+
if (loaded) {
|
|
137
|
+
this.lockFile = loaded.lockFile;
|
|
138
|
+
} else {
|
|
139
|
+
this.lockFile = undefined;
|
|
140
|
+
}
|
|
156
141
|
return this.lockFile;
|
|
157
142
|
}
|
|
158
143
|
|
|
@@ -166,10 +151,6 @@ export class WorkspaceManager {
|
|
|
166
151
|
invalidateCache(): void {
|
|
167
152
|
this.manifestCache = undefined;
|
|
168
153
|
this.lockFile = undefined;
|
|
169
|
-
// Re-apply undefined to git resolver to clear its lock file
|
|
170
|
-
if (this.gitResolver) {
|
|
171
|
-
this.gitResolver.setLockFile(undefined);
|
|
172
|
-
}
|
|
173
154
|
}
|
|
174
155
|
|
|
175
156
|
/**
|
|
@@ -186,32 +167,6 @@ export class WorkspaceManager {
|
|
|
186
167
|
*/
|
|
187
168
|
invalidateLockCache(): void {
|
|
188
169
|
this.lockFile = undefined;
|
|
189
|
-
if (this.gitResolver) {
|
|
190
|
-
this.gitResolver.setLockFile(undefined);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Provides the shared git URL resolver configured with the current lock file.
|
|
196
|
-
*/
|
|
197
|
-
async getGitResolver(): Promise<GitUrlResolver> {
|
|
198
|
-
await this.ensureInitialized();
|
|
199
|
-
if (!this.gitResolver) {
|
|
200
|
-
throw new Error('GitUrlResolver not available. Workspace initialization failed.');
|
|
201
|
-
}
|
|
202
|
-
return this.gitResolver;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Forces dependency resolution and regenerates lock files on disk.
|
|
207
|
-
*/
|
|
208
|
-
async regenerateLockFile(): Promise<LockFile> {
|
|
209
|
-
await this.ensureInitialized();
|
|
210
|
-
await this.generateLockFile(true);
|
|
211
|
-
if (!this.lockFile) {
|
|
212
|
-
throw new Error('Failed to regenerate workspace lock file.');
|
|
213
|
-
}
|
|
214
|
-
return this.lockFile;
|
|
215
170
|
}
|
|
216
171
|
|
|
217
172
|
/**
|
|
@@ -229,7 +184,7 @@ export class WorkspaceManager {
|
|
|
229
184
|
* In the new format, the key IS the owner/package, so source is derived from key
|
|
230
185
|
* ONLY for git dependencies (not for path-based local dependencies).
|
|
231
186
|
*/
|
|
232
|
-
|
|
187
|
+
normalizeDependency(key: string, dep: DependencySpec): ExtendedDependencySpec {
|
|
233
188
|
if (typeof dep === 'string') {
|
|
234
189
|
// Short form: "owner/package": "v1.0.0" or "main"
|
|
235
190
|
// Key is the source (owner/package format)
|
|
@@ -246,14 +201,18 @@ export class WorkspaceManager {
|
|
|
246
201
|
}
|
|
247
202
|
|
|
248
203
|
/**
|
|
249
|
-
* Resolves a
|
|
204
|
+
* Resolves a dependency import specifier to its cached package path.
|
|
250
205
|
*
|
|
251
|
-
* NEW FORMAT (PRS-010): Dependencies are keyed by owner/package directly
|
|
252
206
|
* @param specifier - Import specifier (owner/package format, may include subpaths)
|
|
253
|
-
* @returns
|
|
207
|
+
* @returns Path to the cached package entry point, or undefined if not found
|
|
254
208
|
*/
|
|
255
|
-
async
|
|
209
|
+
async resolveDependencyPath(specifier: string): Promise<string | undefined> {
|
|
256
210
|
await this.ensureInitialized();
|
|
211
|
+
|
|
212
|
+
if (!this.lockFile) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
257
216
|
const manifest = await this.loadManifest();
|
|
258
217
|
const dependencies = manifest?.dependencies;
|
|
259
218
|
|
|
@@ -261,8 +220,7 @@ export class WorkspaceManager {
|
|
|
261
220
|
return undefined;
|
|
262
221
|
}
|
|
263
222
|
|
|
264
|
-
//
|
|
265
|
-
// Import specifier is also owner/package, potentially with subpath
|
|
223
|
+
// Find matching dependency
|
|
266
224
|
for (const [key, dep] of Object.entries(dependencies)) {
|
|
267
225
|
const normalized = this.normalizeDependency(key, dep);
|
|
268
226
|
|
|
@@ -277,29 +235,51 @@ export class WorkspaceManager {
|
|
|
277
235
|
|
|
278
236
|
// Match if specifier equals key or starts with key/
|
|
279
237
|
if (specifier === key || specifier.startsWith(`${key}/`)) {
|
|
238
|
+
// Find in lock file
|
|
239
|
+
const locked = this.lockFile.dependencies[normalized.source];
|
|
240
|
+
if (!locked) {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Compute cache path
|
|
245
|
+
const [owner, repo] = normalized.source.split('/');
|
|
246
|
+
const packageDir = path.join(this.getCacheDir(), owner, repo, locked.commit);
|
|
247
|
+
|
|
248
|
+
// Handle subpaths
|
|
280
249
|
const suffix = specifier.slice(key.length);
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
250
|
+
if (suffix) {
|
|
251
|
+
// Import with subpath: owner/package/subpath
|
|
252
|
+
return path.join(packageDir, suffix);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Read entry point from package's model.yaml
|
|
256
|
+
const entryPoint = await this.readPackageEntry(packageDir);
|
|
257
|
+
return path.join(packageDir, entryPoint);
|
|
286
258
|
}
|
|
287
259
|
}
|
|
288
260
|
|
|
289
261
|
return undefined;
|
|
290
262
|
}
|
|
291
263
|
|
|
264
|
+
/**
|
|
265
|
+
* Reads the entry point from a cached package's model.yaml.
|
|
266
|
+
*/
|
|
267
|
+
private async readPackageEntry(packageDir: string): Promise<string> {
|
|
268
|
+
const manifestPath = path.join(packageDir, 'model.yaml');
|
|
269
|
+
try {
|
|
270
|
+
const content = await fs.readFile(manifestPath, 'utf-8');
|
|
271
|
+
const manifest = YAML.parse(content) as { model?: { entry?: string } };
|
|
272
|
+
return manifest?.model?.entry ?? 'index.dlang';
|
|
273
|
+
} catch {
|
|
274
|
+
return 'index.dlang';
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
292
278
|
private async performInitialization(startPath: string): Promise<void> {
|
|
293
279
|
this.workspaceRoot = await this.findWorkspaceRoot(startPath) ?? path.resolve(startPath);
|
|
294
|
-
|
|
295
|
-
// Per PRS-010: Project-local cache at .dlang/packages/ (like node_modules)
|
|
296
|
-
const cacheDir = path.join(this.workspaceRoot, '.dlang', 'packages');
|
|
297
|
-
this.gitResolver = new GitUrlResolver(cacheDir);
|
|
298
280
|
const loaded = await this.loadLockFileFromDisk();
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (!this.lockFile && this.options.autoResolve !== false && this.options.allowNetwork !== false) {
|
|
302
|
-
await this.generateLockFile();
|
|
281
|
+
if (loaded) {
|
|
282
|
+
this.lockFile = loaded.lockFile;
|
|
303
283
|
}
|
|
304
284
|
}
|
|
305
285
|
|
|
@@ -311,72 +291,21 @@ export class WorkspaceManager {
|
|
|
311
291
|
}
|
|
312
292
|
}
|
|
313
293
|
|
|
314
|
-
private applyLockFile(loaded: LoadedLockFile | undefined): void {
|
|
315
|
-
if (!this.gitResolver) {
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (loaded) {
|
|
320
|
-
this.lockFile = loaded.lockFile;
|
|
321
|
-
this.gitResolver.setLockFile(this.lockFile);
|
|
322
|
-
} else {
|
|
323
|
-
this.lockFile = undefined;
|
|
324
|
-
this.gitResolver.setLockFile(undefined);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
private async generateLockFile(force = false): Promise<void> {
|
|
329
|
-
if (!this.workspaceRoot || !this.gitResolver) {
|
|
330
|
-
throw new Error('WorkspaceManager not initialized.');
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const resolver = this.ensureDependencyResolver();
|
|
334
|
-
if (!force && this.lockFile) {
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
const lockFile = await resolver.resolveDependencies();
|
|
339
|
-
this.lockFile = lockFile;
|
|
340
|
-
this.gitResolver.setLockFile(lockFile);
|
|
341
|
-
|
|
342
|
-
// Write JSON lock file
|
|
343
|
-
await this.writeJsonLockFile(lockFile);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
private ensureDependencyResolver(): DependencyResolver {
|
|
347
|
-
if (!this.workspaceRoot || !this.gitResolver) {
|
|
348
|
-
throw new Error('WorkspaceManager not initialized.');
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (!this.dependencyResolver) {
|
|
352
|
-
this.dependencyResolver = new DependencyResolver(this.workspaceRoot, this.gitResolver);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
return this.dependencyResolver;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
private async writeJsonLockFile(lockFile: LockFile): Promise<void> {
|
|
359
|
-
if (!this.workspaceRoot) {
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
const jsonPath = path.join(this.workspaceRoot, 'model.lock');
|
|
364
|
-
const payload = {
|
|
365
|
-
version: lockFile.version,
|
|
366
|
-
dependencies: lockFile.dependencies,
|
|
367
|
-
} satisfies LockFile;
|
|
368
|
-
|
|
369
|
-
await fs.writeFile(jsonPath, JSON.stringify(payload, undefined, JSON_SPACE), 'utf-8');
|
|
370
|
-
}
|
|
371
|
-
|
|
372
294
|
private async loadLockFileFromDisk(): Promise<LoadedLockFile | undefined> {
|
|
373
295
|
if (!this.workspaceRoot) {
|
|
374
296
|
return undefined;
|
|
375
297
|
}
|
|
376
298
|
|
|
299
|
+
// Try performance optimizer cache first
|
|
300
|
+
const optimizer = getGlobalOptimizer();
|
|
301
|
+
const cached = await optimizer.getCachedLockFile(this.workspaceRoot);
|
|
302
|
+
if (cached) {
|
|
303
|
+
return { lockFile: cached, filePath: path.join(this.workspaceRoot, 'model.lock') };
|
|
304
|
+
}
|
|
305
|
+
|
|
377
306
|
for (const filename of this.lockFiles) {
|
|
378
307
|
const filePath = path.join(this.workspaceRoot, filename);
|
|
379
|
-
const lockFile = await this.tryReadLockFile(filePath
|
|
308
|
+
const lockFile = await this.tryReadLockFile(filePath);
|
|
380
309
|
if (lockFile) {
|
|
381
310
|
return { lockFile, filePath };
|
|
382
311
|
}
|
|
@@ -385,7 +314,7 @@ export class WorkspaceManager {
|
|
|
385
314
|
return undefined;
|
|
386
315
|
}
|
|
387
316
|
|
|
388
|
-
private async tryReadLockFile(filePath: string
|
|
317
|
+
private async tryReadLockFile(filePath: string): Promise<LockFile | undefined> {
|
|
389
318
|
try {
|
|
390
319
|
const content = await fs.readFile(filePath, 'utf-8');
|
|
391
320
|
return this.parseJsonLockFile(content);
|
|
@@ -406,8 +335,7 @@ export class WorkspaceManager {
|
|
|
406
335
|
|
|
407
336
|
try {
|
|
408
337
|
const stat = await fs.stat(manifestPath);
|
|
409
|
-
if (this.manifestCache &&
|
|
410
|
-
this.manifestCache.path === manifestPath &&
|
|
338
|
+
if (this.manifestCache?.path === manifestPath &&
|
|
411
339
|
this.manifestCache.mtimeMs === stat.mtimeMs) {
|
|
412
340
|
return this.manifestCache.manifest;
|
|
413
341
|
}
|
|
@@ -562,7 +490,17 @@ export class WorkspaceManager {
|
|
|
562
490
|
return { version, dependencies };
|
|
563
491
|
}
|
|
564
492
|
|
|
493
|
+
/**
|
|
494
|
+
* Finds workspace root by walking up from startPath looking for model.yaml.
|
|
495
|
+
* Uses configurable manifest files if specified in constructor options.
|
|
496
|
+
*/
|
|
565
497
|
private async findWorkspaceRoot(startPath: string): Promise<string | undefined> {
|
|
498
|
+
// Use shared utility for default case (single manifest file)
|
|
499
|
+
if (this.manifestFiles.length === 1 && this.manifestFiles[0] === 'model.yaml') {
|
|
500
|
+
return findWorkspaceRootUtil(startPath);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Custom logic for multiple or non-default manifest files
|
|
566
504
|
let current = path.resolve(startPath);
|
|
567
505
|
const { root } = path.parse(current);
|
|
568
506
|
|
|
@@ -586,22 +524,10 @@ export class WorkspaceManager {
|
|
|
586
524
|
|
|
587
525
|
private async containsManifest(dir: string): Promise<boolean> {
|
|
588
526
|
for (const manifest of this.manifestFiles) {
|
|
589
|
-
if (await
|
|
527
|
+
if (await checkFileExists(path.join(dir, manifest))) {
|
|
590
528
|
return true;
|
|
591
529
|
}
|
|
592
530
|
}
|
|
593
531
|
return false;
|
|
594
532
|
}
|
|
595
|
-
|
|
596
|
-
private async fileExists(targetPath: string): Promise<boolean> {
|
|
597
|
-
try {
|
|
598
|
-
await fs.access(targetPath);
|
|
599
|
-
return true;
|
|
600
|
-
} catch (error) {
|
|
601
|
-
if ((error as NodeJS.ErrnoException)?.code === 'ENOENT') {
|
|
602
|
-
return false;
|
|
603
|
-
}
|
|
604
|
-
throw error;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
533
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { DocumentState, type LangiumDocument } from 'langium';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Waits for a document to reach a specific state.
|
|
5
|
+
* Useful for ensuring documents are fully linked before accessing cross-references.
|
|
6
|
+
*
|
|
7
|
+
* @param document - The LangiumDocument to wait for
|
|
8
|
+
* @param targetState - The minimum required DocumentState
|
|
9
|
+
* @param timeout - Maximum time to wait in milliseconds (default: 5000ms)
|
|
10
|
+
* @throws {Error} If document doesn't reach target state within timeout
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* // Ensure document is linked before accessing references
|
|
15
|
+
* await waitForState(document, DocumentState.Linked);
|
|
16
|
+
* const domain = bc.domain?.ref; // Now guaranteed to be resolved
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* // Ensure validation is complete
|
|
22
|
+
* await waitForState(document, DocumentState.Validated);
|
|
23
|
+
* const diagnostics = document.diagnostics; // Now includes all validation results
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export async function waitForState(
|
|
27
|
+
document: LangiumDocument,
|
|
28
|
+
targetState: DocumentState,
|
|
29
|
+
timeout = 5000
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
if (document.state >= targetState) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
const timer = setTimeout(() => {
|
|
37
|
+
reject(new Error(
|
|
38
|
+
`Document ${document.uri.toString()} did not reach state ${targetState} within ${timeout}ms. ` +
|
|
39
|
+
`Current state: ${document.state}`
|
|
40
|
+
));
|
|
41
|
+
}, timeout);
|
|
42
|
+
|
|
43
|
+
const checkState = (): void => {
|
|
44
|
+
if (document.state >= targetState) {
|
|
45
|
+
clearTimeout(timer);
|
|
46
|
+
resolve();
|
|
47
|
+
} else {
|
|
48
|
+
// Check every 10ms
|
|
49
|
+
setTimeout(checkState, 10);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
checkState();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Waits for multiple documents to reach a specific state.
|
|
59
|
+
* Useful for batch operations where all documents must be ready.
|
|
60
|
+
*
|
|
61
|
+
* @param documents - Array of LangiumDocuments to wait for
|
|
62
|
+
* @param targetState - The minimum required DocumentState
|
|
63
|
+
* @param timeout - Maximum time to wait per document in milliseconds
|
|
64
|
+
* @throws {Error} If any document doesn't reach target state within timeout
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* // Ensure all imported documents are linked
|
|
69
|
+
* await waitForDocuments(importedDocs, DocumentState.Linked);
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export async function waitForDocuments(
|
|
73
|
+
documents: LangiumDocument[],
|
|
74
|
+
targetState: DocumentState,
|
|
75
|
+
timeout = 5000
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
await Promise.all(
|
|
78
|
+
documents.map(doc => waitForState(doc, targetState, timeout))
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { URI, type LangiumDocument, type LangiumDocuments } from 'langium';
|
|
3
3
|
import type { Model } from '../generated/ast.js';
|
|
4
|
-
import type { GitUrlResolver } from '../services/git-url-resolver.js';
|
|
5
4
|
import { WorkspaceManager } from '../services/workspace-manager.js';
|
|
6
5
|
import { ImportResolver } from '../services/import-resolver.js';
|
|
7
6
|
import type { DomainLangServices } from '../domain-lang-module.js';
|
|
@@ -27,7 +26,7 @@ let lastInitializedDir: string | undefined;
|
|
|
27
26
|
async function getStandaloneImportResolver(startDir: string): Promise<ImportResolver> {
|
|
28
27
|
// Re-initialize if directory changed (workspace boundary)
|
|
29
28
|
if (lastInitializedDir !== startDir || !standaloneImportResolver) {
|
|
30
|
-
standaloneWorkspaceManager = new WorkspaceManager(
|
|
29
|
+
standaloneWorkspaceManager = new WorkspaceManager();
|
|
31
30
|
try {
|
|
32
31
|
await standaloneWorkspaceManager.initialize(startDir);
|
|
33
32
|
} catch (error) {
|
|
@@ -42,18 +41,6 @@ async function getStandaloneImportResolver(startDir: string): Promise<ImportReso
|
|
|
42
41
|
return standaloneImportResolver;
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
/**
|
|
46
|
-
* Gets the git URL resolver from a workspace manager.
|
|
47
|
-
*
|
|
48
|
-
* @param startDir - Directory to start workspace search from
|
|
49
|
-
* @returns Promise resolving to the git URL resolver
|
|
50
|
-
*/
|
|
51
|
-
async function getGitResolver(startDir: string): Promise<GitUrlResolver> {
|
|
52
|
-
const workspaceManager = new WorkspaceManager({ autoResolve: false, allowNetwork: false });
|
|
53
|
-
await workspaceManager.initialize(startDir);
|
|
54
|
-
return workspaceManager.getGitResolver();
|
|
55
|
-
}
|
|
56
|
-
|
|
57
44
|
/**
|
|
58
45
|
* Resolves an import path to an absolute file URI.
|
|
59
46
|
*
|
|
@@ -115,7 +102,7 @@ export async function ensureImportGraphFromDocument(
|
|
|
115
102
|
for (const imp of model.imports ?? []) {
|
|
116
103
|
if (!imp.uri) continue;
|
|
117
104
|
|
|
118
|
-
// Use new resolveImportPath that supports
|
|
105
|
+
// Use new resolveImportPath that supports external dependencies
|
|
119
106
|
const resolvedUri = await resolveImportPath(doc, imp.uri);
|
|
120
107
|
const childDoc = await langiumDocuments.getOrCreateDocument(resolvedUri);
|
|
121
108
|
await visit(childDoc);
|
|
@@ -125,28 +112,3 @@ export async function ensureImportGraphFromDocument(
|
|
|
125
112
|
await visit(document);
|
|
126
113
|
return visited;
|
|
127
114
|
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Gets cache statistics for git imports.
|
|
131
|
-
*
|
|
132
|
-
* @returns Cache statistics including size and number of cached repositories
|
|
133
|
-
*/
|
|
134
|
-
export async function getGitCacheStats(startDir: string = process.cwd()): Promise<{
|
|
135
|
-
totalSize: number;
|
|
136
|
-
repoCount: number;
|
|
137
|
-
cacheDir: string;
|
|
138
|
-
}> {
|
|
139
|
-
const resolver = await getGitResolver(startDir);
|
|
140
|
-
return await resolver.getCacheStats();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Clears the git import cache.
|
|
145
|
-
*
|
|
146
|
-
* @param startDir - Starting directory for workspace resolution
|
|
147
|
-
* @returns Promise that resolves when cache is cleared
|
|
148
|
-
*/
|
|
149
|
-
export async function clearGitCache(startDir: string = process.cwd()): Promise<void> {
|
|
150
|
-
const resolver = await getGitResolver(startDir);
|
|
151
|
-
return await resolver.clearCache();
|
|
152
|
-
}
|