diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a332aa..7acd94e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - SHA-256 checksums are now provided for all artifact downloads (#101) +- Added self updater (#102) - Use `clap-markdown` fork that enables formatting by display name (#103) ## [0.2.1] diff --git a/Cargo.lock b/Cargo.lock index c3a82ce..30fd1d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,30 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + [[package]] name = "aligned" version = "0.4.1" @@ -17,6 +35,21 @@ dependencies = [ "as-slice", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.3.2" @@ -72,6 +105,12 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + [[package]] name = "as-slice" version = "0.2.1" @@ -116,11 +155,15 @@ dependencies = [ "humantime-serde", "include_dir", "indicatif", + "itertools", + "octocrab", "once_cell", "open", "remove_dir_all", "reqwest", "rstest", + "self-replace", + "semver_rs", "serde", "serde_yaml", "sha2", @@ -141,7 +184,7 @@ checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" dependencies = [ "async-trait", "axum-core", - "bitflags", + "bitflags 1.3.2", "bytes", "futures-util", "http", @@ -182,6 +225,27 @@ dependencies = [ "tower-service", ] +[[package]] +name = "backtrace" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.6.2", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.1" @@ -194,6 +258,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" + [[package]] name = "block-buffer" version = "0.10.4" @@ -227,6 +297,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "winapi", +] + [[package]] name = "clap" version = "4.3.0" @@ -254,7 +337,7 @@ checksum = "4f423e341edefb78c9caba2d9c7f7687d0e72e89df3ce3394554754393ac3990" dependencies = [ "anstream", "anstyle", - "bitflags", + "bitflags 1.3.2", "clap_lex", "strsim", ] @@ -296,6 +379,22 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + [[package]] name = "cpufeatures" version = "0.2.8" @@ -376,6 +475,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + [[package]] name = "encode_unicode" version = "0.3.6" @@ -446,7 +557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.7.1", ] [[package]] @@ -594,6 +705,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" + [[package]] name = "h2" version = "0.3.19" @@ -674,6 +791,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -734,11 +857,48 @@ checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" dependencies = [ "http", "hyper", + "log", "rustls", + "rustls-native-certs", "tokio", "tokio-rustls", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.3.0" @@ -858,6 +1018,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.6" @@ -873,6 +1042,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +dependencies = [ + "base64 0.21.1", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -937,6 +1120,15 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -964,7 +1156,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "static_assertions", @@ -989,6 +1181,36 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.15.0" @@ -1005,6 +1227,52 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "object" +version = "0.30.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" +dependencies = [ + "memchr", +] + +[[package]] +name = "octocrab" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bc095e456c43e3afe5a53cdcf11aae1965663b941f7a5efb49b6ef53ce8529" +dependencies = [ + "arc-swap", + "async-trait", + "base64 0.21.1", + "bytes", + "cfg-if", + "chrono", + "either", + "futures", + "futures-util", + "http", + "http-body", + "hyper", + "hyper-rustls", + "hyper-timeout", + "jsonwebtoken", + "once_cell", + "percent-encoding", + "pin-project", + "secrecy", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "snafu", + "tokio", + "tower", + "tower-http", + "tracing", + "url", +] + [[package]] name = "once_cell" version = "1.17.1" @@ -1022,6 +1290,12 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + [[package]] name = "option-ext" version = "0.2.0" @@ -1063,6 +1337,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + [[package]] name = "percent-encoding" version = "2.2.0" @@ -1131,7 +1414,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1140,7 +1423,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1160,6 +1443,8 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1a59b5d8e97dee33696bf13c5ba8ab85341c002922fba050069326b9c498974" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax 0.7.2", ] @@ -1206,7 +1491,7 @@ version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64", + "base64 0.21.1", "bytes", "encoding_rs", "futures-core", @@ -1282,6 +1567,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc_version" version = "0.4.0" @@ -1297,7 +1588,7 @@ version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", @@ -1317,13 +1608,25 @@ dependencies = [ "sct", ] +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64", + "base64 0.21.1", ] [[package]] @@ -1348,6 +1651,15 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -1364,12 +1676,67 @@ dependencies = [ "untrusted", ] +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "self-replace" +version = "1.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0e7c919783db74b5995f13506069227e4721d388bea4a8ac3055acac864ac16" +dependencies = [ + "fastrand", + "tempfile", + "windows-sys 0.48.0", +] + [[package]] name = "semver" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +[[package]] +name = "semver_rs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9594e1aab972e5b5ecbb330754bef51c7ba0dc12644b6bae9e09a4e19d472586" +dependencies = [ + "lazy_static", + "regex", + "thiserror", + "unicase", +] + [[package]] name = "serde" version = "1.0.163" @@ -1479,6 +1846,18 @@ dependencies = [ "libc", ] +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "slab" version = "0.4.8" @@ -1494,6 +1873,29 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "snafu" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "backtrace", + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "socket2" version = "0.4.9" @@ -1610,6 +2012,33 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" +dependencies = [ + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -1644,6 +2073,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.1.0" @@ -1724,6 +2163,26 @@ dependencies = [ "pin-project", "pin-project-lite", "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ac8060a61f8758a61562f6fb53ba3cbe1ca906f001df2e53cccddcdbee91e7c" +dependencies = [ + "bitflags 2.3.3", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", "tower-layer", "tower-service", "tracing", @@ -1829,6 +2288,15 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -2044,6 +2512,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + [[package]] name = "windows-sys" version = "0.45.0" diff --git a/Cargo.toml b/Cargo.toml index 3e8ba68..9fbb034 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,10 +27,14 @@ humantime = "2.1.0" humantime-serde = "1.1.1" include_dir = { version = "0.7.3" } indicatif = "0.17.5" +itertools = "0.10.5" +octocrab = "0.25.0" once_cell = { version = "1.17.1" } open = "4.1.0" remove_dir_all = { version = "0.8.2" } reqwest = { version = "0.11.18", default-features = false, features = ["json", "rustls-tls", "stream"] } +self-replace = "1.3.5" +semver_rs = "0.2.0" serde = { version = "1.0.163", features = ["derive"] } serde_yaml = { version = "0.9.21" } sha2 = "0.10.6" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..845194c --- /dev/null +++ b/build.rs @@ -0,0 +1,6 @@ +use std::env::var; + +fn main() { + // https://stackoverflow.com/a/51311222/11494565 + println!("cargo:rustc-env=TARGET={}", var("TARGET").unwrap()); +} diff --git a/files/autometrics-shared b/files/autometrics-shared index f6f084a..79d53e9 160000 --- a/files/autometrics-shared +++ b/files/autometrics-shared @@ -1 +1 @@ -Subproject commit f6f084ad1391ed1ad9de5c484cfa836a8311fcf6 +Subproject commit 79d53e9a1932633b25f77f704a23244330b7ac5a diff --git a/src/bin/am/commands.rs b/src/bin/am/commands.rs index 65dc457..303f4f3 100644 --- a/src/bin/am/commands.rs +++ b/src/bin/am/commands.rs @@ -8,6 +8,7 @@ use tracing::info; mod explore; pub mod start; pub mod system; +pub mod update; #[derive(Parser)] #[command(author, version, about, long_about = None, display_name = "am")] @@ -47,6 +48,9 @@ pub enum SubCommands { /// discuss various things related to Autometrics and the `am` CLI Discord, + /// Run the updater + Update(update::Arguments), + #[clap(hide = true)] MarkdownHelp, } @@ -65,6 +69,7 @@ pub async fn handle_command(app: Application, config: AmConfig, mp: MultiProgres Ok(()) } + SubCommands::Update(args) => update::handle_command(args, mp).await, SubCommands::MarkdownHelp => { clap_markdown::print_help_markdown::(); Ok(()) diff --git a/src/bin/am/commands/update.rs b/src/bin/am/commands/update.rs new file mode 100644 index 0000000..783987d --- /dev/null +++ b/src/bin/am/commands/update.rs @@ -0,0 +1,213 @@ +use crate::commands::start::CLIENT; +use crate::downloader::download_github_release; +use anyhow::{anyhow, bail, Context, Result}; +use clap::Parser; +use directories::ProjectDirs; +use indicatif::MultiProgress; +use itertools::Itertools; +use octocrab::models::repos::{Asset, Release}; +use self_replace::self_replace; +use semver_rs::Version; +use std::fs::{File, OpenOptions}; +use std::time::{Duration, SystemTime}; +use std::{env, fs}; +use tracing::{debug, error, info, trace, warn}; + +const AUTOMETRICS_GITHUB_ORG: &str = "autometrics-dev"; +const AUTOMETRICS_AM_REPO: &str = "am"; + +#[derive(Parser)] +pub struct Arguments { + /// Whenever to ignore Homebrew checks and forcefully update + #[clap(long, short)] + force: bool, +} + +pub(crate) async fn handle_command(args: Arguments, mp: MultiProgress) -> Result<()> { + let release = latest_release().await?; + + if !update_needed(&release)? { + info!("Already on the latest version"); + return Ok(()); + } + + let new_tag = release.tag_name; + + if is_homebrew() && !args.force { + info!("A new version of `am` is available: {new_tag}"); + info!("You can update by running `brew upgrade am` (or use `am update --force`)"); + return Ok(()); + } + + info!("Updating to {new_tag}"); + + let asset_needed = asset_needed()?; + + let assets: Option<(&Asset, &Asset)> = release + .assets + .iter() + .filter(|a| a.name.starts_with(asset_needed)) + .sorted_by(|a, b| a.name.cmp(&b.name)) + .collect_tuple(); + + if assets.is_none() { + error!("Could not find release for your target platform."); + return Ok(()); + } + + // .unwrap is safe because we checked above if its none + // because of .sorted_by above (which sorts by name), the .sha256 file will be the second one *guaranteed* + let (binary_asset, sha256_asset) = assets.unwrap(); + + let executable = env::current_exe()?; + let temp_exe = executable + .parent() + .ok_or_else(|| anyhow!("Parent directory not found"))? + .join("am_update.part"); + + let file = File::create(&temp_exe)?; + + let calculated_checksum = download_github_release( + &file, + AUTOMETRICS_GITHUB_ORG, + AUTOMETRICS_AM_REPO, + new_tag.strip_prefix('v').unwrap_or(&new_tag), + &binary_asset.name, + &mp, + ) + .await?; + + let checksum_line = CLIENT + .get(sha256_asset.browser_download_url.clone()) + .send() + .await? + .text() + .await?; + + let remote_checksum = checksum_line + .split_once(' ') + .map(|(checksum, _)| checksum) + .unwrap_or(&checksum_line); + + if calculated_checksum != remote_checksum { + debug!( + %remote_checksum, + %calculated_checksum, "Calculated sha256 hash does not match the remote sha256 hash" + ); + + fs::remove_file(&temp_exe).context("Failed to delete file that failed checksum match")?; + drop(temp_exe); + + bail!("Calculated sha256 hash does not match the remote sha256 hash"); + } + + self_replace(&temp_exe).context("failed to replace self")?; + fs::remove_file(&temp_exe).context("failed to delete updater file")?; + + info!("Successfully updated to {new_tag}"); + Ok(()) +} + +pub(crate) async fn update_check() { + let Some(project_dirs) = ProjectDirs::from("", "autometrics", "am") else { + warn!("failed to run update checker: home directory does not exist"); + return; + }; + + let config_dir = project_dirs.config_dir(); + + if let Err(err) = fs::create_dir_all(config_dir) { + error!(?err, "failed to create config directory"); + return; + } + + let check_file = config_dir.join("version_check"); + + let should_check = match fs::metadata(&check_file) { + Ok(metadata) => { + if let Ok(date) = metadata.modified() { + date < (SystemTime::now() - Duration::from_secs(60 * 60 * 24)) + } else { + false + } + } + Err(err) => { + // This will most likely be caused by the file not existing, so we + // will just trace it and go ahead with the version check. + trace!(%err, "checking the update file check resulted in a error"); + true + } + }; + + // We've checked the version recently, so just return early indicating that + // no update should be done. + if !should_check { + return; + } + + let Ok(release) = latest_release().await else { return }; + let Ok(needs_update) = update_needed(&release) else { return }; + + if let Err(err) = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(&check_file) + { + trace!(?err, "failed to create `version_check` file"); + } + + if !needs_update { + return; + } + + info!("New update is available: {}", release.tag_name); +} + +fn update_needed(release: &Release) -> Result { + let current_tag = Version::new(env!("CARGO_PKG_VERSION")).parse()?; + let new_tag = Version::new( + release + .tag_name + .strip_prefix('v') + .unwrap_or(&release.tag_name), + ) + .parse()?; + + Ok(new_tag > current_tag) +} + +async fn latest_release() -> Result { + octocrab::instance() + .repos(AUTOMETRICS_GITHUB_ORG, AUTOMETRICS_AM_REPO) + .releases() + .get_latest() + .await + .context("failed to check latest release from GitHub") +} + +fn asset_needed() -> Result<&'static str> { + Ok(match env!("TARGET") { + "x86_64-unknown-linux-gnu" => "am-linux-x86_64", + "aarch64-unknown-linux-gnu" => "am-linux-aarch64", + "x86_64-apple-darwin" => "am-macos-aarch64", + "aarch64-apple-darwin" => "am-macos-x86_64", + target => bail!("unsupported target: {target}"), + }) +} + +#[inline] +fn is_homebrew() -> bool { + #[cfg(target_os = "linux")] + return env::current_exe() + .map(|path| path.starts_with("/home/linuxbrew/.linuxbrew")) + .unwrap_or_default(); + + #[cfg(target_os = "macos")] + return env::current_exe() + .map(|path| path.starts_with("/usr/local") || path.starts_with("/opt/homebrew")) + .unwrap_or_default(); + + #[cfg(all(not(target_os = "linux"), not(target_os = "macos")))] + return false; +} diff --git a/src/bin/am/main.rs b/src/bin/am/main.rs index c33d36a..1ea1e3c 100644 --- a/src/bin/am/main.rs +++ b/src/bin/am/main.rs @@ -1,11 +1,14 @@ +use crate::commands::update; use anyhow::{bail, Context, Result}; use autometrics_am::config::AmConfig; use clap::Parser; use commands::{handle_command, Application}; use interactive::IndicatifWriter; use std::path::PathBuf; +use std::time::Duration; +use tokio::time::timeout; use tracing::level_filters::LevelFilter; -use tracing::{debug, error}; +use tracing::{debug, error, warn}; use tracing_subscriber::fmt::format; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; @@ -22,11 +25,18 @@ async fn main() { let app = Application::parse(); let (writer, multi_progress) = IndicatifWriter::new(); + if let Err(err) = init_logging(&app, writer) { eprintln!("Unable to initialize logging: {:#}", err); std::process::exit(1); } + let task = if std::env::var_os("AM_NO_UPDATE").is_none() { + tokio::task::spawn(update::update_check()) + } else { + tokio::task::spawn(async { /* intentionally left empty */ }) + }; + let config = match load_config(app.config_file.clone()).await { Ok(config) => config, Err(err) => { @@ -36,6 +46,11 @@ async fn main() { }; let result = handle_command(app, config, multi_progress).await; + + if let Err(err) = timeout(Duration::from_secs(1), task).await { + warn!(?err, "background update check timed out"); + } + match result { Ok(_) => debug!("Command completed successfully"), Err(err) => {