jrl-cmakemodules-scripts 2.2.4__tar.gz → 2.4.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jrl-cmakemodules-scripts
3
- Version: 2.2.4
3
+ Version: 2.4.0
4
4
  Summary: Release scripting tools for JRL CMake modules
5
5
  License: BSD-3-Clause
6
6
  Project-URL: homepage, https://github.com/jrl-umi3218/jrl-cmakemodules
@@ -79,12 +79,15 @@ jrl-release --bump patch --git-commit --git-tag
79
79
  | `package.xml` | `<version>` tag |
80
80
  | `pyproject.toml` | `project.version` |
81
81
  | `CHANGELOG.md` | First `## [X.Y.Z]` section (not Unreleased) |
82
- | `pixi.toml` | `[workspace] version` |
82
+ | `pixi.toml` | `[workspace] version` and `[package] version` (pixi-build) |
83
83
  | `pixi.lock` | Regenerated via `pixi list` |
84
+ | `Cargo.toml` | `[package] version` and `[workspace.package] version` |
85
+ | `Cargo.lock` | Regenerated via `cargo update --workspace` |
84
86
  | `CITATION.cff` | `version` key |
85
87
  | `CMakeLists.txt` | `project(... VERSION X.Y.Z ...)` |
86
88
 
87
- > Requires `pixi` CLI if `pixi.lock` exists in the project root.
89
+ > Requires `pixi` CLI if `pixi.lock` exists in the project root, and `cargo`
90
+ > if `Cargo.lock` does.
88
91
 
89
92
  ## Testing
90
93
 
@@ -61,12 +61,15 @@ jrl-release --bump patch --git-commit --git-tag
61
61
  | `package.xml` | `<version>` tag |
62
62
  | `pyproject.toml` | `project.version` |
63
63
  | `CHANGELOG.md` | First `## [X.Y.Z]` section (not Unreleased) |
64
- | `pixi.toml` | `[workspace] version` |
64
+ | `pixi.toml` | `[workspace] version` and `[package] version` (pixi-build) |
65
65
  | `pixi.lock` | Regenerated via `pixi list` |
66
+ | `Cargo.toml` | `[package] version` and `[workspace.package] version` |
67
+ | `Cargo.lock` | Regenerated via `cargo update --workspace` |
66
68
  | `CITATION.cff` | `version` key |
67
69
  | `CMakeLists.txt` | `project(... VERSION X.Y.Z ...)` |
68
70
 
69
- > Requires `pixi` CLI if `pixi.lock` exists in the project root.
71
+ > Requires `pixi` CLI if `pixi.lock` exists in the project root, and `cargo`
72
+ > if `Cargo.lock` does.
70
73
 
71
74
  ## Testing
72
75
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jrl-cmakemodules-scripts
3
- Version: 2.2.4
3
+ Version: 2.4.0
4
4
  Summary: Release scripting tools for JRL CMake modules
5
5
  License: BSD-3-Clause
6
6
  Project-URL: homepage, https://github.com/jrl-umi3218/jrl-cmakemodules
@@ -79,12 +79,15 @@ jrl-release --bump patch --git-commit --git-tag
79
79
  | `package.xml` | `<version>` tag |
80
80
  | `pyproject.toml` | `project.version` |
81
81
  | `CHANGELOG.md` | First `## [X.Y.Z]` section (not Unreleased) |
82
- | `pixi.toml` | `[workspace] version` |
82
+ | `pixi.toml` | `[workspace] version` and `[package] version` (pixi-build) |
83
83
  | `pixi.lock` | Regenerated via `pixi list` |
84
+ | `Cargo.toml` | `[package] version` and `[workspace.package] version` |
85
+ | `Cargo.lock` | Regenerated via `cargo update --workspace` |
84
86
  | `CITATION.cff` | `version` key |
85
87
  | `CMakeLists.txt` | `project(... VERSION X.Y.Z ...)` |
86
88
 
87
- > Requires `pixi` CLI if `pixi.lock` exists in the project root.
89
+ > Requires `pixi` CLI if `pixi.lock` exists in the project root, and `cargo`
90
+ > if `Cargo.lock` does.
88
91
 
89
92
  ## Testing
