@dennisrepa/elevate 1.2.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/LICENSE +21 -0
- package/README.md +214 -0
- package/dist/cli.js +3569 -0
- package/dist/cli.js.map +1 -0
- package/docs/de.md +305 -0
- package/docs/en.md +309 -0
- package/docs/plans/transactional-updates.md +142 -0
- package/package.json +73 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,3569 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.tsx
|
|
4
|
+
import { render } from "ink";
|
|
5
|
+
|
|
6
|
+
// src/config.ts
|
|
7
|
+
import { existsSync, readFileSync } from "fs";
|
|
8
|
+
import { dirname, join, resolve } from "path";
|
|
9
|
+
function findMonorepoRoot(startDir) {
|
|
10
|
+
let dir = resolve(startDir);
|
|
11
|
+
while (true) {
|
|
12
|
+
const pkgPath = join(dir, "package.json");
|
|
13
|
+
if (existsSync(pkgPath)) {
|
|
14
|
+
try {
|
|
15
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
16
|
+
if (pkg.workspaces) return dir;
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const parent = dirname(dir);
|
|
21
|
+
if (parent === dir) break;
|
|
22
|
+
dir = parent;
|
|
23
|
+
}
|
|
24
|
+
return startDir;
|
|
25
|
+
}
|
|
26
|
+
function loadConfig() {
|
|
27
|
+
const rootDir = findMonorepoRoot(process.cwd());
|
|
28
|
+
const configPath = join(rootDir, "elevate.config.json");
|
|
29
|
+
let file = {};
|
|
30
|
+
if (existsSync(configPath)) {
|
|
31
|
+
try {
|
|
32
|
+
file = JSON.parse(readFileSync(configPath, "utf8"));
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
rootDir,
|
|
38
|
+
author: typeof file.author === "string" ? file.author : void 0,
|
|
39
|
+
excludeScopes: Array.isArray(file.excludeScopes) ? file.excludeScopes : [],
|
|
40
|
+
postUpdateScript: typeof file.postUpdateScript === "string" ? file.postUpdateScript : void 0,
|
|
41
|
+
postUpdateLabel: typeof file.postUpdateLabel === "string" ? file.postUpdateLabel : typeof file.postUpdateScript === "string" ? file.postUpdateScript : void 0,
|
|
42
|
+
locale: file.locale === "de" || file.locale === "en" ? file.locale : void 0,
|
|
43
|
+
channel: file.channel === "all" ? "all" : "stable"
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/app.tsx
|
|
48
|
+
import { useState as useState8, useMemo as useMemo5, useCallback as useCallback5 } from "react";
|
|
49
|
+
import { Box as Box14, useInput as useInput2, useApp } from "ink";
|
|
50
|
+
|
|
51
|
+
// src/adapters/npm/npm-discovery.ts
|
|
52
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync } from "fs";
|
|
53
|
+
import { join as join2 } from "path";
|
|
54
|
+
function readJson(filePath) {
|
|
55
|
+
try {
|
|
56
|
+
return JSON.parse(readFileSync2(filePath, "utf8"));
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
var NpmModuleDiscoveryAdapter = class {
|
|
62
|
+
async discover(rootDir) {
|
|
63
|
+
const rootPkg = readJson(join2(rootDir, "package.json"));
|
|
64
|
+
const modules = [
|
|
65
|
+
{
|
|
66
|
+
id: rootPkg?.name ?? "root",
|
|
67
|
+
name: rootPkg?.name ?? "Root Workspace (npm)",
|
|
68
|
+
path: rootDir,
|
|
69
|
+
relPath: "Root",
|
|
70
|
+
ecosystem: "npm",
|
|
71
|
+
isRoot: true
|
|
72
|
+
}
|
|
73
|
+
];
|
|
74
|
+
if (!rootPkg?.workspaces) return modules;
|
|
75
|
+
const patterns = Array.isArray(rootPkg.workspaces) ? rootPkg.workspaces : [];
|
|
76
|
+
for (const pattern of patterns) {
|
|
77
|
+
if (pattern.endsWith("/*")) {
|
|
78
|
+
const parentDir = join2(rootDir, pattern.slice(0, -2));
|
|
79
|
+
if (!existsSync2(parentDir)) continue;
|
|
80
|
+
for (const entry of readdirSync(parentDir, { withFileTypes: true })) {
|
|
81
|
+
if (!entry.isDirectory()) continue;
|
|
82
|
+
const pkgPath = join2(parentDir, entry.name, "package.json");
|
|
83
|
+
if (!existsSync2(pkgPath)) continue;
|
|
84
|
+
const pkg = readJson(pkgPath);
|
|
85
|
+
modules.push({
|
|
86
|
+
id: pkg?.name ?? `${pattern.slice(0, -2)}/${entry.name}`,
|
|
87
|
+
name: pkg?.name ?? `${pattern.slice(0, -2)}/${entry.name}`,
|
|
88
|
+
path: join2(parentDir, entry.name),
|
|
89
|
+
relPath: `${pattern.slice(0, -2)}/${entry.name}`,
|
|
90
|
+
ecosystem: "npm",
|
|
91
|
+
isRoot: false
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
const explicitPath = join2(rootDir, pattern);
|
|
96
|
+
const pkgPath = join2(explicitPath, "package.json");
|
|
97
|
+
if (!existsSync2(pkgPath)) continue;
|
|
98
|
+
const pkg = readJson(pkgPath);
|
|
99
|
+
modules.push({
|
|
100
|
+
id: pkg?.name ?? pattern,
|
|
101
|
+
name: pkg?.name ?? pattern,
|
|
102
|
+
path: explicitPath,
|
|
103
|
+
relPath: pattern,
|
|
104
|
+
ecosystem: "npm",
|
|
105
|
+
isRoot: false
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return modules;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// src/adapters/npm/npm-registry.ts
|
|
114
|
+
import { exec } from "child_process";
|
|
115
|
+
import { promisify } from "util";
|
|
116
|
+
import semver from "semver";
|
|
117
|
+
var execAsync = promisify(exec);
|
|
118
|
+
var NpmRegistryAdapter = class {
|
|
119
|
+
async getLatestVersion(coordinate, channel = "stable") {
|
|
120
|
+
try {
|
|
121
|
+
if (channel === "stable") {
|
|
122
|
+
const { stdout: stdout2 } = await execAsync(
|
|
123
|
+
`npm view ${coordinate.identifier} "dist-tags.latest" --json`,
|
|
124
|
+
{ timeout: 15e3 }
|
|
125
|
+
);
|
|
126
|
+
const v = stdout2.trim().replace(/^"|"$/g, "");
|
|
127
|
+
if (v && semver.valid(v) && semver.prerelease(v) === null) {
|
|
128
|
+
return v;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const { stdout } = await execAsync(
|
|
132
|
+
`npm view ${coordinate.identifier} version --json`,
|
|
133
|
+
{ timeout: 15e3 }
|
|
134
|
+
);
|
|
135
|
+
return stdout.trim().replace(/^"|"$/g, "");
|
|
136
|
+
} catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** Liefert alle veröffentlichten Versionen (neueste zuerst) */
|
|
141
|
+
async getAllVersions(coordinate) {
|
|
142
|
+
try {
|
|
143
|
+
const { stdout } = await execAsync(
|
|
144
|
+
`npm view ${coordinate.identifier} versions --json`,
|
|
145
|
+
{ timeout: 15e3 }
|
|
146
|
+
);
|
|
147
|
+
const parsed = JSON.parse(stdout.trim());
|
|
148
|
+
const list = Array.isArray(parsed) ? parsed : [parsed];
|
|
149
|
+
return list.map(String).reverse();
|
|
150
|
+
} catch {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// src/adapters/npm/npm-scanner.ts
|
|
157
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
158
|
+
import { join as join3 } from "path";
|
|
159
|
+
import semver2 from "semver";
|
|
160
|
+
var DIFF_ORDER = { major: 0, minor: 1, patch: 2 };
|
|
161
|
+
var CONCURRENCY = 6;
|
|
162
|
+
async function pooled(items, concurrency, fn) {
|
|
163
|
+
const results = [];
|
|
164
|
+
const executing = [];
|
|
165
|
+
for (const item of items) {
|
|
166
|
+
const p = Promise.resolve().then(() => fn(item));
|
|
167
|
+
results.push(p);
|
|
168
|
+
if (concurrency <= items.length) {
|
|
169
|
+
const slot = p.then(() => void executing.splice(executing.indexOf(slot), 1)).catch(() => void executing.splice(executing.indexOf(slot), 1));
|
|
170
|
+
executing.push(slot);
|
|
171
|
+
if (executing.length >= concurrency) await Promise.race(executing);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return Promise.all(results);
|
|
175
|
+
}
|
|
176
|
+
function matchesExcludedScope(name, excludeScopes) {
|
|
177
|
+
return excludeScopes.some((scope) => {
|
|
178
|
+
const normalized = scope.endsWith("/") ? scope : `${scope}/`;
|
|
179
|
+
return name.startsWith(normalized);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
var NpmDependencyAdapter = class {
|
|
183
|
+
constructor(registry) {
|
|
184
|
+
this.registry = registry;
|
|
185
|
+
}
|
|
186
|
+
registry;
|
|
187
|
+
async scan(module, excludedScopes, internalIds, channel = "stable") {
|
|
188
|
+
const pkgPath = join3(module.path, "package.json");
|
|
189
|
+
let pkg;
|
|
190
|
+
try {
|
|
191
|
+
pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
192
|
+
} catch {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
const allDeps = {
|
|
196
|
+
...pkg.dependencies ?? {},
|
|
197
|
+
...pkg.devDependencies ?? {}
|
|
198
|
+
};
|
|
199
|
+
const entries = Object.entries(allDeps).filter(
|
|
200
|
+
([name]) => !internalIds.has(name) && !matchesExcludedScope(name, excludedScopes)
|
|
201
|
+
);
|
|
202
|
+
const candidates = [];
|
|
203
|
+
await pooled(entries, CONCURRENCY, async ([name, currentRange]) => {
|
|
204
|
+
const latest = await this.registry.getLatestVersion(
|
|
205
|
+
{
|
|
206
|
+
identifier: name,
|
|
207
|
+
artifact: name,
|
|
208
|
+
ecosystem: "npm"
|
|
209
|
+
},
|
|
210
|
+
channel
|
|
211
|
+
);
|
|
212
|
+
if (!latest) return;
|
|
213
|
+
const currentClean = semver2.minVersion(currentRange)?.version;
|
|
214
|
+
if (!currentClean || !semver2.gt(latest, currentClean)) return;
|
|
215
|
+
const diff = semver2.diff(currentClean, latest);
|
|
216
|
+
const prefix = currentRange.startsWith("~") ? "~" : currentRange.startsWith("^") ? "^" : "";
|
|
217
|
+
const prereleaseComponents = semver2.prerelease(latest);
|
|
218
|
+
const isPre = prereleaseComponents !== null;
|
|
219
|
+
const preTag = isPre ? String(prereleaseComponents[0]).toUpperCase() : void 0;
|
|
220
|
+
candidates.push({
|
|
221
|
+
coordinate: {
|
|
222
|
+
identifier: name,
|
|
223
|
+
artifact: name,
|
|
224
|
+
ecosystem: "npm"
|
|
225
|
+
},
|
|
226
|
+
currentRange,
|
|
227
|
+
currentClean,
|
|
228
|
+
latest,
|
|
229
|
+
newRange: `${prefix}${latest}`,
|
|
230
|
+
diff,
|
|
231
|
+
scope: pkg.devDependencies?.[name] ? "dev" : "prod",
|
|
232
|
+
selected: diff !== "major",
|
|
233
|
+
isPreRelease: isPre,
|
|
234
|
+
preReleaseTag: preTag
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
candidates.sort((a, b) => (DIFF_ORDER[a.diff] ?? 3) - (DIFF_ORDER[b.diff] ?? 3));
|
|
238
|
+
return candidates;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// src/adapters/npm/npm-updater.ts
|
|
243
|
+
import { writeFileSync, readFileSync as readFileSync4 } from "fs";
|
|
244
|
+
import { join as join4 } from "path";
|
|
245
|
+
import { exec as exec2 } from "child_process";
|
|
246
|
+
import { promisify as promisify2 } from "util";
|
|
247
|
+
var execAsync2 = promisify2(exec2);
|
|
248
|
+
var NpmUpdaterAdapter = class {
|
|
249
|
+
async applyUpdates(module, rootDir, updates, onProgress) {
|
|
250
|
+
const pkgPath = join4(module.path, "package.json");
|
|
251
|
+
let pkg;
|
|
252
|
+
try {
|
|
253
|
+
pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
|
|
254
|
+
} catch {
|
|
255
|
+
throw new Error(`Konnte package.json in ${module.path} nicht lesen.`);
|
|
256
|
+
}
|
|
257
|
+
onProgress("Schreibe neue Versionen in package.json\u2026");
|
|
258
|
+
for (const item of updates) {
|
|
259
|
+
const name = item.coordinate.artifact;
|
|
260
|
+
if (pkg.dependencies?.[name]) {
|
|
261
|
+
pkg.dependencies[name] = item.newRange;
|
|
262
|
+
}
|
|
263
|
+
if (pkg.devDependencies?.[name]) {
|
|
264
|
+
pkg.devDependencies[name] = item.newRange;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
268
|
+
onProgress("F\xFChre npm install aus (bitte kurz warten)\u2026");
|
|
269
|
+
let stdout = "";
|
|
270
|
+
let stderr = "";
|
|
271
|
+
try {
|
|
272
|
+
const res = await execAsync2("npm install", { cwd: rootDir });
|
|
273
|
+
stdout = res.stdout;
|
|
274
|
+
stderr = res.stderr;
|
|
275
|
+
} catch (err) {
|
|
276
|
+
stdout = err.stdout ?? "";
|
|
277
|
+
stderr = err.stderr ?? err.message;
|
|
278
|
+
}
|
|
279
|
+
onProgress("Analysiere Sicherheitsaudit & Output\u2026");
|
|
280
|
+
const combined = stdout + "\n" + stderr;
|
|
281
|
+
const auditMatch = combined.match(/found (\d+ vulnerabilit(?:y|ies).*)/i);
|
|
282
|
+
const fundingMatch = combined.match(/(\d+ packages? (?:is|are) looking for funding)/i);
|
|
283
|
+
return {
|
|
284
|
+
updatedCount: updates.length,
|
|
285
|
+
auditMessage: auditMatch ? auditMatch[0] : "Keine bekannten Schwachstellen gefunden.",
|
|
286
|
+
auditSeverity: auditMatch ? "warn" : "clean",
|
|
287
|
+
fundingMessage: fundingMatch ? fundingMatch[0] : void 0
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// src/adapters/npm/npm-verifier.ts
|
|
293
|
+
import { exec as exec3 } from "child_process";
|
|
294
|
+
import { promisify as promisify3 } from "util";
|
|
295
|
+
var execAsync3 = promisify3(exec3);
|
|
296
|
+
var NpmVerificationAdapter = class {
|
|
297
|
+
async verify(module, rootDir, customScript, customLabel) {
|
|
298
|
+
const script = customScript || "npm run check:versions";
|
|
299
|
+
const label = customLabel || "Workspace-Konsistenz (npm run check:versions)";
|
|
300
|
+
try {
|
|
301
|
+
const { stdout } = await execAsync3(script, { cwd: rootDir });
|
|
302
|
+
return {
|
|
303
|
+
status: "clean",
|
|
304
|
+
details: stdout.trim() || "Alle Workspace-Versionen sind konsistent.",
|
|
305
|
+
label
|
|
306
|
+
};
|
|
307
|
+
} catch (err) {
|
|
308
|
+
return {
|
|
309
|
+
status: "warn",
|
|
310
|
+
details: err.stdout || err.message,
|
|
311
|
+
label
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// src/adapters/npm/npm-strategy.ts
|
|
318
|
+
var NpmEcosystemStrategy = class {
|
|
319
|
+
ecosystem = "npm";
|
|
320
|
+
displayName = "Node / npm";
|
|
321
|
+
icon = "\u{1F4E6}";
|
|
322
|
+
manifestFile = "package.json";
|
|
323
|
+
discovery = new NpmModuleDiscoveryAdapter();
|
|
324
|
+
registry = new NpmRegistryAdapter();
|
|
325
|
+
reader = new NpmDependencyAdapter(this.registry);
|
|
326
|
+
updater = new NpmUpdaterAdapter();
|
|
327
|
+
verifier = new NpmVerificationAdapter();
|
|
328
|
+
getTabLabels(counts, t) {
|
|
329
|
+
return {
|
|
330
|
+
all: t.tabs.all(counts.total),
|
|
331
|
+
prod: t.tabs.dependencies(counts.prodCount),
|
|
332
|
+
dev: t.tabs.devDependencies(counts.devCount)
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// src/adapters/maven/maven-discovery.ts
|
|
338
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5, readdirSync as readdirSync2 } from "fs";
|
|
339
|
+
import { join as join5, relative } from "path";
|
|
340
|
+
function extractPomInfo(pomPath) {
|
|
341
|
+
try {
|
|
342
|
+
const xml = readFileSync5(pomPath, "utf8");
|
|
343
|
+
const parentMatch = xml.match(/<parent>([\s\S]*?)<\/parent>/);
|
|
344
|
+
let parentGroup = "";
|
|
345
|
+
if (parentMatch) {
|
|
346
|
+
const pg = parentMatch[1].match(/<groupId>([^<]+)<\/groupId>/);
|
|
347
|
+
if (pg) parentGroup = pg[1].trim();
|
|
348
|
+
}
|
|
349
|
+
const strippedXml = xml.replace(/<parent>[\s\S]*?<\/parent>/, "").replace(/<dependencies>[\s\S]*?<\/dependencies>/, "").replace(/<build>[\s\S]*?<\/build>/, "");
|
|
350
|
+
const artifactMatch = strippedXml.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
351
|
+
const groupMatch = strippedXml.match(/<groupId>([^<]+)<\/groupId>/);
|
|
352
|
+
const nameMatch = strippedXml.match(/<name>([^<]+)<\/name>/);
|
|
353
|
+
const artifact = artifactMatch ? artifactMatch[1].trim() : "unbekannt";
|
|
354
|
+
const group = groupMatch ? groupMatch[1].trim() : parentGroup;
|
|
355
|
+
const name = nameMatch ? nameMatch[1].trim() : artifact;
|
|
356
|
+
return {
|
|
357
|
+
name: name || artifact,
|
|
358
|
+
id: group ? `${group}:${artifact}` : artifact
|
|
359
|
+
};
|
|
360
|
+
} catch {
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function findPomFiles(dir, depth = 0, maxDepth = 4) {
|
|
365
|
+
if (depth > maxDepth || !existsSync3(dir)) return [];
|
|
366
|
+
const results = [];
|
|
367
|
+
try {
|
|
368
|
+
const entries = readdirSync2(dir, { withFileTypes: true });
|
|
369
|
+
for (const entry of entries) {
|
|
370
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "target") {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const fullPath = join5(dir, entry.name);
|
|
374
|
+
if (entry.isDirectory()) {
|
|
375
|
+
const pomPath = join5(fullPath, "pom.xml");
|
|
376
|
+
if (existsSync3(pomPath)) {
|
|
377
|
+
results.push(pomPath);
|
|
378
|
+
}
|
|
379
|
+
results.push(...findPomFiles(fullPath, depth + 1, maxDepth));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} catch {
|
|
383
|
+
}
|
|
384
|
+
return results;
|
|
385
|
+
}
|
|
386
|
+
var MavenModuleDiscoveryAdapter = class {
|
|
387
|
+
async discover(rootDir) {
|
|
388
|
+
const pomFiles = findPomFiles(rootDir);
|
|
389
|
+
const modules = [];
|
|
390
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
391
|
+
for (const pomPath of pomFiles) {
|
|
392
|
+
const moduleDir = pomPath.slice(0, -"/pom.xml".length);
|
|
393
|
+
if (seenPaths.has(moduleDir)) continue;
|
|
394
|
+
seenPaths.add(moduleDir);
|
|
395
|
+
const info = extractPomInfo(pomPath);
|
|
396
|
+
if (!info) continue;
|
|
397
|
+
const relPath = relative(rootDir, moduleDir).replace(/\\/g, "/");
|
|
398
|
+
modules.push({
|
|
399
|
+
id: info.id,
|
|
400
|
+
name: info.name,
|
|
401
|
+
path: moduleDir,
|
|
402
|
+
relPath: relPath || "Root",
|
|
403
|
+
ecosystem: "maven",
|
|
404
|
+
isRoot: moduleDir === rootDir
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
modules.sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
408
|
+
return modules;
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
// src/adapters/maven/maven-registry.ts
|
|
413
|
+
function isPreReleaseVersion(version) {
|
|
414
|
+
return /[.-]?(alpha|beta|rc|cr|m\d*|preview|milestone|snapshot|ea|dev|b\d+)/i.test(version);
|
|
415
|
+
}
|
|
416
|
+
function extractPreReleaseTag(version) {
|
|
417
|
+
const match = version.match(/[.-]?(alpha|beta|rc|cr|m\d*|preview|milestone|snapshot|ea|dev)/i);
|
|
418
|
+
if (!match) return void 0;
|
|
419
|
+
const tag = match[1].toUpperCase();
|
|
420
|
+
if (tag.startsWith("M") && tag.length <= 3) return `M${tag.slice(1)}`;
|
|
421
|
+
return tag;
|
|
422
|
+
}
|
|
423
|
+
var MavenRegistryAdapter = class {
|
|
424
|
+
async getLatestVersion(coordinate, channel = "stable") {
|
|
425
|
+
if (!coordinate.group || !coordinate.artifact) return null;
|
|
426
|
+
const groupPath = coordinate.group.replace(/\./g, "/");
|
|
427
|
+
const url = `https://repo1.maven.org/maven2/${groupPath}/${coordinate.artifact}/maven-metadata.xml`;
|
|
428
|
+
try {
|
|
429
|
+
const response = await fetch(url, {
|
|
430
|
+
headers: { "User-Agent": "Elevate-Dependency-Updater" },
|
|
431
|
+
signal: AbortSignal.timeout(8e3)
|
|
432
|
+
});
|
|
433
|
+
if (!response.ok) {
|
|
434
|
+
return await this.fallbackSearch(coordinate.group, coordinate.artifact, channel);
|
|
435
|
+
}
|
|
436
|
+
const xml = await response.text();
|
|
437
|
+
const allVersions = [...xml.matchAll(/<version>([^<]+)<\/version>/g)].map((m) => m[1].trim());
|
|
438
|
+
if (channel === "stable") {
|
|
439
|
+
const releaseMatch2 = xml.match(/<release>([^<]+)<\/release>/);
|
|
440
|
+
if (releaseMatch2?.[1] && !isPreReleaseVersion(releaseMatch2[1].trim())) {
|
|
441
|
+
return releaseMatch2[1].trim();
|
|
442
|
+
}
|
|
443
|
+
for (let i = allVersions.length - 1; i >= 0; i--) {
|
|
444
|
+
const v = allVersions[i];
|
|
445
|
+
if (!isPreReleaseVersion(v)) {
|
|
446
|
+
return v;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const releaseMatch = xml.match(/<release>([^<]+)<\/release>/);
|
|
451
|
+
if (releaseMatch?.[1]) return releaseMatch[1].trim();
|
|
452
|
+
const latestMatch = xml.match(/<latest>([^<]+)<\/latest>/);
|
|
453
|
+
if (latestMatch?.[1]) return latestMatch[1].trim();
|
|
454
|
+
if (allVersions.length > 0) {
|
|
455
|
+
return allVersions[allVersions.length - 1];
|
|
456
|
+
}
|
|
457
|
+
return null;
|
|
458
|
+
} catch {
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
async fallbackSearch(group, artifact, channel) {
|
|
463
|
+
try {
|
|
464
|
+
const query = `g:${encodeURIComponent(group)}+AND+a:${encodeURIComponent(artifact)}`;
|
|
465
|
+
const url = `https://search.maven.org/solrsearch/select?q=${query}&rows=20&wt=json`;
|
|
466
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(8e3) });
|
|
467
|
+
if (!res.ok) return null;
|
|
468
|
+
const data = await res.json();
|
|
469
|
+
const docs = data?.response?.docs || [];
|
|
470
|
+
if (docs.length === 0) return null;
|
|
471
|
+
if (channel === "stable") {
|
|
472
|
+
for (const doc of docs) {
|
|
473
|
+
const v = doc.v || doc.latestVersion;
|
|
474
|
+
if (v && !isPreReleaseVersion(v)) return v;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return docs[0]?.latestVersion || docs[0]?.v || null;
|
|
478
|
+
} catch {
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
/** Liefert alle verfügbaren Versionen aus dem Maven-Repository (neueste zuerst) */
|
|
483
|
+
async getAllVersions(coordinate) {
|
|
484
|
+
if (!coordinate.group || !coordinate.artifact) return [];
|
|
485
|
+
const groupPath = coordinate.group.replace(/\./g, "/");
|
|
486
|
+
const url = `https://repo1.maven.org/maven2/${groupPath}/${coordinate.artifact}/maven-metadata.xml`;
|
|
487
|
+
try {
|
|
488
|
+
const response = await fetch(url, {
|
|
489
|
+
headers: { "User-Agent": "Elevate-Dependency-Updater" },
|
|
490
|
+
signal: AbortSignal.timeout(8e3)
|
|
491
|
+
});
|
|
492
|
+
if (!response.ok) {
|
|
493
|
+
return await this.fallbackAllVersions(coordinate.group, coordinate.artifact);
|
|
494
|
+
}
|
|
495
|
+
const xml = await response.text();
|
|
496
|
+
const allVersions = [...xml.matchAll(/<version>([^<]+)<\/version>/g)].map(
|
|
497
|
+
(m) => m[1].trim()
|
|
498
|
+
);
|
|
499
|
+
if (allVersions.length > 0) {
|
|
500
|
+
return allVersions.reverse();
|
|
501
|
+
}
|
|
502
|
+
return await this.fallbackAllVersions(coordinate.group, coordinate.artifact);
|
|
503
|
+
} catch {
|
|
504
|
+
return await this.fallbackAllVersions(coordinate.group, coordinate.artifact);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
async fallbackAllVersions(group, artifact) {
|
|
508
|
+
try {
|
|
509
|
+
const query = `g:${encodeURIComponent(group)}+AND+a:${encodeURIComponent(artifact)}`;
|
|
510
|
+
const url = `https://search.maven.org/solrsearch/select?q=${query}&rows=100&core=gav&wt=json`;
|
|
511
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(8e3) });
|
|
512
|
+
if (!res.ok) return [];
|
|
513
|
+
const data = await res.json();
|
|
514
|
+
const docs = data?.response?.docs || [];
|
|
515
|
+
return docs.map((d) => d.v).filter(Boolean);
|
|
516
|
+
} catch {
|
|
517
|
+
return [];
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
// src/adapters/maven/maven-scanner.ts
|
|
523
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
524
|
+
import { join as join6 } from "path";
|
|
525
|
+
import semver3 from "semver";
|
|
526
|
+
var DIFF_ORDER2 = { major: 0, minor: 1, patch: 2 };
|
|
527
|
+
var CONCURRENCY2 = 6;
|
|
528
|
+
async function pooled2(items, concurrency, fn) {
|
|
529
|
+
const results = [];
|
|
530
|
+
const executing = [];
|
|
531
|
+
for (const item of items) {
|
|
532
|
+
const p = Promise.resolve().then(() => fn(item));
|
|
533
|
+
results.push(p);
|
|
534
|
+
if (concurrency <= items.length) {
|
|
535
|
+
const slot = p.then(() => void executing.splice(executing.indexOf(slot), 1)).catch(() => void executing.splice(executing.indexOf(slot), 1));
|
|
536
|
+
executing.push(slot);
|
|
537
|
+
if (executing.length >= concurrency) await Promise.race(executing);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return Promise.all(results);
|
|
541
|
+
}
|
|
542
|
+
function cleanJavaVersion(v) {
|
|
543
|
+
const match = v.match(/^v?(\d+\.\d+(?:\.\d+)?)/);
|
|
544
|
+
if (!match) return null;
|
|
545
|
+
return semver3.coerce(match[1])?.version ?? null;
|
|
546
|
+
}
|
|
547
|
+
var MavenDependencyAdapter = class {
|
|
548
|
+
constructor(registry) {
|
|
549
|
+
this.registry = registry;
|
|
550
|
+
}
|
|
551
|
+
registry;
|
|
552
|
+
async scan(module, excludedScopes, internalIds, channel = "stable") {
|
|
553
|
+
const pomPath = join6(module.path, "pom.xml");
|
|
554
|
+
let xml = "";
|
|
555
|
+
try {
|
|
556
|
+
xml = readFileSync6(pomPath, "utf8");
|
|
557
|
+
} catch {
|
|
558
|
+
return [];
|
|
559
|
+
}
|
|
560
|
+
const properties = /* @__PURE__ */ new Map();
|
|
561
|
+
const propBlockMatch = xml.match(/<properties>([\s\S]*?)<\/properties>/);
|
|
562
|
+
if (propBlockMatch?.[1]) {
|
|
563
|
+
const propMatches = propBlockMatch[1].matchAll(/<([a-zA-Z0-9_.-]+)>([^<]+)<\/\1>/g);
|
|
564
|
+
for (const m of propMatches) {
|
|
565
|
+
properties.set(m[1].trim(), m[2].trim());
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
const rawDeps = [];
|
|
569
|
+
const seenCoordinates = /* @__PURE__ */ new Set();
|
|
570
|
+
const depBlockMatches = xml.matchAll(/<dependency>([\s\S]*?)<\/dependency>/g);
|
|
571
|
+
for (const match of depBlockMatches) {
|
|
572
|
+
const block = match[1];
|
|
573
|
+
const groupMatch = block.match(/<groupId>([^<]+)<\/groupId>/);
|
|
574
|
+
const artifactMatch = block.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
575
|
+
const versionMatch = block.match(/<version>([^<]+)<\/version>/);
|
|
576
|
+
const scopeMatch = block.match(/<scope>([^<]+)<\/scope>/);
|
|
577
|
+
if (!groupMatch?.[1] || !artifactMatch?.[1]) continue;
|
|
578
|
+
const group = groupMatch[1].trim();
|
|
579
|
+
const artifact = artifactMatch[1].trim();
|
|
580
|
+
const identifier = `${group}:${artifact}`;
|
|
581
|
+
if (seenCoordinates.has(identifier)) continue;
|
|
582
|
+
seenCoordinates.add(identifier);
|
|
583
|
+
let rawVersion = versionMatch?.[1]?.trim() || "";
|
|
584
|
+
if (rawVersion.startsWith("${") && rawVersion.endsWith("}")) {
|
|
585
|
+
const propKey = rawVersion.slice(2, -1);
|
|
586
|
+
rawVersion = properties.get(propKey) || rawVersion;
|
|
587
|
+
}
|
|
588
|
+
if (!rawVersion || rawVersion.startsWith("${")) {
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
const scope = scopeMatch?.[1]?.trim() === "test" ? "test" : "prod";
|
|
592
|
+
const isInternal = internalIds.has(identifier) || internalIds.has(artifact) || excludedScopes.some((s) => group.startsWith(s.replace(/^@/, "")));
|
|
593
|
+
if (isInternal) continue;
|
|
594
|
+
rawDeps.push({ group, artifact, rawVersion, scope });
|
|
595
|
+
}
|
|
596
|
+
const candidates = [];
|
|
597
|
+
await pooled2(rawDeps, CONCURRENCY2, async (dep) => {
|
|
598
|
+
const latest = await this.registry.getLatestVersion(
|
|
599
|
+
{
|
|
600
|
+
identifier: `${dep.group}:${dep.artifact}`,
|
|
601
|
+
group: dep.group,
|
|
602
|
+
artifact: dep.artifact,
|
|
603
|
+
ecosystem: "maven"
|
|
604
|
+
},
|
|
605
|
+
channel
|
|
606
|
+
);
|
|
607
|
+
if (!latest) return;
|
|
608
|
+
const currentClean = cleanJavaVersion(dep.rawVersion);
|
|
609
|
+
const latestClean = cleanJavaVersion(latest);
|
|
610
|
+
if (!currentClean || !latestClean) return;
|
|
611
|
+
if (semver3.gt(latestClean, currentClean)) {
|
|
612
|
+
const diff = semver3.diff(currentClean, latestClean) || "minor";
|
|
613
|
+
const isPre = isPreReleaseVersion(latest);
|
|
614
|
+
const preTag = isPre ? extractPreReleaseTag(latest) : void 0;
|
|
615
|
+
candidates.push({
|
|
616
|
+
coordinate: {
|
|
617
|
+
identifier: `${dep.group}:${dep.artifact}`,
|
|
618
|
+
group: dep.group,
|
|
619
|
+
artifact: dep.artifact,
|
|
620
|
+
ecosystem: "maven"
|
|
621
|
+
},
|
|
622
|
+
currentRange: dep.rawVersion,
|
|
623
|
+
currentClean,
|
|
624
|
+
latest,
|
|
625
|
+
newRange: latest,
|
|
626
|
+
diff,
|
|
627
|
+
scope: dep.scope,
|
|
628
|
+
selected: diff !== "major",
|
|
629
|
+
isPreRelease: isPre,
|
|
630
|
+
preReleaseTag: preTag
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
candidates.sort((a, b) => (DIFF_ORDER2[a.diff] ?? 3) - (DIFF_ORDER2[b.diff] ?? 3));
|
|
635
|
+
return candidates;
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
// src/adapters/maven/maven-updater.ts
|
|
640
|
+
import { writeFileSync as writeFileSync2, readFileSync as readFileSync7 } from "fs";
|
|
641
|
+
import { join as join7 } from "path";
|
|
642
|
+
import { exec as exec4 } from "child_process";
|
|
643
|
+
import { promisify as promisify4 } from "util";
|
|
644
|
+
var execAsync4 = promisify4(exec4);
|
|
645
|
+
var MavenUpdaterAdapter = class {
|
|
646
|
+
async applyUpdates(module, _rootDir, updates, onProgress) {
|
|
647
|
+
const pomPath = join7(module.path, "pom.xml");
|
|
648
|
+
let xml = "";
|
|
649
|
+
try {
|
|
650
|
+
xml = readFileSync7(pomPath, "utf8");
|
|
651
|
+
} catch {
|
|
652
|
+
throw new Error(`Konnte pom.xml in ${module.path} nicht lesen.`);
|
|
653
|
+
}
|
|
654
|
+
onProgress("Schreibe neue Versionen in pom.xml\u2026");
|
|
655
|
+
for (const item of updates) {
|
|
656
|
+
const group = item.coordinate.group;
|
|
657
|
+
const artifact = item.coordinate.artifact;
|
|
658
|
+
const newVersion = item.newRange;
|
|
659
|
+
if (!group || !artifact) continue;
|
|
660
|
+
const depRegex = new RegExp(
|
|
661
|
+
`(<dependency>[\\s\\S]*?<groupId>${group}<\\/groupId>[\\s\\S]*?<artifactId>${artifact}<\\/artifactId>[\\s\\S]*?<version>)([^<]+)(<\\/version>[\\s\\S]*?<\\/dependency>)`,
|
|
662
|
+
"g"
|
|
663
|
+
);
|
|
664
|
+
xml = xml.replace(depRegex, `$1${newVersion}$3`);
|
|
665
|
+
}
|
|
666
|
+
writeFileSync2(pomPath, xml, "utf8");
|
|
667
|
+
onProgress("L\xF6se neue Maven-Abh\xE4ngigkeiten auf (mvn dependency:resolve)\u2026");
|
|
668
|
+
let stdout = "";
|
|
669
|
+
let stderr = "";
|
|
670
|
+
try {
|
|
671
|
+
const res = await execAsync4("mvn dependency:resolve -q -DskipTests", {
|
|
672
|
+
cwd: module.path,
|
|
673
|
+
timeout: 6e4
|
|
674
|
+
});
|
|
675
|
+
stdout = res.stdout;
|
|
676
|
+
stderr = res.stderr;
|
|
677
|
+
} catch (err) {
|
|
678
|
+
stdout = err.stdout ?? "";
|
|
679
|
+
stderr = err.stderr ?? err.message;
|
|
680
|
+
}
|
|
681
|
+
onProgress("Analysiere Maven-Status\u2026");
|
|
682
|
+
const hasError = stdout.includes("BUILD FAILURE") || stderr.includes("BUILD FAILURE");
|
|
683
|
+
return {
|
|
684
|
+
updatedCount: updates.length,
|
|
685
|
+
auditMessage: hasError ? "Achtung: Einige Abh\xE4ngigkeiten konnten nicht sofort aufgel\xF6st werden." : "Alle Maven-Abh\xE4ngigkeiten erfolgreich aufgel\xF6st.",
|
|
686
|
+
auditSeverity: hasError ? "warn" : "clean",
|
|
687
|
+
fundingMessage: void 0
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
// src/adapters/maven/maven-verifier.ts
|
|
693
|
+
import { exec as exec5 } from "child_process";
|
|
694
|
+
import { promisify as promisify5 } from "util";
|
|
695
|
+
var execAsync5 = promisify5(exec5);
|
|
696
|
+
var MavenVerificationAdapter = class {
|
|
697
|
+
async verify(module, _rootDir, customScript, customLabel) {
|
|
698
|
+
const script = customScript || "mvn test-compile -q";
|
|
699
|
+
const label = customLabel || "Java-Kompilierungstest (mvn test-compile)";
|
|
700
|
+
try {
|
|
701
|
+
const { stdout } = await execAsync5(script, {
|
|
702
|
+
cwd: module.path,
|
|
703
|
+
timeout: 9e4
|
|
704
|
+
});
|
|
705
|
+
return {
|
|
706
|
+
status: "clean",
|
|
707
|
+
details: stdout.trim() || "Java-Quellcode kompiliert fehlerfrei.",
|
|
708
|
+
label
|
|
709
|
+
};
|
|
710
|
+
} catch (err) {
|
|
711
|
+
return {
|
|
712
|
+
status: "warn",
|
|
713
|
+
details: (err.stdout || err.stderr || err.message).slice(0, 300),
|
|
714
|
+
label
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
// src/adapters/maven/maven-strategy.ts
|
|
721
|
+
var MavenEcosystemStrategy = class {
|
|
722
|
+
ecosystem = "maven";
|
|
723
|
+
displayName = "Java / Maven";
|
|
724
|
+
icon = "\u2615";
|
|
725
|
+
manifestFile = "pom.xml";
|
|
726
|
+
discovery = new MavenModuleDiscoveryAdapter();
|
|
727
|
+
registry = new MavenRegistryAdapter();
|
|
728
|
+
reader = new MavenDependencyAdapter(this.registry);
|
|
729
|
+
updater = new MavenUpdaterAdapter();
|
|
730
|
+
verifier = new MavenVerificationAdapter();
|
|
731
|
+
getTabLabels(counts, _t) {
|
|
732
|
+
return {
|
|
733
|
+
all: `[1] Alle (${counts.total})`,
|
|
734
|
+
prod: `[2] Compile (${counts.prodCount})`,
|
|
735
|
+
dev: `[3] Test (${counts.devCount})`
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
// src/domain/ecosystem-factory.ts
|
|
741
|
+
var EcosystemFactory = class {
|
|
742
|
+
static instances = /* @__PURE__ */ new Map();
|
|
743
|
+
/** Liefert die Strategie für das gewünschte Ökosystem (Lazy Singleton) */
|
|
744
|
+
static getStrategy(ecosystem) {
|
|
745
|
+
let strategy = this.instances.get(ecosystem);
|
|
746
|
+
if (!strategy) {
|
|
747
|
+
if (ecosystem === "maven") {
|
|
748
|
+
strategy = new MavenEcosystemStrategy();
|
|
749
|
+
} else {
|
|
750
|
+
strategy = new NpmEcosystemStrategy();
|
|
751
|
+
}
|
|
752
|
+
this.instances.set(ecosystem, strategy);
|
|
753
|
+
}
|
|
754
|
+
return strategy;
|
|
755
|
+
}
|
|
756
|
+
/** Liefert alle verfügbaren Ökosysteme */
|
|
757
|
+
static getAvailableEcosystems() {
|
|
758
|
+
return ["npm", "maven"];
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
|
|
762
|
+
// src/i18n/index.ts
|
|
763
|
+
import { useState, useCallback, useMemo } from "react";
|
|
764
|
+
|
|
765
|
+
// src/i18n/locales/de.ts
|
|
766
|
+
var de = {
|
|
767
|
+
header: {
|
|
768
|
+
title: "Elevate",
|
|
769
|
+
developedBy: (name) => `Entwickelt & gepflegt von ${name}`,
|
|
770
|
+
langToggleHint: "[L] DE / EN",
|
|
771
|
+
ecosystemToggleHint: (nextName) => `[E] ${nextName}`
|
|
772
|
+
},
|
|
773
|
+
workspace: {
|
|
774
|
+
label: "Workspace / Modul",
|
|
775
|
+
switchHint: "Dr\xFCcke [W] zum Wechseln",
|
|
776
|
+
modalTitle: "\u{1F4C1} Modul ausw\xE4hlen (\u2191/\u2193 Navigieren, Enter Ausw\xE4hlen, Esc Abbrechen):"
|
|
777
|
+
},
|
|
778
|
+
tabs: {
|
|
779
|
+
all: (count) => `[1] Alle (${count})`,
|
|
780
|
+
dependencies: (count) => `[2] Dependencies (${count})`,
|
|
781
|
+
devDependencies: (count) => `[3] DevDependencies (${count})`
|
|
782
|
+
},
|
|
783
|
+
list: {
|
|
784
|
+
scanning: "\u23F3 Pr\xFCfe Registry auf verf\xFCgbare Updates\u2026",
|
|
785
|
+
allUpToDate: "\u{1F389} Alle Abh\xE4ngigkeiten in diesem Tab sind auf dem neuesten Stand!",
|
|
786
|
+
moreAbove: "\u25B2 \u2026 weitere oben",
|
|
787
|
+
moreBelow: (remaining) => `\u25BC \u2026 weitere unten (${remaining} \xFCbrig)`
|
|
788
|
+
},
|
|
789
|
+
badges: {
|
|
790
|
+
patch: "[Patch]",
|
|
791
|
+
minor: "[Minor]",
|
|
792
|
+
major: "[MAJOR]",
|
|
793
|
+
dev: "(dev/test)",
|
|
794
|
+
prod: "(prod/compile)"
|
|
795
|
+
},
|
|
796
|
+
status: {
|
|
797
|
+
selectedOf: (selected, total) => `${selected} von ${total}`,
|
|
798
|
+
breakdown: (patch, minor, major) => `(${patch} Patch, ${minor} Minor, ${major} Major)`,
|
|
799
|
+
symlinksProtected: (scopes) => `\u{1F6E1}\uFE0F Interne ${scopes.map((s) => `${s}/*`).join(", ")} gesch\xFCtzt`,
|
|
800
|
+
defaultProtected: "\u{1F6E1}\uFE0F Interne Monorepo-Module gesch\xFCtzt"
|
|
801
|
+
},
|
|
802
|
+
controls: {
|
|
803
|
+
navigate: "Navigieren",
|
|
804
|
+
toggle: "Toggle",
|
|
805
|
+
all: "Alle",
|
|
806
|
+
tabs: "Tabs",
|
|
807
|
+
workspace: "Modul",
|
|
808
|
+
ecosystem: "\xD6kosystem",
|
|
809
|
+
version: "Version",
|
|
810
|
+
language: "Sprache",
|
|
811
|
+
update: "Update starten",
|
|
812
|
+
quit: "Beenden"
|
|
813
|
+
},
|
|
814
|
+
versionModal: {
|
|
815
|
+
title: (pkgName) => `Version heraussuchen: ${pkgName}`,
|
|
816
|
+
current: (version) => `Aktuell installiert: ${version}`,
|
|
817
|
+
filterPlaceholder: "Tippe zum Filtern (z. B. 3.2 oder 18)\u2026",
|
|
818
|
+
loading: "Lade alle Versionen aus der Registry\u2026",
|
|
819
|
+
noVersions: "Keine passenden Versionen gefunden.",
|
|
820
|
+
hint: "[\u2191/\u2193/j/k] Navigieren \u2502 [Enter] Ausw\xE4hlen \u2502 [Esc] Abbrechen \u2502 [Backspace] Filter l\xF6schen",
|
|
821
|
+
latestBadge: "LATEST",
|
|
822
|
+
currentBadge: "AKTUELL",
|
|
823
|
+
customBadge: "MANUELL"
|
|
824
|
+
},
|
|
825
|
+
updating: {
|
|
826
|
+
title: "\u26A1 Aktualisierung l\xE4uft\u2026",
|
|
827
|
+
stepWriting: (step, total) => `${step}/${total} \u2014 Schreibe neue Versionen ins Manifest\u2026`,
|
|
828
|
+
stepInstalling: (step, total) => `${step}/${total} \u2014 L\xF6se Abh\xE4ngigkeiten auf (bitte kurz warten)\u2026`,
|
|
829
|
+
stepAnalyzing: (step, total) => `${step}/${total} \u2014 Analysiere Build & Output\u2026`,
|
|
830
|
+
stepCustom: (step, total, label) => `${step}/${total} \u2014 ${label}\u2026`,
|
|
831
|
+
doNotClose: "Bitte das Terminal nicht schlie\xDFen."
|
|
832
|
+
},
|
|
833
|
+
summary: {
|
|
834
|
+
title: "\u2728 Aktualisierung abgeschlossen!",
|
|
835
|
+
successCount: (count, path) => `\u{1F4E6} ${count} Abh\xE4ngigkeiten erfolgreich in ${path} aktualisiert.`,
|
|
836
|
+
securityAudit: "\u{1F6E1}\uFE0F Status & Audit:",
|
|
837
|
+
cleanAudit: "Keine bekannten Probleme gefunden.",
|
|
838
|
+
funding: (msg) => `\u{1F4A1} Funding: ${msg}`,
|
|
839
|
+
postScript: (label) => `\u{1F50D} ${label}:`,
|
|
840
|
+
backHint: "[Enter] Zur\xFCck zum Dashboard \u2502 [Q] Beenden"
|
|
841
|
+
},
|
|
842
|
+
mascot: {
|
|
843
|
+
name: "Pip",
|
|
844
|
+
idleChirps: ["*piep*", "*flausch*", "*pick pick*", "alles frisch! \u{1FAB6}", "*blinzel*"],
|
|
845
|
+
scanningChirps: ["*such*", "pr\xFCfe...", "oh, Updates?", "einen Moment!"],
|
|
846
|
+
updatingChirps: ["*schuft*", "baue Code...", "gleich fertig!", "arbeite emsig..."],
|
|
847
|
+
successChirps: ["hurra! \u{1F389}", "alles aktuell!", "top gepflegt! \u{1FAB6}", "super gemacht!"],
|
|
848
|
+
mavenChirps: ["*kaffee schl\xFCrf* \u2615", "Spring Boot l\xE4uft!", "Java 25 rockt!", "sauber gebaut!"]
|
|
849
|
+
},
|
|
850
|
+
splash: {
|
|
851
|
+
tagline: "Modernes Polyglot Dependency Dashboard",
|
|
852
|
+
loading: "Initialisiere Module & bereite Dashboard vor\u2026",
|
|
853
|
+
skipHint: "[Leertaste / Enter] zum \xDCberspringen"
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
// src/i18n/locales/en.ts
|
|
858
|
+
var en = {
|
|
859
|
+
header: {
|
|
860
|
+
title: "Elevate",
|
|
861
|
+
developedBy: (name) => `Developed & maintained by ${name}`,
|
|
862
|
+
langToggleHint: "[L] EN / DE",
|
|
863
|
+
ecosystemToggleHint: (nextName) => `[E] ${nextName}`
|
|
864
|
+
},
|
|
865
|
+
workspace: {
|
|
866
|
+
label: "Workspace / Module",
|
|
867
|
+
switchHint: "Press [W] to switch",
|
|
868
|
+
modalTitle: "\u{1F4C1} Select Module (\u2191/\u2193 Navigate, Enter Select, Esc Cancel):"
|
|
869
|
+
},
|
|
870
|
+
tabs: {
|
|
871
|
+
all: (count) => `[1] All (${count})`,
|
|
872
|
+
dependencies: (count) => `[2] Dependencies (${count})`,
|
|
873
|
+
devDependencies: (count) => `[3] DevDependencies (${count})`
|
|
874
|
+
},
|
|
875
|
+
list: {
|
|
876
|
+
scanning: "\u23F3 Checking registry for available updates\u2026",
|
|
877
|
+
allUpToDate: "\u{1F389} All dependencies in this tab are up to date!",
|
|
878
|
+
moreAbove: "\u25B2 \u2026 more above",
|
|
879
|
+
moreBelow: (remaining) => `\u25BC \u2026 more below (${remaining} remaining)`
|
|
880
|
+
},
|
|
881
|
+
badges: {
|
|
882
|
+
patch: "[Patch]",
|
|
883
|
+
minor: "[Minor]",
|
|
884
|
+
major: "[MAJOR]",
|
|
885
|
+
dev: "(dev/test)",
|
|
886
|
+
prod: "(prod/compile)"
|
|
887
|
+
},
|
|
888
|
+
status: {
|
|
889
|
+
selectedOf: (selected, total) => `${selected} of ${total}`,
|
|
890
|
+
breakdown: (patch, minor, major) => `(${patch} Patch, ${minor} Minor, ${major} Major)`,
|
|
891
|
+
symlinksProtected: (scopes) => `\u{1F6E1}\uFE0F Internal ${scopes.map((s) => `${s}/*`).join(", ")} protected`,
|
|
892
|
+
defaultProtected: "\u{1F6E1}\uFE0F Internal monorepo modules protected"
|
|
893
|
+
},
|
|
894
|
+
controls: {
|
|
895
|
+
navigate: "Navigate",
|
|
896
|
+
toggle: "Toggle",
|
|
897
|
+
all: "All",
|
|
898
|
+
tabs: "Tabs",
|
|
899
|
+
workspace: "Module",
|
|
900
|
+
ecosystem: "Ecosystem",
|
|
901
|
+
version: "Version",
|
|
902
|
+
language: "Language",
|
|
903
|
+
update: "Start update",
|
|
904
|
+
quit: "Quit"
|
|
905
|
+
},
|
|
906
|
+
versionModal: {
|
|
907
|
+
title: (pkgName) => `Pick version for: ${pkgName}`,
|
|
908
|
+
current: (version) => `Currently installed: ${version}`,
|
|
909
|
+
filterPlaceholder: "Type to filter (e.g. 3.2 or 18)\u2026",
|
|
910
|
+
loading: "Loading all versions from registry\u2026",
|
|
911
|
+
noVersions: "No matching versions found.",
|
|
912
|
+
hint: "[\u2191/\u2193/j/k] Navigate \u2502 [Enter] Select \u2502 [Esc] Cancel \u2502 [Backspace] Clear filter",
|
|
913
|
+
latestBadge: "LATEST",
|
|
914
|
+
currentBadge: "CURRENT",
|
|
915
|
+
customBadge: "MANUAL"
|
|
916
|
+
},
|
|
917
|
+
updating: {
|
|
918
|
+
title: "\u26A1 Update in progress\u2026",
|
|
919
|
+
stepWriting: (step, total) => `${step}/${total} \u2014 Writing new versions to manifest\u2026`,
|
|
920
|
+
stepInstalling: (step, total) => `${step}/${total} \u2014 Resolving dependencies (please wait)\u2026`,
|
|
921
|
+
stepAnalyzing: (step, total) => `${step}/${total} \u2014 Analyzing build & output\u2026`,
|
|
922
|
+
stepCustom: (step, total, label) => `${step}/${total} \u2014 ${label}\u2026`,
|
|
923
|
+
doNotClose: "Please do not close the terminal."
|
|
924
|
+
},
|
|
925
|
+
summary: {
|
|
926
|
+
title: "\u2728 Update completed successfully!",
|
|
927
|
+
successCount: (count, path) => `\u{1F4E6} ${count} dependencies updated successfully in ${path}.`,
|
|
928
|
+
securityAudit: "\u{1F6E1}\uFE0F Status & Audit:",
|
|
929
|
+
cleanAudit: "No known issues found.",
|
|
930
|
+
funding: (msg) => `\u{1F4A1} Funding: ${msg}`,
|
|
931
|
+
postScript: (label) => `\u{1F50D} ${label}:`,
|
|
932
|
+
backHint: "[Enter] Back to Dashboard \u2502 [Q] Quit"
|
|
933
|
+
},
|
|
934
|
+
mascot: {
|
|
935
|
+
name: "Pip",
|
|
936
|
+
idleChirps: ["*chirp*", "*fluff*", "*peck peck*", "all fresh! \u{1FAB6}", "*blink*"],
|
|
937
|
+
scanningChirps: ["*peeking*", "checking...", "ooh, updates?", "just a sec!"],
|
|
938
|
+
updatingChirps: ["*working hard*", "building...", "almost there!", "packing modules..."],
|
|
939
|
+
successChirps: ["hooray! \u{1F389}", "all updated!", "looking sharp! \u{1FAB6}", "great job!"],
|
|
940
|
+
mavenChirps: ["*sipping coffee* \u2615", "Spring Boot rocks!", "Java 25 is fly!", "clean build!"]
|
|
941
|
+
},
|
|
942
|
+
splash: {
|
|
943
|
+
tagline: "Modern Polyglot Dependency Dashboard",
|
|
944
|
+
loading: "Initializing modules & preparing dashboard\u2026",
|
|
945
|
+
skipHint: "[Space / Enter] to skip"
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
|
|
949
|
+
// src/i18n/index.ts
|
|
950
|
+
var dictionaries = { de, en };
|
|
951
|
+
function resolveInitialLocale(configured) {
|
|
952
|
+
if (configured === "de" || configured === "en") return configured;
|
|
953
|
+
const envLang = (process.env.LANG || process.env.LC_ALL || process.env.LANGUAGE || "").toLowerCase();
|
|
954
|
+
if (envLang.startsWith("en")) return "en";
|
|
955
|
+
return "de";
|
|
956
|
+
}
|
|
957
|
+
function getTranslations(locale) {
|
|
958
|
+
return dictionaries[locale] ?? dictionaries.de;
|
|
959
|
+
}
|
|
960
|
+
function useI18n(initialLocale = "de") {
|
|
961
|
+
const [locale, setLocale] = useState(initialLocale);
|
|
962
|
+
const toggleLocale = useCallback(() => {
|
|
963
|
+
setLocale((prev) => prev === "de" ? "en" : "de");
|
|
964
|
+
}, []);
|
|
965
|
+
const t = useMemo(() => getTranslations(locale), [locale]);
|
|
966
|
+
return {
|
|
967
|
+
locale,
|
|
968
|
+
setLocale,
|
|
969
|
+
toggleLocale,
|
|
970
|
+
t
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// src/hooks/use-workspaces.ts
|
|
975
|
+
import { useState as useState2, useEffect, useMemo as useMemo2, useCallback as useCallback2 } from "react";
|
|
976
|
+
function useWorkspaces(strategy, rootDir) {
|
|
977
|
+
const [modules, setModules] = useState2([]);
|
|
978
|
+
const [loading, setLoading] = useState2(true);
|
|
979
|
+
const [activeIndex, setActiveIndex] = useState2(0);
|
|
980
|
+
const reload = useCallback2(async () => {
|
|
981
|
+
setLoading(true);
|
|
982
|
+
setModules([]);
|
|
983
|
+
setActiveIndex(0);
|
|
984
|
+
try {
|
|
985
|
+
const found = await strategy.discovery.discover(rootDir);
|
|
986
|
+
setModules(found);
|
|
987
|
+
setActiveIndex(0);
|
|
988
|
+
} catch {
|
|
989
|
+
setModules([]);
|
|
990
|
+
} finally {
|
|
991
|
+
setLoading(false);
|
|
992
|
+
}
|
|
993
|
+
}, [strategy, rootDir]);
|
|
994
|
+
useEffect(() => {
|
|
995
|
+
reload();
|
|
996
|
+
}, [reload]);
|
|
997
|
+
const internalIds = useMemo2(() => {
|
|
998
|
+
const ids = /* @__PURE__ */ new Set();
|
|
999
|
+
for (const m of modules) {
|
|
1000
|
+
if (m.id) ids.add(m.id);
|
|
1001
|
+
if (m.name) ids.add(m.name);
|
|
1002
|
+
}
|
|
1003
|
+
return ids;
|
|
1004
|
+
}, [modules]);
|
|
1005
|
+
const active = modules[activeIndex] || {
|
|
1006
|
+
id: "empty",
|
|
1007
|
+
name: "Kein Modul gefunden",
|
|
1008
|
+
path: rootDir,
|
|
1009
|
+
relPath: "Root",
|
|
1010
|
+
ecosystem: strategy.ecosystem,
|
|
1011
|
+
isRoot: true
|
|
1012
|
+
};
|
|
1013
|
+
return {
|
|
1014
|
+
modules,
|
|
1015
|
+
active,
|
|
1016
|
+
activeIndex,
|
|
1017
|
+
setActiveIndex,
|
|
1018
|
+
internalIds,
|
|
1019
|
+
loading,
|
|
1020
|
+
reload
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// src/hooks/use-packages.ts
|
|
1025
|
+
import { useState as useState3, useEffect as useEffect2, useMemo as useMemo3, useCallback as useCallback3 } from "react";
|
|
1026
|
+
import semver4 from "semver";
|
|
1027
|
+
function usePackages(strategy, module, internalIds, excludeScopes = [], channel = "stable") {
|
|
1028
|
+
const [candidates, setCandidates] = useState3([]);
|
|
1029
|
+
const [loading, setLoading] = useState3(false);
|
|
1030
|
+
const [activeTab, setActiveTab] = useState3("all");
|
|
1031
|
+
const [cursor, setCursor] = useState3(0);
|
|
1032
|
+
const scan = useCallback3(async () => {
|
|
1033
|
+
if (!module || module.id === "empty") {
|
|
1034
|
+
setCandidates([]);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
setLoading(true);
|
|
1038
|
+
setCursor(0);
|
|
1039
|
+
setCandidates([]);
|
|
1040
|
+
try {
|
|
1041
|
+
const updates = await strategy.reader.scan(module, excludeScopes, internalIds, channel);
|
|
1042
|
+
setCandidates(updates);
|
|
1043
|
+
} catch {
|
|
1044
|
+
setCandidates([]);
|
|
1045
|
+
} finally {
|
|
1046
|
+
setLoading(false);
|
|
1047
|
+
}
|
|
1048
|
+
}, [strategy, module.path, module.id, internalIds, excludeScopes, channel]);
|
|
1049
|
+
useEffect2(() => {
|
|
1050
|
+
if (module && module.id !== "empty") {
|
|
1051
|
+
scan();
|
|
1052
|
+
}
|
|
1053
|
+
}, [module.path, module.id, strategy.ecosystem, channel]);
|
|
1054
|
+
const visible = useMemo3(() => {
|
|
1055
|
+
if (activeTab === "prod") return candidates.filter((c) => c.scope === "prod");
|
|
1056
|
+
if (activeTab === "dev") return candidates.filter((c) => c.scope !== "prod");
|
|
1057
|
+
return candidates;
|
|
1058
|
+
}, [candidates, activeTab]);
|
|
1059
|
+
const counts = useMemo3(() => {
|
|
1060
|
+
const selected = candidates.filter((c) => c.selected);
|
|
1061
|
+
return {
|
|
1062
|
+
total: candidates.length,
|
|
1063
|
+
prodCount: candidates.filter((c) => c.scope === "prod").length,
|
|
1064
|
+
devCount: candidates.filter((c) => c.scope !== "prod").length,
|
|
1065
|
+
selectedCount: selected.length,
|
|
1066
|
+
patchCount: selected.filter((c) => c.diff === "patch").length,
|
|
1067
|
+
minorCount: selected.filter((c) => c.diff === "minor").length,
|
|
1068
|
+
majorCount: selected.filter((c) => c.diff === "major").length
|
|
1069
|
+
};
|
|
1070
|
+
}, [candidates]);
|
|
1071
|
+
const togglePackage = useCallback3((identifier) => {
|
|
1072
|
+
setCandidates(
|
|
1073
|
+
(prev) => prev.map(
|
|
1074
|
+
(c) => c.coordinate.identifier === identifier ? { ...c, selected: !c.selected } : c
|
|
1075
|
+
)
|
|
1076
|
+
);
|
|
1077
|
+
}, []);
|
|
1078
|
+
const toggleAllVisible = useCallback3(() => {
|
|
1079
|
+
const allSelected = visible.every((c) => c.selected);
|
|
1080
|
+
const visibleIds = new Set(visible.map((c) => c.coordinate.identifier));
|
|
1081
|
+
setCandidates(
|
|
1082
|
+
(prev) => prev.map(
|
|
1083
|
+
(c) => visibleIds.has(c.coordinate.identifier) ? { ...c, selected: !allSelected } : c
|
|
1084
|
+
)
|
|
1085
|
+
);
|
|
1086
|
+
}, [visible]);
|
|
1087
|
+
const setCustomVersion = useCallback3((identifier, chosenVersion) => {
|
|
1088
|
+
setCandidates(
|
|
1089
|
+
(prev) => prev.map((c) => {
|
|
1090
|
+
if (c.coordinate.identifier !== identifier) return c;
|
|
1091
|
+
const prefix = c.currentRange.startsWith("~") ? "~" : c.currentRange.startsWith("^") ? "^" : "";
|
|
1092
|
+
let diff = "minor";
|
|
1093
|
+
const cleanChosen = semver4.valid(chosenVersion);
|
|
1094
|
+
if (c.currentClean && cleanChosen) {
|
|
1095
|
+
const semDiff = semver4.diff(c.currentClean, chosenVersion);
|
|
1096
|
+
if (semDiff === "major" || semDiff === "minor" || semDiff === "patch") {
|
|
1097
|
+
diff = semDiff;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
const isPre = isPreReleaseVersion(chosenVersion);
|
|
1101
|
+
const preTag = isPre ? extractPreReleaseTag(chosenVersion) : void 0;
|
|
1102
|
+
return {
|
|
1103
|
+
...c,
|
|
1104
|
+
newRange: `${prefix}${chosenVersion}`,
|
|
1105
|
+
diff,
|
|
1106
|
+
selected: true,
|
|
1107
|
+
isPreRelease: isPre,
|
|
1108
|
+
preReleaseTag: preTag,
|
|
1109
|
+
isCustomVersion: true
|
|
1110
|
+
};
|
|
1111
|
+
})
|
|
1112
|
+
);
|
|
1113
|
+
}, []);
|
|
1114
|
+
const switchTab = useCallback3((tab) => {
|
|
1115
|
+
setActiveTab(tab);
|
|
1116
|
+
setCursor(0);
|
|
1117
|
+
}, []);
|
|
1118
|
+
const cycleTab = useCallback3(() => {
|
|
1119
|
+
setActiveTab((t) => t === "all" ? "prod" : t === "prod" ? "dev" : "all");
|
|
1120
|
+
setCursor(0);
|
|
1121
|
+
}, []);
|
|
1122
|
+
return {
|
|
1123
|
+
packages: candidates,
|
|
1124
|
+
loading,
|
|
1125
|
+
visible,
|
|
1126
|
+
counts,
|
|
1127
|
+
activeTab,
|
|
1128
|
+
cursor,
|
|
1129
|
+
setCursor,
|
|
1130
|
+
scan,
|
|
1131
|
+
switchTab,
|
|
1132
|
+
cycleTab,
|
|
1133
|
+
togglePackage,
|
|
1134
|
+
toggleAllVisible,
|
|
1135
|
+
setCustomVersion
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// src/hooks/use-updater.ts
|
|
1140
|
+
import { useState as useState4, useCallback as useCallback4 } from "react";
|
|
1141
|
+
function useUpdater(strategy, rootDir, setView, postUpdate) {
|
|
1142
|
+
const [step, setStep] = useState4("");
|
|
1143
|
+
const [summary, setSummary] = useState4(null);
|
|
1144
|
+
const run = useCallback4(
|
|
1145
|
+
async (module, selected, _t) => {
|
|
1146
|
+
if (selected.length === 0) return;
|
|
1147
|
+
setView("updating");
|
|
1148
|
+
const updaterResult = await strategy.updater.applyUpdates(
|
|
1149
|
+
module,
|
|
1150
|
+
rootDir,
|
|
1151
|
+
selected,
|
|
1152
|
+
(progressMessage) => setStep(progressMessage)
|
|
1153
|
+
);
|
|
1154
|
+
setStep("F\xFChre Verifikation und Konsistenzchecks durch\u2026");
|
|
1155
|
+
const verifierResult = await strategy.verifier.verify(
|
|
1156
|
+
module,
|
|
1157
|
+
rootDir,
|
|
1158
|
+
postUpdate?.script,
|
|
1159
|
+
postUpdate?.label
|
|
1160
|
+
);
|
|
1161
|
+
setSummary({
|
|
1162
|
+
...updaterResult,
|
|
1163
|
+
verificationStatus: verifierResult.status,
|
|
1164
|
+
verificationDetails: verifierResult.details,
|
|
1165
|
+
verificationLabel: verifierResult.label
|
|
1166
|
+
});
|
|
1167
|
+
setView("summary");
|
|
1168
|
+
},
|
|
1169
|
+
[strategy, rootDir, setView, postUpdate]
|
|
1170
|
+
);
|
|
1171
|
+
return { step, summary, run };
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// src/components/splash-screen.tsx
|
|
1175
|
+
import { useState as useState5, useEffect as useEffect3 } from "react";
|
|
1176
|
+
import { Box, Text } from "ink";
|
|
1177
|
+
|
|
1178
|
+
// src/theme.ts
|
|
1179
|
+
var theme = {
|
|
1180
|
+
icon: "\u{1FAB6}",
|
|
1181
|
+
colors: {
|
|
1182
|
+
// Brand & Akzente (Orange-Palette)
|
|
1183
|
+
brand: "#FF8800",
|
|
1184
|
+
brandLight: "#FFA726",
|
|
1185
|
+
brandDark: "#E65100",
|
|
1186
|
+
brandSoft: "#FFE0B2",
|
|
1187
|
+
accent: "#FF9800",
|
|
1188
|
+
// Süßes Hühnchen (Mascot)
|
|
1189
|
+
mascotBody: "#FFD54F",
|
|
1190
|
+
mascotComb: "#FF7043",
|
|
1191
|
+
mascotBeak: "#FF9800",
|
|
1192
|
+
mascotBlush: "#FF8A80",
|
|
1193
|
+
mascotFeet: "#FFA726",
|
|
1194
|
+
// Statusfarben
|
|
1195
|
+
success: "green",
|
|
1196
|
+
warning: "yellow",
|
|
1197
|
+
danger: "red",
|
|
1198
|
+
muted: "gray",
|
|
1199
|
+
white: "white",
|
|
1200
|
+
// UI-Elemente
|
|
1201
|
+
border: "#FF8800",
|
|
1202
|
+
borderMuted: "gray",
|
|
1203
|
+
focus: "#FFA726"
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
|
|
1207
|
+
// src/components/splash-screen.tsx
|
|
1208
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1209
|
+
var SplashScreen = ({ onComplete, author }) => {
|
|
1210
|
+
const t = en;
|
|
1211
|
+
const [progress, setProgress] = useState5(0);
|
|
1212
|
+
const [frame, setFrame] = useState5(0);
|
|
1213
|
+
useEffect3(() => {
|
|
1214
|
+
const progressTimer = setInterval(() => {
|
|
1215
|
+
setProgress((prev) => {
|
|
1216
|
+
if (prev >= 100) {
|
|
1217
|
+
clearInterval(progressTimer);
|
|
1218
|
+
setTimeout(onComplete, 250);
|
|
1219
|
+
return 100;
|
|
1220
|
+
}
|
|
1221
|
+
return prev + 5;
|
|
1222
|
+
});
|
|
1223
|
+
}, 150);
|
|
1224
|
+
const mascotTimer = setInterval(() => {
|
|
1225
|
+
setFrame((prev) => (prev + 1) % 4);
|
|
1226
|
+
}, 400);
|
|
1227
|
+
return () => {
|
|
1228
|
+
clearInterval(progressTimer);
|
|
1229
|
+
clearInterval(mascotTimer);
|
|
1230
|
+
};
|
|
1231
|
+
}, [onComplete]);
|
|
1232
|
+
const mascotFrames = [
|
|
1233
|
+
{ face: "( \u2022\u04E9\u2022 )", arms: "/ >\u{1FAB6}", chirp: "*chirp!*" },
|
|
1234
|
+
{ face: "( \u1D54\u04E9\u1D54 )", arms: "/ >\u{1FAB6}", chirp: "*hello!*" },
|
|
1235
|
+
{ face: "( -\u04E9- )", arms: "/ >\u{1FAB6}", chirp: "*blink*" },
|
|
1236
|
+
{ face: "( \u2022\u04E9\u2022 )", arms: "\\(\u2022\u04E9\u2022)/", chirp: "ready! \u2728" }
|
|
1237
|
+
];
|
|
1238
|
+
const curMascot = mascotFrames[frame % mascotFrames.length];
|
|
1239
|
+
const barLength = 24;
|
|
1240
|
+
const filledLength = Math.round(progress / 100 * barLength);
|
|
1241
|
+
const emptyLength = Math.max(0, barLength - filledLength);
|
|
1242
|
+
const progressBar = "\u2588".repeat(filledLength) + "\u2591".repeat(emptyLength);
|
|
1243
|
+
return /* @__PURE__ */ jsxs(
|
|
1244
|
+
Box,
|
|
1245
|
+
{
|
|
1246
|
+
flexDirection: "column",
|
|
1247
|
+
alignItems: "center",
|
|
1248
|
+
justifyContent: "center",
|
|
1249
|
+
borderStyle: "round",
|
|
1250
|
+
borderColor: theme.colors.brand,
|
|
1251
|
+
paddingY: 2,
|
|
1252
|
+
paddingX: 4,
|
|
1253
|
+
marginY: 1,
|
|
1254
|
+
children: [
|
|
1255
|
+
/* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { bold: true, color: theme.colors.brand, children: [
|
|
1256
|
+
theme.icon,
|
|
1257
|
+
" E L E V A T E"
|
|
1258
|
+
] }) }),
|
|
1259
|
+
/* @__PURE__ */ jsx(Text, { color: theme.colors.muted, children: t.splash.tagline }),
|
|
1260
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", alignItems: "center", marginY: 1, children: [
|
|
1261
|
+
/* @__PURE__ */ jsx(Text, { color: theme.colors.mascotComb, bold: true, children: "(\\_/)" }),
|
|
1262
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
1263
|
+
/* @__PURE__ */ jsxs(Text, { color: theme.colors.mascotBody, bold: true, children: [
|
|
1264
|
+
curMascot.face,
|
|
1265
|
+
" "
|
|
1266
|
+
] }),
|
|
1267
|
+
/* @__PURE__ */ jsx(Text, { color: theme.colors.brandLight, italic: true, children: curMascot.chirp })
|
|
1268
|
+
] }),
|
|
1269
|
+
/* @__PURE__ */ jsx(Text, { color: theme.colors.brandLight, bold: true, children: curMascot.arms })
|
|
1270
|
+
] }),
|
|
1271
|
+
author && /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, children: t.header.developedBy(author) }) }),
|
|
1272
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", alignItems: "center", marginTop: 1, children: [
|
|
1273
|
+
/* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, { color: theme.colors.brandLight, bold: true, children: [
|
|
1274
|
+
"[",
|
|
1275
|
+
progressBar,
|
|
1276
|
+
"] ",
|
|
1277
|
+
Math.min(100, progress),
|
|
1278
|
+
"%"
|
|
1279
|
+
] }) }),
|
|
1280
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, children: t.splash.loading }) })
|
|
1281
|
+
] }),
|
|
1282
|
+
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: theme.colors.muted, dimColor: true, children: t.splash.skipHint }) })
|
|
1283
|
+
]
|
|
1284
|
+
}
|
|
1285
|
+
);
|
|
1286
|
+
};
|
|
1287
|
+
|
|
1288
|
+
// src/components/header.tsx
|
|
1289
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
1290
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1291
|
+
var Header = ({
|
|
1292
|
+
author,
|
|
1293
|
+
t,
|
|
1294
|
+
currentLocale,
|
|
1295
|
+
strategy,
|
|
1296
|
+
nextStrategyName,
|
|
1297
|
+
channel
|
|
1298
|
+
}) => /* @__PURE__ */ jsxs2(
|
|
1299
|
+
Box2,
|
|
1300
|
+
{
|
|
1301
|
+
borderStyle: "round",
|
|
1302
|
+
borderColor: theme.colors.brand,
|
|
1303
|
+
paddingX: 1,
|
|
1304
|
+
justifyContent: "space-between",
|
|
1305
|
+
alignItems: "center",
|
|
1306
|
+
children: [
|
|
1307
|
+
/* @__PURE__ */ jsxs2(Box2, { alignItems: "center", children: [
|
|
1308
|
+
/* @__PURE__ */ jsxs2(Text2, { bold: true, color: theme.colors.brand, children: [
|
|
1309
|
+
theme.icon,
|
|
1310
|
+
" ",
|
|
1311
|
+
t.header.title
|
|
1312
|
+
] }),
|
|
1313
|
+
/* @__PURE__ */ jsxs2(Box2, { marginLeft: 2, children: [
|
|
1314
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.colors.muted, children: "\u2502 " }),
|
|
1315
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.colors.brandLight, bold: true, children: [
|
|
1316
|
+
strategy.icon,
|
|
1317
|
+
" ",
|
|
1318
|
+
strategy.displayName
|
|
1319
|
+
] }),
|
|
1320
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.colors.muted, children: [
|
|
1321
|
+
" [",
|
|
1322
|
+
channel,
|
|
1323
|
+
"] "
|
|
1324
|
+
] }),
|
|
1325
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.colors.muted, children: [
|
|
1326
|
+
"(",
|
|
1327
|
+
t.header.ecosystemToggleHint(nextStrategyName),
|
|
1328
|
+
")"
|
|
1329
|
+
] })
|
|
1330
|
+
] }),
|
|
1331
|
+
/* @__PURE__ */ jsxs2(Box2, { marginLeft: 2, children: [
|
|
1332
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.colors.muted, children: "\u2502 " }),
|
|
1333
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.colors.brandLight, bold: true, children: [
|
|
1334
|
+
"[",
|
|
1335
|
+
currentLocale.toUpperCase(),
|
|
1336
|
+
"]"
|
|
1337
|
+
] }),
|
|
1338
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.colors.muted, children: [
|
|
1339
|
+
" ",
|
|
1340
|
+
t.header.langToggleHint
|
|
1341
|
+
] })
|
|
1342
|
+
] })
|
|
1343
|
+
] }),
|
|
1344
|
+
author && /* @__PURE__ */ jsx2(Text2, { color: theme.colors.muted, children: t.header.developedBy(author) })
|
|
1345
|
+
]
|
|
1346
|
+
}
|
|
1347
|
+
);
|
|
1348
|
+
|
|
1349
|
+
// src/components/workspace-bar.tsx
|
|
1350
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
1351
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1352
|
+
var WorkspaceBar = ({ module, excludeScopes, strategy, t }) => {
|
|
1353
|
+
const scopeHint = excludeScopes.length > 0 ? t.status.symlinksProtected(excludeScopes) : t.status.defaultProtected;
|
|
1354
|
+
return /* @__PURE__ */ jsxs3(Box3, { marginY: 1, justifyContent: "space-between", alignItems: "center", children: [
|
|
1355
|
+
/* @__PURE__ */ jsxs3(Box3, { alignItems: "center", flexShrink: 1, children: [
|
|
1356
|
+
/* @__PURE__ */ jsxs3(Text3, { bold: true, children: [
|
|
1357
|
+
"\u{1F4CD} ",
|
|
1358
|
+
strategy.icon,
|
|
1359
|
+
" ",
|
|
1360
|
+
t.workspace.label,
|
|
1361
|
+
": "
|
|
1362
|
+
] }),
|
|
1363
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.colors.brandLight, children: module.name }),
|
|
1364
|
+
/* @__PURE__ */ jsxs3(Text3, { color: theme.colors.muted, children: [
|
|
1365
|
+
" (",
|
|
1366
|
+
module.relPath,
|
|
1367
|
+
")"
|
|
1368
|
+
] })
|
|
1369
|
+
] }),
|
|
1370
|
+
/* @__PURE__ */ jsxs3(Box3, { alignItems: "center", marginLeft: 2, children: [
|
|
1371
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.colors.muted, children: scopeHint }),
|
|
1372
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.colors.muted, children: " \u2502 " }),
|
|
1373
|
+
/* @__PURE__ */ jsx3(Text3, { color: theme.colors.brandLight, bold: true, children: "[W]" }),
|
|
1374
|
+
/* @__PURE__ */ jsxs3(Text3, { color: theme.colors.muted, children: [
|
|
1375
|
+
" ",
|
|
1376
|
+
t.workspace.switchHint.replace("[W]", "").trim()
|
|
1377
|
+
] })
|
|
1378
|
+
] })
|
|
1379
|
+
] });
|
|
1380
|
+
};
|
|
1381
|
+
|
|
1382
|
+
// src/components/tab-bar.tsx
|
|
1383
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
1384
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1385
|
+
var TabBar = ({ activeTab, counts, strategy, t }) => {
|
|
1386
|
+
const labels = strategy.getTabLabels(counts, t);
|
|
1387
|
+
return /* @__PURE__ */ jsxs4(Box4, { borderStyle: "single", borderColor: theme.colors.borderMuted, paddingX: 1, children: [
|
|
1388
|
+
/* @__PURE__ */ jsx4(
|
|
1389
|
+
Text4,
|
|
1390
|
+
{
|
|
1391
|
+
bold: true,
|
|
1392
|
+
color: activeTab === "all" ? theme.colors.brand : theme.colors.white,
|
|
1393
|
+
underline: activeTab === "all",
|
|
1394
|
+
children: labels.all
|
|
1395
|
+
}
|
|
1396
|
+
),
|
|
1397
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.colors.muted, children: " \u2502 " }),
|
|
1398
|
+
/* @__PURE__ */ jsx4(
|
|
1399
|
+
Text4,
|
|
1400
|
+
{
|
|
1401
|
+
bold: true,
|
|
1402
|
+
color: activeTab === "prod" ? theme.colors.brand : theme.colors.white,
|
|
1403
|
+
underline: activeTab === "prod",
|
|
1404
|
+
children: labels.prod
|
|
1405
|
+
}
|
|
1406
|
+
),
|
|
1407
|
+
/* @__PURE__ */ jsx4(Text4, { color: theme.colors.muted, children: " \u2502 " }),
|
|
1408
|
+
/* @__PURE__ */ jsx4(
|
|
1409
|
+
Text4,
|
|
1410
|
+
{
|
|
1411
|
+
bold: true,
|
|
1412
|
+
color: activeTab === "dev" ? theme.colors.brand : theme.colors.white,
|
|
1413
|
+
underline: activeTab === "dev",
|
|
1414
|
+
children: labels.dev
|
|
1415
|
+
}
|
|
1416
|
+
)
|
|
1417
|
+
] });
|
|
1418
|
+
};
|
|
1419
|
+
|
|
1420
|
+
// src/components/package-list.tsx
|
|
1421
|
+
import { Box as Box6, Text as Text6 } from "ink";
|
|
1422
|
+
|
|
1423
|
+
// src/components/package-row.tsx
|
|
1424
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
1425
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1426
|
+
var PackageRow = ({ item, isFocused, t }) => {
|
|
1427
|
+
const badge = item.diff === "major" ? /* @__PURE__ */ jsx5(Text5, { bold: true, color: theme.colors.danger, children: t.badges.major }) : item.diff === "minor" ? /* @__PURE__ */ jsx5(Text5, { color: theme.colors.warning, children: t.badges.minor }) : /* @__PURE__ */ jsx5(Text5, { color: theme.colors.success, children: t.badges.patch });
|
|
1428
|
+
const displayName = item.coordinate?.identifier || item.name || "";
|
|
1429
|
+
const isDevOrTest = item.scope === "dev" || item.scope === "test";
|
|
1430
|
+
return /* @__PURE__ */ jsxs5(Box5, { justifyContent: "space-between", children: [
|
|
1431
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1432
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.colors.brandLight, bold: true, children: isFocused ? "\u2794 " : " " }),
|
|
1433
|
+
/* @__PURE__ */ jsx5(Text5, { color: item.selected ? theme.colors.success : theme.colors.muted, bold: true, children: item.selected ? "[\u2714] " : "[ ] " }),
|
|
1434
|
+
/* @__PURE__ */ jsxs5(Text5, { bold: isFocused, color: isFocused ? theme.colors.brandLight : theme.colors.white, children: [
|
|
1435
|
+
displayName.slice(0, 42).padEnd(42),
|
|
1436
|
+
" "
|
|
1437
|
+
] }),
|
|
1438
|
+
/* @__PURE__ */ jsxs5(Text5, { color: theme.colors.muted, children: [
|
|
1439
|
+
item.currentRange.padEnd(12),
|
|
1440
|
+
" "
|
|
1441
|
+
] }),
|
|
1442
|
+
/* @__PURE__ */ jsx5(Text5, { color: theme.colors.muted, children: "\u2794 " }),
|
|
1443
|
+
/* @__PURE__ */ jsxs5(Text5, { bold: true, color: theme.colors.white, children: [
|
|
1444
|
+
item.newRange.padEnd(12),
|
|
1445
|
+
" "
|
|
1446
|
+
] })
|
|
1447
|
+
] }),
|
|
1448
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1449
|
+
item.isCustomVersion && /* @__PURE__ */ jsxs5(Text5, { bold: true, color: theme.colors.brandLight, children: [
|
|
1450
|
+
"[",
|
|
1451
|
+
t.versionModal.customBadge,
|
|
1452
|
+
"]",
|
|
1453
|
+
" "
|
|
1454
|
+
] }),
|
|
1455
|
+
item.isPreRelease && /* @__PURE__ */ jsxs5(Text5, { bold: true, color: "#E040FB", children: [
|
|
1456
|
+
"[",
|
|
1457
|
+
item.preReleaseTag || "PRE",
|
|
1458
|
+
"]",
|
|
1459
|
+
" "
|
|
1460
|
+
] }),
|
|
1461
|
+
badge,
|
|
1462
|
+
/* @__PURE__ */ jsxs5(Text5, { color: theme.colors.muted, children: [
|
|
1463
|
+
" ",
|
|
1464
|
+
isDevOrTest ? t.badges.dev : t.badges.prod
|
|
1465
|
+
] })
|
|
1466
|
+
] })
|
|
1467
|
+
] });
|
|
1468
|
+
};
|
|
1469
|
+
|
|
1470
|
+
// src/components/package-list.tsx
|
|
1471
|
+
import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1472
|
+
var WINDOW_SIZE = 7;
|
|
1473
|
+
var PackageList = ({ items, cursor, loading, t }) => {
|
|
1474
|
+
const startIdx = Math.max(
|
|
1475
|
+
0,
|
|
1476
|
+
Math.min(
|
|
1477
|
+
cursor - Math.floor(WINDOW_SIZE / 2),
|
|
1478
|
+
Math.max(0, items.length - WINDOW_SIZE)
|
|
1479
|
+
)
|
|
1480
|
+
);
|
|
1481
|
+
const window = items.slice(startIdx, startIdx + WINDOW_SIZE);
|
|
1482
|
+
return /* @__PURE__ */ jsx6(
|
|
1483
|
+
Box6,
|
|
1484
|
+
{
|
|
1485
|
+
flexDirection: "column",
|
|
1486
|
+
borderStyle: "single",
|
|
1487
|
+
borderColor: theme.colors.border,
|
|
1488
|
+
padding: 1,
|
|
1489
|
+
marginY: 1,
|
|
1490
|
+
minHeight: 10,
|
|
1491
|
+
children: loading ? /* @__PURE__ */ jsx6(Box6, { marginY: 2, justifyContent: "center", children: /* @__PURE__ */ jsx6(Text6, { color: theme.colors.warning, children: t.list.scanning }) }) : items.length === 0 ? /* @__PURE__ */ jsx6(Box6, { marginY: 2, justifyContent: "center", children: /* @__PURE__ */ jsx6(Text6, { color: theme.colors.success, children: t.list.allUpToDate }) }) : /* @__PURE__ */ jsxs6(Fragment, { children: [
|
|
1492
|
+
startIdx > 0 && /* @__PURE__ */ jsx6(Text6, { color: theme.colors.muted, children: t.list.moreAbove }),
|
|
1493
|
+
window.map((item, idx) => /* @__PURE__ */ jsx6(
|
|
1494
|
+
PackageRow,
|
|
1495
|
+
{
|
|
1496
|
+
item,
|
|
1497
|
+
isFocused: startIdx + idx === cursor,
|
|
1498
|
+
t
|
|
1499
|
+
},
|
|
1500
|
+
`${item.coordinate?.identifier || item.name}_${startIdx + idx}`
|
|
1501
|
+
)),
|
|
1502
|
+
startIdx + WINDOW_SIZE < items.length && /* @__PURE__ */ jsx6(Text6, { color: theme.colors.muted, children: t.list.moreBelow(items.length - (startIdx + WINDOW_SIZE)) })
|
|
1503
|
+
] })
|
|
1504
|
+
}
|
|
1505
|
+
);
|
|
1506
|
+
};
|
|
1507
|
+
|
|
1508
|
+
// src/components/status-bar.tsx
|
|
1509
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
1510
|
+
|
|
1511
|
+
// src/components/mascot.tsx
|
|
1512
|
+
import { useState as useState6, useEffect as useEffect4 } from "react";
|
|
1513
|
+
import { Box as Box7, Text as Text7 } from "ink";
|
|
1514
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1515
|
+
var ChickenMascot = ({ state = "idle", t, ecosystem = "npm" }) => {
|
|
1516
|
+
const [frame, setFrame] = useState6(0);
|
|
1517
|
+
const [chirpIndex, setChirpIndex] = useState6(0);
|
|
1518
|
+
useEffect4(() => {
|
|
1519
|
+
const timer = setInterval(() => {
|
|
1520
|
+
setFrame((prev) => (prev + 1) % 6);
|
|
1521
|
+
}, 1400);
|
|
1522
|
+
return () => clearInterval(timer);
|
|
1523
|
+
}, []);
|
|
1524
|
+
useEffect4(() => {
|
|
1525
|
+
const chirpTimer = setInterval(() => {
|
|
1526
|
+
setChirpIndex((prev) => prev + 1);
|
|
1527
|
+
}, 4200);
|
|
1528
|
+
return () => clearInterval(chirpTimer);
|
|
1529
|
+
}, []);
|
|
1530
|
+
let chickFace = "( \u2022\u04E9\u2022 )";
|
|
1531
|
+
let wingLeft = " ";
|
|
1532
|
+
let wingRight = " ";
|
|
1533
|
+
let currentChirp = "";
|
|
1534
|
+
if (state === "scanning") {
|
|
1535
|
+
const scanningFaces = ["( \u2022\u04E9\u2022 )?", "( \u02D8\u04E9\u02D8 )\u{1F50D}", "( \u2022\u04E9\u2022 )\u{1F50E}", "( \u1D54\u04E9\u1D54 )?"];
|
|
1536
|
+
chickFace = scanningFaces[frame % scanningFaces.length];
|
|
1537
|
+
currentChirp = t.mascot.scanningChirps[chirpIndex % t.mascot.scanningChirps.length];
|
|
1538
|
+
} else if (state === "updating") {
|
|
1539
|
+
const updatingFrames = [
|
|
1540
|
+
{ face: "( \u2022\u04E9\u2022 )", l: "\u0E07", r: "\u0E07" },
|
|
1541
|
+
{ face: "( \u02D8\u04E9\u02D8 )", l: "~", r: "~" },
|
|
1542
|
+
{ face: "( \u2022\u04E9\u2022 )", l: "\u1559", r: "\u1557" },
|
|
1543
|
+
{ face: "( \u1D54\u04E9\u1D54 )", l: "\u0E07", r: "\u0E07" }
|
|
1544
|
+
];
|
|
1545
|
+
const cur = updatingFrames[frame % updatingFrames.length];
|
|
1546
|
+
chickFace = cur.face;
|
|
1547
|
+
wingLeft = cur.l;
|
|
1548
|
+
wingRight = cur.r;
|
|
1549
|
+
currentChirp = t.mascot.updatingChirps[chirpIndex % t.mascot.updatingChirps.length];
|
|
1550
|
+
} else if (state === "success") {
|
|
1551
|
+
const successFaces = ["\\( \u1D54\u04E9\u1D54 )/", "\\( \u2022\u04E9\u2022 )/ \u{1F389}", "*( \u1D54\u04E9\u1D54 )* \u{1FAB6}", "\\( \u1D54\u04E9\u1D54 )/ \u2728"];
|
|
1552
|
+
chickFace = successFaces[frame % successFaces.length];
|
|
1553
|
+
currentChirp = t.mascot.successChirps[chirpIndex % t.mascot.successChirps.length];
|
|
1554
|
+
} else {
|
|
1555
|
+
const idleFaces = [
|
|
1556
|
+
{ face: "( \u2022\u04E9\u2022 )", l: " ", r: " " },
|
|
1557
|
+
{ face: "( \u2022\u04E9\u2022 )", l: " ", r: " " },
|
|
1558
|
+
{ face: "( -\u04E9- )", l: " ", r: " " },
|
|
1559
|
+
// Blinzeln
|
|
1560
|
+
{ face: "( \u1D54\u04E9\u1D54 )", l: " ", r: " " },
|
|
1561
|
+
// Lächeln
|
|
1562
|
+
{ face: "( \u2022\u04E9- )", l: " ", r: " " },
|
|
1563
|
+
// Zwinkern
|
|
1564
|
+
{ face: "( \u02D8\u04E9\u02D8 )", l: " ", r: "~\u{1FAB6}" }
|
|
1565
|
+
// Flausch-Feder
|
|
1566
|
+
];
|
|
1567
|
+
const cur = idleFaces[frame % idleFaces.length];
|
|
1568
|
+
chickFace = cur.face;
|
|
1569
|
+
wingLeft = cur.l;
|
|
1570
|
+
wingRight = cur.r;
|
|
1571
|
+
const chirpsPool = ecosystem === "maven" ? [...t.mascot.idleChirps, ...t.mascot.mavenChirps] : t.mascot.idleChirps;
|
|
1572
|
+
currentChirp = chirpsPool[chirpIndex % chirpsPool.length];
|
|
1573
|
+
}
|
|
1574
|
+
return /* @__PURE__ */ jsxs7(Box7, { alignItems: "center", children: [
|
|
1575
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.colors.mascotComb, bold: true, children: wingLeft === " " ? " " : wingLeft }),
|
|
1576
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.colors.mascotBody, bold: true, children: chickFace }),
|
|
1577
|
+
/* @__PURE__ */ jsx7(Text7, { color: theme.colors.brandLight, bold: true, children: wingRight }),
|
|
1578
|
+
currentChirp ? /* @__PURE__ */ jsx7(Box7, { marginLeft: 1, children: /* @__PURE__ */ jsx7(Text7, { color: theme.colors.brandLight, italic: true, children: currentChirp }) }) : null
|
|
1579
|
+
] });
|
|
1580
|
+
};
|
|
1581
|
+
|
|
1582
|
+
// src/components/status-bar.tsx
|
|
1583
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1584
|
+
var StatusBar = ({
|
|
1585
|
+
counts,
|
|
1586
|
+
visibleCount,
|
|
1587
|
+
t,
|
|
1588
|
+
mascotState = "idle",
|
|
1589
|
+
ecosystem = "npm"
|
|
1590
|
+
}) => {
|
|
1591
|
+
return /* @__PURE__ */ jsxs8(
|
|
1592
|
+
Box8,
|
|
1593
|
+
{
|
|
1594
|
+
borderStyle: "single",
|
|
1595
|
+
borderColor: theme.colors.borderMuted,
|
|
1596
|
+
paddingX: 1,
|
|
1597
|
+
justifyContent: "space-between",
|
|
1598
|
+
alignItems: "center",
|
|
1599
|
+
children: [
|
|
1600
|
+
/* @__PURE__ */ jsxs8(Box8, { children: [
|
|
1601
|
+
/* @__PURE__ */ jsxs8(Text8, { bold: true, children: [
|
|
1602
|
+
t.status.selectedOf(counts.selectedCount, visibleCount),
|
|
1603
|
+
" "
|
|
1604
|
+
] }),
|
|
1605
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.colors.muted, children: t.status.breakdown(counts.patchCount, counts.minorCount, counts.majorCount) })
|
|
1606
|
+
] }),
|
|
1607
|
+
/* @__PURE__ */ jsx8(ChickenMascot, { state: mascotState, t, ecosystem })
|
|
1608
|
+
]
|
|
1609
|
+
}
|
|
1610
|
+
);
|
|
1611
|
+
};
|
|
1612
|
+
|
|
1613
|
+
// src/components/controls-bar.tsx
|
|
1614
|
+
import { Box as Box9, Text as Text9 } from "ink";
|
|
1615
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1616
|
+
var ControlsBar = ({ t }) => /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexWrap: "wrap", children: [
|
|
1617
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.colors.brandLight, bold: true, children: "[\u2191/\u2193]" }),
|
|
1618
|
+
/* @__PURE__ */ jsxs9(Text9, { color: theme.colors.muted, children: [
|
|
1619
|
+
" ",
|
|
1620
|
+
t.controls.navigate,
|
|
1621
|
+
" \u2502 "
|
|
1622
|
+
] }),
|
|
1623
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.colors.brandLight, bold: true, children: "[Space]" }),
|
|
1624
|
+
/* @__PURE__ */ jsxs9(Text9, { color: theme.colors.muted, children: [
|
|
1625
|
+
" ",
|
|
1626
|
+
t.controls.toggle,
|
|
1627
|
+
" \u2502 "
|
|
1628
|
+
] }),
|
|
1629
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.colors.brandLight, bold: true, children: "[A]" }),
|
|
1630
|
+
/* @__PURE__ */ jsxs9(Text9, { color: theme.colors.muted, children: [
|
|
1631
|
+
" ",
|
|
1632
|
+
t.controls.all,
|
|
1633
|
+
" \u2502 "
|
|
1634
|
+
] }),
|
|
1635
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.colors.brandLight, bold: true, children: "[Tab/1/2/3]" }),
|
|
1636
|
+
/* @__PURE__ */ jsxs9(Text9, { color: theme.colors.muted, children: [
|
|
1637
|
+
" ",
|
|
1638
|
+
t.controls.tabs,
|
|
1639
|
+
" \u2502 "
|
|
1640
|
+
] }),
|
|
1641
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.colors.brandLight, bold: true, children: "[W]" }),
|
|
1642
|
+
/* @__PURE__ */ jsxs9(Text9, { color: theme.colors.muted, children: [
|
|
1643
|
+
" ",
|
|
1644
|
+
t.controls.workspace,
|
|
1645
|
+
" \u2502 "
|
|
1646
|
+
] }),
|
|
1647
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.colors.brandLight, bold: true, children: "[V]" }),
|
|
1648
|
+
/* @__PURE__ */ jsxs9(Text9, { color: theme.colors.muted, children: [
|
|
1649
|
+
" ",
|
|
1650
|
+
t.controls.version,
|
|
1651
|
+
" \u2502 "
|
|
1652
|
+
] }),
|
|
1653
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.colors.brand, bold: true, children: "[E]" }),
|
|
1654
|
+
/* @__PURE__ */ jsxs9(Text9, { color: theme.colors.muted, children: [
|
|
1655
|
+
" ",
|
|
1656
|
+
t.controls.ecosystem,
|
|
1657
|
+
" \u2502 "
|
|
1658
|
+
] }),
|
|
1659
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.colors.brandLight, bold: true, children: "[L]" }),
|
|
1660
|
+
/* @__PURE__ */ jsxs9(Text9, { color: theme.colors.muted, children: [
|
|
1661
|
+
" ",
|
|
1662
|
+
t.controls.language,
|
|
1663
|
+
" \u2502 "
|
|
1664
|
+
] }),
|
|
1665
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.colors.success, bold: true, children: "[U]" }),
|
|
1666
|
+
/* @__PURE__ */ jsxs9(Text9, { color: theme.colors.muted, children: [
|
|
1667
|
+
" ",
|
|
1668
|
+
t.controls.update,
|
|
1669
|
+
" \u2502 "
|
|
1670
|
+
] }),
|
|
1671
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.colors.danger, bold: true, children: "[Q]" }),
|
|
1672
|
+
/* @__PURE__ */ jsxs9(Text9, { color: theme.colors.muted, children: [
|
|
1673
|
+
" ",
|
|
1674
|
+
t.controls.quit
|
|
1675
|
+
] })
|
|
1676
|
+
] });
|
|
1677
|
+
|
|
1678
|
+
// src/components/workspace-modal.tsx
|
|
1679
|
+
import { Box as Box10, Text as Text10 } from "ink";
|
|
1680
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1681
|
+
var WorkspaceModal = ({ modules, focusedIndex, strategy, t }) => /* @__PURE__ */ jsxs10(
|
|
1682
|
+
Box10,
|
|
1683
|
+
{
|
|
1684
|
+
flexDirection: "column",
|
|
1685
|
+
borderStyle: "single",
|
|
1686
|
+
borderColor: theme.colors.brand,
|
|
1687
|
+
padding: 1,
|
|
1688
|
+
marginY: 1,
|
|
1689
|
+
children: [
|
|
1690
|
+
/* @__PURE__ */ jsxs10(Text10, { bold: true, color: theme.colors.brandLight, children: [
|
|
1691
|
+
strategy.icon,
|
|
1692
|
+
" ",
|
|
1693
|
+
t.workspace.modalTitle
|
|
1694
|
+
] }),
|
|
1695
|
+
/* @__PURE__ */ jsx10(Box10, { flexDirection: "column", marginTop: 1, children: modules.map((m, idx) => /* @__PURE__ */ jsxs10(Box10, { children: [
|
|
1696
|
+
/* @__PURE__ */ jsxs10(
|
|
1697
|
+
Text10,
|
|
1698
|
+
{
|
|
1699
|
+
color: idx === focusedIndex ? theme.colors.brandLight : theme.colors.white,
|
|
1700
|
+
bold: idx === focusedIndex,
|
|
1701
|
+
children: [
|
|
1702
|
+
idx === focusedIndex ? " \u2794 " : " ",
|
|
1703
|
+
m.name.slice(0, 48).padEnd(48)
|
|
1704
|
+
]
|
|
1705
|
+
}
|
|
1706
|
+
),
|
|
1707
|
+
/* @__PURE__ */ jsxs10(Text10, { color: theme.colors.muted, children: [
|
|
1708
|
+
"(",
|
|
1709
|
+
m.relPath,
|
|
1710
|
+
")"
|
|
1711
|
+
] })
|
|
1712
|
+
] }, m.path)) })
|
|
1713
|
+
]
|
|
1714
|
+
}
|
|
1715
|
+
);
|
|
1716
|
+
|
|
1717
|
+
// src/components/version-modal.tsx
|
|
1718
|
+
import { useState as useState7, useEffect as useEffect5, useMemo as useMemo4 } from "react";
|
|
1719
|
+
import { Box as Box11, Text as Text11, useInput } from "ink";
|
|
1720
|
+
import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1721
|
+
var WINDOW_SIZE2 = 8;
|
|
1722
|
+
var VersionModal = ({
|
|
1723
|
+
candidate,
|
|
1724
|
+
strategy,
|
|
1725
|
+
t,
|
|
1726
|
+
onSelect,
|
|
1727
|
+
onCancel
|
|
1728
|
+
}) => {
|
|
1729
|
+
const [allVersions, setAllVersions] = useState7([]);
|
|
1730
|
+
const [loading, setLoading] = useState7(true);
|
|
1731
|
+
const [filterText, setFilterText] = useState7("");
|
|
1732
|
+
const [cursor, setCursor] = useState7(0);
|
|
1733
|
+
useEffect5(() => {
|
|
1734
|
+
let active = true;
|
|
1735
|
+
setLoading(true);
|
|
1736
|
+
strategy.registry.getAllVersions(candidate.coordinate).then((versions) => {
|
|
1737
|
+
if (!active) return;
|
|
1738
|
+
setAllVersions(versions);
|
|
1739
|
+
setCursor(0);
|
|
1740
|
+
}).catch(() => {
|
|
1741
|
+
if (!active) return;
|
|
1742
|
+
setAllVersions([]);
|
|
1743
|
+
}).finally(() => {
|
|
1744
|
+
if (active) setLoading(false);
|
|
1745
|
+
});
|
|
1746
|
+
return () => {
|
|
1747
|
+
active = false;
|
|
1748
|
+
};
|
|
1749
|
+
}, [strategy, candidate.coordinate]);
|
|
1750
|
+
const filtered = useMemo4(() => {
|
|
1751
|
+
if (!filterText.trim()) return allVersions;
|
|
1752
|
+
const lower = filterText.toLowerCase();
|
|
1753
|
+
return allVersions.filter((v) => v.toLowerCase().includes(lower));
|
|
1754
|
+
}, [allVersions, filterText]);
|
|
1755
|
+
useInput((input, key) => {
|
|
1756
|
+
if (key.escape) {
|
|
1757
|
+
onCancel();
|
|
1758
|
+
return;
|
|
1759
|
+
}
|
|
1760
|
+
if (key.return) {
|
|
1761
|
+
if (filtered.length > 0 && cursor >= 0 && cursor < filtered.length) {
|
|
1762
|
+
onSelect(filtered[cursor]);
|
|
1763
|
+
}
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1766
|
+
if (key.upArrow || input === "k" && filterText === "") {
|
|
1767
|
+
setCursor((prev) => Math.max(0, prev - 1));
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
if (key.downArrow || input === "j" && filterText === "") {
|
|
1771
|
+
setCursor((prev) => Math.min(filtered.length - 1, prev + 1));
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
if (key.pageUp) {
|
|
1775
|
+
setCursor((prev) => Math.max(0, prev - 5));
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
if (key.pageDown) {
|
|
1779
|
+
setCursor((prev) => Math.min(filtered.length - 1, prev + 5));
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
if (key.backspace || key.delete) {
|
|
1783
|
+
setFilterText((prev) => prev.slice(0, -1));
|
|
1784
|
+
setCursor(0);
|
|
1785
|
+
return;
|
|
1786
|
+
}
|
|
1787
|
+
if (input === "q" && filterText === "") {
|
|
1788
|
+
onCancel();
|
|
1789
|
+
return;
|
|
1790
|
+
}
|
|
1791
|
+
if (input && !key.ctrl && !key.meta && /^[\w.\-+~]$/.test(input)) {
|
|
1792
|
+
setFilterText((prev) => prev + input);
|
|
1793
|
+
setCursor(0);
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
const startIdx = Math.max(
|
|
1797
|
+
0,
|
|
1798
|
+
Math.min(
|
|
1799
|
+
cursor - Math.floor(WINDOW_SIZE2 / 2),
|
|
1800
|
+
Math.max(0, filtered.length - WINDOW_SIZE2)
|
|
1801
|
+
)
|
|
1802
|
+
);
|
|
1803
|
+
const windowed = filtered.slice(startIdx, startIdx + WINDOW_SIZE2);
|
|
1804
|
+
return /* @__PURE__ */ jsxs11(
|
|
1805
|
+
Box11,
|
|
1806
|
+
{
|
|
1807
|
+
flexDirection: "column",
|
|
1808
|
+
borderStyle: "round",
|
|
1809
|
+
borderColor: theme.colors.brand,
|
|
1810
|
+
padding: 1,
|
|
1811
|
+
marginY: 1,
|
|
1812
|
+
children: [
|
|
1813
|
+
/* @__PURE__ */ jsxs11(Box11, { justifyContent: "space-between", marginBottom: 1, children: [
|
|
1814
|
+
/* @__PURE__ */ jsxs11(Text11, { bold: true, color: theme.colors.brandLight, children: [
|
|
1815
|
+
theme.icon,
|
|
1816
|
+
" ",
|
|
1817
|
+
t.versionModal.title(candidate.coordinate.identifier)
|
|
1818
|
+
] }),
|
|
1819
|
+
/* @__PURE__ */ jsx11(Text11, { color: theme.colors.muted, children: t.versionModal.current(candidate.currentRange) })
|
|
1820
|
+
] }),
|
|
1821
|
+
/* @__PURE__ */ jsxs11(
|
|
1822
|
+
Box11,
|
|
1823
|
+
{
|
|
1824
|
+
borderStyle: "single",
|
|
1825
|
+
borderColor: theme.colors.border,
|
|
1826
|
+
paddingX: 1,
|
|
1827
|
+
marginBottom: 1,
|
|
1828
|
+
children: [
|
|
1829
|
+
/* @__PURE__ */ jsxs11(Text11, { bold: true, color: theme.colors.white, children: [
|
|
1830
|
+
"Filter:",
|
|
1831
|
+
" "
|
|
1832
|
+
] }),
|
|
1833
|
+
/* @__PURE__ */ jsx11(Text11, { color: filterText ? theme.colors.brandLight : theme.colors.muted, children: filterText || t.versionModal.filterPlaceholder }),
|
|
1834
|
+
filterText ? /* @__PURE__ */ jsx11(Text11, { color: theme.colors.brandLight, bold: true, children: "_" }) : null,
|
|
1835
|
+
filterText ? /* @__PURE__ */ jsx11(Box11, { marginLeft: 2, children: /* @__PURE__ */ jsxs11(Text11, { color: theme.colors.muted, children: [
|
|
1836
|
+
"(",
|
|
1837
|
+
filtered.length,
|
|
1838
|
+
" Treffer)"
|
|
1839
|
+
] }) }) : null
|
|
1840
|
+
]
|
|
1841
|
+
}
|
|
1842
|
+
),
|
|
1843
|
+
loading ? /* @__PURE__ */ jsx11(Box11, { marginY: 2, justifyContent: "center", children: /* @__PURE__ */ jsxs11(Text11, { color: theme.colors.warning, children: [
|
|
1844
|
+
"\u23F3 ",
|
|
1845
|
+
t.versionModal.loading
|
|
1846
|
+
] }) }) : filtered.length === 0 ? /* @__PURE__ */ jsx11(Box11, { marginY: 2, justifyContent: "center", children: /* @__PURE__ */ jsxs11(Text11, { color: theme.colors.danger, children: [
|
|
1847
|
+
"\u274C ",
|
|
1848
|
+
t.versionModal.noVersions
|
|
1849
|
+
] }) }) : /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
|
|
1850
|
+
startIdx > 0 && /* @__PURE__ */ jsxs11(Text11, { color: theme.colors.muted, children: [
|
|
1851
|
+
"\u25B2 \u2026 ",
|
|
1852
|
+
startIdx,
|
|
1853
|
+
" weitere oben"
|
|
1854
|
+
] }),
|
|
1855
|
+
windowed.map((ver, idx) => {
|
|
1856
|
+
const actualIdx = startIdx + idx;
|
|
1857
|
+
const isFocused = actualIdx === cursor;
|
|
1858
|
+
const isLatest = ver === candidate.latest;
|
|
1859
|
+
const isCurrent = ver === candidate.currentClean;
|
|
1860
|
+
const isPre = isPreReleaseVersion(ver);
|
|
1861
|
+
const preTag = isPre ? extractPreReleaseTag(ver) : void 0;
|
|
1862
|
+
return /* @__PURE__ */ jsxs11(Box11, { justifyContent: "space-between", children: [
|
|
1863
|
+
/* @__PURE__ */ jsxs11(Box11, { children: [
|
|
1864
|
+
/* @__PURE__ */ jsx11(
|
|
1865
|
+
Text11,
|
|
1866
|
+
{
|
|
1867
|
+
color: isFocused ? theme.colors.brandLight : theme.colors.muted,
|
|
1868
|
+
bold: isFocused,
|
|
1869
|
+
children: isFocused ? " \u2794 " : " "
|
|
1870
|
+
}
|
|
1871
|
+
),
|
|
1872
|
+
/* @__PURE__ */ jsx11(
|
|
1873
|
+
Text11,
|
|
1874
|
+
{
|
|
1875
|
+
bold: isFocused,
|
|
1876
|
+
color: isFocused ? theme.colors.brandLight : isCurrent ? theme.colors.muted : theme.colors.white,
|
|
1877
|
+
children: ver.padEnd(28)
|
|
1878
|
+
}
|
|
1879
|
+
)
|
|
1880
|
+
] }),
|
|
1881
|
+
/* @__PURE__ */ jsxs11(Box11, { children: [
|
|
1882
|
+
isCurrent && /* @__PURE__ */ jsxs11(Text11, { color: theme.colors.muted, children: [
|
|
1883
|
+
"[",
|
|
1884
|
+
t.versionModal.currentBadge,
|
|
1885
|
+
"]",
|
|
1886
|
+
" "
|
|
1887
|
+
] }),
|
|
1888
|
+
isLatest && /* @__PURE__ */ jsxs11(Text11, { bold: true, color: theme.colors.brand, children: [
|
|
1889
|
+
"[",
|
|
1890
|
+
t.versionModal.latestBadge,
|
|
1891
|
+
"]",
|
|
1892
|
+
" "
|
|
1893
|
+
] }),
|
|
1894
|
+
isPre && /* @__PURE__ */ jsxs11(Text11, { bold: true, color: "#E040FB", children: [
|
|
1895
|
+
"[",
|
|
1896
|
+
preTag || "PRE",
|
|
1897
|
+
"]",
|
|
1898
|
+
" "
|
|
1899
|
+
] })
|
|
1900
|
+
] })
|
|
1901
|
+
] }, ver);
|
|
1902
|
+
}),
|
|
1903
|
+
startIdx + WINDOW_SIZE2 < filtered.length && /* @__PURE__ */ jsxs11(Text11, { color: theme.colors.muted, children: [
|
|
1904
|
+
"\u25BC \u2026 ",
|
|
1905
|
+
filtered.length - (startIdx + WINDOW_SIZE2),
|
|
1906
|
+
" weitere unten"
|
|
1907
|
+
] })
|
|
1908
|
+
] }),
|
|
1909
|
+
/* @__PURE__ */ jsx11(
|
|
1910
|
+
Box11,
|
|
1911
|
+
{
|
|
1912
|
+
borderStyle: "single",
|
|
1913
|
+
borderColor: theme.colors.border,
|
|
1914
|
+
paddingX: 1,
|
|
1915
|
+
marginTop: 1,
|
|
1916
|
+
children: /* @__PURE__ */ jsx11(Text11, { color: theme.colors.muted, children: t.versionModal.hint })
|
|
1917
|
+
}
|
|
1918
|
+
)
|
|
1919
|
+
]
|
|
1920
|
+
}
|
|
1921
|
+
);
|
|
1922
|
+
};
|
|
1923
|
+
|
|
1924
|
+
// src/components/summary-view.tsx
|
|
1925
|
+
import { Box as Box12, Text as Text12 } from "ink";
|
|
1926
|
+
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1927
|
+
var SummaryView = ({ summary, module, strategy, t }) => {
|
|
1928
|
+
const vStatus = summary.verificationStatus || summary.postScriptStatus;
|
|
1929
|
+
const vLabel = summary.verificationLabel || summary.postScriptLabel || "Verifikation";
|
|
1930
|
+
const vDetails = summary.verificationDetails || summary.postScriptDetails;
|
|
1931
|
+
return /* @__PURE__ */ jsxs12(
|
|
1932
|
+
Box12,
|
|
1933
|
+
{
|
|
1934
|
+
flexDirection: "column",
|
|
1935
|
+
borderStyle: "round",
|
|
1936
|
+
borderColor: theme.colors.success,
|
|
1937
|
+
padding: 1,
|
|
1938
|
+
marginY: 1,
|
|
1939
|
+
children: [
|
|
1940
|
+
/* @__PURE__ */ jsxs12(Box12, { justifyContent: "space-between", alignItems: "center", children: [
|
|
1941
|
+
/* @__PURE__ */ jsxs12(Text12, { bold: true, color: theme.colors.success, children: [
|
|
1942
|
+
strategy.icon,
|
|
1943
|
+
" ",
|
|
1944
|
+
t.summary.title
|
|
1945
|
+
] }),
|
|
1946
|
+
/* @__PURE__ */ jsx12(ChickenMascot, { state: "success", t, ecosystem: strategy.ecosystem })
|
|
1947
|
+
] }),
|
|
1948
|
+
/* @__PURE__ */ jsxs12(Box12, { flexDirection: "column", marginY: 1, children: [
|
|
1949
|
+
/* @__PURE__ */ jsx12(Text12, { color: theme.colors.white, children: t.summary.successCount(summary.updatedCount, module.relPath) }),
|
|
1950
|
+
/* @__PURE__ */ jsxs12(Box12, { marginTop: 1, children: [
|
|
1951
|
+
/* @__PURE__ */ jsxs12(
|
|
1952
|
+
Text12,
|
|
1953
|
+
{
|
|
1954
|
+
color: summary.auditSeverity === "clean" ? theme.colors.success : theme.colors.warning,
|
|
1955
|
+
bold: true,
|
|
1956
|
+
children: [
|
|
1957
|
+
t.summary.securityAudit,
|
|
1958
|
+
" "
|
|
1959
|
+
]
|
|
1960
|
+
}
|
|
1961
|
+
),
|
|
1962
|
+
/* @__PURE__ */ jsx12(Text12, { children: summary.auditMessage })
|
|
1963
|
+
] }),
|
|
1964
|
+
summary.fundingMessage && /* @__PURE__ */ jsx12(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx12(Text12, { color: theme.colors.muted, children: t.summary.funding(summary.fundingMessage) }) }),
|
|
1965
|
+
vStatus && /* @__PURE__ */ jsxs12(Box12, { marginTop: 1, flexDirection: "column", children: [
|
|
1966
|
+
/* @__PURE__ */ jsx12(
|
|
1967
|
+
Text12,
|
|
1968
|
+
{
|
|
1969
|
+
color: vStatus === "clean" ? theme.colors.success : theme.colors.warning,
|
|
1970
|
+
bold: true,
|
|
1971
|
+
children: t.summary.postScript(vLabel)
|
|
1972
|
+
}
|
|
1973
|
+
),
|
|
1974
|
+
/* @__PURE__ */ jsx12(Text12, { color: theme.colors.muted, children: vDetails })
|
|
1975
|
+
] })
|
|
1976
|
+
] }),
|
|
1977
|
+
/* @__PURE__ */ jsx12(Box12, { borderStyle: "single", borderColor: theme.colors.borderMuted, paddingX: 1, marginTop: 1, children: /* @__PURE__ */ jsx12(Text12, { bold: true, color: theme.colors.brandLight, children: t.summary.backHint }) })
|
|
1978
|
+
]
|
|
1979
|
+
}
|
|
1980
|
+
);
|
|
1981
|
+
};
|
|
1982
|
+
|
|
1983
|
+
// src/components/updating-view.tsx
|
|
1984
|
+
import { Box as Box13, Text as Text13 } from "ink";
|
|
1985
|
+
import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1986
|
+
var UpdatingView = ({ step, t, ecosystem = "npm" }) => /* @__PURE__ */ jsxs13(
|
|
1987
|
+
Box13,
|
|
1988
|
+
{
|
|
1989
|
+
flexDirection: "column",
|
|
1990
|
+
borderStyle: "round",
|
|
1991
|
+
borderColor: theme.colors.brand,
|
|
1992
|
+
padding: 1,
|
|
1993
|
+
marginY: 1,
|
|
1994
|
+
children: [
|
|
1995
|
+
/* @__PURE__ */ jsxs13(Box13, { justifyContent: "space-between", alignItems: "center", children: [
|
|
1996
|
+
/* @__PURE__ */ jsx13(Text13, { bold: true, color: theme.colors.brandLight, children: t.updating.title }),
|
|
1997
|
+
/* @__PURE__ */ jsx13(ChickenMascot, { state: "updating", t, ecosystem })
|
|
1998
|
+
] }),
|
|
1999
|
+
/* @__PURE__ */ jsx13(Box13, { marginY: 1, children: /* @__PURE__ */ jsx13(Text13, { bold: true, color: theme.colors.brandLight, children: step }) }),
|
|
2000
|
+
/* @__PURE__ */ jsx13(Text13, { color: theme.colors.muted, children: t.updating.doNotClose })
|
|
2001
|
+
]
|
|
2002
|
+
}
|
|
2003
|
+
);
|
|
2004
|
+
|
|
2005
|
+
// src/app.tsx
|
|
2006
|
+
import { Fragment as Fragment2, jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2007
|
+
var App = ({ config }) => {
|
|
2008
|
+
const { exit } = useApp();
|
|
2009
|
+
const [view, setView] = useState8("splash");
|
|
2010
|
+
const [modalIndex, setModalIndex] = useState8(0);
|
|
2011
|
+
const [versionPickerCandidate, setVersionPickerCandidate] = useState8(null);
|
|
2012
|
+
const [ecosystem, setEcosystem] = useState8("npm");
|
|
2013
|
+
const strategy = useMemo5(() => EcosystemFactory.getStrategy(ecosystem), [ecosystem]);
|
|
2014
|
+
const nextEcosystem = ecosystem === "npm" ? "maven" : "npm";
|
|
2015
|
+
const nextStrategy = useMemo5(() => EcosystemFactory.getStrategy(nextEcosystem), [nextEcosystem]);
|
|
2016
|
+
const toggleEcosystem = useCallback5(() => {
|
|
2017
|
+
setEcosystem((prev) => prev === "npm" ? "maven" : "npm");
|
|
2018
|
+
}, []);
|
|
2019
|
+
const { locale, toggleLocale, t } = useI18n(resolveInitialLocale(config.locale));
|
|
2020
|
+
const ws = useWorkspaces(strategy, config.rootDir);
|
|
2021
|
+
const pkg = usePackages(
|
|
2022
|
+
strategy,
|
|
2023
|
+
ws.active,
|
|
2024
|
+
ws.internalIds,
|
|
2025
|
+
config.excludeScopes,
|
|
2026
|
+
config.channel
|
|
2027
|
+
);
|
|
2028
|
+
const updater = useUpdater(strategy, config.rootDir, setView, {
|
|
2029
|
+
script: config.postUpdateScript,
|
|
2030
|
+
label: config.postUpdateLabel
|
|
2031
|
+
});
|
|
2032
|
+
const mascotState = view === "updating" ? "updating" : pkg.loading ? "scanning" : view === "summary" ? "success" : "idle";
|
|
2033
|
+
useInput2((input, key) => {
|
|
2034
|
+
if (view === "version_modal") {
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
if (input === "q" && view !== "updating") {
|
|
2038
|
+
exit();
|
|
2039
|
+
return;
|
|
2040
|
+
}
|
|
2041
|
+
if (view === "splash") {
|
|
2042
|
+
if (key.return || input === " ") {
|
|
2043
|
+
setView("dashboard");
|
|
2044
|
+
pkg.scan();
|
|
2045
|
+
}
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
if ((input === "l" || input === "L") && view !== "updating") {
|
|
2049
|
+
toggleLocale();
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
if (view === "workspace_modal") {
|
|
2053
|
+
if (key.upArrow || input === "k") {
|
|
2054
|
+
setModalIndex((i) => Math.max(0, i - 1));
|
|
2055
|
+
} else if (key.downArrow || input === "j") {
|
|
2056
|
+
setModalIndex((i) => Math.min(ws.modules.length - 1, i + 1));
|
|
2057
|
+
} else if (key.return) {
|
|
2058
|
+
ws.setActiveIndex(modalIndex);
|
|
2059
|
+
setView("dashboard");
|
|
2060
|
+
pkg.scan();
|
|
2061
|
+
} else if (key.escape || input === "w") {
|
|
2062
|
+
setView("dashboard");
|
|
2063
|
+
}
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
if (view === "summary") {
|
|
2067
|
+
if (key.return || input === "d") {
|
|
2068
|
+
setView("dashboard");
|
|
2069
|
+
pkg.scan();
|
|
2070
|
+
}
|
|
2071
|
+
return;
|
|
2072
|
+
}
|
|
2073
|
+
if (view === "dashboard") {
|
|
2074
|
+
if (input === "e" || input === "E") {
|
|
2075
|
+
toggleEcosystem();
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
if (input === "w") {
|
|
2079
|
+
setModalIndex(ws.activeIndex);
|
|
2080
|
+
setView("workspace_modal");
|
|
2081
|
+
} else if (input === "v" || input === "V") {
|
|
2082
|
+
const focused = pkg.visible[pkg.cursor];
|
|
2083
|
+
if (focused) {
|
|
2084
|
+
setVersionPickerCandidate(focused);
|
|
2085
|
+
setView("version_modal");
|
|
2086
|
+
}
|
|
2087
|
+
} else if (key.tab) {
|
|
2088
|
+
pkg.cycleTab();
|
|
2089
|
+
} else if (input === "1") {
|
|
2090
|
+
pkg.switchTab("all");
|
|
2091
|
+
} else if (input === "2") {
|
|
2092
|
+
pkg.switchTab("prod");
|
|
2093
|
+
} else if (input === "3") {
|
|
2094
|
+
pkg.switchTab("dev");
|
|
2095
|
+
} else if (key.upArrow || input === "k") {
|
|
2096
|
+
pkg.setCursor((i) => Math.max(0, i - 1));
|
|
2097
|
+
} else if (key.downArrow || input === "j") {
|
|
2098
|
+
pkg.setCursor((i) => Math.min(pkg.visible.length - 1, i + 1));
|
|
2099
|
+
} else if (input === " ") {
|
|
2100
|
+
const focused = pkg.visible[pkg.cursor];
|
|
2101
|
+
if (focused) pkg.togglePackage(focused.coordinate.identifier);
|
|
2102
|
+
} else if (input === "a") {
|
|
2103
|
+
pkg.toggleAllVisible();
|
|
2104
|
+
} else if (input === "r") {
|
|
2105
|
+
pkg.scan();
|
|
2106
|
+
} else if (input === "u" && pkg.counts.selectedCount > 0 && !pkg.loading) {
|
|
2107
|
+
const selected = pkg.packages.filter((p) => p.selected);
|
|
2108
|
+
updater.run(ws.active, selected, t);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
return /* @__PURE__ */ jsxs14(Box14, { flexDirection: "column", padding: 1, children: [
|
|
2113
|
+
view === "splash" && /* @__PURE__ */ jsx14(
|
|
2114
|
+
SplashScreen,
|
|
2115
|
+
{
|
|
2116
|
+
onComplete: () => {
|
|
2117
|
+
setView("dashboard");
|
|
2118
|
+
pkg.scan();
|
|
2119
|
+
},
|
|
2120
|
+
author: config.author
|
|
2121
|
+
}
|
|
2122
|
+
),
|
|
2123
|
+
view !== "splash" && /* @__PURE__ */ jsxs14(Fragment2, { children: [
|
|
2124
|
+
/* @__PURE__ */ jsx14(
|
|
2125
|
+
Header,
|
|
2126
|
+
{
|
|
2127
|
+
author: config.author,
|
|
2128
|
+
t,
|
|
2129
|
+
currentLocale: locale,
|
|
2130
|
+
strategy,
|
|
2131
|
+
nextStrategyName: `${nextStrategy.icon} ${nextStrategy.displayName}`,
|
|
2132
|
+
channel: config.channel
|
|
2133
|
+
}
|
|
2134
|
+
),
|
|
2135
|
+
view === "workspace_modal" && /* @__PURE__ */ jsx14(
|
|
2136
|
+
WorkspaceModal,
|
|
2137
|
+
{
|
|
2138
|
+
modules: ws.modules,
|
|
2139
|
+
focusedIndex: modalIndex,
|
|
2140
|
+
strategy,
|
|
2141
|
+
t
|
|
2142
|
+
}
|
|
2143
|
+
),
|
|
2144
|
+
view === "version_modal" && versionPickerCandidate && /* @__PURE__ */ jsx14(
|
|
2145
|
+
VersionModal,
|
|
2146
|
+
{
|
|
2147
|
+
candidate: versionPickerCandidate,
|
|
2148
|
+
strategy,
|
|
2149
|
+
t,
|
|
2150
|
+
onSelect: (chosenVersion) => {
|
|
2151
|
+
pkg.setCustomVersion(versionPickerCandidate.coordinate.identifier, chosenVersion);
|
|
2152
|
+
setView("dashboard");
|
|
2153
|
+
setVersionPickerCandidate(null);
|
|
2154
|
+
},
|
|
2155
|
+
onCancel: () => {
|
|
2156
|
+
setView("dashboard");
|
|
2157
|
+
setVersionPickerCandidate(null);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
),
|
|
2161
|
+
view === "updating" && /* @__PURE__ */ jsx14(UpdatingView, { step: updater.step, t, ecosystem: strategy.ecosystem }),
|
|
2162
|
+
view === "summary" && updater.summary && /* @__PURE__ */ jsx14(
|
|
2163
|
+
SummaryView,
|
|
2164
|
+
{
|
|
2165
|
+
summary: updater.summary,
|
|
2166
|
+
module: ws.active,
|
|
2167
|
+
strategy,
|
|
2168
|
+
t
|
|
2169
|
+
}
|
|
2170
|
+
),
|
|
2171
|
+
view === "dashboard" && /* @__PURE__ */ jsxs14(Fragment2, { children: [
|
|
2172
|
+
/* @__PURE__ */ jsx14(
|
|
2173
|
+
WorkspaceBar,
|
|
2174
|
+
{
|
|
2175
|
+
module: ws.active,
|
|
2176
|
+
excludeScopes: config.excludeScopes,
|
|
2177
|
+
strategy,
|
|
2178
|
+
t
|
|
2179
|
+
}
|
|
2180
|
+
),
|
|
2181
|
+
/* @__PURE__ */ jsx14(
|
|
2182
|
+
TabBar,
|
|
2183
|
+
{
|
|
2184
|
+
activeTab: pkg.activeTab,
|
|
2185
|
+
counts: pkg.counts,
|
|
2186
|
+
strategy,
|
|
2187
|
+
t
|
|
2188
|
+
}
|
|
2189
|
+
),
|
|
2190
|
+
/* @__PURE__ */ jsx14(
|
|
2191
|
+
PackageList,
|
|
2192
|
+
{
|
|
2193
|
+
items: pkg.visible,
|
|
2194
|
+
cursor: pkg.cursor,
|
|
2195
|
+
loading: pkg.loading,
|
|
2196
|
+
t
|
|
2197
|
+
}
|
|
2198
|
+
),
|
|
2199
|
+
/* @__PURE__ */ jsx14(
|
|
2200
|
+
StatusBar,
|
|
2201
|
+
{
|
|
2202
|
+
counts: pkg.counts,
|
|
2203
|
+
visibleCount: pkg.visible.length,
|
|
2204
|
+
t,
|
|
2205
|
+
mascotState,
|
|
2206
|
+
ecosystem: strategy.ecosystem
|
|
2207
|
+
}
|
|
2208
|
+
),
|
|
2209
|
+
/* @__PURE__ */ jsx14(ControlsBar, { t })
|
|
2210
|
+
] })
|
|
2211
|
+
] })
|
|
2212
|
+
] });
|
|
2213
|
+
};
|
|
2214
|
+
|
|
2215
|
+
// src/cli/parser.ts
|
|
2216
|
+
function parseCliArgs(args) {
|
|
2217
|
+
const [first, ...rest] = args;
|
|
2218
|
+
if (!first || first === "--help" || first === "-h" || first === "help") {
|
|
2219
|
+
if (first === "--help" || first === "-h" || first === "help") {
|
|
2220
|
+
return { subcommand: "help" };
|
|
2221
|
+
}
|
|
2222
|
+
return {};
|
|
2223
|
+
}
|
|
2224
|
+
if (first === "mcp" || first === "serve") {
|
|
2225
|
+
return { subcommand: "mcp" };
|
|
2226
|
+
}
|
|
2227
|
+
const validCommands = ["modules", "scan", "versions", "update", "init"];
|
|
2228
|
+
const isSubcommand = validCommands.includes(first);
|
|
2229
|
+
const subcommand = isSubcommand ? first : void 0;
|
|
2230
|
+
const remaining = isSubcommand ? rest : args;
|
|
2231
|
+
const result = { subcommand };
|
|
2232
|
+
const positionals = [];
|
|
2233
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
2234
|
+
const arg = remaining[i];
|
|
2235
|
+
if (arg === "--json") {
|
|
2236
|
+
result.json = true;
|
|
2237
|
+
} else if (arg === "--all") {
|
|
2238
|
+
result.all = true;
|
|
2239
|
+
} else if (arg === "--all-modules") {
|
|
2240
|
+
result.allModules = true;
|
|
2241
|
+
} else if (arg === "--allow-major") {
|
|
2242
|
+
result.allowMajor = true;
|
|
2243
|
+
} else if (arg === "--dry-run") {
|
|
2244
|
+
result.dryRun = true;
|
|
2245
|
+
} else if (arg === "--skip-verify") {
|
|
2246
|
+
result.skipVerify = true;
|
|
2247
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
2248
|
+
result.help = true;
|
|
2249
|
+
} else if (arg.startsWith("--ecosystem=")) {
|
|
2250
|
+
const val = arg.split("=")[1];
|
|
2251
|
+
if (val === "npm" || val === "maven") result.ecosystem = val;
|
|
2252
|
+
} else if (arg === "-e" || arg === "--ecosystem") {
|
|
2253
|
+
const val = remaining[++i];
|
|
2254
|
+
if (val === "npm" || val === "maven") result.ecosystem = val;
|
|
2255
|
+
} else if (arg.startsWith("--module=")) {
|
|
2256
|
+
result.module = arg.split("=")[1];
|
|
2257
|
+
} else if (arg === "-m" || arg === "--module") {
|
|
2258
|
+
result.module = remaining[++i];
|
|
2259
|
+
} else if (arg.startsWith("--channel=")) {
|
|
2260
|
+
const val = arg.split("=")[1];
|
|
2261
|
+
if (val === "stable" || val === "all") result.channel = val;
|
|
2262
|
+
} else if (arg === "-c" || arg === "--channel") {
|
|
2263
|
+
const val = remaining[++i];
|
|
2264
|
+
if (val === "stable" || val === "all") result.channel = val;
|
|
2265
|
+
} else if (arg.startsWith("--packages=")) {
|
|
2266
|
+
result.packages = arg.split("=")[1]?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2267
|
+
} else if (arg === "-p" || arg === "--packages") {
|
|
2268
|
+
result.packages = remaining[++i]?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2269
|
+
} else if (!arg.startsWith("-")) {
|
|
2270
|
+
positionals.push(arg);
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
if (positionals.length > 0) {
|
|
2274
|
+
result.positional = positionals[0];
|
|
2275
|
+
}
|
|
2276
|
+
return result;
|
|
2277
|
+
}
|
|
2278
|
+
function printCliHelp() {
|
|
2279
|
+
console.log(`
|
|
2280
|
+
\u{1FAB6} Elevate \u2014 Modern Polyglot Dependency Manager & Updater
|
|
2281
|
+
|
|
2282
|
+
USAGE:
|
|
2283
|
+
elevate Launch interactive terminal dashboard (TUI)
|
|
2284
|
+
elevate <subcommand> [flags] Execute headless command (for CI/CD & AI agents)
|
|
2285
|
+
elevate mcp Start Model Context Protocol (MCP) server
|
|
2286
|
+
|
|
2287
|
+
SUBCOMMANDS:
|
|
2288
|
+
modules List all detected repository modules
|
|
2289
|
+
scan Scan dependencies for available updates
|
|
2290
|
+
versions <package> List all published versions of a package
|
|
2291
|
+
update Apply updates to specified or all packages
|
|
2292
|
+
init Configure AI coding tools (MCP + agent skills)
|
|
2293
|
+
mcp Start stdio-based MCP server
|
|
2294
|
+
|
|
2295
|
+
FLAGS:
|
|
2296
|
+
-e, --ecosystem <npm|maven> Target ecosystem (default: npm)
|
|
2297
|
+
-m, --module <name|path> Target module ID or relative path (default: root)
|
|
2298
|
+
-c, --channel <stable|all> Release channel (default: stable)
|
|
2299
|
+
-p, --packages <pkg1,pkg2@1.2.3> Comma-separated list of packages to update
|
|
2300
|
+
--all Update all eligible dependencies
|
|
2301
|
+
--all-modules Scan across all discovered monorepo modules
|
|
2302
|
+
--allow-major Allow breaking major version upgrades
|
|
2303
|
+
--dry-run Simulate updates without modifying any files
|
|
2304
|
+
--skip-verify Skip post-update build & test verification
|
|
2305
|
+
--json Format output as machine-readable JSON
|
|
2306
|
+
-h, --help Display this help message
|
|
2307
|
+
|
|
2308
|
+
EXAMPLES:
|
|
2309
|
+
elevate scan --json
|
|
2310
|
+
elevate scan --ecosystem=maven --channel=stable --all-modules --json
|
|
2311
|
+
elevate versions chalk --ecosystem=npm --json
|
|
2312
|
+
elevate update --module=apps/e2e-cockpit --packages=chalk@5.6.2 --json
|
|
2313
|
+
elevate update --ecosystem=npm --all --allow-major --dry-run
|
|
2314
|
+
elevate init
|
|
2315
|
+
elevate init --json
|
|
2316
|
+
`);
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
// src/cli/command-modules.ts
|
|
2320
|
+
async function handleModulesCommand(options, config) {
|
|
2321
|
+
const ecosystem = options.ecosystem ?? "npm";
|
|
2322
|
+
const strategy = EcosystemFactory.getStrategy(ecosystem);
|
|
2323
|
+
const modules = await strategy.discovery.discover(config.rootDir);
|
|
2324
|
+
if (options.json) {
|
|
2325
|
+
console.log(JSON.stringify({ ecosystem, count: modules.length, modules }, null, 2));
|
|
2326
|
+
return 0;
|
|
2327
|
+
}
|
|
2328
|
+
console.log(`
|
|
2329
|
+
\u{1FAB6} Discovered ${strategy.icon} ${strategy.displayName} Modules (${modules.length}):
|
|
2330
|
+
`);
|
|
2331
|
+
for (const m of modules) {
|
|
2332
|
+
const isRootBadge = m.isRoot ? " [ROOT]" : "";
|
|
2333
|
+
console.log(` \u2022 ${m.name.padEnd(45)} (${m.relPath})${isRootBadge}`);
|
|
2334
|
+
}
|
|
2335
|
+
console.log("");
|
|
2336
|
+
return 0;
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// src/cli/command-scan.ts
|
|
2340
|
+
async function handleScanCommand(options, config) {
|
|
2341
|
+
const ecosystem = options.ecosystem ?? "npm";
|
|
2342
|
+
const channel = options.channel ?? config.channel ?? "stable";
|
|
2343
|
+
const strategy = EcosystemFactory.getStrategy(ecosystem);
|
|
2344
|
+
const modules = await strategy.discovery.discover(config.rootDir);
|
|
2345
|
+
if (modules.length === 0) {
|
|
2346
|
+
if (options.json) {
|
|
2347
|
+
console.log(JSON.stringify({ error: `No ${strategy.displayName} modules found.`, ecosystem }, null, 2));
|
|
2348
|
+
} else {
|
|
2349
|
+
console.error(`\u274C No ${strategy.displayName} modules found.`);
|
|
2350
|
+
}
|
|
2351
|
+
return 1;
|
|
2352
|
+
}
|
|
2353
|
+
const internalIds = /* @__PURE__ */ new Set();
|
|
2354
|
+
for (const m of modules) {
|
|
2355
|
+
if (m.id) internalIds.add(m.id);
|
|
2356
|
+
if (m.name) internalIds.add(m.name);
|
|
2357
|
+
}
|
|
2358
|
+
const targetModules = [];
|
|
2359
|
+
if (options.allModules) {
|
|
2360
|
+
targetModules.push(...modules);
|
|
2361
|
+
} else if (options.module) {
|
|
2362
|
+
const match = modules.find(
|
|
2363
|
+
(m) => m.id.toLowerCase() === options.module.toLowerCase() || m.relPath.toLowerCase() === options.module.toLowerCase() || m.path.toLowerCase().endsWith(options.module.toLowerCase())
|
|
2364
|
+
);
|
|
2365
|
+
if (!match) {
|
|
2366
|
+
if (options.json) {
|
|
2367
|
+
console.log(
|
|
2368
|
+
JSON.stringify(
|
|
2369
|
+
{ error: `Module '${options.module}' not found.`, available: modules.map((m) => m.relPath) },
|
|
2370
|
+
null,
|
|
2371
|
+
2
|
|
2372
|
+
)
|
|
2373
|
+
);
|
|
2374
|
+
} else {
|
|
2375
|
+
console.error(`\u274C Module '${options.module}' not found.`);
|
|
2376
|
+
}
|
|
2377
|
+
return 1;
|
|
2378
|
+
}
|
|
2379
|
+
targetModules.push(match);
|
|
2380
|
+
} else {
|
|
2381
|
+
targetModules.push(modules[0]);
|
|
2382
|
+
}
|
|
2383
|
+
const allResults = [];
|
|
2384
|
+
let totalUpdatesCount = 0;
|
|
2385
|
+
for (const mod of targetModules) {
|
|
2386
|
+
try {
|
|
2387
|
+
const updates = await strategy.reader.scan(mod, config.excludeScopes, internalIds, channel);
|
|
2388
|
+
allResults.push({ module: mod, updates });
|
|
2389
|
+
totalUpdatesCount += updates.length;
|
|
2390
|
+
} catch {
|
|
2391
|
+
allResults.push({ module: mod, updates: [] });
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
if (options.json) {
|
|
2395
|
+
console.log(
|
|
2396
|
+
JSON.stringify(
|
|
2397
|
+
{
|
|
2398
|
+
ecosystem,
|
|
2399
|
+
channel,
|
|
2400
|
+
scannedModules: targetModules.length,
|
|
2401
|
+
totalUpdatesCount,
|
|
2402
|
+
results: allResults.map((r) => ({
|
|
2403
|
+
moduleId: r.module.id,
|
|
2404
|
+
modulePath: r.module.relPath,
|
|
2405
|
+
updateCount: r.updates.length,
|
|
2406
|
+
updates: r.updates.map((u) => ({
|
|
2407
|
+
identifier: u.coordinate.identifier,
|
|
2408
|
+
currentRange: u.currentRange,
|
|
2409
|
+
currentClean: u.currentClean,
|
|
2410
|
+
latest: u.latest,
|
|
2411
|
+
newRange: u.newRange,
|
|
2412
|
+
diff: u.diff,
|
|
2413
|
+
scope: u.scope,
|
|
2414
|
+
isPreRelease: u.isPreRelease ?? false,
|
|
2415
|
+
preReleaseTag: u.preReleaseTag
|
|
2416
|
+
}))
|
|
2417
|
+
}))
|
|
2418
|
+
},
|
|
2419
|
+
null,
|
|
2420
|
+
2
|
|
2421
|
+
)
|
|
2422
|
+
);
|
|
2423
|
+
return totalUpdatesCount > 0 ? 1 : 0;
|
|
2424
|
+
}
|
|
2425
|
+
console.log(`
|
|
2426
|
+
\u{1FAB6} Elevate Scan Results (${strategy.icon} ${strategy.displayName}, Channel: ${channel}):
|
|
2427
|
+
`);
|
|
2428
|
+
for (const r of allResults) {
|
|
2429
|
+
console.log(`\u{1F4E6} Module: ${r.module.name} (${r.module.relPath}) \u2014 ${r.updates.length} update(s) available`);
|
|
2430
|
+
if (r.updates.length === 0) {
|
|
2431
|
+
console.log(" \u2728 All dependencies are up to date.\n");
|
|
2432
|
+
continue;
|
|
2433
|
+
}
|
|
2434
|
+
for (const u of r.updates) {
|
|
2435
|
+
const diffTag = `[${u.diff.toUpperCase()}]`.padEnd(9);
|
|
2436
|
+
const preBadge = u.isPreRelease ? `[${u.preReleaseTag || "PRE"}] ` : "";
|
|
2437
|
+
console.log(
|
|
2438
|
+
` \u2022 ${diffTag} ${preBadge}${u.coordinate.identifier.padEnd(36)} ${u.currentRange.padEnd(12)} \u2794 ${u.latest}`
|
|
2439
|
+
);
|
|
2440
|
+
}
|
|
2441
|
+
console.log("");
|
|
2442
|
+
}
|
|
2443
|
+
console.log(`Summary: ${totalUpdatesCount} pending update(s) across ${targetModules.length} module(s).
|
|
2444
|
+
`);
|
|
2445
|
+
return totalUpdatesCount > 0 ? 1 : 0;
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
// src/cli/command-versions.ts
|
|
2449
|
+
async function handleVersionsCommand(options) {
|
|
2450
|
+
const pkgIdentifier = options.positional;
|
|
2451
|
+
if (!pkgIdentifier) {
|
|
2452
|
+
console.error("\u274C Error: Please specify a package name (e.g. `elevate versions chalk`).");
|
|
2453
|
+
return 1;
|
|
2454
|
+
}
|
|
2455
|
+
const ecosystem = options.ecosystem ?? (pkgIdentifier.includes(":") ? "maven" : "npm");
|
|
2456
|
+
const strategy = EcosystemFactory.getStrategy(ecosystem);
|
|
2457
|
+
let coordinate;
|
|
2458
|
+
if (ecosystem === "maven") {
|
|
2459
|
+
const parts = pkgIdentifier.split(":");
|
|
2460
|
+
coordinate = {
|
|
2461
|
+
identifier: pkgIdentifier,
|
|
2462
|
+
group: parts[0],
|
|
2463
|
+
artifact: parts[1] || parts[0],
|
|
2464
|
+
ecosystem: "maven"
|
|
2465
|
+
};
|
|
2466
|
+
} else {
|
|
2467
|
+
coordinate = {
|
|
2468
|
+
identifier: pkgIdentifier,
|
|
2469
|
+
artifact: pkgIdentifier,
|
|
2470
|
+
ecosystem: "npm"
|
|
2471
|
+
};
|
|
2472
|
+
}
|
|
2473
|
+
const versions = await strategy.registry.getAllVersions(coordinate);
|
|
2474
|
+
if (options.json) {
|
|
2475
|
+
console.log(
|
|
2476
|
+
JSON.stringify(
|
|
2477
|
+
{
|
|
2478
|
+
ecosystem,
|
|
2479
|
+
package: pkgIdentifier,
|
|
2480
|
+
totalCount: versions.length,
|
|
2481
|
+
latest: versions[0] || null,
|
|
2482
|
+
versions: versions.map((v) => ({
|
|
2483
|
+
version: v,
|
|
2484
|
+
isPreRelease: isPreReleaseVersion(v),
|
|
2485
|
+
tag: extractPreReleaseTag(v)
|
|
2486
|
+
}))
|
|
2487
|
+
},
|
|
2488
|
+
null,
|
|
2489
|
+
2
|
|
2490
|
+
)
|
|
2491
|
+
);
|
|
2492
|
+
return 0;
|
|
2493
|
+
}
|
|
2494
|
+
console.log(`
|
|
2495
|
+
\u{1FAB6} Published versions for '${pkgIdentifier}' (${strategy.icon} ${strategy.displayName}, ${versions.length} total):
|
|
2496
|
+
`);
|
|
2497
|
+
for (const v of versions.slice(0, 30)) {
|
|
2498
|
+
const isPre = isPreReleaseVersion(v);
|
|
2499
|
+
const tag = isPre ? `[${extractPreReleaseTag(v) || "PRE"}]` : "[STABLE]";
|
|
2500
|
+
console.log(` \u2022 ${v.padEnd(25)} ${tag}`);
|
|
2501
|
+
}
|
|
2502
|
+
if (versions.length > 30) {
|
|
2503
|
+
console.log(` \u2026 and ${versions.length - 30} older versions.
|
|
2504
|
+
`);
|
|
2505
|
+
} else {
|
|
2506
|
+
console.log("");
|
|
2507
|
+
}
|
|
2508
|
+
return 0;
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
// src/cli/command-update.ts
|
|
2512
|
+
import semver5 from "semver";
|
|
2513
|
+
async function handleUpdateCommand(options, config) {
|
|
2514
|
+
const ecosystem = options.ecosystem ?? "npm";
|
|
2515
|
+
const strategy = EcosystemFactory.getStrategy(ecosystem);
|
|
2516
|
+
const modules = await strategy.discovery.discover(config.rootDir);
|
|
2517
|
+
if (modules.length === 0) {
|
|
2518
|
+
console.error(`\u274C No ${strategy.displayName} modules found.`);
|
|
2519
|
+
return 1;
|
|
2520
|
+
}
|
|
2521
|
+
let targetModule = modules[0];
|
|
2522
|
+
if (options.module) {
|
|
2523
|
+
const match = modules.find(
|
|
2524
|
+
(m) => m.id.toLowerCase() === options.module.toLowerCase() || m.relPath.toLowerCase() === options.module.toLowerCase() || m.path.toLowerCase().endsWith(options.module.toLowerCase())
|
|
2525
|
+
);
|
|
2526
|
+
if (!match) {
|
|
2527
|
+
console.error(`\u274C Module '${options.module}' not found.`);
|
|
2528
|
+
return 1;
|
|
2529
|
+
}
|
|
2530
|
+
targetModule = match;
|
|
2531
|
+
}
|
|
2532
|
+
const internalIds = /* @__PURE__ */ new Set();
|
|
2533
|
+
for (const m of modules) {
|
|
2534
|
+
if (m.id) internalIds.add(m.id);
|
|
2535
|
+
if (m.name) internalIds.add(m.name);
|
|
2536
|
+
}
|
|
2537
|
+
const availableUpdates = await strategy.reader.scan(
|
|
2538
|
+
targetModule,
|
|
2539
|
+
config.excludeScopes,
|
|
2540
|
+
internalIds,
|
|
2541
|
+
options.channel ?? "stable"
|
|
2542
|
+
);
|
|
2543
|
+
const updatesToApply = [];
|
|
2544
|
+
if (options.all) {
|
|
2545
|
+
for (const c of availableUpdates) {
|
|
2546
|
+
if (c.diff === "major" && !options.allowMajor) {
|
|
2547
|
+
if (!options.json) {
|
|
2548
|
+
console.warn(`\u26A0\uFE0F Skipping major update for '${c.coordinate.identifier}' (requires --allow-major)`);
|
|
2549
|
+
}
|
|
2550
|
+
continue;
|
|
2551
|
+
}
|
|
2552
|
+
updatesToApply.push(c);
|
|
2553
|
+
}
|
|
2554
|
+
} else if (options.packages && options.packages.length > 0) {
|
|
2555
|
+
for (const item of options.packages) {
|
|
2556
|
+
let [name, targetVer] = item.split("@");
|
|
2557
|
+
if (item.startsWith("@") && item.includes("@", 1)) {
|
|
2558
|
+
const atIdx = item.lastIndexOf("@");
|
|
2559
|
+
name = item.slice(0, atIdx);
|
|
2560
|
+
targetVer = item.slice(atIdx + 1);
|
|
2561
|
+
}
|
|
2562
|
+
const found = availableUpdates.find((u) => u.coordinate.identifier === name);
|
|
2563
|
+
if (found) {
|
|
2564
|
+
if (targetVer) {
|
|
2565
|
+
const prefix = found.currentRange.startsWith("~") ? "~" : found.currentRange.startsWith("^") ? "^" : "";
|
|
2566
|
+
const diff = semver5.valid(targetVer) && found.currentClean ? semver5.diff(found.currentClean, targetVer) || "minor" : "minor";
|
|
2567
|
+
if (diff === "major" && !options.allowMajor) {
|
|
2568
|
+
console.error(`\u274C Error: '${name}' requires a major version upgrade to ${targetVer}. Please pass --allow-major.`);
|
|
2569
|
+
return 1;
|
|
2570
|
+
}
|
|
2571
|
+
updatesToApply.push({
|
|
2572
|
+
...found,
|
|
2573
|
+
newRange: `${prefix}${targetVer}`,
|
|
2574
|
+
diff,
|
|
2575
|
+
selected: true
|
|
2576
|
+
});
|
|
2577
|
+
} else {
|
|
2578
|
+
if (found.diff === "major" && !options.allowMajor) {
|
|
2579
|
+
console.error(`\u274C Error: '${name}' is a major update (${found.currentClean} \u2794 ${found.latest}). Please pass --allow-major.`);
|
|
2580
|
+
return 1;
|
|
2581
|
+
}
|
|
2582
|
+
updatesToApply.push({ ...found, selected: true });
|
|
2583
|
+
}
|
|
2584
|
+
} else {
|
|
2585
|
+
if (!targetVer) {
|
|
2586
|
+
console.error(`\u274C Package '${name}' has no pending updates detected. Please specify a target version (e.g. ${name}@1.2.3).`);
|
|
2587
|
+
return 1;
|
|
2588
|
+
}
|
|
2589
|
+
updatesToApply.push({
|
|
2590
|
+
coordinate: {
|
|
2591
|
+
identifier: name,
|
|
2592
|
+
artifact: name,
|
|
2593
|
+
ecosystem
|
|
2594
|
+
},
|
|
2595
|
+
currentRange: "unknown",
|
|
2596
|
+
currentClean: "0.0.0",
|
|
2597
|
+
latest: targetVer,
|
|
2598
|
+
newRange: targetVer,
|
|
2599
|
+
diff: "minor",
|
|
2600
|
+
scope: "prod",
|
|
2601
|
+
selected: true
|
|
2602
|
+
});
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
} else {
|
|
2606
|
+
console.error('\u274C Please specify packages to update via --packages="pkg1,pkg2" or --all.');
|
|
2607
|
+
return 1;
|
|
2608
|
+
}
|
|
2609
|
+
if (updatesToApply.length === 0) {
|
|
2610
|
+
if (options.json) {
|
|
2611
|
+
console.log(JSON.stringify({ message: "No updates selected to apply.", updatedCount: 0 }, null, 2));
|
|
2612
|
+
} else {
|
|
2613
|
+
console.log("\u2139\uFE0F No updates selected to apply.");
|
|
2614
|
+
}
|
|
2615
|
+
return 0;
|
|
2616
|
+
}
|
|
2617
|
+
if (options.dryRun) {
|
|
2618
|
+
const simulationResult = {
|
|
2619
|
+
dryRun: true,
|
|
2620
|
+
targetModule: targetModule.relPath,
|
|
2621
|
+
ecosystem,
|
|
2622
|
+
updatesCount: updatesToApply.length,
|
|
2623
|
+
updates: updatesToApply.map((u) => ({
|
|
2624
|
+
identifier: u.coordinate.identifier,
|
|
2625
|
+
currentRange: u.currentRange,
|
|
2626
|
+
newRange: u.newRange,
|
|
2627
|
+
diff: u.diff
|
|
2628
|
+
}))
|
|
2629
|
+
};
|
|
2630
|
+
if (options.json) {
|
|
2631
|
+
console.log(JSON.stringify(simulationResult, null, 2));
|
|
2632
|
+
} else {
|
|
2633
|
+
console.log(`
|
|
2634
|
+
\u{1FAB6} [DRY-RUN] Planned updates for ${targetModule.name} (${updatesToApply.length}):
|
|
2635
|
+
`);
|
|
2636
|
+
for (const u of updatesToApply) {
|
|
2637
|
+
console.log(` \u2022 ${u.coordinate.identifier.padEnd(36)} ${u.currentRange} \u2794 ${u.newRange} (${u.diff.toUpperCase()})`);
|
|
2638
|
+
}
|
|
2639
|
+
console.log("\n(No files were modified because --dry-run is active).\n");
|
|
2640
|
+
}
|
|
2641
|
+
return 0;
|
|
2642
|
+
}
|
|
2643
|
+
if (!options.json) {
|
|
2644
|
+
console.log(`
|
|
2645
|
+
\u26A1 Applying ${updatesToApply.length} update(s) to ${targetModule.name}\u2026`);
|
|
2646
|
+
}
|
|
2647
|
+
const updateResult = await strategy.updater.applyUpdates(
|
|
2648
|
+
targetModule,
|
|
2649
|
+
config.rootDir,
|
|
2650
|
+
updatesToApply,
|
|
2651
|
+
(step) => {
|
|
2652
|
+
if (!options.json) console.log(` ${step}`);
|
|
2653
|
+
}
|
|
2654
|
+
);
|
|
2655
|
+
let verifyResult;
|
|
2656
|
+
if (!options.skipVerify) {
|
|
2657
|
+
if (!options.json) console.log(`\u{1F50D} Running build & test verification\u2026`);
|
|
2658
|
+
verifyResult = await strategy.verifier.verify(
|
|
2659
|
+
targetModule,
|
|
2660
|
+
config.rootDir,
|
|
2661
|
+
config.postUpdateScript,
|
|
2662
|
+
config.postUpdateLabel
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2665
|
+
const finalSummary = {
|
|
2666
|
+
success: verifyResult?.status !== "warn",
|
|
2667
|
+
module: targetModule.relPath,
|
|
2668
|
+
ecosystem,
|
|
2669
|
+
...updateResult,
|
|
2670
|
+
verification: verifyResult ? {
|
|
2671
|
+
status: verifyResult.status,
|
|
2672
|
+
label: verifyResult.label,
|
|
2673
|
+
details: verifyResult.details
|
|
2674
|
+
} : void 0
|
|
2675
|
+
};
|
|
2676
|
+
if (options.json) {
|
|
2677
|
+
console.log(JSON.stringify(finalSummary, null, 2));
|
|
2678
|
+
return finalSummary.success ? 0 : 2;
|
|
2679
|
+
}
|
|
2680
|
+
console.log(`
|
|
2681
|
+
\u2728 Update completed successfully!
|
|
2682
|
+
`);
|
|
2683
|
+
console.log(` \u2022 Updated: ${updateResult.updatedCount} package(s)`);
|
|
2684
|
+
console.log(` \u2022 Audit Status: ${updateResult.auditSeverity.toUpperCase()} \u2014 ${updateResult.auditMessage}`);
|
|
2685
|
+
if (verifyResult) {
|
|
2686
|
+
console.log(` \u2022 Verification: ${verifyResult.status.toUpperCase()} (${verifyResult.label})`);
|
|
2687
|
+
if (verifyResult.status === "warn") {
|
|
2688
|
+
console.warn(` Details: ${verifyResult.details}`);
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
console.log("");
|
|
2692
|
+
return finalSummary.success ? 0 : 2;
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
// src/cli/command-init.ts
|
|
2696
|
+
import { existsSync as existsSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync3, mkdirSync } from "fs";
|
|
2697
|
+
import { homedir, platform } from "os";
|
|
2698
|
+
import { join as join8, resolve as resolve2 } from "path";
|
|
2699
|
+
function mcpServerEntry() {
|
|
2700
|
+
return {
|
|
2701
|
+
command: "npx",
|
|
2702
|
+
args: ["-y", "@dennisrepa/elevate", "mcp"]
|
|
2703
|
+
};
|
|
2704
|
+
}
|
|
2705
|
+
var ELEVATE_SKILL_CONTENT = `# Elevate \u2014 Dependency Management Agent Skill
|
|
2706
|
+
|
|
2707
|
+
## When to Use
|
|
2708
|
+
Use the Elevate MCP tools whenever the task involves:
|
|
2709
|
+
- Scanning or auditing dependencies for available updates
|
|
2710
|
+
- Updating one or more packages to specific or latest versions
|
|
2711
|
+
- Listing available versions of a package from npm or Maven registries
|
|
2712
|
+
- Discovering monorepo modules and their dependency structure
|
|
2713
|
+
- Any dependency-related operation in a monorepo
|
|
2714
|
+
|
|
2715
|
+
## Important
|
|
2716
|
+
**Never** run raw \`npm update\`, \`npm install <pkg>@latest\`, or manually edit
|
|
2717
|
+
\`package.json\` / \`pom.xml\` version fields. Always use Elevate's MCP tools
|
|
2718
|
+
instead \u2014 they handle monorepo-aware scanning, safety guardrails (major version
|
|
2719
|
+
protection), and post-update verification automatically.
|
|
2720
|
+
|
|
2721
|
+
## Available MCP Tools
|
|
2722
|
+
| Tool | Purpose |
|
|
2723
|
+
|-------------------------|--------------------------------------------------------------|
|
|
2724
|
+
| \`elevate_scan\` | Scan dependencies for available updates with diff analysis |
|
|
2725
|
+
| \`elevate_update\` | Apply updates with safety guardrails and verification |
|
|
2726
|
+
| \`elevate_get_versions\` | List all published versions of a specific package |
|
|
2727
|
+
| \`elevate_list_modules\` | Discover all modules in a monorepo |
|
|
2728
|
+
| \`elevate_health_check\` | Run build & audit verification after updates |
|
|
2729
|
+
|
|
2730
|
+
## Typical Workflow
|
|
2731
|
+
1. \`elevate_list_modules\` \u2014 Discover project structure
|
|
2732
|
+
2. \`elevate_scan\` \u2014 Identify outdated dependencies
|
|
2733
|
+
3. \`elevate_update\` \u2014 Apply selected updates (use \`--allow-major\` only when explicitly requested)
|
|
2734
|
+
4. \`elevate_health_check\` \u2014 Verify build integrity
|
|
2735
|
+
`;
|
|
2736
|
+
var home = homedir();
|
|
2737
|
+
var isWin = platform() === "win32";
|
|
2738
|
+
function appData() {
|
|
2739
|
+
return process.env.APPDATA || join8(home, "AppData", "Roaming");
|
|
2740
|
+
}
|
|
2741
|
+
function detectCursor(cwd) {
|
|
2742
|
+
const env = {
|
|
2743
|
+
id: "cursor",
|
|
2744
|
+
name: "Cursor",
|
|
2745
|
+
icon: "\u{1F5B1}\uFE0F",
|
|
2746
|
+
detected: false,
|
|
2747
|
+
reason: "",
|
|
2748
|
+
mcpConfigured: false,
|
|
2749
|
+
skillConfigured: false
|
|
2750
|
+
};
|
|
2751
|
+
const workspaceCursorDir = join8(cwd, ".cursor");
|
|
2752
|
+
const globalCursorDir = join8(home, ".cursor");
|
|
2753
|
+
if (existsSync4(workspaceCursorDir) || existsSync4(globalCursorDir)) {
|
|
2754
|
+
env.detected = true;
|
|
2755
|
+
env.reason = existsSync4(workspaceCursorDir) ? `Found .cursor/ in workspace` : `Found ~/.cursor/ global config`;
|
|
2756
|
+
}
|
|
2757
|
+
const mcpPath = join8(cwd, ".cursor", "mcp.json");
|
|
2758
|
+
if (existsSync4(mcpPath)) {
|
|
2759
|
+
try {
|
|
2760
|
+
const content = JSON.parse(readFileSync8(mcpPath, "utf8"));
|
|
2761
|
+
if (content?.mcpServers?.elevate) {
|
|
2762
|
+
env.mcpConfigured = true;
|
|
2763
|
+
}
|
|
2764
|
+
} catch {
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
const rulesPath = join8(cwd, ".cursorrules");
|
|
2768
|
+
if (existsSync4(rulesPath)) {
|
|
2769
|
+
const content = readFileSync8(rulesPath, "utf8");
|
|
2770
|
+
if (content.includes("elevate") || content.includes("Elevate")) {
|
|
2771
|
+
env.skillConfigured = true;
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
return env;
|
|
2775
|
+
}
|
|
2776
|
+
function detectClaudeDesktop() {
|
|
2777
|
+
const env = {
|
|
2778
|
+
id: "claude-desktop",
|
|
2779
|
+
name: "Claude Desktop",
|
|
2780
|
+
icon: "\u{1F916}",
|
|
2781
|
+
detected: false,
|
|
2782
|
+
reason: "",
|
|
2783
|
+
mcpConfigured: false,
|
|
2784
|
+
skillConfigured: false
|
|
2785
|
+
};
|
|
2786
|
+
const configPath = isWin ? join8(appData(), "Claude", "claude_desktop_config.json") : join8(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
2787
|
+
if (existsSync4(configPath)) {
|
|
2788
|
+
env.detected = true;
|
|
2789
|
+
env.reason = `Found ${configPath}`;
|
|
2790
|
+
try {
|
|
2791
|
+
const content = JSON.parse(readFileSync8(configPath, "utf8"));
|
|
2792
|
+
if (content?.mcpServers?.elevate) {
|
|
2793
|
+
env.mcpConfigured = true;
|
|
2794
|
+
}
|
|
2795
|
+
} catch {
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
return env;
|
|
2799
|
+
}
|
|
2800
|
+
function detectClaudeCode(cwd) {
|
|
2801
|
+
const env = {
|
|
2802
|
+
id: "claude-code",
|
|
2803
|
+
name: "Claude Code",
|
|
2804
|
+
icon: "\u2328\uFE0F",
|
|
2805
|
+
detected: false,
|
|
2806
|
+
reason: "",
|
|
2807
|
+
mcpConfigured: false,
|
|
2808
|
+
skillConfigured: false
|
|
2809
|
+
};
|
|
2810
|
+
const mcpJsonPath = join8(cwd, ".mcp.json");
|
|
2811
|
+
const claudeMdPath = join8(cwd, "CLAUDE.md");
|
|
2812
|
+
if (existsSync4(mcpJsonPath) || existsSync4(claudeMdPath)) {
|
|
2813
|
+
env.detected = true;
|
|
2814
|
+
env.reason = existsSync4(mcpJsonPath) ? `Found .mcp.json in workspace` : `Found CLAUDE.md in workspace`;
|
|
2815
|
+
}
|
|
2816
|
+
if (existsSync4(mcpJsonPath)) {
|
|
2817
|
+
try {
|
|
2818
|
+
const content = JSON.parse(readFileSync8(mcpJsonPath, "utf8"));
|
|
2819
|
+
if (content?.mcpServers?.elevate) {
|
|
2820
|
+
env.mcpConfigured = true;
|
|
2821
|
+
}
|
|
2822
|
+
} catch {
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
if (existsSync4(claudeMdPath)) {
|
|
2826
|
+
const content = readFileSync8(claudeMdPath, "utf8");
|
|
2827
|
+
if (content.includes("elevate") || content.includes("Elevate")) {
|
|
2828
|
+
env.skillConfigured = true;
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
return env;
|
|
2832
|
+
}
|
|
2833
|
+
function detectAntigravity(cwd) {
|
|
2834
|
+
const env = {
|
|
2835
|
+
id: "antigravity",
|
|
2836
|
+
name: "Google Antigravity",
|
|
2837
|
+
icon: "\u{1F680}",
|
|
2838
|
+
detected: false,
|
|
2839
|
+
reason: "",
|
|
2840
|
+
mcpConfigured: false,
|
|
2841
|
+
skillConfigured: false
|
|
2842
|
+
};
|
|
2843
|
+
const globalGeminiDir = join8(home, ".gemini");
|
|
2844
|
+
const workspaceAgentsDir = join8(cwd, ".agents");
|
|
2845
|
+
if (existsSync4(globalGeminiDir) || existsSync4(workspaceAgentsDir)) {
|
|
2846
|
+
env.detected = true;
|
|
2847
|
+
env.reason = existsSync4(workspaceAgentsDir) ? `Found .agents/ in workspace` : `Found ~/.gemini/ global config`;
|
|
2848
|
+
}
|
|
2849
|
+
const mcpPaths = [
|
|
2850
|
+
join8(cwd, ".agents", "mcp_config.json"),
|
|
2851
|
+
join8(home, ".gemini", "config", "mcp_config.json")
|
|
2852
|
+
];
|
|
2853
|
+
for (const p of mcpPaths) {
|
|
2854
|
+
if (existsSync4(p)) {
|
|
2855
|
+
try {
|
|
2856
|
+
const content = JSON.parse(readFileSync8(p, "utf8"));
|
|
2857
|
+
if (content?.mcpServers?.elevate) {
|
|
2858
|
+
env.mcpConfigured = true;
|
|
2859
|
+
break;
|
|
2860
|
+
}
|
|
2861
|
+
} catch {
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
return env;
|
|
2866
|
+
}
|
|
2867
|
+
function detectVSCode(cwd) {
|
|
2868
|
+
const env = {
|
|
2869
|
+
id: "vscode",
|
|
2870
|
+
name: "VS Code / Copilot",
|
|
2871
|
+
icon: "\u{1F48E}",
|
|
2872
|
+
detected: false,
|
|
2873
|
+
reason: "",
|
|
2874
|
+
mcpConfigured: false,
|
|
2875
|
+
skillConfigured: false
|
|
2876
|
+
};
|
|
2877
|
+
const vscodeDir = join8(cwd, ".vscode");
|
|
2878
|
+
if (existsSync4(vscodeDir)) {
|
|
2879
|
+
env.detected = true;
|
|
2880
|
+
env.reason = `Found .vscode/ in workspace`;
|
|
2881
|
+
}
|
|
2882
|
+
const mcpPath = join8(cwd, ".vscode", "mcp.json");
|
|
2883
|
+
if (existsSync4(mcpPath)) {
|
|
2884
|
+
try {
|
|
2885
|
+
const content = JSON.parse(readFileSync8(mcpPath, "utf8"));
|
|
2886
|
+
if (content?.servers?.elevate) {
|
|
2887
|
+
env.mcpConfigured = true;
|
|
2888
|
+
}
|
|
2889
|
+
} catch {
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
const copilotInstructions = join8(cwd, ".github", "copilot-instructions.md");
|
|
2893
|
+
if (existsSync4(copilotInstructions)) {
|
|
2894
|
+
const content = readFileSync8(copilotInstructions, "utf8");
|
|
2895
|
+
if (content.includes("elevate") || content.includes("Elevate")) {
|
|
2896
|
+
env.skillConfigured = true;
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
return env;
|
|
2900
|
+
}
|
|
2901
|
+
function detectWindsurf() {
|
|
2902
|
+
const env = {
|
|
2903
|
+
id: "windsurf",
|
|
2904
|
+
name: "Windsurf",
|
|
2905
|
+
icon: "\u{1F3C4}",
|
|
2906
|
+
detected: false,
|
|
2907
|
+
reason: "",
|
|
2908
|
+
mcpConfigured: false,
|
|
2909
|
+
skillConfigured: false
|
|
2910
|
+
};
|
|
2911
|
+
const configPath = isWin ? join8(appData(), "Codeium", "Windsurf", "mcp_config.json") : join8(home, ".codeium", "windsurf", "mcp_config.json");
|
|
2912
|
+
const altPath = join8(home, ".codeium", "windsurf", "mcp_config.json");
|
|
2913
|
+
if (existsSync4(configPath) || existsSync4(altPath)) {
|
|
2914
|
+
env.detected = true;
|
|
2915
|
+
env.reason = `Found Windsurf config`;
|
|
2916
|
+
const path = existsSync4(configPath) ? configPath : altPath;
|
|
2917
|
+
try {
|
|
2918
|
+
const content = JSON.parse(readFileSync8(path, "utf8"));
|
|
2919
|
+
if (content?.mcpServers?.elevate) {
|
|
2920
|
+
env.mcpConfigured = true;
|
|
2921
|
+
}
|
|
2922
|
+
} catch {
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
return env;
|
|
2926
|
+
}
|
|
2927
|
+
function mergeJsonMcpServer(filePath, serverKey, topLevelKey) {
|
|
2928
|
+
let existing = {};
|
|
2929
|
+
if (existsSync4(filePath)) {
|
|
2930
|
+
try {
|
|
2931
|
+
existing = JSON.parse(readFileSync8(filePath, "utf8"));
|
|
2932
|
+
} catch {
|
|
2933
|
+
existing = {};
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
const servers = existing[topLevelKey] || {};
|
|
2937
|
+
if (servers[serverKey]) {
|
|
2938
|
+
return { written: false, message: `${filePath}: elevate already registered` };
|
|
2939
|
+
}
|
|
2940
|
+
servers[serverKey] = mcpServerEntry();
|
|
2941
|
+
existing[topLevelKey] = servers;
|
|
2942
|
+
const dir = filePath.substring(0, filePath.lastIndexOf(isWin ? "\\" : "/"));
|
|
2943
|
+
if (dir && !existsSync4(dir)) {
|
|
2944
|
+
mkdirSync(dir, { recursive: true });
|
|
2945
|
+
}
|
|
2946
|
+
writeFileSync3(filePath, JSON.stringify(existing, null, 2) + "\n", "utf8");
|
|
2947
|
+
return { written: true, message: `${filePath}: elevate MCP server registered` };
|
|
2948
|
+
}
|
|
2949
|
+
function appendSkillToFile(filePath, content, marker) {
|
|
2950
|
+
if (existsSync4(filePath)) {
|
|
2951
|
+
const existing = readFileSync8(filePath, "utf8");
|
|
2952
|
+
if (existing.includes(marker)) {
|
|
2953
|
+
return { written: false, message: `${filePath}: skill already present` };
|
|
2954
|
+
}
|
|
2955
|
+
writeFileSync3(filePath, existing + "\n\n" + content, "utf8");
|
|
2956
|
+
return { written: true, message: `${filePath}: skill appended` };
|
|
2957
|
+
}
|
|
2958
|
+
const dir = filePath.substring(0, filePath.lastIndexOf(isWin ? "\\" : "/"));
|
|
2959
|
+
if (dir && !existsSync4(dir)) {
|
|
2960
|
+
mkdirSync(dir, { recursive: true });
|
|
2961
|
+
}
|
|
2962
|
+
writeFileSync3(filePath, content, "utf8");
|
|
2963
|
+
return { written: true, message: `${filePath}: skill created` };
|
|
2964
|
+
}
|
|
2965
|
+
function configureCursor(cwd, result) {
|
|
2966
|
+
const mcpPath = join8(cwd, ".cursor", "mcp.json");
|
|
2967
|
+
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
2968
|
+
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Cursor MCP: ${mcp.message}`);
|
|
2969
|
+
const rulesPath = join8(cwd, ".cursorrules");
|
|
2970
|
+
const skill = appendSkillToFile(rulesPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
|
|
2971
|
+
result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Cursor Rules: ${skill.message}`);
|
|
2972
|
+
}
|
|
2973
|
+
function configureClaudeDesktop(result) {
|
|
2974
|
+
const configPath = isWin ? join8(appData(), "Claude", "claude_desktop_config.json") : join8(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
2975
|
+
const mcp = mergeJsonMcpServer(configPath, "elevate", "mcpServers");
|
|
2976
|
+
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Claude Desktop MCP: ${mcp.message}`);
|
|
2977
|
+
}
|
|
2978
|
+
function configureClaudeCode(cwd, result) {
|
|
2979
|
+
const mcpPath = join8(cwd, ".mcp.json");
|
|
2980
|
+
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
2981
|
+
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Claude Code MCP: ${mcp.message}`);
|
|
2982
|
+
const claudeMdPath = join8(cwd, "CLAUDE.md");
|
|
2983
|
+
const skill = appendSkillToFile(claudeMdPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
|
|
2984
|
+
result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Claude Code Skill: ${skill.message}`);
|
|
2985
|
+
}
|
|
2986
|
+
function configureAntigravity(cwd, result) {
|
|
2987
|
+
const mcpPath = join8(cwd, ".agents", "mcp_config.json");
|
|
2988
|
+
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
2989
|
+
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Antigravity MCP: ${mcp.message}`);
|
|
2990
|
+
}
|
|
2991
|
+
function configureVSCode(cwd, result) {
|
|
2992
|
+
const mcpPath = join8(cwd, ".vscode", "mcp.json");
|
|
2993
|
+
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "servers");
|
|
2994
|
+
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} VS Code MCP: ${mcp.message}`);
|
|
2995
|
+
const instructionsPath = join8(cwd, ".github", "copilot-instructions.md");
|
|
2996
|
+
const skill = appendSkillToFile(instructionsPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
|
|
2997
|
+
result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Copilot Instructions: ${skill.message}`);
|
|
2998
|
+
}
|
|
2999
|
+
function configureWindsurf(result) {
|
|
3000
|
+
const configPath = isWin ? join8(appData(), "Codeium", "Windsurf", "mcp_config.json") : join8(home, ".codeium", "windsurf", "mcp_config.json");
|
|
3001
|
+
const mcp = mergeJsonMcpServer(configPath, "elevate", "mcpServers");
|
|
3002
|
+
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Windsurf MCP: ${mcp.message}`);
|
|
3003
|
+
}
|
|
3004
|
+
async function handleInitCommand(options) {
|
|
3005
|
+
const cwd = resolve2(process.cwd());
|
|
3006
|
+
const result = {
|
|
3007
|
+
cwd,
|
|
3008
|
+
environments: [],
|
|
3009
|
+
actions: [],
|
|
3010
|
+
errors: []
|
|
3011
|
+
};
|
|
3012
|
+
const detectors = [
|
|
3013
|
+
() => detectCursor(cwd),
|
|
3014
|
+
() => detectClaudeDesktop(),
|
|
3015
|
+
() => detectClaudeCode(cwd),
|
|
3016
|
+
() => detectAntigravity(cwd),
|
|
3017
|
+
() => detectVSCode(cwd),
|
|
3018
|
+
() => detectWindsurf()
|
|
3019
|
+
];
|
|
3020
|
+
for (const detect of detectors) {
|
|
3021
|
+
result.environments.push(detect());
|
|
3022
|
+
}
|
|
3023
|
+
const detected = result.environments.filter((e) => e.detected);
|
|
3024
|
+
if (detected.length === 0) {
|
|
3025
|
+
if (!options.json) {
|
|
3026
|
+
console.log(`
|
|
3027
|
+
\u{1FAB6} Elevate Init
|
|
3028
|
+
`);
|
|
3029
|
+
console.log(` No AI coding environments detected in this workspace.`);
|
|
3030
|
+
console.log(` Creating portable .mcp.json for broad compatibility.
|
|
3031
|
+
`);
|
|
3032
|
+
}
|
|
3033
|
+
const mcpPath = join8(cwd, ".mcp.json");
|
|
3034
|
+
const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
|
|
3035
|
+
result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Generic MCP: ${mcp.message}`);
|
|
3036
|
+
} else {
|
|
3037
|
+
if (!options.json) {
|
|
3038
|
+
console.log(`
|
|
3039
|
+
\u{1FAB6} Elevate Init \u2014 Detected ${detected.length} environment(s)
|
|
3040
|
+
`);
|
|
3041
|
+
}
|
|
3042
|
+
const configurators = {
|
|
3043
|
+
cursor: configureCursor,
|
|
3044
|
+
"claude-desktop": (_cwd, r) => configureClaudeDesktop(r),
|
|
3045
|
+
"claude-code": configureClaudeCode,
|
|
3046
|
+
antigravity: configureAntigravity,
|
|
3047
|
+
vscode: configureVSCode,
|
|
3048
|
+
windsurf: (_cwd, r) => configureWindsurf(r)
|
|
3049
|
+
};
|
|
3050
|
+
for (const env of detected) {
|
|
3051
|
+
try {
|
|
3052
|
+
const configure = configurators[env.id];
|
|
3053
|
+
if (configure) {
|
|
3054
|
+
configure(cwd, result);
|
|
3055
|
+
}
|
|
3056
|
+
} catch (err) {
|
|
3057
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3058
|
+
result.errors.push(`${env.name}: ${msg}`);
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
if (options.json) {
|
|
3063
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3064
|
+
return result.errors.length > 0 ? 1 : 0;
|
|
3065
|
+
}
|
|
3066
|
+
console.log(" Environments:");
|
|
3067
|
+
for (const env of result.environments) {
|
|
3068
|
+
const status = env.detected ? "\u2705" : " ";
|
|
3069
|
+
const mcpBadge = env.mcpConfigured ? " [MCP \u2713]" : "";
|
|
3070
|
+
const skillBadge = env.skillConfigured ? " [Skill \u2713]" : "";
|
|
3071
|
+
console.log(` ${status} ${env.icon} ${env.name.padEnd(22)}${env.detected ? env.reason : "not detected"}${mcpBadge}${skillBadge}`);
|
|
3072
|
+
}
|
|
3073
|
+
if (result.actions.length > 0) {
|
|
3074
|
+
console.log(`
|
|
3075
|
+
Actions:`);
|
|
3076
|
+
for (const action of result.actions) {
|
|
3077
|
+
console.log(` ${action}`);
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
if (result.errors.length > 0) {
|
|
3081
|
+
console.log(`
|
|
3082
|
+
\u26A0\uFE0F Errors:`);
|
|
3083
|
+
for (const error of result.errors) {
|
|
3084
|
+
console.log(` \u274C ${error}`);
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
console.log(`
|
|
3088
|
+
Done! Restart your AI coding tools to pick up the new MCP configuration.`);
|
|
3089
|
+
console.log(` Agents will now automatically use Elevate for dependency management.
|
|
3090
|
+
`);
|
|
3091
|
+
return result.errors.length > 0 ? 1 : 0;
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
// src/mcp/mcp-server.ts
|
|
3095
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3096
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3097
|
+
import {
|
|
3098
|
+
CallToolRequestSchema,
|
|
3099
|
+
ListToolsRequestSchema
|
|
3100
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
3101
|
+
import semver6 from "semver";
|
|
3102
|
+
async function startMcpServer(config) {
|
|
3103
|
+
const server = new Server(
|
|
3104
|
+
{
|
|
3105
|
+
name: "elevate-mcp",
|
|
3106
|
+
version: "1.2.0"
|
|
3107
|
+
},
|
|
3108
|
+
{
|
|
3109
|
+
capabilities: {
|
|
3110
|
+
tools: {}
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
);
|
|
3114
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
3115
|
+
return {
|
|
3116
|
+
tools: [
|
|
3117
|
+
{
|
|
3118
|
+
name: "elevate_discover_modules",
|
|
3119
|
+
description: "Discovers all repository modules and workspaces for Node/npm (package.json) or Java/Maven (pom.xml).",
|
|
3120
|
+
inputSchema: {
|
|
3121
|
+
type: "object",
|
|
3122
|
+
properties: {
|
|
3123
|
+
ecosystem: {
|
|
3124
|
+
type: "string",
|
|
3125
|
+
enum: ["npm", "maven"],
|
|
3126
|
+
description: "Target ecosystem (default: npm)"
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
},
|
|
3131
|
+
{
|
|
3132
|
+
name: "elevate_scan",
|
|
3133
|
+
description: "Scans a module (or all modules) for available dependency updates against the registry.",
|
|
3134
|
+
inputSchema: {
|
|
3135
|
+
type: "object",
|
|
3136
|
+
properties: {
|
|
3137
|
+
ecosystem: {
|
|
3138
|
+
type: "string",
|
|
3139
|
+
enum: ["npm", "maven"],
|
|
3140
|
+
description: "Target ecosystem (default: npm)"
|
|
3141
|
+
},
|
|
3142
|
+
moduleId: {
|
|
3143
|
+
type: "string",
|
|
3144
|
+
description: 'Module ID or relative path (e.g. "apps/e2e-cockpit"). If omitted, scans the root or primary module.'
|
|
3145
|
+
},
|
|
3146
|
+
channel: {
|
|
3147
|
+
type: "string",
|
|
3148
|
+
enum: ["stable", "all"],
|
|
3149
|
+
description: 'Release channel: "stable" (production-ready versions only) or "all" (including pre-releases/betas). Default: stable'
|
|
3150
|
+
},
|
|
3151
|
+
allModules: {
|
|
3152
|
+
type: "boolean",
|
|
3153
|
+
description: "If true, scans all discovered modules across the monorepo."
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
},
|
|
3158
|
+
{
|
|
3159
|
+
name: "elevate_get_versions",
|
|
3160
|
+
description: "Fetches the complete published version history of a package from the registry.",
|
|
3161
|
+
inputSchema: {
|
|
3162
|
+
type: "object",
|
|
3163
|
+
properties: {
|
|
3164
|
+
ecosystem: {
|
|
3165
|
+
type: "string",
|
|
3166
|
+
enum: ["npm", "maven"],
|
|
3167
|
+
description: "Target ecosystem (default: npm)"
|
|
3168
|
+
},
|
|
3169
|
+
identifier: {
|
|
3170
|
+
type: "string",
|
|
3171
|
+
description: "Package identifier (npm: chalk, maven: org.slf4j:slf4j-api)"
|
|
3172
|
+
}
|
|
3173
|
+
},
|
|
3174
|
+
required: ["identifier"]
|
|
3175
|
+
}
|
|
3176
|
+
},
|
|
3177
|
+
{
|
|
3178
|
+
name: "elevate_apply_updates",
|
|
3179
|
+
description: "Applies targeted dependency updates to a module, installs packages, and executes automatic build & test verification.",
|
|
3180
|
+
inputSchema: {
|
|
3181
|
+
type: "object",
|
|
3182
|
+
properties: {
|
|
3183
|
+
ecosystem: {
|
|
3184
|
+
type: "string",
|
|
3185
|
+
enum: ["npm", "maven"],
|
|
3186
|
+
description: "Target ecosystem (default: npm)"
|
|
3187
|
+
},
|
|
3188
|
+
moduleId: {
|
|
3189
|
+
type: "string",
|
|
3190
|
+
description: "Module ID or relative path of the target module"
|
|
3191
|
+
},
|
|
3192
|
+
updates: {
|
|
3193
|
+
type: "array",
|
|
3194
|
+
description: "List of packages to update with optional target versions",
|
|
3195
|
+
items: {
|
|
3196
|
+
type: "object",
|
|
3197
|
+
properties: {
|
|
3198
|
+
identifier: { type: "string", description: "Package identifier" },
|
|
3199
|
+
targetVersion: {
|
|
3200
|
+
type: "string",
|
|
3201
|
+
description: 'Specific target version (e.g. "5.6.2" or "^3.2.0"). If omitted, updates to latest.'
|
|
3202
|
+
}
|
|
3203
|
+
},
|
|
3204
|
+
required: ["identifier"]
|
|
3205
|
+
}
|
|
3206
|
+
},
|
|
3207
|
+
allowMajor: {
|
|
3208
|
+
type: "boolean",
|
|
3209
|
+
description: "Safety guardrail: Must be explicitly true to permit breaking major version upgrades."
|
|
3210
|
+
},
|
|
3211
|
+
dryRun: {
|
|
3212
|
+
type: "boolean",
|
|
3213
|
+
description: "If true, simulates planned changes without modifying any files."
|
|
3214
|
+
},
|
|
3215
|
+
skipVerification: {
|
|
3216
|
+
type: "boolean",
|
|
3217
|
+
description: "Skips the post-update build and test verification step."
|
|
3218
|
+
}
|
|
3219
|
+
},
|
|
3220
|
+
required: ["moduleId", "updates"]
|
|
3221
|
+
}
|
|
3222
|
+
},
|
|
3223
|
+
{
|
|
3224
|
+
name: "elevate_verify",
|
|
3225
|
+
description: "Runs module-specific verification and build checks (e.g. npm test, mvn test-compile).",
|
|
3226
|
+
inputSchema: {
|
|
3227
|
+
type: "object",
|
|
3228
|
+
properties: {
|
|
3229
|
+
ecosystem: {
|
|
3230
|
+
type: "string",
|
|
3231
|
+
enum: ["npm", "maven"],
|
|
3232
|
+
description: "Target ecosystem (default: npm)"
|
|
3233
|
+
},
|
|
3234
|
+
moduleId: {
|
|
3235
|
+
type: "string",
|
|
3236
|
+
description: "Module ID or relative path of the module to verify"
|
|
3237
|
+
}
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
]
|
|
3242
|
+
};
|
|
3243
|
+
});
|
|
3244
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
3245
|
+
const { name, arguments: args = {} } = request.params;
|
|
3246
|
+
const ecosystem = args.ecosystem || "npm";
|
|
3247
|
+
const strategy = EcosystemFactory.getStrategy(ecosystem);
|
|
3248
|
+
try {
|
|
3249
|
+
if (name === "elevate_discover_modules") {
|
|
3250
|
+
const modules = await strategy.discovery.discover(config.rootDir);
|
|
3251
|
+
return {
|
|
3252
|
+
content: [
|
|
3253
|
+
{
|
|
3254
|
+
type: "text",
|
|
3255
|
+
text: JSON.stringify(
|
|
3256
|
+
{
|
|
3257
|
+
ecosystem,
|
|
3258
|
+
count: modules.length,
|
|
3259
|
+
modules: modules.map((m) => ({
|
|
3260
|
+
id: m.id,
|
|
3261
|
+
name: m.name,
|
|
3262
|
+
relPath: m.relPath,
|
|
3263
|
+
isRoot: m.isRoot
|
|
3264
|
+
}))
|
|
3265
|
+
},
|
|
3266
|
+
null,
|
|
3267
|
+
2
|
|
3268
|
+
)
|
|
3269
|
+
}
|
|
3270
|
+
]
|
|
3271
|
+
};
|
|
3272
|
+
}
|
|
3273
|
+
if (name === "elevate_scan") {
|
|
3274
|
+
const channel = args.channel || config.channel || "stable";
|
|
3275
|
+
const modules = await strategy.discovery.discover(config.rootDir);
|
|
3276
|
+
const internalIds = /* @__PURE__ */ new Set();
|
|
3277
|
+
for (const m of modules) {
|
|
3278
|
+
if (m.id) internalIds.add(m.id);
|
|
3279
|
+
if (m.name) internalIds.add(m.name);
|
|
3280
|
+
}
|
|
3281
|
+
const targetModules = args.allModules ? modules : args.moduleId ? modules.filter(
|
|
3282
|
+
(m) => m.id.toLowerCase() === String(args.moduleId).toLowerCase() || m.relPath.toLowerCase() === String(args.moduleId).toLowerCase() || m.path.toLowerCase().endsWith(String(args.moduleId).toLowerCase())
|
|
3283
|
+
) : [modules[0]];
|
|
3284
|
+
if (targetModules.length === 0) {
|
|
3285
|
+
throw new Error(`Module '${args.moduleId}' not found.`);
|
|
3286
|
+
}
|
|
3287
|
+
const results = [];
|
|
3288
|
+
let totalCount = 0;
|
|
3289
|
+
for (const mod of targetModules) {
|
|
3290
|
+
const updates = await strategy.reader.scan(mod, config.excludeScopes, internalIds, channel);
|
|
3291
|
+
totalCount += updates.length;
|
|
3292
|
+
results.push({
|
|
3293
|
+
moduleId: mod.id,
|
|
3294
|
+
modulePath: mod.relPath,
|
|
3295
|
+
updateCount: updates.length,
|
|
3296
|
+
updates: updates.map((u) => ({
|
|
3297
|
+
identifier: u.coordinate.identifier,
|
|
3298
|
+
currentRange: u.currentRange,
|
|
3299
|
+
currentClean: u.currentClean,
|
|
3300
|
+
latest: u.latest,
|
|
3301
|
+
newRange: u.newRange,
|
|
3302
|
+
diff: u.diff,
|
|
3303
|
+
scope: u.scope,
|
|
3304
|
+
isPreRelease: u.isPreRelease ?? false,
|
|
3305
|
+
preReleaseTag: u.preReleaseTag
|
|
3306
|
+
}))
|
|
3307
|
+
});
|
|
3308
|
+
}
|
|
3309
|
+
return {
|
|
3310
|
+
content: [
|
|
3311
|
+
{
|
|
3312
|
+
type: "text",
|
|
3313
|
+
text: JSON.stringify(
|
|
3314
|
+
{
|
|
3315
|
+
ecosystem,
|
|
3316
|
+
channel,
|
|
3317
|
+
scannedModules: targetModules.length,
|
|
3318
|
+
totalUpdatesCount: totalCount,
|
|
3319
|
+
results
|
|
3320
|
+
},
|
|
3321
|
+
null,
|
|
3322
|
+
2
|
|
3323
|
+
)
|
|
3324
|
+
}
|
|
3325
|
+
]
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
if (name === "elevate_get_versions") {
|
|
3329
|
+
const identifier = String(args.identifier);
|
|
3330
|
+
let coordinate;
|
|
3331
|
+
if (ecosystem === "maven") {
|
|
3332
|
+
const parts = identifier.split(":");
|
|
3333
|
+
coordinate = {
|
|
3334
|
+
identifier,
|
|
3335
|
+
group: parts[0],
|
|
3336
|
+
artifact: parts[1] || parts[0],
|
|
3337
|
+
ecosystem: "maven"
|
|
3338
|
+
};
|
|
3339
|
+
} else {
|
|
3340
|
+
coordinate = {
|
|
3341
|
+
identifier,
|
|
3342
|
+
artifact: identifier,
|
|
3343
|
+
ecosystem: "npm"
|
|
3344
|
+
};
|
|
3345
|
+
}
|
|
3346
|
+
const versions = await strategy.registry.getAllVersions(coordinate);
|
|
3347
|
+
return {
|
|
3348
|
+
content: [
|
|
3349
|
+
{
|
|
3350
|
+
type: "text",
|
|
3351
|
+
text: JSON.stringify(
|
|
3352
|
+
{
|
|
3353
|
+
ecosystem,
|
|
3354
|
+
package: identifier,
|
|
3355
|
+
totalCount: versions.length,
|
|
3356
|
+
latest: versions[0] || null,
|
|
3357
|
+
versions: versions.map((v) => ({
|
|
3358
|
+
version: v,
|
|
3359
|
+
isPreRelease: isPreReleaseVersion(v),
|
|
3360
|
+
tag: extractPreReleaseTag(v)
|
|
3361
|
+
}))
|
|
3362
|
+
},
|
|
3363
|
+
null,
|
|
3364
|
+
2
|
|
3365
|
+
)
|
|
3366
|
+
}
|
|
3367
|
+
]
|
|
3368
|
+
};
|
|
3369
|
+
}
|
|
3370
|
+
if (name === "elevate_apply_updates") {
|
|
3371
|
+
const modules = await strategy.discovery.discover(config.rootDir);
|
|
3372
|
+
const mod = modules.find(
|
|
3373
|
+
(m) => m.id.toLowerCase() === String(args.moduleId).toLowerCase() || m.relPath.toLowerCase() === String(args.moduleId).toLowerCase() || m.path.toLowerCase().endsWith(String(args.moduleId).toLowerCase())
|
|
3374
|
+
);
|
|
3375
|
+
if (!mod) {
|
|
3376
|
+
throw new Error(`Module '${args.moduleId}' not found.`);
|
|
3377
|
+
}
|
|
3378
|
+
const internalIds = /* @__PURE__ */ new Set();
|
|
3379
|
+
for (const m of modules) {
|
|
3380
|
+
if (m.id) internalIds.add(m.id);
|
|
3381
|
+
if (m.name) internalIds.add(m.name);
|
|
3382
|
+
}
|
|
3383
|
+
const availableUpdates = await strategy.reader.scan(mod, config.excludeScopes, internalIds, "stable");
|
|
3384
|
+
const requestedUpdates = args.updates || [];
|
|
3385
|
+
const updatesToApply = [];
|
|
3386
|
+
for (const req of requestedUpdates) {
|
|
3387
|
+
const found = availableUpdates.find((u) => u.coordinate.identifier === req.identifier);
|
|
3388
|
+
if (found) {
|
|
3389
|
+
const targetVer = req.targetVersion || found.latest;
|
|
3390
|
+
const prefix = found.currentRange.startsWith("~") ? "~" : found.currentRange.startsWith("^") ? "^" : "";
|
|
3391
|
+
const diff = semver6.valid(targetVer) && found.currentClean ? semver6.diff(found.currentClean, targetVer) || "minor" : found.diff;
|
|
3392
|
+
if (diff === "major" && !args.allowMajor) {
|
|
3393
|
+
throw new Error(
|
|
3394
|
+
`Major update for '${req.identifier}' to ${targetVer} rejected. Set 'allowMajor: true' to permit breaking changes.`
|
|
3395
|
+
);
|
|
3396
|
+
}
|
|
3397
|
+
updatesToApply.push({
|
|
3398
|
+
...found,
|
|
3399
|
+
newRange: `${prefix}${targetVer}`,
|
|
3400
|
+
diff,
|
|
3401
|
+
selected: true
|
|
3402
|
+
});
|
|
3403
|
+
} else if (req.targetVersion) {
|
|
3404
|
+
updatesToApply.push({
|
|
3405
|
+
coordinate: {
|
|
3406
|
+
identifier: req.identifier,
|
|
3407
|
+
artifact: req.identifier,
|
|
3408
|
+
ecosystem
|
|
3409
|
+
},
|
|
3410
|
+
currentRange: "unknown",
|
|
3411
|
+
currentClean: "0.0.0",
|
|
3412
|
+
latest: req.targetVersion,
|
|
3413
|
+
newRange: req.targetVersion,
|
|
3414
|
+
diff: "minor",
|
|
3415
|
+
scope: "prod",
|
|
3416
|
+
selected: true
|
|
3417
|
+
});
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
if (args.dryRun) {
|
|
3421
|
+
return {
|
|
3422
|
+
content: [
|
|
3423
|
+
{
|
|
3424
|
+
type: "text",
|
|
3425
|
+
text: JSON.stringify(
|
|
3426
|
+
{
|
|
3427
|
+
dryRun: true,
|
|
3428
|
+
targetModule: mod.relPath,
|
|
3429
|
+
ecosystem,
|
|
3430
|
+
updatesCount: updatesToApply.length,
|
|
3431
|
+
updates: updatesToApply.map((u) => ({
|
|
3432
|
+
identifier: u.coordinate.identifier,
|
|
3433
|
+
currentRange: u.currentRange,
|
|
3434
|
+
newRange: u.newRange,
|
|
3435
|
+
diff: u.diff
|
|
3436
|
+
}))
|
|
3437
|
+
},
|
|
3438
|
+
null,
|
|
3439
|
+
2
|
|
3440
|
+
)
|
|
3441
|
+
}
|
|
3442
|
+
]
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
const updateResult = await strategy.updater.applyUpdates(mod, config.rootDir, updatesToApply, () => {
|
|
3446
|
+
});
|
|
3447
|
+
let verifyResult;
|
|
3448
|
+
if (!args.skipVerification) {
|
|
3449
|
+
verifyResult = await strategy.verifier.verify(
|
|
3450
|
+
mod,
|
|
3451
|
+
config.rootDir,
|
|
3452
|
+
config.postUpdateScript,
|
|
3453
|
+
config.postUpdateLabel
|
|
3454
|
+
);
|
|
3455
|
+
}
|
|
3456
|
+
return {
|
|
3457
|
+
content: [
|
|
3458
|
+
{
|
|
3459
|
+
type: "text",
|
|
3460
|
+
text: JSON.stringify(
|
|
3461
|
+
{
|
|
3462
|
+
success: verifyResult?.status !== "warn",
|
|
3463
|
+
targetModule: mod.relPath,
|
|
3464
|
+
ecosystem,
|
|
3465
|
+
...updateResult,
|
|
3466
|
+
verification: verifyResult
|
|
3467
|
+
},
|
|
3468
|
+
null,
|
|
3469
|
+
2
|
|
3470
|
+
)
|
|
3471
|
+
}
|
|
3472
|
+
]
|
|
3473
|
+
};
|
|
3474
|
+
}
|
|
3475
|
+
if (name === "elevate_verify") {
|
|
3476
|
+
const modules = await strategy.discovery.discover(config.rootDir);
|
|
3477
|
+
const mod = args.moduleId ? modules.find(
|
|
3478
|
+
(m) => m.id.toLowerCase() === String(args.moduleId).toLowerCase() || m.relPath.toLowerCase() === String(args.moduleId).toLowerCase() || m.path.toLowerCase().endsWith(String(args.moduleId).toLowerCase())
|
|
3479
|
+
) : modules[0];
|
|
3480
|
+
if (!mod) {
|
|
3481
|
+
throw new Error(`Module '${args.moduleId || "default"}' not found.`);
|
|
3482
|
+
}
|
|
3483
|
+
const verifyResult = await strategy.verifier.verify(
|
|
3484
|
+
mod,
|
|
3485
|
+
config.rootDir,
|
|
3486
|
+
config.postUpdateScript,
|
|
3487
|
+
config.postUpdateLabel
|
|
3488
|
+
);
|
|
3489
|
+
return {
|
|
3490
|
+
content: [
|
|
3491
|
+
{
|
|
3492
|
+
type: "text",
|
|
3493
|
+
text: JSON.stringify(
|
|
3494
|
+
{
|
|
3495
|
+
module: mod.relPath,
|
|
3496
|
+
ecosystem,
|
|
3497
|
+
...verifyResult
|
|
3498
|
+
},
|
|
3499
|
+
null,
|
|
3500
|
+
2
|
|
3501
|
+
)
|
|
3502
|
+
}
|
|
3503
|
+
]
|
|
3504
|
+
};
|
|
3505
|
+
}
|
|
3506
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
3507
|
+
} catch (err) {
|
|
3508
|
+
return {
|
|
3509
|
+
isError: true,
|
|
3510
|
+
content: [
|
|
3511
|
+
{
|
|
3512
|
+
type: "text",
|
|
3513
|
+
text: `\u274C Error executing tool '${name}': ${err.message || String(err)}`
|
|
3514
|
+
}
|
|
3515
|
+
]
|
|
3516
|
+
};
|
|
3517
|
+
}
|
|
3518
|
+
});
|
|
3519
|
+
const transport = new StdioServerTransport();
|
|
3520
|
+
await server.connect(transport);
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
// src/index.tsx
|
|
3524
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
3525
|
+
async function main() {
|
|
3526
|
+
const config = loadConfig();
|
|
3527
|
+
const options = parseCliArgs(process.argv.slice(2));
|
|
3528
|
+
if (options.subcommand === "help" || options.help) {
|
|
3529
|
+
printCliHelp();
|
|
3530
|
+
process.exit(0);
|
|
3531
|
+
}
|
|
3532
|
+
if (options.subcommand === "mcp") {
|
|
3533
|
+
await startMcpServer(config);
|
|
3534
|
+
return;
|
|
3535
|
+
}
|
|
3536
|
+
if (options.subcommand === "modules") {
|
|
3537
|
+
const exitCode = await handleModulesCommand(options, config);
|
|
3538
|
+
process.exit(exitCode);
|
|
3539
|
+
}
|
|
3540
|
+
if (options.subcommand === "scan") {
|
|
3541
|
+
const exitCode = await handleScanCommand(options, config);
|
|
3542
|
+
process.exit(exitCode);
|
|
3543
|
+
}
|
|
3544
|
+
if (options.subcommand === "versions") {
|
|
3545
|
+
const exitCode = await handleVersionsCommand(options);
|
|
3546
|
+
process.exit(exitCode);
|
|
3547
|
+
}
|
|
3548
|
+
if (options.subcommand === "update") {
|
|
3549
|
+
const exitCode = await handleUpdateCommand(options, config);
|
|
3550
|
+
process.exit(exitCode);
|
|
3551
|
+
}
|
|
3552
|
+
if (options.subcommand === "init") {
|
|
3553
|
+
const exitCode = await handleInitCommand(options);
|
|
3554
|
+
process.exit(exitCode);
|
|
3555
|
+
}
|
|
3556
|
+
if (process.stdout.isTTY) {
|
|
3557
|
+
const minCols = 140;
|
|
3558
|
+
const minRows = 38;
|
|
3559
|
+
const cols = Math.max(process.stdout.columns || 80, minCols);
|
|
3560
|
+
const rows = Math.max(process.stdout.rows || 24, minRows);
|
|
3561
|
+
process.stdout.write(`\x1B[8;${rows};${cols}t`);
|
|
3562
|
+
}
|
|
3563
|
+
render(/* @__PURE__ */ jsx15(App, { config }));
|
|
3564
|
+
}
|
|
3565
|
+
main().catch((err) => {
|
|
3566
|
+
console.error("\u274C Elevate fatal error:", err);
|
|
3567
|
+
process.exit(1);
|
|
3568
|
+
});
|
|
3569
|
+
//# sourceMappingURL=cli.js.map
|