@domainlang/language 0.1.20 → 0.1.82
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/out/domain-lang-module.d.ts +0 -2
- package/out/domain-lang-module.js +3 -11
- package/out/domain-lang-module.js.map +1 -1
- package/out/generated/ast.d.ts +19 -8
- package/out/generated/ast.js +10 -1
- package/out/generated/ast.js.map +1 -1
- package/out/generated/grammar.d.ts +1 -1
- package/out/generated/grammar.js +123 -28
- package/out/generated/grammar.js.map +1 -1
- package/out/generated/module.d.ts +1 -1
- package/out/generated/module.js +1 -1
- package/out/index.d.ts +0 -3
- package/out/index.js +0 -5
- package/out/index.js.map +1 -1
- package/out/lsp/hover/domain-lang-hover.js +4 -0
- package/out/lsp/hover/domain-lang-hover.js.map +1 -1
- package/out/sdk/index.d.ts +1 -1
- package/out/sdk/loader-node.d.ts +3 -7
- package/out/sdk/loader-node.js +9 -24
- package/out/sdk/loader-node.js.map +1 -1
- package/out/sdk/types.d.ts +21 -0
- package/out/services/dependency-analyzer.d.ts +39 -3
- package/out/services/dependency-analyzer.js +47 -22
- package/out/services/dependency-analyzer.js.map +1 -1
- package/out/services/dependency-resolver.d.ts +45 -68
- package/out/services/dependency-resolver.js +43 -243
- package/out/services/dependency-resolver.js.map +1 -1
- package/out/services/git-url-resolver.browser.d.ts +12 -4
- package/out/services/git-url-resolver.browser.js +1 -5
- package/out/services/git-url-resolver.browser.js.map +1 -1
- package/out/services/git-url-resolver.d.ts +56 -22
- package/out/services/git-url-resolver.js +36 -70
- package/out/services/git-url-resolver.js.map +1 -1
- package/out/services/governance-validator.d.ts +37 -1
- package/out/services/governance-validator.js +10 -4
- package/out/services/governance-validator.js.map +1 -1
- package/out/services/import-resolver.d.ts +6 -65
- package/out/services/import-resolver.js +5 -223
- package/out/services/import-resolver.js.map +1 -1
- package/out/services/performance-optimizer.d.ts +1 -1
- package/out/services/workspace-manager.d.ts +10 -57
- package/out/services/workspace-manager.js +21 -187
- package/out/services/workspace-manager.js.map +1 -1
- package/out/syntaxes/domain-lang.monarch.js +1 -1
- package/out/syntaxes/domain-lang.monarch.js.map +1 -1
- package/out/utils/import-utils.d.ts +12 -4
- package/out/utils/import-utils.js +135 -35
- package/out/utils/import-utils.js.map +1 -1
- package/out/validation/constants.d.ts +0 -103
- package/out/validation/constants.js +1 -140
- package/out/validation/constants.js.map +1 -1
- package/out/validation/domain.js +1 -46
- package/out/validation/domain.js.map +1 -1
- package/out/validation/import.d.ts +22 -46
- package/out/validation/import.js +85 -187
- package/out/validation/import.js.map +1 -1
- package/out/validation/maps.js +6 -10
- package/out/validation/maps.js.map +1 -1
- package/out/validation/metadata.js +1 -5
- package/out/validation/metadata.js.map +1 -1
- package/package.json +6 -8
- package/src/domain-lang-module.ts +6 -18
- package/src/domain-lang.langium +12 -7
- package/src/generated/ast.ts +20 -7
- package/src/generated/grammar.ts +123 -28
- package/src/generated/module.ts +1 -1
- package/src/index.ts +0 -7
- package/src/lsp/hover/domain-lang-hover.ts +2 -0
- package/src/sdk/index.ts +2 -0
- package/src/sdk/loader-node.ts +9 -29
- package/src/sdk/types.ts +23 -0
- package/src/services/dependency-analyzer.ts +84 -24
- package/src/services/dependency-resolver.ts +84 -301
- package/src/services/git-url-resolver.browser.ts +14 -9
- package/src/services/git-url-resolver.ts +93 -86
- package/src/services/governance-validator.ts +47 -5
- package/src/services/import-resolver.ts +8 -270
- package/src/services/performance-optimizer.ts +1 -1
- package/src/services/workspace-manager.ts +46 -237
- package/src/syntaxes/domain-lang.monarch.ts +1 -1
- package/src/utils/import-utils.ts +160 -38
- package/src/validation/constants.ts +1 -181
- package/src/validation/domain.ts +1 -54
- package/src/validation/import.ts +104 -228
- package/src/validation/maps.ts +6 -10
- package/src/validation/metadata.ts +1 -5
- package/out/lsp/domain-lang-code-actions.d.ts +0 -55
- package/out/lsp/domain-lang-code-actions.js +0 -143
- package/out/lsp/domain-lang-code-actions.js.map +0 -1
- package/out/lsp/domain-lang-workspace-manager.d.ts +0 -21
- package/out/lsp/domain-lang-workspace-manager.js +0 -93
- package/out/lsp/domain-lang-workspace-manager.js.map +0 -1
- package/out/lsp/manifest-diagnostics.d.ts +0 -82
- package/out/lsp/manifest-diagnostics.js +0 -230
- package/out/lsp/manifest-diagnostics.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/out/services/types.d.ts +0 -340
- package/out/services/types.js +0 -46
- package/out/services/types.js.map +0 -1
- package/out/validation/manifest.d.ts +0 -144
- package/out/validation/manifest.js +0 -327
- package/out/validation/manifest.js.map +0 -1
- package/src/lsp/domain-lang-code-actions.ts +0 -189
- package/src/lsp/domain-lang-workspace-manager.ts +0 -104
- package/src/lsp/manifest-diagnostics.ts +0 -290
- package/src/services/semver.ts +0 -213
- package/src/services/types.ts +0 -415
- package/src/validation/manifest.ts +0 -439
|
@@ -1,224 +1,16 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { URI } from 'langium';
|
|
4
1
|
/**
|
|
5
|
-
* ImportResolver resolves import statements using
|
|
6
|
-
*
|
|
7
|
-
* Import Types (PRS-010):
|
|
8
|
-
* - Local relative: ./path, ../path → Directory-first resolution
|
|
9
|
-
* - Path aliases: @/path, @alias/path → Configurable in model.yaml paths section
|
|
10
|
-
* - External: owner/package → Manifest dependencies
|
|
11
|
-
*
|
|
12
|
-
* Directory-First Resolution:
|
|
13
|
-
* - ./types → ./types/index.dlang → ./types.dlang
|
|
14
|
-
* - Module entry defaults to index.dlang (no model.yaml required)
|
|
2
|
+
* ImportResolver resolves import statements using WorkspaceManager and GitUrlResolver.
|
|
15
3
|
*/
|
|
16
4
|
export class ImportResolver {
|
|
17
5
|
constructor(services) {
|
|
18
6
|
this.workspaceManager = services.imports.WorkspaceManager;
|
|
19
7
|
}
|
|
20
8
|
/**
|
|
21
|
-
* Resolve an import
|
|
9
|
+
* Resolve an import URL to a file URI using the workspace's GitUrlResolver.
|
|
22
10
|
*/
|
|
23
|
-
async
|
|
24
|
-
const
|
|
25
|
-
return
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Resolve an import specifier from a base directory (non-LSP contexts).
|
|
29
|
-
*/
|
|
30
|
-
async resolveFrom(baseDir, specifier) {
|
|
31
|
-
await this.workspaceManager.initialize(baseDir);
|
|
32
|
-
// Local relative paths (./path or ../path) - directory-first resolution
|
|
33
|
-
if (specifier.startsWith('./') || specifier.startsWith('../')) {
|
|
34
|
-
const resolved = path.resolve(baseDir, specifier);
|
|
35
|
-
return this.resolveLocalPath(resolved, specifier);
|
|
36
|
-
}
|
|
37
|
-
// Path aliases (@/path or @alias/path)
|
|
38
|
-
if (specifier.startsWith('@')) {
|
|
39
|
-
return this.resolvePathAlias(specifier);
|
|
40
|
-
}
|
|
41
|
-
// External dependency via manifest (owner/package format)
|
|
42
|
-
return this.resolveExternalDependency(specifier);
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Resolves a path alias import.
|
|
46
|
-
*
|
|
47
|
-
* @param specifier - Import specifier starting with @ (e.g., "@/lib", "@shared/types")
|
|
48
|
-
*/
|
|
49
|
-
async resolvePathAlias(specifier) {
|
|
50
|
-
const aliases = await this.workspaceManager.getPathAliases();
|
|
51
|
-
const root = this.workspaceManager.getWorkspaceRoot();
|
|
52
|
-
// Find matching alias
|
|
53
|
-
const aliasMatch = this.findMatchingAlias(specifier, aliases);
|
|
54
|
-
if (aliasMatch) {
|
|
55
|
-
const { alias: _alias, targetPath, remainder } = aliasMatch;
|
|
56
|
-
const manifestPath = await this.workspaceManager.getManifestPath();
|
|
57
|
-
const manifestDir = manifestPath ? path.dirname(manifestPath) : root;
|
|
58
|
-
const resolvedBase = path.resolve(manifestDir, targetPath);
|
|
59
|
-
const resolved = remainder ? path.join(resolvedBase, remainder) : resolvedBase;
|
|
60
|
-
return this.resolveLocalPath(resolved, specifier);
|
|
61
|
-
}
|
|
62
|
-
// Default: @/ maps to workspace root (implicit)
|
|
63
|
-
if (specifier.startsWith('@/')) {
|
|
64
|
-
const relativePath = specifier.slice(2);
|
|
65
|
-
const resolved = path.join(root, relativePath);
|
|
66
|
-
return this.resolveLocalPath(resolved, specifier);
|
|
67
|
-
}
|
|
68
|
-
throw new Error(`Unknown path alias '${specifier.split('/')[0]}' in import '${specifier}'.\n` +
|
|
69
|
-
`Hint: Define it in model.yaml paths section:\n` +
|
|
70
|
-
` paths:\n` +
|
|
71
|
-
` "${specifier.split('/')[0]}": "./some/path"`);
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Finds the longest matching alias for a specifier.
|
|
75
|
-
*/
|
|
76
|
-
findMatchingAlias(specifier, aliases) {
|
|
77
|
-
if (!aliases) {
|
|
78
|
-
return undefined;
|
|
79
|
-
}
|
|
80
|
-
// Sort by length descending to match most specific alias first
|
|
81
|
-
const sortedAliases = Object.entries(aliases)
|
|
82
|
-
.sort(([a], [b]) => b.length - a.length);
|
|
83
|
-
for (const [alias, targetPath] of sortedAliases) {
|
|
84
|
-
// Exact match
|
|
85
|
-
if (specifier === alias) {
|
|
86
|
-
return { alias, targetPath, remainder: '' };
|
|
87
|
-
}
|
|
88
|
-
// Prefix match (alias + /)
|
|
89
|
-
if (specifier.startsWith(`${alias}/`)) {
|
|
90
|
-
return { alias, targetPath, remainder: specifier.slice(alias.length + 1) };
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Resolves an external dependency via manifest.
|
|
97
|
-
*
|
|
98
|
-
* NEW FORMAT (PRS-010): Import specifier is owner/package format.
|
|
99
|
-
*/
|
|
100
|
-
async resolveExternalDependency(specifier) {
|
|
101
|
-
const manifest = await this.workspaceManager.getManifest();
|
|
102
|
-
if (!manifest) {
|
|
103
|
-
throw new Error(`External dependency '${specifier}' requires model.yaml.\n` +
|
|
104
|
-
`Hint: Create model.yaml and add the dependency:\n` +
|
|
105
|
-
` dependencies:\n` +
|
|
106
|
-
` ${specifier}:\n` +
|
|
107
|
-
` ref: v1.0.0`);
|
|
108
|
-
}
|
|
109
|
-
const lock = await this.workspaceManager.getLockFile();
|
|
110
|
-
if (!lock) {
|
|
111
|
-
throw new Error(`Dependency '${specifier}' not installed.\n` +
|
|
112
|
-
`Hint: Run 'dlang install' to fetch dependencies and generate model.lock.`);
|
|
113
|
-
}
|
|
114
|
-
const mapped = await this.workspaceManager.resolveDependencyImport(specifier);
|
|
115
|
-
if (!mapped) {
|
|
116
|
-
throw new Error(`Dependency '${specifier}' not found in model.yaml.\n` +
|
|
117
|
-
`Hint: Add it to your dependencies:\n` +
|
|
118
|
-
` dependencies:\n` +
|
|
119
|
-
` ${specifier}:\n` +
|
|
120
|
-
` ref: v1.0.0`);
|
|
121
|
-
}
|
|
122
|
-
const git = await this.workspaceManager.getGitResolver();
|
|
123
|
-
return git.resolve(mapped, { allowNetwork: false });
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Resolves a local path using directory-first resolution.
|
|
127
|
-
*
|
|
128
|
-
* Per PRS-010 (updated design):
|
|
129
|
-
* - If path ends with .dlang → direct file import
|
|
130
|
-
* - If no extension → directory-first:
|
|
131
|
-
* 1. Try ./path/index.dlang (module default, no model.yaml required)
|
|
132
|
-
* 2. Try ./path.dlang (file fallback)
|
|
133
|
-
*/
|
|
134
|
-
async resolveLocalPath(resolved, original) {
|
|
135
|
-
const ext = path.extname(resolved);
|
|
136
|
-
if (ext === '.dlang') {
|
|
137
|
-
// Direct file import
|
|
138
|
-
await assertFileExists(resolved, original);
|
|
139
|
-
return URI.file(resolved);
|
|
140
|
-
}
|
|
141
|
-
if (ext && ext !== '.dlang') {
|
|
142
|
-
throw new Error(`Invalid file extension '${ext}' in import '${original}'.\n` +
|
|
143
|
-
`Hint: DomainLang files must use the .dlang extension.`);
|
|
144
|
-
}
|
|
145
|
-
// No extension → directory-first resolution
|
|
146
|
-
return this.resolveDirectoryFirst(resolved, original);
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Directory-first resolution: ./types → ./types/index.dlang → ./types.dlang
|
|
150
|
-
*
|
|
151
|
-
* Module entry defaults to index.dlang without requiring model.yaml.
|
|
152
|
-
* If the directory has model.yaml with custom entry, use that.
|
|
153
|
-
*/
|
|
154
|
-
async resolveDirectoryFirst(resolved, original) {
|
|
155
|
-
// Step 1: Check if directory exists with index.dlang (or custom entry)
|
|
156
|
-
const isDirectory = await this.isDirectory(resolved);
|
|
157
|
-
if (isDirectory) {
|
|
158
|
-
// Check for model.yaml to get custom entry point
|
|
159
|
-
const moduleManifestPath = path.join(resolved, 'model.yaml');
|
|
160
|
-
const entryPoint = await this.readModuleEntry(moduleManifestPath);
|
|
161
|
-
const entryFile = path.join(resolved, entryPoint);
|
|
162
|
-
if (await this.fileExists(entryFile)) {
|
|
163
|
-
return URI.file(entryFile);
|
|
164
|
-
}
|
|
165
|
-
// Directory exists but no entry file
|
|
166
|
-
throw new Error(`Module '${original}' is missing its entry file.\n` +
|
|
167
|
-
`Expected: ${resolved}/${entryPoint}\n` +
|
|
168
|
-
`Hint: Create '${entryPoint}' in the module directory, or specify a custom entry in model.yaml:\n` +
|
|
169
|
-
` model:\n` +
|
|
170
|
-
` entry: main.dlang`);
|
|
171
|
-
}
|
|
172
|
-
// Step 2: Try .dlang file fallback
|
|
173
|
-
const fileWithExt = `${resolved}.dlang`;
|
|
174
|
-
if (await this.fileExists(fileWithExt)) {
|
|
175
|
-
return URI.file(fileWithExt);
|
|
176
|
-
}
|
|
177
|
-
// Neither directory nor file found
|
|
178
|
-
throw new Error(`Cannot resolve import '${original}'.\n` +
|
|
179
|
-
`Tried:\n` +
|
|
180
|
-
` • ${resolved}/index.dlang (directory module)\n` +
|
|
181
|
-
` • ${resolved}.dlang (file)\n` +
|
|
182
|
-
`Hint: Check that the path is correct and the file exists.`);
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Reads the entry point from a module's model.yaml.
|
|
186
|
-
* Defaults to index.dlang if no manifest or no entry specified.
|
|
187
|
-
*/
|
|
188
|
-
async readModuleEntry(manifestPath) {
|
|
189
|
-
try {
|
|
190
|
-
const content = await fs.readFile(manifestPath, 'utf-8');
|
|
191
|
-
const YAML = await import('yaml');
|
|
192
|
-
const manifest = YAML.parse(content);
|
|
193
|
-
return manifest?.model?.entry ?? 'index.dlang';
|
|
194
|
-
}
|
|
195
|
-
catch {
|
|
196
|
-
return 'index.dlang';
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Checks if a path is a directory.
|
|
201
|
-
*/
|
|
202
|
-
async isDirectory(targetPath) {
|
|
203
|
-
try {
|
|
204
|
-
const stat = await fs.stat(targetPath);
|
|
205
|
-
return stat.isDirectory();
|
|
206
|
-
}
|
|
207
|
-
catch {
|
|
208
|
-
return false;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Checks if a file exists.
|
|
213
|
-
*/
|
|
214
|
-
async fileExists(filePath) {
|
|
215
|
-
try {
|
|
216
|
-
await fs.access(filePath);
|
|
217
|
-
return true;
|
|
218
|
-
}
|
|
219
|
-
catch {
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
11
|
+
async resolveImport(importUrl) {
|
|
12
|
+
const gitResolver = await this.workspaceManager.getGitResolver();
|
|
13
|
+
return gitResolver.resolve(importUrl);
|
|
222
14
|
}
|
|
223
15
|
/**
|
|
224
16
|
* Get the current lock file (if loaded).
|
|
@@ -227,14 +19,4 @@ export class ImportResolver {
|
|
|
227
19
|
return this.workspaceManager.getLockFile();
|
|
228
20
|
}
|
|
229
21
|
}
|
|
230
|
-
async function assertFileExists(filePath, original) {
|
|
231
|
-
try {
|
|
232
|
-
await fs.access(filePath);
|
|
233
|
-
}
|
|
234
|
-
catch {
|
|
235
|
-
throw new Error(`Import file not found: '${original}'.\\n` +
|
|
236
|
-
`Resolved path: ${filePath}\\n` +
|
|
237
|
-
`Hint: Check that the file exists and the path is correct.`);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
22
|
//# sourceMappingURL=import-resolver.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-resolver.js","sourceRoot":"","sources":["../../src/services/import-resolver.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"import-resolver.js","sourceRoot":"","sources":["../../src/services/import-resolver.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,MAAM,OAAO,cAAc;IAGvB,YAAY,QAA4B;QACpC,IAAI,CAAC,gBAAgB,GAAG,QAAQ,CAAC,OAAO,CAAC,gBAAgB,CAAC;IAC9D,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa,CAAC,SAAiB;QACjC,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,gBAAgB,CAAC,cAAc,EAAE,CAAC;QACjE,OAAO,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC1C,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,WAAW;QACb,OAAO,IAAI,CAAC,gBAAgB,CAAC,WAAW,EAAE,CAAC;IAC/C,CAAC;CACJ"}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { GitUrlResolver } from './git-url-resolver.js';
|
|
2
|
-
import type { LockFile
|
|
2
|
+
import type { LockFile } from './git-url-resolver.js';
|
|
3
|
+
export interface WorkspaceManagerOptions {
|
|
4
|
+
readonly autoResolve?: boolean;
|
|
5
|
+
readonly manifestFiles?: readonly string[];
|
|
6
|
+
readonly lockFiles?: readonly string[];
|
|
7
|
+
}
|
|
3
8
|
/**
|
|
4
9
|
* Coordinates workspace discovery, lock file lifecycle management, and git resolver configuration.
|
|
5
10
|
*/
|
|
@@ -28,11 +33,6 @@ export declare class WorkspaceManager {
|
|
|
28
33
|
* Resolves the manifest file path within the workspace, if present.
|
|
29
34
|
*/
|
|
30
35
|
getManifestPath(): Promise<string | undefined>;
|
|
31
|
-
/**
|
|
32
|
-
* Returns the parsed manifest when present, otherwise undefined.
|
|
33
|
-
* Uses cached contents when unchanged on disk.
|
|
34
|
-
*/
|
|
35
|
-
getManifest(): Promise<ModelManifest | undefined>;
|
|
36
36
|
/**
|
|
37
37
|
* Returns the cached lock file or triggers resolution when missing.
|
|
38
38
|
*/
|
|
@@ -45,24 +45,6 @@ export declare class WorkspaceManager {
|
|
|
45
45
|
* Reloads the lock file from disk, updating the git resolver.
|
|
46
46
|
*/
|
|
47
47
|
refreshLockFile(): Promise<LockFile | undefined>;
|
|
48
|
-
/**
|
|
49
|
-
* Invalidates all cached data (manifest and lock file).
|
|
50
|
-
* Call this when config files change externally (e.g., from CLI commands).
|
|
51
|
-
*
|
|
52
|
-
* After invalidation, the next call to getManifest() or getLockFile()
|
|
53
|
-
* will re-read from disk.
|
|
54
|
-
*/
|
|
55
|
-
invalidateCache(): void;
|
|
56
|
-
/**
|
|
57
|
-
* Invalidates only the manifest cache.
|
|
58
|
-
* Call this when model.yaml changes.
|
|
59
|
-
*/
|
|
60
|
-
invalidateManifestCache(): void;
|
|
61
|
-
/**
|
|
62
|
-
* Invalidates only the lock file cache.
|
|
63
|
-
* Call this when model.lock changes.
|
|
64
|
-
*/
|
|
65
|
-
invalidateLockCache(): void;
|
|
66
48
|
/**
|
|
67
49
|
* Provides the shared git URL resolver configured with the current lock file.
|
|
68
50
|
*/
|
|
@@ -72,25 +54,12 @@ export declare class WorkspaceManager {
|
|
|
72
54
|
*/
|
|
73
55
|
regenerateLockFile(): Promise<LockFile>;
|
|
74
56
|
/**
|
|
75
|
-
*
|
|
76
|
-
*/
|
|
77
|
-
getPathAliases(): Promise<PathAliases | undefined>;
|
|
78
|
-
/**
|
|
79
|
-
* Normalizes a dependency entry to its extended form.
|
|
80
|
-
* Handles both short form (string version) and extended form (object).
|
|
81
|
-
*
|
|
82
|
-
* In the new format, the key IS the owner/package, so source is derived from key
|
|
83
|
-
* ONLY for git dependencies (not for path-based local dependencies).
|
|
84
|
-
*/
|
|
85
|
-
private normalizeDependency;
|
|
86
|
-
/**
|
|
87
|
-
* Resolves a manifest dependency to its git import string.
|
|
57
|
+
* Resolves a manifest dependency alias to its git import string.
|
|
88
58
|
*
|
|
89
|
-
*
|
|
90
|
-
* @
|
|
91
|
-
* @returns Resolved git import string or undefined when not found
|
|
59
|
+
* @param aliasPath - Alias from import statement (may include subpaths)
|
|
60
|
+
* @returns Resolved git import string or undefined when alias is unknown
|
|
92
61
|
*/
|
|
93
|
-
resolveDependencyImport(
|
|
62
|
+
resolveDependencyImport(aliasPath: string): Promise<string | undefined>;
|
|
94
63
|
private performInitialization;
|
|
95
64
|
private ensureInitialized;
|
|
96
65
|
private applyLockFile;
|
|
@@ -100,22 +69,6 @@ export declare class WorkspaceManager {
|
|
|
100
69
|
private loadLockFileFromDisk;
|
|
101
70
|
private tryReadLockFile;
|
|
102
71
|
private loadManifest;
|
|
103
|
-
/**
|
|
104
|
-
* Validates manifest structure and dependency configurations.
|
|
105
|
-
* Throws detailed errors for invalid manifests.
|
|
106
|
-
*
|
|
107
|
-
* Supports both new format (owner/package: version) and extended format.
|
|
108
|
-
*/
|
|
109
|
-
private validateManifest;
|
|
110
|
-
/**
|
|
111
|
-
* Validates path aliases for security and correctness.
|
|
112
|
-
*/
|
|
113
|
-
private validatePathAliases;
|
|
114
|
-
/**
|
|
115
|
-
* Validates local path dependencies for security.
|
|
116
|
-
* Ensures paths don't escape workspace boundary.
|
|
117
|
-
*/
|
|
118
|
-
private validateLocalPath;
|
|
119
72
|
private parseJsonLockFile;
|
|
120
73
|
private findWorkspaceRoot;
|
|
121
74
|
private containsManifest;
|
|
@@ -57,14 +57,6 @@ export class WorkspaceManager {
|
|
|
57
57
|
}
|
|
58
58
|
return undefined;
|
|
59
59
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Returns the parsed manifest when present, otherwise undefined.
|
|
62
|
-
* Uses cached contents when unchanged on disk.
|
|
63
|
-
*/
|
|
64
|
-
async getManifest() {
|
|
65
|
-
await this.ensureInitialized();
|
|
66
|
-
return this.loadManifest();
|
|
67
|
-
}
|
|
68
60
|
/**
|
|
69
61
|
* Returns the cached lock file or triggers resolution when missing.
|
|
70
62
|
*/
|
|
@@ -78,16 +70,11 @@ export class WorkspaceManager {
|
|
|
78
70
|
this.lockFile = cached;
|
|
79
71
|
}
|
|
80
72
|
else {
|
|
81
|
-
if (this.options.allowNetwork === false) {
|
|
82
|
-
throw new Error('Lock file (model.lock) not found and network access is disabled.\n' +
|
|
83
|
-
'Hint: Run \'dlang install\' to generate the lock file.');
|
|
84
|
-
}
|
|
85
73
|
await this.generateLockFile();
|
|
86
74
|
}
|
|
87
75
|
}
|
|
88
76
|
if (!this.lockFile) {
|
|
89
|
-
throw new Error('Unable to resolve workspace lock file
|
|
90
|
-
'Hint: Ensure model.yaml exists and run \'dlang install\' to generate model.lock.');
|
|
77
|
+
throw new Error('Unable to resolve workspace lock file.');
|
|
91
78
|
}
|
|
92
79
|
return this.lockFile;
|
|
93
80
|
}
|
|
@@ -107,38 +94,6 @@ export class WorkspaceManager {
|
|
|
107
94
|
this.applyLockFile(loaded);
|
|
108
95
|
return this.lockFile;
|
|
109
96
|
}
|
|
110
|
-
/**
|
|
111
|
-
* Invalidates all cached data (manifest and lock file).
|
|
112
|
-
* Call this when config files change externally (e.g., from CLI commands).
|
|
113
|
-
*
|
|
114
|
-
* After invalidation, the next call to getManifest() or getLockFile()
|
|
115
|
-
* will re-read from disk.
|
|
116
|
-
*/
|
|
117
|
-
invalidateCache() {
|
|
118
|
-
this.manifestCache = undefined;
|
|
119
|
-
this.lockFile = undefined;
|
|
120
|
-
// Re-apply undefined to git resolver to clear its lock file
|
|
121
|
-
if (this.gitResolver) {
|
|
122
|
-
this.gitResolver.setLockFile(undefined);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Invalidates only the manifest cache.
|
|
127
|
-
* Call this when model.yaml changes.
|
|
128
|
-
*/
|
|
129
|
-
invalidateManifestCache() {
|
|
130
|
-
this.manifestCache = undefined;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Invalidates only the lock file cache.
|
|
134
|
-
* Call this when model.lock changes.
|
|
135
|
-
*/
|
|
136
|
-
invalidateLockCache() {
|
|
137
|
-
this.lockFile = undefined;
|
|
138
|
-
if (this.gitResolver) {
|
|
139
|
-
this.gitResolver.setLockFile(undefined);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
97
|
/**
|
|
143
98
|
* Provides the shared git URL resolver configured with the current lock file.
|
|
144
99
|
*/
|
|
@@ -161,79 +116,42 @@ export class WorkspaceManager {
|
|
|
161
116
|
return this.lockFile;
|
|
162
117
|
}
|
|
163
118
|
/**
|
|
164
|
-
*
|
|
165
|
-
*/
|
|
166
|
-
async getPathAliases() {
|
|
167
|
-
const manifest = await this.getManifest();
|
|
168
|
-
return manifest?.paths;
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Normalizes a dependency entry to its extended form.
|
|
172
|
-
* Handles both short form (string version) and extended form (object).
|
|
119
|
+
* Resolves a manifest dependency alias to its git import string.
|
|
173
120
|
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
121
|
+
* @param aliasPath - Alias from import statement (may include subpaths)
|
|
122
|
+
* @returns Resolved git import string or undefined when alias is unknown
|
|
176
123
|
*/
|
|
177
|
-
|
|
178
|
-
if (typeof dep === 'string') {
|
|
179
|
-
// Short form: "owner/package": "v1.0.0" or "main"
|
|
180
|
-
// Key is the source (owner/package format)
|
|
181
|
-
return { source: key, ref: dep };
|
|
182
|
-
}
|
|
183
|
-
// Extended form:
|
|
184
|
-
// - If has source: use as-is
|
|
185
|
-
// - If has path: it's a local dep, don't set source
|
|
186
|
-
// - If neither: derive source from key (owner/package becomes source)
|
|
187
|
-
if (dep.source || dep.path) {
|
|
188
|
-
return dep;
|
|
189
|
-
}
|
|
190
|
-
return { ...dep, source: key };
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Resolves a manifest dependency to its git import string.
|
|
194
|
-
*
|
|
195
|
-
* NEW FORMAT (PRS-010): Dependencies are keyed by owner/package directly
|
|
196
|
-
* @param specifier - Import specifier (owner/package format, may include subpaths)
|
|
197
|
-
* @returns Resolved git import string or undefined when not found
|
|
198
|
-
*/
|
|
199
|
-
async resolveDependencyImport(specifier) {
|
|
124
|
+
async resolveDependencyImport(aliasPath) {
|
|
200
125
|
await this.ensureInitialized();
|
|
201
126
|
const manifest = await this.loadManifest();
|
|
202
127
|
const dependencies = manifest?.dependencies;
|
|
203
128
|
if (!dependencies) {
|
|
204
129
|
return undefined;
|
|
205
130
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
for (const [key, dep] of Object.entries(dependencies)) {
|
|
209
|
-
const normalized = this.normalizeDependency(key, dep);
|
|
210
|
-
// Skip path-based dependencies (handled by path aliases)
|
|
211
|
-
if (normalized.path) {
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
if (!normalized.source) {
|
|
131
|
+
for (const [alias, dep] of Object.entries(dependencies)) {
|
|
132
|
+
if (!dep?.source) {
|
|
215
133
|
continue;
|
|
216
134
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
? (ref.startsWith('@') ? ref : `@${ref}`)
|
|
135
|
+
if (aliasPath === alias || aliasPath.startsWith(`${alias}/`)) {
|
|
136
|
+
const suffix = aliasPath.slice(alias.length);
|
|
137
|
+
const version = dep.version ?? '';
|
|
138
|
+
const versionSegment = version
|
|
139
|
+
? (version.startsWith('@') ? version : `@${version}`)
|
|
223
140
|
: '';
|
|
224
|
-
return `${
|
|
141
|
+
return `${dep.source}${versionSegment}${suffix}`;
|
|
225
142
|
}
|
|
226
143
|
}
|
|
227
144
|
return undefined;
|
|
228
145
|
}
|
|
229
146
|
async performInitialization(startPath) {
|
|
230
|
-
this.workspaceRoot = await this.findWorkspaceRoot(startPath)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
147
|
+
this.workspaceRoot = await this.findWorkspaceRoot(startPath);
|
|
148
|
+
if (!this.workspaceRoot) {
|
|
149
|
+
throw new Error('Workspace root (directory with model.yaml) not found.');
|
|
150
|
+
}
|
|
151
|
+
this.gitResolver = new GitUrlResolver();
|
|
234
152
|
const loaded = await this.loadLockFileFromDisk();
|
|
235
153
|
this.applyLockFile(loaded);
|
|
236
|
-
if (!this.lockFile && this.options.autoResolve !== false
|
|
154
|
+
if (!this.lockFile && this.options.autoResolve !== false) {
|
|
237
155
|
await this.generateLockFile();
|
|
238
156
|
}
|
|
239
157
|
}
|
|
@@ -332,8 +250,6 @@ export class WorkspaceManager {
|
|
|
332
250
|
}
|
|
333
251
|
const content = await fs.readFile(manifestPath, 'utf-8');
|
|
334
252
|
const manifest = (YAML.parse(content) ?? {});
|
|
335
|
-
// Validate manifest structure
|
|
336
|
-
this.validateManifest(manifest, manifestPath);
|
|
337
253
|
this.manifestCache = {
|
|
338
254
|
manifest,
|
|
339
255
|
path: manifestPath,
|
|
@@ -349,98 +265,16 @@ export class WorkspaceManager {
|
|
|
349
265
|
throw error;
|
|
350
266
|
}
|
|
351
267
|
}
|
|
352
|
-
/**
|
|
353
|
-
* Validates manifest structure and dependency configurations.
|
|
354
|
-
* Throws detailed errors for invalid manifests.
|
|
355
|
-
*
|
|
356
|
-
* Supports both new format (owner/package: version) and extended format.
|
|
357
|
-
*/
|
|
358
|
-
validateManifest(manifest, manifestPath) {
|
|
359
|
-
// Validate path aliases
|
|
360
|
-
if (manifest.paths) {
|
|
361
|
-
this.validatePathAliases(manifest.paths, manifestPath);
|
|
362
|
-
}
|
|
363
|
-
if (!manifest.dependencies) {
|
|
364
|
-
return; // No dependencies to validate
|
|
365
|
-
}
|
|
366
|
-
for (const [key, dep] of Object.entries(manifest.dependencies)) {
|
|
367
|
-
const normalized = this.normalizeDependency(key, dep);
|
|
368
|
-
// Validate mutually exclusive source and path
|
|
369
|
-
if (normalized.source && normalized.path) {
|
|
370
|
-
throw new Error(`Invalid dependency '${key}' in ${manifestPath}:\n` +
|
|
371
|
-
`Cannot specify both 'source' and 'path'.\n` +
|
|
372
|
-
`Hint: Use 'source' for git dependencies or 'path' for local workspace dependencies.`);
|
|
373
|
-
}
|
|
374
|
-
// For string format, source is always derived from key (valid)
|
|
375
|
-
// For extended format without source or path, error
|
|
376
|
-
if (typeof dep !== 'string' && !normalized.source && !normalized.path) {
|
|
377
|
-
throw new Error(`Invalid dependency '${key}' in ${manifestPath}:\n` +
|
|
378
|
-
`Must specify either 'source' or 'path'.\n` +
|
|
379
|
-
`Hint: Add 'source: owner/repo' for git dependencies, or 'path: ./local/path' for local packages.`);
|
|
380
|
-
}
|
|
381
|
-
// Validate path is relative and within workspace
|
|
382
|
-
if (normalized.path) {
|
|
383
|
-
this.validateLocalPath(normalized.path, key, manifestPath);
|
|
384
|
-
}
|
|
385
|
-
// Validate source has ref when specified
|
|
386
|
-
if (normalized.source && !normalized.ref) {
|
|
387
|
-
throw new Error(`Invalid dependency '${key}' in ${manifestPath}:\n` +
|
|
388
|
-
`Git dependencies must specify a 'ref' (git reference).\n` +
|
|
389
|
-
`Hint: Add 'ref: v1.0.0' (tag), 'ref: main' (branch), or a commit SHA.`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
/**
|
|
394
|
-
* Validates path aliases for security and correctness.
|
|
395
|
-
*/
|
|
396
|
-
validatePathAliases(paths, manifestPath) {
|
|
397
|
-
for (const [alias, targetPath] of Object.entries(paths)) {
|
|
398
|
-
// Validate alias starts with @
|
|
399
|
-
if (!alias.startsWith('@')) {
|
|
400
|
-
throw new Error(`Invalid path alias '${alias}' in ${manifestPath}:\n` +
|
|
401
|
-
`Path aliases must start with '@'.\n` +
|
|
402
|
-
`Hint: Rename to '@${alias}' in your model.yaml paths section.`);
|
|
403
|
-
}
|
|
404
|
-
// Validate target path doesn't escape workspace
|
|
405
|
-
this.validateLocalPath(targetPath, alias, manifestPath);
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* Validates local path dependencies for security.
|
|
410
|
-
* Ensures paths don't escape workspace boundary.
|
|
411
|
-
*/
|
|
412
|
-
validateLocalPath(localPath, alias, manifestPath) {
|
|
413
|
-
// Reject absolute paths
|
|
414
|
-
if (path.isAbsolute(localPath)) {
|
|
415
|
-
throw new Error(`Invalid local path '${alias}' in ${manifestPath}:\n` +
|
|
416
|
-
`Cannot use absolute path '${localPath}'.\n` +
|
|
417
|
-
`Hint: Use relative paths (e.g., './lib', '../shared') for local dependencies.`);
|
|
418
|
-
}
|
|
419
|
-
// Resolve path relative to manifest directory
|
|
420
|
-
const manifestDir = path.dirname(manifestPath);
|
|
421
|
-
const resolvedPath = path.resolve(manifestDir, localPath);
|
|
422
|
-
const workspaceRoot = this.workspaceRoot || manifestDir;
|
|
423
|
-
// Check if resolved path is within workspace
|
|
424
|
-
const relativePath = path.relative(workspaceRoot, resolvedPath);
|
|
425
|
-
if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
|
|
426
|
-
throw new Error(`Invalid local path '${alias}' in ${manifestPath}:\n` +
|
|
427
|
-
`Path '${localPath}' resolves outside workspace boundary.\n` +
|
|
428
|
-
`Resolved: ${resolvedPath}\n` +
|
|
429
|
-
`Workspace: ${workspaceRoot}\n` +
|
|
430
|
-
`Hint: Local dependencies must be within the workspace. Consider moving the dependency or using a git-based source.`);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
268
|
parseJsonLockFile(content) {
|
|
434
269
|
const parsed = JSON.parse(content);
|
|
435
270
|
const version = typeof parsed.version === 'string' ? parsed.version : '1';
|
|
436
271
|
const dependencies = {};
|
|
437
272
|
for (const [key, value] of Object.entries(parsed.dependencies ?? {})) {
|
|
438
|
-
if (!value || typeof value.
|
|
273
|
+
if (!value || typeof value.version !== 'string' || typeof value.resolved !== 'string' || typeof value.commit !== 'string') {
|
|
439
274
|
continue;
|
|
440
275
|
}
|
|
441
276
|
dependencies[key] = {
|
|
442
|
-
|
|
443
|
-
refType: value.refType ?? 'commit', // Default to commit for backwards compatibility
|
|
277
|
+
version: value.version,
|
|
444
278
|
resolved: value.resolved,
|
|
445
279
|
commit: value.commit,
|
|
446
280
|
integrity: value.integrity,
|