90
93
 
@@ -46,11 +46,11 @@ uv run --no-project jrl_release.py --bump patch --git-commit --git-tag
46
46
  | Option | Description |
47
47
  | :--- | :--- |
48
48
  | `--root <PATH>` | Project root (default: cwd). |
49
- | `--bump <major|minor|patch>` | Bump version component. |
49
+ | `--bump {major,minor,patch}` | Bump version component. |
50
50
  | `--update-version <X.Y.Z>` | Set a specific version. |
51
51
  | `--dry-run` | Show changes without writing files. |
52
52
  | `--short` | Print only the version string. |
53
- | `--output-format <text|json>` | Output format (default: text). |
53
+ | `--output-format {text,json}` | Output format (default: text). |
54
54
  | `--confirm` | Skip interactive prompts. |
55
55
  | `--list-files` | List tracked files. |
56
56
  | `--git-commit [MSG]` | Commit changes. Optional message (`{version}` placeholder). |
@@ -71,21 +71,24 @@ uv run --no-project jrl_release.py --bump patch --git-commit --git-tag
71
71
  | `package.xml` | `<version> and <url>` tag |
72
72
  | `pyproject.toml` | `project.version` and `project.urls` |
73
73
  | `CHANGELOG.md` | First `## [X.Y.Z]` section (not Unreleased) |
74
- | `pixi.toml` | `[workspace] version` |
74
+ | `pixi.toml` | `[workspace] version` and `[package] version` (pixi-build) |
75
75
  | `pixi.lock` | Regenerated via `pixi list` |
76
+ | `Cargo.toml` | `[package] version` and `[workspace.package] version` |
77
+ | `Cargo.lock` | Regenerated via `cargo update --workspace` |
76
78
  | `CITATION.cff` | `version` key |
77
79
  | `CMakeLists.txt` | `project(... VERSION X.Y.Z ... HOMEPAGE_URL ...)` |
78
80
  | `debian/changelog` | `version = ...` |
79
81
  | `conanfile.py` | `version = ...` |
80
82
 
81
- > Requires `pixi` CLI if `pixi.lock` exists in the project root.
83
+ > Requires `pixi` CLI if `pixi.lock` exists in the project root, and `cargo`
84
+ > if `Cargo.lock` does.
82
85
 
83
86
  ## Meta-packages
84
87
 
85
88
  ROS meta-packages are handled automatically: every nested directory with a
86
89
  `package.xml` is treated like the repository root — the full set of
87
90
  supported files (`package.xml`, `pyproject.toml`, `CHANGELOG.md`, `pixi.toml`,
88
- `CITATION.cff`, `CMakeLists.txt`, `debian/changelog`) is checked there too,
91
+ `Cargo.toml`, `CITATION.cff`, `CMakeLists.txt`, `debian/changelog`) is checked there too,
89
92
  skipping any that don't exist in that nested directory.
90
93
  Discovery skips hidden directories (`.git`, `.pixi`, `.venv`, …), git submodules,
91
94
  and — honoring your `.gitignore` via `git check-ignore` — ignored dirs like
@@ -98,10 +101,12 @@ import re
98
101
  import os
99
102
  import argparse
100
103
  import datetime
104
+ import importlib.metadata
101
105
  import json
102
106
  import subprocess
103
107
  import shutil
104
108
  import tempfile
109
+ from collections import Counter
105
110
  from pathlib import Path
106
111
  from abc import ABC, abstractmethod
107
112
  from typing import List, Optional, Tuple, Dict
@@ -167,6 +172,11 @@ class VersionExtractor(ABC):
167
172
  def check_file_exists(self) -> bool:
168
173
  return self.file_path.exists()
169
174
 
175
+ @property
176
+ def id(self) -> tuple:
177
+ """Unique identity of the tracked version field."""
178
+ return (self.file_path,)
179
+
170
180
  @property
171
181
  def name(self) -> str:
172
182
  return self.file_path.name
@@ -213,6 +223,8 @@ class XmlVersionExtractor(VersionExtractor):
213
223
  f.write(new_content)
214
224
 
215
225
  def get_url(self) -> str | None:
