upd-cli 0.0.25__tar.gz → 0.0.27__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.
Files changed (55) hide show
  1. {upd_cli-0.0.25 → upd_cli-0.0.27}/CHANGELOG.md +15 -0
  2. {upd_cli-0.0.25 → upd_cli-0.0.27}/Cargo.lock +1 -1
  3. {upd_cli-0.0.25 → upd_cli-0.0.27}/Cargo.toml +1 -1
  4. {upd_cli-0.0.25 → upd_cli-0.0.27}/PKG-INFO +1 -1
  5. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/align.rs +2 -10
  6. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/pypi.rs +129 -27
  7. upd_cli-0.0.27/src/version/mod.rs +105 -0
  8. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/version/pep440.rs +3 -0
  9. upd_cli-0.0.25/src/version/mod.rs +0 -55
  10. {upd_cli-0.0.25 → upd_cli-0.0.27}/.mise.toml +0 -0
  11. {upd_cli-0.0.25 → upd_cli-0.0.27}/.pre-commit-config.yaml +0 -0
  12. {upd_cli-0.0.25 → upd_cli-0.0.27}/.pre-commit-hooks.yaml +0 -0
  13. {upd_cli-0.0.25 → upd_cli-0.0.27}/.rumdl.toml +0 -0
  14. {upd_cli-0.0.25 → upd_cli-0.0.27}/LICENSE +0 -0
  15. {upd_cli-0.0.25 → upd_cli-0.0.27}/Makefile +0 -0
  16. {upd_cli-0.0.25 → upd_cli-0.0.27}/README.md +0 -0
  17. {upd_cli-0.0.25 → upd_cli-0.0.27}/assets/logo-wide.svg +0 -0
  18. {upd_cli-0.0.25 → upd_cli-0.0.27}/assets/logo.svg +0 -0
  19. {upd_cli-0.0.25 → upd_cli-0.0.27}/pyproject.toml +0 -0
  20. {upd_cli-0.0.25 → upd_cli-0.0.27}/python/upd_cli/__init__.py +0 -0
  21. {upd_cli-0.0.25 → upd_cli-0.0.27}/python/upd_cli/__main__.py +0 -0
  22. {upd_cli-0.0.25 → upd_cli-0.0.27}/python/upd_cli/py.typed +0 -0
  23. {upd_cli-0.0.25 → upd_cli-0.0.27}/rust-toolchain.toml +0 -0
  24. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/audit.rs +0 -0
  25. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/cache.rs +0 -0
  26. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/cli.rs +0 -0
  27. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/config.rs +0 -0
  28. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/interactive.rs +0 -0
  29. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/lib.rs +0 -0
  30. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/lockfile.rs +0 -0
  31. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/main.rs +0 -0
  32. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/crates_io.rs +0 -0
  33. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/github_releases.rs +0 -0
  34. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/go_proxy.rs +0 -0
  35. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/mock.rs +0 -0
  36. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/mod.rs +0 -0
  37. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/npm.rs +0 -0
  38. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/nuget.rs +0 -0
  39. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/rubygems.rs +0 -0
  40. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/terraform.rs +0 -0
  41. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/registry/utils.rs +0 -0
  42. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/cargo_toml.rs +0 -0
  43. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/csproj.rs +0 -0
  44. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/gemfile.rs +0 -0
  45. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/github_actions.rs +0 -0
  46. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/go_mod.rs +0 -0
  47. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/mise.rs +0 -0
  48. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/mod.rs +0 -0
  49. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/package_json.rs +0 -0
  50. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/pre_commit.rs +0 -0
  51. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/pyproject.rs +0 -0
  52. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/requirements.rs +0 -0
  53. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/updater/terraform.rs +0 -0
  54. {upd_cli-0.0.25 → upd_cli-0.0.27}/src/version/semver_util.rs +0 -0
  55. {upd_cli-0.0.25 → upd_cli-0.0.27}/vership.toml +0 -0
