@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.
@@ -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
+ };