226
+ if not self.file_path.exists():
227
+ return None
216
228
  with open(self.file_path, "r", encoding="utf-8") as f:
217
229
  content = f.read()
218
230
  for url in re.findall(r"<url[^>]*>(.*?)</url>", content, re.I):
@@ -226,6 +238,11 @@ class TomlVersionExtractor(VersionExtractor):
226
238
  super().__init__(file_path)
227
239
  self.keys = keys
228
240
 
241
+ @property
242
+ def id(self) -> tuple:
243
+ # Several keys can be tracked in the same file (e.g. pixi.toml, Cargo.toml).
244
+ return (self.file_path, tuple(self.keys))
245
+
229
246
  def get_version(self) -> str:
230
247
  with open(self.file_path, "r", encoding="utf-8") as f:
231
248
  data = tomlkit.load(f)
@@ -239,6 +256,13 @@ class TomlVersionExtractor(VersionExtractor):
239
256
  f"Key '{'.'.join(self.keys)}' not found in {self.name}"
240
257
  )
241
258
 
259
+ # e.g. pixi-build `[package] version = { workspace = true }`,
260
+ # or Cargo `version.workspace = true`
261
+ if isinstance(value, dict):
262
+ raise VersionNotPresent(
263
+ f"Key '{'.'.join(self.keys)}' in {self.name} is not a version string"
264
+ )
265
+
242
266
  return str(value)
243
267
 
244
268
  def update_version(self, new_version: str) -> None:
@@ -259,6 +283,8 @@ class TomlVersionExtractor(VersionExtractor):
259
283
  tomlkit.dump(data, f)
260
284
 
261
285
  def get_url(self) -> str | None:
286
+ if not self.file_path.exists():
287
+ return None
262
288
  with open(self.file_path, "r", encoding="utf-8") as f:
263
289
  data = tomlkit.load(f)
264
290
  if "project" in data and "urls" in data["project"]:
@@ -375,6 +401,8 @@ class CMakeListsVersionExtractor(VersionExtractor):
375
401
  return self._get_version_regex(content)
376
402
 
377
403
  def get_url(self) -> str | None:
404
+ if not self.file_path.exists():
405
+ return None
378
406
  with open(self.file_path, "r", encoding="utf-8") as f:
379
407
  content = f.read()
380
408
  if match := re.search(r'HOMEPAGE_URL\s+"([^"]+)"', content):
@@ -775,6 +803,11 @@ def build_root_checks(root_dir: Path) -> List[VersionExtractor]:
775
803
  TomlVersionExtractor(root_dir / "pyproject.toml", ["project", "version"]),
776
804
  ChangelogVersionExtractor(root_dir / "CHANGELOG.md", r""),
777
805
  TomlVersionExtractor(root_dir / "pixi.toml", ["workspace", "version"]),
806
+ TomlVersionExtractor(root_dir / "pixi.toml", ["package", "version"]),
807
+ TomlVersionExtractor(root_dir / "Cargo.toml", ["package", "version"]),
808
+ TomlVersionExtractor(
809
+ root_dir / "Cargo.toml", ["workspace", "package", "version"]
810
+ ),
778
811
  YamlVersionExtractor(root_dir / "CITATION.cff", ["version"]),
779
812
  CMakeListsVersionExtractor(root_dir / "CMakeLists.txt"),
780
813
  DebianChangelogVersionExtractor(root_dir / "debian/changelog"),
@@ -790,21 +823,26 @@ def collect_version_checks(root_dir: Path) -> List[VersionExtractor]:
790
823
  package directory are simply skipped (VersionExtractor.check_file_exists()).
