@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
|
@@ -9,33 +9,56 @@
|
|
|
9
9
|
* 2. Download all direct dependencies
|
|
10
10
|
* 3. Parse each dependency's model.yaml
|
|
11
11
|
* 4. Recursively discover transitive dependencies
|
|
12
|
-
* 5. Resolve version constraints
|
|
12
|
+
* 5. Resolve version constraints (simple: use latest satisfying version)
|
|
13
13
|
* 6. Generate lock file with pinned commit hashes
|
|
14
|
-
*
|
|
15
|
-
* Resolution Strategy ("Latest Wins"):
|
|
16
|
-
* - SemVer tags (same major): Pick highest compatible version
|
|
17
|
-
* - Same branch: No conflict, resolve once
|
|
18
|
-
* - Commit pins: Error (explicit pins are intentional)
|
|
19
|
-
* - Major version mismatch: Error
|
|
20
|
-
* - Tag vs Branch: Error (incompatible intent)
|
|
21
14
|
*/
|
|
22
15
|
|
|
23
16
|
import path from 'node:path';
|
|
24
17
|
import fs from 'node:fs/promises';
|
|
25
18
|
import YAML from 'yaml';
|
|
26
|
-
import { GitUrlParser, GitUrlResolver } from './git-url-resolver.js';
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
import { GitUrlParser, GitUrlResolver, PackageMetadata, LockFile, LockedDependency } from './git-url-resolver.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Dependency graph node representing a package and its dependencies.
|
|
23
|
+
*/
|
|
24
|
+
export interface DependencyNode {
|
|
25
|
+
/** Package identifier (owner/repo) */
|
|
26
|
+
packageKey: string;
|
|
27
|
+
/** Version constraint from parent (e.g., "^1.0.0") */
|
|
28
|
+
versionConstraint: string;
|
|
29
|
+
/** Resolved version */
|
|
30
|
+
resolvedVersion?: string;
|
|
31
|
+
/** Resolved commit hash */
|
|
32
|
+
commitHash?: string;
|
|
33
|
+
/** Full git URL */
|
|
34
|
+
repoUrl?: string;
|
|
35
|
+
/** Direct dependencies of this package */
|
|
36
|
+
dependencies: Record<string, string>;
|
|
37
|
+
/** Parent packages that depend on this one */
|
|
38
|
+
dependents: string[];
|
|
39
|
+
}
|
|
29
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Dependency graph for the entire workspace.
|
|
43
|
+
*/
|
|
44
|
+
export interface DependencyGraph {
|
|
45
|
+
/** All discovered packages, keyed by owner/repo */
|
|
46
|
+
nodes: Record<string, DependencyNode>;
|
|
47
|
+
/** Root package name */
|
|
48
|
+
root: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolves dependencies and generates lock files.
|
|
53
|
+
*/
|
|
54
|
+
export type { LockFile, PackageMetadata } from './git-url-resolver.js';
|
|
30
55
|
export class DependencyResolver {
|
|
31
56
|
private gitResolver: GitUrlResolver;
|
|
32
57
|
private workspaceRoot: string;
|
|
33
58
|
|
|
34
59
|
constructor(workspaceRoot: string, gitResolver?: GitUrlResolver) {
|
|
35
60
|
this.workspaceRoot = workspaceRoot;
|
|
36
|
-
|
|
37
|
-
const cacheDir = path.join(workspaceRoot, '.dlang', 'packages');
|
|
38
|
-
this.gitResolver = gitResolver || new GitUrlResolver(cacheDir);
|
|
61
|
+
this.gitResolver = gitResolver || new GitUrlResolver();
|
|
39
62
|
}
|
|
40
63
|
|
|
41
64
|
/**
|
|
@@ -62,13 +85,6 @@ export class DependencyResolver {
|
|
|
62
85
|
// Build dependency graph
|
|
63
86
|
const graph = await this.buildDependencyGraph(rootConfig);
|
|
64
87
|
|
|
65
|
-
// Apply overrides before conflict detection
|
|
66
|
-
this.applyOverrides(graph, rootConfig.overrides);
|
|
67
|
-
|
|
68
|
-
// Detect version conflicts and package-level cycles before resolving
|
|
69
|
-
this.detectVersionConflicts(graph);
|
|
70
|
-
this.detectPackageCycles(graph);
|
|
71
|
-
|
|
72
88
|
// Resolve version constraints
|
|
73
89
|
await this.resolveVersions(graph);
|
|
74
90
|
|
|
@@ -76,59 +92,22 @@ export class DependencyResolver {
|
|
|
76
92
|
return this.generateLockFile(graph);
|
|
77
93
|
}
|
|
78
94
|
|
|
79
|
-
/**
|
|
80
|
-
* Applies ref overrides from model.yaml to resolve conflicts explicitly.
|
|
81
|
-
*
|
|
82
|
-
* Overrides take precedence over all other constraints.
|
|
83
|
-
*
|
|
84
|
-
* @example
|
|
85
|
-
* ```yaml
|
|
86
|
-
* overrides:
|
|
87
|
-
* domainlang/core: v2.0.0
|
|
88
|
-
* ```
|
|
89
|
-
*/
|
|
90
|
-
private applyOverrides(graph: DependencyGraph, overrides?: Record<string, string>): void {
|
|
91
|
-
if (!overrides) return;
|
|
92
|
-
|
|
93
|
-
for (const [pkg, overrideRef] of Object.entries(overrides)) {
|
|
94
|
-
const node = graph.nodes[pkg];
|
|
95
|
-
if (node) {
|
|
96
|
-
// Override replaces all constraints with a single definitive ref
|
|
97
|
-
node.constraints = new Set([overrideRef]);
|
|
98
|
-
node.refConstraint = overrideRef;
|
|
99
|
-
|
|
100
|
-
// Track that this was an override for messaging
|
|
101
|
-
this.overrideMessages.push(`Override applied: ${pkg}@${overrideRef}`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** Override messages for CLI output */
|
|
107
|
-
private overrideMessages: string[] = [];
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Returns any override messages from the last dependency resolution.
|
|
111
|
-
*/
|
|
112
|
-
getOverrideMessages(): string[] {
|
|
113
|
-
return this.overrideMessages;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
95
|
/**
|
|
117
96
|
* Builds the complete dependency graph by recursively discovering transitive dependencies.
|
|
118
97
|
*/
|
|
119
|
-
private async buildDependencyGraph(rootConfig:
|
|
98
|
+
private async buildDependencyGraph(rootConfig: PackageMetadata): Promise<DependencyGraph> {
|
|
120
99
|
const graph: DependencyGraph = {
|
|
121
100
|
nodes: {},
|
|
122
101
|
root: rootConfig.name || 'root',
|
|
123
102
|
};
|
|
124
103
|
|
|
125
104
|
// Process root dependencies
|
|
126
|
-
const queue: Array<{ packageKey: string;
|
|
105
|
+
const queue: Array<{ packageKey: string; versionConstraint: string; parent: string }> = [];
|
|
127
106
|
|
|
128
|
-
for (const [depName,
|
|
107
|
+
for (const [depName, versionConstraint] of Object.entries(rootConfig.dependencies || {})) {
|
|
129
108
|
queue.push({
|
|
130
109
|
packageKey: depName,
|
|
131
|
-
|
|
110
|
+
versionConstraint,
|
|
132
111
|
parent: graph.root
|
|
133
112
|
});
|
|
134
113
|
}
|
|
@@ -139,17 +118,14 @@ export class DependencyResolver {
|
|
|
139
118
|
while (queue.length > 0) {
|
|
140
119
|
const entry = queue.shift();
|
|
141
120
|
if (!entry) break;
|
|
142
|
-
const { packageKey,
|
|
121
|
+
const { packageKey, versionConstraint, parent } = entry;
|
|
143
122
|
|
|
144
123
|
// Skip if already processed
|
|
145
124
|
if (visited.has(packageKey)) {
|
|
146
|
-
// Update dependents list
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
existing.dependents.push(parent);
|
|
125
|
+
// Update dependents list
|
|
126
|
+
if (!graph.nodes[packageKey].dependents.includes(parent)) {
|
|
127
|
+
graph.nodes[packageKey].dependents.push(parent);
|
|
150
128
|
}
|
|
151
|
-
if (!existing.constraints) existing.constraints = new Set<string>();
|
|
152
|
-
existing.constraints.add(refConstraint);
|
|
153
129
|
continue;
|
|
154
130
|
}
|
|
155
131
|
visited.add(packageKey);
|
|
@@ -157,7 +133,7 @@ export class DependencyResolver {
|
|
|
157
133
|
// Parse package identifier
|
|
158
134
|
const gitInfo = GitUrlParser.parse(packageKey);
|
|
159
135
|
|
|
160
|
-
// Download package to get its
|
|
136
|
+
// Download package to get its dlang.toml
|
|
161
137
|
const packageUri = await this.gitResolver.resolve(packageKey);
|
|
162
138
|
const packageDir = path.dirname(packageUri.fsPath);
|
|
163
139
|
|
|
@@ -167,18 +143,17 @@ export class DependencyResolver {
|
|
|
167
143
|
// Add to graph
|
|
168
144
|
graph.nodes[packageKey] = {
|
|
169
145
|
packageKey,
|
|
170
|
-
|
|
171
|
-
constraints: new Set<string>([refConstraint]),
|
|
146
|
+
versionConstraint,
|
|
172
147
|
repoUrl: gitInfo.repoUrl,
|
|
173
148
|
dependencies: packageConfig.dependencies || {},
|
|
174
149
|
dependents: [parent],
|
|
175
150
|
};
|
|
176
151
|
|
|
177
152
|
// Queue transitive dependencies
|
|
178
|
-
for (const [transDepName,
|
|
153
|
+
for (const [transDepName, transVersionConstraint] of Object.entries(packageConfig.dependencies || {})) {
|
|
179
154
|
queue.push({
|
|
180
155
|
packageKey: transDepName,
|
|
181
|
-
|
|
156
|
+
versionConstraint: transVersionConstraint,
|
|
182
157
|
parent: packageKey,
|
|
183
158
|
});
|
|
184
159
|
}
|
|
@@ -188,52 +163,49 @@ export class DependencyResolver {
|
|
|
188
163
|
}
|
|
189
164
|
|
|
190
165
|
/**
|
|
191
|
-
* Resolves
|
|
166
|
+
* Resolves version constraints to specific versions.
|
|
192
167
|
*
|
|
193
|
-
* Simple algorithm: Use the
|
|
194
|
-
*
|
|
168
|
+
* Simple algorithm: Use the latest version that satisfies all constraints.
|
|
169
|
+
* Future: Implement proper semantic versioning resolution.
|
|
195
170
|
*/
|
|
196
171
|
private async resolveVersions(graph: DependencyGraph): Promise<void> {
|
|
197
172
|
for (const [packageKey, node] of Object.entries(graph.nodes)) {
|
|
198
173
|
// Parse package to get repo info
|
|
199
174
|
const gitInfo = GitUrlParser.parse(packageKey);
|
|
200
175
|
|
|
201
|
-
//
|
|
202
|
-
|
|
176
|
+
// For now, resolve to the version specified in the constraint
|
|
177
|
+
// Future: Implement proper semver range resolution
|
|
178
|
+
const version = this.extractVersionFromConstraint(node.versionConstraint);
|
|
203
179
|
|
|
204
|
-
//
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
// Resolve ref to commit hash
|
|
208
|
-
const commitHash = await this.resolveCommitHash(gitInfo.repoUrl, ref);
|
|
180
|
+
// Resolve version to commit hash
|
|
181
|
+
const commitHash = await this.resolveCommitHash(gitInfo.repoUrl, version);
|
|
209
182
|
|
|
210
|
-
node.
|
|
211
|
-
node.refType = refType;
|
|
183
|
+
node.resolvedVersion = version;
|
|
212
184
|
node.commitHash = commitHash;
|
|
213
185
|
}
|
|
214
186
|
}
|
|
215
187
|
|
|
216
188
|
/**
|
|
217
|
-
* Extracts a
|
|
189
|
+
* Extracts a version from a constraint string.
|
|
218
190
|
*
|
|
219
191
|
* Examples:
|
|
220
|
-
* - "^1.0.0" → "1.0.0"
|
|
221
|
-
* - "~2.3.0" → "2.3.0"
|
|
222
|
-
* - "1.5.0" → "1.5.0"
|
|
223
|
-
* - "main" → "main" (treated as branch)
|
|
224
|
-
* - "abc123def" → "abc123def" (treated as commit)
|
|
192
|
+
* - "^1.0.0" → "1.0.0"
|
|
193
|
+
* - "~2.3.0" → "2.3.0"
|
|
194
|
+
* - "1.5.0" → "1.5.0"
|
|
225
195
|
* - "owner/repo@1.0.0" → "1.0.0"
|
|
196
|
+
*
|
|
197
|
+
* Future: Implement proper semver range parsing and resolution.
|
|
226
198
|
*/
|
|
227
|
-
private
|
|
228
|
-
// Remove semver operators
|
|
229
|
-
let
|
|
199
|
+
private extractVersionFromConstraint(constraint: string): string {
|
|
200
|
+
// Remove semver operators
|
|
201
|
+
let version = constraint.replace(/^[\^~>=<]/, '');
|
|
230
202
|
|
|
231
|
-
// Extract
|
|
232
|
-
if (
|
|
233
|
-
|
|
203
|
+
// Extract version from full import URL if present
|
|
204
|
+
if (version.includes('@')) {
|
|
205
|
+
version = version.split('@')[1];
|
|
234
206
|
}
|
|
235
207
|
|
|
236
|
-
return
|
|
208
|
+
return version || 'main';
|
|
237
209
|
}
|
|
238
210
|
|
|
239
211
|
/**
|
|
@@ -246,7 +218,7 @@ export class DependencyResolver {
|
|
|
246
218
|
const uri = await this.gitResolver.resolve(gitInfo.original);
|
|
247
219
|
|
|
248
220
|
// Extract commit hash from cache path
|
|
249
|
-
//
|
|
221
|
+
// Cache format: ~/.dlang/cache/{platform}/{owner}/{repo}/{commit-hash}/
|
|
250
222
|
const pathParts = uri.fsPath.split(path.sep);
|
|
251
223
|
const commitHashIndex = pathParts.length - 2; // Second to last segment
|
|
252
224
|
return pathParts[commitHashIndex];
|
|
@@ -259,16 +231,12 @@ export class DependencyResolver {
|
|
|
259
231
|
const dependencies: Record<string, LockedDependency> = {};
|
|
260
232
|
|
|
261
233
|
for (const [packageKey, node] of Object.entries(graph.nodes)) {
|
|
262
|
-
if (!node.
|
|
263
|
-
throw new Error(
|
|
264
|
-
`Failed to resolve ref for '${packageKey}'.\n` +
|
|
265
|
-
`Hint: Check that the package exists and the ref is valid.`
|
|
266
|
-
);
|
|
234
|
+
if (!node.resolvedVersion || !node.commitHash) {
|
|
235
|
+
throw new Error(`Failed to resolve version for ${packageKey}`);
|
|
267
236
|
}
|
|
268
237
|
|
|
269
238
|
dependencies[packageKey] = {
|
|
270
|
-
|
|
271
|
-
refType: node.refType ?? 'commit',
|
|
239
|
+
version: node.resolvedVersion,
|
|
272
240
|
resolved: node.repoUrl || '',
|
|
273
241
|
commit: node.commitHash,
|
|
274
242
|
// Future: Calculate integrity hash
|
|
@@ -284,7 +252,7 @@ export class DependencyResolver {
|
|
|
284
252
|
/**
|
|
285
253
|
* Loads and parses a package's model.yaml file.
|
|
286
254
|
*/
|
|
287
|
-
private async loadPackageConfig(packageDir: string): Promise<
|
|
255
|
+
private async loadPackageConfig(packageDir: string): Promise<PackageMetadata> {
|
|
288
256
|
const yamlPath = path.join(packageDir, 'model.yaml');
|
|
289
257
|
|
|
290
258
|
try {
|
|
@@ -308,225 +276,40 @@ export class DependencyResolver {
|
|
|
308
276
|
* dependencies:
|
|
309
277
|
* package-name:
|
|
310
278
|
* source: owner/repo
|
|
311
|
-
*
|
|
279
|
+
* version: ^1.0.0
|
|
312
280
|
*/
|
|
313
|
-
private parseYaml(content: string):
|
|
281
|
+
private parseYaml(content: string): PackageMetadata {
|
|
314
282
|
const parsed = YAML.parse(content) as {
|
|
315
283
|
model?: {
|
|
316
284
|
name?: string;
|
|
317
285
|
version?: string;
|
|
318
286
|
entry?: string;
|
|
319
287
|
};
|
|
320
|
-
dependencies?: Record<string, { source?: string;
|
|
321
|
-
overrides?: Record<string, string>;
|
|
288
|
+
dependencies?: Record<string, { source?: string; version?: string }>;
|
|
322
289
|
};
|
|
323
290
|
|
|
324
|
-
const config:
|
|
291
|
+
const config: PackageMetadata = {};
|
|
325
292
|
|
|
326
293
|
if (parsed.model) {
|
|
327
294
|
config.name = parsed.model.name;
|
|
328
295
|
config.version = parsed.model.version;
|
|
329
|
-
config.
|
|
296
|
+
config.main = parsed.model.entry;
|
|
330
297
|
}
|
|
331
298
|
|
|
332
299
|
if (parsed.dependencies) {
|
|
333
300
|
config.dependencies = {};
|
|
334
301
|
for (const [, depInfo] of Object.entries(parsed.dependencies)) {
|
|
335
302
|
if (depInfo.source) {
|
|
336
|
-
const
|
|
337
|
-
// Store as "source@
|
|
338
|
-
config.dependencies[depInfo.source] =
|
|
303
|
+
const versionConstraint = depInfo.version || 'main';
|
|
304
|
+
// Store as "source@version" for consistency with import resolution
|
|
305
|
+
config.dependencies[depInfo.source] = versionConstraint;
|
|
339
306
|
}
|
|
340
307
|
}
|
|
341
308
|
}
|
|
342
309
|
|
|
343
|
-
// Parse overrides section for explicit ref control
|
|
344
|
-
if (parsed.overrides) {
|
|
345
|
-
config.overrides = parsed.overrides;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
310
|
return config;
|
|
349
311
|
}
|
|
350
312
|
|
|
351
|
-
/**
|
|
352
|
-
* Detects ref conflicts and applies "Latest Wins" resolution strategy.
|
|
353
|
-
*
|
|
354
|
-
* Resolution Rules:
|
|
355
|
-
* - SemVer tags (same major): Pick highest version automatically
|
|
356
|
-
* - Same branch refs: No conflict, use single resolution
|
|
357
|
-
* - Commit SHA conflicts: Error (explicit pins are intentional)
|
|
358
|
-
* - Major version mismatch: Error (breaking change)
|
|
359
|
-
* - Tag vs Branch: Error (incompatible intent)
|
|
360
|
-
*
|
|
361
|
-
* Modifies graph nodes in-place to set the resolved constraint.
|
|
362
|
-
* Throws an error only for unresolvable conflicts.
|
|
363
|
-
*/
|
|
364
|
-
private detectVersionConflicts(graph: DependencyGraph): void {
|
|
365
|
-
const resolutionMessages: string[] = [];
|
|
366
|
-
|
|
367
|
-
for (const [pkg, node] of Object.entries(graph.nodes)) {
|
|
368
|
-
const constraints = node.constraints ?? new Set<string>([node.refConstraint]);
|
|
369
|
-
|
|
370
|
-
if (constraints.size <= 1) continue; // No conflict
|
|
371
|
-
|
|
372
|
-
const refs = Array.from(constraints);
|
|
373
|
-
const refTypes = refs.map(ref => ({
|
|
374
|
-
ref,
|
|
375
|
-
type: detectRefType(ref),
|
|
376
|
-
semver: parseSemVer(ref),
|
|
377
|
-
}));
|
|
378
|
-
|
|
379
|
-
// Check for mixed types (tag vs branch vs commit)
|
|
380
|
-
const types = new Set(refTypes.map(r => r.type));
|
|
381
|
-
|
|
382
|
-
// Case 1: All commits - must be exact match
|
|
383
|
-
if (types.size === 1 && types.has('commit')) {
|
|
384
|
-
this.throwConflictError(pkg, refs, node.dependents,
|
|
385
|
-
'Explicit commit pins cannot be automatically resolved.\n' +
|
|
386
|
-
'Add an override in model.yaml:\n\n' +
|
|
387
|
-
' overrides:\n' +
|
|
388
|
-
` ${pkg}: ${refs[0]}`
|
|
389
|
-
);
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Case 2: Mixed types (tag vs branch or tag vs commit)
|
|
393
|
-
if (types.size > 1) {
|
|
394
|
-
this.throwConflictError(pkg, refs, node.dependents,
|
|
395
|
-
'Cannot mix ref types (tags, branches, commits).\n' +
|
|
396
|
-
'Add an override in model.yaml to specify which to use:\n\n' +
|
|
397
|
-
' overrides:\n' +
|
|
398
|
-
` ${pkg}: <ref>`
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
// Case 3: All branches - must be same branch
|
|
403
|
-
if (types.size === 1 && types.has('branch')) {
|
|
404
|
-
const uniqueBranches = new Set(refs);
|
|
405
|
-
if (uniqueBranches.size > 1) {
|
|
406
|
-
this.throwConflictError(pkg, refs, node.dependents,
|
|
407
|
-
'Different branch refs cannot be automatically resolved.\n' +
|
|
408
|
-
'Add an override in model.yaml:\n\n' +
|
|
409
|
-
' overrides:\n' +
|
|
410
|
-
` ${pkg}: ${refs[0]}`
|
|
411
|
-
);
|
|
412
|
-
}
|
|
413
|
-
// Same branch - no conflict, continue
|
|
414
|
-
continue;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Case 4: All SemVer tags - apply "Latest Wins"
|
|
418
|
-
const semvers = refTypes
|
|
419
|
-
.filter((r): r is typeof r & { semver: SemVer } => r.semver !== undefined)
|
|
420
|
-
.map(r => r.semver);
|
|
421
|
-
|
|
422
|
-
if (semvers.length !== refs.length) {
|
|
423
|
-
// Some refs don't parse as SemVer - can't auto-resolve
|
|
424
|
-
this.throwConflictError(pkg, refs, node.dependents,
|
|
425
|
-
'Not all refs are valid SemVer tags.\n' +
|
|
426
|
-
'Add an override in model.yaml:\n\n' +
|
|
427
|
-
' overrides:\n' +
|
|
428
|
-
` ${pkg}: <ref>`
|
|
429
|
-
);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
// Check major version compatibility
|
|
433
|
-
const majors = new Set(semvers.map(s => s.major));
|
|
434
|
-
if (majors.size > 1) {
|
|
435
|
-
const majorList = Array.from(majors).sort().join(' vs ');
|
|
436
|
-
this.throwConflictError(pkg, refs, node.dependents,
|
|
437
|
-
`Major version mismatch (v${majorList}). This may indicate breaking changes.\n` +
|
|
438
|
-
'Add an override in model.yaml if you want to force a version:\n\n' +
|
|
439
|
-
' overrides:\n' +
|
|
440
|
-
` ${pkg}: ${refs[refs.length - 1]}`
|
|
441
|
-
);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// All same major version - pick latest (Latest Wins!)
|
|
445
|
-
const latest = pickLatestSemVer(refs);
|
|
446
|
-
if (latest && latest !== node.refConstraint) {
|
|
447
|
-
// Update the node to use the resolved ref
|
|
448
|
-
node.refConstraint = latest;
|
|
449
|
-
|
|
450
|
-
// Log the resolution for user feedback
|
|
451
|
-
const otherRefs = refs.filter(r => r !== latest).join(', ');
|
|
452
|
-
resolutionMessages.push(
|
|
453
|
-
`Resolved ${pkg}: using ${latest} (satisfies ${otherRefs})`
|
|
454
|
-
);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// Store resolution messages for later output
|
|
459
|
-
if (resolutionMessages.length > 0) {
|
|
460
|
-
this.resolutionMessages = resolutionMessages;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* Throws a formatted conflict error with actionable hints.
|
|
466
|
-
*/
|
|
467
|
-
private throwConflictError(
|
|
468
|
-
pkg: string,
|
|
469
|
-
refs: string[],
|
|
470
|
-
dependents: string[],
|
|
471
|
-
hint: string
|
|
472
|
-
): never {
|
|
473
|
-
const depLines = dependents.map((d, i) =>
|
|
474
|
-
` └─ ${d} requires ${pkg}@${refs[i] || refs[0]}`
|
|
475
|
-
).join('\n');
|
|
476
|
-
|
|
477
|
-
throw new Error(
|
|
478
|
-
`Dependency ref conflict for '${pkg}'\n` +
|
|
479
|
-
depLines + '\n\n' +
|
|
480
|
-
hint
|
|
481
|
-
);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/** Resolution messages from "Latest Wins" auto-resolution */
|
|
485
|
-
private resolutionMessages: string[] = [];
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Returns any resolution messages from the last dependency resolution.
|
|
489
|
-
* Useful for CLI output to inform users about auto-resolved conflicts.
|
|
490
|
-
*/
|
|
491
|
-
getResolutionMessages(): string[] {
|
|
492
|
-
return this.resolutionMessages;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* Detects package-level cycles in the dependency graph and throws a clear error.
|
|
497
|
-
*/
|
|
498
|
-
private detectPackageCycles(graph: DependencyGraph): void {
|
|
499
|
-
const GRAY = 1, BLACK = 2;
|
|
500
|
-
const color: Record<string, number> = {};
|
|
501
|
-
const stack: string[] = [];
|
|
502
|
-
|
|
503
|
-
const visit = (pkg: string): void => {
|
|
504
|
-
color[pkg] = GRAY;
|
|
505
|
-
stack.push(pkg);
|
|
506
|
-
const deps = Object.keys(graph.nodes[pkg]?.dependencies ?? {});
|
|
507
|
-
for (const dep of deps) {
|
|
508
|
-
if (!graph.nodes[dep]) continue; // Unknown dep will resolve later
|
|
509
|
-
if (color[dep] === GRAY) {
|
|
510
|
-
// Found a back edge: cycle
|
|
511
|
-
const cycleStart = stack.indexOf(dep);
|
|
512
|
-
const cyclePath = [...stack.slice(cycleStart), dep].join(' → ');
|
|
513
|
-
throw new Error(
|
|
514
|
-
`Cyclic package dependency detected:\n` +
|
|
515
|
-
` ${cyclePath}\n\n` +
|
|
516
|
-
`Hint: Extract shared types into a separate package that both can depend on.`
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
if (color[dep] !== BLACK) visit(dep);
|
|
520
|
-
}
|
|
521
|
-
stack.pop();
|
|
522
|
-
color[pkg] = BLACK;
|
|
523
|
-
};
|
|
524
|
-
|
|
525
|
-
for (const pkg of Object.keys(graph.nodes)) {
|
|
526
|
-
if (!color[pkg]) visit(pkg);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
313
|
/**
|
|
531
314
|
* Loads an existing lock file from disk.
|
|
532
315
|
*/
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
// Browser stub for GitUrlResolver
|
|
2
|
-
// Git operations are not available in the browser environment
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
export interface GitImportInfo {
|
|
4
|
+
original: string;
|
|
5
|
+
platform: 'github' | 'gitlab' | 'bitbucket' | 'generic';
|
|
6
|
+
owner: string;
|
|
7
|
+
repo: string;
|
|
8
|
+
version: string;
|
|
9
|
+
repoUrl: string;
|
|
10
|
+
entryPoint: string;
|
|
11
|
+
}
|
|
8
12
|
|
|
9
13
|
export class GitUrlResolver {
|
|
10
14
|
constructor() {
|
|
@@ -12,11 +16,9 @@ export class GitUrlResolver {
|
|
|
12
16
|
}
|
|
13
17
|
}
|
|
14
18
|
|
|
19
|
+
|
|
15
20
|
export const GitUrlParser = {
|
|
16
|
-
parse(
|
|
17
|
-
throw new Error('GitUrlParser is not available in the browser.');
|
|
18
|
-
},
|
|
19
|
-
isGitUrl(_importStr: string): boolean {
|
|
21
|
+
parse() {
|
|
20
22
|
throw new Error('GitUrlParser is not available in the browser.');
|
|
21
23
|
}
|
|
22
24
|
};
|
|
@@ -24,3 +26,6 @@ export const GitUrlParser = {
|
|
|
24
26
|
export function loadLockFile(): void {
|
|
25
27
|
throw new Error('loadLockFile is not available in the browser.');
|
|
26
28
|
}
|
|
29
|
+
|
|
30
|
+
export type LockFile = unknown;
|
|
31
|
+
export type LockedDependency = unknown;
|