@deplens/mcp 0.1.6 → 0.1.8
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/bin/deplens-mcp.js +0 -0
- package/package.json +6 -3
- package/src/core/changelog-parser.mjs +313 -0
- package/src/core/diff-analyzer.mjs +590 -0
- package/src/core/diff.mjs +145 -0
- package/src/core/inspect.mjs +950 -482
- package/src/core/parse-dts.mjs +198 -172
- package/src/core/parse-source.mjs +524 -0
- package/src/core/version-resolver.mjs +317 -0
- package/src/server.mjs +579 -40
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* version-resolver.mjs - Download and manage npm package versions for comparison
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { execSync } from "child_process";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
import path from "path";
|
|
8
|
+
import os from "os";
|
|
9
|
+
|
|
10
|
+
// Use user's home directory for more reliable caching
|
|
11
|
+
const CACHE_DIR = path.join(os.homedir(), ".deplens-cache", "versions");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Ensure cache directory exists
|
|
15
|
+
*/
|
|
16
|
+
function ensureCacheDir() {
|
|
17
|
+
if (!fs.existsSync(CACHE_DIR)) {
|
|
18
|
+
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
return CACHE_DIR;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get cache path for a specific package version
|
|
25
|
+
*/
|
|
26
|
+
function getCachePath(packageName, version) {
|
|
27
|
+
const safeName = packageName.replace(/[/@]/g, "_");
|
|
28
|
+
return path.join(ensureCacheDir(), `${safeName}@${version}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if version is already cached
|
|
33
|
+
*/
|
|
34
|
+
function isCached(packageName, version) {
|
|
35
|
+
const cachePath = getCachePath(packageName, version);
|
|
36
|
+
return (
|
|
37
|
+
fs.existsSync(cachePath) &&
|
|
38
|
+
fs.existsSync(path.join(cachePath, "node_modules"))
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get latest version from npm registry
|
|
44
|
+
*/
|
|
45
|
+
export function getLatestVersion(packageName) {
|
|
46
|
+
try {
|
|
47
|
+
const result = execSync(`npm view ${packageName} version`, {
|
|
48
|
+
encoding: "utf-8",
|
|
49
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
50
|
+
});
|
|
51
|
+
return result.trim();
|
|
52
|
+
} catch (e) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Failed to get latest version for ${packageName}: ${e.message}`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get all available versions from npm registry
|
|
61
|
+
*/
|
|
62
|
+
export function getAllVersions(packageName) {
|
|
63
|
+
try {
|
|
64
|
+
const result = execSync(`npm view ${packageName} versions --json`, {
|
|
65
|
+
encoding: "utf-8",
|
|
66
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
67
|
+
});
|
|
68
|
+
return JSON.parse(result);
|
|
69
|
+
} catch (e) {
|
|
70
|
+
throw new Error(`Failed to get versions for ${packageName}: ${e.message}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get installed version in a project
|
|
76
|
+
*/
|
|
77
|
+
export function getInstalledVersion(packageName, projectDir) {
|
|
78
|
+
try {
|
|
79
|
+
const pkgPath = path.join(
|
|
80
|
+
projectDir,
|
|
81
|
+
"node_modules",
|
|
82
|
+
packageName,
|
|
83
|
+
"package.json",
|
|
84
|
+
);
|
|
85
|
+
if (fs.existsSync(pkgPath)) {
|
|
86
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
87
|
+
return pkg.version;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
} catch (e) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Download a specific package version to cache
|
|
97
|
+
*/
|
|
98
|
+
export function downloadVersion(packageName, version, options = {}) {
|
|
99
|
+
const { force = false, timeout = 60000 } = options;
|
|
100
|
+
|
|
101
|
+
const cachePath = getCachePath(packageName, version);
|
|
102
|
+
|
|
103
|
+
// Return cached if available
|
|
104
|
+
if (!force && isCached(packageName, version)) {
|
|
105
|
+
return {
|
|
106
|
+
path: cachePath,
|
|
107
|
+
packageDir: path.join(cachePath, "node_modules", packageName),
|
|
108
|
+
cached: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Create fresh directory
|
|
113
|
+
if (fs.existsSync(cachePath)) {
|
|
114
|
+
fs.rmSync(cachePath, { recursive: true, force: true });
|
|
115
|
+
}
|
|
116
|
+
fs.mkdirSync(cachePath, { recursive: true });
|
|
117
|
+
|
|
118
|
+
// Install package to cache directory
|
|
119
|
+
try {
|
|
120
|
+
execSync(
|
|
121
|
+
`npm install ${packageName}@${version} --prefix "${cachePath}" --ignore-scripts --no-audit --no-fund`,
|
|
122
|
+
{
|
|
123
|
+
encoding: "utf-8",
|
|
124
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
125
|
+
timeout,
|
|
126
|
+
cwd: cachePath,
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
} catch (e) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`Failed to download ${packageName}@${version}: ${e.message}`,
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const packageDir = path.join(cachePath, "node_modules", packageName);
|
|
136
|
+
|
|
137
|
+
if (!fs.existsSync(packageDir)) {
|
|
138
|
+
throw new Error(`Package directory not found after install: ${packageDir}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
path: cachePath,
|
|
143
|
+
packageDir,
|
|
144
|
+
cached: false,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Resolve version string to actual version
|
|
150
|
+
* Supports: "latest", "installed", "^1.0.0", "1.2.3", etc.
|
|
151
|
+
*/
|
|
152
|
+
export function resolveVersion(
|
|
153
|
+
packageName,
|
|
154
|
+
versionSpec,
|
|
155
|
+
projectDir = process.cwd(),
|
|
156
|
+
) {
|
|
157
|
+
if (versionSpec === "latest") {
|
|
158
|
+
return getLatestVersion(packageName);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (versionSpec === "installed") {
|
|
162
|
+
const installed = getInstalledVersion(packageName, projectDir);
|
|
163
|
+
if (!installed) {
|
|
164
|
+
throw new Error(`${packageName} is not installed in ${projectDir}`);
|
|
165
|
+
}
|
|
166
|
+
return installed;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// If it's a range, resolve to max satisfying
|
|
170
|
+
if (
|
|
171
|
+
versionSpec.startsWith("^") ||
|
|
172
|
+
versionSpec.startsWith("~") ||
|
|
173
|
+
versionSpec.includes("x")
|
|
174
|
+
) {
|
|
175
|
+
try {
|
|
176
|
+
const result = execSync(
|
|
177
|
+
`npm view ${packageName}@"${versionSpec}" version`,
|
|
178
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] },
|
|
179
|
+
);
|
|
180
|
+
// npm may return multiple versions, take the last (highest)
|
|
181
|
+
const versions = result.trim().split("\n");
|
|
182
|
+
return versions[versions.length - 1].trim();
|
|
183
|
+
} catch (e) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Failed to resolve version ${versionSpec} for ${packageName}`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Assume it's an exact version
|
|
191
|
+
return versionSpec;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Download two versions for comparison
|
|
196
|
+
*/
|
|
197
|
+
export async function downloadVersionPair(
|
|
198
|
+
packageName,
|
|
199
|
+
fromSpec,
|
|
200
|
+
toSpec,
|
|
201
|
+
options = {},
|
|
202
|
+
) {
|
|
203
|
+
const { projectDir = process.cwd() } = options;
|
|
204
|
+
|
|
205
|
+
// Resolve version specs to actual versions
|
|
206
|
+
const fromVersion = resolveVersion(packageName, fromSpec, projectDir);
|
|
207
|
+
const toVersion = resolveVersion(packageName, toSpec, projectDir);
|
|
208
|
+
|
|
209
|
+
if (fromVersion === toVersion) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`Both versions resolve to the same version: ${fromVersion}`,
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Download both versions (can be parallelized)
|
|
216
|
+
const [fromResult, toResult] = await Promise.all([
|
|
217
|
+
Promise.resolve(downloadVersion(packageName, fromVersion, options)),
|
|
218
|
+
Promise.resolve(downloadVersion(packageName, toVersion, options)),
|
|
219
|
+
]);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
package: packageName,
|
|
223
|
+
from: {
|
|
224
|
+
version: fromVersion,
|
|
225
|
+
...fromResult,
|
|
226
|
+
},
|
|
227
|
+
to: {
|
|
228
|
+
version: toVersion,
|
|
229
|
+
...toResult,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Clear version cache
|
|
236
|
+
*/
|
|
237
|
+
export function clearCache(packageName = null) {
|
|
238
|
+
if (packageName) {
|
|
239
|
+
const safeName = packageName.replace(/[/@]/g, "_");
|
|
240
|
+
const entries = fs.readdirSync(CACHE_DIR);
|
|
241
|
+
for (const entry of entries) {
|
|
242
|
+
if (entry.startsWith(safeName + "@")) {
|
|
243
|
+
fs.rmSync(path.join(CACHE_DIR, entry), {
|
|
244
|
+
recursive: true,
|
|
245
|
+
force: true,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
if (fs.existsSync(CACHE_DIR)) {
|
|
251
|
+
fs.rmSync(CACHE_DIR, { recursive: true, force: true });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get cache stats
|
|
258
|
+
*/
|
|
259
|
+
export function getCacheStats() {
|
|
260
|
+
if (!fs.existsSync(CACHE_DIR)) {
|
|
261
|
+
return { entries: 0, size: 0, packages: [] };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const entries = fs.readdirSync(CACHE_DIR);
|
|
265
|
+
let totalSize = 0;
|
|
266
|
+
const packages = [];
|
|
267
|
+
|
|
268
|
+
for (const entry of entries) {
|
|
269
|
+
const entryPath = path.join(CACHE_DIR, entry);
|
|
270
|
+
const stats = fs.statSync(entryPath);
|
|
271
|
+
if (stats.isDirectory()) {
|
|
272
|
+
const size = getDirSize(entryPath);
|
|
273
|
+
totalSize += size;
|
|
274
|
+
packages.push({ name: entry, size });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
entries: packages.length,
|
|
280
|
+
size: totalSize,
|
|
281
|
+
sizeFormatted: formatBytes(totalSize),
|
|
282
|
+
packages,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function getDirSize(dirPath) {
|
|
287
|
+
let size = 0;
|
|
288
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
289
|
+
for (const entry of entries) {
|
|
290
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
291
|
+
if (entry.isDirectory()) {
|
|
292
|
+
size += getDirSize(fullPath);
|
|
293
|
+
} else {
|
|
294
|
+
size += fs.statSync(fullPath).size;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return size;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function formatBytes(bytes) {
|
|
301
|
+
if (bytes === 0) return "0 B";
|
|
302
|
+
const k = 1024;
|
|
303
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
304
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
305
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export default {
|
|
309
|
+
getLatestVersion,
|
|
310
|
+
getAllVersions,
|
|
311
|
+
getInstalledVersion,
|
|
312
|
+
downloadVersion,
|
|
313
|
+
resolveVersion,
|
|
314
|
+
downloadVersionPair,
|
|
315
|
+
clearCache,
|
|
316
|
+
getCacheStats,
|
|
317
|
+
};
|