791
824
  """
792
825
  checks = list(build_root_checks(root_dir))
793
- seen_paths = {check.file_path for check in checks}
826
+ seen_ids = {check.id for check in checks}
794
827
 
795
828
  for package_root in discover_package_roots(root_dir):
796
829
  for check in build_root_checks(package_root):
797
- if check.file_path in seen_paths:
830
+ if check.id in seen_ids:
798
831
  continue
799
832
  checks.append(check)
800
- seen_paths.add(check.file_path)
833
+ seen_ids.add(check.id)
801
834
 
802
835
  # Label nested files by their relative path so identical basenames stay distinct.
836
+ checks_per_file = Counter(check.file_path for check in checks)
803
837
  for check in checks:
804
838
  try:
805
839
  check.label = str(check.file_path.relative_to(root_dir))
806
840
  except ValueError:
807
841
  check.label = check.file_path.name
842
+ # A file tracking several version fields (e.g. pixi.toml, Cargo.toml)
843
+ # gets its table appended so each field stays distinct.
844
+ if checks_per_file[check.file_path] > 1:
845
+ check.label += f" [{'.'.join(check.keys[:-1])}]"
808
846
 
809
847
  return checks
810
848
 
@@ -1235,63 +1273,113 @@ def gh_release(
1235
1273
  return False, e.stderr or ""
1236
1274
 
1237
1275
 
1238
- def update_pixi_lock(root_dir: Path, dry_run: bool = False) -> Optional[str]:
1239
- """Update pixi.lock file by running 'pixi list'.
1276
+ def update_lock_file(
1277
+ root_dir: Path,
1278
+ lock_name: str,
1279
+ command: List[str],
1280
+ install_url: str,
1281
+ dry_run: bool = False,
1282
+ backups: Optional[Dict[Path, Path]] = None,
1283
+ ) -> Optional[str]:
1284
+ """Regenerate a lock file by running `command` in root_dir.
1240
1285
 
1241
- Returns the path to pixi.lock if updated, None otherwise.
1286
+ The lock file is added to `backups` before being rewritten, so a later
1287
+ failure restores it. Returns the path to the lock file if it exists (and
1288
+ so is, or in dry run would be, regenerated), None otherwise.
1242
1289
  """
1243
- pixi_lock_path = root_dir / "pixi.lock"
1290
+ lock_path = root_dir / lock_name
1291
+ tool = command[0]
1292
+ cmd_str = " ".join(command)
1244
1293
 
1245
- if not pixi_lock_path.exists():
1294
+ if not lock_path.exists():
1246
1295
  return None
1247
1296
 
1248
1297
  if dry_run:
1249
- return None
1298
+ return str(lock_path)
1299
+
1300
+ if backups is not None:
1301
+ backup_file(backups, lock_path)
1250
1302
 
1251
1303
  console.print(
1252
- f"[{STYLE_INFO}]Running 'pixi list' to update pixi.lock...[/{STYLE_INFO}]"
1304
+ f"[{STYLE_INFO}]Running '{cmd_str}' to update {lock_name}...[/{STYLE_INFO}]"
1253
1305
  )
1254
1306
  try:
1255
1307
  result = subprocess.run(
1256
- ["pixi", "list"],
1308
+ command,
1257
1309
  cwd=root_dir,
1258
1310
  timeout=60,
1259
1311
  )
1260
1312
 
1261
1313
  if result.returncode != 0:
1262
1314
  console.print(
1263
- f"[{STYLE_ERROR}]Error: 'pixi list' returned non-zero exit code: {result.returncode}[/{STYLE_ERROR}]"
1315
+ f"[{STYLE_ERROR}]Error: '{cmd_str}' returned non-zero exit code: {result.returncode}[/{STYLE_ERROR}]"
1264
1316
  )
1265
1317
  console.print(
1266
- f"[{STYLE_ERROR}]Failed to update pixi.lock. Please ensure 'pixi' is installed.[/{STYLE_ERROR}]"
1318
+ f"[{STYLE_ERROR}]Failed to update {lock_name}. Please ensure '{tool}' is installed.[/{STYLE_ERROR}]"
1267
1319
  )
1268
- raise RuntimeError(f"'pixi list' failed with exit code {result.returncode}")
1320
+ raise RuntimeError(f"'{cmd_str}' failed with exit code {result.returncode}")
1269
1321
 
1270
1322
  except subprocess.TimeoutExpired:
1271
1323
  console.print(
1272
- f"[{STYLE_ERROR}]Error: 'pixi list' command timed out[/{STYLE_ERROR}]"
1324
+ f"[{STYLE_ERROR}]Error: '{cmd_str}' command timed out[/{STYLE_ERROR}]"
1273
1325
  )
1274
- raise RuntimeError("'pixi list' command timed out after 30 seconds")
1326
+ raise RuntimeError(f"'{cmd_str}' command timed out after 60 seconds")
1275
1327
  except FileNotFoundError:
1276
- console.print(f"[{STYLE_ERROR}]Error: 'pixi' command not found[/{STYLE_ERROR}]")
1277
1328
  console.print(
1278
- f"[{STYLE_ERROR}]pixi.lock exists but 'pixi' executable is not available.[/{STYLE_ERROR}]"
1329
+ f"[{STYLE_ERROR}]Error: '{tool}' command not found[/{STYLE_ERROR}]"
1330
+ )
1331
+ console.print(
1332
+ f"[{STYLE_ERROR}]{lock_name} exists but '{tool}' executable is not available.[/{STYLE_ERROR}]"
1279
1333
  )
1280
1334
  console.print(
1281
- f"[{STYLE_INFO}]Please install pixi: https://pixi.sh[/{STYLE_INFO}]"
1335
+ f"[{STYLE_INFO}]Please install {tool}: {install_url}[/{STYLE_INFO}]"
1282
1336
  )
1283
- raise RuntimeError("'pixi' executable not found. Install from https://pixi.sh")
1337
+ raise RuntimeError(f"'{tool}' executable not found. Install from {install_url}")
1284
1338
  except Exception as e:
1285
1339
  console.print(
1286
- f"[{STYLE_ERROR}]Error: Failed to run 'pixi list': {e}[/{STYLE_ERROR}]"
1340
+ f"[{STYLE_ERROR}]Error: Failed to run '{cmd_str}': {e}[/{STYLE_ERROR}]"
1287
1341
  )
1288
- raise RuntimeError(f"Failed to run 'pixi list': {e}") from e
1342
+ raise RuntimeError(f"Failed to run '{cmd_str}': {e}") from e
1289
1343
 
1290
1344
  console.print(
1291
- f"[{STYLE_SUCCESS}]✓ Updated pixi.lock via 'pixi list'[/{STYLE_SUCCESS}]"
1345
+ f"[{STYLE_SUCCESS}]✓ Updated {lock_name} via '{cmd_str}'[/{STYLE_SUCCESS}]"
1292
1346
  )
1293
1347
 
1294
- return str(pixi_lock_path)
1348
+ return str(lock_path)
1349
+
1350
+
1351
+ def update_pixi_lock(
1352
+ root_dir: Path,
1353
+ dry_run: bool = False,
1354
+ backups: Optional[Dict[Path, Path]] = None,
1355
+ ) -> Optional[str]:
1356
+ """Update pixi.lock file by running 'pixi list'.
1357
+
1358
+ Returns the path to pixi.lock if it exists, None otherwise.
1359
+ """
1360
+ return update_lock_file(
1361
+ root_dir, "pixi.lock", ["pixi", "list"], "https://pixi.sh", dry_run, backups
1362
+ )
1363
+
1364
+
1365
+ def update_cargo_lock(
1366
+ root_dir: Path,
1367
+ dry_run: bool = False,
1368
+ backups: Optional[Dict[Path, Path]] = None,
1369
+ ) -> Optional[str]:
1370
+ """Update Cargo.lock file by running 'cargo update --workspace'.
1371
+
1372
+ Only the workspace crates' own versions are refreshed, not dependencies.
1373
+ Returns the path to Cargo.lock if it exists, None otherwise.
1374
+ """
1375
+ return update_lock_file(
1376
+ root_dir,
1377
+ "Cargo.lock",
1378
+ ["cargo", "update", "--workspace"],
1379
+ "https://rustup.rs",
1380
+ dry_run,
1381
+ backups,
1382
+ )
1295
1383
 
1296
1384
 
1297
1385
  def create_backups(file_paths: List[Path]) -> Dict[Path, Path]:
@@ -1299,17 +1387,27 @@ def create_backups(file_paths: List[Path]) -> Dict[Path, Path]:
1299
1387
 
1300
1388
  Returns a mapping of original paths to backup paths.