@@ -6,6 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
8
 
9
+
10
+
11
+ ## [0.0.27](https://github.com/rvben/upd/compare/v0.0.26...v0.0.27) - 2026-04-15
12
+
13
+ ### Fixed
14
+
15
+ - **align**: use pep440_rs for Python stable-version check ([7f132b3](https://github.com/rvben/upd/commit/7f132b351cdd9225a31df96d2a421c8c42926987))
16
+ - **version**: use PEP 440 release segments for precision matching ([fff041d](https://github.com/rvben/upd/commit/fff041d2d2e9508f117012a6bfc857ee57e5cd20))
17
+
18
+ ## [0.0.26](https://github.com/rvben/upd/compare/v0.0.25...v0.0.26) - 2026-04-15
19
+
20
+ ### Fixed
21
+
22
+ - **pypi**: rewrite HTML Simple API parser to handle multi-line anchor tags ([f9c937b](https://github.com/rvben/upd/commit/f9c937be297112e0556cae205f8c0f3ce54997f4))
23
+
9
24
  ## [0.0.25](https://github.com/rvben/upd/compare/v0.0.24...v0.0.25) - 2026-04-15
10
25
 
11
26
  ### Fixed
@@ -1780,7 +1780,7 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
1780
1780
 
1781
1781
  [[package]]
1782
1782
  name = "upd"
1783
- version = "0.0.25"
1783
+ version = "0.0.27"
1784
1784
  dependencies = [
1785
1785
  "anyhow",
1786
1786
  "async-trait",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "upd"
3
- version = "0.0.25"
3
+ version = "0.0.27"
4
4
  edition = "2024"
5
5
  rust-version = "1.91.1"
6
6
  description = "A fast dependency updater for Python, Node.js, Rust, Go, Ruby, Terraform, GitHub Actions, pre-commit, and Mise projects"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: upd-cli
3
- Version: 0.0.25
3
+ Version: 0.0.27
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -8,6 +8,7 @@ use crate::updater::{
8
8
  Lang, MiseUpdater, PackageJsonUpdater, ParsedDependency, PreCommitUpdater, PyProjectUpdater,
9
9
  RequirementsUpdater, TerraformUpdater, Updater,
10
10
  };
11
+ use crate::version::is_stable_pep440;
11
12
  use anyhow::Result;
12
13
  use std::collections::HashMap;
13
14
  use std::path::{Path, PathBuf};
@@ -170,16 +171,7 @@ fn find_highest_version(occurrences: &[PackageOccurrence], lang: Lang) -> Option
170
171
  /// Check if a version is stable (not a pre-release)
171
172
  fn is_stable_version(version: &str, lang: Lang) -> bool {
172
173
  match lang {
173
- Lang::Python => {
174
- // Python pre-release indicators: a, b, rc, alpha, beta, dev
175
- let v = version.to_lowercase();
176
- !v.contains("a")
177
- && !v.contains("b")
178
- && !v.contains("rc")
179
- && !v.contains("alpha")
180
- && !v.contains("beta")
181
- && !v.contains("dev")
182
- }
174
+ Lang::Python => is_stable_pep440(version),
183
175
  Lang::Node | Lang::Rust | Lang::Go | Lang::DotNet => {
184
176
  // Semver pre-release indicator: hyphen followed by identifier
185
177
  !version.contains('-')
@@ -542,38 +542,88 @@ impl PyPiRegistry {
542
542
  let mut versions: Vec<(Version, String)> = Vec::new();
543
543
  let normalized = package.to_lowercase().replace('_', "-");
544
544
 
545
- // Extract versions from href attributes in anchor tags
546
- // Format: <a href="...">package-version.tar.gz</a> or package-version-py3-none-any.whl
547
- // Yanked packages have data-yanked attribute: <a href="..." data-yanked="">...</a>
545
+ // Use a state machine to collect multi-line <a> opening tags.
546
+ //
547
+ // Some registries (e.g. Nexus Repository Manager) spread attributes
548
+ // across multiple lines:
549
+ // <a href="...pkg-1.0.0.whl#sha256=..."
550
+ // rel="internal"
551
+ // data-requires-python="&gt;=3.10"
552
+ // >
553
+ // pkg-1.0.0.whl
554
+ // </a>
555
+ //
556
+ // We extract the filename from the `href` URL (the last path segment
557
+ // before the `#sha256=...` fragment) rather than the link text, so
558
+ // both single-line and multi-line formats are handled uniformly.
559
+ let mut collecting = false;
560
+ let mut is_yanked = false;
561
+ let mut href_value: Option<String> = None;
562
+
548
563
  for line in html.lines() {
549
- // Skip yanked packages (marked with data-yanked attribute)
550
- if line.contains("data-yanked") {
551
- continue;
564
+ let trimmed = line.trim();
565
+
566
+ // Detect start of an anchor element
567
+ if trimmed.starts_with("<a ") || trimmed.starts_with("<a\t") {
568
+ collecting = true;
569
+ is_yanked = false;
570
+ href_value = None;
552
571
  }
553
572
 
554
- let Some(start) = line.find('>') else {
555
- continue;
556
- };
557
- let Some(end) = line[start..].find('<') else {
558
- continue;
559
- };
560
- let filename = &line[start + 1..start + end];
561
- let Some(version_str) = Self::extract_version_from_filename(filename, &normalized)
562
- else {
563
- continue;
564
- };
573
+ if collecting {
574
+ // Check for yanked attribute (may be on any line of the element)
575
+ if trimmed.contains("data-yanked") {
576
+ is_yanked = true;
577
+ }
565
578
 
566
- if !include_prereleases && !Self::is_stable_version(&version_str) {
567
- continue;
568
- }
579
+ // Extract href value from whichever line carries it
580
+ if href_value.is_none()
581
+ && let Some(href_start) = trimmed.find("href=\"")
582
+ {
583
+ let after = &trimmed[href_start + 6..];
584
+ if let Some(href_end) = after.find('"') {
585
+ href_value = Some(after[..href_end].to_string());
586
+ }
587
+ }
569
588
 
570
- let Ok(version) = version_str.parse::<Version>() else {
571
- continue;
572
- };
589
+ // The opening tag ends when we see a bare `>`.
590
+ // `&gt;` in attribute values (e.g. data-requires-python="&gt;=3.10")
591
+ // is the HTML entity and does NOT contain a literal `>` byte in the
592
+ // raw HTTP response, so it won't trigger this check.
593
+ if trimmed.contains('>') {
594
+ collecting = false;
573
595
 
574
- // Avoid duplicates
575
- if !versions.iter().any(|(_, v)| v == &version_str) {
576
- versions.push((version, version_str));
596
+ if is_yanked {
597
+ continue;
598
+ }
599
+
600
+ let Some(href) = href_value.take() else {
601
+ continue;
602
+ };
603
+
604
+ // Filename is the last URL path segment, before any `#fragment`
605
+ let url_path = href.split('#').next().unwrap_or(&href);
606
+ let filename = url_path.split('/').next_back().unwrap_or("");
607
+
608
+ let Some(version_str) =
609
+ Self::extract_version_from_filename(filename, &normalized)
610
+ else {
611
+ continue;
612
+ };
613
+
614
+ if !include_prereleases && !Self::is_stable_version(&version_str) {
615
+ continue;
616
+ }
617
+
618
+ let Ok(version) = version_str.parse::<Version>() else {
619
+ continue;
620
+ };
621
+
622
+ // Avoid duplicates (e.g. both .whl and .tar.gz for the same version)
623
+ if !versions.iter().any(|(_, v)| v == &version_str) {
624
+ versions.push((version, version_str));
625
+ }
626
+ }
577
627
  }
578
628
  }
579
629
 
@@ -846,6 +896,9 @@ mod tests {
846
896
  fn test_stable_version_detection() {
847
897
  assert!(PyPiRegistry::is_stable_version("1.0.0"));
848
898
  assert!(PyPiRegistry::is_stable_version("2.31.0"));
899
+ // Post-releases are stable releases that fix packaging issues
900
+ assert!(PyPiRegistry::is_stable_version("1.0.0.post1"));
901
+ assert!(PyPiRegistry::is_stable_version("2.1117.0.post1"));
849
902
  assert!(!PyPiRegistry::is_stable_version("1.0.0a1"));
850
903
  assert!(!PyPiRegistry::is_stable_version("1.0.0b2"));
851
904
  assert!(!PyPiRegistry::is_stable_version("1.0.0rc1"));
@@ -952,6 +1005,56 @@ mod tests {
952
1005
  assert_eq!(versions[1].1, "1.0.0");
953
1006
  }
954
1007
 
1008
+ #[test]
1009
+ fn test_parse_simple_api_response_multiline_anchor_format() {
1010
+ // Some registries (e.g. Nexus Repository Manager) spread <a> attributes
1011
+ // across multiple lines, with the filename text on a separate line after
1012
+ // the closing '>'. Versions must be extracted from the href URL, not the
1013
+ // link text.
1014
+ let registry = PyPiRegistry::new();
1015
+ let html = r#"
1016
+ <!DOCTYPE html>
1017
+ <html lang="en">
1018
+ <head><title>Links for my-package</title>
1019
+ <meta name="api-version" value="2"/>
1020
+ </head>
1021
+ <body>
1022
+ <h1>Links for my-package</h1>
1023
+ <a href="../../packages/my-package/2.0.0/my_package-2.0.0.tar.gz#sha256=abc123"
1024
+ rel="internal"
1025
+ data-requires-python="&gt;=3.10"
1026
+
1027
+ >
1028
+ my_package-2.0.0.tar.gz
1029
+ </a>
1030
+ <br/>
1031
+ <a href="../../packages/my-package/2.0.0/my_package-2.0.0-py3-none-any.whl#sha256=def456"
1032
+ rel="internal"
1033
+ data-requires-python="&gt;=3.10"
1034
+
1035
+ >
1036
+ my_package-2.0.0-py3-none-any.whl
1037
+ </a>
1038
+ <br/>
1039
+ <a href="../../packages/my-package/1.9.0/my_package-1.9.0-py3-none-any.whl#sha256=ghi789"
1040
+ rel="internal"
1041
+ data-requires-python="&gt;=3.10"
1042
+ data-yanked="superseded"
1043
+ >
1044
+ my_package-1.9.0-py3-none-any.whl
1045
+ </a>
1046
+ </body>
1047
+ </html>
1048
+ "#;
1049
+ let versions = registry
1050
+ .parse_simple_api_response(html, "my-package", false)
1051
+ .unwrap();
1052
+ // 2.0.0 appears twice (tar.gz + whl) but should be deduplicated
1053
+ // 1.9.0 is yanked and should be excluded
1054
+ assert_eq!(versions.len(), 1);
1055
+ assert_eq!(versions[0].1, "2.0.0");
1056
+ }
1057
+
955
1058
  #[test]
956
1059
  fn test_parse_simple_api_json_response() {
957
1060
  let registry = PyPiRegistry::new();
@@ -1901,7 +2004,6 @@ mod tests {
1901
2004
  let mock_server = MockServer::start().await;
1902
2005
 
1903
2006
  // Server responds with HTML (no PEP 691 support)
1904
- // Each <a> tag needs to be on its own line for the parser
1905
2007
  let html = r#"<!DOCTYPE html>
1906
2008
  <html>
1907
2009
  <body>
@@ -0,0 +1,105 @@
1
+ pub mod pep440;
2
+ pub mod semver_util;
3
+
4
+ pub use pep440::is_stable_pep440;
5
+ pub use semver_util::is_stable_semver;
6
+
7
+ /// Match the precision of a new version to the original version's precision.
8
+ ///
9
+ /// For PEP 440 versions (Python), the release segment length is determined by
10
+ /// parsing with `pep440_rs`, so post-release (`.post1`), dev (`.dev0`), and
11
+ /// pre-release (`a1`, `b1`, `rc1`) suffixes are not counted as release segments
12
+ /// and are preserved when the release segment count matches.
13
+ ///
14
+ /// For other version schemes (semver, Go, Ruby…) that `pep440_rs` cannot parse,
15
+ /// the function falls back to counting dot-separated segments.
16
+ ///
17
+ /// Examples:
18
+ /// - ("2.0", "3.0.5") → "3.0"
19
+ /// - ("2.0.0", "3.0.5") → "3.0.5"
20
+ /// - ("2", "3.0.5") → "3"
21
+ /// - ("1.2.3.4", "5.6.7.8") → "5.6.7.8" (preserve all parts)
22
+ /// - ("2.1117.0", "2.1117.0.post1") → "2.1117.0.post1" (post-release preserved)
23
+ pub fn match_version_precision(original: &str, new_version: &str) -> String {
24
+ use pep440_rs::Version;
25
+
26
+ // Try PEP 440 parsing first; `.release()` returns only the numeric
27
+ // release tuple, correctly excluding pre/post/dev labels.
28
+ if let (Ok(orig), Ok(new)) = (original.parse::<Version>(), new_version.parse::<Version>()) {
29
+ let orig_len = orig.release().len();
30
+ let new_len = new.release().len();
31
+
32
+ return if orig_len >= new_len {
33
+ // Same or fewer release segments — keep the full new version string
34
+ // (including any post/pre/dev suffix) as written by the caller.
35
+ new_version.to_string()
36
+ } else {
37
+ // More release segments than original — truncate to original precision.
38
+ // Any post/pre/dev suffix on the new version is intentionally dropped
39
+ // since it belongs to a more specific release than we're tracking.
40
+ new.release()[..orig_len]
41
+ .iter()
42
+ .map(|n| n.to_string())
43
+ .collect::<Vec<_>>()
44
+ .join(".")
45
+ };
46
+ }
47
+
48
+ // Fallback for non-PEP-440 versions (semver, Go modules, etc.)
49
+ let original_parts: Vec<&str> = original.split('.').collect();
50
+ let new_parts: Vec<&str> = new_version.split('.').collect();
51
+ let precision = original_parts.len();
52
+
53
+ if precision >= new_parts.len() {
54
+ new_version.to_string()
55
+ } else {
56
+ new_parts[..precision].join(".")
57
+ }
58
+ }
59
+
60
+ #[cfg(test)]
61
+ mod tests {
62
+ use super::*;
63
+
64
+ #[test]
65
+ fn test_match_version_precision() {
66
+ // Major.minor only
67
+ assert_eq!(match_version_precision("2.0", "3.0.5"), "3.0");
68
+ assert_eq!(match_version_precision("2.8", "3.1.2"), "3.1");
69
+
70
+ // Full precision
71
+ assert_eq!(match_version_precision("2.0.0", "3.0.5"), "3.0.5");
72
+ assert_eq!(match_version_precision("1.2.3", "4.5.6"), "4.5.6");
73
+
74
+ // Major only
75
+ assert_eq!(match_version_precision("2", "3.0.5"), "3");
76
+
77
+ // New version has fewer parts than original (edge case)
78
+ assert_eq!(match_version_precision("2.0.0", "3.0"), "3.0");
79
+ assert_eq!(match_version_precision("2.0.0.0", "3.0.5"), "3.0.5");
80
+
81
+ // Same precision
82
+ assert_eq!(match_version_precision("1.0.0", "2.0.0"), "2.0.0");
83
+
84
+ // PEP 440 post-release suffix must be preserved when release precision matches
85
+ assert_eq!(
86
+ match_version_precision("2.1117.0", "2.1117.0.post1"),
87
+ "2.1117.0.post1"
88
+ );
89
+ assert_eq!(
90
+ match_version_precision("1.0.0", "1.0.0.post2"),
91
+ "1.0.0.post2"
92
+ );
93
+
94
+ // PEP 440 pre-release labels (no dot separator) are also preserved
95
+ assert_eq!(match_version_precision("2.0.0", "2.0.1a1"), "2.0.1a1");
96
+ assert_eq!(match_version_precision("2.0.0", "2.0.1b1"), "2.0.1b1");
97
+ assert_eq!(match_version_precision("2.0.0", "2.0.1rc1"), "2.0.1rc1");
98
+
99
+ // PEP 440 dev releases are also preserved
100
+ assert_eq!(match_version_precision("2.0.0", "2.0.0.dev1"), "2.0.0.dev1");
101
+
102
+ // Truncation path: suffix is dropped when original has lower precision
103
+ assert_eq!(match_version_precision("2.0", "3.0.5.post1"), "3.0");
104
+ }
105
+ }
@@ -28,6 +28,9 @@ mod tests {
28
28
  assert!(is_stable_pep440("0.1.0"));
29
29
  assert!(is_stable_pep440("1.0"));
30
30
  assert!(is_stable_pep440("1"));
31
+ // Post-releases are stable releases that fix packaging issues
32
+ assert!(is_stable_pep440("1.0.0.post1"));
33
+ assert!(is_stable_pep440("2.1117.0.post1"));
31
34
  }
32
35
 
33
36
  #[test]
@@ -1,55 +0,0 @@
1
- pub mod pep440;
2
- pub mod semver_util;
3
-
4
- pub use pep440::is_stable_pep440;
5
- pub use semver_util::is_stable_semver;
6
-
7
- /// Match the precision of a new version to the original version's precision.
8
- ///
9
- /// Examples:
10
- /// - ("2.0", "3.0.5") → "3.0"
11
- /// - ("2.0.0", "3.0.5") → "3.0.5"
12
- /// - ("2", "3.0.5") → "3"
13
- /// - ("1.2.3.4", "5.6.7.8") → "5.6.7.8" (preserve all parts)
14
- pub fn match_version_precision(original: &str, new_version: &str) -> String {
15
- let original_parts: Vec<&str> = original.split('.').collect();
16
- let new_parts: Vec<&str> = new_version.split('.').collect();
17
-
18
- // Count how many parts the original has
19
- let precision = original_parts.len();
20
-
21
- // Take the same number of parts from the new version
22
- if precision >= new_parts.len() {
23
- // Original has same or more precision, use full new version
24
- new_version.to_string()
25
- } else {
26
- // Original has less precision, truncate new version
27
- new_parts[..precision].join(".")
28
- }
29
- }
30
-
31
- #[cfg(test)]
32
- mod tests {
33
- use super::*;
34
-
35
- #[test]
36
- fn test_match_version_precision() {
37
- // Major.minor only
38
- assert_eq!(match_version_precision("2.0", "3.0.5"), "3.0");
39
- assert_eq!(match_version_precision("2.8", "3.1.2"), "3.1");
40
-
41
- // Full precision
42
- assert_eq!(match_version_precision("2.0.0", "3.0.5"), "3.0.5");
43
- assert_eq!(match_version_precision("1.2.3", "4.5.6"), "4.5.6");
44
-
45
- // Major only
46
- assert_eq!(match_version_precision("2", "3.0.5"), "3");
47
-
48
- // New version has fewer parts than original (edge case)
49
- assert_eq!(match_version_precision("2.0.0", "3.0"), "3.0");
50
- assert_eq!(match_version_precision("2.0.0.0", "3.0.5"), "3.0.5");
51
-
52
- // Same precision
53
- assert_eq!(match_version_precision("1.0.0", "2.0.0"), "2.0.0");
54
- }
55
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes