@dennisrepa/elevate 1.5.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/README.md CHANGED
@@ -215,7 +215,7 @@ Detailed parameter references are available in [`docs/en.md`](./docs/en.md) and
215
215
  * **pnpm specifics.** `catalog:` entries are updated where they are declared, in `pnpm-workspace.yaml`. A plain range on a workspace module (`^1.0.0`) is listed as *not linked* and never rewritten to `workspace:`, because pnpm resolves it from the registry. Elevate does not read pnpm settings such as `minimumReleaseAge` or the approval of build scripts, so pnpm may refuse an offered version; the update is then rolled back with pnpm's message. pnpm 8 and older, Windows and `shared-workspace-lockfile=false` are not covered.
216
216
  * **Broken configuration stops Elevate.** An `elevate.config.json` that is not valid JSON, not an object, or whose `internalScopes` is not a list of strings is reported and Elevate exits with code `1`, because ignoring it would switch off the protection of internal packages. Without any configuration file Elevate works with defaults.
217
217
  * **Dependency sections.** `dependencies`, `devDependencies` and `optionalDependencies` are covered. `peerDependencies` and `overrides` are never changed.
218
- * **Cargo.** Requirements are raised keeping their precision, so a loose `"1"` only shows breaking updates; wildcard and compound requirements (`*`, `>=1.2, <2`), git and external path dependencies are left alone. Lock-file-only updates, registries that need authentication and `[patch]` sections are not covered; a vendored or local-registry source replacement is refused rather than bypassed.
218
+ * **Cargo.** Requirements are raised keeping their precision, so a loose `"1"` only shows breaking updates; wildcard and compound requirements (`*`, `>=1.2, <2`), git and external path dependencies are left alone. Lock-file-only updates (`[Lock]`) are offered for direct dependencies when `Cargo.lock` is behind a requirement that already allows a newer release; transitive crates are not. Registries that need authentication and `[patch]` sections are not covered; a vendored or local-registry source replacement is refused rather than bypassed.
219
219
  * **Maven.** Maven plugin versions and declarations inside `<profiles>` are not offered. Dependencies whose version is declared outside the repository (an external parent or BOM) are listed as *not offered* with the reason.
220
220
 
221
221
  ---
package/dist/cli.js CHANGED
@@ -1351,7 +1351,7 @@ var CargoConfigReader = class {
1351
1351
 
1352
1352
  // src/version.ts
1353
1353
  import { createRequire } from "module";
1354
- var VERSION = true ? "1.5.0" : fromPackage();
1354
+ var VERSION = true ? "1.6.0" : fromPackage();
1355
1355
 
1356
1356
  // src/adapters/cargo/cargo-index.ts
1357
1357
  var RegistryAuthRequiredError = class extends Error {
@@ -1463,6 +1463,13 @@ import semver6 from "semver";
1463
1463
  // src/adapters/cargo/cargo-versions.ts
1464
1464
  import semver5 from "semver";
1465
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
+ }
1466
1473
  function selectVersion(versions, options) {
1467
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));
1468
1475
  const project = options.rustVersion ? rust(options.rustVersion) : void 0;
@@ -1549,7 +1556,7 @@ function cargoDiff(from, to) {
1549
1556
  }
1550
1557
 
1551
1558
  // src/adapters/cargo/cargo-scanner.ts
1552
- import { readFileSync as readFileSync10 } from "fs";
1559
+ import { readFileSync as readFileSync11 } from "fs";
1553
1560
  import { join as join11, relative as relative4 } from "path";
1554
1561
  import semver8 from "semver";
1555
1562
 
@@ -1621,6 +1628,20 @@ function locateSection(path) {
1621
1628
  return { section, inWorkspace, target, rest: path.slice(i + 1) };
1622
1629
  }
1623
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
+
1624
1645
  // src/adapters/cargo/cargo-scanner.ts
1625
1646
  var CONCURRENCY2 = 6;
1626
1647
  var DIFF_ORDER2 = { major: 0, minor: 1, patch: 2 };
