@dennisrepa/elevate 1.4.0 → 1.6.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
@@ -4,8 +4,65 @@
4
4
  import { render } from "ink";
5
5
 
6
6
  // src/config.ts
7
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
8
- import { dirname as dirname8, join as join18, resolve as resolve10 } from "path";
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
+ };
9
66
 
10
67
  // src/adapters/npm/npm-package-manager.ts
11
68
  import { existsSync as existsSync2, readFileSync } from "fs";
@@ -37,7 +94,7 @@ var UnsafeArgumentError = class extends Error {
37
94
  var IS_WINDOWS = process.platform === "win32";
38
95
  var WINDOWS_UNSAFE = /["%!^&|<>\r\n]/;
39
96
  function runCommand(command, args, options) {
40
- return new Promise((resolve12, reject) => {
97
+ return new Promise((resolve16, reject) => {
41
98
  const spawnOptions = { cwd: options.cwd, windowsHide: true, env: process.env };
42
99
  let child2;
43
100
  if (IS_WINDOWS) {
@@ -67,19 +124,19 @@ function runCommand(command, args, options) {
67
124
  });
68
125
  child2.on("close", (code) => {
69
126
  if (timer) clearTimeout(timer);
70
- resolve12({ exitCode: code ?? 1, stdout, stderr, timedOut });
127
+ resolve16({ exitCode: code ?? 1, stdout, stderr, timedOut });
71
128
  });
72
129
  });
73
130
  }
74
131
  function runShellScript(script, options) {
75
- return new Promise((resolve12) => {
132
+ return new Promise((resolve16) => {
76
133
  exec(
77
134
  script,
78
135
  { cwd: options.cwd, timeout: options.timeoutMs, maxBuffer: 64 * 1024 * 1024, windowsHide: true },
79
136
  (error, stdout, stderr) => {
80
137
  const timedOut = Boolean(error?.killed);
81
138
  const exitCode = error ? typeof error.code === "number" ? error.code : 1 : 0;
82
- resolve12({ exitCode, stdout, stderr, timedOut });
139
+ resolve16({ exitCode, stdout, stderr, timedOut });
83
140
  }
84
141
  );
85
142
  });
@@ -304,7 +361,7 @@ var NpmModuleDiscoveryAdapter = class {
304
361
  };
305
362
 
306
363
  // src/adapters/npm/npm-registry.ts
307
- import semver from "semver";
364
+ import semver3 from "semver";
308
365
 
309
366
  // src/domain/origin.ts
310
367
  function buildWorkspaceIndex(modules) {
@@ -434,13 +491,13 @@ var NpmRegistryAdapter = class {
434
491
  if (!isValidPackageName(coordinate.identifier)) return null;
435
492
  if (channel === "stable") {
436
493
  const latest = await this.view(coordinate.identifier, "dist-tags.latest", cwd);
437
- if (typeof latest === "string" && semver.valid(latest) && semver.prerelease(latest) === null) {
494
+ if (typeof latest === "string" && semver3.valid(latest) && semver3.prerelease(latest) === null) {
438
495
  return latest;
439
496
  }
440
497
  const releases = (await this.getAllVersions(coordinate, cwd)).filter(
441
- (v) => semver.valid(v) && semver.prerelease(v) === null
498
+ (v) => semver3.valid(v) && semver3.prerelease(v) === null
442
499
  );
443
- return releases.length > 0 ? semver.rsort(releases)[0] : null;
500
+ return releases.length > 0 ? semver3.rsort(releases)[0] : null;
444
501
  }
445
502
  const version = await this.view(coordinate.identifier, "version", cwd);
446
503
  return typeof version === "string" && version ? version : null;
@@ -470,42 +527,7 @@ var NpmRegistryAdapter = class {
470
527
  // src/adapters/npm/npm-scanner.ts
471
528
  import { readFileSync as readFileSync4 } from "fs";
472
529
  import { join as join5 } from "path";
473
- import semver3 from "semver";
474
-
475
- // src/domain/versions.ts
476
- import semver2 from "semver";
477
- var PRE_RELEASE = /(?:^|[.\-_+]|(?<=\d))(alpha|beta|rc|cr|milestone|preview|snapshot|ea|dev|pre|(?:a|b|m)(?=\d))(?=$|[.\-_+]|\d)/i;
478
- function isPreReleaseVersion(version) {
479
- return PRE_RELEASE.test(version);
480
- }
481
- function extractPreReleaseTag(version) {
482
- const match = version.match(PRE_RELEASE);
483
- if (!match) return void 0;
484
- const token = match[1].toLowerCase();
485
- if (token === "a") return "ALPHA";
486
- if (token === "b") return "BETA";
487
- if (token === "m") {
488
- const number = version.slice((match.index ?? 0) + match[0].length).match(/^\d+/)?.[0];
489
- return `M${number ?? ""}`;
490
- }
491
- return token.toUpperCase();
492
- }
493
- function cleanJavaVersion(version) {
494
- const match = version.match(/^v?(\d+(?:\.\d+){0,2})/);
495
- if (!match) return null;
496
- return semver2.coerce(match[1])?.version ?? null;
497
- }
498
- function diffVersions(from, to) {
499
- const kind = semver2.diff(from, to);
500
- if (kind === "major" || kind === "premajor") return "major";
501
- if (kind === "minor" || kind === "preminor") return "minor";
502
- return "patch";
503
- }
504
- function rangePrefix(range) {
505
- if (range.startsWith("^")) return "^";
506
- if (range.startsWith("~")) return "~";
507
- return "";
508
- }
530
+ import semver4 from "semver";
509
531
 
510
532
  // src/adapters/shared/pool.ts
511
533
  async function mapPooled(items, concurrency, fn) {
@@ -636,9 +658,9 @@ var NpmDependencyAdapter = class {
636
658
  if (!latest) {
637
659
  return { skipped: { identifier: dep.name, origin: origin.kind, reason: "lookup-failed" } };
638
660
  }
639
- const currentClean = semver3.minVersion(dep.range)?.version;
640
- if (!currentClean || !semver3.valid(latest) || !semver3.gt(latest, currentClean)) return null;
641
- const tag = semver3.prerelease(latest) ? extractPreReleaseTag(latest) ?? "PRE" : void 0;
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;
642
664
  const diff = diffVersions(currentClean, latest);
643
665
  return {
644
666
  candidate: {
@@ -673,18 +695,18 @@ function declaredDependencies(pkg) {
673
695
  function isUpgradableRange(range) {
674
696
  const trimmed = range.trim();
675
697
  if (trimmed === "" || trimmed === "*" || trimmed.toLowerCase() === "x") return false;
676
- return semver3.validRange(trimmed) !== null;
698
+ return semver4.validRange(trimmed) !== null;
677
699
  }
678
700
  function alignmentCandidate(dep, target, origin, pnpm = false) {
679
701
  const split = pnpm ? splitRangeProtocol(dep.range) : { protocol: void 0, rest: dep.range };
680
702
  const range = split.protocol === "workspace" ? split.rest : dep.range;
681
703
  const protocol = split.protocol === "workspace" ? "workspace:" : "";
682
704
  const local = target.version;
683
- if (!local || !semver3.valid(local) || !isUpgradableRange(range)) return null;
684
- if (semver3.satisfies(local, range, { includePrerelease: true })) return null;
685
- const currentClean = semver3.minVersion(range)?.version ?? "0.0.0";
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";
686
708
  const diff = diffVersions(currentClean, local);
687
- const tag = semver3.prerelease(local) ? extractPreReleaseTag(local) ?? "PRE" : void 0;
709
+ const tag = semver4.prerelease(local) ? extractPreReleaseTag(local) ?? "PRE" : void 0;
688
710
  return {
689
711
  coordinate: { identifier: dep.name, artifact: dep.name, ecosystem: "npm" },
690
712
  currentRange: dep.range,
@@ -1035,6 +1057,7 @@ var NpmEcosystemStrategy = class {
1035
1057
  }
1036
1058
  icon = "\u{1F4E6}";
1037
1059
  manifestFile = "package.json";
1060
+ versioning = npmVersionPolicy;
1038
1061
  discovery = new NpmModuleDiscoveryAdapter();
1039
1062
  registry = new NpmRegistryAdapter();
1040
1063
  reader = new NpmDependencyAdapter(this.registry, this.registry.config);
@@ -1049,17 +1072,799 @@ var NpmEcosystemStrategy = class {
1049
1072
  }
1050
1073
  };
1051
1074
 
1052
- // src/adapters/maven/maven-discovery.ts
1053
- import { existsSync as existsSync7 } from "fs";
1054
- import { dirname as dirname5, join as join11, relative as relative3, resolve as resolve6 } from "path";
1075
+ // src/adapters/cargo/cargo-strategy.ts
1076
+ import semver10 from "semver";
1055
1077
 
1056
- // src/adapters/maven/maven-project.ts
1057
- import { existsSync as existsSync6, readdirSync, statSync as statSync2 } from "fs";
1058
- import { join as join10, resolve as resolve5 } from "path";
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";
1059
1081
 
1060
- // src/adapters/maven/maven-pom.ts
1061
- import { existsSync as existsSync5, readFileSync as readFileSync8, statSync } from "fs";
1062
- import { dirname as dirname4, join as join9, resolve as resolve4 } from "path";
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.6.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 null;
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 null;
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 alignment(dep, target, located, common) {
1788
+ const local = target.version;
1789
+ if (!located || !local || !semver8.valid(local)) return null;
1790
+ const literal = located.version.value;
1791
+ if (!isUpgradableRequirement(literal) || satisfiesRequirement(literal, local)) return null;
1792
+ const floor = requirementFloor(literal) ?? "0.0.0";
1793
+ const diff = cargoDiff(floor, local);
1794
+ const tag = semver8.prerelease(local) ? extractPreReleaseTag(local) ?? "PRE" : void 0;
1795
+ return {
1796
+ candidate: {
1797
+ coordinate: { identifier: dep.name, artifact: dep.name, ecosystem: "cargo" },
1798
+ currentRange: literal,
1799
+ currentClean: floor,
1800
+ latest: local,
1801
+ newRange: rewriteRequirement(literal, local),
1802
+ diff,
1803
+ scope: common.scope,
1804
+ selected: diff !== "major",
1805
+ origin: { kind: "workspace", moduleId: target.id, moduleRelPath: target.relPath },
1806
+ action: "align",
1807
+ declaration: located.declaration,
1808
+ availableVersions: [local],
1809
+ isPreRelease: tag !== void 0,
1810
+ preReleaseTag: tag,
1811
+ ...common.qualifier ? { qualifier: common.qualifier } : {}
1812
+ }
1813
+ };
1814
+ }
1815
+ function groupDependencies(deps) {
1816
+ const groups = /* @__PURE__ */ new Map();
1817
+ for (const dep of deps) {
1818
+ const id = `${dep.rename ?? dep.name}`;
1819
+ groups.set(id, [...groups.get(id) ?? [], dep]);
1820
+ }
1821
+ return [...groups.values()];
1822
+ }
1823
+ function readDeclarations(manifestPath) {
1824
+ try {
1825
+ return parseCargoManifest(readFileSync11(manifestPath, "utf8"));
1826
+ } catch {
1827
+ return [];
1828
+ }
1829
+ }
1830
+ var sectionOf = (dep) => dep.kind === "dev" ? "dev-dependencies" : dep.kind === "build" ? "build-dependencies" : "dependencies";
1831
+ function findDeclaration(decls, key, dep) {
1832
+ const candidates = decls.filter((d) => !d.inWorkspace && d.key === key);
1833
+ return candidates.find((d) => d.section === sectionOf(dep) && (d.target ?? null) === dep.target) ?? candidates[0];
1834
+ }
1835
+ function locate(decl, root, key, repoRoot, manifestPath, rootManifest) {
1836
+ if (!decl) return void 0;
1837
+ if (decl.inherited) {
1838
+ const shared = root.find((d) => d.inWorkspace && d.key === key);
1839
+ if (!shared?.version) return void 0;
1840
+ return {
1841
+ version: shared.version,
1842
+ inherited: true,
1843
+ declaration: { file: rootManifest, kind: "dependency-management", displayPath: display(repoRoot, rootManifest) }
1844
+ };
1845
+ }
1846
+ if (!decl.version) return void 0;
1847
+ return {
1848
+ version: decl.version,
1849
+ inherited: false,
1850
+ declaration: { file: manifestPath, kind: "dependency", displayPath: display(repoRoot, manifestPath) }
1851
+ };
1852
+ }
1853
+ function sharersOf(metadata, self, key) {
1854
+ return metadata.packages.filter((p) => p !== self).filter((p) => readDeclarations(p.manifest_path).some((d) => d.key === key && d.inherited)).map((p) => p.name);
1855
+ }
1856
+ var display = (repoRoot, file) => relative4(repoRoot, file).replace(/\\/g, "/");
1857
+ var samePath = (a, b) => relative4(a, b) === "";
1858
+ function sameRequirement(literal, req) {
1859
+ const a = toSemverRange(literal);
1860
+ const b = toSemverRange(req);
1861
+ return a !== null && b !== null && semver8.validRange(a) === semver8.validRange(b);
1862
+ }
1863
+
1864
+ // src/adapters/cargo/cargo-updater.ts
1865
+ import { existsSync as existsSync8, readFileSync as readFileSync12, writeFileSync as writeFileSync2 } from "fs";
1866
+ import { join as join12, resolve as resolve6 } from "path";
1867
+ import semver9 from "semver";
1063
1868
 
1064
1869
  // src/adapters/shared/xml.ts
1065
1870
  import { SaxesParser } from "saxes";
@@ -1192,9 +1997,241 @@ function localName(qualified) {
1192
1997
  return colon >= 0 ? qualified.slice(colon + 1) : qualified;
1193
1998
  }
1194
1999
 
2000
+ // src/adapters/shared/text-edit.ts
2001
+ function applyRangeEdits(source, edits) {
2002
+ for (const edit of edits) {
2003
+ const found = source.slice(edit.start, edit.end);
2004
+ if (found !== edit.expected) {
2005
+ throw new StaleEditError(`Found '${found}', expected '${edit.expected}'. The file changed since it was scanned.`);
2006
+ }
2007
+ }
2008
+ const ordered = [...edits].sort((a, b) => b.start - a.start);
2009
+ for (let i = 1; i < ordered.length; i++) {
2010
+ if (ordered[i].end > ordered[i - 1].start) throw new StaleEditError("Overlapping edits on the same text.");
2011
+ }
2012
+ let result = source;
2013
+ for (const edit of ordered) result = result.slice(0, edit.start) + edit.replacement + result.slice(edit.end);
2014
+ return result;
2015
+ }
2016
+
2017
+ // src/adapters/cargo/cargo-updater.ts
2018
+ var UPDATE_TIMEOUT_MS = 10 * 6e4;
2019
+ var workspaceDir = (module, rootDir) => module.aggregatorDir ?? module.path ?? rootDir;
2020
+ var CargoUpdaterAdapter = class {
2021
+ affectedFiles(module, rootDir, updates) {
2022
+ const files = new Set(updates.map((u) => resolve6(u.declaration?.file ?? join12(module.path, "Cargo.toml"))));
2023
+ files.add(join12(workspaceDir(module, rootDir), "Cargo.lock"));
2024
+ return [...files];
2025
+ }
2026
+ async applyUpdates(module, rootDir, updates, onProgress) {
2027
+ const dir = workspaceDir(module, rootDir);
2028
+ const lockFile = join12(dir, "Cargo.lock");
2029
+ const lockedBefore = readLockedVersions(lockFile);
2030
+ const lockUpdates = updates.filter((u) => u.action === "lock");
2031
+ updates = updates.filter((u) => u.action !== "lock");
2032
+ this.checkLockUpdates(lockUpdates, lockFile, lockedBefore);
2033
+ const byFile = /* @__PURE__ */ new Map();
2034
+ for (const update of updates) {
2035
+ const file = resolve6(update.declaration?.file ?? join12(module.path, "Cargo.toml"));
2036
+ byFile.set(file, [...byFile.get(file) ?? [], update]);
2037
+ }
2038
+ const writes = [];
2039
+ for (const [file, fileUpdates] of byFile) {
2040
+ onProgress(`Preparing ${fileUpdates.length} change(s) in ${file}\u2026`);
2041
+ const source = readFileSync12(file, "utf8");
2042
+ const decls = parseCargoManifest(source);
2043
+ const edits = [];
2044
+ for (const update of fileUpdates) {
2045
+ const inWorkspace = update.declaration?.kind === "dependency-management";
2046
+ const name = update.coordinate.identifier;
2047
+ const found = decls.filter(
2048
+ (d) => d.inWorkspace === inWorkspace && (d.package ?? d.key) === name && d.version?.value === update.currentRange
2049
+ );
2050
+ if (found.length === 0) {
2051
+ throw new InstallError(`${file}: cannot find the version of ${name} (${update.currentRange}) any more; rescan the module.`);
2052
+ }
2053
+ for (const d of found) {
2054
+ edits.push({ start: d.version.start, end: d.version.end, expected: d.version.value, replacement: update.newRange });
2055
+ }
2056
+ }
2057
+ try {
2058
+ writes.push({ file, content: applyRangeEdits(source, edits) });
2059
+ } catch (err) {
2060
+ if (err instanceof StaleEditError) throw new InstallError(`${file}: ${err.message}`);
2061
+ throw err;
2062
+ }
2063
+ }
2064
+ onProgress("Writing Cargo.toml files\u2026");
2065
+ for (const write of writes) writeFileSync2(write.file, write.content, "utf8");
2066
+ const registryUpdates = updates.filter((u) => u.action === "update");
2067
+ if (existsSync8(lockFile) && registryUpdates.length > 0) {
2068
+ await this.refreshLock(dir, lockFile, lockedBefore, registryUpdates, onProgress);
2069
+ }
2070
+ for (const update of lockUpdates) {
2071
+ const name = update.coordinate.identifier;
2072
+ onProgress(`Locking ${name} at ${update.latest}\u2026`);
2073
+ await this.cargoUpdate(dir, ["-p", `${name}@${update.currentClean}`, "--precise", update.latest]);
2074
+ }
2075
+ const parts = [];
2076
+ if (updates.length > 0) {
2077
+ parts.push(`Updated ${updates.length} requirement(s) in ${writes.length} Cargo.toml file(s)${existsSync8(lockFile) ? "; Cargo.lock refreshed" : ""}`);
2078
+ }
2079
+ if (lockUpdates.length > 0) parts.push(`locked ${lockUpdates.length} crate(s) at a newer version in Cargo.lock`);
2080
+ const message2 = parts.join("; ");
2081
+ return {
2082
+ updatedCount: updates.length + lockUpdates.length,
2083
+ auditMessage: `${message2.charAt(0).toUpperCase()}${message2.slice(1)}.`,
2084
+ auditSeverity: "clean",
2085
+ integrityViolations: []
2086
+ };
2087
+ }
2088
+ /** A lock-only update needs a lock file that still holds the version the scan saw. */
2089
+ checkLockUpdates(lockUpdates, lockFile, locked) {
2090
+ if (lockUpdates.length === 0) return;
2091
+ if (!existsSync8(lockFile)) {
2092
+ throw new InstallError(`${lockFile} does not exist any more; rescan the module.`);
2093
+ }
2094
+ for (const update of lockUpdates) {
2095
+ const name = update.coordinate.identifier;
2096
+ if (!(locked.get(name) ?? []).includes(update.currentClean)) {
2097
+ throw new InstallError(`Cargo.lock no longer locks ${name} at ${update.currentClean}; rescan the module.`);
2098
+ }
2099
+ }
2100
+ }
2101
+ async resync() {
2102
+ }
2103
+ async refreshLock(dir, lockFile, before, updates, onProgress) {
2104
+ const specs = [];
2105
+ for (const update of updates) {
2106
+ const name = update.coordinate.identifier;
2107
+ const versions = before.get(name) ?? [];
2108
+ const old = versions.find((v) => satisfiesRequirement(update.currentRange, v)) ?? versions[0];
2109
+ if (old) specs.push(`${name}@${old}`);
2110
+ }
2111
+ if (specs.length === 0) return;
2112
+ onProgress("Updating Cargo.lock\u2026");
2113
+ await this.cargoUpdate(dir, specs.flatMap((s) => ["-p", s]));
2114
+ const after = readLockedVersions(lockFile);
2115
+ for (const update of updates) {
2116
+ const name = update.coordinate.identifier;
2117
+ const locked = (after.get(name) ?? []).find((v) => satisfiesRequirement(update.newRange, v));
2118
+ if (locked && semver9.valid(update.latest) && locked !== update.latest) {
2119
+ onProgress(`Pinning ${name} to ${update.latest}\u2026`);
2120
+ await this.cargoUpdate(dir, ["-p", `${name}@${locked}`, "--precise", update.latest]);
2121
+ }
2122
+ }
2123
+ }
2124
+ async cargoUpdate(dir, args) {
2125
+ const result = await runCommand("cargo", ["update", ...args], { cwd: dir, timeoutMs: UPDATE_TIMEOUT_MS });
2126
+ if (result.exitCode !== 0) {
2127
+ const reason = result.timedOut ? "cargo update timed out" : "cargo update failed";
2128
+ throw new InstallError(reason, tail(result.stderr || result.stdout));
2129
+ }
2130
+ }
2131
+ };
2132
+
2133
+ // src/adapters/cargo/cargo-verifier.ts
2134
+ import { existsSync as existsSync9 } from "fs";
2135
+ import { join as join13, relative as relative5, resolve as resolve7 } from "path";
2136
+ var VERIFY_TIMEOUT_MS2 = 30 * 6e4;
2137
+ var same = (a, b) => relative5(resolve7(a), resolve7(b)) === "";
2138
+ function planCheck(metadata, module, changedFiles, locked) {
2139
+ const args = ["check", "--all-targets", ...locked ? ["--locked"] : []];
2140
+ const rootManifest = join13(metadata.workspace_root, "Cargo.toml");
2141
+ const lockFile = join13(metadata.workspace_root, "Cargo.lock");
2142
+ const memberManifests = new Set(metadata.packages.map((p) => resolve7(p.manifest_path)));
2143
+ if (changedFiles.some((f) => same(f, rootManifest))) {
2144
+ const isVirtualRoot = !memberManifests.has(resolve7(rootManifest));
2145
+ if (isVirtualRoot || metadata.packages.length > 1) return [...args, "--workspace"];
2146
+ }
2147
+ if (changedFiles.length > 0 && changedFiles.every((f) => same(f, lockFile))) return [...args, "--workspace"];
2148
+ const changed = new Set(
2149
+ metadata.packages.filter((p) => changedFiles.some((f) => same(f, p.manifest_path))).map((p) => p.name)
2150
+ );
2151
+ if (changed.size === 0) changed.add(module.id);
2152
+ for (let grew = true; grew; ) {
2153
+ grew = false;
2154
+ for (const p of metadata.packages) {
2155
+ if (changed.has(p.name)) continue;
2156
+ if (p.dependencies.some((d) => d.source === null && d.path && changed.has(d.name))) {
2157
+ changed.add(p.name);
2158
+ grew = true;
2159
+ }
2160
+ }
2161
+ }
2162
+ const ordered = metadata.packages.filter((p) => changed.has(p.name)).map((p) => p.name);
2163
+ return [...args, ...ordered.flatMap((name) => ["-p", name])];
2164
+ }
2165
+ var CargoVerificationAdapter = class {
2166
+ constructor(deps = { loadMetadata, hasLock: (dir) => existsSync9(join13(dir, "Cargo.lock")) }) {
2167
+ this.deps = deps;
2168
+ }
2169
+ deps;
2170
+ async verify(module, rootDir, options = {}) {
2171
+ const dir = module.aggregatorDir ?? module.path ?? rootDir;
2172
+ if (options.customScript) {
2173
+ const label2 = options.customLabel || options.customScript;
2174
+ const result2 = await runShellScript(options.customScript, { cwd: dir, timeoutMs: VERIFY_TIMEOUT_MS2 });
2175
+ return result2.exitCode === 0 ? { status: "clean", details: "Verification script succeeded.", label: label2 } : { status: "warn", details: tail(result2.stderr || result2.stdout), label: label2 };
2176
+ }
2177
+ const metadata = await this.deps.loadMetadata(dir);
2178
+ const args = planCheck(metadata, module, options.changedFiles ?? [], this.deps.hasLock(dir));
2179
+ const label = `Cargo build (cargo ${args.join(" ")})`;
2180
+ const result = await runCargo(args, { cwd: dir, timeoutMs: VERIFY_TIMEOUT_MS2 });
2181
+ if (result.exitCode === 0) return { status: "clean", details: "All affected crates type-check, including tests.", label };
2182
+ return {
2183
+ status: "warn",
2184
+ details: result.timedOut ? "cargo check timed out." : cargoErrors(`${result.stderr}
2185
+ ${result.stdout}`),
2186
+ label
2187
+ };
2188
+ }
2189
+ };
2190
+
2191
+ // src/adapters/cargo/cargo-strategy.ts
2192
+ var cargoVersionPolicy = {
2193
+ normalize: (v) => semver10.valid(v),
2194
+ rewrite: rewriteRequirement,
2195
+ diff: (current, target) => semver10.valid(current) && semver10.valid(target) ? semver10.eq(current, target) ? "patch" : cargoDiff(current, target) : "minor",
2196
+ // Updates are written where the scan found the declaration.
2197
+ allowsManualTargets: false
2198
+ };
2199
+ var CargoEcosystemStrategy = class {
2200
+ ecosystem = "cargo";
2201
+ displayName = "Rust / Cargo";
2202
+ icon = "\u{1F980}";
2203
+ manifestFile = "Cargo.toml";
2204
+ versioning = cargoVersionPolicy;
2205
+ config = new CargoConfigReader();
2206
+ index = new CargoIndexClient();
2207
+ endpoint = (registry, cwd) => this.config.resolve(registry, cwd);
2208
+ discovery = new CargoModuleDiscoveryAdapter();
2209
+ registry = new CargoRegistryAdapter({ index: this.index, endpoint: this.endpoint });
2210
+ reader = new CargoDependencyAdapter({ loadMetadata, index: this.index, endpoint: this.endpoint });
2211
+ updater = new CargoUpdaterAdapter();
2212
+ verifier = new CargoVerificationAdapter();
2213
+ getTabLabels(counts, t) {
2214
+ return {
2215
+ all: t.tabs.all(counts.total),
2216
+ prod: t.tabs.dependencies(counts.prodCount),
2217
+ dev: t.tabs.devDependencies(counts.devCount)
2218
+ };
2219
+ }
2220
+ };
2221
+
2222
+ // src/adapters/maven/maven-discovery.ts
2223
+ import { existsSync as existsSync12 } from "fs";
2224
+ import { dirname as dirname7, join as join16, relative as relative6, resolve as resolve10 } from "path";
2225
+
2226
+ // src/adapters/maven/maven-project.ts
2227
+ import { existsSync as existsSync11, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
2228
+ import { join as join15, resolve as resolve9 } from "path";
2229
+
1195
2230
  // src/adapters/maven/maven-pom.ts
2231
+ import { existsSync as existsSync10, readFileSync as readFileSync13, statSync } from "fs";
2232
+ import { dirname as dirname6, join as join14, resolve as resolve8 } from "path";
1196
2233
  function readPom(file) {
1197
- const source = readFileSync8(file, "utf8");
2234
+ const source = readFileSync13(file, "utf8");
1198
2235
  return parsePom(source, file);
1199
2236
  }
1200
2237
  function parsePom(source, file) {
@@ -1213,7 +2250,7 @@ function parsePom(source, file) {
1213
2250
  }
1214
2251
  return {
1215
2252
  file,
1216
- dir: dirname4(file),
2253
+ dir: dirname6(file),
1217
2254
  groupId: childText(project, "groupId"),
1218
2255
  artifactId: childText(project, "artifactId") ?? "unknown",
1219
2256
  version: childText(project, "version"),
@@ -1242,9 +2279,9 @@ function parentPomFile(pom) {
1242
2279
  if (!pom.parent) return void 0;
1243
2280
  const relativePath = pom.parent.relativePath ?? "../pom.xml";
1244
2281
  if (relativePath === "") return void 0;
1245
- let candidate = resolve4(pom.dir, relativePath);
1246
- if (existsSync5(candidate) && statSync(candidate).isDirectory()) candidate = join9(candidate, "pom.xml");
1247
- return existsSync5(candidate) ? candidate : void 0;
2282
+ let candidate = resolve8(pom.dir, relativePath);
2283
+ if (existsSync10(candidate) && statSync(candidate).isDirectory()) candidate = join14(candidate, "pom.xml");
2284
+ return existsSync10(candidate) ? candidate : void 0;
1248
2285
  }
1249
2286
  function builtinProperty(name, module) {
1250
2287
  switch (name) {
@@ -1319,9 +2356,9 @@ var MavenProject = class _MavenProject {
1319
2356
  }
1320
2357
  poms.push(pom);
1321
2358
  for (const entry of pom.modules) {
1322
- const target = resolve5(pom.dir, entry);
1323
- const moduleFile = target.endsWith(".xml") ? target : join10(target, "pom.xml");
1324
- if (!seen.has(moduleFile) && existsSync6(moduleFile)) {
2359
+ const target = resolve9(pom.dir, entry);
2360
+ const moduleFile = target.endsWith(".xml") ? target : join15(target, "pom.xml");
2361
+ if (!seen.has(moduleFile) && existsSync11(moduleFile)) {
1325
2362
  seen.add(moduleFile);
1326
2363
  queue.push(moduleFile);
1327
2364
  }
@@ -1330,7 +2367,7 @@ var MavenProject = class _MavenProject {
1330
2367
  return new _MavenProject(rootDir, poms);
1331
2368
  }
1332
2369
  pomAt(file) {
1333
- return this.byFile.get(resolve5(file));
2370
+ return this.byFile.get(resolve9(file));
1334
2371
  }
1335
2372
  /** The project followed by its local parents, stopping at the first external parent. */
1336
2373
  chainOf(pom) {
@@ -1373,7 +2410,7 @@ var MavenProject = class _MavenProject {
1373
2410
  return groupId === parent.groupId && candidate.artifactId === parent.artifactId && (version.includes("${") || expected.includes("${") || version === expected);
1374
2411
  };
1375
2412
  const file = parentPomFile(pom);
1376
- const byPath = file ? this.byFile.get(resolve5(file)) : void 0;
2413
+ const byPath = file ? this.byFile.get(resolve9(file)) : void 0;
1377
2414
  return matches(byPath) ? byPath : void 0;
1378
2415
  }
1379
2416
  /**
@@ -1390,8 +2427,8 @@ var MavenProject = class _MavenProject {
1390
2427
  if (this.aggregators.has(pom.file)) continue;
1391
2428
  this.aggregators.set(pom.file, aggregator.dir);
1392
2429
  for (const entry of pom.modules) {
1393
- const target = resolve5(pom.dir, entry);
1394
- const file = target.endsWith(".xml") ? target : join10(target, "pom.xml");
2430
+ const target = resolve9(pom.dir, entry);
2431
+ const file = target.endsWith(".xml") ? target : join15(target, "pom.xml");
1395
2432
  const modulePom = this.byFile.get(file);
1396
2433
  if (modulePom) queue.push(modulePom);
1397
2434
  }
@@ -1401,23 +2438,23 @@ var MavenProject = class _MavenProject {
1401
2438
  };
1402
2439
  function findPomFiles(rootDir) {
1403
2440
  const results = [];
1404
- const rootPom = join10(rootDir, "pom.xml");
1405
- if (existsSync6(rootPom)) results.push(resolve5(rootPom));
2441
+ const rootPom = join15(rootDir, "pom.xml");
2442
+ if (existsSync11(rootPom)) results.push(resolve9(rootPom));
1406
2443
  const walk = (dir, depth) => {
1407
2444
  if (depth > MAX_DEPTH) return;
1408
2445
  let entries2;
1409
2446
  try {
1410
- entries2 = readdirSync(dir, { withFileTypes: true });
2447
+ entries2 = readdirSync2(dir, { withFileTypes: true });
1411
2448
  } catch {
1412
2449
  return;
1413
2450
  }
1414
- const isProject = existsSync6(join10(dir, "pom.xml"));
2451
+ const isProject = existsSync11(join15(dir, "pom.xml"));
1415
2452
  for (const entry of entries2) {
1416
2453
  if (!entry.isDirectory() || entry.name.startsWith(".") || SKIPPED_DIRS.has(entry.name)) continue;
1417
2454
  if (isProject && entry.name === "src") continue;
1418
- const subDir = join10(dir, entry.name);
1419
- const pom = join10(subDir, "pom.xml");
1420
- if (existsSync6(pom) && statSync2(pom).isFile()) results.push(resolve5(pom));
2455
+ const subDir = join15(dir, entry.name);
2456
+ const pom = join15(subDir, "pom.xml");
2457
+ if (existsSync11(pom) && statSync2(pom).isFile()) results.push(resolve9(pom));
1421
2458
  walk(subDir, depth + 1);
1422
2459
  }
1423
2460
  };
@@ -1432,9 +2469,9 @@ var MavenModuleDiscoveryAdapter = class {
1432
2469
  * the POM above until there is none: the top of the connected POM chain.
1433
2470
  */
1434
2471
  findRoot(startDir, boundaryDir) {
1435
- let dir = resolve6(startDir);
1436
- if (!existsSync7(join11(dir, "pom.xml"))) return void 0;
1437
- const boundary = boundaryDir ? resolve6(boundaryDir) : void 0;
2472
+ let dir = resolve10(startDir);
2473
+ if (!existsSync12(join16(dir, "pom.xml"))) return void 0;
2474
+ const boundary = boundaryDir ? resolve10(boundaryDir) : void 0;
1438
2475
  for (; ; ) {
1439
2476
  const above = pomAbove(dir, boundary);
1440
2477
  if (!above) return dir;
@@ -1445,7 +2482,7 @@ var MavenModuleDiscoveryAdapter = class {
1445
2482
  const project = MavenProject.load(rootDir);
1446
2483
  const modules = project.poms.map((pom) => {
1447
2484
  const coordinates = project.coordinatesOf(pom);
1448
- const relPath = relative3(rootDir, pom.dir).replace(/\\/g, "/");
2485
+ const relPath = relative6(rootDir, pom.dir).replace(/\\/g, "/");
1449
2486
  return {
1450
2487
  id: coordinates.groupId ? `${coordinates.groupId}:${coordinates.artifactId}` : coordinates.artifactId,
1451
2488
  name: pom.name ? interpolate(pom.name, project.chainOf(pom).poms) : pom.artifactId,
@@ -1472,7 +2509,7 @@ function ancestorsWithin(dir, boundary) {
1472
2509
  const result = [];
1473
2510
  let current = dir;
1474
2511
  while (current !== boundary) {
1475
- const parent = dirname5(current);
2512
+ const parent = dirname7(current);
1476
2513
  if (parent === current) break;
1477
2514
  result.push(parent);
1478
2515
  current = parent;
@@ -1480,22 +2517,22 @@ function ancestorsWithin(dir, boundary) {
1480
2517
  return result;
1481
2518
  }
1482
2519
  function pomAbove(dir, boundary) {
1483
- const pom = tryReadPom(join11(dir, "pom.xml"));
2520
+ const pom = tryReadPom(join16(dir, "pom.xml"));
1484
2521
  if (!pom) return void 0;
1485
2522
  const ancestors = ancestorsWithin(dir, boundary);
1486
2523
  const found = [];
1487
2524
  const parentFile = parentPomFile(pom);
1488
- if (parentFile && pom.parent && ancestors.includes(dirname5(parentFile))) {
2525
+ if (parentFile && pom.parent && ancestors.includes(dirname7(parentFile))) {
1489
2526
  const parent = tryReadPom(parentFile);
1490
2527
  const groupId = parent?.groupId ?? parent?.parent?.groupId;
1491
2528
  if (parent && parent.artifactId === pom.parent.artifactId && groupId === pom.parent.groupId) {
1492
- found.push(dirname5(parentFile));
2529
+ found.push(dirname7(parentFile));
1493
2530
  }
1494
2531
  }
1495
- const ownFile = join11(dir, "pom.xml");
2532
+ const ownFile = join16(dir, "pom.xml");
1496
2533
  const aggregator = ancestors.find(
1497
- (ancestor) => tryReadPom(join11(ancestor, "pom.xml"))?.modules.some((entry) => {
1498
- const target = resolve6(ancestor, entry);
2534
+ (ancestor) => tryReadPom(join16(ancestor, "pom.xml"))?.modules.some((entry) => {
2535
+ const target = resolve10(ancestor, entry);
1499
2536
  return target === dir || target === ownFile;
1500
2537
  })
1501
2538
  );
@@ -1504,12 +2541,12 @@ function pomAbove(dir, boundary) {
1504
2541
  }
1505
2542
 
1506
2543
  // src/adapters/maven/maven-registry.ts
1507
- import { existsSync as existsSync10 } from "fs";
1508
- import { join as join14 } from "path";
2544
+ import { existsSync as existsSync15 } from "fs";
2545
+ import { join as join19 } from "path";
1509
2546
 
1510
2547
  // src/adapters/maven/maven-command.ts
1511
- import { existsSync as existsSync8 } from "fs";
1512
- import { dirname as dirname6, isAbsolute, join as join12, relative as relative4, resolve as resolve7, sep } from "path";
2548
+ import { existsSync as existsSync13 } from "fs";
2549
+ import { dirname as dirname8, isAbsolute as isAbsolute2, join as join17, relative as relative7, resolve as resolve11, sep } from "path";
1513
2550
  var MavenUnavailableError = class extends Error {
1514
2551
  constructor() {
1515
2552
  super("Maven is not available: no Maven Wrapper (mvnw) in the project and no `mvn` on the PATH.");
@@ -1529,27 +2566,27 @@ var WRAPPER = IS_WINDOWS2 ? "mvnw.cmd" : "mvnw";
1529
2566
  var BATCH_ARGS = ["-B", "--no-transfer-progress"];
1530
2567
  var mvnOnPath;
1531
2568
  function findMavenWrapper(fromDir, rootDir) {
1532
- let dir = resolve7(fromDir);
1533
- const root = resolve7(rootDir);
2569
+ let dir = resolve11(fromDir);
2570
+ const root = resolve11(rootDir);
1534
2571
  for (; ; ) {
1535
- const candidate = join12(dir, WRAPPER);
1536
- if (existsSync8(candidate)) return candidate;
1537
- if (dir === root || relative4(root, dir).startsWith("..")) return void 0;
1538
- const parent = dirname6(dir);
2572
+ const candidate = join17(dir, WRAPPER);
2573
+ if (existsSync13(candidate)) return candidate;
2574
+ if (dir === root || relative7(root, dir).startsWith("..")) return void 0;
2575
+ const parent = dirname8(dir);
1539
2576
  if (parent === dir) return void 0;
1540
2577
  dir = parent;
1541
2578
  }
1542
2579
  }
1543
2580
  function relativeToCwd(cwd, file) {
1544
- const path = relative4(cwd, file);
1545
- return isAbsolute(path) ? file : path || ".";
2581
+ const path = relative7(cwd, file);
2582
+ return isAbsolute2(path) ? file : path || ".";
1546
2583
  }
1547
2584
  async function runMaven(args, options) {
1548
2585
  const wrapper = findMavenWrapper(options.cwd, options.rootDir);
1549
2586
  const run = { cwd: options.cwd, timeoutMs: options.timeoutMs };
1550
2587
  if (wrapper) {
1551
2588
  const relativeWrapper = relativeToCwd(options.cwd, wrapper);
1552
- const path = isAbsolute(relativeWrapper) || relativeWrapper.includes(sep) ? relativeWrapper : `.${sep}${relativeWrapper}`;
2589
+ const path = isAbsolute2(relativeWrapper) || relativeWrapper.includes(sep) ? relativeWrapper : `.${sep}${relativeWrapper}`;
1553
2590
  return IS_WINDOWS2 ? runCommand(path, [...BATCH_ARGS, ...args], run) : runCommand("sh", [path, ...BATCH_ARGS, ...args], run);
1554
2591
  }
1555
2592
  mvnOnPath ??= runCommand("mvn", ["-v"], { cwd: options.cwd, timeoutMs: 3e4 }).then(
@@ -1566,8 +2603,8 @@ function mavenErrors(output) {
1566
2603
 
1567
2604
  // src/adapters/maven/maven-resolution.ts
1568
2605
  import { randomUUID } from "crypto";
1569
- import { existsSync as existsSync9, mkdirSync, readFileSync as readFileSync9, readdirSync as readdirSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
1570
- import { join as join13 } from "path";
2606
+ import { existsSync as existsSync14, mkdirSync, readFileSync as readFileSync14, readdirSync as readdirSync3, rmSync, writeFileSync as writeFileSync3 } from "fs";
2607
+ import { join as join18 } from "path";
1571
2608
  var DEFAULT_MAVEN_PLUGINS = { help: "3.5.2", versions: "2.22.0" };
1572
2609
  var HELP_PLUGIN_ID = "org.apache.maven.plugins:maven-help-plugin";
1573
2610
  var VERSIONS_PLUGIN_ID = "org.codehaus.mojo:versions-maven-plugin";
@@ -1599,21 +2636,21 @@ var ScratchDirectory = class {
1599
2636
  target;
1600
2637
  createdTarget;
1601
2638
  constructor(moduleDir) {
1602
- this.target = join13(moduleDir, "target");
1603
- this.createdTarget = !existsSync9(this.target);
1604
- this.path = join13(this.target, `.elevate-${randomUUID().slice(0, 8)}`);
2639
+ this.target = join18(moduleDir, "target");
2640
+ this.createdTarget = !existsSync14(this.target);
2641
+ this.path = join18(this.target, `.elevate-${randomUUID().slice(0, 8)}`);
1605
2642
  mkdirSync(this.path, { recursive: true });
1606
2643
  }
1607
2644
  dispose() {
1608
2645
  rmSync(this.path, { recursive: true, force: true });
1609
2646
  try {
1610
- if (this.createdTarget && readdirSync2(this.target).length === 0) rmSync(this.target, { recursive: true });
2647
+ if (this.createdTarget && readdirSync3(this.target).length === 0) rmSync(this.target, { recursive: true });
1611
2648
  } catch {
1612
2649
  }
1613
2650
  }
1614
2651
  };
1615
2652
  async function readEffectivePoms(pomFile, moduleDir, rootDir, scratch, plugins, recursive = false) {
1616
- const output = join13(scratch.path, "effective-pom.xml");
2653
+ const output = join18(scratch.path, "effective-pom.xml");
1617
2654
  const args = [
1618
2655
  "-f",
1619
2656
  relativeToCwd(moduleDir, pomFile),
@@ -1625,7 +2662,7 @@ async function readEffectivePoms(pomFile, moduleDir, rootDir, scratch, plugins,
1625
2662
  rootDir,
1626
2663
  timeoutMs: MAVEN_TIMEOUT_MS
1627
2664
  });
1628
- if (result.exitCode !== 0 || !existsSync9(output)) {
2665
+ if (result.exitCode !== 0 || !existsSync14(output)) {
1629
2666
  const text = `${result.stdout}
1630
2667
  ${result.stderr}`;
1631
2668
  if (isPluginUnavailable(text, HELP_PLUGIN_ID)) {
@@ -1637,7 +2674,7 @@ ${result.stderr}`;
1637
2674
  throw new MavenCommandError(`Maven could not build the effective POM:
1638
2675
  ${mavenErrors(text)}`, text);
1639
2676
  }
1640
- return parseEffectivePoms(readFileSync9(output, "utf8"));
2677
+ return parseEffectivePoms(readFileSync14(output, "utf8"));
1641
2678
  }
1642
2679
  function parseEffectivePoms(source) {
1643
2680
  const root = parseXml(source);
@@ -1649,7 +2686,7 @@ function parseEffectivePoms(source) {
1649
2686
  return models;
1650
2687
  }
1651
2688
  function mergeRepositories(models) {
1652
- const merge = (name, pick) => {
2689
+ const merge2 = (name, pick) => {
1653
2690
  const byId = /* @__PURE__ */ new Map();
1654
2691
  for (const element of models.map(pick)) {
1655
2692
  for (const repository of element?.children ?? []) {
@@ -1662,8 +2699,8 @@ function mergeRepositories(models) {
1662
2699
  return {
1663
2700
  dependencies: [],
1664
2701
  managedDependencies: [],
1665
- repositories: merge("repositories", (m) => m.repositories),
1666
- pluginRepositories: merge("pluginRepositories", (m) => m.pluginRepositories)
2702
+ repositories: merge2("repositories", (m) => m.repositories),
2703
+ pluginRepositories: merge2("pluginRepositories", (m) => m.pluginRepositories)
1667
2704
  };
1668
2705
  }
1669
2706
  function toEffectiveModel(project) {
@@ -1721,10 +2758,10 @@ function buildProbePom(artifacts, model) {
1721
2758
  }
1722
2759
  async function queryNewerVersions(artifacts, model, moduleDir, rootDir, scratch, plugins) {
1723
2760
  if (artifacts.length === 0) return { newerVersions: /* @__PURE__ */ new Map(), failedLookups: /* @__PURE__ */ new Set() };
1724
- const probeDir = join13(scratch.path, "probe");
2761
+ const probeDir = join18(scratch.path, "probe");
1725
2762
  mkdirSync(probeDir, { recursive: true });
1726
- const probePom = join13(probeDir, "pom.xml");
1727
- writeFileSync2(probePom, buildProbePom(artifacts, model), "utf8");
2763
+ const probePom = join18(probeDir, "pom.xml");
2764
+ writeFileSync3(probePom, buildProbePom(artifacts, model), "utf8");
1728
2765
  const result = await runMaven(
1729
2766
  [
1730
2767
  "-N",
@@ -1738,8 +2775,8 @@ async function queryNewerVersions(artifacts, model, moduleDir, rootDir, scratch,
1738
2775
  );
1739
2776
  const log = `${result.stdout}
1740
2777
  ${result.stderr}`;
1741
- const reportFile = join13(probeDir, "target", "dependency-updates-report.xml");
1742
- if (result.exitCode !== 0 || !existsSync9(reportFile)) {
2778
+ const reportFile = join18(probeDir, "target", "dependency-updates-report.xml");
2779
+ if (result.exitCode !== 0 || !existsSync14(reportFile)) {
1743
2780
  if (isPluginUnavailable(log, VERSIONS_PLUGIN_ID)) {
1744
2781
  throw new MavenPluginUnavailableError(
1745
2782
  { id: VERSIONS_PLUGIN_ID, version: plugins.versions, key: "versions", purpose: "look up newer versions" },
@@ -1750,7 +2787,7 @@ ${result.stderr}`;
1750
2787
  ${mavenErrors(log)}`, log);
1751
2788
  }
1752
2789
  return {
1753
- newerVersions: parseUpdatesReport(readFileSync9(reportFile, "utf8")),
2790
+ newerVersions: parseUpdatesReport(readFileSync14(reportFile, "utf8")),
1754
2791
  failedLookups: parseFailedLookups(log)
1755
2792
  };
1756
2793
  }
@@ -1835,18 +2872,18 @@ var MavenRegistryAdapter = class {
1835
2872
  }
1836
2873
  };
1837
2874
  async function projectRepositories(cwd, scratch, plugins) {
1838
- const rootPom = join14(cwd, "pom.xml");
1839
- if (!existsSync10(rootPom)) return NO_REPOSITORIES;
2875
+ const rootPom = join19(cwd, "pom.xml");
2876
+ if (!existsSync15(rootPom)) return NO_REPOSITORIES;
1840
2877
  const models = await readEffectivePoms(rootPom, cwd, cwd, scratch, plugins);
1841
2878
  return [...models.values()][0] ?? NO_REPOSITORIES;
1842
2879
  }
1843
2880
 
1844
2881
  // src/adapters/maven/maven-scanner.ts
1845
- import { join as join15 } from "path";
1846
- import semver4 from "semver";
2882
+ import { join as join20 } from "path";
2883
+ import semver11 from "semver";
1847
2884
 
1848
2885
  // src/adapters/maven/maven-locator.ts
1849
- import { relative as relative5 } from "path";
2886
+ import { relative as relative8 } from "path";
1850
2887
  function locateDependencyVersion(chain, target, rootDir) {
1851
2888
  for (const pom of chain) {
1852
2889
  const dependency = findDependency(pom.dependencies, target, chain);
@@ -1927,13 +2964,13 @@ function resolveLiteral(pom, kind, versionElement, chain, target, rootDir) {
1927
2964
  file,
1928
2965
  kind: propertyName ? "property" : kind,
1929
2966
  propertyName,
1930
- displayPath: relative5(rootDir, file).replace(/\\/g, "/")
2967
+ displayPath: relative8(rootDir, file).replace(/\\/g, "/")
1931
2968
  };
1932
2969
  return { found: true, declaration, element, value };
1933
2970
  }
1934
2971
 
1935
2972
  // src/adapters/maven/maven-scanner.ts
1936
- var DIFF_ORDER2 = { major: 0, minor: 1, patch: 2 };
2973
+ var DIFF_ORDER3 = { major: 0, minor: 1, patch: 2 };
1937
2974
  var MavenDependencyAdapter = class {
1938
2975
  constructor(plugins = DEFAULT_MAVEN_PLUGINS) {
1939
2976
  this.plugins = plugins;
@@ -1987,14 +3024,14 @@ function groupModules(project, modules) {
1987
3024
  if (module.aggregatorDir) reactorSizes.set(module.aggregatorDir, (reactorSizes.get(module.aggregatorDir) ?? 0) + 1);
1988
3025
  }
1989
3026
  for (const module of modules) {
1990
- const pom = project.pomAt(join15(module.path, "pom.xml"));
3027
+ const pom = project.pomAt(join20(module.path, "pom.xml"));
1991
3028
  if (!pom) continue;
1992
3029
  const reactor = module.aggregatorDir;
1993
3030
  const batched = reactor !== void 0 && (reactorSizes.get(reactor) ?? 0) > 1;
1994
3031
  const key = batched ? `reactor:${reactor}` : `module:${module.path}`;
1995
3032
  const group = groups.get(key) ?? {
1996
3033
  dir: batched ? reactor : module.path,
1997
- aggregatorPom: batched ? join15(reactor, "pom.xml") : void 0,
3034
+ aggregatorPom: batched ? join20(reactor, "pom.xml") : void 0,
1998
3035
  members: []
1999
3036
  };
2000
3037
  group.members.push({ module, pom });
@@ -2083,7 +3120,7 @@ function finishModule(plan, report, channel) {
2083
3120
  collect2(updateCandidate(subject, latest, newer, origin), candidates, skipped);
2084
3121
  }
2085
3122
  markSharedDeclarations(candidates);
2086
- candidates.sort((a, b) => (DIFF_ORDER2[a.diff] ?? 3) - (DIFF_ORDER2[b.diff] ?? 3));
3123
+ candidates.sort((a, b) => (DIFF_ORDER3[a.diff] ?? 3) - (DIFF_ORDER3[b.diff] ?? 3));
2087
3124
  return { candidates, skipped };
2088
3125
  }
2089
3126
  function newerThan(current, ascending) {
@@ -2114,7 +3151,7 @@ function buildCandidate(subject, target, origin, action) {
2114
3151
  }
2115
3152
  const currentClean = cleanJavaVersion(subject.version) ?? "0.0.0";
2116
3153
  const targetClean = cleanJavaVersion(target) ?? currentClean;
2117
- const diff = semver4.eq(currentClean, targetClean) ? "patch" : diffVersions(currentClean, targetClean);
3154
+ const diff = semver11.eq(currentClean, targetClean) ? "patch" : diffVersions(currentClean, targetClean);
2118
3155
  const isPreRelease = isPreReleaseVersion(target);
2119
3156
  return {
2120
3157
  candidate: {
@@ -2162,11 +3199,11 @@ function markSharedDeclarations(candidates) {
2162
3199
  }
2163
3200
 
2164
3201
  // src/adapters/maven/maven-updater.ts
2165
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync3 } from "fs";
2166
- import { join as join16, resolve as resolve8 } from "path";
3202
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync4 } from "fs";
3203
+ import { join as join21, resolve as resolve12 } from "path";
2167
3204
  var MavenUpdaterAdapter = class {
2168
3205
  affectedFiles(module, _rootDir, updates) {
2169
- const files = new Set(updates.map((u) => resolve8(u.declaration?.file ?? join16(module.path, "pom.xml"))));
3206
+ const files = new Set(updates.map((u) => resolve12(u.declaration?.file ?? join21(module.path, "pom.xml"))));
2170
3207
  return [...files];
2171
3208
  }
2172
3209
  async applyUpdates(module, rootDir, updates, onProgress) {
@@ -2176,13 +3213,13 @@ var MavenUpdaterAdapter = class {
2176
3213
  if (!update.declaration) {
2177
3214
  throw new InstallError(`No version declaration known for ${update.coordinate.identifier}; rescan the module.`);
2178
3215
  }
2179
- const file = resolve8(update.declaration.file);
3216
+ const file = resolve12(update.declaration.file);
2180
3217
  byFile.set(file, [...byFile.get(file) ?? [], update]);
2181
3218
  }
2182
3219
  const writes = [];
2183
3220
  for (const [file, fileUpdates] of byFile) {
2184
3221
  onProgress(`Preparing ${fileUpdates.length} change(s) in ${fileUpdates[0].declaration.displayPath}\u2026`);
2185
- const source = readFileSync10(file, "utf8");
3222
+ const source = readFileSync15(file, "utf8");
2186
3223
  const pom = parsePom(source, file);
2187
3224
  const chain = project.pomAt(file) ? project.chainOf(project.pomAt(file)).poms : [pom];
2188
3225
  const edits = collectEdits(pom, [pom, ...chain.slice(1)], fileUpdates);
@@ -2194,7 +3231,7 @@ var MavenUpdaterAdapter = class {
2194
3231
  }
2195
3232
  }
2196
3233
  onProgress("Writing POM files\u2026");
2197
- for (const write of writes) writeFileSync3(write.file, write.content, "utf8");
3234
+ for (const write of writes) writeFileSync4(write.file, write.content, "utf8");
2198
3235
  return {
2199
3236
  updatedCount: updates.length,
2200
3237
  auditMessage: `Updated ${updates.length} version declaration(s) in ${writes.length} POM file(s).`,
@@ -2252,13 +3289,13 @@ function describe(declaration) {
2252
3289
  }
2253
3290
 
2254
3291
  // src/adapters/maven/maven-verifier.ts
2255
- import { dirname as dirname7, join as join17, relative as relative6, resolve as resolve9 } from "path";
2256
- var VERIFY_TIMEOUT_MS2 = 30 * 6e4;
3292
+ import { dirname as dirname9, join as join22, relative as relative9, resolve as resolve13 } from "path";
3293
+ var VERIFY_TIMEOUT_MS3 = 30 * 6e4;
2257
3294
  var MavenVerificationAdapter = class {
2258
3295
  async verify(module, rootDir, options = {}) {
2259
3296
  if (options.customScript) {
2260
3297
  const label2 = options.customLabel || options.customScript;
2261
- const result = await runShellScript(options.customScript, { cwd: module.path, timeoutMs: VERIFY_TIMEOUT_MS2 });
3298
+ const result = await runShellScript(options.customScript, { cwd: module.path, timeoutMs: VERIFY_TIMEOUT_MS3 });
2262
3299
  return result.exitCode === 0 ? { status: "clean", details: "Verification script succeeded.", label: label2 } : { status: "warn", details: failureDetails2(result), label: label2 };
2263
3300
  }
2264
3301
  const plan = planBuild(module, rootDir, options.changedFiles ?? []);
@@ -2267,7 +3304,7 @@ var MavenVerificationAdapter = class {
2267
3304
  const result = await runMaven(["-q", "test-compile", ...plan.args], {
2268
3305
  cwd: plan.cwd,
2269
3306
  rootDir,
2270
- timeoutMs: VERIFY_TIMEOUT_MS2
3307
+ timeoutMs: VERIFY_TIMEOUT_MS3
2271
3308
  });
2272
3309
  if (result.exitCode === 0) {
2273
3310
  return { status: "clean", details: "All affected modules compile, including tests.", label };
@@ -2289,15 +3326,15 @@ function planBuild(module, rootDir, changedFiles) {
2289
3326
  const aggregatorDir = module.aggregatorDir;
2290
3327
  if (!aggregatorDir) return { cwd: module.path, args: [] };
2291
3328
  const project = MavenProject.load(rootDir);
2292
- const files = changedFiles.length > 0 ? changedFiles : [join17(module.path, "pom.xml")];
3329
+ const files = changedFiles.length > 0 ? changedFiles : [join22(module.path, "pom.xml")];
2293
3330
  const selected = /* @__PURE__ */ new Set();
2294
3331
  for (const file of files) {
2295
3332
  const pom = project.pomAt(file);
2296
- const dir = resolve9(dirname7(file));
2297
- if (!pom || project.aggregatorDirOf(pom) !== aggregatorDir || dir === resolve9(aggregatorDir)) {
3333
+ const dir = resolve13(dirname9(file));
3334
+ if (!pom || project.aggregatorDirOf(pom) !== aggregatorDir || dir === resolve13(aggregatorDir)) {
2298
3335
  return { cwd: aggregatorDir, args: [] };
2299
3336
  }
2300
- selected.add(relative6(aggregatorDir, dir).replace(/\\/g, "/"));
3337
+ selected.add(relative9(aggregatorDir, dir).replace(/\\/g, "/"));
2301
3338
  }
2302
3339
  return { cwd: aggregatorDir, args: ["-pl", [...selected].join(","), "-amd"] };
2303
3340
  }
@@ -2308,6 +3345,7 @@ var MavenEcosystemStrategy = class {
2308
3345
  displayName = "Java / Maven";
2309
3346
  icon = "\u2615";
2310
3347
  manifestFile = "pom.xml";
3348
+ versioning = mavenVersionPolicy;
2311
3349
  discovery = new MavenModuleDiscoveryAdapter();
2312
3350
  registry;
2313
3351
  reader;
@@ -2345,6 +3383,8 @@ var EcosystemFactory = class {
2345
3383
  if (!strategy) {
2346
3384
  if (ecosystem === "maven") {
2347
3385
  strategy = new MavenEcosystemStrategy(this.settings.mavenPlugins);
3386
+ } else if (ecosystem === "cargo") {
3387
+ strategy = new CargoEcosystemStrategy();
2348
3388
  } else {
2349
3389
  strategy = new NpmEcosystemStrategy();
2350
3390
  }
@@ -2354,18 +3394,23 @@ var EcosystemFactory = class {
2354
3394
  }
2355
3395
  /** All supported ecosystems. */
2356
3396
  static getAvailableEcosystems() {
2357
- return ["npm", "maven"];
3397
+ return ["npm", "maven", "cargo"];
3398
+ }
3399
+ /** The ecosystem after `current`, for the dashboard's ecosystem switch. */
3400
+ static next(current) {
3401
+ const all = this.getAvailableEcosystems();
3402
+ return all[(all.indexOf(current) + 1) % all.length];
2358
3403
  }
2359
3404
  };
2360
3405
 
2361
3406
  // src/config.ts
2362
3407
  function findRepositoryRoot(startDir) {
2363
- const start = resolve10(startDir);
3408
+ const start = resolve14(startDir);
2364
3409
  const boundary = findCheckoutBoundary(start);
2365
3410
  let dir = start;
2366
3411
  for (; ; ) {
2367
- if (existsSync11(join18(dir, "elevate.config.json"))) return dir;
2368
- const parent = dirname8(dir);
3412
+ if (existsSync16(join23(dir, "elevate.config.json"))) return dir;
3413
+ const parent = dirname10(dir);
2369
3414
  if (dir === boundary || parent === dir) break;
2370
3415
  dir = parent;
2371
3416
  }
@@ -2430,15 +3475,15 @@ function resolveConfig(rootDir, file, source = "elevate.config.json") {
2430
3475
  }
2431
3476
  function loadConfig2(startDir = process.cwd()) {
2432
3477
  const rootDir = findRepositoryRoot(startDir);
2433
- const configPath = join18(rootDir, "elevate.config.json");
3478
+ const configPath = join23(rootDir, "elevate.config.json");
2434
3479
  let file = {};
2435
- if (existsSync11(configPath)) {
3480
+ if (existsSync16(configPath)) {
2436
3481
  const unreadable = (detail) => new ConfigError(
2437
3482
  `Cannot read elevate.config.json: ${detail} (${configPath}). Fix the file or remove it; ${NO_CONTINUE}`
2438
3483
  );
2439
3484
  let parsed;
2440
3485
  try {
2441
- parsed = JSON.parse(readFileSync11(configPath, "utf8").replace(/^/, ""));
3486
+ parsed = JSON.parse(readFileSync16(configPath, "utf8").replace(/^/, ""));
2442
3487
  } catch (err) {
2443
3488
  throw unreadable(err instanceof Error ? err.message : String(err));
2444
3489
  }
@@ -2499,7 +3544,9 @@ var de = {
2499
3544
  dev: "(dev/test)",
2500
3545
  prod: "(prod/compile)",
2501
3546
  align: "[Angleichen]",
2502
- internal: "[Intern]"
3547
+ internal: "[Intern]",
3548
+ lock: "[Lock]",
3549
+ shared: (count) => `[+${count} Crates]`
2503
3550
  },
2504
3551
  status: {
2505
3552
  selectedOf: (selected, total) => `${selected} von ${total}`,
@@ -2528,6 +3575,7 @@ var de = {
2528
3575
  hint: "[\u2191/\u2193/j/k] Navigieren \u2502 [Enter] Ausw\xE4hlen \u2502 [Esc] Abbrechen \u2502 [Backspace] Filter l\xF6schen",
2529
3576
  latestBadge: "LATEST",
2530
3577
  currentBadge: "AKTUELL",
3578
+ alsoChanges: (names) => `\xC4ndert auch: ${names.join(", ")}`,
2531
3579
  customBadge: "MANUELL"
2532
3580
  },
2533
3581
  updating: {
@@ -2556,6 +3604,7 @@ var de = {
2556
3604
  scanningChirps: ["*such*", "pr\xFCfe...", "oh, Updates?", "einen Moment!"],
2557
3605
  updatingChirps: ["*schuft*", "baue Code...", "gleich fertig!", "arbeite emsig..."],
2558
3606
  successChirps: ["hurra! \u{1F389}", "alles aktuell!", "top gepflegt! \u{1FAB6}", "super gemacht!"],
3607
+ cargoChirps: ["*krabbel* \u{1F980}", "cargo check l\xE4uft durch!", "Borrow Checker nickt!", "furchtlose Updates!"],
2559
3608
  mavenChirps: ["*kaffee schl\xFCrf* \u2615", "Spring Boot l\xE4uft!", "Java 25 rockt!", "sauber gebaut!"]
2560
3609
  },
2561
3610
  splash: {
@@ -2607,7 +3656,9 @@ var en = {
2607
3656
  dev: "(dev/test)",
2608
3657
  prod: "(prod/compile)",
2609
3658
  align: "[Align]",
2610
- internal: "[Internal]"
3659
+ internal: "[Internal]",
3660
+ lock: "[Lock]",
3661
+ shared: (count) => `[+${count} crates]`
2611
3662
  },
2612
3663
  status: {
2613
3664
  selectedOf: (selected, total) => `${selected} of ${total}`,
@@ -2636,6 +3687,7 @@ var en = {
2636
3687
  hint: "[\u2191/\u2193/j/k] Navigate \u2502 [Enter] Select \u2502 [Esc] Cancel \u2502 [Backspace] Clear filter",
2637
3688
  latestBadge: "LATEST",
2638
3689
  currentBadge: "CURRENT",
3690
+ alsoChanges: (names) => `Also changes: ${names.join(", ")}`,
2639
3691
  customBadge: "MANUAL"
2640
3692
  },
2641
3693
  updating: {
@@ -2664,6 +3716,7 @@ var en = {
2664
3716
  scanningChirps: ["*peeking*", "checking...", "ooh, updates?", "just a sec!"],
2665
3717
  updatingChirps: ["*working hard*", "building...", "almost there!", "packing modules..."],
2666
3718
  successChirps: ["hooray! \u{1F389}", "all updated!", "looking sharp! \u{1FAB6}", "great job!"],
3719
+ cargoChirps: ["*crab walk* \u{1F980}", "cargo check passes!", "borrow checker approves!", "fearless updates!"],
2667
3720
  mavenChirps: ["*sipping coffee* \u2615", "Spring Boot rocks!", "Java 25 is fly!", "clean build!"]
2668
3721
  },
2669
3722
  splash: {
@@ -2700,48 +3753,12 @@ function useI18n(initialLocale = "de") {
2700
3753
 
2701
3754
  // src/hooks/use-workspaces.ts
2702
3755
  import { useState as useState2, useEffect, useCallback as useCallback2 } from "react";
2703
- function useWorkspaces(strategy, rootDir) {
2704
- const [modules, setModules] = useState2([]);
2705
- const [loading, setLoading] = useState2(true);
2706
- const [activeIndex, setActiveIndex] = useState2(0);
2707
- const reload = useCallback2(async () => {
2708
- setLoading(true);
2709
- setModules([]);
2710
- setActiveIndex(0);
2711
- try {
2712
- setModules(await strategy.discovery.discover(rootDir));
2713
- } catch {
2714
- setModules([]);
2715
- } finally {
2716
- setLoading(false);
2717
- }
2718
- }, [strategy, rootDir]);
2719
- useEffect(() => {
2720
- reload();
2721
- }, [reload]);
2722
- const active = modules[activeIndex] || {
2723
- id: "empty",
2724
- name: "No module found",
2725
- path: rootDir,
2726
- relPath: "Root",
2727
- ecosystem: strategy.ecosystem,
2728
- isRoot: true
2729
- };
2730
- return {
2731
- modules,
2732
- active,
2733
- activeIndex,
2734
- setActiveIndex,
2735
- loading,
2736
- reload
2737
- };
2738
- }
2739
-
2740
- // src/hooks/use-packages.ts
2741
- import { useState as useState3, useEffect as useEffect2, useMemo as useMemo2, useCallback as useCallback3, useRef } from "react";
2742
- import semver5 from "semver";
2743
3756
 
2744
3757
  // src/application/scan.ts
3758
+ async function discoverModules(strategy, rootDir) {
3759
+ const modules = await strategy.discovery.discover(rootDir);
3760
+ return { modules, diagnostics: strategy.discovery.diagnostics?.() ?? [] };
3761
+ }
2745
3762
  function createScanContext(modules, config, channel) {
2746
3763
  return {
2747
3764
  rootDir: config.rootDir,
@@ -2801,7 +3818,52 @@ function candidateToJson(u) {
2801
3818
  };
2802
3819
  }
2803
3820
 
3821
+ // src/hooks/use-workspaces.ts
3822
+ function useWorkspaces(strategy, rootDir) {
3823
+ const [modules, setModules] = useState2([]);
3824
+ const [loading, setLoading] = useState2(true);
3825
+ const [activeIndex, setActiveIndex] = useState2(0);
3826
+ const [diagnostics, setDiagnostics] = useState2([]);
3827
+ const reload = useCallback2(async () => {
3828
+ setLoading(true);
3829
+ setModules([]);
3830
+ setActiveIndex(0);
3831
+ setDiagnostics([]);
3832
+ try {
3833
+ const found = await discoverModules(strategy, rootDir);
3834
+ setModules(found.modules);
3835
+ setDiagnostics(found.diagnostics);
3836
+ } catch (err) {
3837
+ setModules([]);
3838
+ setDiagnostics([err instanceof Error ? err.message : String(err)]);
3839
+ } finally {
3840
+ setLoading(false);
3841
+ }
3842
+ }, [strategy, rootDir]);
3843
+ useEffect(() => {
3844
+ reload();
3845
+ }, [reload]);
3846
+ const active = modules[activeIndex] || {
3847
+ id: "empty",
3848
+ name: "No module found",
3849
+ path: rootDir,
3850
+ relPath: "Root",
3851
+ ecosystem: strategy.ecosystem,
3852
+ isRoot: true
3853
+ };
3854
+ return {
3855
+ modules,
3856
+ active,
3857
+ activeIndex,
3858
+ setActiveIndex,
3859
+ loading,
3860
+ diagnostics,
3861
+ reload
3862
+ };
3863
+ }
3864
+
2804
3865
  // src/hooks/use-packages.ts
3866
+ import { useState as useState3, useEffect as useEffect2, useMemo as useMemo2, useCallback as useCallback3, useRef } from "react";
2805
3867
  function usePackages(strategy, module, modules, config, channel = "stable") {
2806
3868
  const [candidates, setCandidates] = useState3([]);
2807
3869
  const [skipped, setSkipped] = useState3([]);
@@ -2868,15 +3930,12 @@ function usePackages(strategy, module, modules, config, channel = "stable") {
2868
3930
  setCandidates(
2869
3931
  (prev) => prev.map((c) => {
2870
3932
  if (c.coordinate.identifier !== identifier) return c;
2871
- const target = c.coordinate.ecosystem === "maven" ? cleanJavaVersion(chosenVersion) : semver5.valid(chosenVersion);
2872
- let diff = "minor";
2873
- if (c.currentClean && target && semver5.valid(c.currentClean)) {
2874
- diff = semver5.eq(c.currentClean, target) ? "patch" : diffVersions(c.currentClean, target);
2875
- }
3933
+ const policy = strategy.versioning;
3934
+ const diff = c.currentClean ? policy.diff(c.currentClean, chosenVersion) : "minor";
2876
3935
  const isPre = isPreReleaseVersion(chosenVersion);
2877
3936
  return {
2878
3937
  ...c,
2879
- newRange: c.coordinate.ecosystem === "npm" ? `${rangePrefix(c.currentRange)}${chosenVersion}` : chosenVersion,
3938
+ newRange: policy.rewrite(c.currentRange, chosenVersion),
2880
3939
  latest: chosenVersion,
2881
3940
  diff,
2882
3941
  selected: true,
@@ -2886,7 +3945,7 @@ function usePackages(strategy, module, modules, config, channel = "stable") {
2886
3945
  };
2887
3946
  })
2888
3947
  );
2889
- }, []);
3948
+ }, [strategy]);
2890
3949
  const switchTab = useCallback3((tab) => {
2891
3950
  setActiveTab(tab);
2892
3951
  setCursor(0);
@@ -2918,11 +3977,11 @@ function usePackages(strategy, module, modules, config, channel = "stable") {
2918
3977
  import { useState as useState4, useCallback as useCallback4 } from "react";
2919
3978
 
2920
3979
  // src/application/update-workflow.ts
2921
- import { existsSync as existsSync13 } from "fs";
2922
- import { relative as relative7 } from "path";
3980
+ import { existsSync as existsSync18 } from "fs";
3981
+ import { relative as relative10 } from "path";
2923
3982
 
2924
3983
  // src/adapters/shared/file-snapshot.ts
2925
- import { existsSync as existsSync12, readFileSync as readFileSync12, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
3984
+ import { existsSync as existsSync17, readFileSync as readFileSync17, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
2926
3985
  var FileSnapshot = class _FileSnapshot {
2927
3986
  constructor(entries2) {
2928
3987
  this.entries = entries2;
@@ -2933,7 +3992,7 @@ var FileSnapshot = class _FileSnapshot {
2933
3992
  const entries2 = /* @__PURE__ */ new Map();
2934
3993
  for (const file of files) {
2935
3994
  if (entries2.has(file)) continue;
2936
- entries2.set(file, existsSync12(file) ? readFileSync12(file) : null);
3995
+ entries2.set(file, existsSync17(file) ? readFileSync17(file) : null);
2937
3996
  }
2938
3997
  return new _FileSnapshot(entries2);
2939
3998
  }
@@ -2945,9 +4004,9 @@ var FileSnapshot = class _FileSnapshot {
2945
4004
  changedFiles() {
2946
4005
  return this.files.filter((file) => {
2947
4006
  const before = this.entries.get(file) ?? null;
2948
- const exists = existsSync12(file);
4007
+ const exists = existsSync17(file);
2949
4008
  if (before === null) return exists;
2950
- return !exists || !readFileSync12(file).equals(before);
4009
+ return !exists || !readFileSync17(file).equals(before);
2951
4010
  });
2952
4011
  }
2953
4012
  /** Writes every file back to its captured state; removes files that did not exist. */
@@ -2956,7 +4015,7 @@ var FileSnapshot = class _FileSnapshot {
2956
4015
  if (content === null) {
2957
4016
  rmSync2(file, { force: true });
2958
4017
  } else {
2959
- writeFileSync4(file, content);
4018
+ writeFileSync5(file, content);
2960
4019
  }
2961
4020
  }
2962
4021
  }
@@ -2995,7 +4054,7 @@ Restoring the installed state failed as well: ${message(err)}`;
2995
4054
  let baseline;
2996
4055
  if (!options.skipVerification) {
2997
4056
  progress("Running verification on the current state\u2026");
2998
- baseline = await verify(snapshot.files.filter((file) => existsSync13(file)));
4057
+ baseline = await verify(snapshot.files.filter((file) => existsSync18(file)));
2999
4058
  }
3000
4059
  let applied;
3001
4060
  try {
@@ -3041,7 +4100,7 @@ function message(err) {
3041
4100
  return err instanceof Error ? err.message : String(err);
3042
4101
  }
3043
4102
  function displayPaths(files, rootDir) {
3044
- return files.map((file) => relative7(rootDir, file).replace(/\\/g, "/"));
4103
+ return files.map((file) => relative10(rootDir, file).replace(/\\/g, "/"));
3045
4104
  }
3046
4105
 
3047
4106
  // src/hooks/use-updater.ts
@@ -3255,33 +4314,39 @@ var Header = ({
3255
4314
  // src/components/workspace-bar.tsx
3256
4315
  import { Box as Box3, Text as Text3 } from "ink";
3257
4316
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
3258
- var WorkspaceBar = ({ module, internalScopes, strategy, t }) => {
4317
+ var WorkspaceBar = ({ module, internalScopes, strategy, t, diagnostics = [] }) => {
3259
4318
  const scopeHint = internalScopes.length > 0 ? t.status.internalScopes(internalScopes) : t.status.noInternalScopes;
3260
- return /* @__PURE__ */ jsxs3(Box3, { marginY: 1, justifyContent: "space-between", alignItems: "center", children: [
3261
- /* @__PURE__ */ jsxs3(Box3, { alignItems: "center", flexShrink: 1, children: [
3262
- /* @__PURE__ */ jsxs3(Text3, { bold: true, children: [
3263
- "\u{1F4CD} ",
3264
- strategy.icon,
3265
- " ",
3266
- t.workspace.label,
3267
- ": "
4319
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
4320
+ /* @__PURE__ */ jsxs3(Box3, { marginY: 1, justifyContent: "space-between", alignItems: "center", children: [
4321
+ /* @__PURE__ */ jsxs3(Box3, { alignItems: "center", flexShrink: 1, children: [
4322
+ /* @__PURE__ */ jsxs3(Text3, { bold: true, children: [
4323
+ "\u{1F4CD} ",
4324
+ strategy.icon,
4325
+ " ",
4326
+ t.workspace.label,
4327
+ ": "
4328
+ ] }),
4329
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.colors.brandLight, children: module.name }),
4330
+ /* @__PURE__ */ jsxs3(Text3, { color: theme.colors.muted, children: [
4331
+ " (",
4332
+ module.relPath,
4333
+ ")"
4334
+ ] })
3268
4335
  ] }),
3269
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.colors.brandLight, children: module.name }),
3270
- /* @__PURE__ */ jsxs3(Text3, { color: theme.colors.muted, children: [
3271
- " (",
3272
- module.relPath,
3273
- ")"
4336
+ /* @__PURE__ */ jsxs3(Box3, { alignItems: "center", marginLeft: 2, children: [
4337
+ /* @__PURE__ */ jsx3(Text3, { color: theme.colors.muted, children: scopeHint }),
4338
+ /* @__PURE__ */ jsx3(Text3, { color: theme.colors.muted, children: " \u2502 " }),
4339
+ /* @__PURE__ */ jsx3(Text3, { color: theme.colors.brandLight, bold: true, children: "[W]" }),
4340
+ /* @__PURE__ */ jsxs3(Text3, { color: theme.colors.muted, children: [
4341
+ " ",
4342
+ t.workspace.switchHint.replace("[W]", "").trim()
4343
+ ] })
3274
4344
  ] })
3275
4345
  ] }),
3276
- /* @__PURE__ */ jsxs3(Box3, { alignItems: "center", marginLeft: 2, children: [
3277
- /* @__PURE__ */ jsx3(Text3, { color: theme.colors.muted, children: scopeHint }),
3278
- /* @__PURE__ */ jsx3(Text3, { color: theme.colors.muted, children: " \u2502 " }),
3279
- /* @__PURE__ */ jsx3(Text3, { color: theme.colors.brandLight, bold: true, children: "[W]" }),
3280
- /* @__PURE__ */ jsxs3(Text3, { color: theme.colors.muted, children: [
3281
- " ",
3282
- t.workspace.switchHint.replace("[W]", "").trim()
3283
- ] })
3284
- ] })
4346
+ diagnostics.slice(0, 2).map((d) => /* @__PURE__ */ jsxs3(Text3, { color: theme.colors.danger, wrap: "truncate-end", children: [
4347
+ "\u26A0 ",
4348
+ d.split("\n")[0]
4349
+ ] }, d))
3285
4350
  ] });
3286
4351
  };
3287
4352
 
@@ -3331,7 +4396,9 @@ import { Box as Box5, Text as Text5 } from "ink";
3331
4396
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
3332
4397
  var PackageRow = ({ item, isFocused, t }) => {
3333
4398
  const badge = item.diff === "major" ? /* @__PURE__ */ jsx5(Text5, { bold: true, color: theme.colors.danger, children: t.badges.major }) : item.diff === "minor" ? /* @__PURE__ */ jsx5(Text5, { color: theme.colors.warning, children: t.badges.minor }) : /* @__PURE__ */ jsx5(Text5, { color: theme.colors.success, children: t.badges.patch });
3334
- const displayName = item.coordinate.identifier;
4399
+ const shared = item.sharedWith && item.sharedWith.length > 0 ? ` ${t.badges.shared(item.sharedWith.length)}` : "";
4400
+ const label = item.qualifier ? `${item.coordinate.identifier} (${item.qualifier})` : item.coordinate.identifier;
4401
+ const displayName = label.slice(0, 42 - shared.length) + shared;
3335
4402
  const isDevOrTest = item.scope === "dev" || item.scope === "test";
3336
4403
  const property = item.declaration?.kind === "property" ? `\${${item.declaration.propertyName}}` : void 0;
3337
4404
  return /* @__PURE__ */ jsxs5(Box5, { justifyContent: "space-between", children: [
@@ -3361,6 +4428,10 @@ var PackageRow = ({ item, isFocused, t }) => {
3361
4428
  t.badges.align,
3362
4429
  " "
3363
4430
  ] }),
4431
+ item.action === "lock" && /* @__PURE__ */ jsxs5(Text5, { bold: true, color: theme.colors.brandLight, children: [
4432
+ t.badges.lock,
4433
+ " "
4434
+ ] }),
3364
4435
  item.origin.kind === "private" && /* @__PURE__ */ jsxs5(Text5, { color: theme.colors.brandLight, children: [
3365
4436
  t.badges.internal,
3366
4437
  " "
@@ -3510,7 +4581,7 @@ var ChickenMascot = ({ state = "idle", t, ecosystem = "npm" }) => {
3510
4581
  chickFace = cur.face;
3511
4582
  wingLeft = cur.l;
3512
4583
  wingRight = cur.r;
3513
- const chirpsPool = ecosystem === "maven" ? [...t.mascot.idleChirps, ...t.mascot.mavenChirps] : t.mascot.idleChirps;
4584
+ const chirpsPool = ecosystem === "maven" ? [...t.mascot.idleChirps, ...t.mascot.mavenChirps] : ecosystem === "cargo" ? [...t.mascot.idleChirps, ...t.mascot.cargoChirps] : t.mascot.idleChirps;
3514
4585
  currentChirp = chirpsPool[chirpIndex % chirpsPool.length];
3515
4586
  }
3516
4587
  return /* @__PURE__ */ jsxs7(Box7, { alignItems: "center", children: [
@@ -3788,6 +4859,7 @@ var VersionModal = ({
3788
4859
  ] }),
3789
4860
  /* @__PURE__ */ jsx11(Text11, { color: theme.colors.muted, children: t.versionModal.current(candidate.currentRange) })
3790
4861
  ] }),
4862
+ candidate.sharedWith && candidate.sharedWith.length > 0 && /* @__PURE__ */ jsx11(Box11, { marginBottom: 1, children: /* @__PURE__ */ jsx11(Text11, { color: theme.colors.warning, children: t.versionModal.alsoChanges(candidate.sharedWith) }) }),
3791
4863
  /* @__PURE__ */ jsxs11(
3792
4864
  Box11,
3793
4865
  {
@@ -3962,10 +5034,10 @@ var App = ({ config }) => {
3962
5034
  const [versionPickerCandidate, setVersionPickerCandidate] = useState8(null);
3963
5035
  const [ecosystem, setEcosystem] = useState8("npm");
3964
5036
  const strategy = useMemo4(() => EcosystemFactory.getStrategy(ecosystem), [ecosystem]);
3965
- const nextEcosystem = ecosystem === "npm" ? "maven" : "npm";
5037
+ const nextEcosystem = EcosystemFactory.next(ecosystem);
3966
5038
  const nextStrategy = useMemo4(() => EcosystemFactory.getStrategy(nextEcosystem), [nextEcosystem]);
3967
5039
  const toggleEcosystem = useCallback5(() => {
3968
- setEcosystem((prev) => prev === "npm" ? "maven" : "npm");
5040
+ setEcosystem((prev) => EcosystemFactory.next(prev));
3969
5041
  }, []);
3970
5042
  const { locale, toggleLocale, t } = useI18n(resolveInitialLocale(config.locale));
3971
5043
  const ws = useWorkspaces(strategy, config.rootDir);
@@ -4117,7 +5189,8 @@ var App = ({ config }) => {
4117
5189
  module: ws.active,
4118
5190
  internalScopes: config.internalScopes,
4119
5191
  strategy,
4120
- t
5192
+ t,
5193
+ diagnostics: ws.diagnostics
4121
5194
  }
4122
5195
  ),
4123
5196
  /* @__PURE__ */ jsx14(
@@ -4157,6 +5230,7 @@ var App = ({ config }) => {
4157
5230
  };
4158
5231
 
4159
5232
  // src/cli/parser.ts
5233
+ var isEcosystem = (v) => EcosystemFactory.getAvailableEcosystems().includes(v);
4160
5234
  function parseCliArgs(args) {
4161
5235
  const [first, ...rest] = args;
4162
5236
  if (!first || first === "--help" || first === "-h" || first === "help") {
@@ -4194,10 +5268,10 @@ function parseCliArgs(args) {
4194
5268
  result.help = true;
4195
5269
  } else if (arg.startsWith("--ecosystem=")) {
4196
5270
  const val = arg.split("=")[1];
4197
- if (val === "npm" || val === "maven") result.ecosystem = val;
5271
+ if (isEcosystem(val)) result.ecosystem = val;
4198
5272
  } else if (arg === "-e" || arg === "--ecosystem") {
4199
5273
  const val = remaining[++i];
4200
- if (val === "npm" || val === "maven") result.ecosystem = val;
5274
+ if (isEcosystem(val)) result.ecosystem = val;
4201
5275
  } else if (arg.startsWith("--module=")) {
4202
5276
  result.module = arg.split("=")[1];
4203
5277
  } else if (arg === "-m" || arg === "--module") {
@@ -4239,7 +5313,7 @@ SUBCOMMANDS:
4239
5313
  mcp Start stdio-based MCP server
4240
5314
 
4241
5315
  FLAGS:
4242
- -e, --ecosystem <npm|maven> Target ecosystem (default: npm)
5316
+ -e, --ecosystem <npm|maven|cargo> Target ecosystem (default: npm)
4243
5317
  -m, --module <name|path> Target module ID or relative path (default: root)
4244
5318
  -c, --channel <stable|all> Release channel (default: stable)
4245
5319
  -p, --packages <pkg1,pkg2@1.2.3> Comma-separated list of packages to update
@@ -4267,11 +5341,11 @@ EXAMPLES:
4267
5341
  async function handleModulesCommand(options, config) {
4268
5342
  const ecosystem = options.ecosystem ?? "npm";
4269
5343
  const strategy = EcosystemFactory.getStrategy(ecosystem);
4270
- const modules = await strategy.discovery.discover(config.rootDir);
5344
+ const { modules, diagnostics } = await discoverModules(strategy, config.rootDir);
4271
5345
  const displayName = strategy.displayNameFor?.(config.rootDir) ?? strategy.displayName;
4272
5346
  const packageManager = strategy.packageManagerFor?.(config.rootDir);
4273
5347
  if (options.json) {
4274
- console.log(JSON.stringify({ ecosystem, ...packageManager ? { packageManager } : {}, count: modules.length, modules }, null, 2));
5348
+ console.log(JSON.stringify({ ecosystem, ...packageManager ? { packageManager } : {}, count: modules.length, modules, diagnostics }, null, 2));
4275
5349
  return 0;
4276
5350
  }
4277
5351
  console.log(`
@@ -4282,6 +5356,7 @@ async function handleModulesCommand(options, config) {
4282
5356
  console.log(` \u2022 ${m.name.padEnd(45)} (${m.relPath})${isRootBadge}`);
4283
5357
  }
4284
5358
  console.log("");
5359
+ for (const problem of diagnostics) console.error(`\u26A0\uFE0F ${problem}`);
4285
5360
  return 0;
4286
5361
  }
4287
5362
 
@@ -4303,6 +5378,8 @@ function describeSkip(skipped) {
4303
5378
  return `workspace package that pnpm does not link unless it is declared with the workspace: protocol${detail}`;
4304
5379
  case "catalog-unresolved":
4305
5380
  return `catalog entry could not be resolved${detail}`;
5381
+ case "incompatible-rust-version":
5382
+ return `newer versions need a newer Rust than the project's rust-version${detail}`;
4306
5383
  }
4307
5384
  }
4308
5385
 
@@ -4311,7 +5388,8 @@ async function handleScanCommand(options, config) {
4311
5388
  const ecosystem = options.ecosystem ?? "npm";
4312
5389
  const channel = options.channel ?? config.channel ?? "stable";
4313
5390
  const strategy = EcosystemFactory.getStrategy(ecosystem);
4314
- const modules = await strategy.discovery.discover(config.rootDir);
5391
+ const { modules, diagnostics } = await discoverModules(strategy, config.rootDir);
5392
+ for (const problem of diagnostics) console.error(`\u26A0\uFE0F ${problem}`);
4315
5393
  if (modules.length === 0) {
4316
5394
  if (options.json) {
4317
5395
  console.log(JSON.stringify({ error: `No ${strategy.displayName} modules found.`, ecosystem }, null, 2));
@@ -4390,7 +5468,7 @@ async function handleScanCommand(options, config) {
4390
5468
  if (result.candidates.length === 0) console.log(" \u2728 All dependencies are up to date.");
4391
5469
  for (const u of result.candidates) {
4392
5470
  const diffTag = `[${u.diff.toUpperCase()}]`.padEnd(9);
4393
- const actionTag = u.action === "align" ? "[ALIGN] " : u.origin.kind === "private" ? "[INTERNAL] " : "";
5471
+ const actionTag = u.action === "align" ? "[ALIGN] " : u.action === "lock" ? "[LOCK] " : u.origin.kind === "private" ? "[INTERNAL] " : "";
4394
5472
  const preBadge = u.isPreRelease ? `[${u.preReleaseTag || "PRE"}] ` : "";
4395
5473
  const where = u.declaration && u.declaration.displayPath !== modulePom ? ` (in ${u.declaration.displayPath}${u.declaration.propertyName ? ` \${${u.declaration.propertyName}}` : ""})` : "";
4396
5474
  console.log(
@@ -4461,7 +5539,7 @@ async function handleVersionsCommand(options, config) {
4461
5539
  }
4462
5540
 
4463
5541
  // src/application/update-selection.ts
4464
- import semver6 from "semver";
5542
+ import semver12 from "semver";
4465
5543
  var UpdateSelectionError = class extends Error {
4466
5544
  constructor(message2) {
4467
5545
  super(message2);
@@ -4486,9 +5564,9 @@ function selectRequested(scan, requests, options) {
4486
5564
  `'${request.identifier}' has no pending update. Specify a target version (e.g. ${request.identifier}@1.2.3).`
4487
5565
  );
4488
5566
  }
4489
- if (options.ecosystem === "maven") {
5567
+ if (!policyOf(options).allowsManualTargets) {
4490
5568
  throw new UpdateSelectionError(
4491
- `'${request.identifier}' has no pending update. Maven updates are written where the scan located the version; a dependency without an offered update cannot be retargeted.`
5569
+ `'${request.identifier}' has no pending update. Updates are written where the scan located the version; a dependency without an offered update cannot be retargeted.`
4492
5570
  );
4493
5571
  }
4494
5572
  if (options.originOf(request.identifier) !== "public") {
@@ -4513,12 +5591,12 @@ function retarget(found, target, options) {
4513
5591
  `'${found.coordinate.identifier}' is a workspace module; it can only be aligned to its local version ${found.latest}.`
4514
5592
  );
4515
5593
  }
4516
- if (options.ecosystem === "maven" && found.availableVersions && !found.availableVersions.includes(target)) {
5594
+ if (!policyOf(options).allowsManualTargets && found.availableVersions && !found.availableVersions.includes(target)) {
4517
5595
  throw new UpdateSelectionError(
4518
5596
  `'${found.coordinate.identifier}' has no version ${target} in the configured repositories. Available: ${found.availableVersions.slice(0, 5).join(", ")}${found.availableVersions.length > 5 ? ", \u2026" : ""}.`
4519
5597
  );
4520
5598
  }
4521
- const diff = computeDiff(found.currentClean, target, options.ecosystem);
5599
+ const diff = policyOf(options).diff(found.currentClean, target);
4522
5600
  if (diff === "major" && !options.allowMajor) {
4523
5601
  throw new UpdateSelectionError(
4524
5602
  `'${found.coordinate.identifier}' requires a major upgrade to ${target}. Pass --allow-major / allowMajor.`
@@ -4527,7 +5605,9 @@ function retarget(found, target, options) {
4527
5605
  const isPreRelease = isPreReleaseVersion(target);
4528
5606
  return {
4529
5607
  ...found,
4530
- newRange: options.ecosystem === "npm" ? `${rangePrefix(found.currentRange)}${target}` : target,
5608
+ // A lock update pins exactly the version that was picked.
5609
+ ...found.action === "lock" ? { latest: target } : {},
5610
+ newRange: policyOf(options).rewrite(found.currentRange, target),
4531
5611
  diff,
4532
5612
  selected: true,
4533
5613
  isCustomVersion: true,
@@ -4536,7 +5616,7 @@ function retarget(found, target, options) {
4536
5616
  };
4537
5617
  }
4538
5618
  function manualNpmCandidate(identifier, target, options) {
4539
- if (!semver6.valid(target)) {
5619
+ if (!semver12.valid(target)) {
4540
5620
  throw new UpdateSelectionError(`'${target}' is not a valid version for '${identifier}'.`);
4541
5621
  }
4542
5622
  return {
@@ -4553,10 +5633,8 @@ function manualNpmCandidate(identifier, target, options) {
4553
5633
  isCustomVersion: true
4554
5634
  };
4555
5635
  }
4556
- function computeDiff(current, target, ecosystem) {
4557
- const to = ecosystem === "maven" ? cleanJavaVersion(target) : semver6.valid(target);
4558
- if (!to || !semver6.valid(current)) return "minor";
4559
- return semver6.eq(current, to) ? "patch" : diffVersions(current, to);
5636
+ function policyOf(options) {
5637
+ return EcosystemFactory.getStrategy(options.ecosystem).versioning;
4560
5638
  }
4561
5639
 
4562
5640
  // src/cli/command-update.ts
@@ -4568,7 +5646,8 @@ async function handleUpdateCommand(options, config) {
4568
5646
  else console.error(`\u274C ${message2}`);
4569
5647
  return code;
4570
5648
  };
4571
- const modules = await strategy.discovery.discover(config.rootDir);
5649
+ const { modules, diagnostics } = await discoverModules(strategy, config.rootDir);
5650
+ for (const problem of diagnostics) console.error(`\u26A0\uFE0F ${problem}`);
4572
5651
  if (modules.length === 0) return fail(`No ${strategy.displayName} modules found.`);
4573
5652
  const targetModule = options.module ? findModule(modules, options.module) : modules[0];
4574
5653
  if (!targetModule) return fail(`Module '${options.module}' not found.`);
@@ -4709,9 +5788,9 @@ function parsePackageArgument(item) {
4709
5788
  }
4710
5789
 
4711
5790
  // src/cli/command-init.ts
4712
- import { existsSync as existsSync14, readFileSync as readFileSync13, writeFileSync as writeFileSync5, mkdirSync as mkdirSync2 } from "fs";
4713
- import { homedir, platform } from "os";
4714
- import { join as join19, resolve as resolve11 } from "path";
5791
+ import { existsSync as existsSync19, readFileSync as readFileSync18, writeFileSync as writeFileSync6, mkdirSync as mkdirSync2 } from "fs";
5792
+ import { homedir as homedir2, platform } from "os";
5793
+ import { join as join24, resolve as resolve15 } from "path";
4715
5794
  function mcpServerEntry() {
4716
5795
  return {
4717
5796
  command: "npx",
@@ -4754,10 +5833,10 @@ automatic rollback.
4754
5833
  requested). A failed install or verification is rolled back automatically.
4755
5834
  4. \`elevate_verify\` \u2014 Verify build integrity when needed
4756
5835
  `;
4757
- var home = homedir();
5836
+ var home = homedir2();
4758
5837
  var isWin = platform() === "win32";
4759
5838
  function appData() {
4760
- return process.env.APPDATA || join19(home, "AppData", "Roaming");
5839
+ return process.env.APPDATA || join24(home, "AppData", "Roaming");
4761
5840
  }
4762
5841
  function detectCursor(cwd) {
4763
5842
  const env = {
@@ -4769,25 +5848,25 @@ function detectCursor(cwd) {
4769
5848
  mcpConfigured: false,
4770
5849
  skillConfigured: false
4771
5850
  };
4772
- const workspaceCursorDir = join19(cwd, ".cursor");
4773
- const globalCursorDir = join19(home, ".cursor");
4774
- if (existsSync14(workspaceCursorDir) || existsSync14(globalCursorDir)) {
5851
+ const workspaceCursorDir = join24(cwd, ".cursor");
5852
+ const globalCursorDir = join24(home, ".cursor");
5853
+ if (existsSync19(workspaceCursorDir) || existsSync19(globalCursorDir)) {
4775
5854
  env.detected = true;
4776
- env.reason = existsSync14(workspaceCursorDir) ? `Found .cursor/ in workspace` : `Found ~/.cursor/ global config`;
5855
+ env.reason = existsSync19(workspaceCursorDir) ? `Found .cursor/ in workspace` : `Found ~/.cursor/ global config`;
4777
5856
  }
4778
- const mcpPath = join19(cwd, ".cursor", "mcp.json");
4779
- if (existsSync14(mcpPath)) {
5857
+ const mcpPath = join24(cwd, ".cursor", "mcp.json");
5858
+ if (existsSync19(mcpPath)) {
4780
5859
  try {
4781
- const content = JSON.parse(readFileSync13(mcpPath, "utf8"));
5860
+ const content = JSON.parse(readFileSync18(mcpPath, "utf8"));
4782
5861
  if (content?.mcpServers?.elevate) {
4783
5862
  env.mcpConfigured = true;
4784
5863
  }
4785
5864
  } catch {
4786
5865
  }
4787
5866
  }
4788
- const rulesPath = join19(cwd, ".cursorrules");
4789
- if (existsSync14(rulesPath)) {
4790
- const content = readFileSync13(rulesPath, "utf8");
5867
+ const rulesPath = join24(cwd, ".cursorrules");
5868
+ if (existsSync19(rulesPath)) {
5869
+ const content = readFileSync18(rulesPath, "utf8");
4791
5870
  if (content.includes("elevate") || content.includes("Elevate")) {
4792
5871
  env.skillConfigured = true;
4793
5872
  }
@@ -4804,12 +5883,12 @@ function detectClaudeDesktop() {
4804
5883
  mcpConfigured: false,
4805
5884
  skillConfigured: false
4806
5885
  };
4807
- const configPath = isWin ? join19(appData(), "Claude", "claude_desktop_config.json") : join19(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
4808
- if (existsSync14(configPath)) {
5886
+ const configPath = isWin ? join24(appData(), "Claude", "claude_desktop_config.json") : join24(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
5887
+ if (existsSync19(configPath)) {
4809
5888
  env.detected = true;
4810
5889
  env.reason = `Found ${configPath}`;
4811
5890
  try {
4812
- const content = JSON.parse(readFileSync13(configPath, "utf8"));
5891
+ const content = JSON.parse(readFileSync18(configPath, "utf8"));
4813
5892
  if (content?.mcpServers?.elevate) {
4814
5893
  env.mcpConfigured = true;
4815
5894
  }
@@ -4828,23 +5907,23 @@ function detectClaudeCode(cwd) {
4828
5907
  mcpConfigured: false,
4829
5908
  skillConfigured: false
4830
5909
  };
4831
- const mcpJsonPath = join19(cwd, ".mcp.json");
4832
- const claudeMdPath = join19(cwd, "CLAUDE.md");
4833
- if (existsSync14(mcpJsonPath) || existsSync14(claudeMdPath)) {
5910
+ const mcpJsonPath = join24(cwd, ".mcp.json");
5911
+ const claudeMdPath = join24(cwd, "CLAUDE.md");
5912
+ if (existsSync19(mcpJsonPath) || existsSync19(claudeMdPath)) {
4834
5913
  env.detected = true;
4835
- env.reason = existsSync14(mcpJsonPath) ? `Found .mcp.json in workspace` : `Found CLAUDE.md in workspace`;
5914
+ env.reason = existsSync19(mcpJsonPath) ? `Found .mcp.json in workspace` : `Found CLAUDE.md in workspace`;
4836
5915
  }
4837
- if (existsSync14(mcpJsonPath)) {
5916
+ if (existsSync19(mcpJsonPath)) {
4838
5917
  try {
4839
- const content = JSON.parse(readFileSync13(mcpJsonPath, "utf8"));
5918
+ const content = JSON.parse(readFileSync18(mcpJsonPath, "utf8"));
4840
5919
  if (content?.mcpServers?.elevate) {
4841
5920
  env.mcpConfigured = true;
4842
5921
  }
4843
5922
  } catch {
4844
5923
  }
4845
5924
  }
4846
- if (existsSync14(claudeMdPath)) {
4847
- const content = readFileSync13(claudeMdPath, "utf8");
5925
+ if (existsSync19(claudeMdPath)) {
5926
+ const content = readFileSync18(claudeMdPath, "utf8");
4848
5927
  if (content.includes("elevate") || content.includes("Elevate")) {
4849
5928
  env.skillConfigured = true;
4850
5929
  }
@@ -4861,20 +5940,20 @@ function detectAntigravity(cwd) {
4861
5940
  mcpConfigured: false,
4862
5941
  skillConfigured: false
4863
5942
  };
4864
- const globalGeminiDir = join19(home, ".gemini");
4865
- const workspaceAgentsDir = join19(cwd, ".agents");
4866
- if (existsSync14(globalGeminiDir) || existsSync14(workspaceAgentsDir)) {
5943
+ const globalGeminiDir = join24(home, ".gemini");
5944
+ const workspaceAgentsDir = join24(cwd, ".agents");
5945
+ if (existsSync19(globalGeminiDir) || existsSync19(workspaceAgentsDir)) {
4867
5946
  env.detected = true;
4868
- env.reason = existsSync14(workspaceAgentsDir) ? `Found .agents/ in workspace` : `Found ~/.gemini/ global config`;
5947
+ env.reason = existsSync19(workspaceAgentsDir) ? `Found .agents/ in workspace` : `Found ~/.gemini/ global config`;
4869
5948
  }
4870
5949
  const mcpPaths = [
4871
- join19(cwd, ".agents", "mcp_config.json"),
4872
- join19(home, ".gemini", "config", "mcp_config.json")
5950
+ join24(cwd, ".agents", "mcp_config.json"),
5951
+ join24(home, ".gemini", "config", "mcp_config.json")
4873
5952
  ];
4874
5953
  for (const p of mcpPaths) {
4875
- if (existsSync14(p)) {
5954
+ if (existsSync19(p)) {
4876
5955
  try {
4877
- const content = JSON.parse(readFileSync13(p, "utf8"));
5956
+ const content = JSON.parse(readFileSync18(p, "utf8"));
4878
5957
  if (content?.mcpServers?.elevate) {
4879
5958
  env.mcpConfigured = true;
4880
5959
  break;
@@ -4895,24 +5974,24 @@ function detectVSCode(cwd) {
4895
5974
  mcpConfigured: false,
4896
5975
  skillConfigured: false
4897
5976
  };
4898
- const vscodeDir = join19(cwd, ".vscode");
4899
- if (existsSync14(vscodeDir)) {
5977
+ const vscodeDir = join24(cwd, ".vscode");
5978
+ if (existsSync19(vscodeDir)) {
4900
5979
  env.detected = true;
4901
5980
  env.reason = `Found .vscode/ in workspace`;
4902
5981
  }
4903
- const mcpPath = join19(cwd, ".vscode", "mcp.json");
4904
- if (existsSync14(mcpPath)) {
5982
+ const mcpPath = join24(cwd, ".vscode", "mcp.json");
5983
+ if (existsSync19(mcpPath)) {
4905
5984
  try {
4906
- const content = JSON.parse(readFileSync13(mcpPath, "utf8"));
5985
+ const content = JSON.parse(readFileSync18(mcpPath, "utf8"));
4907
5986
  if (content?.servers?.elevate) {
4908
5987
  env.mcpConfigured = true;
4909
5988
  }
4910
5989
  } catch {
4911
5990
  }
4912
5991
  }
4913
- const copilotInstructions = join19(cwd, ".github", "copilot-instructions.md");
4914
- if (existsSync14(copilotInstructions)) {
4915
- const content = readFileSync13(copilotInstructions, "utf8");
5992
+ const copilotInstructions = join24(cwd, ".github", "copilot-instructions.md");
5993
+ if (existsSync19(copilotInstructions)) {
5994
+ const content = readFileSync18(copilotInstructions, "utf8");
4916
5995
  if (content.includes("elevate") || content.includes("Elevate")) {
4917
5996
  env.skillConfigured = true;
4918
5997
  }
@@ -4929,14 +6008,14 @@ function detectWindsurf() {
4929
6008
  mcpConfigured: false,
4930
6009
  skillConfigured: false
4931
6010
  };
4932
- const configPath = isWin ? join19(appData(), "Codeium", "Windsurf", "mcp_config.json") : join19(home, ".codeium", "windsurf", "mcp_config.json");
4933
- const altPath = join19(home, ".codeium", "windsurf", "mcp_config.json");
4934
- if (existsSync14(configPath) || existsSync14(altPath)) {
6011
+ const configPath = isWin ? join24(appData(), "Codeium", "Windsurf", "mcp_config.json") : join24(home, ".codeium", "windsurf", "mcp_config.json");
6012
+ const altPath = join24(home, ".codeium", "windsurf", "mcp_config.json");
6013
+ if (existsSync19(configPath) || existsSync19(altPath)) {
4935
6014
  env.detected = true;
4936
6015
  env.reason = `Found Windsurf config`;
4937
- const path = existsSync14(configPath) ? configPath : altPath;
6016
+ const path = existsSync19(configPath) ? configPath : altPath;
4938
6017
  try {
4939
- const content = JSON.parse(readFileSync13(path, "utf8"));
6018
+ const content = JSON.parse(readFileSync18(path, "utf8"));
4940
6019
  if (content?.mcpServers?.elevate) {
4941
6020
  env.mcpConfigured = true;
4942
6021
  }
@@ -4947,9 +6026,9 @@ function detectWindsurf() {
4947
6026
  }
4948
6027
  function mergeJsonMcpServer(filePath, serverKey, topLevelKey) {
4949
6028
  let existing = {};
4950
- if (existsSync14(filePath)) {
6029
+ if (existsSync19(filePath)) {
4951
6030
  try {
4952
- existing = JSON.parse(readFileSync13(filePath, "utf8"));
6031
+ existing = JSON.parse(readFileSync18(filePath, "utf8"));
4953
6032
  } catch {
4954
6033
  existing = {};
4955
6034
  }
@@ -4961,69 +6040,69 @@ function mergeJsonMcpServer(filePath, serverKey, topLevelKey) {
4961
6040
  servers[serverKey] = mcpServerEntry();
4962
6041
  existing[topLevelKey] = servers;
4963
6042
  const dir = filePath.substring(0, filePath.lastIndexOf(isWin ? "\\" : "/"));
4964
- if (dir && !existsSync14(dir)) {
6043
+ if (dir && !existsSync19(dir)) {
4965
6044
  mkdirSync2(dir, { recursive: true });
4966
6045
  }
4967
- writeFileSync5(filePath, JSON.stringify(existing, null, 2) + "\n", "utf8");
6046
+ writeFileSync6(filePath, JSON.stringify(existing, null, 2) + "\n", "utf8");
4968
6047
  return { written: true, message: `${filePath}: elevate MCP server registered` };
4969
6048
  }
4970
6049
  function appendSkillToFile(filePath, content, marker) {
4971
- if (existsSync14(filePath)) {
4972
- const existing = readFileSync13(filePath, "utf8");
6050
+ if (existsSync19(filePath)) {
6051
+ const existing = readFileSync18(filePath, "utf8");
4973
6052
  if (existing.includes(marker)) {
4974
6053
  return { written: false, message: `${filePath}: skill already present` };
4975
6054
  }
4976
- writeFileSync5(filePath, existing + "\n\n" + content, "utf8");
6055
+ writeFileSync6(filePath, existing + "\n\n" + content, "utf8");
4977
6056
  return { written: true, message: `${filePath}: skill appended` };
4978
6057
  }
4979
6058
  const dir = filePath.substring(0, filePath.lastIndexOf(isWin ? "\\" : "/"));
4980
- if (dir && !existsSync14(dir)) {
6059
+ if (dir && !existsSync19(dir)) {
4981
6060
  mkdirSync2(dir, { recursive: true });
4982
6061
  }
4983
- writeFileSync5(filePath, content, "utf8");
6062
+ writeFileSync6(filePath, content, "utf8");
4984
6063
  return { written: true, message: `${filePath}: skill created` };
4985
6064
  }
4986
6065
  function configureCursor(cwd, result) {
4987
- const mcpPath = join19(cwd, ".cursor", "mcp.json");
6066
+ const mcpPath = join24(cwd, ".cursor", "mcp.json");
4988
6067
  const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
4989
6068
  result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Cursor MCP: ${mcp.message}`);
4990
- const rulesPath = join19(cwd, ".cursorrules");
6069
+ const rulesPath = join24(cwd, ".cursorrules");
4991
6070
  const skill = appendSkillToFile(rulesPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
4992
6071
  result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Cursor Rules: ${skill.message}`);
4993
6072
  }
4994
6073
  function configureClaudeDesktop(result) {
4995
- const configPath = isWin ? join19(appData(), "Claude", "claude_desktop_config.json") : join19(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
6074
+ const configPath = isWin ? join24(appData(), "Claude", "claude_desktop_config.json") : join24(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
4996
6075
  const mcp = mergeJsonMcpServer(configPath, "elevate", "mcpServers");
4997
6076
  result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Claude Desktop MCP: ${mcp.message}`);
4998
6077
  }
4999
6078
  function configureClaudeCode(cwd, result) {
5000
- const mcpPath = join19(cwd, ".mcp.json");
6079
+ const mcpPath = join24(cwd, ".mcp.json");
5001
6080
  const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
5002
6081
  result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Claude Code MCP: ${mcp.message}`);
5003
- const claudeMdPath = join19(cwd, "CLAUDE.md");
6082
+ const claudeMdPath = join24(cwd, "CLAUDE.md");
5004
6083
  const skill = appendSkillToFile(claudeMdPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
5005
6084
  result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Claude Code Skill: ${skill.message}`);
5006
6085
  }
5007
6086
  function configureAntigravity(cwd, result) {
5008
- const mcpPath = join19(cwd, ".agents", "mcp_config.json");
6087
+ const mcpPath = join24(cwd, ".agents", "mcp_config.json");
5009
6088
  const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
5010
6089
  result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Antigravity MCP: ${mcp.message}`);
5011
6090
  }
5012
6091
  function configureVSCode(cwd, result) {
5013
- const mcpPath = join19(cwd, ".vscode", "mcp.json");
6092
+ const mcpPath = join24(cwd, ".vscode", "mcp.json");
5014
6093
  const mcp = mergeJsonMcpServer(mcpPath, "elevate", "servers");
5015
6094
  result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} VS Code MCP: ${mcp.message}`);
5016
- const instructionsPath = join19(cwd, ".github", "copilot-instructions.md");
6095
+ const instructionsPath = join24(cwd, ".github", "copilot-instructions.md");
5017
6096
  const skill = appendSkillToFile(instructionsPath, ELEVATE_SKILL_CONTENT, "elevate_scan");
5018
6097
  result.actions.push(`${skill.written ? "\u2705" : "\u23ED\uFE0F"} Copilot Instructions: ${skill.message}`);
5019
6098
  }
5020
6099
  function configureWindsurf(result) {
5021
- const configPath = isWin ? join19(appData(), "Codeium", "Windsurf", "mcp_config.json") : join19(home, ".codeium", "windsurf", "mcp_config.json");
6100
+ const configPath = isWin ? join24(appData(), "Codeium", "Windsurf", "mcp_config.json") : join24(home, ".codeium", "windsurf", "mcp_config.json");
5022
6101
  const mcp = mergeJsonMcpServer(configPath, "elevate", "mcpServers");
5023
6102
  result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Windsurf MCP: ${mcp.message}`);
5024
6103
  }
5025
6104
  async function handleInitCommand(options) {
5026
- const cwd = resolve11(process.cwd());
6105
+ const cwd = resolve15(process.cwd());
5027
6106
  const result = {
5028
6107
  cwd,
5029
6108
  environments: [],
@@ -5051,7 +6130,7 @@ async function handleInitCommand(options) {
5051
6130
  console.log(` Creating portable .mcp.json for broad compatibility.
5052
6131
  `);
5053
6132
  }
5054
- const mcpPath = join19(cwd, ".mcp.json");
6133
+ const mcpPath = join24(cwd, ".mcp.json");
5055
6134
  const mcp = mergeJsonMcpServer(mcpPath, "elevate", "mcpServers");
5056
6135
  result.actions.push(`${mcp.written ? "\u2705" : "\u23ED\uFE0F"} Generic MCP: ${mcp.message}`);
5057
6136
  } else {
@@ -5118,14 +6197,14 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
5118
6197
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5119
6198
  var ECOSYSTEM_PROPERTY = {
5120
6199
  type: "string",
5121
- enum: ["npm", "maven"],
6200
+ enum: EcosystemFactory.getAvailableEcosystems(),
5122
6201
  description: "Target ecosystem (default: npm)"
5123
6202
  };
5124
6203
  async function startMcpServer(config) {
5125
6204
  const server = new Server(
5126
6205
  {
5127
6206
  name: "elevate-mcp",
5128
- version: "1.2.0"
6207
+ version: VERSION
5129
6208
  },
5130
6209
  {
5131
6210
  capabilities: {
@@ -5138,7 +6217,7 @@ async function startMcpServer(config) {
5138
6217
  tools: [
5139
6218
  {
5140
6219
  name: "elevate_discover_modules",
5141
- description: "Discovers all repository modules and workspaces for Node/npm (package.json) or Java/Maven (pom.xml).",
6220
+ description: "Discovers all repository modules and workspaces for Node/npm (package.json), Java/Maven (pom.xml) or Rust/Cargo (Cargo.toml).",
5142
6221
  inputSchema: {
5143
6222
  type: "object",
5144
6223
  properties: {
@@ -5178,7 +6257,7 @@ async function startMcpServer(config) {
5178
6257
  ecosystem: ECOSYSTEM_PROPERTY,
5179
6258
  identifier: {
5180
6259
  type: "string",
5181
- description: "Package identifier (npm: chalk, maven: org.slf4j:slf4j-api)"
6260
+ description: "Package identifier (npm: chalk, maven: org.slf4j:slf4j-api, cargo: serde)"
5182
6261
  }
5183
6262
  },
5184
6263
  required: ["identifier"]
@@ -5259,12 +6338,13 @@ async function startMcpServer(config) {
5259
6338
  };
5260
6339
  try {
5261
6340
  if (name === "elevate_discover_modules") {
5262
- const modules = await strategy.discovery.discover(config.rootDir);
6341
+ const { modules, diagnostics } = await discoverModules(strategy, config.rootDir);
5263
6342
  const packageManager = strategy.packageManagerFor?.(config.rootDir);
5264
6343
  return reply({
5265
6344
  ecosystem,
5266
6345
  ...packageManager ? { packageManager } : {},
5267
6346
  count: modules.length,
6347
+ diagnostics,
5268
6348
  modules: modules.map((m) => ({
5269
6349
  id: m.id,
5270
6350
  name: m.name,