1301
1389
  """
1302
- backups = {}
1303
- temp_dir = Path(tempfile.mkdtemp(prefix="release_backup_"))
1390
+ backups: Dict[Path, Path] = {}
1391
+ for file_path in file_paths:
1392
+ backup_file(backups, file_path)
1393
+ return backups
1304
1394
 
1305
- # Index-prefix the name so files sharing a basename don't collide.
1306
- for index, file_path in enumerate(file_paths):
1307
- if file_path.exists():
1308
- backup_path = temp_dir / f"{index:04d}_{file_path.name}"
1309
- shutil.copy2(file_path, backup_path)
1310
- backups[file_path] = backup_path
1311
1395
 
1312
- return backups
1396
+ def backup_file(backups: Dict[Path, Path], file_path: Path) -> None:
1397
+ """Add a backup copy of file_path to backups (no-op if missing or already there)."""
1398
+ # Deduplicate: a file may carry several tracked version fields.
1399
+ if file_path in backups or not file_path.exists():
1400
+ return
1401
+
1402
+ if backups:
1403
+ temp_dir = next(iter(backups.values())).parent
1404
+ else:
1405
+ temp_dir = Path(tempfile.mkdtemp(prefix="release_backup_"))
1406
+
1407
+ # Index-prefix the name so files sharing a basename don't collide.
1408
+ backup_path = temp_dir / f"{len(backups):04d}_{file_path.name}"
1409
+ shutil.copy2(file_path, backup_path)
1410
+ backups[file_path] = backup_path
1313
1411
 
1314
1412
 
1315
1413
  def restore_backups(backups: Dict[Path, Path]) -> None:
@@ -1541,13 +1639,14 @@ def perform_version_updates(
1541
1639
  check.update_version(target_version)
1542
1640
  dry_run_rows.append((check.label, old_version, target_version))
1543
1641
  line = Text()
1544
- line.append(f" {check.label:<28}", style="cyan")
1642
+ line.append(f" {check.label:<28} ", style="cyan")
1545
1643
  line.append(old_version, style=STYLE_OLD_VALUE)
1546
1644
  line.append(" → ", style="dim")
1547
1645
  line.append(target_version, style=STYLE_NEW_VALUE)
1548
1646
  console.print(line)
1549
1647
  updated_files.append(check.label)
1550
- updated_file_paths.append(str(check.file_path))
1648
+ if str(check.file_path) not in updated_file_paths:
1649
+ updated_file_paths.append(str(check.file_path))
1551
1650
  except VersionNotPresent:
1552
1651
  pass # file exists but has no version configured; skip
1553
1652
  except Exception as e:
@@ -1560,9 +1659,17 @@ def perform_version_updates(
1560
1659
  return updated_files, updated_file_paths, failed, dry_run_rows
1561
1660
 
1562
1661
 
1662
+ def print_lock_rows(lock_paths: List[str]) -> None:
1663
+ for lock_path in lock_paths:
1664
+ line = Text()
1665
+ line.append(f" {Path(lock_path).name:<28}", style="cyan")
1666
+ line.append("regenerated", style="dim")
1667
+ console.print(line)
1668
+
1669
+
1563
1670
  def show_dry_run_panel(
1564
1671
  dry_run_rows: List[Tuple[str, str, str]],
1565
- pixi_lock_would_update: bool,
1672
+ lock_paths: List[str],
1566
1673
  git_lines: List[str],
1567
1674
  ) -> None:
1568
1675
  """Display a unified dry-run preview."""
@@ -1580,11 +1687,7 @@ def show_dry_run_panel(
1580
1687
  line.append(" → ", style="dim")
1581
1688
  line.append(new, style=STYLE_NEW_VALUE)
1582
1689
  console.print(line)
1583
- if pixi_lock_would_update:
1584
- line = Text()
1585
- line.append(f" {'pixi.lock':<28}", style="cyan")
1586
- line.append("regenerated via pixi list", style="dim")
1587
- console.print(line)
1690
+ print_lock_rows(lock_paths)
1588
1691
 
1589
1692
  if git_lines:
1590
1693
  console.print()
@@ -1596,14 +1699,10 @@ def show_dry_run_panel(
1596
1699
 
1597
1700
 
1598
1701
  def show_result_panel(
1599
- pixi_lock_updated: bool,
1702
+ lock_paths: List[str],
1600
1703
  ) -> None:
1601
1704
  """Display a polished summary of completed version updates."""
1602
- if pixi_lock_updated:
1603
- line = Text()
1604
- line.append(f" {'pixi.lock':<28}", style="cyan")
1605
- line.append("regenerated via pixi list", style="dim")
1606
- console.print(line)
1705
+ print_lock_rows(lock_paths)
1607
1706
  console.print()
1608
1707
  console.print(
1609
1708
  f"[{STYLE_SUCCESS_STRONG}]✓ Version updated successfully[/{STYLE_SUCCESS_STRONG}]"
@@ -1730,6 +1829,11 @@ def main():
1730
1829
  )
1731
1830
 
1732
1831
  group = parser.add_mutually_exclusive_group(required=True)
1832
+ group.add_argument(
1833
+ "--version",
1834
+ action="store_true",
1835
+ help="Show jrl-cmakemodules-scripts version and exit",
1836
+ )
1733
1837
  group.add_argument(
1734
1838
  "--check-version", action="store_true", help="Check versions across files."
1735
1839
  )
@@ -1756,6 +1860,11 @@ def main():
1756
1860
  )
1757
1861
 
1758
1862
  args = parser.parse_args()
1863
+
1864
+ if args.version:
1865
+ print(importlib.metadata.version("jrl_cmakemodules_scripts"))
1866
+ sys.exit(0)
1867
+
1759
1868
  root_dir = args.root
1760
1869
 
1761
1870
  # Redirect console output to stderr for clean stdout with json/short
@@ -1882,13 +1991,17 @@ def main():
1882
1991
  sys.exit(1)
1883
1992
 
1884
1993
  try:
1885
- pixi_lock_path = update_pixi_lock(root_dir, args.dry_run)
1886
- if pixi_lock_path:
1887
- updated_files.append("pixi.lock")
1888
- updated_file_paths.append(pixi_lock_path)
1994
+ lock_paths: List[str] = []
1995
+ if lock_path := update_pixi_lock(root_dir, args.dry_run, backups):
1996
+ lock_paths.append(lock_path)
1997
+ if lock_path := update_cargo_lock(root_dir, args.dry_run, backups):
1998
+ lock_paths.append(lock_path)
1999
+ for lock_path in lock_paths:
2000
+ updated_files.append(Path(lock_path).name)
2001
+ updated_file_paths.append(lock_path)
1889
2002
  except RuntimeError as e:
1890
2003
  console.print(
1891
- f"[{STYLE_ERROR}]Pixi lock update failed: {e}[/{STYLE_ERROR}]"
2004
+ f"[{STYLE_ERROR}]Lock file update failed: {e}[/{STYLE_ERROR}]"
1892
2005
  )
1893
2006
  if backups:
1894
2007
  console.print(
@@ -1943,8 +2056,6 @@ def main():
1943
2056
  archive_name = f"{project_url.split('/')[-1]}-{target_version}.tar.gz"
1944
2057
 
1945
2058
  if args.dry_run:
1946
- pixi_lock_would_update = (root_dir / "pixi.lock").exists()
1947
-
1948
2059
  git_lines: List[str] = []
1949
2060
  if args.git_commit is not None:
1950
2061
  custom_message = None if args.git_commit is True else args.git_commit
@@ -2000,11 +2111,11 @@ def main():
2000
2111
  )
2001
2112
  git_lines.append(call)
2002
2113
 
2003
- show_dry_run_panel(dry_run_rows, pixi_lock_would_update, git_lines)
2114
+ show_dry_run_panel(dry_run_rows, lock_paths, git_lines)
2004
2115
  sys.exit(0)
2005
2116
  else:
2006
2117
  if not args.short and args.output_format == "text":
2007
- show_result_panel(pixi_lock_path is not None)
2118
+ show_result_panel(lock_paths)
2008
2119
 
2009
2120
  # Git operations - only perform if explicitly requested
2010
2121
  if args.git_tag is not None and args.git_commit is None:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "jrl-cmakemodules-scripts"
7
- version = "2.2.4"
7
+ version = "2.4.0"
8
8
  description = "Release scripting tools for JRL CMake modules"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"