@dennisrepa/elevate 1.7.0 → 1.8.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/dist/cli.js CHANGED
@@ -1,3505 +1,20 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ ConfigError,
4
+ EcosystemFactory,
5
+ InstallError,
6
+ VERSION,
7
+ buildWorkspaceIndex,
8
+ classifyOrigin,
9
+ extractPreReleaseTag,
10
+ isPreReleaseVersion,
11
+ loadConfig,
12
+ matchesInternalScope
13
+ } from "./chunk-4YCMSRA6.js";
2
14
 
3
15
  // src/index.tsx
4
16
  import { render } from "ink";
5
17
 
6
- // src/config.ts
7
- import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
8
- import { dirname as dirname10, join as join23, resolve as resolve14 } from "path";
9
-
10
- // src/domain/version-policy.ts
11
- import semver2 from "semver";
12
-
13
- // src/domain/versions.ts
14
- import semver from "semver";
15
- var PRE_RELEASE = /(?:^|[.\-_+]|(?<=\d))(alpha|beta|rc|cr|milestone|preview|snapshot|ea|dev|pre|(?:a|b|m)(?=\d))(?=$|[.\-_+]|\d)/i;
16
- function isPreReleaseVersion(version) {
17
- return PRE_RELEASE.test(version);
18
- }
19
- function extractPreReleaseTag(version) {
20
- const match = version.match(PRE_RELEASE);
21
- if (!match) return void 0;
22
- const token = match[1].toLowerCase();
23
- if (token === "a") return "ALPHA";
24
- if (token === "b") return "BETA";
25
- if (token === "m") {
26
- const number = version.slice((match.index ?? 0) + match[0].length).match(/^\d+/)?.[0];
27
- return `M${number ?? ""}`;
28
- }
29
- return token.toUpperCase();
30
- }
31
- function cleanJavaVersion(version) {
32
- const match = version.match(/^v?(\d+(?:\.\d+){0,2})/);
33
- if (!match) return null;
34
- return semver.coerce(match[1])?.version ?? null;
35
- }
36
- function diffVersions(from, to) {
37
- const kind = semver.diff(from, to);
38
- if (kind === "major" || kind === "premajor") return "major";
39
- if (kind === "minor" || kind === "preminor") return "minor";
40
- return "patch";
41
- }
42
- function rangePrefix(range) {
43
- if (range.startsWith("^")) return "^";
44
- if (range.startsWith("~")) return "~";
45
- return "";
46
- }
47
-
48
- // src/domain/version-policy.ts
49
- function semverDiff(current, target, normalize) {
50
- const to = normalize(target);
51
- if (!to || !semver2.valid(current)) return "minor";
52
- return semver2.eq(current, to) ? "patch" : diffVersions(current, to);
53
- }
54
- var npmVersionPolicy = {
55
- normalize: (v) => semver2.valid(v),
56
- rewrite: (current, target) => `${rangePrefix(current)}${target}`,
57
- diff: (current, target) => semverDiff(current, target, (v) => semver2.valid(v)),
58
- allowsManualTargets: true
59
- };
60
- var mavenVersionPolicy = {
61
- normalize: cleanJavaVersion,
62
- rewrite: (_current, target) => target,
63
- diff: (current, target) => semverDiff(current, target, cleanJavaVersion),
64
- allowsManualTargets: false
65
- };
66
-
67
- // src/adapters/npm/npm-package-manager.ts
68
- import { existsSync as existsSync2, readFileSync } from "fs";
69
- import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
70
-
71
- // src/adapters/shared/checkout.ts
72
- import { existsSync } from "fs";
73
- import { dirname, join, resolve } from "path";
74
- function findCheckoutBoundary(startDir) {
75
- let dir = resolve(startDir);
76
- for (; ; ) {
77
- if (existsSync(join(dir, ".git"))) return dir;
78
- const parent = dirname(dir);
79
- if (parent === dir) return void 0;
80
- dir = parent;
81
- }
82
- }
83
-
84
- // src/adapters/shared/process.ts
85
- import { exec, spawn } from "child_process";
86
- var UnsafeArgumentError = class extends Error {
87
- constructor(argument) {
88
- super(
89
- `Cannot pass ${JSON.stringify(argument)} to the Windows command shell: it contains one of the characters " % ! ^ & | < > or a line break, which cmd.exe would interpret. If this is part of a directory or file name, rename it or move the project to a path without these characters.`
90
- );
91
- this.name = "UnsafeArgumentError";
92
- }
93
- };
94
- var IS_WINDOWS = process.platform === "win32";
95
- var WINDOWS_UNSAFE = /["%!^&|<>\r\n]/;
96
- function runCommand(command, args, options) {
97
- return new Promise((resolve16, reject) => {
98
- const spawnOptions = { cwd: options.cwd, windowsHide: true, env: process.env };
99
- let child2;
100
- if (IS_WINDOWS) {
101
- for (const arg of [command, ...args]) {
102
- if (WINDOWS_UNSAFE.test(arg)) {
103
- reject(new UnsafeArgumentError(arg));
104
- return;
105
- }
106
- }
107
- const commandLine = [command, ...args].map(quoteWindows).join(" ");
108
- child2 = spawn(commandLine, { ...spawnOptions, shell: true });
109
- } else {
110
- child2 = spawn(command, args, spawnOptions);
111
- }
112
- let stdout = "";
113
- let stderr = "";
114
- let timedOut = false;
115
- child2.stdout.setEncoding("utf8").on("data", (chunk) => stdout += chunk);
116
- child2.stderr.setEncoding("utf8").on("data", (chunk) => stderr += chunk);
117
- const timer = options.timeoutMs ? setTimeout(() => {
118
- timedOut = true;
119
- killTree(child2.pid, () => child2.kill());
120
- }, options.timeoutMs) : void 0;
121
- child2.on("error", (err) => {
122
- if (timer) clearTimeout(timer);
123
- reject(err);
124
- });
125
- child2.on("close", (code) => {
126
- if (timer) clearTimeout(timer);
127
- resolve16({ exitCode: code ?? 1, stdout, stderr, timedOut });
128
- });
129
- });
130
- }
131
- function runShellScript(script, options) {
132
- return new Promise((resolve16) => {
133
- exec(
134
- script,
135
- { cwd: options.cwd, timeout: options.timeoutMs, maxBuffer: 64 * 1024 * 1024, windowsHide: true },
136
- (error, stdout, stderr) => {
137
- const timedOut = Boolean(error?.killed);
138
- const exitCode = error ? typeof error.code === "number" ? error.code : 1 : 0;
139
- resolve16({ exitCode, stdout, stderr, timedOut });
140
- }
141
- );
142
- });
143
- }
144
- function killTree(pid, fallback) {
145
- if (!IS_WINDOWS || pid === void 0) {
146
- fallback();
147
- return;
148
- }
149
- const killer = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
150
- killer.on("error", fallback);
151
- }
152
- function tail(output, lines = 15) {
153
- return output.trim().split(/\r?\n/).slice(-lines).join("\n");
154
- }
155
- function quoteWindows(arg) {
156
- return /^[\w.:\\/@+-]+$/.test(arg) ? arg : `"${arg}"`;
157
- }
158
-
159
- // src/adapters/npm/npm-package-manager.ts
160
- var MARKER_FILES = [
161
- ["npm", ["package-lock.json", "npm-shrinkwrap.json"]],
162
- ["pnpm", ["pnpm-lock.yaml", "pnpm-workspace.yaml"]],
163
- ["yarn", ["yarn.lock", ".yarnrc.yml"]],
164
- ["bun", ["bun.lock", "bun.lockb"]]
165
- ];
166
- var UnsupportedPackageManagerError = class extends Error {
167
- constructor(detected) {
168
- super(
169
- `This repository is managed by ${detected.name} (${detected.evidence}). Elevate supports npm and pnpm for Node.js projects; using either here would create a lockfile next to ${detected.name}'s. Nothing was changed.`
170
- );
171
- this.name = "UnsupportedPackageManagerError";
172
- }
173
- };
174
- function detectPackageManager(dir) {
175
- const boundary = findCheckoutBoundary(dir);
176
- let current = resolve2(dir);
177
- for (; ; ) {
178
- const found = detectIn(current);
179
- if (found) return found;
180
- const parent = dirname2(current);
181
- if (boundary === void 0 || current === boundary || parent === current) break;
182
- current = parent;
183
- }
184
- return { name: "npm", evidence: "no other package manager detected" };
185
- }
186
- function detectIn(dir) {
187
- const declared = readPackageManagerField(dir);
188
- const name = declared?.match(/^(npm|pnpm|yarn|bun)@/)?.[1];
189
- if (declared && name) return { name, evidence: `"packageManager": "${declared}" in package.json` };
190
- for (const [manager, files] of MARKER_FILES) {
191
- const found = files.find((file) => existsSync2(join2(dir, file)));
192
- if (found) return { name: manager, evidence: found };
193
- }
194
- return void 0;
195
- }
196
- function assertSupportedPackageManager(rootDir) {
197
- const detected = detectPackageManager(rootDir);
198
- if (detected.name === "npm" || detected.name === "pnpm") return detected.name;
199
- throw new UnsupportedPackageManagerError(detected);
200
- }
201
- var PackageManagerNotFoundError = class extends Error {
202
- constructor(reason, detected) {
203
- super(
204
- `pnpm could not be run in this repository (${reason}). This repository is managed by pnpm (${detected.evidence}); install pnpm or enable Corepack (corepack enable). Nothing was changed.`
205
- );
206
- this.name = "PackageManagerNotFoundError";
207
- }
208
- };
209
- var VERSION_TIMEOUT_MS = 15e3;
210
- var verifiedRoots = /* @__PURE__ */ new Set();
211
- async function ensurePnpmRunnable(rootDir, detected) {
212
- const key = resolve2(rootDir);
213
- if (verifiedRoots.has(key)) return;
214
- let reason;
215
- try {
216
- const result = await runCommand("pnpm", ["--version"], { cwd: rootDir, timeoutMs: VERSION_TIMEOUT_MS });
217
- if (result.timedOut) reason = "pnpm --version timed out";
218
- else if (result.exitCode !== 0) {
219
- const line = firstLine(result.stderr) || firstLine(result.stdout);
220
- reason = `pnpm --version exited with code ${result.exitCode}: ${line}`;
221
- }
222
- } catch {
223
- reason = "command not found";
224
- }
225
- if (reason !== void 0) throw new PackageManagerNotFoundError(reason, detected);
226
- verifiedRoots.add(key);
227
- }
228
- async function prepareSupportedPackageManager(rootDir) {
229
- const manager = assertSupportedPackageManager(rootDir);
230
- if (manager === "pnpm") await ensurePnpmRunnable(rootDir, detectPackageManager(rootDir));
231
- return manager;
232
- }
233
- function firstLine(text) {
234
- return text.split(/\r?\n/).find((l) => l.trim() !== "")?.trim() ?? "";
235
- }
236
- function readPackageManagerField(rootDir) {
237
- try {
238
- const field = JSON.parse(readFileSync(join2(rootDir, "package.json"), "utf8"))?.packageManager;
239
- return typeof field === "string" ? field : void 0;
240
- } catch {
241
- return void 0;
242
- }
243
- }
244
-
245
- // src/adapters/npm/npm-discovery.ts
246
- import { readFileSync as readFileSync3 } from "fs";
247
- import { dirname as dirname3, join as join4, relative, resolve as resolve3 } from "path";
248
- import mapWorkspaces from "@npmcli/map-workspaces";
249
-
250
- // src/adapters/npm/pnpm-workspace.ts
251
- import { readFileSync as readFileSync2 } from "fs";
252
- import { join as join3 } from "path";
253
- import { isMap, isScalar, parse, parseDocument } from "yaml";
254
- var PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
255
- function isRecord(value) {
256
- return typeof value === "object" && value !== null && !Array.isArray(value);
257
- }
258
- function entries(value) {
259
- const map = /* @__PURE__ */ new Map();
260
- if (isRecord(value)) {
261
- for (const [name, range] of Object.entries(value)) {
262
- if (typeof range === "string") map.set(name, range);
263
- }
264
- }
265
- return map;
266
- }
267
- function readPnpmWorkspace(rootDir) {
268
- const file = join3(rootDir, PNPM_WORKSPACE_FILE);
269
- let data;
270
- try {
271
- data = parse(readFileSync2(file, "utf8"));
272
- } catch {
273
- return void 0;
274
- }
275
- if (data !== null && !isRecord(data)) return void 0;
276
- const doc = data ?? {};
277
- const packages = Array.isArray(doc.packages) ? doc.packages.filter((p) => typeof p === "string") : [];
278
- const catalogs = /* @__PURE__ */ new Map();
279
- const named = isRecord(doc.catalogs) ? doc.catalogs : {};
280
- for (const [name, content] of Object.entries(named)) catalogs.set(name, entries(content));
281
- const hasTopLevel = isRecord(doc.catalog);
282
- const hasNamedDefault = isRecord(named.default);
283
- if (hasTopLevel) catalogs.set("default", entries(doc.catalog));
284
- return { file, packages, catalogs, ambiguousDefaultCatalog: hasTopLevel && hasNamedDefault };
285
- }
286
- function setCatalogRange(source, catalog, name, newRange) {
287
- const doc = parseDocument(source);
288
- const holders = catalog === "default" ? [doc.get("catalog", true), doc.getIn(["catalogs", "default"], true)] : [doc.getIn(["catalogs", catalog], true)];
289
- for (const holder of holders) {
290
- if (!isMap(holder)) continue;
291
- const node = holder.get(name, true);
292
- if (!isScalar(node) || !node.range) continue;
293
- const scalar = node;
294
- const [start, end] = scalar.range;
295
- const quote = scalar.type === "QUOTE_DOUBLE" ? '"' : scalar.type === "QUOTE_SINGLE" ? "'" : "";
296
- return source.slice(0, start) + quote + newRange + quote + source.slice(end);
297
- }
298
- throw new Error(`No entry for '${name}' in catalog '${catalog}' of ${PNPM_WORKSPACE_FILE}.`);
299
- }
300
-
301
- // src/adapters/npm/npm-discovery.ts
302
- function readJson(filePath) {
303
- try {
304
- return JSON.parse(readFileSync3(filePath, "utf8"));
305
- } catch {
306
- return null;
307
- }
308
- }
309
- var NpmModuleDiscoveryAdapter = class {
310
- /**
311
- * The nearest directory, from `startDir` upwards to the boundary, whose
312
- * package.json is valid JSON and declares `workspaces`, or that contains a
313
- * valid `pnpm-workspace.yaml`.
314
- */
315
- findRoot(startDir, boundaryDir) {
316
- let dir = resolve3(startDir);
317
- const boundary = boundaryDir ? resolve3(boundaryDir) : void 0;
318
- for (; ; ) {
319
- if (readJson(join4(dir, "package.json"))?.workspaces || readPnpmWorkspace(dir)) return dir;
320
- const parent = dirname3(dir);
321
- if (dir === boundary || parent === dir) return void 0;
322
- dir = parent;
323
- }
324
- }
325
- async discover(rootDir) {
326
- const rootPkg = readJson(join4(rootDir, "package.json"));
327
- const modules = [
328
- {
329
- id: rootPkg?.name ?? "root",
330
- name: rootPkg?.name ?? "Root Workspace (npm)",
331
- path: rootDir,
332
- relPath: "Root",
333
- ecosystem: "npm",
334
- isRoot: true,
335
- version: typeof rootPkg?.version === "string" ? rootPkg.version : void 0
336
- }
337
- ];
338
- const patterns = detectPackageManager(rootDir).name === "pnpm" ? readPnpmWorkspace(rootDir)?.packages : rootPkg?.workspaces;
339
- if (!patterns || Array.isArray(patterns) && patterns.length === 0) return modules;
340
- let workspaces;
341
- try {
342
- workspaces = await mapWorkspaces({ cwd: rootDir, pkg: { ...rootPkg, workspaces: patterns } });
343
- } catch {
344
- return modules;
345
- }
346
- for (const [name, path] of workspaces) {
347
- const pkg = readJson(join4(path, "package.json"));
348
- modules.push({
349
- id: name,
350
- name,
351
- path,
352
- relPath: relative(rootDir, path).replace(/\\/g, "/"),
353
- ecosystem: "npm",
354
- isRoot: false,
355
- version: typeof pkg?.version === "string" ? pkg.version : void 0
356
- });
357
- }
358
- modules.sort((a, b) => a.isRoot ? -1 : b.isRoot ? 1 : a.relPath.localeCompare(b.relPath));
359
- return modules;
360
- }
361
- };
362
-
363
- // src/adapters/npm/npm-registry.ts
364
- import semver3 from "semver";
365
-
366
- // src/domain/origin.ts
367
- function buildWorkspaceIndex(modules) {
368
- const index = /* @__PURE__ */ new Map();
369
- for (const module of modules) {
370
- if (module.id && module.id !== "empty") index.set(module.id, module);
371
- }
372
- return index;
373
- }
374
- function classifyOrigin(identifier, ecosystem, context) {
375
- const module = context.workspace.get(identifier);
376
- if (module) {
377
- return { kind: "workspace", moduleId: module.id, moduleRelPath: module.relPath };
378
- }
379
- if (matchesInternalScope(identifier, ecosystem, context.internalScopes)) {
380
- return { kind: "private", matchedBy: "internal-scope" };
381
- }
382
- if (ecosystem === "npm" && context.scopedRegistries) {
383
- const scope = npmScopeOf(identifier);
384
- const registry = scope ? context.scopedRegistries.get(scope) : void 0;
385
- if (registry) return { kind: "private", matchedBy: "scoped-registry", registry };
386
- }
387
- return { kind: "public" };
388
- }
389
- function matchesInternalScope(identifier, ecosystem, internalScopes) {
390
- if (ecosystem === "maven") {
391
- const groupId = identifier.split(":")[0] ?? "";
392
- return internalScopes.some((entry) => {
393
- const prefix = entry.replace(/^@/, "").replace(/[.:/]+$/, "");
394
- return prefix.length > 0 && (groupId === prefix || groupId.startsWith(`${prefix}.`));
395
- });
396
- }
397
- return internalScopes.some((entry) => {
398
- if (entry.startsWith("@")) {
399
- const scope = entry.replace(/\/+$/, "");
400
- return identifier.startsWith(`${scope}/`);
401
- }
402
- if (entry.endsWith("*")) return identifier.startsWith(entry.slice(0, -1));
403
- return identifier === entry;
404
- });
405
- }
406
- function npmScopeOf(name) {
407
- if (!name.startsWith("@")) return void 0;
408
- const slash = name.indexOf("/");
409
- return slash > 1 ? name.slice(0, slash) : void 0;
410
- }
411
-
412
- // src/adapters/npm/npm-commands.ts
413
- function driverFor(dir) {
414
- return detectPackageManager(dir).name === "pnpm" ? "pnpm" : "npm";
415
- }
416
- var commands = {
417
- /** Install after Elevate wrote new ranges. pnpm is told explicitly not to freeze: `CI` would otherwise imply it. */
418
- install: (m) => m === "pnpm" ? { command: "pnpm", args: ["install", "--no-frozen-lockfile"] } : { command: "npm", args: ["install"] },
419
- /** Restore the installed state after a rollback. */
420
- restore: (m) => m === "pnpm" ? { command: "pnpm", args: ["install", "--frozen-lockfile"] } : { command: "npm", args: ["install"] },
421
- /** Default verification. */
422
- verify: (m) => m === "pnpm" ? { command: "pnpm", args: ["install", "--frozen-lockfile", "--lockfile-only"] } : { command: "npm", args: ["ls"] },
423
- view: (m, name, field) => ({ command: m, args: ["view", name, field, "--json"] }),
424
- config: (m) => ({ command: m, args: ["config", "list", "--json"] })
425
- };
426
-
427
- // src/adapters/npm/npm-config.ts
428
- var DEFAULT_REGISTRY = "https://registry.npmjs.org/";
429
- var PUBLIC_NPM_HOSTS = /* @__PURE__ */ new Set(["registry.npmjs.org", "registry.npmjs.com", "registry.yarnpkg.com", "npm.jsr.io"]);
430
- var NpmConfigReader = class {
431
- cache = /* @__PURE__ */ new Map();
432
- /** Returns the registry configuration that applies in `cwd` (cached per directory). */
433
- read(cwd) {
434
- let pending = this.cache.get(cwd);
435
- if (!pending) {
436
- pending = loadConfig(cwd);
437
- this.cache.set(cwd, pending);
438
- }
439
- return pending;
440
- }
441
- /** The registry npm would contact for this package. */
442
- async registryFor(packageName, cwd) {
443
- const config = await this.read(cwd);
444
- const scope = npmScopeOf(packageName);
445
- return scope && config.scopedRegistries.get(scope) || config.registry;
446
- }
447
- };
448
- function isPublicNpmRegistry(url) {
449
- try {
450
- return PUBLIC_NPM_HOSTS.has(new URL(url).hostname.toLowerCase());
451
- } catch {
452
- return false;
453
- }
454
- }
455
- function parseNpmConfig(json) {
456
- const scopedRegistries = /* @__PURE__ */ new Map();
457
- for (const [key, value] of Object.entries(json)) {
458
- const match = key.match(/^(@[^:]+):registry$/);
459
- if (match && typeof value === "string" && value) scopedRegistries.set(match[1], value);
460
- }
461
- const registry = typeof json.registry === "string" && json.registry ? json.registry : DEFAULT_REGISTRY;
462
- return { registry, scopedRegistries };
463
- }
464
- async function loadConfig(cwd) {
465
- try {
466
- const { command, args } = commands.config(driverFor(cwd));
467
- const result = await runCommand(command, args, { cwd, timeoutMs: 15e3 });
468
- if (result.exitCode !== 0) return parseNpmConfig({});
469
- return parseNpmConfig(JSON.parse(result.stdout));
470
- } catch {
471
- return parseNpmConfig({});
472
- }
473
- }
474
-
475
- // src/adapters/npm/npm-registry.ts
476
- var LOOKUP_TIMEOUT_MS = 15e3;
477
- var PACKAGE_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i;
478
- function isValidPackageName(name) {
479
- return name.length <= 214 && PACKAGE_NAME.test(name);
480
- }
481
- var NpmRegistryAdapter = class {
482
- constructor(config = new NpmConfigReader()) {
483
- this.config = config;
484
- }
485
- config;
486
- async resolveEndpoint(coordinate, cwd) {
487
- const url = await this.config.registryFor(coordinate.identifier, cwd);
488
- return { url, isPublic: isPublicNpmRegistry(url) };
489
- }
490
- async getLatestVersion(coordinate, channel = "stable", cwd = process.cwd()) {
491
- if (!isValidPackageName(coordinate.identifier)) return null;
492
- if (channel === "stable") {
493
- const latest = await this.view(coordinate.identifier, "dist-tags.latest", cwd);
494
- if (typeof latest === "string" && semver3.valid(latest) && semver3.prerelease(latest) === null) {
495
- return latest;
496
- }
497
- const releases = (await this.getAllVersions(coordinate, cwd)).filter(
498
- (v) => semver3.valid(v) && semver3.prerelease(v) === null
499
- );
500
- return releases.length > 0 ? semver3.rsort(releases)[0] : null;
501
- }
502
- const version = await this.view(coordinate.identifier, "version", cwd);
503
- return typeof version === "string" && version ? version : null;
504
- }
505
- async getAllVersions(coordinate, cwd = process.cwd()) {
506
- if (!isValidPackageName(coordinate.identifier)) return [];
507
- const versions = await this.view(coordinate.identifier, "versions", cwd);
508
- const list = Array.isArray(versions) ? versions : versions ? [versions] : [];
509
- return list.map(String).reverse();
510
- }
511
- /** Runs `npm view <name> <field> --json` (`pnpm view` in a pnpm repository); resolves null on any failure. */
512
- async view(name, field, cwd) {
513
- try {
514
- const { command, args } = commands.view(driverFor(cwd), name, field);
515
- const result = await runCommand(command, args, {
516
- cwd,
517
- timeoutMs: LOOKUP_TIMEOUT_MS
518
- });
519
- if (result.exitCode !== 0 || !result.stdout.trim()) return null;
520
- return JSON.parse(result.stdout);
521
- } catch {
522
- return null;
523
- }
524
- }
525
- };
526
-
527
- // src/adapters/npm/npm-scanner.ts
528
- import { readFileSync as readFileSync4 } from "fs";
529
- import { join as join5 } from "path";
530
- import semver4 from "semver";
531
-
532
- // src/adapters/shared/pool.ts
533
- async function mapPooled(items, concurrency, fn) {
534
- const results = new Array(items.length);
535
- let next = 0;
536
- async function worker() {
537
- while (next < items.length) {
538
- const index = next++;
539
- results[index] = await fn(items[index]);
540
- }
541
- }
542
- const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker());
543
- await Promise.all(workers);
544
- return results;
545
- }
546
-
547
- // src/adapters/npm/npm-protocols.ts
548
- function splitRangeProtocol(range) {
549
- if (range.startsWith("workspace:")) return { protocol: "workspace", rest: range.slice("workspace:".length) };
550
- if (range.startsWith("catalog:")) return { protocol: "catalog", rest: range.slice("catalog:".length) };
551
- return { protocol: void 0, rest: range };
552
- }
553
-
554
- // src/adapters/npm/npm-scanner.ts
555
- var CONCURRENCY = 6;
556
- var DIFF_ORDER = { major: 0, minor: 1, patch: 2 };
557
- var NpmDependencyAdapter = class {
558
- constructor(registry, config) {
559
- this.registry = registry;
560
- this.config = config;
561
- }
562
- registry;
563
- config;
564
- async scan(module, context) {
565
- const manager = await prepareSupportedPackageManager(context.rootDir);
566
- const pnpm = manager === "pnpm";
567
- let pkg;
568
- try {
569
- pkg = JSON.parse(readFileSync4(join5(module.path, "package.json"), "utf8"));
570
- } catch {
571
- return { candidates: [], skipped: [] };
572
- }
573
- const npmConfig = await this.config.read(context.rootDir);
574
- const originContext = {
575
- workspace: context.workspace,
576
- internalScopes: context.internalScopes,
577
- // A scope mapped to a public registry is not internal; only private
578
- // mappings mark their scope as private.
579
- scopedRegistries: new Map(
580
- [...npmConfig.scopedRegistries].filter(([, url]) => !isPublicNpmRegistry(url))
581
- )
582
- };
583
- const workspaceFile = pnpm ? readPnpmWorkspace(context.rootDir) : void 0;
584
- const outcomes = await mapPooled(declaredDependencies(pkg), CONCURRENCY, async (dep) => {
585
- const origin = classifyOrigin(dep.name, "npm", originContext);
586
- if (origin.kind === "workspace") {
587
- const target = context.workspace.get(dep.name);
588
- if (pnpm && isUpgradableRange(dep.range) && splitRangeProtocol(dep.range).protocol === void 0) {
589
- return { skipped: { identifier: dep.name, origin: "workspace", reason: "not-linked", detail: dep.range } };
590
- }
591
- const candidate = target ? alignmentCandidate(dep, target, origin, pnpm) : null;
592
- return candidate ? { candidate } : null;
593
- }
594
- if (pnpm && splitRangeProtocol(dep.range).protocol === "catalog") {
595
- return this.catalogOutcome(dep, origin, context, module.path, workspaceFile);
596
- }
597
- return this.registryOutcome(dep, origin, context);
598
- });
599
- const candidates = [];
600
- const skipped = [];
601
- for (const outcome of outcomes) {
602
- if (!outcome) continue;
603
- if ("candidate" in outcome) candidates.push(outcome.candidate);
604
- else skipped.push(outcome.skipped);
605
- }
606
- candidates.sort((a, b) => (DIFF_ORDER[a.diff] ?? 3) - (DIFF_ORDER[b.diff] ?? 3));
607
- return { candidates, skipped };
608
- }
609
- /** A `catalog:` dependency: the range comes from pnpm-workspace.yaml and is written back there. */
610
- async catalogOutcome(dep, origin, context, modulePath, workspaceFile) {
611
- const catalogName = splitRangeProtocol(dep.range).rest || "default";
612
- const unresolved = (detail) => ({
613
- skipped: { identifier: dep.name, origin: origin.kind, reason: "catalog-unresolved", detail }
614
- });
615
- if (!workspaceFile) return unresolved("pnpm-workspace.yaml is missing or not valid YAML");
616
- if (catalogName === "default" && workspaceFile.ambiguousDefaultCatalog) {
617
- return unresolved("catalog 'default' is defined twice (catalog and catalogs.default)");
618
- }
619
- const catalog = workspaceFile.catalogs.get(catalogName);
620
- if (!catalog) return unresolved(`no catalog '${catalogName}'`);
621
- const range = catalog.get(dep.name);
622
- if (range === void 0) return unresolved(`no entry for '${dep.name}' in catalog '${catalogName}'`);
623
- const declaration = {
624
- file: workspaceFile.file,
625
- kind: "catalog",
626
- catalogName,
627
- displayPath: "pnpm-workspace.yaml"
628
- };
629
- const sharedWith = usersOfCatalogEntry(context, modulePath, dep.name, catalogName);
630
- return this.registryOutcome({ ...dep, range }, origin, context, {
631
- declaration,
632
- ...sharedWith.length > 0 ? { sharedWith } : {}
633
- });
634
- }
635
- /** Looks up a private or public dependency in its registry. */
636
- async registryOutcome(dep, origin, context, extra = {}) {
637
- if (!isUpgradableRange(dep.range)) return null;
638
- if (!isValidPackageName(dep.name)) {
639
- return { skipped: { identifier: dep.name, origin: origin.kind, reason: "invalid-name" } };
640
- }
641
- const coordinate = { identifier: dep.name, artifact: dep.name, ecosystem: "npm" };
642
- let resolvedOrigin = origin;
643
- if (origin.kind === "private") {
644
- const endpoint = await this.registry.resolveEndpoint(coordinate, context.rootDir);
645
- if (endpoint.isPublic) {
646
- return {
647
- skipped: {
648
- identifier: dep.name,
649
- origin: "private",
650
- reason: "private-on-public-registry",
651
- detail: endpoint.url
652
- }
653
- };
654
- }
655
- resolvedOrigin = { ...origin, registry: endpoint.url };
656
- }
657
- const latest = await this.registry.getLatestVersion(coordinate, context.channel, context.rootDir);
658
- if (!latest) {
659
- return { skipped: { identifier: dep.name, origin: origin.kind, reason: "lookup-failed" } };
660
- }
661
- const currentClean = semver4.minVersion(dep.range)?.version;
662
- if (!currentClean || !semver4.valid(latest) || !semver4.gt(latest, currentClean)) return null;
663
- const tag = semver4.prerelease(latest) ? extractPreReleaseTag(latest) ?? "PRE" : void 0;
664
- const diff = diffVersions(currentClean, latest);
665
- return {
666
- candidate: {
667
- coordinate,
668
- currentRange: dep.range,
669
- currentClean,
670
- latest,
671
- newRange: `${rangePrefix(dep.range)}${latest}`,
672
- diff,
673
- scope: dep.scope,
674
- selected: diff !== "major",
675
- origin: resolvedOrigin,
676
- action: "update",
677
- isPreRelease: tag !== void 0,
678
- preReleaseTag: tag,
679
- ...extra
680
- }
681
- };
682
- }
683
- };
684
- var DEPENDENCY_SECTIONS = ["dependencies", "optionalDependencies", "devDependencies"];
685
- function declaredDependencies(pkg) {
686
- const deps = /* @__PURE__ */ new Map();
687
- for (const section of DEPENDENCY_SECTIONS) {
688
- const scope = section === "devDependencies" ? "dev" : "prod";
689
- for (const [name, range] of Object.entries(pkg[section] ?? {})) {
690
- if (typeof range === "string") deps.set(name, { name, range, scope });
691
- }
692
- }
693
- return [...deps.values()];
694
- }
695
- function isUpgradableRange(range) {
696
- const trimmed = range.trim();
697
- if (trimmed === "" || trimmed === "*" || trimmed.toLowerCase() === "x") return false;
698
- return semver4.validRange(trimmed) !== null;
699
- }
700
- function alignmentCandidate(dep, target, origin, pnpm = false) {
701
- const split = pnpm ? splitRangeProtocol(dep.range) : { protocol: void 0, rest: dep.range };
702
- const range = split.protocol === "workspace" ? split.rest : dep.range;
703
- const protocol = split.protocol === "workspace" ? "workspace:" : "";
704
- const local = target.version;
705
- if (!local || !semver4.valid(local) || !isUpgradableRange(range)) return null;
706
- if (semver4.satisfies(local, range, { includePrerelease: true })) return null;
707
- const currentClean = semver4.minVersion(range)?.version ?? "0.0.0";
708
- const diff = diffVersions(currentClean, local);
709
- const tag = semver4.prerelease(local) ? extractPreReleaseTag(local) ?? "PRE" : void 0;
710
- return {
711
- coordinate: { identifier: dep.name, artifact: dep.name, ecosystem: "npm" },
712
- currentRange: dep.range,
713
- currentClean,
714
- latest: local,
715
- newRange: `${protocol}${rangePrefix(range)}${local}`,
716
- diff,
717
- scope: dep.scope,
718
- selected: diff !== "major",
719
- origin,
720
- action: "align",
721
- availableVersions: [local],
722
- isPreRelease: tag !== void 0,
723
- preReleaseTag: tag
724
- };
725
- }
726
- function usersOfCatalogEntry(context, selfPath, name, catalogName) {
727
- const users = [];
728
- for (const module of context.workspace.values()) {
729
- if (module.path === selfPath) continue;
730
- let pkg;
731
- try {
732
- pkg = JSON.parse(readFileSync4(join5(module.path, "package.json"), "utf8"));
733
- } catch {
734
- continue;
735
- }
736
- const dep = declaredDependencies(pkg).find((d) => d.name === name);
737
- if (!dep || splitRangeProtocol(dep.range).protocol !== "catalog") continue;
738
- if ((splitRangeProtocol(dep.range).rest || "default") !== catalogName) continue;
739
- users.push(module.id);
740
- }
741
- return users;
742
- }
743
-
744
- // src/adapters/npm/npm-updater.ts
745
- import { readFileSync as readFileSync7, writeFileSync } from "fs";
746
- import { join as join8, relative as relative2 } from "path";
747
-
748
- // src/domain/ports.ts
749
- var InstallError = class extends Error {
750
- constructor(message2, output) {
751
- super(message2);
752
- this.output = output;
753
- this.name = "InstallError";
754
- }
755
- output;
756
- };
757
-
758
- // src/adapters/npm/npm-lockfile.ts
759
- import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
760
- import { join as join6 } from "path";
761
- function readLockfile(rootDir) {
762
- const file = join6(rootDir, "package-lock.json");
763
- if (!existsSync3(file)) return void 0;
764
- try {
765
- return JSON.parse(readFileSync5(file, "utf8"));
766
- } catch {
767
- return void 0;
768
- }
769
- }
770
- function findUnlinkedWorkspaceDependencies(lockfile, moduleRelPath, names) {
771
- const packages = lockfile.packages ?? {};
772
- const prefix = moduleRelPath ? `${moduleRelPath}/` : "";
773
- const violations = [];
774
- for (const name of names) {
775
- const entry = packages[`${prefix}node_modules/${name}`] ?? packages[`node_modules/${name}`];
776
- if (!entry) {
777
- violations.push(`${name}: not present in package-lock.json`);
778
- } else if (!entry.link) {
779
- const source = entry.resolved ?? "unknown source";
780
- violations.push(`${name}: installed from ${source} instead of linking the workspace package`);
781
- }
782
- }
783
- return violations;
784
- }
785
-
786
- // src/adapters/npm/pnpm-audit.ts
787
- var AUDIT_TIMEOUT_MS = 6e4;
788
- var LEVELS = ["info", "low", "moderate", "high", "critical"];
789
- function unavailable(reason) {
790
- return { message: `Vulnerability check unavailable: ${reason}.`, severity: "warn" };
791
- }
792
- function parsePnpmAudit(output) {
793
- let json;
794
- try {
795
- json = JSON.parse(output);
796
- } catch {
797
- return unavailable("pnpm audit returned no result");
798
- }
799
- const counts = json?.metadata?.vulnerabilities;
800
- if (counts && typeof counts === "object") {
801
- const parts = [];
802
- let total = 0;
803
- for (const level of LEVELS) {
804
- const n = Number(counts[level]) || 0;
805
- total += n;
806
- if (n > 0) parts.push(`${n} ${level}`);
807
- }
808
- if (total === 0) return { message: "No known vulnerabilities found.", severity: "clean" };
809
- return {
810
- message: `${total} ${total === 1 ? "vulnerability" : "vulnerabilities"} (${parts.join(", ")})`,
811
- severity: "warn"
812
- };
813
- }
814
- const errorMessage = json?.error?.message;
815
- if (typeof errorMessage === "string" && errorMessage) return unavailable(errorMessage);
816
- return unavailable("pnpm audit returned no result");
817
- }
818
-
819
- // src/adapters/npm/pnpm-lockfile.ts
820
- import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
821
- import { join as join7 } from "path";
822
- import { parse as parse2 } from "yaml";
823
- var SECTIONS = ["dependencies", "devDependencies", "optionalDependencies"];
824
- function readPnpmLockfile(rootDir) {
825
- const file = join7(rootDir, "pnpm-lock.yaml");
826
- if (!existsSync4(file)) return { kind: "missing" };
827
- let data;
828
- try {
829
- data = parse2(readFileSync6(file, "utf8"));
830
- } catch {
831
- return { kind: "unreadable" };
832
- }
833
- if (typeof data !== "object" || data === null || Array.isArray(data)) return { kind: "unreadable" };
834
- const lockfileVersion = String(data.lockfileVersion ?? "");
835
- if (!/^9(\.|$)/.test(lockfileVersion)) return { kind: "unsupported", lockfileVersion };
836
- const importers = data.importers ?? {};
837
- if (typeof importers !== "object" || Array.isArray(importers)) return { kind: "unreadable" };
838
- return { kind: "ok", importers };
839
- }
840
- function findUnlinkedPnpmDependencies(lockfile, moduleRelPath, names) {
841
- switch (lockfile.kind) {
842
- case "missing":
843
- return ["pnpm-lock.yaml is missing; workspace links cannot be verified"];
844
- case "unreadable":
845
- return ["pnpm-lock.yaml could not be read; workspace links cannot be verified"];
846
- case "unsupported":
847
- return [
848
- `pnpm-lock.yaml has lockfileVersion '${lockfile.lockfileVersion}', which Elevate cannot read; workspace links cannot be verified`
849
- ];
850
- }
851
- const importer = lockfile.importers[moduleRelPath === "" ? "." : moduleRelPath] ?? {};
852
- const violations = [];
853
- for (const name of names) {
854
- let entry;
855
- for (const section of SECTIONS) {
856
- entry = importer[section]?.[name];
857
- if (entry) break;
858
- }
859
- if (!entry) violations.push(`${name}: not present in pnpm-lock.yaml`);
860
- else if (!String(entry.version).startsWith("link:")) {
861
- violations.push(`${name}: resolved to ${entry.version} instead of linking the workspace package`);
862
- }
863
- }
864
- return violations;
865
- }
866
-
867
- // src/adapters/npm/npm-updater.ts
868
- var INSTALL_TIMEOUT_MS = 10 * 6e4;
869
- function failureDetails(manager, result) {
870
- return manager === "pnpm" ? tail(`${result.stdout}
871
- ${result.stderr}`) : tail(result.stderr || result.stdout);
872
- }
873
- var NpmUpdaterAdapter = class {
874
- affectedFiles(module, rootDir) {
875
- if (detectPackageManager(rootDir).name === "pnpm") {
876
- return [
877
- join8(module.path, "package.json"),
878
- join8(rootDir, "package.json"),
879
- join8(rootDir, "pnpm-lock.yaml"),
880
- join8(rootDir, "pnpm-workspace.yaml")
881
- ];
882
- }
883
- return [
884
- join8(module.path, "package.json"),
885
- join8(rootDir, "package.json"),
886
- join8(rootDir, "package-lock.json"),
887
- join8(rootDir, "npm-shrinkwrap.json")
888
- ];
889
- }
890
- async applyUpdates(module, rootDir, updates, onProgress) {
891
- let manager;
892
- try {
893
- manager = await prepareSupportedPackageManager(rootDir);
894
- } catch (err) {
895
- if (err instanceof UnsupportedPackageManagerError || err instanceof PackageManagerNotFoundError) {
896
- throw new InstallError(err.message);
897
- }
898
- throw err;
899
- }
900
- const catalogUpdates = updates.filter((u) => u.declaration?.kind === "catalog");
901
- const manifestUpdates = updates.filter((u) => u.declaration?.kind !== "catalog");
902
- const pkgPath = join8(module.path, "package.json");
903
- let manifest;
904
- if (manifestUpdates.length > 0 || catalogUpdates.length === 0) {
905
- try {
906
- const source = readFileSync7(pkgPath, "utf8");
907
- manifest = { source, pkg: JSON.parse(source) };
908
- } catch {
909
- throw new InstallError(`Cannot read package.json in ${module.path}.`);
910
- }
911
- }
912
- if (manifest) {
913
- onProgress("Writing new versions to package.json\u2026");
914
- for (const item of manifestUpdates) {
915
- const name = item.coordinate.artifact;
916
- for (const section of DEPENDENCY_SECTIONS) {
917
- if (typeof manifest.pkg[section]?.[name] === "string") manifest.pkg[section][name] = item.newRange;
918
- }
919
- }
920
- writeFileSync(pkgPath, serializeLike(manifest.source, manifest.pkg), "utf8");
921
- }
922
- if (catalogUpdates.length > 0) {
923
- onProgress("Writing new versions to pnpm-workspace.yaml\u2026");
924
- writeCatalogRanges(catalogUpdates);
925
- }
926
- const install = commands.install(manager);
927
- onProgress(`Running ${manager} install\u2026`);
928
- const result = await runCommand(install.command, install.args, { cwd: rootDir, timeoutMs: INSTALL_TIMEOUT_MS });
929
- if (result.exitCode !== 0) {
930
- const reason = result.timedOut ? `${manager} install timed out` : `${manager} install failed`;
931
- throw new InstallError(reason, failureDetails(manager, result));
932
- }
933
- const lockName = manager === "pnpm" ? "pnpm-lock.yaml" : "package-lock.json";
934
- onProgress(`Checking workspace links in ${lockName}\u2026`);
935
- const integrityViolations = verifyAlignedLinks(manager, module, rootDir, updates);
936
- if (manager === "pnpm") {
937
- const audit2 = integrityViolations.length > 0 ? void 0 : await runPnpmAudit(rootDir);
938
- return {
939
- updatedCount: updates.length,
940
- auditMessage: audit2?.message ?? "",
941
- auditSeverity: audit2?.severity ?? "clean",
942
- fundingMessage: void 0,
943
- integrityViolations
944
- };
945
- }
946
- const combined = `${result.stdout}
947
- ${result.stderr}`;
948
- const audit = parseAuditSummary(combined);
949
- const fundingMatch = combined.match(/(\d+ packages? (?:is|are) looking for funding)/i);
950
- return {
951
- updatedCount: updates.length,
952
- auditMessage: audit.message,
953
- auditSeverity: audit.severity,
954
- fundingMessage: fundingMatch ? fundingMatch[0] : void 0,
955
- integrityViolations
956
- };
957
- }
958
- async resync(_module, rootDir, onProgress) {
959
- const name = detectPackageManager(rootDir).name;
960
- if (name !== "npm" && name !== "pnpm") return;
961
- const restore = commands.restore(name);
962
- onProgress(`Restoring node_modules (${name} install)\u2026`);
963
- const result = await runCommand(restore.command, restore.args, { cwd: rootDir, timeoutMs: INSTALL_TIMEOUT_MS });
964
- if (result.exitCode !== 0) {
965
- throw new InstallError(
966
- `${name} install failed while restoring the previous state`,
967
- name === "pnpm" ? failureDetails(name, result) : tail(result.stderr)
968
- );
969
- }
970
- }
971
- };
972
- function writeCatalogRanges(updates) {
973
- const sources = /* @__PURE__ */ new Map();
974
- for (const item of updates) {
975
- const declaration = item.declaration;
976
- try {
977
- let source = sources.get(declaration.file) ?? readFileSync7(declaration.file, "utf8");
978
- source = setCatalogRange(source, declaration.catalogName ?? "default", item.coordinate.artifact, item.newRange);
979
- sources.set(declaration.file, source);
980
- } catch (err) {
981
- throw new InstallError(err instanceof Error ? err.message : `Cannot update ${declaration.displayPath}.`);
982
- }
983
- }
984
- for (const [file, source] of sources) writeFileSync(file, source, "utf8");
985
- }
986
- async function runPnpmAudit(rootDir) {
987
- try {
988
- const result = await runCommand("pnpm", ["audit", "--json"], { cwd: rootDir, timeoutMs: AUDIT_TIMEOUT_MS });
989
- if (result.timedOut) return unavailable(`pnpm audit timed out after ${AUDIT_TIMEOUT_MS / 1e3} seconds`);
990
- return parsePnpmAudit(result.stdout);
991
- } catch {
992
- return unavailable("pnpm audit returned no result");
993
- }
994
- }
995
- var VULNERABILITY_LINE = /^(?:found\s+)?(\d+)\s+(?:(?:info|low|moderate|high|critical)\s+severity\s+)?vulnerabilit(?:y|ies)\b.*$/i;
996
- function parseAuditSummary(output) {
997
- for (const line of output.split(/\r?\n/)) {
998
- const match = line.trim().match(VULNERABILITY_LINE);
999
- if (match && Number(match[1]) > 0) return { message: line.trim(), severity: "warn" };
1000
- }
1001
- return { message: "No known vulnerabilities found.", severity: "clean" };
1002
- }
1003
- function verifyAlignedLinks(manager, module, rootDir, updates) {
1004
- const aligned = updates.filter((u) => u.origin.kind === "workspace").map((u) => u.coordinate.identifier);
1005
- if (aligned.length === 0) return [];
1006
- const moduleRelPath = relative2(rootDir, module.path).replace(/\\/g, "/");
1007
- if (manager === "pnpm") return findUnlinkedPnpmDependencies(readPnpmLockfile(rootDir), moduleRelPath, aligned);
1008
- const lockfile = readLockfile(rootDir);
1009
- if (!lockfile) return ["package-lock.json is missing; workspace links cannot be verified"];
1010
- return findUnlinkedWorkspaceDependencies(lockfile, moduleRelPath, aligned);
1011
- }
1012
- function serializeLike(original, value) {
1013
- const indent = original.match(/^[ \t]+(?=")/m)?.[0] ?? " ";
1014
- const newline = original.includes("\r\n") ? "\r\n" : "\n";
1015
- const body = JSON.stringify(value, null, indent).replace(/\n/g, newline);
1016
- return original.endsWith("\n") ? body + newline : body;
1017
- }
1018
-
1019
- // src/adapters/npm/npm-verifier.ts
1020
- var VERIFY_TIMEOUT_MS = 10 * 6e4;
1021
- var NpmVerificationAdapter = class {
1022
- async verify(_module, rootDir, options = {}) {
1023
- let manager = "npm";
1024
- if (!options.customScript) {
1025
- try {
1026
- manager = await prepareSupportedPackageManager(rootDir);
1027
- } catch (err) {
1028
- if (err instanceof UnsupportedPackageManagerError || err instanceof PackageManagerNotFoundError) {
1029
- return { status: "warn", details: err.message, label: options.customLabel || "Dependency verification" };
1030
- }
1031
- throw err;
1032
- }
1033
- }
1034
- const defaultLabel = manager === "pnpm" ? "Lockfile consistency (pnpm install --frozen-lockfile)" : "Dependency tree integrity (npm ls)";
1035
- const label = options.customLabel || options.customScript || defaultLabel;
1036
- const verify = commands.verify(manager);
1037
- const result = options.customScript ? await runShellScript(options.customScript, { cwd: rootDir, timeoutMs: VERIFY_TIMEOUT_MS }) : await runCommand(verify.command, verify.args, { cwd: rootDir, timeoutMs: VERIFY_TIMEOUT_MS });
1038
- if (result.exitCode === 0) {
1039
- const details = manager === "pnpm" && !options.customScript ? "pnpm-lock.yaml is up to date with all package.json files." : "All dependencies are installed and consistent.";
1040
- return { status: "clean", details, label };
1041
- }
1042
- const output = manager === "pnpm" && !options.customScript ? tail(`${result.stdout}
1043
- ${result.stderr}`) : tail(result.stderr || result.stdout);
1044
- return { status: "warn", details: result.timedOut ? "Verification timed out." : output, label };
1045
- }
1046
- };
1047
-
1048
- // src/adapters/npm/npm-strategy.ts
1049
- var NpmEcosystemStrategy = class {
1050
- ecosystem = "npm";
1051
- displayName = "Node / npm";
1052
- packageManagerFor(rootDir) {
1053
- return detectPackageManager(rootDir).name;
1054
- }
1055
- displayNameFor(rootDir) {
1056
- return detectPackageManager(rootDir).name === "pnpm" ? "Node / pnpm" : "Node / npm";
1057
- }
1058
- icon = "\u{1F4E6}";
1059
- manifestFile = "package.json";
1060
- versioning = npmVersionPolicy;
1061
- discovery = new NpmModuleDiscoveryAdapter();
1062
- registry = new NpmRegistryAdapter();
1063
- reader = new NpmDependencyAdapter(this.registry, this.registry.config);
1064
- updater = new NpmUpdaterAdapter();
1065
- verifier = new NpmVerificationAdapter();
1066
- getTabLabels(counts, t) {
1067
- return {
1068
- all: t.tabs.all(counts.total),
1069
- prod: t.tabs.dependencies(counts.prodCount),
1070
- dev: t.tabs.devDependencies(counts.devCount)
1071
- };
1072
- }
1073
- };
1074
-
1075
- // src/adapters/cargo/cargo-strategy.ts
1076
- import semver10 from "semver";
1077
-
1078
- // src/adapters/cargo/cargo-discovery.ts
1079
- import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync8 } from "fs";
1080
- import { dirname as dirname4, join as join9, relative as relative3, resolve as resolve4 } from "path";
1081
-
1082
- // src/adapters/cargo/cargo-command.ts
1083
- var CargoUnavailableError = class extends Error {
1084
- constructor() {
1085
- super("Cargo is not available: no `cargo` on the PATH. Install Rust (https://rustup.rs).");
1086
- this.name = "CargoUnavailableError";
1087
- }
1088
- };
1089
- async function runCargo(args, options) {
1090
- let result;
1091
- try {
1092
- result = await runCommand("cargo", args, options);
1093
- } catch (err) {
1094
- if (err.code === "ENOENT") throw new CargoUnavailableError();
1095
- throw err;
1096
- }
1097
- if (looksLikeMissingCommand(result) && !await cargoStarts(options.cwd)) throw new CargoUnavailableError();
1098
- return result;
1099
- }
1100
- function looksLikeMissingCommand(result) {
1101
- return process.platform === "win32" && result.exitCode !== 0 && !result.stdout.trim() && !/^error/m.test(result.stderr);
1102
- }
1103
- async function cargoStarts(cwd) {
1104
- try {
1105
- return (await runCommand("cargo", ["--version"], { cwd, timeoutMs: 3e4 })).exitCode === 0;
1106
- } catch {
1107
- return false;
1108
- }
1109
- }
1110
- function cargoErrors(output) {
1111
- const lines = output.split(/\r?\n/);
1112
- const errors = lines.filter((l) => /^error(\[E\d+\])?:/.test(l));
1113
- const summary = (errors.length > 0 ? errors : lines.filter((l) => l.trim()).slice(-10)).slice(0, 12);
1114
- if (/toolchain '.+' is not installed/.test(output)) {
1115
- summary.push("Install it with `rustup toolchain install` (the project pins it in rust-toolchain.toml).");
1116
- }
1117
- return summary.join("\n");
1118
- }
1119
-
1120
- // src/adapters/cargo/cargo-metadata.ts
1121
- var CargoMetadataError = class extends Error {
1122
- constructor(message2, dir) {
1123
- super(message2);
1124
- this.dir = dir;
1125
- this.name = "CargoMetadataError";
1126
- }
1127
- dir;
1128
- };
1129
- async function loadMetadata(dir) {
1130
- const result = await runCargo(["metadata", "--no-deps", "--format-version", "1"], { cwd: dir, timeoutMs: 6e4 });
1131
- if (result.exitCode !== 0) {
1132
- throw new CargoMetadataError(`cargo metadata failed in ${dir}: ${cargoErrors(result.stderr || result.stdout)}`, dir);
1133
- }
1134
- try {
1135
- return JSON.parse(result.stdout);
1136
- } catch {
1137
- throw new CargoMetadataError(`cargo metadata printed no JSON in ${dir}.`, dir);
1138
- }
1139
- }
1140
-
1141
- // src/adapters/cargo/cargo-discovery.ts
1142
- var IGNORED = /* @__PURE__ */ new Set(["target", "node_modules", "vendor", "dist", "build", "out", "src"]);
1143
- var norm = (path) => resolve4(path).replace(/\\/g, "/");
1144
- function findManifests(root) {
1145
- const found = [];
1146
- const walk = (dir, depth) => {
1147
- let entries2;
1148
- try {
1149
- entries2 = readdirSync(dir, { withFileTypes: true });
1150
- } catch {
1151
- return;
1152
- }
1153
- if (entries2.some((e) => e.isFile() && e.name === "Cargo.toml")) found.push(join9(dir, "Cargo.toml"));
1154
- for (const entry of entries2) {
1155
- if (entry.isDirectory() && !entry.name.startsWith(".") && !IGNORED.has(entry.name)) walk(join9(dir, entry.name), depth + 1);
1156
- }
1157
- };
1158
- walk(root, 0);
1159
- return found.sort((a, b) => a.split(/[\\/]/).length - b.split(/[\\/]/).length || a.localeCompare(b));
1160
- }
1161
- var declaresWorkspace = (dir) => {
1162
- try {
1163
- return /^\s*\[workspace[\].]/m.test(readFileSync8(join9(dir, "Cargo.toml"), "utf8"));
1164
- } catch {
1165
- return false;
1166
- }
1167
- };
1168
- var CargoModuleDiscoveryAdapter = class {
1169
- problems = [];
1170
- diagnostics() {
1171
- return [...this.problems];
1172
- }
1173
- /** The nearest ancestor declaring `[workspace]`; failing that, the nearest directory with a manifest. */
1174
- findRoot(startDir, boundaryDir) {
1175
- const boundary = boundaryDir ? resolve4(boundaryDir) : void 0;
1176
- const chain = [];
1177
- for (let dir = resolve4(startDir); ; ) {
1178
- chain.push(dir);
1179
- const parent = dirname4(dir);
1180
- if (dir === boundary || parent === dir) break;
1181
- dir = parent;
1182
- }
1183
- return chain.find(declaresWorkspace) ?? chain.find((d) => existsSync5(join9(d, "Cargo.toml")));
1184
- }
1185
- async discover(rootDir) {
1186
- const covered = /* @__PURE__ */ new Set();
1187
- const modules = [];
1188
- const errors = [];
1189
- this.problems = [];
1190
- for (const manifest of findManifests(rootDir)) {
1191
- if (covered.has(norm(manifest))) continue;
1192
- try {
1193
- const metadata = await loadMetadata(dirname4(manifest));
1194
- for (const pkg of metadata.packages) {
1195
- covered.add(norm(pkg.manifest_path));
1196
- const path = dirname4(pkg.manifest_path);
1197
- const relPath = relative3(rootDir, path).replace(/\\/g, "/");
1198
- modules.push({
1199
- id: pkg.name,
1200
- name: pkg.name,
1201
- path,
1202
- relPath: relPath === "" ? "Root" : relPath,
1203
- ecosystem: "cargo",
1204
- isRoot: relPath === "",
1205
- version: pkg.version,
1206
- aggregatorDir: resolve4(metadata.workspace_root)
1207
- });
1208
- }
1209
- } catch (err) {
1210
- if (err instanceof CargoUnavailableError) throw err;
1211
- if (err instanceof CargoMetadataError) {
1212
- errors.push(err);
1213
- this.problems.push(err.message);
1214
- } else throw err;
1215
- }
1216
- }
1217
- if (modules.length === 0 && errors.length > 0) throw errors[0];
1218
- modules.sort((a, b) => a.isRoot ? -1 : b.isRoot ? 1 : a.relPath.localeCompare(b.relPath));
1219
- const taken = /* @__PURE__ */ new Set();
1220
- for (const module of modules) {
1221
- if (taken.has(module.id)) module.id = `${module.name} (${module.relPath})`;
1222
- taken.add(module.id);
1223
- }
1224
- return modules;
1225
- }
1226
- };
1227
-
1228
- // src/adapters/cargo/cargo-config.ts
1229
- import { existsSync as existsSync6, readFileSync as readFileSync9 } from "fs";
1230
- import { homedir } from "os";
1231
- import { dirname as dirname5, isAbsolute, join as join10, resolve as resolve5 } from "path";
1232
- import { getStaticTOMLValue, parseTOML } from "toml-eslint-parser";
1233
- var CRATES_IO = "registry+https://github.com/rust-lang/crates.io-index";
1234
- var PUBLIC_HOSTS = /* @__PURE__ */ new Set(["index.crates.io", "static.crates.io", "crates.io", "github.com"]);
1235
- var UnsupportedSourceError = class extends Error {
1236
- constructor(detail) {
1237
- super(`Cargo is configured to use a package source Elevate cannot read (${detail}). No lookup was made.`);
1238
- this.name = "UnsupportedSourceError";
1239
- }
1240
- };
1241
- function isPublicIndex(index) {
1242
- try {
1243
- const url = new URL(index.replace(/^(sparse|registry)\+/, ""));
1244
- return PUBLIC_HOSTS.has(url.hostname.toLowerCase()) && (url.hostname !== "github.com" || url.pathname.includes("crates.io-index"));
1245
- } catch {
1246
- return false;
1247
- }
1248
- }
1249
- function merge(base, top) {
1250
- const out = { ...base };
1251
- for (const [key, value] of Object.entries(top)) {
1252
- const existing = out[key];
1253
- out[key] = value && typeof value === "object" && !Array.isArray(value) && existing && typeof existing === "object" && !Array.isArray(existing) ? merge(existing, value) : value;
1254
- }
1255
- return out;
1256
- }
1257
- function loadFile(file, depth = 0) {
1258
- let table;
1259
- try {
1260
- table = getStaticTOMLValue(parseTOML(readFileSync9(file, "utf8")));
1261
- } catch {
1262
- return {};
1263
- }
1264
- const includes = table.include;
1265
- delete table.include;
1266
- if (!Array.isArray(includes) || depth > 4) return table;
1267
- let included = {};
1268
- for (const entry of includes) {
1269
- const path = typeof entry === "string" ? entry : entry?.path;
1270
- if (typeof path === "string") included = merge(included, loadFile(isAbsolute(path) ? path : resolve5(dirname5(file), path), depth + 1));
1271
- }
1272
- return merge(included, table);
1273
- }
1274
- var CargoConfigReader = class {
1275
- env;
1276
- cache = /* @__PURE__ */ new Map();
1277
- constructor(options = {}) {
1278
- this.env = options.env ?? process.env;
1279
- }
1280
- /** The configuration as Cargo would merge it for `cwd`: nearest file first, CARGO_HOME last. */
1281
- config(cwd) {
1282
- const start = resolve5(cwd);
1283
- let cached = this.cache.get(start);
1284
- if (cached) return cached;
1285
- const files = [];
1286
- for (let dir = start; ; ) {
1287
- for (const name of ["config.toml", "config"]) {
1288
- const file = join10(dir, ".cargo", name);
1289
- if (existsSync6(file)) {
1290
- files.push(file);
1291
- break;
1292
- }
1293
- }
1294
- const parent = dirname5(dir);
1295
- if (parent === dir) break;
1296
- dir = parent;
1297
- }
1298
- const home2 = this.env.CARGO_HOME ?? join10(homedir(), ".cargo");
1299
- for (const name of ["config.toml", "config"]) {
1300
- const file = join10(home2, name);
1301
- if (existsSync6(file) && !files.includes(file)) {
1302
- files.push(file);
1303
- break;
1304
- }
1305
- }
1306
- cached = files.reduceRight((acc, file) => merge(acc, loadFile(file)), {});
1307
- this.cache.set(start, cached);
1308
- return cached;
1309
- }
1310
- /** `[source.*]` comes from files only: Cargo ignores `CARGO_SOURCE_*` variables (checked with Cargo 1.97). */
1311
- sourceField(config, source, field) {
1312
- const value = config.source?.[source]?.[field];
1313
- return typeof value === "string" ? value : void 0;
1314
- }
1315
- /**
1316
- * The index a lookup goes to. `registry` is the index URL of an alternative
1317
- * registry as Cargo reports it, or undefined for crates.io.
1318
- */
1319
- async resolve(registry, cwd) {
1320
- const config = this.config(cwd);
1321
- let name = registry === void 0 ? "crates-io" : this.sourceNamed(config, registry);
1322
- let index = registry ?? CRATES_IO;
1323
- const seen = /* @__PURE__ */ new Set();
1324
- while (name !== void 0) {
1325
- if (seen.has(name)) throw new UnsupportedSourceError(`replacement cycle at '${name}'`);
1326
- seen.add(name);
1327
- const next = this.sourceField(config, name, "replace-with");
1328
- if (next === void 0) break;
1329
- for (const kind of ["directory", "local-registry", "git"]) {
1330
- if (this.sourceField(config, next, kind) !== void 0) throw new UnsupportedSourceError(`'${next}' is a ${kind} source`);
1331
- }
1332
- const target = this.sourceField(config, next, "registry");
1333
- if (target !== void 0) {
1334
- index = target;
1335
- name = next;
1336
- if (this.sourceField(config, next, "replace-with") === void 0) break;
1337
- continue;
1338
- }
1339
- if (this.sourceField(config, next, "replace-with") === void 0) {
1340
- throw new UnsupportedSourceError(`'${next}' has no registry`);
1341
- }
1342
- name = next;
1343
- }
1344
- return { index, isPublic: isPublicIndex(index) };
1345
- }
1346
- /** Name of the `[source.*]` table whose registry is `url`, if any. */
1347
- sourceNamed(config, url) {
1348
- return Object.keys(config.source ?? {}).find((n) => this.sourceField(config, n, "registry") === url);
1349
- }
1350
- };
1351
-
1352
- // src/version.ts
1353
- import { createRequire } from "module";
1354
- var VERSION = true ? "1.7.0" : fromPackage();
1355
-
1356
- // src/adapters/cargo/cargo-index.ts
1357
- var RegistryAuthRequiredError = class extends Error {
1358
- constructor(index) {
1359
- super(`The registry ${index} requires authentication. Elevate does not handle registry credentials.`);
1360
- this.index = index;
1361
- this.name = "RegistryAuthRequiredError";
1362
- }
1363
- index;
1364
- };
1365
- var CrateNotFoundError = class extends Error {
1366
- constructor(crate, index) {
1367
- super(`'${crate}' is not in the registry ${index}.`);
1368
- this.crate = crate;
1369
- this.index = index;
1370
- this.name = "CrateNotFoundError";
1371
- }
1372
- crate;
1373
- index;
1374
- };
1375
- var UnsupportedIndexError = class extends Error {
1376
- constructor(index) {
1377
- super(`The registry index ${index} is not a sparse index; Elevate cannot read it.`);
1378
- this.index = index;
1379
- this.name = "UnsupportedIndexError";
1380
- }
1381
- index;
1382
- };
1383
- var CRATES_IO_SPARSE = "https://index.crates.io/";
1384
- var CRATES_IO_GIT = "registry+https://github.com/rust-lang/crates.io-index";
1385
- function indexPath(name) {
1386
- const n = name.toLowerCase();
1387
- if (n.length <= 2) return `${n.length}/${n}`;
1388
- if (n.length === 3) return `3/${n[0]}/${n}`;
1389
- return `${n.slice(0, 2)}/${n.slice(2, 4)}/${n}`;
1390
- }
1391
- function parseIndex(text) {
1392
- const versions = [];
1393
- for (const raw of text.split(/\r?\n/)) {
1394
- if (!raw.trim()) continue;
1395
- try {
1396
- const entry = JSON.parse(raw);
1397
- if (typeof entry.vers !== "string") continue;
1398
- versions.push({ version: entry.vers, yanked: entry.yanked === true, rustVersion: entry.rust_version });
1399
- } catch {
1400
- }
1401
- }
1402
- return versions;
1403
- }
1404
- function sparseRoot(index) {
1405
- if (index === CRATES_IO_GIT) return CRATES_IO_SPARSE;
1406
- if (index.startsWith("sparse+")) {
1407
- const url = index.slice("sparse+".length);
1408
- return url.endsWith("/") ? url : `${url}/`;
1409
- }
1410
- throw new UnsupportedIndexError(index);
1411
- }
1412
- var userAgent = () => `elevate/${VERSION} (+https://github.com/DennisRepa/Elevate)`;
1413
- function networkReason(err) {
1414
- const cause = err?.cause?.message;
1415
- return cause ?? (err instanceof Error ? err.message : String(err));
1416
- }
1417
- var CargoIndexClient = class {
1418
- constructor(fetchFn = fetch, env = process.env) {
1419
- this.fetchFn = fetchFn;
1420
- this.env = env;
1421
- }
1422
- fetchFn;
1423
- env;
1424
- memo = /* @__PURE__ */ new Map();
1425
- /** All versions of a crate in oldest-first file order. Memoised per index and crate. */
1426
- versions(index, crate) {
1427
- const key = `${index}
1428
- ${crate.toLowerCase()}`;
1429
- let pending = this.memo.get(key);
1430
- if (!pending) {
1431
- pending = this.load(index, crate);
1432
- this.memo.set(key, pending);
1433
- }
1434
- return pending;
1435
- }
1436
- async load(index, crate) {
1437
- const url = `${sparseRoot(index)}${indexPath(crate)}`;
1438
- let response;
1439
- try {
1440
- response = await this.fetchFn(url, { headers: { "user-agent": userAgent() } });
1441
- } catch (err) {
1442
- throw new Error(`Cannot reach ${url}: ${networkReason(err)}${this.proxyHint()}`);
1443
- }
1444
- if (response.status === 401 || response.status === 403) throw new RegistryAuthRequiredError(index);
1445
- if (response.status === 404) throw new CrateNotFoundError(crate, index);
1446
- if (!response.ok) throw new Error(`Registry answered ${response.status} for ${url}.`);
1447
- return parseIndex(await response.text());
1448
- }
1449
- /**
1450
- * Node's fetch ignores HTTP(S)_PROXY unless NODE_USE_ENV_PROXY=1 is set when
1451
- * the process starts (measured with Node 22.23; it cannot be switched on later).
1452
- */
1453
- proxyHint() {
1454
- const proxied = this.env.HTTPS_PROXY || this.env.https_proxy || this.env.HTTP_PROXY || this.env.http_proxy;
1455
- if (!proxied || this.env.NODE_USE_ENV_PROXY === "1") return "";
1456
- return " A proxy is configured, but Node does not use it for registry lookups unless you start Elevate with NODE_USE_ENV_PROXY=1.";
1457
- }
1458
- };
1459
-
1460
- // src/adapters/cargo/cargo-registry.ts
1461
- import semver6 from "semver";
1462
-
1463
- // src/adapters/cargo/cargo-versions.ts
1464
- import semver5 from "semver";
1465
- var rust = (v) => semver5.coerce(v)?.version;
1466
- function eligibleVersions(versions, options) {
1467
- const project = options.rustVersion ? rust(options.rustVersion) : void 0;
1468
- return versions.filter((v) => !v.yanked && semver5.valid(v.version)).filter((v) => options.channel === "all" || semver5.prerelease(v.version) === null).filter((v) => {
1469
- const needs = v.rustVersion ? rust(v.rustVersion) : void 0;
1470
- return !(project && needs && semver5.gt(needs, project));
1471
- }).map((v) => v.version).sort(semver5.rcompare);
1472
- }
1473
- function selectVersion(versions, options) {
1474
- const candidates = versions.filter((v) => !v.yanked && semver5.valid(v.version)).filter((v) => options.channel === "all" || semver5.prerelease(v.version) === null).sort((a, b) => semver5.rcompare(a.version, b.version));
1475
- const project = options.rustVersion ? rust(options.rustVersion) : void 0;
1476
- let blocked;
1477
- for (const candidate of candidates) {
1478
- const needs = candidate.rustVersion ? rust(candidate.rustVersion) : void 0;
1479
- if (project && needs && semver5.gt(needs, project)) {
1480
- blocked ??= { version: candidate.version, requires: candidate.rustVersion };
1481
- continue;
1482
- }
1483
- return { version: candidate.version, ...blocked ? { blockedByMsrv: blocked } : {} };
1484
- }
1485
- return { version: void 0, ...blocked ? { blockedByMsrv: blocked } : {} };
1486
- }
1487
-
1488
- // src/adapters/cargo/cargo-registry.ts
1489
- var CargoRegistryAdapter = class {
1490
- constructor(deps) {
1491
- this.deps = deps;
1492
- }
1493
- deps;
1494
- async resolveEndpoint(coordinate, cwd) {
1495
- const { index, isPublic } = await this.deps.endpoint(coordinate.registry, cwd);
1496
- return { url: index, isPublic };
1497
- }
1498
- async getLatestVersion(coordinate, channel = "stable", cwd = process.cwd()) {
1499
- return selectVersion(await this.load(coordinate, cwd), { channel }).version ?? null;
1500
- }
1501
- /** Published, not yanked, newest first. Lookup errors propagate so they are not mistaken for "no versions". */
1502
- async getAllVersions(coordinate, cwd = process.cwd()) {
1503
- return (await this.load(coordinate, cwd)).filter((v) => !v.yanked && semver6.valid(v.version)).map((v) => v.version).sort(semver6.rcompare);
1504
- }
1505
- async load(coordinate, cwd) {
1506
- const { index } = await this.deps.endpoint(coordinate.registry, cwd);
1507
- return this.deps.index.versions(index, coordinate.artifact);
1508
- }
1509
- };
1510
-
1511
- // src/adapters/cargo/cargo-requirement.ts
1512
- import semver7 from "semver";
1513
- var SIMPLE = /^(\^|~|=)?\s*(\d+(?:\.\d+){0,2})$/;
1514
- function toSemverRange(requirement) {
1515
- const parts = requirement.split(",").map((p) => p.trim());
1516
- if (parts.some((p) => p === "")) return null;
1517
- const converted = parts.map((part) => {
1518
- const bare = part.match(/^\d/);
1519
- return bare ? `^${part}` : part;
1520
- });
1521
- return semver7.validRange(converted.join(" ")) ? converted.join(" ") : null;
1522
- }
1523
- function satisfiesRequirement(requirement, version) {
1524
- const range = toSemverRange(requirement);
1525
- return range !== null && semver7.satisfies(version, range, { includePrerelease: false });
1526
- }
1527
- function isUpgradableRequirement(requirement) {
1528
- return SIMPLE.test(requirement.trim());
1529
- }
1530
- function requirementFloor(requirement) {
1531
- const range = toSemverRange(requirement);
1532
- return range ? semver7.minVersion(range)?.version ?? null : null;
1533
- }
1534
- function rewriteRequirement(current, target) {
1535
- const match = current.trim().match(SIMPLE);
1536
- if (!match) return target;
1537
- if (semver7.prerelease(target)) return `${match[1] ?? ""}${target}`;
1538
- const precision = match[2].split(".").length;
1539
- const [major, minor, patch] = target.split(/[-+]/)[0].split(".");
1540
- const written = [major, minor, patch].slice(0, precision).join(".");
1541
- return `${match[1] ?? ""}${written}`;
1542
- }
1543
- function cargoDiff(from, to) {
1544
- const a = semver7.parse(from);
1545
- const b = semver7.parse(to);
1546
- if (!a || !b) return "minor";
1547
- if (a.major > 0 || b.major > a.major) {
1548
- if (b.major !== a.major) return "major";
1549
- return b.minor !== a.minor ? "minor" : "patch";
1550
- }
1551
- if (a.minor > 0) {
1552
- if (b.minor !== a.minor) return "major";
1553
- return "minor";
1554
- }
1555
- return "major";
1556
- }
1557
-
1558
- // src/adapters/cargo/cargo-scanner.ts
1559
- import { readFileSync as readFileSync11 } from "fs";
1560
- import { join as join11, relative as relative4 } from "path";
1561
- import semver8 from "semver";
1562
-
1563
- // src/adapters/cargo/cargo-toml.ts
1564
- import { parseTOML as parseTOML2 } from "toml-eslint-parser";
1565
- var SECTIONS2 = {
1566
- dependencies: "dependencies",
1567
- "dev-dependencies": "dev-dependencies",
1568
- dev_dependencies: "dev-dependencies",
1569
- "build-dependencies": "build-dependencies",
1570
- build_dependencies: "build-dependencies"
1571
- };
1572
- function parseCargoManifest(source) {
1573
- const root = parseTOML2(source).body[0];
1574
- const leaves = [];
1575
- const keyPath = (key) => key.keys.map((k) => k.type === "TOMLBare" ? k.name : k.value);
1576
- const visitPair = (prefix, pair) => {
1577
- const path = [...prefix, ...keyPath(pair.key)];
1578
- leaves.push({ path, node: pair.value });
1579
- if (pair.value.type === "TOMLInlineTable") for (const inner of pair.value.body) visitPair(path, inner);
1580
- };
1581
- for (const element of root.body) {
1582
- if (element.type === "TOMLKeyValue") visitPair([], element);
1583
- else if (element.type === "TOMLTable") {
1584
- const header = keyPath(element.key);
1585
- for (const pair of element.body) visitPair(header, pair);
1586
- leaves.push({ path: header, node: element });
1587
- }
1588
- }
1589
- const byKey = /* @__PURE__ */ new Map();
1590
- for (const { path, node } of leaves) {
1591
- const located = locateSection(path);
1592
- if (!located || located.rest.length === 0) continue;
1593
- const [key, field] = located.rest;
1594
- const id = `${located.inWorkspace}|${located.target ?? ""}|${located.section}|${key}`;
1595
- let decl = byKey.get(id);
1596
- if (!decl) {
1597
- decl = { key, section: located.section, target: located.target, inWorkspace: located.inWorkspace, inherited: false };
1598
- byKey.set(id, decl);
1599
- }
1600
- if (located.rest.length === 1 && node.type === "TOMLValue" && node.kind === "string") {
1601
- decl.version = literalOf(node);
1602
- } else if (field === "version" && located.rest.length === 2 && node.type === "TOMLValue" && node.kind === "string") {
1603
- decl.version = literalOf(node);
1604
- } else if (field === "workspace" && located.rest.length === 2 && node.value === true) {
1605
- decl.inherited = true;
1606
- } else if (field === "package" && located.rest.length === 2 && node.type === "TOMLValue") {
1607
- decl.package = String(node.value);
1608
- }
1609
- }
1610
- return [...byKey.values()];
1611
- }
1612
- function literalOf(node) {
1613
- return { value: node.value, start: node.range[0] + 1, end: node.range[1] - 1 };
1614
- }
1615
- function locateSection(path) {
1616
- let i = 0;
1617
- let target;
1618
- let inWorkspace = false;
1619
- if (path[0] === "workspace" && path[1] === "dependencies") {
1620
- return { section: "dependencies", inWorkspace: true, target, rest: path.slice(2) };
1621
- }
1622
- if (path[0] === "target" && path.length >= 3) {
1623
- target = path[1];
1624
- i = 2;
1625
- }
1626
- const section = SECTIONS2[path[i] ?? ""];
1627
- if (!section) return void 0;
1628
- return { section, inWorkspace, target, rest: path.slice(i + 1) };
1629
- }
1630
-
1631
- // src/adapters/cargo/cargo-lockfile.ts
1632
- import { existsSync as existsSync7, readFileSync as readFileSync10 } from "fs";
1633
- import { getStaticTOMLValue as getStaticTOMLValue2, parseTOML as parseTOML3 } from "toml-eslint-parser";
1634
- function readLockedVersions(lockFile) {
1635
- const locked = /* @__PURE__ */ new Map();
1636
- if (!existsSync7(lockFile)) return locked;
1637
- try {
1638
- const data = getStaticTOMLValue2(parseTOML3(readFileSync10(lockFile, "utf8")));
1639
- for (const pkg of data.package ?? []) locked.set(pkg.name, [...locked.get(pkg.name) ?? [], pkg.version]);
1640
- } catch {
1641
- }
1642
- return locked;
1643
- }
1644
-
1645
- // src/adapters/cargo/cargo-scanner.ts
1646
- var CONCURRENCY2 = 6;
1647
- var DIFF_ORDER2 = { major: 0, minor: 1, patch: 2 };
1648
- var CargoDependencyAdapter = class {
1649
- constructor(deps) {
1650
- this.deps = deps;
1651
- }
1652
- deps;
1653
- async scan(module, context) {
1654
- const metadata = await this.deps.loadMetadata(module.aggregatorDir ?? module.path);
1655
- const pkg = metadata.packages.find((p) => samePath(join11(p.manifest_path, ".."), module.path));
1656
- if (!pkg) return { candidates: [], skipped: [] };
1657
- const own = readDeclarations(pkg.manifest_path);
1658
- const locks = readLockedVersions(join11(metadata.workspace_root, "Cargo.lock"));
1659
- const rootManifest = join11(metadata.workspace_root, "Cargo.toml");
1660
- const root = readDeclarations(rootManifest);
1661
- const groups = groupDependencies(pkg.dependencies);
1662
- const outcomes = await mapPooled(groups, CONCURRENCY2, async (group) => {
1663
- const primary = group.find((d) => d.kind === null) ?? group[0];
1664
- const key = primary.rename ?? primary.name;
1665
- const decl = findDeclaration(own, key, primary);
1666
- const located = locate(decl, root, key, context.rootDir, pkg.manifest_path, rootManifest);
1667
- const scope = group.every((d) => d.kind === "dev") ? "dev" : "prod";
1668
- const qualifier = primary.target ?? (primary.kind === "build" ? "build" : void 0);
1669
- const common = { scope, qualifier };
1670
- if (primary.source === null && primary.path) {
1671
- const target = [...context.workspace.values()].find((m) => samePath(m.path, primary.path));
1672
- return target ? alignment(primary, target, located, common) : null;
1673
- }
1674
- if (!primary.source || primary.source.startsWith("git+")) return null;
1675
- const outcome = await this.registryOutcome(primary, located, pkg, metadata, locks, common, context);
1676
- const other = group.find((d) => d !== primary && !sameRequirement(d.req, primary.req));
1677
- if (outcome && "candidate" in outcome && other) {
1678
- outcome.note = {
1679
- identifier: primary.name,
1680
- origin: outcome.candidate.origin.kind,
1681
- reason: "declaration-mismatch",
1682
- detail: `${sectionOf(other)} requires ${other.req}; it is not changed`
1683
- };
1684
- }
1685
- return outcome;
1686
- });
1687
- const candidates = [];
1688
- const skipped = [];
1689
- for (const outcome of outcomes) {
1690
- if (!outcome) continue;
1691
- if ("candidate" in outcome) {
1692
- candidates.push(outcome.candidate);
1693
- if (outcome.note) skipped.push(outcome.note);
1694
- } else skipped.push(outcome.skipped);
1695
- }
1696
- candidates.sort((a, b) => (DIFF_ORDER2[a.diff] ?? 3) - (DIFF_ORDER2[b.diff] ?? 3));
1697
- return { candidates, skipped };
1698
- }
1699
- async registryOutcome(dep, located, pkg, metadata, locks, common, context) {
1700
- const literal = located?.version.value ?? dep.req;
1701
- if (!isUpgradableRequirement(literal)) return null;
1702
- if (!located || !sameRequirement(located.version.value, dep.req)) {
1703
- return { skipped: { identifier: dep.name, origin: "public", reason: "declaration-mismatch", detail: `${literal} in the manifest, ${dep.req} for Cargo` } };
1704
- }
1705
- let origin = { kind: "public" };
1706
- if (dep.registry) origin = { kind: "private", matchedBy: "scoped-registry", registry: dep.registry };
1707
- else if (matchesInternalScope(dep.name, "cargo", context.internalScopes)) origin = { kind: "private", matchedBy: "internal-scope" };
1708
- const endpoint = await this.deps.endpoint(dep.registry ?? void 0, context.rootDir);
1709
- if (origin.kind === "private" && endpoint.isPublic) {
1710
- return { skipped: { identifier: dep.name, origin: "private", reason: "private-on-public-registry", detail: endpoint.index } };
1711
- }
1712
- let versions;
1713
- try {
1714
- versions = await this.deps.index.versions(endpoint.index, dep.name);
1715
- } catch (err) {
1716
- return { skipped: { identifier: dep.name, origin: origin.kind, reason: "lookup-failed", detail: err instanceof Error ? err.message : String(err) } };
1717
- }
1718
- const chosen = selectVersion(versions, { channel: context.channel, rustVersion: pkg.rust_version ?? void 0 });
1719
- const floor = requirementFloor(literal);
1720
- if (!chosen.version) {
1721
- if (chosen.blockedByMsrv && floor) {
1722
- const { version, requires } = chosen.blockedByMsrv;
1723
- return { skipped: { identifier: dep.name, origin: origin.kind, reason: "incompatible-rust-version", detail: ` (${version} needs Rust ${requires})` } };
1724
- }
1725
- return { skipped: { identifier: dep.name, origin: origin.kind, reason: "lookup-failed", detail: "no usable version in the registry" } };
1726
- }
1727
- const latest = chosen.version;
1728
- if (!floor || !semver8.gt(latest, floor)) {
1729
- const held = chosen.blockedByMsrv;
1730
- if (floor && held && semver8.gt(held.version, floor)) {
1731
- return { skipped: { identifier: dep.name, origin: origin.kind, reason: "incompatible-rust-version", detail: ` (${held.version} needs Rust ${held.requires})` } };
1732
- }
1733
- return yankedLock(dep, literal, origin, versions, locks);
1734
- }
1735
- const newRange = rewriteRequirement(literal, latest);
1736
- if (newRange === literal) return lockOutcome(dep, literal, origin, versions, pkg, metadata, locks, common, context);
1737
- const tag = semver8.prerelease(latest) ? extractPreReleaseTag(latest) ?? "PRE" : void 0;
1738
- const diff = cargoDiff(floor, latest);
1739
- const candidate = {
1740
- coordinate: { identifier: dep.name, artifact: dep.name, ecosystem: "cargo", ...dep.registry ? { registry: dep.registry } : {} },
1741
- currentRange: literal,
1742
- currentClean: floor,
1743
- latest,
1744
- newRange,
1745
- diff,
1746
- scope: common.scope,
1747
- selected: diff !== "major",
1748
- origin,
1749
- action: "update",
1750
- declaration: located.declaration,
1751
- isPreRelease: tag !== void 0,
1752
- preReleaseTag: tag,
1753
- ...common.qualifier ? { qualifier: common.qualifier } : {},
1754
- ...located.inherited ? { sharedWith: sharersOf(metadata, pkg, dep.rename ?? dep.name) } : {}
1755
- };
1756
- return { candidate };
1757
- }
1758
- };
1759
- function lockOutcome(dep, requirement, origin, versions, pkg, metadata, locks, common, context) {
1760
- const locked = (locks.get(dep.name) ?? []).filter((v) => satisfiesRequirement(requirement, v)).sort(semver8.rcompare)[0];
1761
- if (!locked) return null;
1762
- const options = { channel: context.channel, rustVersion: pkg.rust_version ?? void 0 };
1763
- const allowed = versions.filter((v) => satisfiesRequirement(requirement, v.version));
1764
- const newer = eligibleVersions(allowed, options).filter((v) => semver8.gt(v, locked));
1765
- const latest = newer[0];
1766
- if (!latest) return yankedLock(dep, requirement, origin, versions, locks);
1767
- const lockFile = join11(metadata.workspace_root, "Cargo.lock");
1768
- return {
1769
- candidate: {
1770
- coordinate: { identifier: dep.name, artifact: dep.name, ecosystem: "cargo", ...dep.registry ? { registry: dep.registry } : {} },
1771
- currentRange: locked,
1772
- currentClean: locked,
1773
- latest,
1774
- newRange: latest,
1775
- diff: cargoDiff(locked, latest),
1776
- scope: common.scope,
1777
- // A release the requirement allows is compatible by definition, so it is safe to preselect.
1778
- selected: true,
1779
- origin,
1780
- action: "lock",
1781
- declaration: { file: lockFile, kind: "lockfile", displayPath: display(context.rootDir, lockFile) },
1782
- availableVersions: newer,
1783
- ...common.qualifier ? { qualifier: common.qualifier } : {}
1784
- }
1785
- };
1786
- }
1787
- function yankedLock(dep, requirement, origin, versions, locks) {
1788
- const locked = (locks.get(dep.name) ?? []).filter((v) => satisfiesRequirement(requirement, v)).sort(semver8.rcompare)[0];
1789
- if (!locked || !versions.some((v) => v.yanked && v.version === locked)) return null;
1790
- return { skipped: { identifier: dep.name, origin: origin.kind, reason: "locked-version-yanked", detail: locked } };
1791
- }
1792
- function alignment(dep, target, located, common) {
1793
- const local = target.version;
1794
- if (!located || !local || !semver8.valid(local)) return null;
1795
- const literal = located.version.value;
1796
- if (!isUpgradableRequirement(literal) || satisfiesRequirement(literal, local)) return null;
1797
- const floor = requirementFloor(literal) ?? "0.0.0";
1798
- const diff = cargoDiff(floor, local);
1799
- const tag = semver8.prerelease(local) ? extractPreReleaseTag(local) ?? "PRE" : void 0;
1800
- return {
1801
- candidate: {
1802
- coordinate: { identifier: dep.name, artifact: dep.name, ecosystem: "cargo" },
1803
- currentRange: literal,
1804
- currentClean: floor,
1805
- latest: local,
1806
- newRange: rewriteRequirement(literal, local),
1807
- diff,
1808
- scope: common.scope,
1809
- selected: diff !== "major",
1810
- origin: { kind: "workspace", moduleId: target.id, moduleRelPath: target.relPath },
1811
- action: "align",
1812
- declaration: located.declaration,
1813
- availableVersions: [local],
1814
- isPreRelease: tag !== void 0,
1815
- preReleaseTag: tag,
1816
- ...common.qualifier ? { qualifier: common.qualifier } : {}
1817
- }
1818
- };
1819
- }
1820
- function groupDependencies(deps) {
1821
- const groups = /* @__PURE__ */ new Map();
1822
- for (const dep of deps) {
1823
- const id = `${dep.rename ?? dep.name}`;
1824
- groups.set(id, [...groups.get(id) ?? [], dep]);
1825
- }
1826
- return [...groups.values()];
1827
- }
1828
- function readDeclarations(manifestPath) {
1829
- try {
1830
- return parseCargoManifest(readFileSync11(manifestPath, "utf8"));
1831
- } catch {
1832
- return [];
1833
- }
1834
- }
1835
- var sectionOf = (dep) => dep.kind === "dev" ? "dev-dependencies" : dep.kind === "build" ? "build-dependencies" : "dependencies";
1836
- function findDeclaration(decls, key, dep) {
1837
- const candidates = decls.filter((d) => !d.inWorkspace && d.key === key);
1838
- return candidates.find((d) => d.section === sectionOf(dep) && (d.target ?? null) === dep.target) ?? candidates[0];
1839
- }
1840
- function locate(decl, root, key, repoRoot, manifestPath, rootManifest) {
1841
- if (!decl) return void 0;
1842
- if (decl.inherited) {
1843
- const shared = root.find((d) => d.inWorkspace && d.key === key);
1844
- if (!shared?.version) return void 0;
1845
- return {
1846
- version: shared.version,
1847
- inherited: true,
1848
- declaration: { file: rootManifest, kind: "dependency-management", displayPath: display(repoRoot, rootManifest) }
1849
- };
1850
- }
1851
- if (!decl.version) return void 0;
1852
- return {
1853
- version: decl.version,
1854
- inherited: false,
1855
- declaration: { file: manifestPath, kind: "dependency", displayPath: display(repoRoot, manifestPath) }
1856
- };
1857
- }
1858
- function sharersOf(metadata, self, key) {
1859
- return metadata.packages.filter((p) => p !== self).filter((p) => readDeclarations(p.manifest_path).some((d) => d.key === key && d.inherited)).map((p) => p.name);
1860
- }
1861
- var display = (repoRoot, file) => relative4(repoRoot, file).replace(/\\/g, "/");
1862
- var samePath = (a, b) => relative4(a, b) === "";
1863
- function sameRequirement(literal, req) {
1864
- const a = toSemverRange(literal);
1865
- const b = toSemverRange(req);
1866
- return a !== null && b !== null && semver8.validRange(a) === semver8.validRange(b);
1867
- }
1868
-
1869
- // src/adapters/cargo/cargo-updater.ts
1870
- import { existsSync as existsSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync2 } from "fs";
1871
- import { join as join12, resolve as resolve6 } from "path";
1872
- import semver9 from "semver";
1873
-
1874
- // src/adapters/shared/xml.ts
1875
- import { SaxesParser } from "saxes";
1876
- var XmlParseError = class extends Error {
1877
- constructor(message2, file) {
1878
- super(file ? `${file}: ${message2}` : message2);
1879
- this.file = file;
1880
- this.name = "XmlParseError";
1881
- }
1882
- file;
1883
- };
1884
- function parseXml(source, file) {
1885
- const parser = new SaxesParser({ position: true });
1886
- const stack = [];
1887
- const openEnds = [];
1888
- let root;
1889
- let failure;
1890
- parser.on("error", (err) => {
1891
- failure ??= err;
1892
- });
1893
- parser.on("opentag", (tag) => {
1894
- const attributes = {};
1895
- for (const [key, value] of Object.entries(tag.attributes)) {
1896
- attributes[key] = typeof value === "string" ? value : value.value;
1897
- }
1898
- const element = {
1899
- name: localName(tag.name),
1900
- attributes,
1901
- children: [],
1902
- text: ""
1903
- };
1904
- const parent = stack[stack.length - 1];
1905
- if (parent) parent.children.push(element);
1906
- else root ??= element;
1907
- stack.push(element);
1908
- openEnds.push(parser.position);
1909
- });
1910
- parser.on("closetag", (tag) => {
1911
- const element = stack.pop();
1912
- const openEnd = openEnds.pop();
1913
- if (!element || openEnd === void 0 || tag.isSelfClosing) return;
1914
- const closeEnd = parser.position;
1915
- const closeStart = source.lastIndexOf("</", closeEnd - 1);
1916
- if (closeStart >= openEnd) {
1917
- element.contentRange = { start: openEnd, end: closeStart };
1918
- }
1919
- });
1920
- parser.on("text", (text) => {
1921
- const current = stack[stack.length - 1];
1922
- if (current) current.text += text;
1923
- });
1924
- parser.on("cdata", (data) => {
1925
- const current = stack[stack.length - 1];
1926
- if (current) current.text += data;
1927
- });
1928
- try {
1929
- parser.write(source).close();
1930
- } catch (err) {
1931
- failure ??= err;
1932
- }
1933
- if (failure) throw new XmlParseError(failure.message, file);
1934
- if (!root) throw new XmlParseError("document has no root element", file);
1935
- return root;
1936
- }
1937
- function child(element, name) {
1938
- return element?.children.find((c) => c.name === name);
1939
- }
1940
- function children(element, name) {
1941
- return element ? element.children.filter((c) => c.name === name) : [];
1942
- }
1943
- function childAt(element, ...path) {
1944
- let current = element;
1945
- for (const name of path) current = child(current, name);
1946
- return current;
1947
- }
1948
- function childText(element, name) {
1949
- const value = child(element, name)?.text.trim();
1950
- return value ? value : void 0;
1951
- }
1952
- var StaleEditError = class extends Error {
1953
- constructor(message2) {
1954
- super(message2);
1955
- this.name = "StaleEditError";
1956
- }
1957
- };
1958
- function applyTextEdits(source, edits) {
1959
- const splices = edits.map((edit) => {
1960
- const range = edit.element.contentRange;
1961
- if (!range) {
1962
- throw new StaleEditError(`<${edit.element.name}> has no editable text content.`);
1963
- }
1964
- const raw = source.slice(range.start, range.end);
1965
- const leading = raw.length - raw.trimStart().length;
1966
- const value = raw.trim();
1967
- if (value !== edit.expected) {
1968
- throw new StaleEditError(
1969
- `<${edit.element.name}> contains '${value}', expected '${edit.expected}'. The file changed since it was scanned.`
1970
- );
1971
- }
1972
- const start = range.start + leading;
1973
- return { start, end: start + value.length, replacement: edit.replacement };
1974
- });
1975
- splices.sort((a, b) => b.start - a.start);
1976
- for (let i = 1; i < splices.length; i++) {
1977
- if (splices[i].end > splices[i - 1].start) {
1978
- throw new StaleEditError("Overlapping edits on the same element.");
1979
- }
1980
- }
1981
- let result = source;
1982
- for (const splice of splices) {
1983
- result = result.slice(0, splice.start) + splice.replacement + result.slice(splice.end);
1984
- }
1985
- return result;
1986
- }
1987
- function escapeXml(value) {
1988
- return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1989
- }
1990
- function serializeXml(element, indent = "") {
1991
- const attrs = Object.entries(element.attributes).map(([key, value]) => ` ${key}="${escapeXml(value).replace(/"/g, "&quot;")}"`).join("");
1992
- if (element.children.length === 0) {
1993
- return `${indent}<${element.name}${attrs}>${escapeXml(element.text.trim())}</${element.name}>`;
1994
- }
1995
- const inner = element.children.map((c) => serializeXml(c, `${indent} `)).join("\n");
1996
- return `${indent}<${element.name}${attrs}>
1997
- ${inner}
1998
- ${indent}</${element.name}>`;
1999
- }
2000
- function localName(qualified) {
2001
- const colon = qualified.indexOf(":");
2002
- return colon >= 0 ? qualified.slice(colon + 1) : qualified;
2003
- }
2004
-
2005
- // src/adapters/shared/text-edit.ts
2006
- function applyRangeEdits(source, edits) {
2007
- for (const edit of edits) {
2008
- const found = source.slice(edit.start, edit.end);
2009
- if (found !== edit.expected) {
2010
- throw new StaleEditError(`Found '${found}', expected '${edit.expected}'. The file changed since it was scanned.`);
2011
- }
2012
- }
2013
- const ordered = [...edits].sort((a, b) => b.start - a.start);
2014
- for (let i = 1; i < ordered.length; i++) {
2015
- if (ordered[i].end > ordered[i - 1].start) throw new StaleEditError("Overlapping edits on the same text.");
2016
- }
2017
- let result = source;
2018
- for (const edit of ordered) result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);
2019
- return result;
2020
- }
2021
-
2022
- // src/adapters/cargo/cargo-updater.ts
2023
- var UPDATE_TIMEOUT_MS = 10 * 6e4;
2024
- var workspaceDir = (module, rootDir) => module.aggregatorDir ?? module.path ?? rootDir;
2025
- var CargoUpdaterAdapter = class {
2026
- affectedFiles(module, rootDir, updates) {
2027
- const files = new Set(updates.map((u) => resolve6(u.declaration?.file ?? join12(module.path, "Cargo.toml"))));
2028
- files.add(join12(workspaceDir(module, rootDir), "Cargo.lock"));
2029
- return [...files];
2030
- }
2031
- async applyUpdates(module, rootDir, updates, onProgress) {
2032
- const dir = workspaceDir(module, rootDir);
2033
- const lockFile = join12(dir, "Cargo.lock");
2034
- const lockedBefore = readLockedVersions(lockFile);
2035
- const lockUpdates = updates.filter((u) => u.action === "lock");
2036
- updates = updates.filter((u) => u.action !== "lock");
2037
- this.checkLockUpdates(lockUpdates, lockFile, lockedBefore);
2038
- const byFile = /* @__PURE__ */ new Map();
2039
- for (const update of updates) {
2040
- const file = resolve6(update.declaration?.file ?? join12(module.path, "Cargo.toml"));
2041
- byFile.set(file, [...byFile.get(file) ?? [], update]);
2042
- }
2043
- const writes = [];
2044
- for (const [file, fileUpdates] of byFile) {
2045
- onProgress(`Preparing ${fileUpdates.length} change(s) in ${file}\u2026`);
2046
- const source = readFileSync12(file, "utf8");
2047
- const decls = parseCargoManifest(source);
2048
- const edits = [];
2049
- for (const update of fileUpdates) {
2050
- const inWorkspace = update.declaration?.kind === "dependency-management";
2051
- const name = update.coordinate.identifier;
2052
- const found = decls.filter(
2053
- (d) => d.inWorkspace === inWorkspace && (d.package ?? d.key) === name && d.version?.value === update.currentRange
2054
- );
2055
- if (found.length === 0) {
2056
- throw new InstallError(`${file}: cannot find the version of ${name} (${update.currentRange}) any more; rescan the module.`);
2057
- }
2058
- for (const d of found) {
2059
- edits.push({ start: d.version.start, end: d.version.end, expected: d.version.value, replacement: update.newRange });
2060
- }
2061
- }
2062
- try {
2063
- writes.push({ file, content: applyRangeEdits(source, edits) });
2064
- } catch (err) {
2065
- if (err instanceof StaleEditError) throw new InstallError(`${file}: ${err.message}`);
2066
- throw err;
2067
- }
2068
- }
2069
- onProgress("Writing Cargo.toml files\u2026");
2070
- for (const write of writes) writeFileSync2(write.file, write.content, "utf8");
2071
- const registryUpdates = updates.filter((u) => u.action === "update");
2072
- if (existsSync8(lockFile) && registryUpdates.length > 0) {
2073
- await this.refreshLock(dir, lockFile, lockedBefore, registryUpdates, onProgress);
2074
- }
2075
- for (const update of lockUpdates) {
2076
- const name = update.coordinate.identifier;
2077
- onProgress(`Locking ${name} at ${update.latest}\u2026`);
2078
- await this.cargoUpdate(dir, ["-p", `${name}@${update.currentClean}`, "--precise", update.latest]);
2079
- }
2080
- const parts = [];
2081
- if (updates.length > 0) {
2082
- parts.push(`Updated ${updates.length} requirement(s) in ${writes.length} Cargo.toml file(s)${existsSync8(lockFile) ? "; Cargo.lock refreshed" : ""}`);
2083
- }
2084
- if (lockUpdates.length > 0) parts.push(`locked ${lockUpdates.length} crate(s) at a newer version in Cargo.lock`);
2085
- const message2 = parts.join("; ");
2086
- return {
2087
- updatedCount: updates.length + lockUpdates.length,
2088
- auditMessage: `${message2.charAt(0).toUpperCase()}${message2.slice(1)}.`,
2089
- auditSeverity: "clean",
2090
- integrityViolations: []
2091
- };
2092
- }
2093
- /** A lock-only update needs a lock file that still holds the version the scan saw. */
2094
- checkLockUpdates(lockUpdates, lockFile, locked) {
2095
- if (lockUpdates.length === 0) return;
2096
- if (!existsSync8(lockFile)) {
2097
- throw new InstallError(`${lockFile} does not exist any more; rescan the module.`);
2098
- }
2099
- for (const update of lockUpdates) {
2100
- const name = update.coordinate.identifier;
2101
- if (!(locked.get(name) ?? []).includes(update.currentClean)) {
2102
- throw new InstallError(`Cargo.lock no longer locks ${name} at ${update.currentClean}; rescan the module.`);
2103
- }
2104
- }
2105
- }
2106
- async resync() {
2107
- }
2108
- async refreshLock(dir, lockFile, before, updates, onProgress) {
2109
- const specs = [];
2110
- for (const update of updates) {
2111
- const name = update.coordinate.identifier;
2112
- const versions = before.get(name) ?? [];
2113
- const old = versions.find((v) => satisfiesRequirement(update.currentRange, v)) ?? versions[0];
2114
- if (old) specs.push(`${name}@${old}`);
2115
- }
2116
- if (specs.length === 0) return;
2117
- onProgress("Updating Cargo.lock\u2026");
2118
- await this.cargoUpdate(dir, specs.flatMap((s) => ["-p", s]));
2119
- const after = readLockedVersions(lockFile);
2120
- for (const update of updates) {
2121
- const name = update.coordinate.identifier;
2122
- const locked = (after.get(name) ?? []).find((v) => satisfiesRequirement(update.newRange, v));
2123
- if (locked && semver9.valid(update.latest) && locked !== update.latest) {
2124
- onProgress(`Pinning ${name} to ${update.latest}\u2026`);
2125
- await this.cargoUpdate(dir, ["-p", `${name}@${locked}`, "--precise", update.latest]);
2126
- }
2127
- }
2128
- }
2129
- async cargoUpdate(dir, args) {
2130
- const result = await runCommand("cargo", ["update", ...args], { cwd: dir, timeoutMs: UPDATE_TIMEOUT_MS });
2131
- if (result.exitCode !== 0) {
2132
- const reason = result.timedOut ? "cargo update timed out" : "cargo update failed";
2133
- throw new InstallError(reason, tail(result.stderr || result.stdout));
2134
- }
2135
- }
2136
- };
2137
-
2138
- // src/adapters/cargo/cargo-verifier.ts
2139
- import { existsSync as existsSync9 } from "fs";
2140
- import { join as join13, relative as relative5, resolve as resolve7 } from "path";
2141
- var VERIFY_TIMEOUT_MS2 = 30 * 6e4;
2142
- var same = (a, b) => relative5(resolve7(a), resolve7(b)) === "";
2143
- function planCheck(metadata, module, changedFiles, locked) {
2144
- const args = ["check", "--all-targets", ...locked ? ["--locked"] : []];
2145
- const rootManifest = join13(metadata.workspace_root, "Cargo.toml");
2146
- const lockFile = join13(metadata.workspace_root, "Cargo.lock");
2147
- const memberManifests = new Set(metadata.packages.map((p) => resolve7(p.manifest_path)));
2148
- if (changedFiles.some((f) => same(f, rootManifest))) {
2149
- const isVirtualRoot = !memberManifests.has(resolve7(rootManifest));
2150
- if (isVirtualRoot || metadata.packages.length > 1) return [...args, "--workspace"];
2151
- }
2152
- if (changedFiles.length > 0 && changedFiles.every((f) => same(f, lockFile))) return [...args, "--workspace"];
2153
- const changed = new Set(
2154
- metadata.packages.filter((p) => changedFiles.some((f) => same(f, p.manifest_path))).map((p) => p.name)
2155
- );
2156
- if (changed.size === 0) changed.add(module.id);
2157
- for (let grew = true; grew; ) {
2158
- grew = false;
2159
- for (const p of metadata.packages) {
2160
- if (changed.has(p.name)) continue;
2161
- if (p.dependencies.some((d) => d.source === null && d.path && changed.has(d.name))) {
2162
- changed.add(p.name);
2163
- grew = true;
2164
- }
2165
- }
2166
- }
2167
- const ordered = metadata.packages.filter((p) => changed.has(p.name)).map((p) => p.name);
2168
- return [...args, ...ordered.flatMap((name) => ["-p", name])];
2169
- }
2170
- var CargoVerificationAdapter = class {
2171
- constructor(deps = { loadMetadata, hasLock: (dir) => existsSync9(join13(dir, "Cargo.lock")) }) {
2172
- this.deps = deps;
2173
- }
2174
- deps;
2175
- async verify(module, rootDir, options = {}) {
2176
- const dir = module.aggregatorDir ?? module.path ?? rootDir;
2177
- if (options.customScript) {
2178
- const label2 = options.customLabel || options.customScript;
2179
- const result2 = await runShellScript(options.customScript, { cwd: dir, timeoutMs: VERIFY_TIMEOUT_MS2 });
2180
- return result2.exitCode === 0 ? { status: "clean", details: "Verification script succeeded.", label: label2 } : { status: "warn", details: tail(result2.stderr || result2.stdout), label: label2 };
2181
- }
2182
- const metadata = await this.deps.loadMetadata(dir);
2183
- const args = planCheck(metadata, module, options.changedFiles ?? [], this.deps.hasLock(dir));
2184
- const label = `Cargo build (cargo ${args.join(" ")})`;
2185
- const result = await runCargo(args, { cwd: dir, timeoutMs: VERIFY_TIMEOUT_MS2 });
2186
- if (result.exitCode === 0) return { status: "clean", details: "All affected crates type-check, including tests.", label };
2187
- return {
2188
- status: "warn",
2189
- details: result.timedOut ? "cargo check timed out." : cargoErrors(`${result.stderr}
2190
- ${result.stdout}`),
2191
- label
2192
- };
2193
- }
2194
- };
2195
-
2196
- // src/adapters/cargo/cargo-strategy.ts
2197
- var cargoVersionPolicy = {
2198
- normalize: (v) => semver10.valid(v),
2199
- rewrite: rewriteRequirement,
2200
- diff: (current, target) => semver10.valid(current) && semver10.valid(target) ? semver10.eq(current, target) ? "patch" : cargoDiff(current, target) : "minor",
2201
- // Updates are written where the scan found the declaration.
2202
- allowsManualTargets: false
2203
- };
2204
- var CargoEcosystemStrategy = class {
2205
- ecosystem = "cargo";
2206
- displayName = "Rust / Cargo";
2207
- icon = "\u{1F980}";
2208
- manifestFile = "Cargo.toml";
2209
- versioning = cargoVersionPolicy;
2210
- config = new CargoConfigReader();
2211
- index = new CargoIndexClient();
2212
- endpoint = (registry, cwd) => this.config.resolve(registry, cwd);
2213
- discovery = new CargoModuleDiscoveryAdapter();
2214
- registry = new CargoRegistryAdapter({ index: this.index, endpoint: this.endpoint });
2215
- reader = new CargoDependencyAdapter({ loadMetadata, index: this.index, endpoint: this.endpoint });
2216
- updater = new CargoUpdaterAdapter();
2217
- verifier = new CargoVerificationAdapter();
2218
- getTabLabels(counts, t) {
2219
- return {
2220
- all: t.tabs.all(counts.total),
2221
- prod: t.tabs.dependencies(counts.prodCount),
2222
- dev: t.tabs.devDependencies(counts.devCount)
2223
- };
2224
- }
2225
- };
2226
-
2227
- // src/adapters/maven/maven-discovery.ts
2228
- import { existsSync as existsSync12 } from "fs";
2229
- import { dirname as dirname7, join as join16, relative as relative6, resolve as resolve10 } from "path";
2230
-
2231
- // src/adapters/maven/maven-project.ts
2232
- import { existsSync as existsSync11, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
2233
- import { join as join15, resolve as resolve9 } from "path";
2234
-
2235
- // src/adapters/maven/maven-pom.ts
2236
- import { existsSync as existsSync10, readFileSync as readFileSync13, statSync } from "fs";
2237
- import { dirname as dirname6, join as join14, resolve as resolve8 } from "path";
2238
- function readPom(file) {
2239
- const source = readFileSync13(file, "utf8");
2240
- return parsePom(source, file);
2241
- }
2242
- function parsePom(source, file) {
2243
- const project = parseXml(source, file);
2244
- const parentElement = child(project, "parent");
2245
- const parent = parentElement ? {
2246
- groupId: childText(parentElement, "groupId") ?? "",
2247
- artifactId: childText(parentElement, "artifactId") ?? "",
2248
- version: childText(parentElement, "version") ?? "",
2249
- relativePath: child(parentElement, "relativePath")?.text.trim(),
2250
- versionElement: child(parentElement, "version")
2251
- } : void 0;
2252
- const properties = /* @__PURE__ */ new Map();
2253
- for (const element of child(project, "properties")?.children ?? []) {
2254
- properties.set(element.name, { value: element.text.trim(), element });
2255
- }
2256
- return {
2257
- file,
2258
- dir: dirname6(file),
2259
- groupId: childText(project, "groupId"),
2260
- artifactId: childText(project, "artifactId") ?? "unknown",
2261
- version: childText(project, "version"),
2262
- versionElement: child(project, "version"),
2263
- name: childText(project, "name"),
2264
- packaging: childText(project, "packaging") ?? "jar",
2265
- parent,
2266
- properties,
2267
- dependencies: readDependencies(child(project, "dependencies")),
2268
- managedDependencies: readDependencies(childAt(project, "dependencyManagement", "dependencies")),
2269
- modules: children(child(project, "modules"), "module").map((m) => m.text.trim()).filter(Boolean)
2270
- };
2271
- }
2272
- function readDependencies(container) {
2273
- return children(container, "dependency").map((element) => ({
2274
- groupId: childText(element, "groupId") ?? "",
2275
- artifactId: childText(element, "artifactId") ?? "",
2276
- version: childText(element, "version"),
2277
- type: childText(element, "type"),
2278
- classifier: childText(element, "classifier"),
2279
- scope: childText(element, "scope"),
2280
- versionElement: child(element, "version")
2281
- }));
2282
- }
2283
- function parentPomFile(pom) {
2284
- if (!pom.parent) return void 0;
2285
- const relativePath = pom.parent.relativePath ?? "../pom.xml";
2286
- if (relativePath === "") return void 0;
2287
- let candidate = resolve8(pom.dir, relativePath);
2288
- if (existsSync10(candidate) && statSync(candidate).isDirectory()) candidate = join14(candidate, "pom.xml");
2289
- return existsSync10(candidate) ? candidate : void 0;
2290
- }
2291
- function builtinProperty(name, module) {
2292
- switch (name) {
2293
- case "project.groupId":
2294
- case "pom.groupId":
2295
- return module.groupId ?? module.parent?.groupId;
2296
- case "project.artifactId":
2297
- case "pom.artifactId":
2298
- return module.artifactId;
2299
- case "project.version":
2300
- case "pom.version":
2301
- return module.version ?? module.parent?.version;
2302
- case "project.parent.groupId":
2303
- return module.parent?.groupId;
2304
- case "project.parent.version":
2305
- return module.parent?.version;
2306
- default:
2307
- return void 0;
2308
- }
2309
- }
2310
- function findProperty(name, chain) {
2311
- for (const pom of chain) {
2312
- const property = pom.properties.get(name);
2313
- if (property) return { pom, property };
2314
- }
2315
- return void 0;
2316
- }
2317
- function interpolate(text, chain, depth = 0) {
2318
- if (depth > 10 || !text.includes("${")) return text;
2319
- const module = chain[0];
2320
- const replaced = text.replace(/\$\{([^}]+)\}/g, (expression, name) => {
2321
- const builtin = module ? builtinProperty(name, module) : void 0;
2322
- if (builtin !== void 0) return builtin;
2323
- return findProperty(name, chain)?.property.value ?? expression;
2324
- });
2325
- return replaced === text ? text : interpolate(replaced, chain, depth + 1);
2326
- }
2327
- function propertyReference(text) {
2328
- return text.match(/^\$\{([^}]+)\}$/)?.[1];
2329
- }
2330
-
2331
- // src/adapters/maven/maven-project.ts
2332
- var MAX_DEPTH = 6;
2333
- var SKIPPED_DIRS = /* @__PURE__ */ new Set(["node_modules", "target", "build", "dist", "out"]);
2334
- var MavenProject = class _MavenProject {
2335
- constructor(rootDir, poms) {
2336
- this.rootDir = rootDir;
2337
- this.poms = poms;
2338
- for (const pom of poms) this.byFile.set(pom.file, pom);
2339
- this.assignAggregators();
2340
- }
2341
- rootDir;
2342
- poms;
2343
- byFile = /* @__PURE__ */ new Map();
2344
- aggregators = /* @__PURE__ */ new Map();
2345
- /**
2346
- * Loads every readable pom.xml below `rootDir`, plus every module listed in
2347
- * a `<modules>` section, even in directories the file walk skips (such as
2348
- * `build/` or `dist/`). Malformed files are skipped.
2349
- */
2350
- static load(rootDir) {
2351
- const poms = [];
2352
- const queue = findPomFiles(rootDir);
2353
- const seen = new Set(queue);
2354
- while (queue.length > 0) {
2355
- const file = queue.shift();
2356
- let pom;
2357
- try {
2358
- pom = readPom(file);
2359
- } catch {
2360
- continue;
2361
- }
2362
- poms.push(pom);
2363
- for (const entry of pom.modules) {
2364
- const target = resolve9(pom.dir, entry);
2365
- const moduleFile = target.endsWith(".xml") ? target : join15(target, "pom.xml");
2366
- if (!seen.has(moduleFile) && existsSync11(moduleFile)) {
2367
- seen.add(moduleFile);
2368
- queue.push(moduleFile);
2369
- }
2370
- }
2371
- }
2372
- return new _MavenProject(rootDir, poms);
2373
- }
2374
- pomAt(file) {
2375
- return this.byFile.get(resolve9(file));
2376
- }
2377
- /** The project followed by its local parents, stopping at the first external parent. */
2378
- chainOf(pom) {
2379
- const poms = [pom];
2380
- let current = pom;
2381
- while (current.parent) {
2382
- const parent = this.localParentOf(current);
2383
- if (!parent || poms.includes(parent)) {
2384
- return { poms, externalParent: parent ? void 0 : current.parent };
2385
- }
2386
- poms.push(parent);
2387
- current = parent;
2388
- }
2389
- return { poms };
2390
- }
2391
- /** Coordinates of a project with inherited groupId/version and interpolated values. */
2392
- coordinatesOf(pom) {
2393
- const chain = this.chainOf(pom).poms;
2394
- const groupId = pom.groupId ?? pom.parent?.groupId ?? "";
2395
- const rawVersion = pom.version ?? pom.parent?.version;
2396
- const version = rawVersion ? interpolate(rawVersion, chain) : void 0;
2397
- return {
2398
- groupId: interpolate(groupId, chain),
2399
- artifactId: pom.artifactId,
2400
- version: version && !version.includes("${") ? version : void 0
2401
- };
2402
- }
2403
- /** Directory of the outermost aggregator whose reactor includes this project. */
2404
- aggregatorDirOf(pom) {
2405
- return this.aggregators.get(pom.file);
2406
- }
2407
- localParentOf(pom) {
2408
- const parent = pom.parent;
2409
- if (!parent) return void 0;
2410
- const matches = (candidate) => {
2411
- if (!candidate) return false;
2412
- const groupId = interpolate(candidate.groupId ?? candidate.parent?.groupId ?? "", [candidate]);
2413
- const version = interpolate(candidate.version ?? candidate.parent?.version ?? "", [candidate]);
2414
- const expected = interpolate(parent.version, [pom]);
2415
- return groupId === parent.groupId && candidate.artifactId === parent.artifactId && (version.includes("${") || expected.includes("${") || version === expected);
2416
- };
2417
- const file = parentPomFile(pom);
2418
- const byPath = file ? this.byFile.get(resolve9(file)) : void 0;
2419
- return matches(byPath) ? byPath : void 0;
2420
- }
2421
- /**
2422
- * Walks `<modules>` from every aggregator, outermost first, and records for
2423
- * each reached project the aggregator that builds it.
2424
- */
2425
- assignAggregators() {
2426
- const aggregatorPoms = this.poms.filter((pom) => pom.modules.length > 0).sort((a, b) => a.dir.length - b.dir.length);
2427
- for (const aggregator of aggregatorPoms) {
2428
- if (this.aggregators.has(aggregator.file)) continue;
2429
- const queue = [aggregator];
2430
- while (queue.length > 0) {
2431
- const pom = queue.shift();
2432
- if (this.aggregators.has(pom.file)) continue;
2433
- this.aggregators.set(pom.file, aggregator.dir);
2434
- for (const entry of pom.modules) {
2435
- const target = resolve9(pom.dir, entry);
2436
- const file = target.endsWith(".xml") ? target : join15(target, "pom.xml");
2437
- const modulePom = this.byFile.get(file);
2438
- if (modulePom) queue.push(modulePom);
2439
- }
2440
- }
2441
- }
2442
- }
2443
- };
2444
- function findPomFiles(rootDir) {
2445
- const results = [];
2446
- const rootPom = join15(rootDir, "pom.xml");
2447
- if (existsSync11(rootPom)) results.push(resolve9(rootPom));
2448
- const walk = (dir, depth) => {
2449
- if (depth > MAX_DEPTH) return;
2450
- let entries2;
2451
- try {
2452
- entries2 = readdirSync2(dir, { withFileTypes: true });
2453
- } catch {
2454
- return;
2455
- }
2456
- const isProject = existsSync11(join15(dir, "pom.xml"));
2457
- for (const entry of entries2) {
2458
- if (!entry.isDirectory() || entry.name.startsWith(".") || SKIPPED_DIRS.has(entry.name)) continue;
2459
- if (isProject && entry.name === "src") continue;
2460
- const subDir = join15(dir, entry.name);
2461
- const pom = join15(subDir, "pom.xml");
2462
- if (existsSync11(pom) && statSync2(pom).isFile()) results.push(resolve9(pom));
2463
- walk(subDir, depth + 1);
2464
- }
2465
- };
2466
- walk(rootDir, 1);
2467
- return results;
2468
- }
2469
-
2470
- // src/adapters/maven/maven-discovery.ts
2471
- var MavenModuleDiscoveryAdapter = class {
2472
- /**
2473
- * Proposes a root only when `startDir` holds a pom.xml, and then climbs to
2474
- * the POM above until there is none: the top of the connected POM chain.
2475
- */
2476
- findRoot(startDir, boundaryDir) {
2477
- let dir = resolve10(startDir);
2478
- if (!existsSync12(join16(dir, "pom.xml"))) return void 0;
2479
- const boundary = boundaryDir ? resolve10(boundaryDir) : void 0;
2480
- for (; ; ) {
2481
- const above = pomAbove(dir, boundary);
2482
- if (!above) return dir;
2483
- dir = above;
2484
- }
2485
- }
2486
- async discover(rootDir) {
2487
- const project = MavenProject.load(rootDir);
2488
- const modules = project.poms.map((pom) => {
2489
- const coordinates = project.coordinatesOf(pom);
2490
- const relPath = relative6(rootDir, pom.dir).replace(/\\/g, "/");
2491
- return {
2492
- id: coordinates.groupId ? `${coordinates.groupId}:${coordinates.artifactId}` : coordinates.artifactId,
2493
- name: pom.name ? interpolate(pom.name, project.chainOf(pom).poms) : pom.artifactId,
2494
- path: pom.dir,
2495
- relPath: relPath || "Root",
2496
- ecosystem: "maven",
2497
- isRoot: relPath === "",
2498
- version: coordinates.version,
2499
- aggregatorDir: project.aggregatorDirOf(pom)
2500
- };
2501
- });
2502
- modules.sort((a, b) => a.isRoot ? -1 : b.isRoot ? 1 : a.relPath.localeCompare(b.relPath));
2503
- return modules;
2504
- }
2505
- };
2506
- function tryReadPom(file) {
2507
- try {
2508
- return readPom(file);
2509
- } catch {
2510
- return void 0;
2511
- }
2512
- }
2513
- function ancestorsWithin(dir, boundary) {
2514
- const result = [];
2515
- let current = dir;
2516
- while (current !== boundary) {
2517
- const parent = dirname7(current);
2518
- if (parent === current) break;
2519
- result.push(parent);
2520
- current = parent;
2521
- }
2522
- return result;
2523
- }
2524
- function pomAbove(dir, boundary) {
2525
- const pom = tryReadPom(join16(dir, "pom.xml"));
2526
- if (!pom) return void 0;
2527
- const ancestors = ancestorsWithin(dir, boundary);
2528
- const found = [];
2529
- const parentFile = parentPomFile(pom);
2530
- if (parentFile && pom.parent && ancestors.includes(dirname7(parentFile))) {
2531
- const parent = tryReadPom(parentFile);
2532
- const groupId = parent?.groupId ?? parent?.parent?.groupId;
2533
- if (parent && parent.artifactId === pom.parent.artifactId && groupId === pom.parent.groupId) {
2534
- found.push(dirname7(parentFile));
2535
- }
2536
- }
2537
- const ownFile = join16(dir, "pom.xml");
2538
- const aggregator = ancestors.find(
2539
- (ancestor) => tryReadPom(join16(ancestor, "pom.xml"))?.modules.some((entry) => {
2540
- const target = resolve10(ancestor, entry);
2541
- return target === dir || target === ownFile;
2542
- })
2543
- );
2544
- if (aggregator) found.push(aggregator);
2545
- return found.sort((a, b) => a.length - b.length)[0];
2546
- }
2547
-
2548
- // src/adapters/maven/maven-registry.ts
2549
- import { existsSync as existsSync15 } from "fs";
2550
- import { join as join19 } from "path";
2551
-
2552
- // src/adapters/maven/maven-command.ts
2553
- import { existsSync as existsSync13 } from "fs";
2554
- import { dirname as dirname8, isAbsolute as isAbsolute2, join as join17, relative as relative7, resolve as resolve11, sep } from "path";
2555
- var MavenUnavailableError = class extends Error {
2556
- constructor() {
2557
- super("Maven is not available: no Maven Wrapper (mvnw) in the project and no `mvn` on the PATH.");
2558
- this.name = "MavenUnavailableError";
2559
- }
2560
- };
2561
- var MavenCommandError = class extends Error {
2562
- constructor(message2, output) {
2563
- super(message2);
2564
- this.output = output;
2565
- this.name = "MavenCommandError";
2566
- }
2567
- output;
2568
- };
2569
- var IS_WINDOWS2 = process.platform === "win32";
2570
- var WRAPPER = IS_WINDOWS2 ? "mvnw.cmd" : "mvnw";
2571
- var BATCH_ARGS = ["-B", "--no-transfer-progress"];
2572
- var mvnOnPath;
2573
- function findMavenWrapper(fromDir, rootDir) {
2574
- let dir = resolve11(fromDir);
2575
- const root = resolve11(rootDir);
2576
- for (; ; ) {
2577
- const candidate = join17(dir, WRAPPER);
2578
- if (existsSync13(candidate)) return candidate;
2579
- if (dir === root || relative7(root, dir).startsWith("..")) return void 0;
2580
- const parent = dirname8(dir);
2581
- if (parent === dir) return void 0;
2582
- dir = parent;
2583
- }
2584
- }
2585
- function relativeToCwd(cwd, file) {
2586
- const path = relative7(cwd, file);
2587
- return isAbsolute2(path) ? file : path || ".";
2588
- }
2589
- async function runMaven(args, options) {
2590
- const wrapper = findMavenWrapper(options.cwd, options.rootDir);
2591
- const run = { cwd: options.cwd, timeoutMs: options.timeoutMs };
2592
- if (wrapper) {
2593
- const relativeWrapper = relativeToCwd(options.cwd, wrapper);
2594
- const path = isAbsolute2(relativeWrapper) || relativeWrapper.includes(sep) ? relativeWrapper : `.${sep}${relativeWrapper}`;
2595
- return IS_WINDOWS2 ? runCommand(path, [...BATCH_ARGS, ...args], run) : runCommand("sh", [path, ...BATCH_ARGS, ...args], run);
2596
- }
2597
- mvnOnPath ??= runCommand("mvn", ["-v"], { cwd: options.cwd, timeoutMs: 3e4 }).then(
2598
- (result) => result.exitCode === 0,
2599
- () => false
2600
- );
2601
- if (!await mvnOnPath) throw new MavenUnavailableError();
2602
- return runCommand("mvn", [...BATCH_ARGS, ...args], run);
2603
- }
2604
- function mavenErrors(output) {
2605
- const errors = output.split(/\r?\n/).filter((line) => line.startsWith("[ERROR]")).map((line) => line.replace(/^\[ERROR\]\s?/, "")).filter((line) => line.trim() && !line.startsWith("->") && !line.includes("Re-run Maven"));
2606
- return (errors.length > 0 ? errors : output.trim().split(/\r?\n/).slice(-10)).slice(0, 12).join("\n");
2607
- }
2608
-
2609
- // src/adapters/maven/maven-resolution.ts
2610
- import { randomUUID } from "crypto";
2611
- import { existsSync as existsSync14, mkdirSync, readFileSync as readFileSync14, readdirSync as readdirSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
2612
- import { join as join18 } from "path";
2613
- var DEFAULT_MAVEN_PLUGINS = { help: "3.5.2", versions: "2.22.0" };
2614
- var HELP_PLUGIN_ID = "org.apache.maven.plugins:maven-help-plugin";
2615
- var VERSIONS_PLUGIN_ID = "org.codehaus.mojo:versions-maven-plugin";
2616
- var DOWNLOAD_FAILURES = [
2617
- "could not be resolved",
2618
- "could not find artifact",
2619
- "failed to read artifact descriptor",
2620
- "could not transfer artifact"
2621
- ];
2622
- var MavenPluginUnavailableError = class extends MavenCommandError {
2623
- constructor(plugin, output) {
2624
- super(
2625
- `Maven could not download ${plugin.id}:${plugin.version}, which Elevate uses to ${plugin.purpose}. Make this plugin available in the repository or mirror your Maven is configured with, or choose a version that is available there with "mavenPlugins": { "${plugin.key}": "<version>" } in elevate.config.json.
2626
- ${mavenErrors(output)}`,
2627
- output
2628
- );
2629
- this.name = "MavenPluginUnavailableError";
2630
- }
2631
- };
2632
- function isPluginUnavailable(output, pluginId) {
2633
- return output.split(/\r?\n/).some((line) => {
2634
- const lower = line.toLowerCase();
2635
- return line.includes(pluginId) && DOWNLOAD_FAILURES.some((phrase) => lower.includes(phrase));
2636
- });
2637
- }
2638
- var MAVEN_TIMEOUT_MS = 5 * 6e4;
2639
- var ScratchDirectory = class {
2640
- path;
2641
- target;
2642
- createdTarget;
2643
- constructor(moduleDir) {
2644
- this.target = join18(moduleDir, "target");
2645
- this.createdTarget = !existsSync14(this.target);
2646
- this.path = join18(this.target, `.elevate-${randomUUID().slice(0, 8)}`);
2647
- mkdirSync(this.path, { recursive: true });
2648
- }
2649
- dispose() {
2650
- rmSync(this.path, { recursive: true, force: true });
2651
- try {
2652
- if (this.createdTarget && readdirSync3(this.target).length === 0) rmSync(this.target, { recursive: true });
2653
- } catch {
2654
- }
2655
- }
2656
- };
2657
- async function readEffectivePoms(pomFile, moduleDir, rootDir, scratch, plugins, recursive = false) {
2658
- const output = join18(scratch.path, "effective-pom.xml");
2659
- const args = [
2660
- "-f",
2661
- relativeToCwd(moduleDir, pomFile),
2662
- `${HELP_PLUGIN_ID}:${plugins.help}:effective-pom`,
2663
- `-Doutput=${relativeToCwd(moduleDir, output)}`
2664
- ];
2665
- const result = await runMaven(recursive ? args : ["-N", ...args], {
2666
- cwd: moduleDir,
2667
- rootDir,
2668
- timeoutMs: MAVEN_TIMEOUT_MS
2669
- });
2670
- if (result.exitCode !== 0 || !existsSync14(output)) {
2671
- const text = `${result.stdout}
2672
- ${result.stderr}`;
2673
- if (isPluginUnavailable(text, HELP_PLUGIN_ID)) {
2674
- throw new MavenPluginUnavailableError(
2675
- { id: HELP_PLUGIN_ID, version: plugins.help, key: "help", purpose: "read the effective POM" },
2676
- text
2677
- );
2678
- }
2679
- throw new MavenCommandError(`Maven could not build the effective POM:
2680
- ${mavenErrors(text)}`, text);
2681
- }
2682
- return parseEffectivePoms(readFileSync14(output, "utf8"));
2683
- }
2684
- function parseEffectivePoms(source) {
2685
- const root = parseXml(source);
2686
- const projects = root.name === "projects" ? children(root, "project") : [root];
2687
- const models = /* @__PURE__ */ new Map();
2688
- for (const project of projects) {
2689
- models.set(`${childText(project, "groupId")}:${childText(project, "artifactId")}`, toEffectiveModel(project));
2690
- }
2691
- return models;
2692
- }
2693
- function mergeRepositories(models) {
2694
- const merge2 = (name, pick) => {
2695
- const byId = /* @__PURE__ */ new Map();
2696
- for (const element of models.map(pick)) {
2697
- for (const repository of element?.children ?? []) {
2698
- const id = childText(repository, "id") ?? childText(repository, "url") ?? "";
2699
- if (!byId.has(id)) byId.set(id, repository);
2700
- }
2701
- }
2702
- return byId.size > 0 ? { name, attributes: {}, children: [...byId.values()], text: "" } : void 0;
2703
- };
2704
- return {
2705
- dependencies: [],
2706
- managedDependencies: [],
2707
- repositories: merge2("repositories", (m) => m.repositories),
2708
- pluginRepositories: merge2("pluginRepositories", (m) => m.pluginRepositories)
2709
- };
2710
- }
2711
- function toEffectiveModel(project) {
2712
- return {
2713
- dependencies: readEffectiveDependencies(child(project, "dependencies")),
2714
- managedDependencies: readEffectiveDependencies(childAt(project, "dependencyManagement", "dependencies")),
2715
- repositories: child(project, "repositories"),
2716
- pluginRepositories: child(project, "pluginRepositories")
2717
- };
2718
- }
2719
- function readEffectiveDependencies(container) {
2720
- return children(container, "dependency").map((element) => ({
2721
- groupId: childText(element, "groupId") ?? "",
2722
- artifactId: childText(element, "artifactId") ?? "",
2723
- version: childText(element, "version") ?? "",
2724
- scope: childText(element, "scope") ?? "compile",
2725
- type: childText(element, "type") ?? "jar",
2726
- classifier: childText(element, "classifier")
2727
- }));
2728
- }
2729
- function buildProbePom(artifacts, model) {
2730
- const seen = /* @__PURE__ */ new Set();
2731
- const entries2 = [];
2732
- for (const artifact of artifacts) {
2733
- const key = `${artifact.groupId}:${artifact.artifactId}:${artifact.type ?? "jar"}:${artifact.classifier ?? ""}`;
2734
- if (seen.has(key)) continue;
2735
- seen.add(key);
2736
- const fields = [
2737
- `<groupId>${escapeXml(artifact.groupId)}</groupId>`,
2738
- `<artifactId>${escapeXml(artifact.artifactId)}</artifactId>`,
2739
- `<version>${escapeXml(artifact.version)}</version>`,
2740
- artifact.type && artifact.type !== "jar" ? `<type>${escapeXml(artifact.type)}</type>` : "",
2741
- artifact.classifier ? `<classifier>${escapeXml(artifact.classifier)}</classifier>` : ""
2742
- ].filter(Boolean);
2743
- entries2.push(` <dependency>${fields.join("")}</dependency>`);
2744
- }
2745
- const repositories = [model.repositories, model.pluginRepositories].filter((element) => element !== void 0).map((element) => serializeXml(element, " ")).join("\n");
2746
- return [
2747
- '<?xml version="1.0" encoding="UTF-8"?>',
2748
- '<project xmlns="http://maven.apache.org/POM/4.0.0">',
2749
- " <modelVersion>4.0.0</modelVersion>",
2750
- " <groupId>elevate.probe</groupId>",
2751
- " <artifactId>elevate-probe</artifactId>",
2752
- " <version>0</version>",
2753
- " <packaging>pom</packaging>",
2754
- repositories,
2755
- " <dependencyManagement>",
2756
- " <dependencies>",
2757
- ...entries2,
2758
- " </dependencies>",
2759
- " </dependencyManagement>",
2760
- "</project>",
2761
- ""
2762
- ].filter((line) => line !== "").join("\n");
2763
- }
2764
- async function queryNewerVersions(artifacts, model, moduleDir, rootDir, scratch, plugins) {
2765
- if (artifacts.length === 0) return { newerVersions: /* @__PURE__ */ new Map(), failedLookups: /* @__PURE__ */ new Set() };
2766
- const probeDir = join18(scratch.path, "probe");
2767
- mkdirSync(probeDir, { recursive: true });
2768
- const probePom = join18(probeDir, "pom.xml");
2769
- writeFileSync3(probePom, buildProbePom(artifacts, model), "utf8");
2770
- const result = await runMaven(
2771
- [
2772
- "-N",
2773
- "-f",
2774
- relativeToCwd(moduleDir, probePom),
2775
- `${VERSIONS_PLUGIN_ID}:${plugins.versions}:dependency-updates-report`,
2776
- "-DdependencyUpdatesReportFormats=xml",
2777
- "-DprocessDependencyManagementTransitive=false"
2778
- ],
2779
- { cwd: moduleDir, rootDir, timeoutMs: MAVEN_TIMEOUT_MS }
2780
- );
2781
- const log = `${result.stdout}
2782
- ${result.stderr}`;
2783
- const reportFile = join18(probeDir, "target", "dependency-updates-report.xml");
2784
- if (result.exitCode !== 0 || !existsSync14(reportFile)) {
2785
- if (isPluginUnavailable(log, VERSIONS_PLUGIN_ID)) {
2786
- throw new MavenPluginUnavailableError(
2787
- { id: VERSIONS_PLUGIN_ID, version: plugins.versions, key: "versions", purpose: "look up newer versions" },
2788
- log
2789
- );
2790
- }
2791
- throw new MavenCommandError(`Maven could not look up newer versions:
2792
- ${mavenErrors(log)}`, log);
2793
- }
2794
- return {
2795
- newerVersions: parseUpdatesReport(readFileSync14(reportFile, "utf8")),
2796
- failedLookups: parseFailedLookups(log)
2797
- };
2798
- }
2799
- function parseUpdatesReport(source) {
2800
- const report = parseXml(source);
2801
- const sections = [
2802
- ...children(child(report, "dependencyManagements"), "dependencyManagement"),
2803
- ...children(child(report, "dependencies"), "dependency")
2804
- ];
2805
- const result = /* @__PURE__ */ new Map();
2806
- for (const entry of sections) {
2807
- const key = `${childText(entry, "groupId")}:${childText(entry, "artifactId")}`;
2808
- const versions = [
2809
- ...collect(entry, "subIncrementals", "subIncremental"),
2810
- ...collect(entry, "incrementals", "incremental"),
2811
- ...collect(entry, "minors", "minor"),
2812
- ...collect(entry, "majors", "major")
2813
- ];
2814
- const known = result.get(key) ?? [];
2815
- for (const version of versions) if (!known.includes(version)) known.push(version);
2816
- result.set(key, known);
2817
- }
2818
- return result;
2819
- }
2820
- function collect(entry, container, item) {
2821
- return children(child(entry, container), item).map((element) => element.text.trim()).filter(Boolean);
2822
- }
2823
- function parseFailedLookups(log) {
2824
- const failed = /* @__PURE__ */ new Set();
2825
- const pattern = /Could not (?:transfer|read) metadata ([^:\s/]+):([^:\s/]+)(?::[^\s/]+)?\/maven-metadata\.xml/g;
2826
- for (const match of log.matchAll(pattern)) failed.add(`${match[1]}:${match[2]}`);
2827
- return failed;
2828
- }
2829
-
2830
- // src/adapters/maven/maven-registry.ts
2831
- var PROBE_VERSION = "0";
2832
- var NO_REPOSITORIES = { dependencies: [], managedDependencies: [] };
2833
- var MavenRegistryAdapter = class {
2834
- constructor(plugins = DEFAULT_MAVEN_PLUGINS) {
2835
- this.plugins = plugins;
2836
- }
2837
- plugins;
2838
- /**
2839
- * The repositories come from Maven's own configuration, which Elevate does
2840
- * not interpret. They are reported as non-public: the lookup goes exactly
2841
- * where the project's build would go.
2842
- */
2843
- async resolveEndpoint() {
2844
- return { url: "repositories configured for Maven (settings.xml, POM)", isPublic: false };
2845
- }
2846
- async getLatestVersion(coordinate, channel = "stable", cwd = process.cwd()) {
2847
- const versions = await this.getAllVersions(coordinate, cwd);
2848
- return versions.find((v) => channel === "all" || !isPreReleaseVersion(v)) ?? null;
2849
- }
2850
- /**
2851
- * All versions available in the configured repositories, newest first.
2852
- * Throws `MavenCommandError` when Maven fails or a repository could not be
2853
- * read, so an error is never mistaken for "no versions".
2854
- */
2855
- async getAllVersions(coordinate, cwd = process.cwd()) {
2856
- if (!coordinate.group || !coordinate.artifact) return [];
2857
- const key = `${coordinate.group}:${coordinate.artifact}`;
2858
- const scratch = new ScratchDirectory(cwd);
2859
- try {
2860
- const repositories = await projectRepositories(cwd, scratch, this.plugins);
2861
- const report = await queryNewerVersions(
2862
- [{ groupId: coordinate.group, artifactId: coordinate.artifact, version: PROBE_VERSION }],
2863
- repositories,
2864
- cwd,
2865
- cwd,
2866
- scratch,
2867
- this.plugins
2868
- );
2869
- const versions = report.newerVersions.get(key) ?? [];
2870
- if (versions.length === 0 && report.failedLookups.has(key)) {
2871
- throw new MavenCommandError(`A Maven repository failed to deliver the versions of ${key}.`, "");
2872
- }
2873
- return [...versions].reverse();
2874
- } finally {
2875
- scratch.dispose();
2876
- }
2877
- }
2878
- };
2879
- async function projectRepositories(cwd, scratch, plugins) {
2880
- const rootPom = join19(cwd, "pom.xml");
2881
- if (!existsSync15(rootPom)) return NO_REPOSITORIES;
2882
- const models = await readEffectivePoms(rootPom, cwd, cwd, scratch, plugins);
2883
- return [...models.values()][0] ?? NO_REPOSITORIES;
2884
- }
2885
-
2886
- // src/adapters/maven/maven-scanner.ts
2887
- import { join as join20 } from "path";
2888
- import semver11 from "semver";
2889
-
2890
- // src/adapters/maven/maven-locator.ts
2891
- import { relative as relative8 } from "path";
2892
- function locateDependencyVersion(chain, target, rootDir) {
2893
- for (const pom of chain) {
2894
- const dependency = findDependency(pom.dependencies, target, chain);
2895
- if (!dependency) continue;
2896
- if (!dependency.versionElement) break;
2897
- return resolveLiteral(pom, "dependency", dependency.versionElement, chain, target, rootDir);
2898
- }
2899
- return locateManagedVersion(chain, target, rootDir);
2900
- }
2901
- function locateManagedVersion(chain, target, rootDir) {
2902
- for (const pom of chain) {
2903
- const managed = findDependency(pom.managedDependencies, target, chain);
2904
- if (managed?.versionElement) {
2905
- return resolveLiteral(pom, "dependency-management", managed.versionElement, chain, target, rootDir);
2906
- }
2907
- }
2908
- return {
2909
- found: false,
2910
- reason: "managed-externally",
2911
- detail: "version is managed outside this repository (external parent POM or imported BOM)"
2912
- };
2913
- }
2914
- function locateParentVersion(module, target, rootDir) {
2915
- const element = module.parent?.versionElement;
2916
- if (!element) {
2917
- return { found: false, reason: "declaration-mismatch", detail: "parent has no <version> element" };
2918
- }
2919
- return resolveLiteral(module, "parent", element, [module], target, rootDir);
2920
- }
2921
- function findDependency(dependencies, target, chain) {
2922
- return dependencies.find(
2923
- (dep) => dep.scope !== "import" && interpolate(dep.groupId, chain) === target.groupId && interpolate(dep.artifactId, chain) === target.artifactId
2924
- );
2925
- }
2926
- function resolveLiteral(pom, kind, versionElement, chain, target, rootDir) {
2927
- let file = pom.file;
2928
- let element = versionElement;
2929
- let value = versionElement.text.trim();
2930
- let propertyName;
2931
- for (let depth = 0; depth < 10; depth++) {
2932
- const reference = propertyReference(value);
2933
- if (!reference) break;
2934
- if (reference.startsWith("project.") || reference.startsWith("pom.")) {
2935
- return {
2936
- found: false,
2937
- reason: "declaration-mismatch",
2938
- detail: `version is the expression \${${reference}}, not an editable value`
2939
- };
2940
- }
2941
- const definition = findProperty(reference, chain);
2942
- if (!definition) {
2943
- return {
2944
- found: false,
2945
- reason: "managed-externally",
2946
- detail: `property \${${reference}} is defined outside this repository`
2947
- };
2948
- }
2949
- file = definition.pom.file;
2950
- element = definition.property.element;
2951
- value = definition.property.value;
2952
- propertyName = reference;
2953
- }
2954
- if (value.includes("${")) {
2955
- return {
2956
- found: false,
2957
- reason: "declaration-mismatch",
2958
- detail: `version '${value}' combines several expressions`
2959
- };
2960
- }
2961
- if (value !== target.effectiveVersion) {
2962
- return {
2963
- found: false,
2964
- reason: "declaration-mismatch",
2965
- detail: `declared '${value}' but Maven resolves '${target.effectiveVersion}' (profile or command-line override?)`
2966
- };
2967
- }
2968
- const declaration = {
2969
- file,
2970
- kind: propertyName ? "property" : kind,
2971
- propertyName,
2972
- displayPath: relative8(rootDir, file).replace(/\\/g, "/")
2973
- };
2974
- return { found: true, declaration, element, value };
2975
- }
2976
-
2977
- // src/adapters/maven/maven-scanner.ts
2978
- var DIFF_ORDER3 = { major: 0, minor: 1, patch: 2 };
2979
- var MavenDependencyAdapter = class {
2980
- constructor(plugins = DEFAULT_MAVEN_PLUGINS) {
2981
- this.plugins = plugins;
2982
- }
2983
- plugins;
2984
- async scan(module, context) {
2985
- const results = await this.scanMany([module], context);
2986
- return results.get(module) ?? { candidates: [], skipped: [] };
2987
- }
2988
- async scanMany(modules, context) {
2989
- const project = MavenProject.load(context.rootDir);
2990
- const results = /* @__PURE__ */ new Map();
2991
- for (const group of groupModules(project, modules)) {
2992
- const scratch = new ScratchDirectory(group.dir);
2993
- try {
2994
- for (const [module, result] of await this.scanGroup(project, group, context, scratch)) {
2995
- results.set(module, result);
2996
- }
2997
- } finally {
2998
- scratch.dispose();
2999
- }
3000
- }
3001
- return results;
3002
- }
3003
- async scanGroup(project, group, context, scratch) {
3004
- const rootDir = context.rootDir;
3005
- const effectiveModels = group.aggregatorPom ? await readEffectivePoms(group.aggregatorPom, group.dir, rootDir, scratch, this.plugins, true) : await readEffectivePoms(group.members[0].pom.file, group.dir, rootDir, scratch, this.plugins);
3006
- const plans = [];
3007
- for (const { module, pom } of group.members) {
3008
- const coordinates = project.coordinatesOf(pom);
3009
- const effective = effectiveModels.get(`${coordinates.groupId}:${coordinates.artifactId}`) ?? (group.aggregatorPom ? [...(await readEffectivePoms(pom.file, module.path, rootDir, scratch, this.plugins)).values()][0] : [...effectiveModels.values()][0]);
3010
- if (!effective) continue;
3011
- plans.push(planModule(project, pom, module, effective, context));
3012
- }
3013
- const probe = plans.flatMap((plan) => plan.lookups.map(({ subject }) => subject));
3014
- const report = await queryNewerVersions(
3015
- probe,
3016
- mergeRepositories(plans.map((plan) => plan.effective)),
3017
- group.dir,
3018
- rootDir,
3019
- scratch,
3020
- this.plugins
3021
- );
3022
- return new Map(plans.map((plan) => [plan.module, finishModule(plan, report, context.channel)]));
3023
- }
3024
- };
3025
- function groupModules(project, modules) {
3026
- const groups = /* @__PURE__ */ new Map();
3027
- const reactorSizes = /* @__PURE__ */ new Map();
3028
- for (const module of modules) {
3029
- if (module.aggregatorDir) reactorSizes.set(module.aggregatorDir, (reactorSizes.get(module.aggregatorDir) ?? 0) + 1);
3030
- }
3031
- for (const module of modules) {
3032
- const pom = project.pomAt(join20(module.path, "pom.xml"));
3033
- if (!pom) continue;
3034
- const reactor = module.aggregatorDir;
3035
- const batched = reactor !== void 0 && (reactorSizes.get(reactor) ?? 0) > 1;
3036
- const key = batched ? `reactor:${reactor}` : `module:${module.path}`;
3037
- const group = groups.get(key) ?? {
3038
- dir: batched ? reactor : module.path,
3039
- aggregatorPom: batched ? join20(reactor, "pom.xml") : void 0,
3040
- members: []
3041
- };
3042
- group.members.push({ module, pom });
3043
- groups.set(key, group);
3044
- }
3045
- return [...groups.values()];
3046
- }
3047
- function planModule(project, pom, module, effective, context) {
3048
- const { poms: chain, externalParent } = project.chainOf(pom);
3049
- const rootDir = context.rootDir;
3050
- const subjects = /* @__PURE__ */ new Map();
3051
- for (const dep of effective.dependencies) {
3052
- const key = `${dep.groupId}:${dep.artifactId}`;
3053
- if (subjects.has(key)) continue;
3054
- const target = { groupId: dep.groupId, artifactId: dep.artifactId };
3055
- subjects.set(key, {
3056
- ...dep,
3057
- scope: dep.scope === "test" ? "test" : "prod",
3058
- locate: (effectiveVersion) => locateDependencyVersion(chain, { ...target, effectiveVersion }, rootDir)
3059
- });
3060
- }
3061
- for (const managed of pom.managedDependencies) {
3062
- const groupId = interpolate(managed.groupId, chain);
3063
- const artifactId = interpolate(managed.artifactId, chain);
3064
- const key = `${groupId}:${artifactId}`;
3065
- if (subjects.has(key) || !managed.version) continue;
3066
- const isImport = managed.scope === "import";
3067
- const version = isImport ? interpolate(managed.version, chain) : effective.managedDependencies.find((m) => m.groupId === groupId && m.artifactId === artifactId)?.version;
3068
- if (!version || version.includes("${")) continue;
3069
- const target = { groupId, artifactId };
3070
- subjects.set(key, {
3071
- groupId,
3072
- artifactId,
3073
- version,
3074
- type: isImport ? "pom" : managed.type,
3075
- classifier: managed.classifier,
3076
- scope: "prod",
3077
- locate: (effectiveVersion) => locateManagedVersion(chain, { ...target, effectiveVersion }, rootDir)
3078
- });
3079
- }
3080
- if (pom.parent && externalParent === pom.parent) {
3081
- const { groupId, artifactId } = pom.parent;
3082
- const version = interpolate(pom.parent.version, chain);
3083
- subjects.set(`${groupId}:${artifactId}`, {
3084
- groupId,
3085
- artifactId,
3086
- version,
3087
- type: "pom",
3088
- scope: "prod",
3089
- locate: (effectiveVersion) => locateParentVersion(pom, { groupId, artifactId, effectiveVersion }, rootDir)
3090
- });
3091
- }
3092
- const plan = { module, effective, candidates: [], skipped: [], lookups: [] };
3093
- for (const [key, subject] of subjects) {
3094
- const origin = classifyOrigin(key, "maven", context);
3095
- if (origin.kind !== "workspace") {
3096
- plan.lookups.push({ subject, origin });
3097
- continue;
3098
- }
3099
- const target = context.workspace.get(key);
3100
- if (target && inSameReactor(module, target)) {
3101
- if (target.version && target.version !== subject.version) {
3102
- collect2(alignmentCandidate2(subject, target.version, origin), plan.candidates, plan.skipped);
3103
- }
3104
- } else {
3105
- plan.lookups.push({ subject, origin: classifyOrigin(key, "maven", { ...context, workspace: /* @__PURE__ */ new Map() }) });
3106
- }
3107
- }
3108
- return plan;
3109
- }
3110
- function inSameReactor(a, b) {
3111
- return a.aggregatorDir !== void 0 && a.aggregatorDir === b.aggregatorDir;
3112
- }
3113
- function finishModule(plan, report, channel) {
3114
- const { candidates, skipped } = plan;
3115
- for (const { subject, origin } of plan.lookups) {
3116
- const key = `${subject.groupId}:${subject.artifactId}`;
3117
- const newer = newerThan(subject.version, report.newerVersions.get(key) ?? []);
3118
- const latest = pickLatest(subject.version, newer, channel);
3119
- if (!latest) {
3120
- if (report.failedLookups.has(key)) {
3121
- skipped.push({ identifier: key, origin: origin.kind, reason: "lookup-failed", detail: "repository error" });
3122
- }
3123
- continue;
3124
- }
3125
- collect2(updateCandidate(subject, latest, newer, origin), candidates, skipped);
3126
- }
3127
- markSharedDeclarations(candidates);
3128
- candidates.sort((a, b) => (DIFF_ORDER3[a.diff] ?? 3) - (DIFF_ORDER3[b.diff] ?? 3));
3129
- return { candidates, skipped };
3130
- }
3131
- function newerThan(current, ascending) {
3132
- const index = ascending.indexOf(current);
3133
- return index >= 0 ? ascending.slice(index + 1) : [...ascending];
3134
- }
3135
- function collect2(built, candidates, skipped) {
3136
- if ("candidate" in built) candidates.push(built.candidate);
3137
- else skipped.push(built.skipped);
3138
- }
3139
- function updateCandidate(subject, latest, newer, origin) {
3140
- const base = buildCandidate(subject, latest, origin, "update");
3141
- if ("candidate" in base) {
3142
- base.candidate.availableVersions = [...newer].reverse().concat(subject.version);
3143
- }
3144
- return base;
3145
- }
3146
- function alignmentCandidate2(subject, localVersion, origin) {
3147
- const built = buildCandidate(subject, localVersion, origin, "align");
3148
- if ("candidate" in built) built.candidate.availableVersions = [localVersion];
3149
- return built;
3150
- }
3151
- function buildCandidate(subject, target, origin, action) {
3152
- const identifier = `${subject.groupId}:${subject.artifactId}`;
3153
- const location = subject.locate(subject.version);
3154
- if (!location.found) {
3155
- return { skipped: { identifier, origin: origin.kind, reason: location.reason, detail: location.detail } };
3156
- }
3157
- const currentClean = cleanJavaVersion(subject.version) ?? "0.0.0";
3158
- const targetClean = cleanJavaVersion(target) ?? currentClean;
3159
- const diff = semver11.eq(currentClean, targetClean) ? "patch" : diffVersions(currentClean, targetClean);
3160
- const isPreRelease = isPreReleaseVersion(target);
3161
- return {
3162
- candidate: {
3163
- coordinate: { identifier, group: subject.groupId, artifact: subject.artifactId, ecosystem: "maven" },
3164
- currentRange: subject.version,
3165
- currentClean,
3166
- latest: target,
3167
- newRange: target,
3168
- diff,
3169
- scope: subject.scope,
3170
- selected: diff !== "major",
3171
- origin,
3172
- action,
3173
- declaration: location.declaration,
3174
- isPreRelease,
3175
- preReleaseTag: isPreRelease ? extractPreReleaseTag(target) : void 0
3176
- }
3177
- };
3178
- }
3179
- function pickLatest(current, newer, channel) {
3180
- const allowed = newer.filter((v) => channel === "all" || !isPreReleaseVersion(v));
3181
- const flavour = flavourOf(current);
3182
- const sameFlavour = flavour ? allowed.filter((v) => flavourOf(v) === flavour) : [];
3183
- const pool = sameFlavour.length > 0 ? sameFlavour : allowed;
3184
- return pool[pool.length - 1];
3185
- }
3186
- function flavourOf(version) {
3187
- const suffix = version.match(/-([A-Za-z]+)$/)?.[1];
3188
- return suffix && !isPreReleaseVersion(version) ? suffix.toLowerCase() : void 0;
3189
- }
3190
- function markSharedDeclarations(candidates) {
3191
- const groups = /* @__PURE__ */ new Map();
3192
- for (const candidate of candidates) {
3193
- const declaration = candidate.declaration;
3194
- if (declaration?.kind !== "property") continue;
3195
- const key = `${declaration.file}#${declaration.propertyName}`;
3196
- groups.set(key, [...groups.get(key) ?? [], candidate]);
3197
- }
3198
- for (const group of groups.values()) {
3199
- if (group.length < 2) continue;
3200
- for (const candidate of group) {
3201
- candidate.sharedWith = group.filter((other) => other !== candidate).map((other) => other.coordinate.identifier);
3202
- }
3203
- }
3204
- }
3205
-
3206
- // src/adapters/maven/maven-updater.ts
3207
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync4 } from "fs";
3208
- import { join as join21, resolve as resolve12 } from "path";
3209
- var MavenUpdaterAdapter = class {
3210
- affectedFiles(module, _rootDir, updates) {
3211
- const files = new Set(updates.map((u) => resolve12(u.declaration?.file ?? join21(module.path, "pom.xml"))));
3212
- return [...files];
3213
- }
3214
- async applyUpdates(module, rootDir, updates, onProgress) {
3215
- const project = MavenProject.load(rootDir);
3216
- const byFile = /* @__PURE__ */ new Map();
3217
- for (const update of updates) {
3218
- if (!update.declaration) {
3219
- throw new InstallError(`No version declaration known for ${update.coordinate.identifier}; rescan the module.`);
3220
- }
3221
- const file = resolve12(update.declaration.file);
3222
- byFile.set(file, [...byFile.get(file) ?? [], update]);
3223
- }
3224
- const writes = [];
3225
- for (const [file, fileUpdates] of byFile) {
3226
- onProgress(`Preparing ${fileUpdates.length} change(s) in ${fileUpdates[0].declaration.displayPath}\u2026`);
3227
- const source = readFileSync15(file, "utf8");
3228
- const pom = parsePom(source, file);
3229
- const chain = project.pomAt(file) ? project.chainOf(project.pomAt(file)).poms : [pom];
3230
- const edits = collectEdits(pom, [pom, ...chain.slice(1)], fileUpdates);
3231
- try {
3232
- writes.push({ file, content: applyTextEdits(source, edits) });
3233
- } catch (err) {
3234
- if (err instanceof StaleEditError) throw new InstallError(`${file}: ${err.message}`);
3235
- throw err;
3236
- }
3237
- }
3238
- onProgress("Writing POM files\u2026");
3239
- for (const write of writes) writeFileSync4(write.file, write.content, "utf8");
3240
- return {
3241
- updatedCount: updates.length,
3242
- auditMessage: `Updated ${updates.length} version declaration(s) in ${writes.length} POM file(s).`,
3243
- auditSeverity: "clean",
3244
- integrityViolations: []
3245
- };
3246
- }
3247
- async resync() {
3248
- }
3249
- };
3250
- function collectEdits(pom, chain, updates) {
3251
- const edits = /* @__PURE__ */ new Map();
3252
- for (const update of updates) {
3253
- const element = findDeclarationElement(pom, chain, update);
3254
- if (!element) {
3255
- throw new InstallError(
3256
- `${update.declaration.displayPath}: cannot find the version of ${update.coordinate.identifier} any more; rescan the module.`
3257
- );
3258
- }
3259
- const existing = edits.get(element);
3260
- if (existing) {
3261
- if (existing.replacement !== update.newRange) {
3262
- throw new InstallError(
3263
- `${update.coordinate.identifier} and ${existing.identifier} share ${describe(update.declaration)} but target different versions (${update.newRange} vs ${existing.replacement}).`
3264
- );
3265
- }
3266
- continue;
3267
- }
3268
- edits.set(element, {
3269
- element,
3270
- expected: update.currentRange,
3271
- replacement: update.newRange,
3272
- identifier: update.coordinate.identifier
3273
- });
3274
- }
3275
- return [...edits.values()];
3276
- }
3277
- function findDeclarationElement(pom, chain, update) {
3278
- const declaration = update.declaration;
3279
- const { group, artifact } = update.coordinate;
3280
- const matches = (dep) => interpolate(dep.groupId, chain) === group && interpolate(dep.artifactId, chain) === artifact;
3281
- switch (declaration.kind) {
3282
- case "property":
3283
- return declaration.propertyName ? pom.properties.get(declaration.propertyName)?.element : void 0;
3284
- case "parent":
3285
- return pom.parent?.versionElement;
3286
- case "dependency":
3287
- return pom.dependencies.find(matches)?.versionElement;
3288
- case "dependency-management":
3289
- return pom.managedDependencies.find(matches)?.versionElement;
3290
- }
3291
- }
3292
- function describe(declaration) {
3293
- return declaration.kind === "property" ? `the property \${${declaration.propertyName}} in ${declaration.displayPath}` : `a version in ${declaration.displayPath}`;
3294
- }
3295
-
3296
- // src/adapters/maven/maven-verifier.ts
3297
- import { dirname as dirname9, join as join22, relative as relative9, resolve as resolve13 } from "path";
3298
- var VERIFY_TIMEOUT_MS3 = 30 * 6e4;
3299
- var MavenVerificationAdapter = class {
3300
- async verify(module, rootDir, options = {}) {
3301
- if (options.customScript) {
3302
- const label2 = options.customLabel || options.customScript;
3303
- const result = await runShellScript(options.customScript, { cwd: module.path, timeoutMs: VERIFY_TIMEOUT_MS3 });
3304
- return result.exitCode === 0 ? { status: "clean", details: "Verification script succeeded.", label: label2 } : { status: "warn", details: failureDetails2(result), label: label2 };
3305
- }
3306
- const plan = planBuild(module, rootDir, options.changedFiles ?? []);
3307
- const label = `Maven build (${["test-compile", ...plan.args].join(" ")})`;
3308
- try {
3309
- const result = await runMaven(["-q", "test-compile", ...plan.args], {
3310
- cwd: plan.cwd,
3311
- rootDir,
3312
- timeoutMs: VERIFY_TIMEOUT_MS3
3313
- });
3314
- if (result.exitCode === 0) {
3315
- return { status: "clean", details: "All affected modules compile, including tests.", label };
3316
- }
3317
- const details = result.timedOut ? "Maven build timed out." : failureDetails2(result);
3318
- return { status: "warn", details, label };
3319
- } catch (err) {
3320
- if (err instanceof MavenUnavailableError) return { status: "warn", details: err.message, label };
3321
- throw err;
3322
- }
3323
- }
3324
- };
3325
- function failureDetails2(result) {
3326
- const output = `${result.stdout}
3327
- ${result.stderr}`.trim();
3328
- return output ? mavenErrors(output) : `Exited with code ${result.exitCode}.`;
3329
- }
3330
- function planBuild(module, rootDir, changedFiles) {
3331
- const aggregatorDir = module.aggregatorDir;
3332
- if (!aggregatorDir) return { cwd: module.path, args: [] };
3333
- const project = MavenProject.load(rootDir);
3334
- const files = changedFiles.length > 0 ? changedFiles : [join22(module.path, "pom.xml")];
3335
- const selected = /* @__PURE__ */ new Set();
3336
- for (const file of files) {
3337
- const pom = project.pomAt(file);
3338
- const dir = resolve13(dirname9(file));
3339
- if (!pom || project.aggregatorDirOf(pom) !== aggregatorDir || dir === resolve13(aggregatorDir)) {
3340
- return { cwd: aggregatorDir, args: [] };
3341
- }
3342
- selected.add(relative9(aggregatorDir, dir).replace(/\\/g, "/"));
3343
- }
3344
- return { cwd: aggregatorDir, args: ["-pl", [...selected].join(","), "-amd"] };
3345
- }
3346
-
3347
- // src/adapters/maven/maven-strategy.ts
3348
- var MavenEcosystemStrategy = class {
3349
- ecosystem = "maven";
3350
- displayName = "Java / Maven";
3351
- icon = "\u2615";
3352
- manifestFile = "pom.xml";
3353
- versioning = mavenVersionPolicy;
3354
- discovery = new MavenModuleDiscoveryAdapter();
3355
- registry;
3356
- reader;
3357
- updater = new MavenUpdaterAdapter();
3358
- verifier = new MavenVerificationAdapter();
3359
- /** @param plugins versions of the Maven plugins Elevate runs (default: the pinned ones) */
3360
- constructor(plugins = DEFAULT_MAVEN_PLUGINS) {
3361
- this.registry = new MavenRegistryAdapter(plugins);
3362
- this.reader = new MavenDependencyAdapter(plugins);
3363
- }
3364
- getTabLabels(counts, _t) {
3365
- return {
3366
- all: `[1] Alle (${counts.total})`,
3367
- prod: `[2] Compile (${counts.prodCount})`,
3368
- dev: `[3] Test (${counts.devCount})`
3369
- };
3370
- }
3371
- };
3372
-
3373
- // src/domain/ecosystem-factory.ts
3374
- var EcosystemFactory = class {
3375
- static instances = /* @__PURE__ */ new Map();
3376
- static settings = {};
3377
- /**
3378
- * Stores the settings and drops the cached strategies, so strategies created
3379
- * afterwards use them. Without a call the defaults apply.
3380
- */
3381
- static configure(settings) {
3382
- this.settings = settings;
3383
- this.instances.clear();
3384
- }
3385
- /** Returns the strategy for an ecosystem (lazily created singleton). */
3386
- static getStrategy(ecosystem) {
3387
- let strategy = this.instances.get(ecosystem);
3388
- if (!strategy) {
3389
- if (ecosystem === "maven") {
3390
- strategy = new MavenEcosystemStrategy(this.settings.mavenPlugins);
3391
- } else if (ecosystem === "cargo") {
3392
- strategy = new CargoEcosystemStrategy();
3393
- } else {
3394
- strategy = new NpmEcosystemStrategy();
3395
- }
3396
- this.instances.set(ecosystem, strategy);
3397
- }
3398
- return strategy;
3399
- }
3400
- /** All supported ecosystems. */
3401
- static getAvailableEcosystems() {
3402
- return ["npm", "maven", "cargo"];
3403
- }
3404
- /** The ecosystem after `current`, for the dashboard's ecosystem switch. */
3405
- static next(current) {
3406
- const all = this.getAvailableEcosystems();
3407
- return all[(all.indexOf(current) + 1) % all.length];
3408
- }
3409
- };
3410
-
3411
- // src/config.ts
3412
- function findRepositoryRoot(startDir) {
3413
- const start = resolve14(startDir);
3414
- const boundary = findCheckoutBoundary(start);
3415
- let dir = start;
3416
- for (; ; ) {
3417
- if (existsSync16(join23(dir, "elevate.config.json"))) return dir;
3418
- const parent = dirname10(dir);
3419
- if (dir === boundary || parent === dir) break;
3420
- dir = parent;
3421
- }
3422
- const proposals = EcosystemFactory.getAvailableEcosystems().map((ecosystem) => EcosystemFactory.getStrategy(ecosystem).discovery.findRoot?.(start, boundary)).filter((root) => root !== void 0);
3423
- return proposals.sort((a, b) => a.length - b.length)[0] ?? start;
3424
- }
3425
- var ConfigError = class extends Error {
3426
- constructor(message2) {
3427
- super(message2);
3428
- this.name = "ConfigError";
3429
- }
3430
- };
3431
- var NO_CONTINUE = "Elevate does not continue without your internalScopes.";
3432
- function stringList(file, key, source) {
3433
- if (!Object.hasOwn(file, key)) return void 0;
3434
- const value = file[key];
3435
- if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) return value;
3436
- throw new ConfigError(
3437
- `Invalid elevate.config.json (${source}): \`${key}\` must be a list of strings, for example ["@my-org", "com.mycompany"]. ${NO_CONTINUE}`
3438
- );
3439
- }
3440
- var PLUGIN_VERSION = /^[0-9][A-Za-z0-9.-]{0,63}$/;
3441
- function resolveMavenPlugins(value, warnings) {
3442
- const plugins = { ...DEFAULT_MAVEN_PLUGINS };
3443
- if (typeof value !== "object" || value === null || Array.isArray(value)) return plugins;
3444
- const given = value;
3445
- for (const key of Object.keys(DEFAULT_MAVEN_PLUGINS)) {
3446
- if (!Object.hasOwn(given, key)) continue;
3447
- const version = given[key];
3448
- if (typeof version === "string" && PLUGIN_VERSION.test(version)) {
3449
- plugins[key] = version;
3450
- } else {
3451
- warnings.push(
3452
- `\`mavenPlugins.${key}\` is not a valid version (${JSON.stringify(version)}); using the default ${DEFAULT_MAVEN_PLUGINS[key]}.`
3453
- );
3454
- }
3455
- }
3456
- return plugins;
3457
- }
3458
- function resolveConfig(rootDir, file, source = "elevate.config.json") {
3459
- const deprecations = [];
3460
- const warnings = [];
3461
- const internal = stringList(file, "internalScopes", source);
3462
- const legacy = stringList(file, "excludeScopes", source);
3463
- if (legacy) {
3464
- deprecations.push(
3465
- internal ? "`excludeScopes` is deprecated and ignored because `internalScopes` is set; remove it from elevate.config.json." : "`excludeScopes` is deprecated; rename it to `internalScopes` in elevate.config.json."
3466
- );
3467
- }
3468
- return {
3469
- rootDir,
3470
- author: typeof file.author === "string" ? file.author : void 0,
3471
- internalScopes: internal ?? legacy ?? [],
3472
- postUpdateScript: typeof file.postUpdateScript === "string" ? file.postUpdateScript : void 0,
3473
- postUpdateLabel: typeof file.postUpdateLabel === "string" ? file.postUpdateLabel : typeof file.postUpdateScript === "string" ? file.postUpdateScript : void 0,
3474
- locale: file.locale === "de" || file.locale === "en" ? file.locale : void 0,
3475
- channel: file.channel === "all" ? "all" : "stable",
3476
- deprecations,
3477
- mavenPlugins: resolveMavenPlugins(file.mavenPlugins, warnings),
3478
- warnings
3479
- };
3480
- }
3481
- function loadConfig2(startDir = process.cwd()) {
3482
- const rootDir = findRepositoryRoot(startDir);
3483
- const configPath = join23(rootDir, "elevate.config.json");
3484
- let file = {};
3485
- if (existsSync16(configPath)) {
3486
- const unreadable = (detail) => new ConfigError(
3487
- `Cannot read elevate.config.json: ${detail} (${configPath}). Fix the file or remove it; ${NO_CONTINUE}`
3488
- );
3489
- let parsed;
3490
- try {
3491
- parsed = JSON.parse(readFileSync16(configPath, "utf8").replace(/^/, ""));
3492
- } catch (err) {
3493
- throw unreadable(err instanceof Error ? err.message : String(err));
3494
- }
3495
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
3496
- throw unreadable("the file must contain a JSON object");
3497
- }
3498
- file = parsed;
3499
- }
3500
- return resolveConfig(rootDir, file, configPath);
3501
- }
3502
-
3503
18
  // src/app.tsx
