stone-lang 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -0
- package/StoneEngine.js +879 -0
- package/StoneEngineService.js +1727 -0
- package/adapters/FileSystemAdapter.js +230 -0
- package/adapters/OutputAdapter.js +208 -0
- package/adapters/index.js +6 -0
- package/cli/CLIOutputAdapter.js +196 -0
- package/cli/DaemonClient.js +349 -0
- package/cli/JSONOutputAdapter.js +135 -0
- package/cli/ReplSession.js +567 -0
- package/cli/ViewerServer.js +590 -0
- package/cli/commands/check.js +84 -0
- package/cli/commands/daemon.js +189 -0
- package/cli/commands/kill.js +66 -0
- package/cli/commands/package.js +713 -0
- package/cli/commands/ps.js +65 -0
- package/cli/commands/run.js +537 -0
- package/cli/entry.js +169 -0
- package/cli/index.js +14 -0
- package/cli/stonec.js +358 -0
- package/cli/test-compiler.js +181 -0
- package/cli/viewer/index.html +495 -0
- package/daemon/IPCServer.js +455 -0
- package/daemon/ProcessManager.js +327 -0
- package/daemon/ProcessRunner.js +307 -0
- package/daemon/daemon.js +398 -0
- package/daemon/index.js +16 -0
- package/frontend/analysis/index.js +5 -0
- package/frontend/analysis/livenessAnalyzer.js +568 -0
- package/frontend/analysis/treeShaker.js +265 -0
- package/frontend/index.js +20 -0
- package/frontend/parsing/astBuilder.js +2196 -0
- package/frontend/parsing/index.js +7 -0
- package/frontend/parsing/sonParser.js +592 -0
- package/frontend/parsing/stoneAstTypes.js +703 -0
- package/frontend/parsing/terminal-registry.js +435 -0
- package/frontend/parsing/tokenizer.js +692 -0
- package/frontend/type-checker/OverloadedFunctionType.js +43 -0
- package/frontend/type-checker/TypeEnvironment.js +165 -0
- package/frontend/type-checker/bidirectionalInference.js +149 -0
- package/frontend/type-checker/index.js +10 -0
- package/frontend/type-checker/moduleAnalysis.js +248 -0
- package/frontend/type-checker/operatorMappings.js +35 -0
- package/frontend/type-checker/overloadResolution.js +605 -0
- package/frontend/type-checker/typeChecker.js +452 -0
- package/frontend/type-checker/typeCompatibility.js +389 -0
- package/frontend/type-checker/visitors/controlFlow.js +483 -0
- package/frontend/type-checker/visitors/functions.js +604 -0
- package/frontend/type-checker/visitors/index.js +38 -0
- package/frontend/type-checker/visitors/literals.js +341 -0
- package/frontend/type-checker/visitors/modules.js +159 -0
- package/frontend/type-checker/visitors/operators.js +109 -0
- package/frontend/type-checker/visitors/statements.js +768 -0
- package/frontend/types/index.js +5 -0
- package/frontend/types/operatorMap.js +134 -0
- package/frontend/types/types.js +2046 -0
- package/frontend/utils/errorCollector.js +244 -0
- package/frontend/utils/index.js +5 -0
- package/frontend/utils/moduleResolver.js +479 -0
- package/package.json +50 -0
- package/packages/browserCache.js +359 -0
- package/packages/fetcher.js +236 -0
- package/packages/index.js +130 -0
- package/packages/lockfile.js +271 -0
- package/packages/manifest.js +291 -0
- package/packages/packageResolver.js +356 -0
- package/packages/resolver.js +310 -0
- package/packages/semver.js +635 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stone Package-Aware Module Resolver
|
|
3
|
+
*
|
|
4
|
+
* Extends the base module resolver to support:
|
|
5
|
+
* - package.son project root detection
|
|
6
|
+
* - stone_modules resolution
|
|
7
|
+
* - Global ~/.stone/stone_modules fallback
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { parseSON } from '../frontend/parsing/sonParser.js';
|
|
11
|
+
|
|
12
|
+
// Platform-specific path utilities
|
|
13
|
+
const isWindows = typeof process !== 'undefined' && process.platform === 'win32';
|
|
14
|
+
const pathSep = isWindows ? '\\' : '/';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Normalize path separators
|
|
18
|
+
*/
|
|
19
|
+
function normalizePath(p) {
|
|
20
|
+
if (!p) return p;
|
|
21
|
+
return p.replace(/[\\/]+/g, pathSep);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Join path segments
|
|
26
|
+
*/
|
|
27
|
+
function joinPath(...segments) {
|
|
28
|
+
return segments.filter(Boolean).join(pathSep).replace(/[\\/]+/g, pathSep);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get parent directory
|
|
33
|
+
*/
|
|
34
|
+
function getParentDir(p) {
|
|
35
|
+
if (!p) return null;
|
|
36
|
+
const normalized = normalizePath(p);
|
|
37
|
+
const lastSep = normalized.lastIndexOf(pathSep);
|
|
38
|
+
if (lastSep <= 0) return null;
|
|
39
|
+
return normalized.substring(0, lastSep);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Check if path is at filesystem root
|
|
44
|
+
*/
|
|
45
|
+
function isRoot(p) {
|
|
46
|
+
if (!p) return true;
|
|
47
|
+
const normalized = normalizePath(p);
|
|
48
|
+
// Unix root
|
|
49
|
+
if (normalized === '/') return true;
|
|
50
|
+
// Windows root (C:\)
|
|
51
|
+
if (/^[A-Za-z]:[\\/]?$/.test(normalized)) return true;
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get user home directory
|
|
57
|
+
*/
|
|
58
|
+
function getHomeDir() {
|
|
59
|
+
if (typeof process !== 'undefined') {
|
|
60
|
+
return process.env.HOME || process.env.USERPROFILE || '';
|
|
61
|
+
}
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get global Stone directory (~/.stone)
|
|
67
|
+
*/
|
|
68
|
+
function getStoneHome() {
|
|
69
|
+
// Check environment variable first
|
|
70
|
+
if (typeof process !== 'undefined' && process.env.STONE_HOME) {
|
|
71
|
+
return process.env.STONE_HOME;
|
|
72
|
+
}
|
|
73
|
+
// Default to ~/.stone
|
|
74
|
+
const home = getHomeDir();
|
|
75
|
+
if (home) {
|
|
76
|
+
return joinPath(home, '.stone');
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Package Resolution Context
|
|
83
|
+
*/
|
|
84
|
+
export class PackageContext {
|
|
85
|
+
constructor(options = {}) {
|
|
86
|
+
// File system access functions
|
|
87
|
+
this.fileExists = options.fileExists || (() => false);
|
|
88
|
+
this.readFile = options.readFile || (() => null);
|
|
89
|
+
this.listDir = options.listDir || (() => []);
|
|
90
|
+
|
|
91
|
+
// Cache
|
|
92
|
+
this.projectRootCache = new Map();
|
|
93
|
+
this.manifestCache = new Map();
|
|
94
|
+
this.resolvedModules = new Map();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Find project root by walking up to find package.son
|
|
99
|
+
* @param {string} startPath - Starting directory path
|
|
100
|
+
* @returns {string|null} Project root path or null
|
|
101
|
+
*/
|
|
102
|
+
async findProjectRoot(startPath) {
|
|
103
|
+
if (!startPath) return null;
|
|
104
|
+
|
|
105
|
+
// Check cache
|
|
106
|
+
if (this.projectRootCache.has(startPath)) {
|
|
107
|
+
return this.projectRootCache.get(startPath);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let currentDir = normalizePath(startPath);
|
|
111
|
+
|
|
112
|
+
// Walk up until we find package.son or hit root
|
|
113
|
+
while (currentDir && !isRoot(currentDir)) {
|
|
114
|
+
const manifestPath = joinPath(currentDir, 'package.son');
|
|
115
|
+
|
|
116
|
+
if (await this.fileExists(manifestPath)) {
|
|
117
|
+
this.projectRootCache.set(startPath, currentDir);
|
|
118
|
+
return currentDir;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
currentDir = getParentDir(currentDir);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// No package.son found
|
|
125
|
+
this.projectRootCache.set(startPath, null);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get package manifest for a directory
|
|
131
|
+
*/
|
|
132
|
+
async getManifest(dir) {
|
|
133
|
+
if (!dir) return null;
|
|
134
|
+
|
|
135
|
+
const manifestPath = joinPath(dir, 'package.son');
|
|
136
|
+
|
|
137
|
+
if (this.manifestCache.has(manifestPath)) {
|
|
138
|
+
return this.manifestCache.get(manifestPath);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
const content = await this.readFile(manifestPath);
|
|
143
|
+
if (!content) {
|
|
144
|
+
this.manifestCache.set(manifestPath, null);
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const manifest = parseSON(content, manifestPath);
|
|
149
|
+
this.manifestCache.set(manifestPath, manifest);
|
|
150
|
+
return manifest;
|
|
151
|
+
} catch (e) {
|
|
152
|
+
this.manifestCache.set(manifestPath, null);
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Resolve a module from stone_modules
|
|
159
|
+
* @param {string} moduleName - Module name (not a path)
|
|
160
|
+
* @param {string} projectRoot - Project root directory
|
|
161
|
+
* @returns {string|null} Path to module's entry file
|
|
162
|
+
*/
|
|
163
|
+
async resolveFromStoneModules(moduleName, projectRoot) {
|
|
164
|
+
if (!moduleName || !projectRoot) return null;
|
|
165
|
+
|
|
166
|
+
const stoneModulesDir = joinPath(projectRoot, 'stone_modules', moduleName);
|
|
167
|
+
|
|
168
|
+
// Check if module directory exists
|
|
169
|
+
const moduleManifestPath = joinPath(stoneModulesDir, 'package.son');
|
|
170
|
+
if (await this.fileExists(moduleManifestPath)) {
|
|
171
|
+
// Get entry point from manifest
|
|
172
|
+
const manifest = await this.getManifest(stoneModulesDir);
|
|
173
|
+
const entry = manifest?.entry || 'main.stn';
|
|
174
|
+
const entryPath = joinPath(stoneModulesDir, entry);
|
|
175
|
+
|
|
176
|
+
if (await this.fileExists(entryPath)) {
|
|
177
|
+
return entryPath;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Try direct .stn file
|
|
182
|
+
const directPath = joinPath(projectRoot, 'stone_modules', `${moduleName}.stn`);
|
|
183
|
+
if (await this.fileExists(directPath)) {
|
|
184
|
+
return directPath;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Try main.stn in directory without manifest
|
|
188
|
+
const mainPath = joinPath(stoneModulesDir, 'main.stn');
|
|
189
|
+
if (await this.fileExists(mainPath)) {
|
|
190
|
+
return mainPath;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Resolve from global stone_modules
|
|
198
|
+
*/
|
|
199
|
+
async resolveFromGlobal(moduleName) {
|
|
200
|
+
const stoneHome = getStoneHome();
|
|
201
|
+
if (!stoneHome) return null;
|
|
202
|
+
|
|
203
|
+
return this.resolveFromStoneModules(moduleName, stoneHome);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Full module resolution
|
|
208
|
+
* @param {string} moduleName - Module name to resolve
|
|
209
|
+
* @param {string} fromPath - Path of the importing file
|
|
210
|
+
* @param {object} options - Resolution options
|
|
211
|
+
* @returns {string|null} Resolved module path
|
|
212
|
+
*/
|
|
213
|
+
async resolveModule(moduleName, fromPath, options = {}) {
|
|
214
|
+
// Skip relative imports - handled by base resolver
|
|
215
|
+
if (moduleName.startsWith('./') || moduleName.startsWith('../')) {
|
|
216
|
+
return null; // Let base resolver handle it
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Skip built-ins (handled by base resolver)
|
|
220
|
+
if (options.isBuiltin?.(moduleName)) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const cacheKey = `${fromPath}:${moduleName}`;
|
|
225
|
+
if (this.resolvedModules.has(cacheKey)) {
|
|
226
|
+
return this.resolvedModules.get(cacheKey);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Find project root
|
|
230
|
+
const startDir = getParentDir(fromPath) || fromPath;
|
|
231
|
+
const projectRoot = await this.findProjectRoot(startDir);
|
|
232
|
+
|
|
233
|
+
// 1. Try local stone_modules (at project root)
|
|
234
|
+
if (projectRoot) {
|
|
235
|
+
const localPath = await this.resolveFromStoneModules(moduleName, projectRoot);
|
|
236
|
+
if (localPath) {
|
|
237
|
+
this.resolvedModules.set(cacheKey, localPath);
|
|
238
|
+
return localPath;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 2. Try global stone_modules
|
|
243
|
+
const globalPath = await this.resolveFromGlobal(moduleName);
|
|
244
|
+
if (globalPath) {
|
|
245
|
+
this.resolvedModules.set(cacheKey, globalPath);
|
|
246
|
+
return globalPath;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this.resolvedModules.set(cacheKey, null);
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Clear all caches
|
|
255
|
+
*/
|
|
256
|
+
clearCache() {
|
|
257
|
+
this.projectRootCache.clear();
|
|
258
|
+
this.manifestCache.clear();
|
|
259
|
+
this.resolvedModules.clear();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Create a package context for Node.js environment
|
|
265
|
+
*/
|
|
266
|
+
export async function createNodePackageContext() {
|
|
267
|
+
// Dynamic import for Node.js fs module
|
|
268
|
+
let fs, path;
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
fs = await import('fs');
|
|
272
|
+
path = await import('path');
|
|
273
|
+
} catch (e) {
|
|
274
|
+
// Not in Node.js environment
|
|
275
|
+
return new PackageContext();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return new PackageContext({
|
|
279
|
+
fileExists: async (p) => {
|
|
280
|
+
try {
|
|
281
|
+
await fs.promises.access(p);
|
|
282
|
+
return true;
|
|
283
|
+
} catch {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
readFile: async (p) => {
|
|
288
|
+
try {
|
|
289
|
+
return await fs.promises.readFile(p, 'utf8');
|
|
290
|
+
} catch {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
listDir: async (p) => {
|
|
295
|
+
try {
|
|
296
|
+
return await fs.promises.readdir(p);
|
|
297
|
+
} catch {
|
|
298
|
+
return [];
|
|
299
|
+
}
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Create a package context for web/Riva environment
|
|
306
|
+
*/
|
|
307
|
+
export function createWebPackageContext(options = {}) {
|
|
308
|
+
// virtualFS should be a Map or object with file paths as keys
|
|
309
|
+
const virtualFS = options.virtualFS || new Map();
|
|
310
|
+
const fetchPackage = options.fetchPackage || (() => null);
|
|
311
|
+
|
|
312
|
+
return new PackageContext({
|
|
313
|
+
fileExists: async (p) => {
|
|
314
|
+
if (virtualFS instanceof Map) {
|
|
315
|
+
return virtualFS.has(p);
|
|
316
|
+
}
|
|
317
|
+
return p in virtualFS;
|
|
318
|
+
},
|
|
319
|
+
readFile: async (p) => {
|
|
320
|
+
if (virtualFS instanceof Map) {
|
|
321
|
+
return virtualFS.get(p) || null;
|
|
322
|
+
}
|
|
323
|
+
return virtualFS[p] || null;
|
|
324
|
+
},
|
|
325
|
+
listDir: async (p) => {
|
|
326
|
+
const prefix = p.endsWith('/') ? p : p + '/';
|
|
327
|
+
const files = [];
|
|
328
|
+
|
|
329
|
+
const entries = virtualFS instanceof Map
|
|
330
|
+
? Array.from(virtualFS.keys())
|
|
331
|
+
: Object.keys(virtualFS);
|
|
332
|
+
|
|
333
|
+
for (const entry of entries) {
|
|
334
|
+
if (entry.startsWith(prefix)) {
|
|
335
|
+
const rest = entry.substring(prefix.length);
|
|
336
|
+
const nextSep = rest.indexOf('/');
|
|
337
|
+
const name = nextSep >= 0 ? rest.substring(0, nextSep) : rest;
|
|
338
|
+
if (name && !files.includes(name)) {
|
|
339
|
+
files.push(name);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return files;
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export default {
|
|
350
|
+
PackageContext,
|
|
351
|
+
createNodePackageContext,
|
|
352
|
+
createWebPackageContext,
|
|
353
|
+
getStoneHome,
|
|
354
|
+
normalizePath,
|
|
355
|
+
joinPath,
|
|
356
|
+
};
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stone Package Dependency Resolver
|
|
3
|
+
*
|
|
4
|
+
* Resolves package dependencies with version constraints.
|
|
5
|
+
* Builds dependency graph and finds compatible versions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { parseRange, satisfies, maxSatisfying, parseVersion } from './semver.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolution error
|
|
12
|
+
*/
|
|
13
|
+
export class ResolutionError extends Error {
|
|
14
|
+
constructor(message, pkg = null, details = null) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = 'ResolutionError';
|
|
17
|
+
this.package = pkg;
|
|
18
|
+
this.details = details;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Dependency graph node
|
|
24
|
+
*/
|
|
25
|
+
class DependencyNode {
|
|
26
|
+
constructor(name) {
|
|
27
|
+
this.name = name;
|
|
28
|
+
this.constraints = []; // Array of { range, source } where source is the dependent package
|
|
29
|
+
this.resolvedVersion = null;
|
|
30
|
+
this.dependencies = new Map(); // name -> DependencyNode
|
|
31
|
+
this.source = null; // URL source for fetching
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
addConstraint(range, source, packageSource = null) {
|
|
35
|
+
this.constraints.push({ range, source });
|
|
36
|
+
if (packageSource && !this.source) {
|
|
37
|
+
this.source = packageSource;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Dependency Resolver
|
|
44
|
+
*/
|
|
45
|
+
export class DependencyResolver {
|
|
46
|
+
constructor(options = {}) {
|
|
47
|
+
// Function to fetch available versions for a package
|
|
48
|
+
// Should return: { versions: string[], source?: string }
|
|
49
|
+
this.fetchVersions = options.fetchVersions || null;
|
|
50
|
+
|
|
51
|
+
// Cache of available versions per package
|
|
52
|
+
this.versionCache = new Map();
|
|
53
|
+
|
|
54
|
+
// The resolved dependency graph
|
|
55
|
+
this.graph = new Map();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Resolve dependencies starting from a root manifest
|
|
60
|
+
* @param {object} manifest - The root package manifest (parsed package.son)
|
|
61
|
+
* @returns {Map<string, ResolvedPackage>} Map of package name to resolved info
|
|
62
|
+
*/
|
|
63
|
+
async resolve(manifest) {
|
|
64
|
+
// Clear previous state
|
|
65
|
+
this.graph.clear();
|
|
66
|
+
|
|
67
|
+
// Build the dependency graph
|
|
68
|
+
await this.buildGraph(manifest);
|
|
69
|
+
|
|
70
|
+
// Resolve versions for each package
|
|
71
|
+
const resolved = await this.resolveVersions();
|
|
72
|
+
|
|
73
|
+
return resolved;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Build the dependency graph from a manifest
|
|
78
|
+
*/
|
|
79
|
+
async buildGraph(manifest, source = '<root>') {
|
|
80
|
+
const deps = this.normalizeDependencies(manifest.dependencies || {});
|
|
81
|
+
|
|
82
|
+
for (const [name, spec] of Object.entries(deps)) {
|
|
83
|
+
await this.addDependency(name, spec.version, source, spec.source);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Add a dependency to the graph
|
|
89
|
+
*/
|
|
90
|
+
async addDependency(name, versionRange, source, packageSource = null) {
|
|
91
|
+
// Get or create node
|
|
92
|
+
let node = this.graph.get(name);
|
|
93
|
+
if (!node) {
|
|
94
|
+
node = new DependencyNode(name);
|
|
95
|
+
this.graph.set(name, node);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Add constraint
|
|
99
|
+
const range = parseRange(versionRange);
|
|
100
|
+
node.addConstraint(range, source, packageSource);
|
|
101
|
+
|
|
102
|
+
// If we have a version fetcher, get the package's dependencies too
|
|
103
|
+
if (this.fetchVersions && !node.resolvedVersion) {
|
|
104
|
+
try {
|
|
105
|
+
const pkgInfo = await this.getPackageInfo(name, packageSource);
|
|
106
|
+
if (pkgInfo && pkgInfo.dependencies) {
|
|
107
|
+
// Add transitive dependencies
|
|
108
|
+
const transDeps = this.normalizeDependencies(pkgInfo.dependencies);
|
|
109
|
+
for (const [depName, depSpec] of Object.entries(transDeps)) {
|
|
110
|
+
await this.addDependency(depName, depSpec.version, name, depSpec.source);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch (e) {
|
|
114
|
+
// Package info not available, continue without transitive deps
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Normalize dependencies to { version, source? } format
|
|
121
|
+
*/
|
|
122
|
+
normalizeDependencies(deps) {
|
|
123
|
+
const result = {};
|
|
124
|
+
for (const [name, spec] of Object.entries(deps)) {
|
|
125
|
+
if (typeof spec === 'string') {
|
|
126
|
+
result[name] = { version: spec };
|
|
127
|
+
} else {
|
|
128
|
+
result[name] = { version: spec.version, source: spec.source };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get package info (versions, dependencies) from cache or fetcher
|
|
136
|
+
*/
|
|
137
|
+
async getPackageInfo(name, source = null) {
|
|
138
|
+
// Check cache
|
|
139
|
+
if (this.versionCache.has(name)) {
|
|
140
|
+
return this.versionCache.get(name);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Fetch if we have a fetcher
|
|
144
|
+
if (this.fetchVersions) {
|
|
145
|
+
try {
|
|
146
|
+
const info = await this.fetchVersions(name, source);
|
|
147
|
+
this.versionCache.set(name, info);
|
|
148
|
+
return info;
|
|
149
|
+
} catch (e) {
|
|
150
|
+
throw new ResolutionError(`Failed to fetch versions for ${name}: ${e.message}`, name);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Resolve versions for all packages in the graph
|
|
159
|
+
*/
|
|
160
|
+
async resolveVersions() {
|
|
161
|
+
const resolved = new Map();
|
|
162
|
+
|
|
163
|
+
for (const [name, node] of this.graph) {
|
|
164
|
+
const version = await this.resolvePackageVersion(name, node);
|
|
165
|
+
|
|
166
|
+
resolved.set(name, {
|
|
167
|
+
name,
|
|
168
|
+
version,
|
|
169
|
+
source: node.source,
|
|
170
|
+
constraints: node.constraints.map(c => ({
|
|
171
|
+
range: c.range.toString(),
|
|
172
|
+
source: c.source,
|
|
173
|
+
})),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return resolved;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Resolve a single package's version
|
|
182
|
+
*/
|
|
183
|
+
async resolvePackageVersion(name, node) {
|
|
184
|
+
// Get available versions
|
|
185
|
+
const pkgInfo = await this.getPackageInfo(name, node.source);
|
|
186
|
+
const availableVersions = pkgInfo?.versions || [];
|
|
187
|
+
|
|
188
|
+
if (availableVersions.length === 0) {
|
|
189
|
+
// No versions available - check if constraints can be satisfied with any version
|
|
190
|
+
// For offline/local development, we might not have version info
|
|
191
|
+
throw new ResolutionError(
|
|
192
|
+
`No versions available for package: ${name}`,
|
|
193
|
+
name,
|
|
194
|
+
{ constraints: node.constraints.map(c => ({ range: c.range.toString(), source: c.source })) }
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Find versions that satisfy ALL constraints
|
|
199
|
+
const satisfyingVersions = availableVersions.filter(v => {
|
|
200
|
+
return node.constraints.every(c => satisfies(v, c.range));
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (satisfyingVersions.length === 0) {
|
|
204
|
+
// No version satisfies all constraints - report conflict
|
|
205
|
+
const constraintDetails = node.constraints.map(c => ({
|
|
206
|
+
range: c.range.toString(),
|
|
207
|
+
source: c.source,
|
|
208
|
+
satisfying: availableVersions.filter(v => satisfies(v, c.range)),
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
throw new ResolutionError(
|
|
212
|
+
`Cannot resolve ${name}: no version satisfies all constraints`,
|
|
213
|
+
name,
|
|
214
|
+
{
|
|
215
|
+
available: availableVersions,
|
|
216
|
+
constraints: constraintDetails,
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Return the highest satisfying version
|
|
222
|
+
return maxSatisfying(satisfyingVersions, '*');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Simple resolver for when all versions are known upfront
|
|
228
|
+
*/
|
|
229
|
+
export class SimpleResolver {
|
|
230
|
+
/**
|
|
231
|
+
* Resolve dependencies with known available versions
|
|
232
|
+
* @param {object} dependencies - Dependencies to resolve { name: versionRange }
|
|
233
|
+
* @param {object} availableVersions - Available versions { name: [versions] }
|
|
234
|
+
* @returns {object} Resolved versions { name: version }
|
|
235
|
+
*/
|
|
236
|
+
static resolve(dependencies, availableVersions) {
|
|
237
|
+
const resolved = {};
|
|
238
|
+
const errors = [];
|
|
239
|
+
|
|
240
|
+
for (const [name, versionRange] of Object.entries(dependencies)) {
|
|
241
|
+
const range = typeof versionRange === 'string' ? versionRange : versionRange.version;
|
|
242
|
+
const available = availableVersions[name] || [];
|
|
243
|
+
|
|
244
|
+
if (available.length === 0) {
|
|
245
|
+
errors.push(`No versions available for: ${name}`);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const matching = maxSatisfying(available, range);
|
|
250
|
+
if (!matching) {
|
|
251
|
+
errors.push(`No version of ${name} satisfies ${range}. Available: ${available.join(', ')}`);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
resolved[name] = matching;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (errors.length > 0) {
|
|
259
|
+
throw new ResolutionError(
|
|
260
|
+
`Resolution failed:\n${errors.join('\n')}`,
|
|
261
|
+
null,
|
|
262
|
+
{ errors }
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return resolved;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Check if a set of dependencies can be resolved together
|
|
272
|
+
* @param {object} dependencies - Map of package name to constraints
|
|
273
|
+
* @param {object} availableVersions - Map of package name to available versions
|
|
274
|
+
* @returns {object} { valid: boolean, conflicts?: [...] }
|
|
275
|
+
*/
|
|
276
|
+
export function checkCompatibility(dependencies, availableVersions) {
|
|
277
|
+
const conflicts = [];
|
|
278
|
+
|
|
279
|
+
for (const [name, constraints] of Object.entries(dependencies)) {
|
|
280
|
+
const available = availableVersions[name] || [];
|
|
281
|
+
|
|
282
|
+
// Find versions that satisfy all constraints for this package
|
|
283
|
+
const constraintList = Array.isArray(constraints) ? constraints : [constraints];
|
|
284
|
+
const ranges = constraintList.map(c => parseRange(typeof c === 'string' ? c : c.range));
|
|
285
|
+
|
|
286
|
+
const satisfying = available.filter(v => {
|
|
287
|
+
return ranges.every(r => r.test(parseVersion(v)));
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
if (satisfying.length === 0) {
|
|
291
|
+
conflicts.push({
|
|
292
|
+
package: name,
|
|
293
|
+
constraints: constraintList,
|
|
294
|
+
available,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
valid: conflicts.length === 0,
|
|
301
|
+
conflicts,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export default {
|
|
306
|
+
DependencyResolver,
|
|
307
|
+
SimpleResolver,
|
|
308
|
+
ResolutionError,
|
|
309
|
+
checkCompatibility,
|
|
310
|
+
};
|