@@ -1634,6 +1655,7 @@ var CargoDependencyAdapter = class {
1634
1655
  const pkg = metadata.packages.find((p) => samePath(join11(p.manifest_path, ".."), module.path));
1635
1656
  if (!pkg) return { candidates: [], skipped: [] };
1636
1657
  const own = readDeclarations(pkg.manifest_path);
1658
+ const locks = readLockedVersions(join11(metadata.workspace_root, "Cargo.lock"));
1637
1659
  const rootManifest = join11(metadata.workspace_root, "Cargo.toml");
1638
1660
  const root = readDeclarations(rootManifest);
1639
1661
  const groups = groupDependencies(pkg.dependencies);
@@ -1650,7 +1672,7 @@ var CargoDependencyAdapter = class {
1650
1672
  return target ? alignment(primary, target, located, common) : null;
1651
1673
  }
1652
1674
  if (!primary.source || primary.source.startsWith("git+")) return null;
1653
- const outcome = await this.registryOutcome(primary, located, pkg, metadata, common, context);
1675
+ const outcome = await this.registryOutcome(primary, located, pkg, metadata, locks, common, context);
1654
1676
  const other = group.find((d) => d !== primary && !sameRequirement(d.req, primary.req));
1655
1677
  if (outcome && "candidate" in outcome && other) {
1656
1678
  outcome.note = {
@@ -1674,7 +1696,7 @@ var CargoDependencyAdapter = class {
1674
1696
  candidates.sort((a, b) => (DIFF_ORDER2[a.diff] ?? 3) - (DIFF_ORDER2[b.diff] ?? 3));
1675
1697
  return { candidates, skipped };
1676
1698
  }
1677
- async registryOutcome(dep, located, pkg, metadata, common, context) {
1699
+ async registryOutcome(dep, located, pkg, metadata, locks, common, context) {
1678
1700
  const literal = located?.version.value ?? dep.req;
1679
1701
  if (!isUpgradableRequirement(literal)) return null;
1680
1702
  if (!located || !sameRequirement(located.version.value, dep.req)) {
@@ -1711,7 +1733,7 @@ var CargoDependencyAdapter = class {
1711
1733
  return null;
1712
1734
  }
1713
1735
  const newRange = rewriteRequirement(literal, latest);
1714
- if (newRange === literal) return null;
1736
+ if (newRange === literal) return lockOutcome(dep, literal, origin, versions, pkg, metadata, locks, common, context);
1715
1737
  const tag = semver8.prerelease(latest) ? extractPreReleaseTag(latest) ?? "PRE" : void 0;
1716
1738
  const diff = cargoDiff(floor, latest);
1717
1739
  const candidate = {
@@ -1734,6 +1756,34 @@ var CargoDependencyAdapter = class {
1734
1756
  return { candidate };
1735
1757
  }
1736
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
+ }
1737
1787
  function alignment(dep, target, located, common) {
1738
1788
  const local = target.version;
1739
1789
  if (!located || !local || !semver8.valid(local)) return null;
@@ -1772,7 +1822,7 @@ function groupDependencies(deps) {
1772
1822
  }
1773
1823
  function readDeclarations(manifestPath) {
1774
1824
  try {
1775
- return parseCargoManifest(readFileSync10(manifestPath, "utf8"));
1825
+ return parseCargoManifest(readFileSync11(manifestPath, "utf8"));
1776
1826
  } catch {
1777
1827
  return [];
1778
1828
  }
@@ -1964,20 +2014,6 @@ function applyRangeEdits(source, edits) {
1964
2014
  return result;
1965
2015
  }
1966
2016
 
1967
- // src/adapters/cargo/cargo-lockfile.ts
1968
- import { existsSync as existsSync7, readFileSync as readFileSync11 } from "fs";
1969
- import { getStaticTOMLValue as getStaticTOMLValue2, parseTOML as parseTOML3 } from "toml-eslint-parser";
1970
- function readLockedVersions(lockFile) {
1971
- const locked = /* @__PURE__ */ new Map();
1972
- if (!existsSync7(lockFile)) return locked;
1973
- try {
1974
- const data = getStaticTOMLValue2(parseTOML3(readFileSync11(lockFile, "utf8")));
1975
- for (const pkg of data.package ?? []) locked.set(pkg.name, [...locked.get(pkg.name) ?? [], pkg.version]);
1976
- } catch {
1977
- }
1978
- return locked;
1979
- }
1980
-
1981
2017
  // src/adapters/cargo/cargo-updater.ts
1982
2018
  var UPDATE_TIMEOUT_MS = 10 * 6e4;
1983
2019
  var workspaceDir = (module, rootDir) => module.aggregatorDir ?? module.path ?? rootDir;
@@ -1991,6 +2027,9 @@ var CargoUpdaterAdapter = class {
1991
2027
  const dir = workspaceDir(module, rootDir);
1992
2028
  const lockFile = join12(dir, "Cargo.lock");
1993
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);
1994
2033
  const byFile = /* @__PURE__ */ new Map();
1995
2034
  for (const update of updates) {
1996
2035
  const file = resolve6(update.declaration?.file ?? join12(module.path, "Cargo.toml"));
@@ -2028,13 +2067,37 @@ var CargoUpdaterAdapter = class {
2028
2067
  if (existsSync8(lockFile) && registryUpdates.length > 0) {
2029
2068
  await this.refreshLock(dir, lockFile, lockedBefore, registryUpdates, onProgress);
2030
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("; ");
2031
2081
  return {
2032
- updatedCount: updates.length,
2033
- auditMessage: `Updated ${updates.length} requirement(s) in ${writes.length} Cargo.toml file(s)${existsSync8(lockFile) ? "; Cargo.lock refreshed" : ""}.`,
2082
+ updatedCount: updates.length + lockUpdates.length,
2083
+ auditMessage: `${message2.charAt(0).toUpperCase()}${message2.slice(1)}.`,
2034
2084
  auditSeverity: "clean",
2035
2085
  integrityViolations: []
2036
2086
  };
2037
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
+ }
2038
2101
  async resync() {
2039
2102
  }
2040
2103
  async refreshLock(dir, lockFile, before, updates, onProgress) {
@@ -2075,11 +2138,13 @@ var same = (a, b) => relative5(resolve7(a), resolve7(b)) === "";
2075
2138
  function planCheck(metadata, module, changedFiles, locked) {
2076
2139
  const args = ["check", "--all-targets", ...locked ? ["--locked"] : []];
2077
2140
  const rootManifest = join13(metadata.workspace_root, "Cargo.toml");
2141
+ const lockFile = join13(metadata.workspace_root, "Cargo.lock");
2078
2142
  const memberManifests = new Set(metadata.packages.map((p) => resolve7(p.manifest_path)));
2079
2143
  if (changedFiles.some((f) => same(f, rootManifest))) {
2080
2144
  const isVirtualRoot = !memberManifests.has(resolve7(rootManifest));
2081
2145
  if (isVirtualRoot || metadata.packages.length > 1) return [...args, "--workspace"];
2082
2146
  }
2147
+ if (changedFiles.length > 0 && changedFiles.every((f) => same(f, lockFile))) return [...args, "--workspace"];
2083
2148
  const changed = new Set(
2084
2149
  metadata.packages.filter((p) => changedFiles.some((f) => same(f, p.manifest_path))).map((p) => p.name)
2085
2150
  );
@@ -3480,6 +3545,7 @@ var de = {
3480
3545
  prod: "(prod/compile)",
3481
3546
  align: "[Angleichen]",
3482
3547
  internal: "[Intern]",
3548
+ lock: "[Lock]",
3483
3549
  shared: (count) => `[+${count} Crates]`
3484
3550
  },
3485
3551
  status: {
@@ -3591,6 +3657,7 @@ var en = {
3591
3657
  prod: "(prod/compile)",
3592
3658
  align: "[Align]",
3593
3659
  internal: "[Internal]",
3660
+ lock: "[Lock]",
3594
3661
  shared: (count) => `[+${count} crates]`
3595
3662
  },
3596
3663
  status: {
@@ -4361,6 +4428,10 @@ var PackageRow = ({ item, isFocused, t }) => {
4361
4428
  t.badges.align,
4362
4429
  " "
4363
4430
  ] }),
4431
+ item.action === "lock" && /* @__PURE__ */ jsxs5(Text5, { bold: true, color: theme.colors.brandLight, children: [
4432
+ t.badges.lock,
4433
+ " "
4434
+ ] }),
4364
4435
  item.origin.kind === "private" && /* @__PURE__ */ jsxs5(Text5, { color: theme.colors.brandLight, children: [
4365
4436
  t.badges.internal,
4366
4437
  " "
@@ -5397,7 +5468,7 @@ async function handleScanCommand(options, config) {
5397
5468
  if (result.candidates.length === 0) console.log(" \u2728 All dependencies are up to date.");
5398
5469
  for (const u of result.candidates) {
5399
5470
  const diffTag = `[${u.diff.toUpperCase()}]`.padEnd(9);
5400
- 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] " : "";
5401
5472
  const preBadge = u.isPreRelease ? `[${u.preReleaseTag || "PRE"}] ` : "";
5402
5473
  const where = u.declaration && u.declaration.displayPath !== modulePom ? ` (in ${u.declaration.displayPath}${u.declaration.propertyName ? ` \${${u.declaration.propertyName}}` : ""})` : "";
5403
5474
  console.log(
@@ -5534,6 +5605,8 @@ function retarget(found, target, options) {
5534
5605
  const isPreRelease = isPreReleaseVersion(target);
5535
5606
  return {
5536
5607
  ...found,
5608
+ // A lock update pins exactly the version that was picked.
5609
+ ...found.action === "lock" ? { latest: target } : {},
5537
5610
  newRange: policyOf(options).rewrite(found.currentRange, target),
5538
5611
  diff,
5539
5612
  selected: true,