@domainlang/language 0.1.82 → 0.4.1
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 +18 -18
- package/out/domain-lang-module.d.ts +2 -0
- package/out/domain-lang-module.js +11 -3
- package/out/domain-lang-module.js.map +1 -1
- package/out/generated/ast.d.ts +8 -19
- package/out/generated/ast.js +1 -10
- package/out/generated/ast.js.map +1 -1
- package/out/generated/grammar.d.ts +1 -1
- package/out/generated/grammar.js +28 -123
- 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 +3 -0
- package/out/index.js +5 -0
- package/out/index.js.map +1 -1
- package/out/lsp/domain-lang-code-actions.d.ts +55 -0
- package/out/lsp/domain-lang-code-actions.js +143 -0
- package/out/lsp/domain-lang-code-actions.js.map +1 -0
- package/out/lsp/domain-lang-workspace-manager.d.ts +21 -0
- package/out/lsp/domain-lang-workspace-manager.js +93 -0
- package/out/lsp/domain-lang-workspace-manager.js.map +1 -0
- package/out/lsp/hover/domain-lang-hover.js +0 -4
- package/out/lsp/hover/domain-lang-hover.js.map +1 -1
- package/out/lsp/manifest-diagnostics.d.ts +82 -0
- package/out/lsp/manifest-diagnostics.js +230 -0
- package/out/lsp/manifest-diagnostics.js.map +1 -0
- package/out/sdk/index.d.ts +1 -1
- package/out/sdk/loader-node.d.ts +7 -3
- package/out/sdk/loader-node.js +24 -9
- package/out/sdk/loader-node.js.map +1 -1
- package/out/sdk/types.d.ts +0 -21
- package/out/services/dependency-analyzer.d.ts +3 -39
- package/out/services/dependency-analyzer.js +22 -47
- package/out/services/dependency-analyzer.js.map +1 -1
- package/out/services/dependency-resolver.d.ts +68 -45
- package/out/services/dependency-resolver.js +243 -43
- package/out/services/dependency-resolver.js.map +1 -1
- package/out/services/git-url-resolver.browser.d.ts +4 -12
- package/out/services/git-url-resolver.browser.js +5 -1
- package/out/services/git-url-resolver.browser.js.map +1 -1
- package/out/services/git-url-resolver.d.ts +22 -56
- package/out/services/git-url-resolver.js +70 -36
- package/out/services/git-url-resolver.js.map +1 -1
- package/out/services/governance-validator.d.ts +1 -37
- package/out/services/governance-validator.js +4 -10
- package/out/services/governance-validator.js.map +1 -1
- package/out/services/import-resolver.d.ts +65 -6
- package/out/services/import-resolver.js +223 -5
- package/out/services/import-resolver.js.map +1 -1
- package/out/services/performance-optimizer.d.ts +1 -1
- package/out/services/semver.d.ts +98 -0
- package/out/services/semver.js +195 -0
- package/out/services/semver.js.map +1 -0
- package/out/services/types.d.ts +340 -0
- package/out/services/types.js +46 -0
- package/out/services/types.js.map +1 -0
- package/out/services/workspace-manager.d.ts +57 -10
- package/out/services/workspace-manager.js +187 -21
- 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 +4 -12
- package/out/utils/import-utils.js +35 -135
- package/out/utils/import-utils.js.map +1 -1
- package/out/validation/constants.d.ts +103 -0
- package/out/validation/constants.js +141 -2
- package/out/validation/constants.js.map +1 -1
- package/out/validation/domain.js +46 -1
- package/out/validation/domain.js.map +1 -1
- package/out/validation/import.d.ts +46 -22
- package/out/validation/import.js +187 -85
- package/out/validation/import.js.map +1 -1
- package/out/validation/manifest.d.ts +144 -0
- package/out/validation/manifest.js +327 -0
- package/out/validation/manifest.js.map +1 -0
- package/out/validation/maps.js +10 -6
- package/out/validation/maps.js.map +1 -1
- package/out/validation/metadata.js +5 -1
- package/out/validation/metadata.js.map +1 -1
- package/package.json +8 -6
- package/src/domain-lang-module.ts +18 -6
- package/src/domain-lang.langium +7 -12
- package/src/generated/ast.ts +7 -20
- package/src/generated/grammar.ts +28 -123
- package/src/generated/module.ts +1 -1
- package/src/index.ts +7 -0
- package/src/lsp/domain-lang-code-actions.ts +189 -0
- package/src/lsp/domain-lang-workspace-manager.ts +104 -0
- package/src/lsp/hover/domain-lang-hover.ts +0 -2
- package/src/lsp/manifest-diagnostics.ts +290 -0
- package/src/sdk/index.ts +0 -2
- package/src/sdk/loader-node.ts +29 -9
- package/src/sdk/types.ts +0 -23
- package/src/services/dependency-analyzer.ts +24 -84
- package/src/services/dependency-resolver.ts +301 -84
- package/src/services/git-url-resolver.browser.ts +9 -14
- package/src/services/git-url-resolver.ts +86 -93
- package/src/services/governance-validator.ts +5 -47
- package/src/services/import-resolver.ts +270 -8
- package/src/services/performance-optimizer.ts +1 -1
- package/src/services/semver.ts +213 -0
- package/src/services/types.ts +415 -0
- package/src/services/workspace-manager.ts +237 -46
- package/src/utils/import-utils.ts +38 -160
- package/src/validation/constants.ts +182 -2
- package/src/validation/domain.ts +54 -1
- package/src/validation/import.ts +228 -104
- package/src/validation/manifest.ts +439 -0
- package/src/validation/maps.ts +10 -6
- package/src/validation/metadata.ts +5 -1
- package/src/syntaxes/domain-lang.monarch.ts +0 -29
|
@@ -9,56 +9,33 @@
|
|
|
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 using "Latest Wins" strategy
|
|
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)
|
|
14
21
|
*/
|
|
15
22
|
|
|
16
23
|
import path from 'node:path';
|
|
17
24
|
import fs from 'node:fs/promises';
|
|
18
25
|
import YAML from 'yaml';
|
|
19
|
-
import { GitUrlParser, GitUrlResolver
|
|
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
|
-
}
|
|
26
|
+
import { GitUrlParser, GitUrlResolver } from './git-url-resolver.js';
|
|
27
|
+
import { parseSemVer, pickLatestSemVer, detectRefType } from './semver.js';
|
|
28
|
+
import type { SemVer, ResolvingPackage, LockFile, LockedDependency, DependencyGraph } from './types.js';
|
|
40
29
|
|
|
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';
|
|
55
30
|
export class DependencyResolver {
|
|
56
31
|
private gitResolver: GitUrlResolver;
|
|
57
32
|
private workspaceRoot: string;
|
|
58
33
|
|
|
59
34
|
constructor(workspaceRoot: string, gitResolver?: GitUrlResolver) {
|
|
60
35
|
this.workspaceRoot = workspaceRoot;
|
|
61
|
-
|
|
36
|
+
// Per PRS-010: Project-local cache at .dlang/packages/
|
|
37
|
+
const cacheDir = path.join(workspaceRoot, '.dlang', 'packages');
|
|
38
|
+
this.gitResolver = gitResolver || new GitUrlResolver(cacheDir);
|
|
62
39
|
}
|
|
63
40
|
|
|
64
41
|
/**
|
|
@@ -85,6 +62,13 @@ export class DependencyResolver {
|
|
|
85
62
|
// Build dependency graph
|
|
86
63
|
const graph = await this.buildDependencyGraph(rootConfig);
|
|
87
64
|
|
|
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
|
+
|
|
88
72
|
// Resolve version constraints
|
|
89
73
|
await this.resolveVersions(graph);
|
|
90
74
|
|
|
@@ -92,22 +76,59 @@ export class DependencyResolver {
|
|
|
92
76
|
return this.generateLockFile(graph);
|
|
93
77
|
}
|
|
94
78
|
|
|
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
|
+
|
|
95
116
|
/**
|
|
96
117
|
* Builds the complete dependency graph by recursively discovering transitive dependencies.
|
|
97
118
|
*/
|
|
98
|
-
private async buildDependencyGraph(rootConfig:
|
|
119
|
+
private async buildDependencyGraph(rootConfig: ResolvingPackage): Promise<DependencyGraph> {
|
|
99
120
|
const graph: DependencyGraph = {
|
|
100
121
|
nodes: {},
|
|
101
122
|
root: rootConfig.name || 'root',
|
|
102
123
|
};
|
|
103
124
|
|
|
104
125
|
// Process root dependencies
|
|
105
|
-
const queue: Array<{ packageKey: string;
|
|
126
|
+
const queue: Array<{ packageKey: string; refConstraint: string; parent: string }> = [];
|
|
106
127
|
|
|
107
|
-
for (const [depName,
|
|
128
|
+
for (const [depName, refConstraint] of Object.entries(rootConfig.dependencies || {})) {
|
|
108
129
|
queue.push({
|
|
109
130
|
packageKey: depName,
|
|
110
|
-
|
|
131
|
+
refConstraint,
|
|
111
132
|
parent: graph.root
|
|
112
133
|
});
|
|
113
134
|
}
|
|
@@ -118,14 +139,17 @@ export class DependencyResolver {
|
|
|
118
139
|
while (queue.length > 0) {
|
|
119
140
|
const entry = queue.shift();
|
|
120
141
|
if (!entry) break;
|
|
121
|
-
const { packageKey,
|
|
142
|
+
const { packageKey, refConstraint, parent } = entry;
|
|
122
143
|
|
|
123
144
|
// Skip if already processed
|
|
124
145
|
if (visited.has(packageKey)) {
|
|
125
|
-
// Update dependents list
|
|
126
|
-
|
|
127
|
-
|
|
146
|
+
// Update dependents list and record constraint
|
|
147
|
+
const existing = graph.nodes[packageKey];
|
|
148
|
+
if (!existing.dependents.includes(parent)) {
|
|
149
|
+
existing.dependents.push(parent);
|
|
128
150
|
}
|
|
151
|
+
if (!existing.constraints) existing.constraints = new Set<string>();
|
|
152
|
+
existing.constraints.add(refConstraint);
|
|
129
153
|
continue;
|
|
130
154
|
}
|
|
131
155
|
visited.add(packageKey);
|
|
@@ -133,7 +157,7 @@ export class DependencyResolver {
|
|
|
133
157
|
// Parse package identifier
|
|
134
158
|
const gitInfo = GitUrlParser.parse(packageKey);
|
|
135
159
|
|
|
136
|
-
// Download package to get its
|
|
160
|
+
// Download package to get its model.yaml
|
|
137
161
|
const packageUri = await this.gitResolver.resolve(packageKey);
|
|
138
162
|
const packageDir = path.dirname(packageUri.fsPath);
|
|
139
163
|
|
|
@@ -143,17 +167,18 @@ export class DependencyResolver {
|
|
|
143
167
|
// Add to graph
|
|
144
168
|
graph.nodes[packageKey] = {
|
|
145
169
|
packageKey,
|
|
146
|
-
|
|
170
|
+
refConstraint,
|
|
171
|
+
constraints: new Set<string>([refConstraint]),
|
|
147
172
|
repoUrl: gitInfo.repoUrl,
|
|
148
173
|
dependencies: packageConfig.dependencies || {},
|
|
149
174
|
dependents: [parent],
|
|
150
175
|
};
|
|
151
176
|
|
|
152
177
|
// Queue transitive dependencies
|
|
153
|
-
for (const [transDepName,
|
|
178
|
+
for (const [transDepName, transRefConstraint] of Object.entries(packageConfig.dependencies || {})) {
|
|
154
179
|
queue.push({
|
|
155
180
|
packageKey: transDepName,
|
|
156
|
-
|
|
181
|
+
refConstraint: transRefConstraint,
|
|
157
182
|
parent: packageKey,
|
|
158
183
|
});
|
|
159
184
|
}
|
|
@@ -163,49 +188,52 @@ export class DependencyResolver {
|
|
|
163
188
|
}
|
|
164
189
|
|
|
165
190
|
/**
|
|
166
|
-
* Resolves
|
|
191
|
+
* Resolves ref constraints to specific commits.
|
|
167
192
|
*
|
|
168
|
-
* Simple algorithm: Use the
|
|
169
|
-
*
|
|
193
|
+
* Simple algorithm: Use the ref specified in the constraint.
|
|
194
|
+
* Detects refType (tag, branch, or commit) based on format.
|
|
170
195
|
*/
|
|
171
196
|
private async resolveVersions(graph: DependencyGraph): Promise<void> {
|
|
172
197
|
for (const [packageKey, node] of Object.entries(graph.nodes)) {
|
|
173
198
|
// Parse package to get repo info
|
|
174
199
|
const gitInfo = GitUrlParser.parse(packageKey);
|
|
175
200
|
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
const version = this.extractVersionFromConstraint(node.versionConstraint);
|
|
201
|
+
// Extract ref from constraint
|
|
202
|
+
const ref = this.extractRefFromConstraint(node.refConstraint);
|
|
179
203
|
|
|
180
|
-
//
|
|
181
|
-
const
|
|
204
|
+
// Detect ref type based on format
|
|
205
|
+
const refType = detectRefType(ref);
|
|
206
|
+
|
|
207
|
+
// Resolve ref to commit hash
|
|
208
|
+
const commitHash = await this.resolveCommitHash(gitInfo.repoUrl, ref);
|
|
182
209
|
|
|
183
|
-
node.
|
|
210
|
+
node.resolvedRef = ref;
|
|
211
|
+
node.refType = refType;
|
|
184
212
|
node.commitHash = commitHash;
|
|
185
213
|
}
|
|
186
214
|
}
|
|
187
215
|
|
|
188
216
|
/**
|
|
189
|
-
* Extracts a
|
|
217
|
+
* Extracts a ref from a constraint string.
|
|
190
218
|
*
|
|
191
219
|
* Examples:
|
|
192
|
-
* - "^1.0.0" → "1.0.0"
|
|
193
|
-
* - "~2.3.0" → "2.3.0"
|
|
194
|
-
* - "1.5.0" → "1.5.0"
|
|
220
|
+
* - "^1.0.0" → "1.0.0" (treated as tag)
|
|
221
|
+
* - "~2.3.0" → "2.3.0" (treated as tag)
|
|
222
|
+
* - "1.5.0" → "1.5.0" (treated as tag)
|
|
223
|
+
* - "main" → "main" (treated as branch)
|
|
224
|
+
* - "abc123def" → "abc123def" (treated as commit)
|
|
195
225
|
* - "owner/repo@1.0.0" → "1.0.0"
|
|
196
|
-
*
|
|
197
|
-
* Future: Implement proper semver range parsing and resolution.
|
|
198
226
|
*/
|
|
199
|
-
private
|
|
200
|
-
// Remove semver operators
|
|
201
|
-
let
|
|
227
|
+
private extractRefFromConstraint(constraint: string): string {
|
|
228
|
+
// Remove semver operators (legacy support)
|
|
229
|
+
let ref = constraint.replace(/^[\^~>=<]/, '');
|
|
202
230
|
|
|
203
|
-
// Extract
|
|
204
|
-
if (
|
|
205
|
-
|
|
231
|
+
// Extract ref from full import URL if present
|
|
232
|
+
if (ref.includes('@')) {
|
|
233
|
+
ref = ref.split('@')[1];
|
|
206
234
|
}
|
|
207
235
|
|
|
208
|
-
return
|
|
236
|
+
return ref || 'main';
|
|
209
237
|
}
|
|
210
238
|
|
|
211
239
|
/**
|
|
@@ -218,7 +246,7 @@ export class DependencyResolver {
|
|
|
218
246
|
const uri = await this.gitResolver.resolve(gitInfo.original);
|
|
219
247
|
|
|
220
248
|
// Extract commit hash from cache path
|
|
221
|
-
//
|
|
249
|
+
// Per PRS-010: Project-local cache at .dlang/packages/{owner}/{repo}/{commit}/
|
|
222
250
|
const pathParts = uri.fsPath.split(path.sep);
|
|
223
251
|
const commitHashIndex = pathParts.length - 2; // Second to last segment
|
|
224
252
|
return pathParts[commitHashIndex];
|
|
@@ -231,12 +259,16 @@ export class DependencyResolver {
|
|
|
231
259
|
const dependencies: Record<string, LockedDependency> = {};
|
|
232
260
|
|
|
233
261
|
for (const [packageKey, node] of Object.entries(graph.nodes)) {
|
|
234
|
-
if (!node.
|
|
235
|
-
throw new Error(
|
|
262
|
+
if (!node.resolvedRef || !node.commitHash) {
|
|
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
|
+
);
|
|
236
267
|
}
|
|
237
268
|
|
|
238
269
|
dependencies[packageKey] = {
|
|
239
|
-
|
|
270
|
+
ref: node.resolvedRef,
|
|
271
|
+
refType: node.refType ?? 'commit',
|
|
240
272
|
resolved: node.repoUrl || '',
|
|
241
273
|
commit: node.commitHash,
|
|
242
274
|
// Future: Calculate integrity hash
|
|
@@ -252,7 +284,7 @@ export class DependencyResolver {
|
|
|
252
284
|
/**
|
|
253
285
|
* Loads and parses a package's model.yaml file.
|
|
254
286
|
*/
|
|
255
|
-
private async loadPackageConfig(packageDir: string): Promise<
|
|
287
|
+
private async loadPackageConfig(packageDir: string): Promise<ResolvingPackage> {
|
|
256
288
|
const yamlPath = path.join(packageDir, 'model.yaml');
|
|
257
289
|
|
|
258
290
|
try {
|
|
@@ -276,40 +308,225 @@ export class DependencyResolver {
|
|
|
276
308
|
* dependencies:
|
|
277
309
|
* package-name:
|
|
278
310
|
* source: owner/repo
|
|
279
|
-
*
|
|
311
|
+
* ref: v1.0.0
|
|
280
312
|
*/
|
|
281
|
-
private parseYaml(content: string):
|
|
313
|
+
private parseYaml(content: string): ResolvingPackage {
|
|
282
314
|
const parsed = YAML.parse(content) as {
|
|
283
315
|
model?: {
|
|
284
316
|
name?: string;
|
|
285
317
|
version?: string;
|
|
286
318
|
entry?: string;
|
|
287
319
|
};
|
|
288
|
-
dependencies?: Record<string, { source?: string;
|
|
320
|
+
dependencies?: Record<string, { source?: string; ref?: string }>;
|
|
321
|
+
overrides?: Record<string, string>;
|
|
289
322
|
};
|
|
290
323
|
|
|
291
|
-
const config:
|
|
324
|
+
const config: ResolvingPackage = {};
|
|
292
325
|
|
|
293
326
|
if (parsed.model) {
|
|
294
327
|
config.name = parsed.model.name;
|
|
295
328
|
config.version = parsed.model.version;
|
|
296
|
-
config.
|
|
329
|
+
config.entry = parsed.model.entry;
|
|
297
330
|
}
|
|
298
331
|
|
|
299
332
|
if (parsed.dependencies) {
|
|
300
333
|
config.dependencies = {};
|
|
301
334
|
for (const [, depInfo] of Object.entries(parsed.dependencies)) {
|
|
302
335
|
if (depInfo.source) {
|
|
303
|
-
const
|
|
304
|
-
// Store as "source@
|
|
305
|
-
config.dependencies[depInfo.source] =
|
|
336
|
+
const refConstraint = depInfo.ref || 'main';
|
|
337
|
+
// Store as "source@ref" for consistency with import resolution
|
|
338
|
+
config.dependencies[depInfo.source] = refConstraint;
|
|
306
339
|
}
|
|
307
340
|
}
|
|
308
341
|
}
|
|
309
342
|
|
|
343
|
+
// Parse overrides section for explicit ref control
|
|
344
|
+
if (parsed.overrides) {
|
|
345
|
+
config.overrides = parsed.overrides;
|
|
346
|
+
}
|
|
347
|
+
|
|
310
348
|
return config;
|
|
311
349
|
}
|
|
312
350
|
|
|
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
|
+
|
|
313
530
|
/**
|
|
314
531
|
* Loads an existing lock file from disk.
|
|
315
532
|
*/
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
// Browser stub for GitUrlResolver
|
|
2
|
+
// Git operations are not available in the browser environment
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
repo: string;
|
|
8
|
-
version: string;
|
|
9
|
-
repoUrl: string;
|
|
10
|
-
entryPoint: string;
|
|
11
|
-
}
|
|
4
|
+
import type { GitImportInfo } from './types.js';
|
|
5
|
+
|
|
6
|
+
// Re-export the type for API consistency
|
|
7
|
+
export type { GitImportInfo } from './types.js';
|
|
12
8
|
|
|
13
9
|
export class GitUrlResolver {
|
|
14
10
|
constructor() {
|
|
@@ -16,9 +12,11 @@ export class GitUrlResolver {
|
|
|
16
12
|
}
|
|
17
13
|
}
|
|
18
14
|
|
|
19
|
-
|
|
20
15
|
export const GitUrlParser = {
|
|
21
|
-
parse() {
|
|
16
|
+
parse(_importStr: string): GitImportInfo {
|
|
17
|
+
throw new Error('GitUrlParser is not available in the browser.');
|
|
18
|
+
},
|
|
19
|
+
isGitUrl(_importStr: string): boolean {
|
|
22
20
|
throw new Error('GitUrlParser is not available in the browser.');
|
|
23
21
|
}
|
|
24
22
|
};
|
|
@@ -26,6 +24,3 @@ export const GitUrlParser = {
|
|
|
26
24
|
export function loadLockFile(): void {
|
|
27
25
|
throw new Error('loadLockFile is not available in the browser.');
|
|
28
26
|
}
|
|
29
|
-
|
|
30
|
-
export type LockFile = unknown;
|
|
31
|
-
export type LockedDependency = unknown;
|