upd-cli 0.1.1__tar.gz → 0.1.3__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.
- {upd_cli-0.1.1 → upd_cli-0.1.3}/CHANGELOG.md +45 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/Cargo.lock +115 -1
- {upd_cli-0.1.1 → upd_cli-0.1.3}/Cargo.toml +2 -1
- {upd_cli-0.1.1 → upd_cli-0.1.3}/PKG-INFO +77 -2
- {upd_cli-0.1.1 → upd_cli-0.1.3}/README.md +76 -1
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/audit/mod.rs +38 -39
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/cache.rs +83 -1
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/cli.rs +40 -2
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/config.rs +324 -2
- upd_cli-0.1.3/src/cooldown.rs +926 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/lib.rs +2 -1
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/lockfile.rs +174 -71
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/main.rs +459 -15
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/output.rs +142 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/registry/crates_io.rs +92 -6
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/registry/github_releases.rs +104 -1
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/registry/go_proxy.rs +119 -1
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/registry/mock.rs +61 -1
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/registry/mod.rs +47 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/registry/npm.rs +150 -1
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/registry/nuget.rs +14 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/registry/pypi.rs +109 -4
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/registry/rubygems.rs +73 -1
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/updater/cargo_toml.rs +65 -6
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/updater/csproj.rs +55 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/updater/gemfile.rs +61 -3
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/updater/github_actions.rs +53 -1
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/updater/go_mod.rs +58 -1
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/updater/mise.rs +55 -1
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/updater/mod.rs +403 -1
- upd_cli-0.1.3/src/updater/npm_range.rs +257 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/updater/package_json.rs +485 -20
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/updater/pre_commit.rs +53 -1
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/updater/pyproject.rs +120 -10
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/updater/requirements.rs +56 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/updater/terraform.rs +1 -0
- upd_cli-0.1.3/src/version/compare.rs +79 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/version/mod.rs +1 -0
- upd_cli-0.1.3/tests/cooldown_e2e.rs +77 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/.mise.toml +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/.pre-commit-config.yaml +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/.pre-commit-hooks.yaml +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/.rumdl.toml +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/LICENSE +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/Makefile +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/assets/logo-wide.svg +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/assets/logo.svg +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/pyproject.toml +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/python/upd_cli/__init__.py +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/python/upd_cli/__main__.py +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/python/upd_cli/py.typed +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/rust-toolchain.toml +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/align.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/audit/cache.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/audit/cvss.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/interactive.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/registry/terraform.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/registry/utils.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/version/pep440.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/version/semver_util.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/src/version/tag.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/tests/audit_offline.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/tests/audit_sarif.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/tests/audit_severity.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/tests/bump_filter.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/tests/exit_codes.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/tests/fix_audit.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/tests/format_json.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/tests/help_text.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/tests/interactive_tty.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/tests/invalid_positional.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/tests/no_args_scope.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/tests/output_streams.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/tests/package_filter.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.3}/vership.toml +0 -0
|
@@ -11,6 +11,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## [0.1.3](https://github.com/rvben/upd/compare/v0.1.2...v0.1.3) - 2026-04-25
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **lock**: scope lockfile regeneration to the packages upd actually changed ([6b6cfa6](https://github.com/rvben/upd/commit/6b6cfa6fe3e8e8d787e78c7afea24883ebe67833))
|
|
21
|
+
- **npm**: classify and rewrite comparator-range specs ([a60979a](https://github.com/rvben/upd/commit/a60979acd7edb7eb6adfac554a590412a6cf8271))
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **lock**: include config pins in targeted regenerate and update CLI help ([207fdf9](https://github.com/rvben/upd/commit/207fdf94af95c74865f828f258ec9cfa61d22085))
|
|
26
|
+
- **npm**: preserve upper bound when pinning comparator-range specs ([fb7c863](https://github.com/rvben/upd/commit/fb7c863de9f07201af310ab13a59696d17fcb766))
|
|
27
|
+
- **npm**: apply config policy and cooldown to comparator-range updates ([be095dd](https://github.com/rvben/upd/commit/be095dd30443dd547fb6913dae551cbd530cb019))
|
|
28
|
+
- **npm**: update comparator-range specs via constraint-aware resolution ([4481b8b](https://github.com/rvben/upd/commit/4481b8bad6a02c6f761cf489a547b5546099e871))
|
|
29
|
+
- **audit**: order fix versions numerically, not lexicographically ([069eb1d](https://github.com/rvben/upd/commit/069eb1dc8771640376957339e40ada7b60a82b0a))
|
|
30
|
+
|
|
31
|
+
## [0.1.2](https://github.com/rvben/upd/compare/v0.1.1...v0.1.2) - 2026-04-24
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **cache**: add optional versions field to CacheEntry for future list_versions caching ([1beb34d](https://github.com/rvben/upd/commit/1beb34dc030f160e3748dff9a63e71bfa1772043))
|
|
36
|
+
- **output**: report held-back and skipped-by-cooldown packages ([3d1a2ce](https://github.com/rvben/upd/commit/3d1a2cef2ae31c59a87b417b074e0d672b7256d2))
|
|
37
|
+
- **updater**: propagate cooldown policy to remaining updaters ([8e80f25](https://github.com/rvben/upd/commit/8e80f252339f022d90f8120694e529c68c3bcf90))
|
|
38
|
+
- **updater**: apply cooldown policy in requirements updater ([5d6cfd3](https://github.com/rvben/upd/commit/5d6cfd32bfe87df7af86453476a93e2945f009ff))
|
|
39
|
+
- **registry**: implement list_versions for GitHub releases ([5f6472b](https://github.com/rvben/upd/commit/5f6472b5d7c4394d59fbabfd4bd9a1d9b736a67a))
|
|
40
|
+
- **registry**: implement list_versions for RubyGems ([1a1dda3](https://github.com/rvben/upd/commit/1a1dda31d73e0f25d8a73e6438aed7c4daadc007))
|
|
41
|
+
- **registry**: implement list_versions for Go module proxy ([196fef6](https://github.com/rvben/upd/commit/196fef634b0ae3c53096222e8bbe3161d8b67a33))
|
|
42
|
+
- **registry**: implement list_versions for crates.io ([8869dec](https://github.com/rvben/upd/commit/8869dec1a69148b5b3db44cc3393a05aaa2b01fb))
|
|
43
|
+
- **registry**: implement list_versions for npm ([b23cd78](https://github.com/rvben/upd/commit/b23cd787e748dfc5404a8fd51981c67f858e1d5a))
|
|
44
|
+
- **registry**: implement list_versions for PyPI ([9aa342c](https://github.com/rvben/upd/commit/9aa342c5c11ae504024aed5aa2d8930c66b4a6df))
|
|
45
|
+
- **cli**: add --min-age flag for cooldown override ([b5bfb30](https://github.com/rvben/upd/commit/b5bfb304c39f6b8099aef3a066e2ef4ed17f606f))
|
|
46
|
+
- **config**: show cooldown policy in --show-config ([9486257](https://github.com/rvben/upd/commit/9486257b22456e55e9af23709089a574cce262be))
|
|
47
|
+
- **config**: add [cooldown] table with default and per-ecosystem overrides ([a9ff8e3](https://github.com/rvben/upd/commit/a9ff8e31050485056a9bb6e03f4d313df1262998))
|
|
48
|
+
- **cooldown**: implement select() selection algorithm ([8b588bb](https://github.com/rvben/upd/commit/8b588bb1b42d84d696a7a17d8e97141a223351a1))
|
|
49
|
+
- **cooldown**: add CooldownPolicy with precedence resolution ([ddba284](https://github.com/rvben/upd/commit/ddba284329dad87bf84cf566ecb487ef665408a1))
|
|
50
|
+
- **cooldown**: add parse_duration for release-age config ([a7e67e0](https://github.com/rvben/upd/commit/a7e67e035764e4d905ace1dc4092f41c27510a5c))
|
|
51
|
+
- **registry**: re-export VersionMeta from crate root ([b2cdd60](https://github.com/rvben/upd/commit/b2cdd6037c09110881f53db4992542e77596f6c3))
|
|
52
|
+
- **registry**: add VersionMeta and list_versions trait method ([09ddbf9](https://github.com/rvben/upd/commit/09ddbf9c2b8f0546feafeb642f27547bed1882da))
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **cooldown**: harden selection against real-world constraints and per-file policy ([a284ea4](https://github.com/rvben/upd/commit/a284ea497dcf3abf65fda2c7e7f6c0c03c3dd8e2))
|
|
57
|
+
- **updater**: pass Poetry constraint to cooldown selection ([a0383e9](https://github.com/rvben/upd/commit/a0383e9167b2ef05cc4583aa23c1035d32268750))
|
|
58
|
+
|
|
14
59
|
## [0.1.1](https://github.com/rvben/upd/compare/v0.1.0...v0.1.1) - 2026-04-22
|
|
15
60
|
|
|
16
61
|
### Added
|
|
@@ -17,6 +17,15 @@ dependencies = [
|
|
|
17
17
|
"memchr",
|
|
18
18
|
]
|
|
19
19
|
|
|
20
|
+
[[package]]
|
|
21
|
+
name = "android_system_properties"
|
|
22
|
+
version = "0.1.5"
|
|
23
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
24
|
+
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
|
25
|
+
dependencies = [
|
|
26
|
+
"libc",
|
|
27
|
+
]
|
|
28
|
+
|
|
20
29
|
[[package]]
|
|
21
30
|
name = "anstream"
|
|
22
31
|
version = "1.0.0"
|
|
@@ -113,6 +122,12 @@ version = "1.1.2"
|
|
|
113
122
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
114
123
|
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
|
115
124
|
|
|
125
|
+
[[package]]
|
|
126
|
+
name = "autocfg"
|
|
127
|
+
version = "1.5.0"
|
|
128
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
129
|
+
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|
130
|
+
|
|
116
131
|
[[package]]
|
|
117
132
|
name = "aws-lc-rs"
|
|
118
133
|
version = "1.16.3"
|
|
@@ -199,6 +214,18 @@ version = "0.2.1"
|
|
|
199
214
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
200
215
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
|
201
216
|
|
|
217
|
+
[[package]]
|
|
218
|
+
name = "chrono"
|
|
219
|
+
version = "0.4.44"
|
|
220
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
221
|
+
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
|
222
|
+
dependencies = [
|
|
223
|
+
"iana-time-zone",
|
|
224
|
+
"num-traits",
|
|
225
|
+
"serde",
|
|
226
|
+
"windows-link",
|
|
227
|
+
]
|
|
228
|
+
|
|
202
229
|
[[package]]
|
|
203
230
|
name = "clap"
|
|
204
231
|
version = "4.6.1"
|
|
@@ -728,6 +755,30 @@ dependencies = [
|
|
|
728
755
|
"tracing",
|
|
729
756
|
]
|
|
730
757
|
|
|
758
|
+
[[package]]
|
|
759
|
+
name = "iana-time-zone"
|
|
760
|
+
version = "0.1.65"
|
|
761
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
762
|
+
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
|
763
|
+
dependencies = [
|
|
764
|
+
"android_system_properties",
|
|
765
|
+
"core-foundation-sys",
|
|
766
|
+
"iana-time-zone-haiku",
|
|
767
|
+
"js-sys",
|
|
768
|
+
"log",
|
|
769
|
+
"wasm-bindgen",
|
|
770
|
+
"windows-core",
|
|
771
|
+
]
|
|
772
|
+
|
|
773
|
+
[[package]]
|
|
774
|
+
name = "iana-time-zone-haiku"
|
|
775
|
+
version = "0.1.2"
|
|
776
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
777
|
+
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
|
778
|
+
dependencies = [
|
|
779
|
+
"cc",
|
|
780
|
+
]
|
|
781
|
+
|
|
731
782
|
[[package]]
|
|
732
783
|
name = "icu_collections"
|
|
733
784
|
version = "2.1.1"
|
|
@@ -1030,6 +1081,15 @@ dependencies = [
|
|
|
1030
1081
|
"windows-sys 0.61.2",
|
|
1031
1082
|
]
|
|
1032
1083
|
|
|
1084
|
+
[[package]]
|
|
1085
|
+
name = "num-traits"
|
|
1086
|
+
version = "0.2.19"
|
|
1087
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1088
|
+
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
|
1089
|
+
dependencies = [
|
|
1090
|
+
"autocfg",
|
|
1091
|
+
]
|
|
1092
|
+
|
|
1033
1093
|
[[package]]
|
|
1034
1094
|
name = "num_cpus"
|
|
1035
1095
|
version = "1.17.0"
|
|
@@ -1964,10 +2024,11 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
|
|
1964
2024
|
|
|
1965
2025
|
[[package]]
|
|
1966
2026
|
name = "upd"
|
|
1967
|
-
version = "0.1.
|
|
2027
|
+
version = "0.1.3"
|
|
1968
2028
|
dependencies = [
|
|
1969
2029
|
"anyhow",
|
|
1970
2030
|
"async-trait",
|
|
2031
|
+
"chrono",
|
|
1971
2032
|
"clap",
|
|
1972
2033
|
"colored",
|
|
1973
2034
|
"directories",
|
|
@@ -2143,12 +2204,65 @@ dependencies = [
|
|
|
2143
2204
|
"windows-sys 0.61.2",
|
|
2144
2205
|
]
|
|
2145
2206
|
|
|
2207
|
+
[[package]]
|
|
2208
|
+
name = "windows-core"
|
|
2209
|
+
version = "0.62.2"
|
|
2210
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2211
|
+
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
|
2212
|
+
dependencies = [
|
|
2213
|
+
"windows-implement",
|
|
2214
|
+
"windows-interface",
|
|
2215
|
+
"windows-link",
|
|
2216
|
+
"windows-result",
|
|
2217
|
+
"windows-strings",
|
|
2218
|
+
]
|
|
2219
|
+
|
|
2220
|
+
[[package]]
|
|
2221
|
+
name = "windows-implement"
|
|
2222
|
+
version = "0.60.2"
|
|
2223
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2224
|
+
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
|
2225
|
+
dependencies = [
|
|
2226
|
+
"proc-macro2",
|
|
2227
|
+
"quote",
|
|
2228
|
+
"syn",
|
|
2229
|
+
]
|
|
2230
|
+
|
|
2231
|
+
[[package]]
|
|
2232
|
+
name = "windows-interface"
|
|
2233
|
+
version = "0.59.3"
|
|
2234
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2235
|
+
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
|
2236
|
+
dependencies = [
|
|
2237
|
+
"proc-macro2",
|
|
2238
|
+
"quote",
|
|
2239
|
+
"syn",
|
|
2240
|
+
]
|
|
2241
|
+
|
|
2146
2242
|
[[package]]
|
|
2147
2243
|
name = "windows-link"
|
|
2148
2244
|
version = "0.2.1"
|
|
2149
2245
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2150
2246
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
|
2151
2247
|
|
|
2248
|
+
[[package]]
|
|
2249
|
+
name = "windows-result"
|
|
2250
|
+
version = "0.4.1"
|
|
2251
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2252
|
+
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
|
2253
|
+
dependencies = [
|
|
2254
|
+
"windows-link",
|
|
2255
|
+
]
|
|
2256
|
+
|
|
2257
|
+
[[package]]
|
|
2258
|
+
name = "windows-strings"
|
|
2259
|
+
version = "0.5.1"
|
|
2260
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2261
|
+
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
|
2262
|
+
dependencies = [
|
|
2263
|
+
"windows-link",
|
|
2264
|
+
]
|
|
2265
|
+
|
|
2152
2266
|
[[package]]
|
|
2153
2267
|
name = "windows-sys"
|
|
2154
2268
|
version = "0.45.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "upd"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.3"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
rust-version = "1.95.0"
|
|
6
6
|
description = "A fast dependency updater for Python, Node.js, Rust, Go, Ruby, Terraform, GitHub Actions, pre-commit, and Mise projects"
|
|
@@ -60,6 +60,7 @@ colored = "3.1.1"
|
|
|
60
60
|
async-trait = "0.1.89"
|
|
61
61
|
futures = "0.3.32"
|
|
62
62
|
url = "2.5"
|
|
63
|
+
chrono = { version = "0.4", default-features = false, features = ["serde", "clock"] }
|
|
63
64
|
|
|
64
65
|
[dev-dependencies]
|
|
65
66
|
tempfile = "3.27.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: upd-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -46,7 +46,10 @@ pipx run --spec upd-cli upd --apply
|
|
|
46
46
|
|
|
47
47
|
- **Multi-ecosystem**: Python, Node.js, Rust, Go, Ruby, .NET, Terraform, GitHub Actions, pre-commit, Mise/asdf
|
|
48
48
|
- **Fast**: Parallel registry requests for all dependencies
|
|
49
|
-
- **Constraint-aware**: Respects
|
|
49
|
+
- **Constraint-aware**: Respects `>=2.0,<3` (Python), `~> 7.1` (Ruby), and `^2.0.0` / `~2.0.0` (npm, Cargo).
|
|
50
|
+
For npm, comparator ranges such as `">=1.0.0 <2.0.0"` are rewritten with a **bump strategy**: the lower
|
|
51
|
+
bound moves to the highest version satisfying the constraint, preserving the upper bound. Hyphen
|
|
52
|
+
(`"1 - 2"`) and OR (`"^1 || ^2"`) ranges are reported as warnings and left untouched.
|
|
50
53
|
- **Smart caching**: 24-hour version cache for faster subsequent runs
|
|
51
54
|
- **Update filters**: Filter by bump level with `--only-bump <major|minor|patch>` (repeatable) or cap with `--max-bump`
|
|
52
55
|
- **Interactive mode**: Approve updates individually with `-i`
|
|
@@ -469,6 +472,51 @@ upd --verbose
|
|
|
469
472
|
# pyproject.toml:13: Skipped internal-utils 1.0.0 (ignored)
|
|
470
473
|
```
|
|
471
474
|
|
|
475
|
+
## Cooldown (minimum release age)
|
|
476
|
+
|
|
477
|
+
Hold back updates to versions that have been public for less than N days.
|
|
478
|
+
Reduces exposure to supply-chain attacks that rely on freshly published
|
|
479
|
+
malicious versions being installed before detection. Modelled after
|
|
480
|
+
Renovate's `minimumReleaseAge` / Dependabot's `cooldown`.
|
|
481
|
+
|
|
482
|
+
Enable in `.updrc.toml`:
|
|
483
|
+
|
|
484
|
+
```toml
|
|
485
|
+
[cooldown]
|
|
486
|
+
default = "7d" # applies to every ecosystem unless overridden
|
|
487
|
+
|
|
488
|
+
[cooldown.ecosystem]
|
|
489
|
+
npm = "14d" # stricter for npm
|
|
490
|
+
pypi = "14d"
|
|
491
|
+
"crates.io" = "3d"
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
Duration syntax: `<integer><unit>` where unit is `s`, `m`, `h`, `d`, `w`.
|
|
495
|
+
A bare `0` disables cooldown.
|
|
496
|
+
|
|
497
|
+
Override from the CLI for one-off runs:
|
|
498
|
+
|
|
499
|
+
```text
|
|
500
|
+
upd --min-age 14d # use 14 days regardless of config
|
|
501
|
+
upd --min-age 0 # disable cooldown entirely for this run
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
**How it works:** when the latest version is still inside the cooldown
|
|
505
|
+
window, `upd` updates to the newest version that *is* old enough. If nothing
|
|
506
|
+
newer is old enough yet, the package is held back. Output marks these
|
|
507
|
+
packages explicitly:
|
|
508
|
+
|
|
509
|
+
```text
|
|
510
|
+
requirements.txt: Updated requests 2.28.0 → 2.31.0
|
|
511
|
+
package.json: Held back lodash 4.17.20 → 4.17.21 (4.17.22 released 2d ago, cooldown 7d)
|
|
512
|
+
package.json: Skipped express (only newer version 4.19.0 released 1d ago, cooldown 7d)
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
**Supported ecosystems:** PyPI, npm, crates.io, Go modules, RubyGems,
|
|
516
|
+
GitHub releases (covers GitHub Actions, pre-commit, Mise). NuGet and
|
|
517
|
+
Terraform Registry do not expose per-version publish dates we can
|
|
518
|
+
consume today; cooldown is reported as unavailable for those files.
|
|
519
|
+
|
|
472
520
|
## Caching
|
|
473
521
|
|
|
474
522
|
Version lookups are cached for 24 hours in:
|
|
@@ -722,6 +770,33 @@ Global flags (accepted on every subcommand):
|
|
|
722
770
|
|
|
723
771
|
Subcommands: `update` (default), `align`, `audit`, `clean-cache`, `self-update`.
|
|
724
772
|
|
|
773
|
+
#### Commands run by `--lock`
|
|
774
|
+
|
|
775
|
+
`upd --lock` runs the narrowest per-ecosystem refresh command that
|
|
776
|
+
updates only the packages `upd` just rewrote. Targeted forms are used
|
|
777
|
+
wherever the package manager supports them; targeting falls back to
|
|
778
|
+
`--lockfile-only` flags where no per-package form exists; otherwise
|
|
779
|
+
the manifest-wide refresh command is used.
|
|
780
|
+
|
|
781
|
+
| Ecosystem | Lockfile | Command |
|
|
782
|
+
|-----------|--------------------------|------------------------------------------------|
|
|
783
|
+
| Python | `poetry.lock` | `poetry lock --no-update` |
|
|
784
|
+
| Python | `uv.lock` | `uv lock` |
|
|
785
|
+
| Node | `package-lock.json` | `npm install --package-lock-only` |
|
|
786
|
+
| Node | `yarn.lock` | `yarn install --mode update-lockfile` (Yarn 2+)|
|
|
787
|
+
| Node | `pnpm-lock.yaml` | `pnpm install --lockfile-only` |
|
|
788
|
+
| Node | `bun.lockb` | `bun install` |
|
|
789
|
+
| Rust | `Cargo.lock` | `cargo update -p <changed> -p <changed> …` |
|
|
790
|
+
| Go | `go.sum` | `go mod tidy` (no targeted form) |
|
|
791
|
+
| Ruby | `Gemfile.lock` | `bundle lock --update <changed> …` |
|
|
792
|
+
| .NET | `packages.lock.json` | `dotnet restore` (no targeted form) |
|
|
793
|
+
| Terraform | `.terraform.lock.hcl` | `terraform providers lock` (no targeted form) |
|
|
794
|
+
|
|
795
|
+
Manifests whose `upd` pass produced zero changes have their lockfile
|
|
796
|
+
refresh skipped entirely. A directory where only config pins were
|
|
797
|
+
applied is still refreshed, and the changed-package list includes
|
|
798
|
+
those pinned packages so `cargo update -p <pkg>` / `bundle lock --update <pkg>` stay scoped.
|
|
799
|
+
|
|
725
800
|
Stable `audit`-specific flags:
|
|
726
801
|
|
|
727
802
|
| Flag | Purpose |
|
|
@@ -23,7 +23,10 @@ pipx run --spec upd-cli upd --apply
|
|
|
23
23
|
|
|
24
24
|
- **Multi-ecosystem**: Python, Node.js, Rust, Go, Ruby, .NET, Terraform, GitHub Actions, pre-commit, Mise/asdf
|
|
25
25
|
- **Fast**: Parallel registry requests for all dependencies
|
|
26
|
-
- **Constraint-aware**: Respects
|
|
26
|
+
- **Constraint-aware**: Respects `>=2.0,<3` (Python), `~> 7.1` (Ruby), and `^2.0.0` / `~2.0.0` (npm, Cargo).
|
|
27
|
+
For npm, comparator ranges such as `">=1.0.0 <2.0.0"` are rewritten with a **bump strategy**: the lower
|
|
28
|
+
bound moves to the highest version satisfying the constraint, preserving the upper bound. Hyphen
|
|
29
|
+
(`"1 - 2"`) and OR (`"^1 || ^2"`) ranges are reported as warnings and left untouched.
|
|
27
30
|
- **Smart caching**: 24-hour version cache for faster subsequent runs
|
|
28
31
|
- **Update filters**: Filter by bump level with `--only-bump <major|minor|patch>` (repeatable) or cap with `--max-bump`
|
|
29
32
|
- **Interactive mode**: Approve updates individually with `-i`
|
|
@@ -446,6 +449,51 @@ upd --verbose
|
|
|
446
449
|
# pyproject.toml:13: Skipped internal-utils 1.0.0 (ignored)
|
|
447
450
|
```
|
|
448
451
|
|
|
452
|
+
## Cooldown (minimum release age)
|
|
453
|
+
|
|
454
|
+
Hold back updates to versions that have been public for less than N days.
|
|
455
|
+
Reduces exposure to supply-chain attacks that rely on freshly published
|
|
456
|
+
malicious versions being installed before detection. Modelled after
|
|
457
|
+
Renovate's `minimumReleaseAge` / Dependabot's `cooldown`.
|
|
458
|
+
|
|
459
|
+
Enable in `.updrc.toml`:
|
|
460
|
+
|
|
461
|
+
```toml
|
|
462
|
+
[cooldown]
|
|
463
|
+
default = "7d" # applies to every ecosystem unless overridden
|
|
464
|
+
|
|
465
|
+
[cooldown.ecosystem]
|
|
466
|
+
npm = "14d" # stricter for npm
|
|
467
|
+
pypi = "14d"
|
|
468
|
+
"crates.io" = "3d"
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Duration syntax: `<integer><unit>` where unit is `s`, `m`, `h`, `d`, `w`.
|
|
472
|
+
A bare `0` disables cooldown.
|
|
473
|
+
|
|
474
|
+
Override from the CLI for one-off runs:
|
|
475
|
+
|
|
476
|
+
```text
|
|
477
|
+
upd --min-age 14d # use 14 days regardless of config
|
|
478
|
+
upd --min-age 0 # disable cooldown entirely for this run
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**How it works:** when the latest version is still inside the cooldown
|
|
482
|
+
window, `upd` updates to the newest version that *is* old enough. If nothing
|
|
483
|
+
newer is old enough yet, the package is held back. Output marks these
|
|
484
|
+
packages explicitly:
|
|
485
|
+
|
|
486
|
+
```text
|
|
487
|
+
requirements.txt: Updated requests 2.28.0 → 2.31.0
|
|
488
|
+
package.json: Held back lodash 4.17.20 → 4.17.21 (4.17.22 released 2d ago, cooldown 7d)
|
|
489
|
+
package.json: Skipped express (only newer version 4.19.0 released 1d ago, cooldown 7d)
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
**Supported ecosystems:** PyPI, npm, crates.io, Go modules, RubyGems,
|
|
493
|
+
GitHub releases (covers GitHub Actions, pre-commit, Mise). NuGet and
|
|
494
|
+
Terraform Registry do not expose per-version publish dates we can
|
|
495
|
+
consume today; cooldown is reported as unavailable for those files.
|
|
496
|
+
|
|
449
497
|
## Caching
|
|
450
498
|
|
|
451
499
|
Version lookups are cached for 24 hours in:
|
|
@@ -699,6 +747,33 @@ Global flags (accepted on every subcommand):
|
|
|
699
747
|
|
|
700
748
|
Subcommands: `update` (default), `align`, `audit`, `clean-cache`, `self-update`.
|
|
701
749
|
|
|
750
|
+
#### Commands run by `--lock`
|
|
751
|
+
|
|
752
|
+
`upd --lock` runs the narrowest per-ecosystem refresh command that
|
|
753
|
+
updates only the packages `upd` just rewrote. Targeted forms are used
|
|
754
|
+
wherever the package manager supports them; targeting falls back to
|
|
755
|
+
`--lockfile-only` flags where no per-package form exists; otherwise
|
|
756
|
+
the manifest-wide refresh command is used.
|
|
757
|
+
|
|
758
|
+
| Ecosystem | Lockfile | Command |
|
|
759
|
+
|-----------|--------------------------|------------------------------------------------|
|
|
760
|
+
| Python | `poetry.lock` | `poetry lock --no-update` |
|
|
761
|
+
| Python | `uv.lock` | `uv lock` |
|
|
762
|
+
| Node | `package-lock.json` | `npm install --package-lock-only` |
|
|
763
|
+
| Node | `yarn.lock` | `yarn install --mode update-lockfile` (Yarn 2+)|
|
|
764
|
+
| Node | `pnpm-lock.yaml` | `pnpm install --lockfile-only` |
|
|
765
|
+
| Node | `bun.lockb` | `bun install` |
|
|
766
|
+
| Rust | `Cargo.lock` | `cargo update -p <changed> -p <changed> …` |
|
|
767
|
+
| Go | `go.sum` | `go mod tidy` (no targeted form) |
|
|
768
|
+
| Ruby | `Gemfile.lock` | `bundle lock --update <changed> …` |
|
|
769
|
+
| .NET | `packages.lock.json` | `dotnet restore` (no targeted form) |
|
|
770
|
+
| Terraform | `.terraform.lock.hcl` | `terraform providers lock` (no targeted form) |
|
|
771
|
+
|
|
772
|
+
Manifests whose `upd` pass produced zero changes have their lockfile
|
|
773
|
+
refresh skipped entirely. A directory where only config pins were
|
|
774
|
+
applied is still refreshed, and the changed-package list includes
|
|
775
|
+
those pinned packages so `cargo update -p <pkg>` / `bundle lock --update <pkg>` stay scoped.
|
|
776
|
+
|
|
702
777
|
Stable `audit`-specific flags:
|
|
703
778
|
|
|
704
779
|
| Flag | Purpose |
|
|
@@ -134,7 +134,7 @@ pub fn compute_fix_plan(audit: &AuditResult) -> (HashMap<String, String>, Vec<(S
|
|
|
134
134
|
.vulnerabilities
|
|
135
135
|
.iter()
|
|
136
136
|
.filter_map(|v| v.fixed_version.as_deref())
|
|
137
|
-
.max_by(|a, b|
|
|
137
|
+
.max_by(|a, b| crate::version::compare::compare_versions(a, b));
|
|
138
138
|
|
|
139
139
|
if let Some(version) = max_fixed {
|
|
140
140
|
fixable.insert(name.clone(), version.to_string());
|
|
@@ -144,44 +144,6 @@ pub fn compute_fix_plan(audit: &AuditResult) -> (HashMap<String, String>, Vec<(S
|
|
|
144
144
|
(fixable, unfixable)
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
/// Compare two version strings for ordering, preferring semver but falling back
|
|
148
|
-
/// to lexicographic comparison for non-semver ecosystems.
|
|
149
|
-
fn compare_fix_versions(a: &str, b: &str) -> std::cmp::Ordering {
|
|
150
|
-
match (semver_parse(a), semver_parse(b)) {
|
|
151
|
-
(Some(va), Some(vb)) => va.cmp(&vb),
|
|
152
|
-
_ => a.cmp(b),
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/// Parse a version string as semver, accepting an optional leading `v`.
|
|
157
|
-
///
|
|
158
|
-
/// Returns `(major, minor, patch, is_stable)` where `is_stable` is 1 for stable
|
|
159
|
-
/// releases and 0 for pre-releases (e.g. `2.0.0-rc1`). This ensures stable
|
|
160
|
-
/// versions beat pre-releases when all numeric components are equal.
|
|
161
|
-
fn semver_parse(v: &str) -> Option<(u64, u64, u64, u8)> {
|
|
162
|
-
let v = v.trim_start_matches('v');
|
|
163
|
-
let parts: Vec<&str> = v.split('.').collect();
|
|
164
|
-
if parts.len() < 2 {
|
|
165
|
-
return None;
|
|
166
|
-
}
|
|
167
|
-
let major: u64 = parts[0].parse().ok()?;
|
|
168
|
-
let minor: u64 = parts[1].parse().ok()?;
|
|
169
|
-
// Patch may carry a pre-release or build suffix (e.g. "0-rc1", "3+build1").
|
|
170
|
-
// Take only the leading digit run so "0-rc1" → patch=0, rest="-rc1".
|
|
171
|
-
let patch_part = parts.get(2).copied().unwrap_or("0");
|
|
172
|
-
let (patch_digits, rest) = patch_part
|
|
173
|
-
.split_once(|c: char| !c.is_ascii_digit())
|
|
174
|
-
.unwrap_or((patch_part, ""));
|
|
175
|
-
let patch: u64 = if patch_digits.is_empty() {
|
|
176
|
-
0
|
|
177
|
-
} else {
|
|
178
|
-
patch_digits.parse().ok()?
|
|
179
|
-
};
|
|
180
|
-
// Stability flag: 1 = stable, 0 = pre-release. Stable wins ties.
|
|
181
|
-
let is_stable: u8 = if rest.is_empty() { 1 } else { 0 };
|
|
182
|
-
Some((major, minor, patch, is_stable))
|
|
183
|
-
}
|
|
184
|
-
|
|
185
147
|
/// Return a sort key for a severity string such that Critical sorts first.
|
|
186
148
|
///
|
|
187
149
|
/// Lower numeric values sort earlier, so Critical = 0, Unknown = 5.
|
|
@@ -858,6 +820,43 @@ mod tests {
|
|
|
858
820
|
assert_eq!(fixable.get("pkg").map(|s| s.as_str()), Some("2.10.0"));
|
|
859
821
|
}
|
|
860
822
|
|
|
823
|
+
#[test]
|
|
824
|
+
fn test_compute_fix_plan_picks_numerically_highest_non_semver_fix() {
|
|
825
|
+
// Regression test for 4-segment versions (e.g. Ruby or multi-segment tags)
|
|
826
|
+
// where the legacy `semver_parse` dropped the 4th segment, making
|
|
827
|
+
// `1.0.0.10` and `1.0.0.9` compare equal. `max_by` then returned the
|
|
828
|
+
// *last* element, so the answer depended on fix_version ordering.
|
|
829
|
+
//
|
|
830
|
+
// The correct "minimum safe fix" must order by full numeric value.
|
|
831
|
+
let mut audit = AuditResult::default();
|
|
832
|
+
audit.vulnerable.push(PackageAuditResult {
|
|
833
|
+
package: Package {
|
|
834
|
+
name: "pkg".to_string(),
|
|
835
|
+
version: "1.0.0.1".to_string(),
|
|
836
|
+
ecosystem: Ecosystem::RubyGems,
|
|
837
|
+
},
|
|
838
|
+
vulnerabilities: vec![
|
|
839
|
+
Vulnerability {
|
|
840
|
+
id: "CVE-A".to_string(),
|
|
841
|
+
summary: None,
|
|
842
|
+
severity: None,
|
|
843
|
+
url: None,
|
|
844
|
+
fixed_version: Some("1.0.0.10".to_string()),
|
|
845
|
+
},
|
|
846
|
+
Vulnerability {
|
|
847
|
+
id: "CVE-B".to_string(),
|
|
848
|
+
summary: None,
|
|
849
|
+
severity: None,
|
|
850
|
+
url: None,
|
|
851
|
+
fixed_version: Some("1.0.0.9".to_string()),
|
|
852
|
+
},
|
|
853
|
+
],
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
let (fixable, _) = compute_fix_plan(&audit);
|
|
857
|
+
assert_eq!(fixable.get("pkg").map(String::as_str), Some("1.0.0.10"));
|
|
858
|
+
}
|
|
859
|
+
|
|
861
860
|
// ─── check_packages_cached unit tests ────────────────────────────────────
|
|
862
861
|
|
|
863
862
|
fn sample_package(name: &str) -> Package {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
use crate::registry::Registry;
|
|
1
|
+
use crate::registry::{Registry, VersionMeta};
|
|
2
2
|
use anyhow::Result;
|
|
3
3
|
use async_trait::async_trait;
|
|
4
4
|
use directories::ProjectDirs;
|
|
@@ -35,6 +35,21 @@ pub struct Cache {
|
|
|
35
35
|
pub struct CacheEntry {
|
|
36
36
|
pub version: String,
|
|
37
37
|
pub fetched_at: u64, // Unix timestamp
|
|
38
|
+
/// Full per-version metadata fetched via `list_versions`, when available.
|
|
39
|
+
/// Older cache files predate this field and deserialize with `None`.
|
|
40
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
41
|
+
pub versions: Option<Vec<CachedVersionMeta>>,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Cache-friendly mirror of [`crate::registry::VersionMeta`]. `published_at`
|
|
45
|
+
/// is stored as a Unix timestamp (seconds) so the cache file stays stable
|
|
46
|
+
/// across `chrono` serde format changes.
|
|
47
|
+
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
48
|
+
pub struct CachedVersionMeta {
|
|
49
|
+
pub version: String,
|
|
50
|
+
pub published_at: Option<i64>,
|
|
51
|
+
pub yanked: bool,
|
|
52
|
+
pub prerelease: bool,
|
|
38
53
|
}
|
|
39
54
|
|
|
40
55
|
impl Cache {
|
|
@@ -121,6 +136,7 @@ impl Cache {
|
|
|
121
136
|
CacheEntry {
|
|
122
137
|
version,
|
|
123
138
|
fetched_at,
|
|
139
|
+
versions: None,
|
|
124
140
|
},
|
|
125
141
|
);
|
|
126
142
|
}
|
|
@@ -255,6 +271,10 @@ impl<R: Registry> Registry for CachedRegistry<R> {
|
|
|
255
271
|
Ok(version)
|
|
256
272
|
}
|
|
257
273
|
|
|
274
|
+
async fn list_versions(&self, package: &str) -> Result<Vec<VersionMeta>> {
|
|
275
|
+
self.inner.list_versions(package).await
|
|
276
|
+
}
|
|
277
|
+
|
|
258
278
|
fn name(&self) -> &'static str {
|
|
259
279
|
self.inner.name()
|
|
260
280
|
}
|
|
@@ -320,6 +340,7 @@ mod tests {
|
|
|
320
340
|
CacheEntry {
|
|
321
341
|
version: "0.1.0".to_string(),
|
|
322
342
|
fetched_at: expired_time,
|
|
343
|
+
versions: None,
|
|
323
344
|
},
|
|
324
345
|
);
|
|
325
346
|
|
|
@@ -346,6 +367,7 @@ mod tests {
|
|
|
346
367
|
CacheEntry {
|
|
347
368
|
version: "0.1.0".to_string(),
|
|
348
369
|
fetched_at: expired_time,
|
|
370
|
+
versions: None,
|
|
349
371
|
},
|
|
350
372
|
);
|
|
351
373
|
|
|
@@ -405,6 +427,66 @@ mod tests {
|
|
|
405
427
|
assert_eq!(restored.get("npm", "lodash"), Some("4.17.21".to_string()));
|
|
406
428
|
}
|
|
407
429
|
|
|
430
|
+
#[test]
|
|
431
|
+
fn test_cache_entry_deserialises_without_versions_field() {
|
|
432
|
+
// Older cache files predate the `versions` field and must still
|
|
433
|
+
// deserialise so upgrades do not invalidate on-disk caches.
|
|
434
|
+
let json = r#"{"version":"1.2.3","fetched_at":1700000000}"#;
|
|
435
|
+
let entry: CacheEntry = serde_json::from_str(json).unwrap();
|
|
436
|
+
assert_eq!(entry.version, "1.2.3");
|
|
437
|
+
assert_eq!(entry.fetched_at, 1_700_000_000);
|
|
438
|
+
assert!(entry.versions.is_none(), "missing field defaults to None");
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
#[test]
|
|
442
|
+
fn test_cache_roundtrip_with_versions() {
|
|
443
|
+
let entry = CacheEntry {
|
|
444
|
+
version: "2.0.0".to_string(),
|
|
445
|
+
fetched_at: 1_700_000_000,
|
|
446
|
+
versions: Some(vec![
|
|
447
|
+
CachedVersionMeta {
|
|
448
|
+
version: "2.0.0".to_string(),
|
|
449
|
+
published_at: Some(1_700_000_000),
|
|
450
|
+
yanked: false,
|
|
451
|
+
prerelease: false,
|
|
452
|
+
},
|
|
453
|
+
CachedVersionMeta {
|
|
454
|
+
version: "1.9.0".to_string(),
|
|
455
|
+
published_at: None,
|
|
456
|
+
yanked: true,
|
|
457
|
+
prerelease: false,
|
|
458
|
+
},
|
|
459
|
+
]),
|
|
460
|
+
};
|
|
461
|
+
let json = serde_json::to_string(&entry).unwrap();
|
|
462
|
+
let back: CacheEntry = serde_json::from_str(&json).unwrap();
|
|
463
|
+
assert_eq!(back.version, entry.version);
|
|
464
|
+
let metas = back.versions.expect("versions must round-trip");
|
|
465
|
+
assert_eq!(metas.len(), 2);
|
|
466
|
+
assert!(metas.iter().any(|m| m.yanked));
|
|
467
|
+
assert!(
|
|
468
|
+
metas
|
|
469
|
+
.iter()
|
|
470
|
+
.any(|m| m.version == "2.0.0" && m.published_at == Some(1_700_000_000))
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
#[test]
|
|
475
|
+
fn test_cache_entry_skips_serializing_none_versions() {
|
|
476
|
+
// Ensure the new field is omitted from JSON when absent, keeping
|
|
477
|
+
// file size small and preserving compatibility with older readers.
|
|
478
|
+
let entry = CacheEntry {
|
|
479
|
+
version: "1.0.0".to_string(),
|
|
480
|
+
fetched_at: 1_700_000_000,
|
|
481
|
+
versions: None,
|
|
482
|
+
};
|
|
483
|
+
let json = serde_json::to_string(&entry).unwrap();
|
|
484
|
+
assert!(
|
|
485
|
+
!json.contains("versions"),
|
|
486
|
+
"versions field must be omitted when None, got: {json}"
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
408
490
|
#[test]
|
|
409
491
|
fn test_cache_file_operations() {
|
|
410
492
|
use tempfile::tempdir;
|