upd-cli 0.1.1__tar.gz → 0.1.2__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.2}/CHANGELOG.md +29 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/Cargo.lock +115 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/Cargo.toml +2 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/PKG-INFO +46 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/README.md +45 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/cache.rs +83 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/cli.rs +37 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/config.rs +324 -2
- upd_cli-0.1.2/src/cooldown.rs +965 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/lib.rs +2 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/main.rs +411 -10
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/output.rs +142 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/registry/crates_io.rs +92 -6
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/registry/github_releases.rs +104 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/registry/go_proxy.rs +119 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/registry/mock.rs +61 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/registry/mod.rs +47 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/registry/npm.rs +150 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/registry/nuget.rs +14 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/registry/pypi.rs +109 -4
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/registry/rubygems.rs +73 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/updater/cargo_toml.rs +65 -6
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/updater/csproj.rs +55 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/updater/gemfile.rs +61 -3
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/updater/github_actions.rs +53 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/updater/go_mod.rs +58 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/updater/mise.rs +55 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/updater/mod.rs +401 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/updater/package_json.rs +53 -3
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/updater/pre_commit.rs +53 -1
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/updater/pyproject.rs +120 -10
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/updater/requirements.rs +56 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/updater/terraform.rs +1 -0
- upd_cli-0.1.2/tests/cooldown_e2e.rs +77 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/.mise.toml +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/.pre-commit-config.yaml +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/.pre-commit-hooks.yaml +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/.rumdl.toml +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/LICENSE +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/Makefile +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/assets/logo-wide.svg +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/assets/logo.svg +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/pyproject.toml +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/python/upd_cli/__init__.py +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/python/upd_cli/__main__.py +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/python/upd_cli/py.typed +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/rust-toolchain.toml +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/align.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/audit/cache.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/audit/cvss.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/audit/mod.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/interactive.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/lockfile.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/registry/terraform.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/registry/utils.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/version/mod.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/version/pep440.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/version/semver_util.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/src/version/tag.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/tests/audit_offline.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/tests/audit_sarif.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/tests/audit_severity.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/tests/bump_filter.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/tests/exit_codes.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/tests/fix_audit.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/tests/format_json.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/tests/help_text.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/tests/interactive_tty.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/tests/invalid_positional.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/tests/no_args_scope.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/tests/output_streams.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/tests/package_filter.rs +0 -0
- {upd_cli-0.1.1 → upd_cli-0.1.2}/vership.toml +0 -0
|
@@ -11,6 +11,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
|
|
15
|
+
## [0.1.2](https://github.com/rvben/upd/compare/v0.1.1...v0.1.2) - 2026-04-24
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **cache**: add optional versions field to CacheEntry for future list_versions caching ([1beb34d](https://github.com/rvben/upd/commit/1beb34dc030f160e3748dff9a63e71bfa1772043))
|
|
20
|
+
- **output**: report held-back and skipped-by-cooldown packages ([3d1a2ce](https://github.com/rvben/upd/commit/3d1a2cef2ae31c59a87b417b074e0d672b7256d2))
|
|
21
|
+
- **updater**: propagate cooldown policy to remaining updaters ([8e80f25](https://github.com/rvben/upd/commit/8e80f252339f022d90f8120694e529c68c3bcf90))
|
|
22
|
+
- **updater**: apply cooldown policy in requirements updater ([5d6cfd3](https://github.com/rvben/upd/commit/5d6cfd32bfe87df7af86453476a93e2945f009ff))
|
|
23
|
+
- **registry**: implement list_versions for GitHub releases ([5f6472b](https://github.com/rvben/upd/commit/5f6472b5d7c4394d59fbabfd4bd9a1d9b736a67a))
|
|
24
|
+
- **registry**: implement list_versions for RubyGems ([1a1dda3](https://github.com/rvben/upd/commit/1a1dda31d73e0f25d8a73e6438aed7c4daadc007))
|
|
25
|
+
- **registry**: implement list_versions for Go module proxy ([196fef6](https://github.com/rvben/upd/commit/196fef634b0ae3c53096222e8bbe3161d8b67a33))
|
|
26
|
+
- **registry**: implement list_versions for crates.io ([8869dec](https://github.com/rvben/upd/commit/8869dec1a69148b5b3db44cc3393a05aaa2b01fb))
|
|
27
|
+
- **registry**: implement list_versions for npm ([b23cd78](https://github.com/rvben/upd/commit/b23cd787e748dfc5404a8fd51981c67f858e1d5a))
|
|
28
|
+
- **registry**: implement list_versions for PyPI ([9aa342c](https://github.com/rvben/upd/commit/9aa342c5c11ae504024aed5aa2d8930c66b4a6df))
|
|
29
|
+
- **cli**: add --min-age flag for cooldown override ([b5bfb30](https://github.com/rvben/upd/commit/b5bfb304c39f6b8099aef3a066e2ef4ed17f606f))
|
|
30
|
+
- **config**: show cooldown policy in --show-config ([9486257](https://github.com/rvben/upd/commit/9486257b22456e55e9af23709089a574cce262be))
|
|
31
|
+
- **config**: add [cooldown] table with default and per-ecosystem overrides ([a9ff8e3](https://github.com/rvben/upd/commit/a9ff8e31050485056a9bb6e03f4d313df1262998))
|
|
32
|
+
- **cooldown**: implement select() selection algorithm ([8b588bb](https://github.com/rvben/upd/commit/8b588bb1b42d84d696a7a17d8e97141a223351a1))
|
|
33
|
+
- **cooldown**: add CooldownPolicy with precedence resolution ([ddba284](https://github.com/rvben/upd/commit/ddba284329dad87bf84cf566ecb487ef665408a1))
|
|
34
|
+
- **cooldown**: add parse_duration for release-age config ([a7e67e0](https://github.com/rvben/upd/commit/a7e67e035764e4d905ace1dc4092f41c27510a5c))
|
|
35
|
+
- **registry**: re-export VersionMeta from crate root ([b2cdd60](https://github.com/rvben/upd/commit/b2cdd6037c09110881f53db4992542e77596f6c3))
|
|
36
|
+
- **registry**: add VersionMeta and list_versions trait method ([09ddbf9](https://github.com/rvben/upd/commit/09ddbf9c2b8f0546feafeb642f27547bed1882da))
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- **cooldown**: harden selection against real-world constraints and per-file policy ([a284ea4](https://github.com/rvben/upd/commit/a284ea497dcf3abf65fda2c7e7f6c0c03c3dd8e2))
|
|
41
|
+
- **updater**: pass Poetry constraint to cooldown selection ([a0383e9](https://github.com/rvben/upd/commit/a0383e9167b2ef05cc4583aa23c1035d32268750))
|
|
42
|
+
|
|
14
43
|
## [0.1.1](https://github.com/rvben/upd/compare/v0.1.0...v0.1.1) - 2026-04-22
|
|
15
44
|
|
|
16
45
|
### 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.2"
|
|
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.2"
|
|
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.2
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -469,6 +469,51 @@ upd --verbose
|
|
|
469
469
|
# pyproject.toml:13: Skipped internal-utils 1.0.0 (ignored)
|
|
470
470
|
```
|
|
471
471
|
|
|
472
|
+
## Cooldown (minimum release age)
|
|
473
|
+
|
|
474
|
+
Hold back updates to versions that have been public for less than N days.
|
|
475
|
+
Reduces exposure to supply-chain attacks that rely on freshly published
|
|
476
|
+
malicious versions being installed before detection. Modelled after
|
|
477
|
+
Renovate's `minimumReleaseAge` / Dependabot's `cooldown`.
|
|
478
|
+
|
|
479
|
+
Enable in `.updrc.toml`:
|
|
480
|
+
|
|
481
|
+
```toml
|
|
482
|
+
[cooldown]
|
|
483
|
+
default = "7d" # applies to every ecosystem unless overridden
|
|
484
|
+
|
|
485
|
+
[cooldown.ecosystem]
|
|
486
|
+
npm = "14d" # stricter for npm
|
|
487
|
+
pypi = "14d"
|
|
488
|
+
"crates.io" = "3d"
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
Duration syntax: `<integer><unit>` where unit is `s`, `m`, `h`, `d`, `w`.
|
|
492
|
+
A bare `0` disables cooldown.
|
|
493
|
+
|
|
494
|
+
Override from the CLI for one-off runs:
|
|
495
|
+
|
|
496
|
+
```text
|
|
497
|
+
upd --min-age 14d # use 14 days regardless of config
|
|
498
|
+
upd --min-age 0 # disable cooldown entirely for this run
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
**How it works:** when the latest version is still inside the cooldown
|
|
502
|
+
window, `upd` updates to the newest version that *is* old enough. If nothing
|
|
503
|
+
newer is old enough yet, the package is held back. Output marks these
|
|
504
|
+
packages explicitly:
|
|
505
|
+
|
|
506
|
+
```text
|
|
507
|
+
requirements.txt: Updated requests 2.28.0 → 2.31.0
|
|
508
|
+
package.json: Held back lodash 4.17.20 → 4.17.21 (4.17.22 released 2d ago, cooldown 7d)
|
|
509
|
+
package.json: Skipped express (only newer version 4.19.0 released 1d ago, cooldown 7d)
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
**Supported ecosystems:** PyPI, npm, crates.io, Go modules, RubyGems,
|
|
513
|
+
GitHub releases (covers GitHub Actions, pre-commit, Mise). NuGet and
|
|
514
|
+
Terraform Registry do not expose per-version publish dates we can
|
|
515
|
+
consume today; cooldown is reported as unavailable for those files.
|
|
516
|
+
|
|
472
517
|
## Caching
|
|
473
518
|
|
|
474
519
|
Version lookups are cached for 24 hours in:
|
|
@@ -446,6 +446,51 @@ upd --verbose
|
|
|
446
446
|
# pyproject.toml:13: Skipped internal-utils 1.0.0 (ignored)
|
|
447
447
|
```
|
|
448
448
|
|
|
449
|
+
## Cooldown (minimum release age)
|
|
450
|
+
|
|
451
|
+
Hold back updates to versions that have been public for less than N days.
|
|
452
|
+
Reduces exposure to supply-chain attacks that rely on freshly published
|
|
453
|
+
malicious versions being installed before detection. Modelled after
|
|
454
|
+
Renovate's `minimumReleaseAge` / Dependabot's `cooldown`.
|
|
455
|
+
|
|
456
|
+
Enable in `.updrc.toml`:
|
|
457
|
+
|
|
458
|
+
```toml
|
|
459
|
+
[cooldown]
|
|
460
|
+
default = "7d" # applies to every ecosystem unless overridden
|
|
461
|
+
|
|
462
|
+
[cooldown.ecosystem]
|
|
463
|
+
npm = "14d" # stricter for npm
|
|
464
|
+
pypi = "14d"
|
|
465
|
+
"crates.io" = "3d"
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
Duration syntax: `<integer><unit>` where unit is `s`, `m`, `h`, `d`, `w`.
|
|
469
|
+
A bare `0` disables cooldown.
|
|
470
|
+
|
|
471
|
+
Override from the CLI for one-off runs:
|
|
472
|
+
|
|
473
|
+
```text
|
|
474
|
+
upd --min-age 14d # use 14 days regardless of config
|
|
475
|
+
upd --min-age 0 # disable cooldown entirely for this run
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
**How it works:** when the latest version is still inside the cooldown
|
|
479
|
+
window, `upd` updates to the newest version that *is* old enough. If nothing
|
|
480
|
+
newer is old enough yet, the package is held back. Output marks these
|
|
481
|
+
packages explicitly:
|
|
482
|
+
|
|
483
|
+
```text
|
|
484
|
+
requirements.txt: Updated requests 2.28.0 → 2.31.0
|
|
485
|
+
package.json: Held back lodash 4.17.20 → 4.17.21 (4.17.22 released 2d ago, cooldown 7d)
|
|
486
|
+
package.json: Skipped express (only newer version 4.19.0 released 1d ago, cooldown 7d)
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
**Supported ecosystems:** PyPI, npm, crates.io, Go modules, RubyGems,
|
|
490
|
+
GitHub releases (covers GitHub Actions, pre-commit, Mise). NuGet and
|
|
491
|
+
Terraform Registry do not expose per-version publish dates we can
|
|
492
|
+
consume today; cooldown is reported as unavailable for those files.
|
|
493
|
+
|
|
449
494
|
## Caching
|
|
450
495
|
|
|
451
496
|
Version lookups are cached for 24 hours in:
|
|
@@ -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;
|
|
@@ -161,6 +161,17 @@ pub struct Cli {
|
|
|
161
161
|
#[arg(long, global = true)]
|
|
162
162
|
pub show_config: bool,
|
|
163
163
|
|
|
164
|
+
/// Minimum release age before a version is eligible for update.
|
|
165
|
+
///
|
|
166
|
+
/// Overrides the `[cooldown]` config for this run. Setting `--min-age 0`
|
|
167
|
+
/// disables cooldown entirely. Accepts durations like `72h`, `7d`, `2w`.
|
|
168
|
+
///
|
|
169
|
+
/// Example: `upd --min-age 7d` only updates to versions published at least
|
|
170
|
+
/// 7 days ago. Protects against supply-chain attacks that rely on freshly
|
|
171
|
+
/// published malicious packages being installed before detection.
|
|
172
|
+
#[arg(long, global = true, value_name = "DURATION")]
|
|
173
|
+
pub min_age: Option<String>,
|
|
174
|
+
|
|
164
175
|
/// Update only the named package(s), skipping all others.
|
|
165
176
|
///
|
|
166
177
|
/// Comma-separated or repeatable. Exact case-sensitive match.
|
|
@@ -275,6 +286,7 @@ mod tests {
|
|
|
275
286
|
assert!(!cli.apply);
|
|
276
287
|
assert!(cli.paths.is_empty());
|
|
277
288
|
assert!(cli.command.is_none());
|
|
289
|
+
assert!(cli.min_age.is_none());
|
|
278
290
|
}
|
|
279
291
|
|
|
280
292
|
#[test]
|
|
@@ -804,4 +816,29 @@ mod tests {
|
|
|
804
816
|
"align --help should describe monorepo use-case; got:\n{align_help}"
|
|
805
817
|
);
|
|
806
818
|
}
|
|
819
|
+
|
|
820
|
+
#[test]
|
|
821
|
+
fn test_cli_parses_min_age() {
|
|
822
|
+
let cli = Cli::try_parse_from(["upd", "--min-age", "7d"]).unwrap();
|
|
823
|
+
assert_eq!(cli.min_age.as_deref(), Some("7d"));
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
#[test]
|
|
827
|
+
fn test_cli_min_age_zero_for_disable() {
|
|
828
|
+
let cli = Cli::try_parse_from(["upd", "--min-age", "0"]).unwrap();
|
|
829
|
+
assert_eq!(cli.min_age.as_deref(), Some("0"));
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
#[test]
|
|
833
|
+
fn test_cli_min_age_default_none() {
|
|
834
|
+
let cli = Cli::try_parse_from(["upd"]).unwrap();
|
|
835
|
+
assert!(cli.min_age.is_none());
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
#[test]
|
|
839
|
+
fn test_cli_min_age_is_global_across_subcommands() {
|
|
840
|
+
let cli = Cli::try_parse_from(["upd", "update", "--min-age", "14d", "path1"]).unwrap();
|
|
841
|
+
assert_eq!(cli.min_age.as_deref(), Some("14d"));
|
|
842
|
+
assert!(matches!(cli.command, Some(Command::Update { .. })));
|
|
843
|
+
}
|
|
807
844
|
}
|