3504
19
  import { useState as useState8, useMemo as useMemo4, useCallback as useCallback5 } from "react";
3505
20
  import { Box as Box14, useInput as useInput2, useApp } from "ink";
@@ -3523,7 +38,9 @@ var de = {
3523
38
  tabs: {
3524
39
  all: (count) => `[1] Alle (${count})`,
3525
40
  dependencies: (count) => `[2] Dependencies (${count})`,
3526
- devDependencies: (count) => `[3] DevDependencies (${count})`
41
+ devDependencies: (count) => `[3] DevDependencies (${count})`,
42
+ main: (count) => `[2] Haupt (${count})`,
43
+ testAndPlugins: (count) => `[3] Test & Plugins (${count})`
3527
44
  },
3528
45
  list: {
3529
46
  scanning: "\u23F3 Pr\xFCfe Registry auf verf\xFCgbare Updates\u2026",
@@ -3539,7 +56,8 @@ var de = {
3539
56
  "declaration-mismatch": "Ort der Versionsangabe ist nicht eindeutig",
3540
57
  "invalid-name": "ung\xFCltiger Paketname",
3541
58
  "not-linked": "Workspace-Paket, das pnpm ohne das workspace:-Protokoll nicht verlinkt",
3542
- "catalog-unresolved": "Catalog-Eintrag konnte nicht aufgel\xF6st werden"
59
+ "catalog-unresolved": "Catalog-Eintrag konnte nicht aufgel\xF6st werden",
60
+ "version-constraint": "Version ist ein Selektor oder eine Einschr\xE4nkung, keine feste Version"
3543
61
  })[reason]
3544
62
  },
