@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.
Files changed (111) hide show
  1. package/README.md +18 -18
  2. package/out/domain-lang-module.d.ts +2 -0
  3. package/out/domain-lang-module.js +11 -3
  4. package/out/domain-lang-module.js.map +1 -1
  5. package/out/generated/ast.d.ts +8 -19
  6. package/out/generated/ast.js +1 -10
  7. package/out/generated/ast.js.map +1 -1
  8. package/out/generated/grammar.d.ts +1 -1
  9. package/out/generated/grammar.js +28 -123
  10. package/out/generated/grammar.js.map +1 -1
  11. package/out/generated/module.d.ts +1 -1
  12. package/out/generated/module.js +1 -1
  13. package/out/index.d.ts +3 -0
  14. package/out/index.js +5 -0
  15. package/out/index.js.map +1 -1
  16. package/out/lsp/domain-lang-code-actions.d.ts +55 -0
  17. package/out/lsp/domain-lang-code-actions.js +143 -0
  18. package/out/lsp/domain-lang-code-actions.js.map +1 -0
  19. package/out/lsp/domain-lang-workspace-manager.d.ts +21 -0
  20. package/out/lsp/domain-lang-workspace-manager.js +93 -0
  21. package/out/lsp/domain-lang-workspace-manager.js.map +1 -0
  22. package/out/lsp/hover/domain-lang-hover.js +0 -4
  23. package/out/lsp/hover/domain-lang-hover.js.map +1 -1
  24. package/out/lsp/manifest-diagnostics.d.ts +82 -0
  25. package/out/lsp/manifest-diagnostics.js +230 -0
  26. package/out/lsp/manifest-diagnostics.js.map +1 -0
  27. package/out/sdk/index.d.ts +1 -1
  28. package/out/sdk/loader-node.d.ts +7 -3
  29. package/out/sdk/loader-node.js +24 -9
  30. package/out/sdk/loader-node.js.map +1 -1
  31. package/out/sdk/types.d.ts +0 -21
  32. package/out/services/dependency-analyzer.d.ts +3 -39
  33. package/out/services/dependency-analyzer.js +22 -47
  34. package/out/services/dependency-analyzer.js.map +1 -1
  35. package/out/services/dependency-resolver.d.ts +68 -45
  36. package/out/services/dependency-resolver.js +243 -43
  37. package/out/services/dependency-resolver.js.map +1 -1
  38. package/out/services/git-url-resolver.browser.d.ts +4 -12
  39. package/out/services/git-url-resolver.browser.js +5 -1
  40. package/out/services/git-url-resolver.browser.js.map +1 -1
  41. package/out/services/git-url-resolver.d.ts +22 -56
  42. package/out/services/git-url-resolver.js +70 -36
  43. package/out/services/git-url-resolver.js.map +1 -1
  44. package/out/services/governance-validator.d.ts +1 -37
  45. package/out/services/governance-validator.js +4 -10
  46. package/out/services/governance-validator.js.map +1 -1
  47. package/out/services/import-resolver.d.ts +65 -6
  48. package/out/services/import-resolver.js +223 -5
  49. package/out/services/import-resolver.js.map +1 -1
  50. package/out/services/performance-optimizer.d.ts +1 -1
  51. package/out/services/semver.d.ts +98 -0
  52. package/out/services/semver.js +195 -0
  53. package/out/services/semver.js.map +1 -0
  54. package/out/services/types.d.ts +340 -0
  55. package/out/services/types.js +46 -0
  56. package/out/services/types.js.map +1 -0
  57. package/out/services/workspace-manager.d.ts +57 -10
  58. package/out/services/workspace-manager.js +187 -21
  59. package/out/services/workspace-manager.js.map +1 -1
  60. package/out/syntaxes/domain-lang.monarch.js +1 -1
  61. package/out/syntaxes/domain-lang.monarch.js.map +1 -1
  62. package/out/utils/import-utils.d.ts +4 -12
  63. package/out/utils/import-utils.js +35 -135
  64. package/out/utils/import-utils.js.map +1 -1
  65. package/out/validation/constants.d.ts +103 -0
  66. package/out/validation/constants.js +141 -2
  67. package/out/validation/constants.js.map +1 -1
  68. package/out/validation/domain.js +46 -1
  69. package/out/validation/domain.js.map +1 -1
  70. package/out/validation/import.d.ts +46 -22
  71. package/out/validation/import.js +187 -85
  72. package/out/validation/import.js.map +1 -1
  73. package/out/validation/manifest.d.ts +144 -0
  74. package/out/validation/manifest.js +327 -0
  75. package/out/validation/manifest.js.map +1 -0
  76. package/out/validation/maps.js +10 -6
  77. package/out/validation/maps.js.map +1 -1
  78. package/out/validation/metadata.js +5 -1
  79. package/out/validation/metadata.js.map +1 -1
  80. package/package.json +8 -6
  81. package/src/domain-lang-module.ts +18 -6
  82. package/src/domain-lang.langium +7 -12
  83. package/src/generated/ast.ts +7 -20
  84. package/src/generated/grammar.ts +28 -123
  85. package/src/generated/module.ts +1 -1
  86. package/src/index.ts +7 -0
  87. package/src/lsp/domain-lang-code-actions.ts +189 -0
  88. package/src/lsp/domain-lang-workspace-manager.ts +104 -0
  89. package/src/lsp/hover/domain-lang-hover.ts +0 -2
  90. package/src/lsp/manifest-diagnostics.ts +290 -0
  91. package/src/sdk/index.ts +0 -2
  92. package/src/sdk/loader-node.ts +29 -9
  93. package/src/sdk/types.ts +0 -23
  94. package/src/services/dependency-analyzer.ts +24 -84
  95. package/src/services/dependency-resolver.ts +301 -84
  96. package/src/services/git-url-resolver.browser.ts +9 -14
  97. package/src/services/git-url-resolver.ts +86 -93
  98. package/src/services/governance-validator.ts +5 -47
  99. package/src/services/import-resolver.ts +270 -8
  100. package/src/services/performance-optimizer.ts +1 -1
  101. package/src/services/semver.ts +213 -0
  102. package/src/services/types.ts +415 -0
  103. package/src/services/workspace-manager.ts +237 -46
  104. package/src/utils/import-utils.ts +38 -160
  105. package/src/validation/constants.ts +182 -2
  106. package/src/validation/domain.ts +54 -1
  107. package/src/validation/import.ts +228 -104
  108. package/src/validation/manifest.ts +439 -0
  109. package/src/validation/maps.ts +10 -6
  110. package/src/validation/metadata.ts +5 -1
  111. package/src/syntaxes/domain-lang.monarch.ts +0 -29
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Manifest Diagnostics Service for DomainLang.
3
+ *
4
+ * Provides LSP diagnostics for model.yaml files by integrating the ManifestValidator
5
+ * with the VS Code language server protocol.
6
+ *
7
+ * This service:
8
+ * - Validates model.yaml files using ManifestValidator
9
+ * - Converts ManifestDiagnostic to LSP Diagnostic format
10
+ * - Sends diagnostics to the LSP connection
11
+ *
12
+ * @module
13
+ */
14
+
15
+ import type { Connection } from 'vscode-languageserver';
16
+ import { Diagnostic, DiagnosticSeverity, Position, Range } from 'vscode-languageserver-types';
17
+ import YAML, { type Document as YAMLDocument, type YAMLMap, type Pair, isMap, isPair, isScalar } from 'yaml';
18
+ import { ManifestValidator, type ManifestDiagnostic, type ManifestSeverity } from '../validation/manifest.js';
19
+ import type { ModelManifest } from '../services/types.js';
20
+
21
+ /**
22
+ * Service for validating model.yaml and sending diagnostics via LSP.
23
+ */
24
+ export class ManifestDiagnosticsService {
25
+ private readonly validator = new ManifestValidator();
26
+ private connection: Connection | undefined;
27
+
28
+ /**
29
+ * Sets the LSP connection for sending diagnostics.
30
+ * Must be called before validateAndSendDiagnostics.
31
+ */
32
+ setConnection(connection: Connection): void {
33
+ this.connection = connection;
34
+ }
35
+
36
+ /**
37
+ * Validates a model.yaml file and sends diagnostics to the LSP connection.
38
+ *
39
+ * @param manifestUri - URI of the model.yaml file
40
+ * @param content - Raw YAML content of the file
41
+ * @param options - Validation options
42
+ */
43
+ async validateAndSendDiagnostics(
44
+ manifestUri: string,
45
+ content: string,
46
+ options?: { requirePublishable?: boolean }
47
+ ): Promise<void> {
48
+ if (!this.connection) {
49
+ return; // No connection, skip diagnostics
50
+ }
51
+
52
+ const diagnostics = this.validate(content, options);
53
+
54
+ await this.connection.sendDiagnostics({
55
+ uri: manifestUri,
56
+ diagnostics
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Validates manifest content and returns LSP diagnostics.
62
+ *
63
+ * @param content - Raw YAML content
64
+ * @param options - Validation options
65
+ * @returns Array of LSP diagnostics
66
+ */
67
+ validate(
68
+ content: string,
69
+ options?: { requirePublishable?: boolean }
70
+ ): Diagnostic[] {
71
+ // Parse YAML to get both the manifest object and source map
72
+ let yamlDoc: YAMLDocument.Parsed;
73
+ let manifest: ModelManifest;
74
+
75
+ try {
76
+ yamlDoc = YAML.parseDocument(content);
77
+
78
+ // Check for YAML parse errors (they're in the errors array, not thrown)
79
+ if (yamlDoc.errors && yamlDoc.errors.length > 0) {
80
+ return yamlDoc.errors.map(err => ({
81
+ severity: DiagnosticSeverity.Error,
82
+ range: this.yamlErrorToRange(err, content),
83
+ message: `YAML parse error: ${err.message}`,
84
+ source: 'domainlang'
85
+ }));
86
+ }
87
+
88
+ manifest = (yamlDoc.toJSON() ?? {}) as ModelManifest;
89
+ } catch (error) {
90
+ // Fallback for unexpected errors
91
+ const message = error instanceof Error ? error.message : 'Invalid YAML syntax';
92
+ return [{
93
+ severity: DiagnosticSeverity.Error,
94
+ range: Range.create(Position.create(0, 0), Position.create(0, 1)),
95
+ message: `YAML parse error: ${message}`,
96
+ source: 'domainlang'
97
+ }];
98
+ }
99
+
100
+ // Run manifest validation
101
+ const result = this.validator.validate(manifest, options);
102
+
103
+ // Convert to LSP diagnostics with source locations
104
+ return result.diagnostics.map(diag =>
105
+ this.toVSCodeDiagnostic(diag, yamlDoc)
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Converts a YAML error to an LSP Range.
111
+ */
112
+ private yamlErrorToRange(err: YAML.YAMLError, _content: string): Range {
113
+ if (err.linePos && err.linePos.length >= 1) {
114
+ const startPos = err.linePos[0];
115
+ const startLine = startPos.line - 1; // YAML uses 1-based lines
116
+ const startCol = startPos.col - 1; // YAML uses 1-based columns
117
+ const endPos = err.linePos.length >= 2 ? err.linePos[1] : undefined;
118
+ const endLine = endPos ? endPos.line - 1 : startLine;
119
+ const endCol = endPos ? endPos.col - 1 : startCol + 1;
120
+ return Range.create(
121
+ Position.create(startLine, startCol),
122
+ Position.create(endLine, endCol)
123
+ );
124
+ }
125
+ return Range.create(Position.create(0, 0), Position.create(0, 1));
126
+ }
127
+
128
+ /**
129
+ * Clears diagnostics for a manifest file.
130
+ * Call this when the file is closed or deleted.
131
+ */
132
+ async clearDiagnostics(manifestUri: string): Promise<void> {
133
+ if (!this.connection) {
134
+ return;
135
+ }
136
+
137
+ await this.connection.sendDiagnostics({
138
+ uri: manifestUri,
139
+ diagnostics: []
140
+ });
141
+ }
142
+
143
+ /**
144
+ * Converts a ManifestDiagnostic to an LSP Diagnostic.
145
+ */
146
+ private toVSCodeDiagnostic(
147
+ diag: ManifestDiagnostic,
148
+ yamlDoc: YAMLDocument.Parsed
149
+ ): Diagnostic {
150
+ const range = this.findRangeForPath(diag.path, yamlDoc);
151
+
152
+ let message = diag.message;
153
+ if (diag.hint) {
154
+ message += `\nHint: ${diag.hint}`;
155
+ }
156
+
157
+ return {
158
+ severity: this.toVSCodeSeverity(diag.severity),
159
+ range,
160
+ message,
161
+ source: 'domainlang',
162
+ code: diag.code
163
+ };
164
+ }
165
+
166
+ /**
167
+ * Converts ManifestSeverity to LSP DiagnosticSeverity.
168
+ */
169
+ private toVSCodeSeverity(severity: ManifestSeverity): DiagnosticSeverity {
170
+ switch (severity) {
171
+ case 'error':
172
+ return DiagnosticSeverity.Error;
173
+ case 'warning':
174
+ return DiagnosticSeverity.Warning;
175
+ case 'info':
176
+ return DiagnosticSeverity.Information;
177
+ default:
178
+ return DiagnosticSeverity.Warning;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Finds the source range for a YAML path like "dependencies.core.version".
184
+ * Returns a fallback range at start of file if path not found.
185
+ */
186
+ private findRangeForPath(path: string, yamlDoc: YAMLDocument.Parsed): Range {
187
+ const fallback = Range.create(Position.create(0, 0), Position.create(0, 1));
188
+
189
+ if (!yamlDoc.contents || !isMap(yamlDoc.contents)) {
190
+ return fallback;
191
+ }
192
+
193
+ const parts = path.split('.');
194
+ let currentNode: unknown = yamlDoc.contents;
195
+
196
+ for (const part of parts) {
197
+ if (!isMap(currentNode)) {
198
+ return fallback;
199
+ }
200
+
201
+ const mapNode = currentNode as YAMLMap;
202
+ const item = mapNode.items.find((pair): pair is Pair =>
203
+ isPair(pair) && isScalar(pair.key) && String(pair.key.value) === part
204
+ );
205
+
206
+ if (!item) {
207
+ return fallback;
208
+ }
209
+
210
+ // If this is the last part, return the range of the key
211
+ if (part === parts[parts.length - 1]) {
212
+ const keyNode = item.key;
213
+ if (isScalar(keyNode) && keyNode.range) {
214
+ const [start, end] = keyNode.range;
215
+ return this.offsetsToRange(start, end, yamlDoc.toString());
216
+ }
217
+ }
218
+
219
+ currentNode = item.value;
220
+ }
221
+
222
+ return fallback;
223
+ }
224
+
225
+ /**
226
+ * Converts byte offsets to a VS Code Range using line/column calculation.
227
+ */
228
+ private offsetsToRange(startOffset: number, endOffset: number, content: string): Range {
229
+ const lines = content.split('\n');
230
+ let currentOffset = 0;
231
+ let startLine = 0;
232
+ let startCol = 0;
233
+ let endLine = 0;
234
+ let endCol = 0;
235
+ let foundStart = false;
236
+ let foundEnd = false;
237
+
238
+ for (let lineNum = 0; lineNum < lines.length && !foundEnd; lineNum++) {
239
+ const lineLength = lines[lineNum].length + 1; // +1 for newline
240
+
241
+ if (!foundStart && currentOffset + lineLength > startOffset) {
242
+ startLine = lineNum;
243
+ startCol = startOffset - currentOffset;
244
+ foundStart = true;
245
+ }
246
+
247
+ if (!foundEnd && currentOffset + lineLength >= endOffset) {
248
+ endLine = lineNum;
249
+ endCol = endOffset - currentOffset;
250
+ foundEnd = true;
251
+ }
252
+
253
+ currentOffset += lineLength;
254
+ }
255
+
256
+ return Range.create(
257
+ Position.create(startLine, startCol),
258
+ Position.create(endLine, endCol)
259
+ );
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Singleton instance for use across the language server.
265
+ */
266
+ let manifestDiagnosticsService: ManifestDiagnosticsService | undefined;
267
+
268
+ /**
269
+ * Gets or creates the manifest diagnostics service singleton.
270
+ */
271
+ export function getManifestDiagnosticsService(): ManifestDiagnosticsService {
272
+ if (!manifestDiagnosticsService) {
273
+ manifestDiagnosticsService = new ManifestDiagnosticsService();
274
+ }
275
+ return manifestDiagnosticsService;
276
+ }
277
+
278
+ /**
279
+ * Helper to validate a manifest URI with the given content.
280
+ * Convenience function for use in file watchers.
281
+ */
282
+ export async function validateManifestFile(
283
+ connection: Connection,
284
+ manifestUri: string,
285
+ content: string
286
+ ): Promise<void> {
287
+ const service = getManifestDiagnosticsService();
288
+ service.setConnection(connection);
289
+ await service.validateAndSendDiagnostics(manifestUri, content);
290
+ }
package/src/sdk/index.ts CHANGED
@@ -123,6 +123,4 @@ export type {
123
123
  LoadOptions,
124
124
  BcQueryBuilder,
125
125
  RelationshipView,
126
- AugmentedBoundedContext,
127
- AugmentedDomain,
128
126
  } from './types.js';
@@ -25,14 +25,16 @@ import { isModel } from '../generated/ast.js';
25
25
  import { createDomainLangServices } from '../domain-lang-module.js';
26
26
  import type { LoadOptions, QueryContext } from './types.js';
27
27
  import { fromModel, augmentModel } from './query.js';
28
+ import { ensureImportGraphFromDocument } from '../utils/import-utils.js';
28
29
 
29
30
  /**
30
31
  * Loads a DomainLang model from a file on disk.
31
32
  *
32
33
  * **Node.js only** - uses file system APIs.
33
34
  *
34
- * Note: This loads a single file. For multi-file models with imports,
35
- * consider using the WorkspaceManager or hosting an LSP server.
35
+ * Supports multi-file models with imports: all imported files are
36
+ * automatically loaded and linked. Use `documents` in the result
37
+ * to see all loaded files.
36
38
  *
37
39
  * @param entryFile - Path to the entry .dlang file
38
40
  * @param options - Optional load configuration
@@ -43,13 +45,16 @@ import { fromModel, augmentModel } from './query.js';
43
45
  * ```typescript
44
46
  * import { loadModel } from '@domainlang/language/sdk/loader-node';
45
47
  *
46
- * const { query, model } = await loadModel('./domains.dlang', {
48
+ * const { query, model, documents } = await loadModel('./domains.dlang', {
47
49
  * workspaceDir: process.cwd()
48
50
  * });
49
51
  *
52
+ * // Query spans all imported files
50
53
  * for (const bc of query.boundedContexts()) {
51
54
  * console.log(bc.name);
52
55
  * }
56
+ *
57
+ * console.log(`Loaded ${documents.length} files`);
53
58
  * ```
54
59
  */
55
60
  export async function loadModel(
@@ -89,9 +94,19 @@ export async function loadModel(
89
94
 
90
95
  // Register document and build it
91
96
  shared.workspace.LangiumDocuments.addDocument(document);
92
- await shared.workspace.DocumentBuilder.build([document], { validation: true });
97
+ await shared.workspace.DocumentBuilder.build([document], { validation: false });
98
+
99
+ // Traverse import graph to load all imported files
100
+ const importedUris = await ensureImportGraphFromDocument(
101
+ document,
102
+ shared.workspace.LangiumDocuments
103
+ );
93
104
 
94
- // Wait for document to be fully processed
105
+ // Build all imported documents with validation
106
+ const allDocuments = Array.from(shared.workspace.LangiumDocuments.all);
107
+ await shared.workspace.DocumentBuilder.build(allDocuments, { validation: true });
108
+
109
+ // Wait for entry document to be fully processed
95
110
  if (document.state < DocumentState.Validated) {
96
111
  throw new Error(`Document not fully processed: ${absolutePath}`);
97
112
  }
@@ -112,11 +127,16 @@ export async function loadModel(
112
127
  throw new Error(`Document root is not a Model: ${entryFile}`);
113
128
  }
114
129
 
115
- // Augment AST nodes with SDK properties
116
- augmentModel(model);
130
+ // Augment AST nodes with SDK properties for all loaded models
131
+ for (const doc of allDocuments) {
132
+ const docModel = doc.parseResult.value;
133
+ if (isModel(docModel)) {
134
+ augmentModel(docModel);
135
+ }
136
+ }
117
137
 
118
- // Collect all document URIs
119
- const documentUris: URI[] = Array.from(shared.workspace.LangiumDocuments.all).map(doc => doc.uri);
138
+ // Collect all document URIs from the import graph
139
+ const documentUris: URI[] = Array.from(importedUris).map(uriStr => URI.parse(uriStr));
120
140
 
121
141
  return {
122
142
  model,
package/src/sdk/types.ts CHANGED
@@ -305,29 +305,6 @@ export interface RelationshipView {
305
305
  readonly astNode: Relationship;
306
306
  }
307
307
 
308
- /**
309
- * Augmented BoundedContext with SDK-resolved properties.
310
- * These properties are computed during model loading using precedence rules.
311
- *
312
- * Property names follow the PRS design: natural names that "just work" with
313
- * IDE autocomplete. Use `resolved*` prefix to avoid conflicts with existing
314
- * AST properties while maintaining discoverability.
315
- *
316
- * @deprecated Since SDK now uses module augmentation in ast-augmentation.ts,
317
- * this type alias is only kept for backwards compatibility. Use BoundedContext
318
- * directly after importing the SDK.
319
- */
320
- export type AugmentedBoundedContext = BoundedContext;
321
-
322
- /**
323
- * Augmented Domain with SDK-resolved properties.
324
- *
325
- * @deprecated Since SDK now uses module augmentation in ast-augmentation.ts,
326
- * this type alias is only kept for backwards compatibility. Use Domain
327
- * directly after importing the SDK.
328
- */
329
- export type AugmentedDomain = Domain;
330
-
331
308
  /**
332
309
  * Internal index structure for O(1) lookups.
333
310
  * Not exported from public API.
@@ -8,42 +8,12 @@
8
8
  * - Version policy enforcement
9
9
  */
10
10
 
11
- import type { LockFile } from './git-url-resolver.js';
11
+ import type { LockFile, DependencyTreeNode, ReverseDependency, VersionPolicy } from './types.js';
12
12
  import path from 'node:path';
13
13
  import fs from 'node:fs/promises';
14
14
  import os from 'node:os';
15
15
  import YAML from 'yaml';
16
-
17
- export interface DependencyTreeNode {
18
- /** Package identifier */
19
- packageKey: string;
20
- /** Locked version */
21
- version: string;
22
- /** Commit hash */
23
- commit: string;
24
- /** Direct dependencies */
25
- dependencies: DependencyTreeNode[];
26
- /** Depth in tree (0 = root) */
27
- depth: number;
28
- }
29
-
30
- export interface ReverseDependency {
31
- /** Package that depends on the target */
32
- dependentPackage: string;
33
- /** Version of the dependent package */
34
- version: string;
35
- /** Type: direct or transitive */
36
- type: 'direct' | 'transitive';
37
- }
38
-
39
- export interface VersionPolicy {
40
- /** Policy name: latest, stable, or specific version */
41
- policy: 'latest' | 'stable' | 'pinned';
42
- /** Resolved version */
43
- version: string;
44
- /** Available versions for policy */
45
- availableVersions?: string[];
46
- }
16
+ import { sortVersionsDescending, isPreRelease } from './semver.js';
47
17
 
48
18
  /**
49
19
  * Analyzes dependency relationships and provides visualization/analysis tools.
@@ -109,7 +79,7 @@ export class DependencyAnalyzer {
109
79
  if (visited.has(packageKey)) {
110
80
  return {
111
81
  packageKey,
112
- version: locked.version,
82
+ ref: locked.ref,
113
83
  commit: locked.commit,
114
84
  dependencies: [], // Don't recurse into already visited
115
85
  depth,
@@ -133,7 +103,7 @@ export class DependencyAnalyzer {
133
103
 
134
104
  return {
135
105
  packageKey,
136
- version: locked.version,
106
+ ref: locked.ref,
137
107
  commit: locked.commit,
138
108
  dependencies: children,
139
109
  depth,
@@ -168,7 +138,7 @@ export class DependencyAnalyzer {
168
138
  if (dep.source === targetPackage) {
169
139
  reverseDeps.push({
170
140
  dependentPackage: 'root',
171
- version: 'workspace',
141
+ ref: 'workspace',
172
142
  type: 'direct',
173
143
  });
174
144
  }
@@ -190,7 +160,7 @@ export class DependencyAnalyzer {
190
160
  if (packageDeps[targetPackage]) {
191
161
  reverseDeps.push({
192
162
  dependentPackage: packageKey,
193
- version: locked.version,
163
+ ref: locked.ref,
194
164
  type: 'direct',
195
165
  });
196
166
  }
@@ -207,11 +177,11 @@ export class DependencyAnalyzer {
207
177
 
208
178
  const formatNode = (node: DependencyTreeNode, prefix: string, isLast: boolean): void => {
209
179
  const branch = isLast ? '└── ' : '├── ';
210
- const versionStr = options.showCommits
211
- ? `${node.version} (${node.commit.substring(0, 7)})`
212
- : node.version;
180
+ const refStr = options.showCommits
181
+ ? `${node.ref} (${node.commit.substring(0, 7)})`
182
+ : node.ref;
213
183
 
214
- lines.push(`${prefix}${branch}${node.packageKey}@${versionStr}`);
184
+ lines.push(`${prefix}${branch}${node.packageKey}@${refStr}`);
215
185
 
216
186
  const childPrefix = prefix + (isLast ? ' ' : '│ ');
217
187
  node.dependencies.forEach((child, index) => {
@@ -273,38 +243,38 @@ export class DependencyAnalyzer {
273
243
  }
274
244
 
275
245
  /**
276
- * Resolves version policies (latest, stable) to concrete versions.
246
+ * Resolves ref policies (latest, stable) to concrete refs.
277
247
  */
278
248
  async resolveVersionPolicy(
279
- packageKey: string,
249
+ _packageKey: string,
280
250
  policy: string,
281
- availableVersions: string[]
251
+ availableRefs: string[]
282
252
  ): Promise<VersionPolicy> {
283
253
  if (policy === 'latest') {
284
- // Return the most recent version
285
- const sorted = this.sortVersions(availableVersions);
254
+ // Return the most recent ref
255
+ const sorted = sortVersionsDescending(availableRefs);
286
256
  return {
287
257
  policy: 'latest',
288
- version: sorted[0] || 'main',
289
- availableVersions: sorted,
258
+ ref: sorted[0] || 'main',
259
+ availableRefs: sorted,
290
260
  };
291
261
  }
292
262
 
293
263
  if (policy === 'stable') {
294
- // Return the most recent stable version (exclude pre-release)
295
- const stable = availableVersions.filter(v => !this.isPreRelease(v));
296
- const sorted = this.sortVersions(stable);
264
+ // Return the most recent stable ref (exclude pre-release)
265
+ const stable = availableRefs.filter(v => !isPreRelease(v));
266
+ const sorted = sortVersionsDescending(stable);
297
267
  return {
298
268
  policy: 'stable',
299
- version: sorted[0] || 'main',
300
- availableVersions: sorted,
269
+ ref: sorted[0] || 'main',
270
+ availableRefs: sorted,
301
271
  };
302
272
  }
303
273
 
304
- // Pinned version
274
+ // Pinned ref
305
275
  return {
306
276
  policy: 'pinned',
307
- version: policy,
277
+ ref: policy,
308
278
  };
309
279
  }
310
280
 
@@ -348,34 +318,4 @@ export class DependencyAnalyzer {
348
318
  return {};
349
319
  }
350
320
  }
351
-
352
- /**
353
- * Sorts versions in descending order (newest first).
354
- */
355
- private sortVersions(versions: string[]): string[] {
356
- return versions.sort((a, b) => {
357
- // Simple lexicographic sort (good enough for basic semver)
358
- // Production: use semver library
359
- const aParts = a.replace(/^v/, '').split('.').map(Number);
360
- const bParts = b.replace(/^v/, '').split('.').map(Number);
361
-
362
- for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
363
- const aNum = aParts[i] || 0;
364
- const bNum = bParts[i] || 0;
365
- if (aNum !== bNum) {
366
- return bNum - aNum; // Descending
367
- }
368
- }
369
-
370
- return 0;
371
- });
372
- }
373
-
374
- /**
375
- * Checks if a version is a pre-release.
376
- */
377
- private isPreRelease(version: string): boolean {
378
- const clean = version.replace(/^v/, '');
379
- return /-(alpha|beta|rc|pre|dev)/.test(clean);
380
- }
381
321
  }