3545
63
  badges: {
@@ -3635,7 +153,9 @@ var en = {
3635
153
  tabs: {
3636
154
  all: (count) => `[1] All (${count})`,
3637
155
  dependencies: (count) => `[2] Dependencies (${count})`,
3638
- devDependencies: (count) => `[3] DevDependencies (${count})`
156
+ devDependencies: (count) => `[3] DevDependencies (${count})`,
157
+ main: (count) => `[2] Main (${count})`,
158
+ testAndPlugins: (count) => `[3] Test & plugins (${count})`
3639
159
  },
3640
160
  list: {
3641
161
  scanning: "\u23F3 Checking registry for available updates\u2026",
@@ -3651,7 +171,8 @@ var en = {
3651
171
  "declaration-mismatch": "version location is ambiguous",
3652
172
  "invalid-name": "invalid package name",
3653
173
  "not-linked": "workspace package that pnpm does not link without the workspace: protocol",
3654
- "catalog-unresolved": "catalog entry could not be resolved"
174
+ "catalog-unresolved": "catalog entry could not be resolved",
175
+ "version-constraint": "version is a selector or constraint, not a plain version"
3655
176
  })[reason]
3656
177
  },
3657
178
  badges: {
@@ -3800,6 +321,15 @@ async function scanModules(strategy, modules, context) {
3800
321
  for (const module of modules) results.set(module, await scanModule(strategy, module, context));
3801
322
  return results;
3802
323
  }
324
+ function declarationToJson(declaration) {
325
+ return {
326
+ file: declaration.displayPath,
327
+ kind: declaration.kind,
328
+ property: declaration.propertyName,
329
+ catalog: declaration.catalogName,
330
+ entry: declaration.entry
331
+ };
332
+ }
3803
333
  function candidateToJson(u) {
3804
334
  return {
3805
335
  identifier: u.coordinate.identifier,
@@ -3813,12 +343,8 @@ function candidateToJson(u) {
3813
343
  scope: u.scope,
3814
344
  isPreRelease: u.isPreRelease ?? false,
3815
345
  preReleaseTag: u.preReleaseTag,
3816
- declaredIn: u.declaration ? {
3817
- file: u.declaration.displayPath,
3818
- kind: u.declaration.kind,
3819
- property: u.declaration.propertyName,
3820
- catalog: u.declaration.catalogName
3821
- } : void 0,
346
+ declaredIn: u.declaration ? declarationToJson(u.declaration) : void 0,
347
+ alsoDeclaredIn: u.declarations && u.declarations.length > 1 ? u.declarations.slice(1).map(declarationToJson) : void 0,
3822
348
  sharedWith: u.sharedWith
3823
349
  };
3824
350
  }
@@ -3982,24 +508,24 @@ function usePackages(strategy, module, modules, config, channel = "stable") {
3982
508
  import { useState as useState4, useCallback as useCallback4 } from "react";
3983
509
 
3984
510
  // src/application/update-workflow.ts
3985
- import { existsSync as existsSync18 } from "fs";
3986
- import { relative as relative10 } from "path";
511
+ import { existsSync as existsSync2 } from "fs";
512
+ import { relative } from "path";
3987
513
 
3988
514
  // src/adapters/shared/file-snapshot.ts
3989
- import { existsSync as existsSync17, readFileSync as readFileSync17, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
515
+ import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
3990
516
  var FileSnapshot = class _FileSnapshot {
3991
- constructor(entries2) {
3992
- this.entries = entries2;
517
+ constructor(entries) {
518
+ this.entries = entries;
3993
519
  }
3994
520
  entries;
3995
521
  /** Records the current content (or absence) of each file. */
3996
522
  static capture(files) {
3997
- const entries2 = /* @__PURE__ */ new Map();
523
+ const entries = /* @__PURE__ */ new Map();
3998
524
  for (const file of files) {
3999
- if (entries2.has(file)) continue;
4000
- entries2.set(file, existsSync17(file) ? readFileSync17(file) : null);
525
+ if (entries.has(file)) continue;
526
+ entries.set(file, existsSync(file) ? readFileSync(file) : null);
4001
527
  }
4002
- return new _FileSnapshot(entries2);
528
+ return new _FileSnapshot(entries);
4003
529
  }
4004
530
  /** The files covered by this snapshot. */
4005
531
  get files() {
@@ -4009,18 +535,18 @@ var FileSnapshot = class _FileSnapshot {
4009
535
  changedFiles() {
4010
536
  return this.files.filter((file) => {
4011
537
  const before = this.entries.get(file) ?? null;
4012
- const exists = existsSync17(file);
538
+ const exists = existsSync(file);
4013
539
  if (before === null) return exists;
4014
- return !exists || !readFileSync17(file).equals(before);
540
+ return !exists || !readFileSync(file).equals(before);
4015
541
  });
4016
542
  }
4017
543
  /** Writes every file back to its captured state; removes files that did not exist. */
4018
544
  restore() {
4019
545
  for (const [file, content] of this.entries) {
4020
546
  if (content === null) {
4021
- rmSync2(file, { force: true });
547
+ rmSync(file, { force: true });
4022
548
  } else {
4023
- writeFileSync5(file, content);
549
+ writeFileSync(file, content);
4024
550
  }
4025
551
  }
4026
552
  }
@@ -4059,7 +585,7 @@ Restoring the installed state failed as well: ${message(err)}`;
4059
585
  let baseline;
4060
586
  if (!options.skipVerification) {
4061
587
  progress("Running verification on the current state\u2026");
4062
- baseline = await verify(snapshot.files.filter((file) => existsSync18(file)));
588
+ baseline = await verify(snapshot.files.filter((file) => existsSync2(file)));
4063
589
  }
4064
590
  let applied;
4065
591
  try {
@@ -4105,7 +631,7 @@ function message(err) {
4105
631
  return err instanceof Error ? err.message : String(err);
4106
632
  }
4107
633
  function displayPaths(files, rootDir) {
4108
- return files.map((file) => relative10(rootDir, file).replace(/\\/g, "/"));
634
+ return files.map((file) => relative(rootDir, file).replace(/\\/g, "/"));
4109
635
  }
4110
636
 
4111
637
  // src/hooks/use-updater.ts
@@ -4586,7 +1112,7 @@ var ChickenMascot = ({ state = "idle", t, ecosystem = "npm" }) => {
4586
1112
  chickFace = cur.face;
4587
1113
  wingLeft = cur.l;
4588
1114
  wingRight = cur.r;
4589
- const chirpsPool = ecosystem === "maven" ? [...t.mascot.idleChirps, ...t.mascot.mavenChirps] : ecosystem === "cargo" ? [...t.mascot.idleChirps, ...t.mascot.cargoChirps] : t.mascot.idleChirps;
1115
+ const chirpsPool = ecosystem === "maven" || ecosystem === "gradle" ? [...t.mascot.idleChirps, ...t.mascot.mavenChirps] : ecosystem === "cargo" ? [...t.mascot.idleChirps, ...t.mascot.cargoChirps] : t.mascot.idleChirps;
4590
1116
  currentChirp = chirpsPool[chirpIndex % chirpsPool.length];
4591
1117
  }
4592
1118
  return /* @__PURE__ */ jsxs7(Box7, { alignItems: "center", children: [
@@ -4746,7 +1272,7 @@ var PublicLookupRefusedError = class extends Error {
4746
1272
  }
4747
1273
  };
4748
1274
  function coordinateFromIdentifier(identifier, ecosystem) {
4749
- if (ecosystem === "maven") {
1275
+ if (ecosystem === "maven" || ecosystem === "gradle") {
4750
1276
  const [group, artifact] = identifier.split(":");
4751
1277
  return { identifier, group, artifact: artifact || group, ecosystem };
4752
1278
  }
@@ -5318,7 +1844,7 @@ SUBCOMMANDS:
5318
1844
  mcp Start stdio-based MCP server
5319
1845
 
5320
1846
  FLAGS:
5321
- -e, --ecosystem <npm|maven|cargo> Target ecosystem (default: npm)
1847
+ -e, --ecosystem <name> Target ecosystem: npm, maven, cargo or gradle (default: npm)
5322
1848
  -m, --module <name|path> Target module ID or relative path (default: root)
5323
1849
  -c, --channel <stable|all> Release channel (default: stable)
5324
1850
  -p, --packages <pkg1,pkg2@1.2.3> Comma-separated list of packages to update
@@ -5334,6 +1860,7 @@ FLAGS:
5334
1860
  EXAMPLES:
5335
1861
  elevate scan --json
5336
1862
  elevate scan --ecosystem=maven --channel=stable --all-modules --json
1863
+ elevate scan --ecosystem=gradle --json
5337
1864
  elevate versions chalk --ecosystem=npm --json
5338
1865
  elevate update --module=apps/e2e-cockpit --packages=chalk@5.6.2 --json
5339
1866
  elevate update --ecosystem=npm --all --allow-major --dry-run
@@ -5385,6 +1912,8 @@ function describeSkip(skipped) {
5385
1912
  return `catalog entry could not be resolved${detail}`;
5386
1913
  case "incompatible-rust-version":
5387
1914
  return `newer versions need a newer Rust than the project's rust-version${detail}`;
1915
+ case "version-constraint":
1916
+ return `version is a selector or constraint, not a plain version${detail}`;
5388
1917
  case "locked-version-yanked":
5389
1918
  return `Cargo.lock pins a yanked release and no allowed release can replace it${detail}`;
5390
1919
  }
@@ -5471,13 +2000,14 @@ async function handleScanCommand(options, config) {
5471
2000
  `);
5472
2001
  continue;
5473
2002
  }
5474
- const modulePom = module.isRoot ? "pom.xml" : `${module.relPath}/pom.xml`;
2003
+ const prefix = module.isRoot ? "" : `${module.relPath}/`;
2004
+ const ownFiles = module.ecosystem === "gradle" ? [`${prefix}build.gradle`, `${prefix}build.gradle.kts`] : [`${prefix}pom.xml`];
5475
2005
  if (result.candidates.length === 0) console.log(" \u2728 All dependencies are up to date.");
5476
2006
  for (const u of result.candidates) {
5477
2007
  const diffTag = `[${u.diff.toUpperCase()}]`.padEnd(9);
5478
2008
  const actionTag = u.action === "align" ? "[ALIGN] " : u.action === "lock" ? "[LOCK] " : u.origin.kind === "private" ? "[INTERNAL] " : "";
5479
2009
  const preBadge = u.isPreRelease ? `[${u.preReleaseTag || "PRE"}] ` : "";
5480
- const where = u.declaration && u.declaration.displayPath !== modulePom ? ` (in ${u.declaration.displayPath}${u.declaration.propertyName ? ` \${${u.declaration.propertyName}}` : ""})` : "";
2010
+ const where = u.declaration && !ownFiles.includes(u.declaration.displayPath) ? ` (in ${u.declaration.displayPath}${u.declaration.propertyName ? ` \${${u.declaration.propertyName}}` : ""}${u.declaration.entry ? ` ${u.declaration.entry}` : ""})` : "";
5481
2011
  console.log(
5482
2012
  ` \u2022 ${diffTag} ${actionTag}${preBadge}${u.coordinate.identifier.padEnd(36)} ${u.currentRange.padEnd(12)} \u2794 ${u.latest}${where}`
5483
2013
  );
@@ -5546,7 +2076,7 @@ async function handleVersionsCommand(options, config) {
5546
2076
  }
5547
2077
 
5548
2078
  // src/application/update-selection.ts
5549
- import semver12 from "semver";
2079
+ import semver from "semver";
5550
2080
  var UpdateSelectionError = class extends Error {
5551
2081
  constructor(message2) {
5552
2082
  super(message2);
@@ -5623,7 +2153,7 @@ function retarget(found, target, options) {
5623
2153
  };
5624
2154
  }
5625
2155
  function manualNpmCandidate(identifier, target, options) {
5626
- if (!semver12.valid(target)) {
2156
+ if (!semver.valid(target)) {
5627
2157
  throw new UpdateSelectionError(`'${target}' is not a valid version for '${identifier}'.`);
5628
2158
  }
5629
2159
  return {
@@ -5795,9 +2325,9 @@ function parsePackageArgument(item) {
5795
2325
  }
5796
2326
 
5797
2327
  // src/cli/command-init.ts
5798
- import { existsSync as existsSync19, readFileSync as readFileSync18, writeFileSync as writeFileSync6, mkdirSync as mkdirSync2 } from "fs";
5799
- import { homedir as homedir2, platform } from "os";
5800
- import { join as join24, resolve as resolve15 } from "path";
2328
+ import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
2329
+ import { homedir, platform } from "os";
2330
+ import { join, resolve } from "path";
5801
2331
  function mcpServerEntry() {
5802
2332
  return {
5803
2333
  command: "npx",
@@ -5810,7 +2340,7 @@ var ELEVATE_SKILL_CONTENT = `# Elevate \u2014 Dependency Management Agent Skill
5810
2340
  Use the Elevate MCP tools whenever the task involves:
5811
2341
  - Scanning or auditing dependencies for available updates
5812
2342
  - Updating one or more packages to specific or latest versions
5813
- - Listing available versions of a package from npm or Maven registries
2343
+ - Listing available versions of a package from npm registries or Maven and Gradle repositories
5814
2344
  - Discovering monorepo modules and their dependency structure
5815
2345
  - Any dependency-related operation in a monorepo
5816
2346
 
@@ -5840,10 +2370,10 @@ automatic rollback.
5840
2370
  requested). A failed install or verification is rolled back automatically.
5841
2371
  4. \`elevate_verify\` \u2014 Verify build integrity when needed
5842
2372
  `;
5843
- var home = homedir2();
2373
+ var home = homedir();
5844
2374
  var isWin = platform() === "win32";
5845
2375
  function appData() {
5846
- return process.env.APPDATA || join24(home, "AppData", "Roaming");
2376
+ return process.env.APPDATA || join(home, "AppData", "Roaming");
5847
2377
  }
5848
2378
  function detectCursor(cwd) {
5849
2379
  const env = {
@@ -5855,25 +2385,25 @@ function detectCursor(cwd) {
5855
2385
  mcpConfigured: false,
5856
2386
  skillConfigured: false
5857
2387
  };
5858
- const workspaceCursorDir = join24(cwd, ".cursor");
5859
- const globalCursorDir = join24(home, ".cursor");
5860
- if (existsSync19(workspaceCursorDir) || existsSync19(globalCursorDir)) {
2388
+ const workspaceCursorDir = join(cwd, ".cursor");
2389
+ const globalCursorDir = join(home, ".cursor");
2390
+ if (existsSync3(workspaceCursorDir) || existsSync3(globalCursorDir)) {
5861
2391
  env.detected = true;
5862
- env.reason = existsSync19(workspaceCursorDir) ? `Found .cursor/ in workspace` : `Found ~/.cursor/ global config`;
2392
+ env.reason = existsSync3(workspaceCursorDir) ? `Found .cursor/ in workspace` : `Found ~/.cursor/ global config`;
5863
2393
  }
5864
- const mcpPath = join24(cwd, ".cursor", "mcp.json");
5865
- if (existsSync19(mcpPath)) {
2394
+ const mcpPath = join(cwd, ".cursor", "mcp.json");
2395
+ if (existsSync3(mcpPath)) {
5866
2396
  try {
5867
- const content = JSON.parse(readFileSync18(mcpPath, "utf8"));
2397
+ const content = JSON.parse(readFileSync2(mcpPath, "utf8"));
5868
2398
  if (content?.mcpServers?.elevate) {
5869
2399
  env.mcpConfigured = true;
5870
2400
  }
5871
2401
  } catch {
5872
2402
  }
5873
2403
  }
5874
- const rulesPath = join24(cwd, ".cursorrules");
5875
- if (existsSync19(rulesPath)) {
5876
- const content = readFileSync18(rulesPath, "utf8");
2404
+ const rulesPath = join(cwd, ".cursorrules");
2405
+ if (existsSync3(rulesPath)) {
2406
+ const content = readFileSync2(rulesPath, "utf8");
5877
2407
  if (content.includes("elevate") || content.includes("Elevate")) {
5878
2408
  env.skillConfigured = true;
5879
2409
  }
@@ -5890,12 +2420,12 @@ function detectClaudeDesktop() {
5890
2420
  mcpConfigured: false,
5891
2421
  skillConfigured: false
5892
2422
  };
5893
- const configPath = isWin ? join24(appData(), "Claude", "claude_desktop_config.json") : join24(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
5894
- if (existsSync19(configPath)) {
2423
+ const configPath = isWin ? join(appData(), "Claude", "claude_desktop_config.json") : join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
2424
+ if (existsSync3(configPath)) {
5895
2425
  env.detected = true;
5896
2426
  env.reason = `Found ${configPath}`;
5897
2427
  try {
5898
- const content = JSON.parse(readFileSync18(configPath, "utf8"));
2428
+ const content = JSON.parse(readFileSync2(configPath, "utf8"));
5899
2429
  if (content?.mcpServers?.elevate) {
5900
2430
  env.mcpConfigured = true;
5901
2431
  }
@@ -5914,23 +2444,23 @@ function detectClaudeCode(cwd) {
5914
2444
  mcpConfigured: false,
5915
2445
  skillConfigured: false
5916
2446
  };
5917
- const mcpJsonPath = join24(cwd, ".mcp.json");
5918
- const claudeMdPath = join24(cwd, "CLAUDE.md");
5919
- if (existsSync19(mcpJsonPath) || existsSync19(claudeMdPath)) {
2447
+ const mcpJsonPath = join(cwd, ".mcp.json");
2448
+ const claudeMdPath = join(cwd, "CLAUDE.md");
2449
+ if (existsSync3(mcpJsonPath) || existsSync3(claudeMdPath)) {
5920
2450
  env.detected = true;
5921
- env.reason = existsSync19(mcpJsonPath) ? `Found .mcp.json in workspace` : `Found CLAUDE.md in workspace`;
2451
+ env.reason = existsSync3(mcpJsonPath) ? `Found .mcp.json in workspace` : `Found CLAUDE.md in workspace`;
5922
2452
  }
5923
- if (existsSync19(mcpJsonPath)) {
2453
+ if (existsSync3(mcpJsonPath)) {
5924
2454
  try {
5925
- const content = JSON.parse(readFileSync18(mcpJsonPath, "utf8"));
2455
+ const content = JSON.parse(readFileSync2(mcpJsonPath, "utf8"));
5926
2456
  if (content?.mcpServers?.elevate) {
5927
2457
  env.mcpConfigured = true;
5928
2458
  }
5929
2459
  } catch {
5930
2460
  }
5931
2461
  }
5932
- if (existsSync19(claudeMdPath)) {
5933
- const content = readFileSync18(claudeMdPath, "utf8");
2462
+ if (existsSync3(claudeMdPath)) {
2463
+ const content = readFileSync2(claudeMdPath, "utf8");
5934
2464
  if (content.includes("elevate") || content.includes("Elevate")) {
5935
2465
  env.skillConfigured = true;
5936
2466
  }
@@ -5947,20 +2477,20 @@ function detectAntigravity(cwd) {
5947
2477
  mcpConfigured: false,
5948
2478
  skillConfigured: false
5949
2479
  };
5950
- const globalGeminiDir = join24(home, ".gemini");
5951
- const workspaceAgentsDir = join24(cwd, ".agents");
5952
- if (existsSync19(globalGeminiDir) || existsSync19(workspaceAgentsDir)) {
2480
+ const globalGeminiDir = join(home, ".gemini");
2481
+ const workspaceAgentsDir = join(cwd, ".agents");
2482
+ if (existsSync3(globalGeminiDir) || existsSync3(workspaceAgentsDir)) {
5953
2483
  env.detected = true;
5954
- env.reason = existsSync19(workspaceAgentsDir) ? `Found .agents/ in workspace` : `Found ~/.gemini/ global config`;
2484
+ env.reason = existsSync3(workspaceAgentsDir) ? `Found .agents/ in workspace` : `Found ~/.gemini/ global config`;
5955
2485
  }
5956
2486
  const mcpPaths = [
5957
- join24(cwd, ".agents", "mcp_config.json"),
5958
- join24(home, ".gemini", "config", "mcp_config.json")
2487
+ join(cwd, ".agents", "mcp_config.json"),
2488
+ join(home, ".gemini", "config", "mcp_config.json")
5959
2489
  ];
5960
2490
  for (const p of mcpPaths) {
5961
- if (existsSync19(p)) {
2491
+ if (existsSync3(p)) {
5962
2492
  try {
5963
- const content = JSON.parse(readFileSync18(p, "utf8"));
2493
+ const content = JSON.parse(readFileSync2(p, "utf8"));
5964
2494
  if (content?.mcpServers?.elevate) {
5965
2495
  env.mcpConfigured = true;
5966
2496
  break;
@@ -5981,24 +2511,24 @@ function detectVSCode(cwd) {
5981
2511
  mcpConfigured: false,
5982
2512
  skillConfigured: false
5983
2513
  };
5984
- const vscodeDir = join24(cwd, ".vscode");
5985
- if (existsSync19(vscodeDir)) {
2514
+ const vscodeDir = join(cwd, ".vscode");
2515
+ if (existsSync3(vscodeDir)) {
5986
2516
  env.detected = true;
5987
2517
  env.reason = `Found .vscode/ in workspace`;
5988
2518
  }
5989
- const mcpPath = join24(cwd, ".vscode", "mcp.json");
5990
- if (existsSync19(mcpPath)) {
2519
+ const mcpPath = join(cwd, ".vscode", "mcp.json");
2520
+ if (existsSync3(mcpPath)) {
5991
2521
  try {
5992
- const content = JSON.parse(readFileSync18(mcpPath, "utf8"));
2522
+ const content = JSON.parse(readFileSync2(mcpPath, "utf8"));
5993
2523
  if (content?.servers?.elevate) {
5994
2524
  env.mcpConfigured = true;
5995
2525
  }
5996
2526
  } catch {
5997
2527
  }
5998
2528
  }
5999
- const copilotInstructions = join24(cwd, ".github", "copilot-instructions.md");
6000
- if (existsSync19(copilotInstructions)) {
6001
- const content = readFileSync18(copilotInstructions, "utf8");
2529
+ const copilotInstructions = join(cwd, ".github", "copilot-instructions.md");
2530
+ if (existsSync3(copilotInstructions)) {
2531
+ const content = readFileSync2(copilotInstructions, "utf8");
6002
2532
  if (content.includes("elevate") || content.includes("Elevate")) {
6003
2533
  env.skillConfigured = true;
6004
2534
  }
@@ -6015,14 +2545,14 @@ function detectWindsurf() {
6015
2545
  mcpConfigured: false,
6016
2546
  skillConfigured: false
6017
2547
  };
6018
- const configPath = isWin ? join24(appData(), "Codeium", "Windsurf", "mcp_config.json") : join24(home, ".codeium", "windsurf", "mcp_config.json");
6019
- const altPath = join24(home, ".codeium", "windsurf", "mcp_config.json");
6020
- if (existsSync19(configPath) || existsSync19(altPath)) {
2548
+ const configPath = isWin ? join(appData(), "Codeium", "Windsurf", "mcp_config.json") : join(home, ".codeium", "windsurf", "mcp_config.json");
2549
+ const altPath = join(home, ".codeium", "windsurf", "mcp_config.json");
2550
+ if (existsSync3(configPath) || existsSync3(altPath)) {
6021
2551
  env.detected = true;
6022
2552
  env.reason = `Found Windsurf config`;
6023
- const path = existsSync19(configPath) ? configPath : altPath;
2553
+ const path = existsSync3(configPath) ? configPath : altPath;
6024
2554
  try {
6025
- const content = JSON.parse(readFileSync18(path, "utf8"));
2555
+ const content = JSON.parse(readFileSync2(path, "utf8"));
6026
2556
  if (content?.mcpServers?.elevate) {
6027
2557
  env.mcpConfigured = true;
6028
2558
  }
@@ -6033,9 +2563,9 @@ function detectWindsurf() {
6033
2563
  }
6034
2564
  function mergeJsonMcpServer(filePath, serverKey, topLevelKey) {
6035
2565
  let existing = {};
6036
- if (existsSync19(filePath)) {
2566
+ if (existsSync3(filePath)) {
6037
2567
  try {
6038
- existing = JSON.parse(readFileSync18(filePath, "utf8"));
2568
+ existing = JSON.parse(readFileSync2(filePath, "utf8"));
6039
2569
  } catch {
6040
2570
  existing = {};
6041
2571
  }
@@ -6047,69 +2577,69 @@ function mergeJsonMcpServer(filePath, serverKey, topLevelKey) {
6047
2577
  servers[serverKey] = mcpServerEntry();
6048
2578
  existing[topLevelKey] = servers;
6049
2579
  const dir = filePath.substring(0, filePath.lastIndexOf(isWin ? "\\" : "/"));
6050
- if (dir && !existsSync19(dir)) {
6051
- mkdirSync2(dir, { recursive: true });
2580
+ if (dir && !existsSync3(dir)) {
2581
+ mkdirSync(dir, { recursive: true });
6052
2582
  }
6053
- writeFileSync6(filePath, JSON.stringify(existing, null, 2) + "\n", "utf8");
2583
+ writeFileSync2(filePath, JSON.stringify(existing, null, 2) + "\n", "utf8");
6054
2584
  return { written: true, message: `${filePath}: elevate MCP server registered` };
6055
2585
  }
6056
2586
  function appendSkillToFile(filePath, content, marker) {
6057
- if (existsSync19(filePath)) {
6058
- const existing = readFileSync18(filePath, "utf8");
2587
+ if (existsSync3(filePath)) {
2588
+ const existing = readFileSync2(filePath, "utf8");
6059
2589
  if (existing.includes(marker)) {
6060
2590
  return { written: false, message: `${filePath}: skill already present` };
6061
2591
  }
6062
- writeFileSync6(filePath, existing + "\n\n" + content, "utf8");
2592
+ writeFileSync2(filePath, existing + "\n\n" + content, "utf8");
6063
2593
  return { written: true, message: `${filePath}: skill appended` };
6064
2594
  }
6065
2595
  const dir = filePath.substring(0, filePath.lastIndexOf(isWin ? "\\" : "/"));
6066
- if (dir && !existsSync19(dir)) {
6067
- mkdirSync2(dir, { recursive: true });
2596
+ if (dir && !existsSync3(dir)) {
2597
+ mkdirSync(dir, { recursive: true });
6068
2598
  }
6069
- writeFileSync6(filePath, content, "utf8");
2599
+ writeFileSync2(filePath, content, "utf8");
6070
2600
  return { written: true, message: `${filePath}: skill created` };
6071
2601
  }
6072
2602
  function configureCursor(cwd, result) {
6073
- const mcpPath = join24(cwd, ".cursor", "mcp.json");
2603
+ const mcpPath = join(cwd, ".cursor", "mcp.json");
6074
2604
  const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
6075
2605
  result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Cursor MCP: ${mcp.message}`);
6076
- const rulesPath = join24(cwd, ".cursorrules");
2606
+ const rulesPath = join(cwd, ".cursorrules");
6077
2607
  const skill = appendSkillToFile(rulesPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
6078
2608
  result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Cursor Rules: ${skill.message}`);
6079
2609
  }
6080
2610
  function configureClaudeDesktop(result) {
6081
- const configPath = isWin ? join24(appData(), "Claude", "claude_desktop_config.json") : join24(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
2611
+ const configPath = isWin ? join(appData(), "Claude", "claude_desktop_config.json") : join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
6082
2612
  const mcp = mergeJsonMcpServer(configPath, "elevate", "mcpServers");
6083
2613
  result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Claude Desktop MCP: ${mcp.message}`);
6084
2614
  }
6085
2615
  function configureClaudeCode(cwd, result) {
6086
- const mcpPath = join24(cwd, ".mcp.json");
2616
+ const mcpPath = join(cwd, ".mcp.json");
6087
2617
  const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
6088
2618
  result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Claude Code MCP: ${mcp.message}`);
6089
- const claudeMdPath = join24(cwd, "CLAUDE.md");
2619
+ const claudeMdPath = join(cwd, "CLAUDE.md");
6090
2620
  const skill = appendSkillToFile(claudeMdPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
6091
2621
  result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Claude Code Skill: ${skill.message}`);
6092
2622
  }
6093
2623
  function configureAntigravity(cwd, result) {
6094
- const mcpPath = join24(cwd, ".agents", "mcp_config.json");
2624
+ const mcpPath = join(cwd, ".agents", "mcp_config.json");
6095
2625
  const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
6096
2626
  result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Antigravity MCP: ${mcp.message}`);
6097
2627
  }
6098
2628
  function configureVSCode(cwd, result) {
6099
- const mcpPath = join24(cwd, ".vscode", "mcp.json");
2629
+ const mcpPath = join(cwd, ".vscode", "mcp.json");
6100
2630
  const mcp = mergeJsonMcpServer(mcpPath, "elevate", "servers");
6101
2631
  result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} VS Code MCP: ${mcp.message}`);
6102
- const instructionsPath = join24(cwd, ".github", "copilot-instructions.md");
2632
+ const instructionsPath = join(cwd, ".github", "copilot-instructions.md");
6103
2633
  const skill = appendSkillToFile(instructionsPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
6104
2634
  result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Copilot Instructions: ${skill.message}`);
6105
2635
  }
6106
2636
  function configureWindsurf(result) {
6107
- const configPath = isWin ? join24(appData(), "Codeium", "Windsurf", "mcp_config.json") : join24(home, ".codeium", "windsurf", "mcp_config.json");
2637
+ const configPath = isWin ? join(appData(), "Codeium", "Windsurf", "mcp_config.json") : join(home, ".codeium", "windsurf", "mcp_config.json");
6108
2638
  const mcp = mergeJsonMcpServer(configPath, "elevate", "mcpServers");
6109
2639
  result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Windsurf MCP: ${mcp.message}`);
6110
2640
  }
6111
2641
  async function handleInitCommand(options) {
6112
- const cwd = resolve15(process.cwd());
2642
+ const cwd = resolve(process.cwd());
6113
2643
  const result = {
6114
2644
  cwd,
6115
2645
  environments: [],
@@ -6137,7 +2667,7 @@ async function handleInitCommand(options) {
6137
2667
  console.log(` Creating portable .mcp.json for broad compatibility.
6138
2668
  `);
6139
2669
  }
6140
- const mcpPath = join24(cwd, ".mcp.json");
2670
+ const mcpPath = join(cwd, ".mcp.json");
6141
2671
  const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
6142
2672
  result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Generic MCP: ${mcp.message}`);
6143
2673
  } else {
@@ -6224,7 +2754,7 @@ async function startMcpServer(config) {
6224
2754
  tools: [
6225
2755
  {
6226
2756
  name: "elevate_discover_modules",
6227
- description: "Discovers all repository modules and workspaces for Node/npm (package.json), Java/Maven (pom.xml) or Rust/Cargo (Cargo.toml).",
2757
+ description: "Discovers all repository modules and workspaces for Node/npm (package.json), Java/Maven (pom.xml), Rust/Cargo (Cargo.toml) or Java/Gradle (build.gradle). Gradle modules also report the kind of their build (root, included, buildSrc).",
6228
2758
  inputSchema: {
6229
2759
  type: "object",
6230
2760
  properties: {
@@ -6264,7 +2794,7 @@ async function startMcpServer(config) {
6264
2794
  ecosystem: ECOSYSTEM_PROPERTY,
6265
2795
  identifier: {
6266
2796
  type: "string",
6267
- description: "Package identifier (npm: chalk, maven: org.slf4j:slf4j-api, cargo: serde)"
2797
+ description: "Package identifier (npm: chalk, maven/gradle: org.slf4j:slf4j-api, gradle plugin: org.springframework.boot, cargo: serde)"
6268
2798
  }
6269
2799
  },
6270
2800
  required: ["identifier"]
@@ -6357,7 +2887,8 @@ async function startMcpServer(config) {
6357
2887
  name: m.name,
6358
2888
  relPath: m.relPath,
6359
2889
  isRoot: m.isRoot,
6360
- version: m.version
2890
+ version: m.version,
2891
+ ...m.buildKind ? { buildKind: m.buildKind } : {}
6361
2892
  }))
6362
2893
  });
6363
2894
  }
@@ -6472,7 +3003,7 @@ import { jsx as jsx15 } from "react/jsx-runtime";
6472
3003
  async function main() {
6473
3004
  let config;
6474
3005
  try {
6475
- config = loadConfig2();
3006
+ config = loadConfig();
6476
3007
  } catch (err) {
6477
3008
  if (!(err instanceof ConfigError)) throw err;
6478
3009
  process.stderr.write(`\u274C ${err.message}
@@ -6521,7 +3052,8 @@ async function main() {
6521
3052
  render(/* @__PURE__ */ jsx15(App, { config }));
6522
3053
  }
6523
3054
  main().catch((err) => {
6524
- console.error("\u274C Elevate fatal error:", err);
3055
+ const detail = err instanceof Error ? process.env.ELEVATE_DEBUG ? err.stack ?? err.message : `${err.name}: ${err.message}` : String(err);
3056
+ console.error(`\u274C Elevate fatal error: ${detail}`);
6525
3057
  process.exit(1);
6526
3058
  });
6527
3059
  //# sourceMappingURL=cli.js.map