Compare commits
No commits in common. "d40cbb74fca3d47706c15dd1ffd236d9aa9653e2" and "96c24680974eb4b058d3798c52ebda22f9ba713e" have entirely different histories.
d40cbb74fc
...
96c2468097
22 changed files with 264 additions and 1116 deletions
371
Cargo.lock
generated
371
Cargo.lock
generated
|
|
@ -16,7 +16,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cipher",
|
"cipher",
|
||||||
"cpufeatures 0.2.17",
|
"cpufeatures",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -80,9 +80,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.101"
|
version = "1.0.100"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "assert-json-diff"
|
name = "assert-json-diff"
|
||||||
|
|
@ -211,17 +211,6 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chacha20"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"cpufeatures 0.3.0",
|
|
||||||
"rand_core 0.10.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cipher"
|
name = "cipher"
|
||||||
version = "0.4.4"
|
version = "0.4.4"
|
||||||
|
|
@ -234,9 +223,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.58"
|
version = "4.5.54"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806"
|
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
|
|
@ -244,9 +233,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.58"
|
version = "4.5.54"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2"
|
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
|
|
@ -256,9 +245,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.55"
|
version = "4.5.49"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -268,9 +257,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "1.0.0"
|
version = "0.7.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cmake"
|
name = "cmake"
|
||||||
|
|
@ -332,9 +321,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "constant_time_eq"
|
name = "constant_time_eq"
|
||||||
version = "0.4.2"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
|
|
@ -371,15 +360,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cpufeatures"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc"
|
name = "crc"
|
||||||
version = "3.3.0"
|
version = "3.3.0"
|
||||||
|
|
@ -518,9 +498,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_filter"
|
name = "env_filter"
|
||||||
version = "1.0.0"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f"
|
checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"regex",
|
"regex",
|
||||||
|
|
@ -528,9 +508,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.11.9"
|
version = "0.11.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d"
|
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
|
|
@ -584,12 +564,6 @@ version = "1.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foldhash"
|
|
||||||
version = "0.1.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "form_urlencoded"
|
name = "form_urlencoded"
|
||||||
version = "1.2.2"
|
version = "1.2.2"
|
||||||
|
|
@ -731,27 +705,11 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "getrandom"
|
|
||||||
version = "0.4.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"js-sys",
|
|
||||||
"libc",
|
|
||||||
"r-efi",
|
|
||||||
"rand_core 0.10.0",
|
|
||||||
"wasip2",
|
|
||||||
"wasip3",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "git2"
|
name = "git2"
|
||||||
version = "0.20.4"
|
version = "0.20.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b"
|
checksum = "3e2b37e2f62729cdada11f0e6b3b6fe383c69c29fc619e391223e12856af308c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.10.0",
|
"bitflags 2.10.0",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -762,12 +720,6 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "glob"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
|
|
@ -787,15 +739,6 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.15.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
|
||||||
dependencies = [
|
|
||||||
"foldhash",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.16.1"
|
version = "0.16.1"
|
||||||
|
|
@ -1008,12 +951,6 @@ dependencies = [
|
||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "id-arena"
|
|
||||||
version = "2.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|
@ -1042,9 +979,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown",
|
||||||
"serde",
|
|
||||||
"serde_core",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1173,12 +1108,6 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "leb128fmt"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libbz2-rs-sys"
|
name = "libbz2-rs-sys"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|
@ -1187,9 +1116,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.181"
|
version = "0.2.180"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5"
|
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libgit2-sys"
|
name = "libgit2-sys"
|
||||||
|
|
@ -1334,9 +1263,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mockito"
|
name = "mockito"
|
||||||
version = "1.7.2"
|
version = "1.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "90820618712cab19cfc46b274c6c22546a82affcb3c3bdf0f29e3db8e1bb92c0"
|
checksum = "7e0603425789b4a70fcc4ac4f5a46a566c116ee3e2a6b768dc623f7719c611de"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assert-json-diff",
|
"assert-json-diff",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
@ -1349,7 +1278,7 @@ dependencies = [
|
||||||
"hyper-util",
|
"hyper-util",
|
||||||
"log",
|
"log",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rand 0.9.2",
|
"rand",
|
||||||
"regex",
|
"regex",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
|
|
@ -1359,9 +1288,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
|
|
@ -1411,17 +1340,16 @@ dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
"git2",
|
"git2",
|
||||||
"glob",
|
|
||||||
"indicatif",
|
"indicatif",
|
||||||
"keyring",
|
"keyring",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
"md-5",
|
"md-5",
|
||||||
"mockito",
|
"mockito",
|
||||||
"rand 0.10.0",
|
"once_cell",
|
||||||
|
"rand",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"semver",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha1",
|
"sha1",
|
||||||
|
|
@ -1429,7 +1357,7 @@ dependencies = [
|
||||||
"strsim",
|
"strsim",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"textwrap",
|
"textwrap",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"yansi",
|
"yansi",
|
||||||
|
|
@ -1525,9 +1453,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppmd-rust"
|
name = "ppmd-rust"
|
||||||
version = "1.4.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24"
|
checksum = "d558c559f0450f16f2a27a1f017ef38468c1090c9ce63c8e51366232d53717b4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ppv-lite86"
|
name = "ppv-lite86"
|
||||||
|
|
@ -1538,16 +1466,6 @@ dependencies = [
|
||||||
"zerocopy",
|
"zerocopy",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "prettyplease"
|
|
||||||
version = "0.2.37"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.105"
|
version = "1.0.105"
|
||||||
|
|
@ -1571,7 +1489,7 @@ dependencies = [
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"rustls",
|
"rustls",
|
||||||
"socket2",
|
"socket2",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.17",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"web-time",
|
"web-time",
|
||||||
|
|
@ -1587,13 +1505,13 @@ dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
"lru-slab",
|
"lru-slab",
|
||||||
"rand 0.9.2",
|
"rand",
|
||||||
"ring",
|
"ring",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"slab",
|
"slab",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.17",
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
"tracing",
|
"tracing",
|
||||||
"web-time",
|
"web-time",
|
||||||
|
|
@ -1635,18 +1553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rand_chacha",
|
"rand_chacha",
|
||||||
"rand_core 0.9.3",
|
"rand_core",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8"
|
|
||||||
dependencies = [
|
|
||||||
"chacha20",
|
|
||||||
"getrandom 0.4.1",
|
|
||||||
"rand_core 0.10.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1656,7 +1563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ppv-lite86",
|
"ppv-lite86",
|
||||||
"rand_core 0.9.3",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1668,12 +1575,6 @@ dependencies = [
|
||||||
"getrandom 0.3.4",
|
"getrandom 0.3.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.10.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.18"
|
version = "0.5.18"
|
||||||
|
|
@ -1685,9 +1586,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.12.3"
|
version = "1.12.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
|
@ -1714,9 +1615,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.13.2"
|
version = "0.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
|
checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
@ -1919,12 +1820,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "semver"
|
|
||||||
version = "1.0.27"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
|
|
@ -1987,7 +1882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures 0.2.17",
|
"cpufeatures",
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1998,7 +1893,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures 0.2.17",
|
"cpufeatures",
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2136,12 +2031,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.25.0"
|
version = "3.24.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
|
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom 0.4.1",
|
"getrandom 0.3.4",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
|
|
@ -2169,11 +2064,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.18"
|
version = "2.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl 2.0.18",
|
"thiserror-impl 2.0.17",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2189,9 +2084,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "2.0.18"
|
version = "2.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -2200,23 +2095,22 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.47"
|
version = "0.3.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"js-sys",
|
|
||||||
"num-conv",
|
"num-conv",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde_core",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-core"
|
name = "time-core"
|
||||||
version = "0.1.8"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
|
|
@ -2364,12 +2258,6 @@ version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "typed-path"
|
|
||||||
version = "0.12.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typenum"
|
name = "typenum"
|
||||||
version = "1.19.0"
|
version = "1.19.0"
|
||||||
|
|
@ -2400,12 +2288,6 @@ version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode-xid"
|
|
||||||
version = "0.2.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unit-prefix"
|
name = "unit-prefix"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
|
@ -2485,16 +2367,7 @@ version = "1.0.1+wasi-0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wit-bindgen 0.46.0",
|
"wit-bindgen",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasip3"
|
|
||||||
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
|
||||||
dependencies = [
|
|
||||||
"wit-bindgen 0.51.0",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2555,40 +2428,6 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-encoder"
|
|
||||||
version = "0.244.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
|
||||||
dependencies = [
|
|
||||||
"leb128fmt",
|
|
||||||
"wasmparser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasm-metadata"
|
|
||||||
version = "0.244.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"indexmap",
|
|
||||||
"wasm-encoder",
|
|
||||||
"wasmparser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wasmparser"
|
|
||||||
version = "0.244.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 2.10.0",
|
|
||||||
"hashbrown 0.15.5",
|
|
||||||
"indexmap",
|
|
||||||
"semver",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.83"
|
version = "0.3.83"
|
||||||
|
|
@ -2921,94 +2760,6 @@ version = "0.46.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-bindgen"
|
|
||||||
version = "0.51.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
|
||||||
dependencies = [
|
|
||||||
"wit-bindgen-rust-macro",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-bindgen-core"
|
|
||||||
version = "0.51.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"heck",
|
|
||||||
"wit-parser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-bindgen-rust"
|
|
||||||
version = "0.51.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"heck",
|
|
||||||
"indexmap",
|
|
||||||
"prettyplease",
|
|
||||||
"syn",
|
|
||||||
"wasm-metadata",
|
|
||||||
"wit-bindgen-core",
|
|
||||||
"wit-component",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-bindgen-rust-macro"
|
|
||||||
version = "0.51.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"prettyplease",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
"wit-bindgen-core",
|
|
||||||
"wit-bindgen-rust",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-component"
|
|
||||||
version = "0.244.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"bitflags 2.10.0",
|
|
||||||
"indexmap",
|
|
||||||
"log",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"wasm-encoder",
|
|
||||||
"wasm-metadata",
|
|
||||||
"wasmparser",
|
|
||||||
"wit-parser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wit-parser"
|
|
||||||
version = "0.244.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"id-arena",
|
|
||||||
"indexmap",
|
|
||||||
"log",
|
|
||||||
"semver",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"unicode-xid",
|
|
||||||
"wasmparser",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
|
|
@ -3140,9 +2891,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip"
|
name = "zip"
|
||||||
version = "7.4.0"
|
version = "7.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cc12baa6db2b15a140161ce53d72209dacea594230798c24774139b54ecaa980"
|
checksum = "9013f1222db8a6d680f13a7ccdc60a781199cd09c2fa4eff58e728bb181757fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"bzip2",
|
"bzip2",
|
||||||
|
|
@ -3150,7 +2901,8 @@ dependencies = [
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"deflate64",
|
"deflate64",
|
||||||
"flate2",
|
"flate2",
|
||||||
"getrandom 0.4.1",
|
"generic-array",
|
||||||
|
"getrandom 0.3.4",
|
||||||
"hmac",
|
"hmac",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"lzma-rust2",
|
"lzma-rust2",
|
||||||
|
|
@ -3159,7 +2911,6 @@ dependencies = [
|
||||||
"ppmd-rust",
|
"ppmd-rust",
|
||||||
"sha1",
|
"sha1",
|
||||||
"time",
|
"time",
|
||||||
"typed-path",
|
|
||||||
"zeroize",
|
"zeroize",
|
||||||
"zopfli",
|
"zopfli",
|
||||||
"zstd",
|
"zstd",
|
||||||
|
|
|
||||||
50
Cargo.toml
50
Cargo.toml
|
|
@ -1,47 +1,47 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pakker"
|
name = "pakker"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = [ "NotAShelf <raf@notashelf.dev>" ]
|
authors = [ "NotAShelf <raf@notashelf.dev" ]
|
||||||
rust-version = "1.91.0"
|
|
||||||
readme = true
|
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.101"
|
anyhow = "1.0.100"
|
||||||
async-trait = "0.1.89"
|
async-trait = "0.1.89"
|
||||||
clap = { version = "4.5.58", features = [ "derive" ] }
|
clap = { version = "4.5.54", features = [ "derive" ] }
|
||||||
comfy-table = "7.2.2"
|
comfy-table = "7.1"
|
||||||
dialoguer = "0.12.0"
|
dialoguer = "0.12.0"
|
||||||
env_logger = "0.11.9"
|
env_logger = "0.11.8"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
git2 = "0.20.4"
|
git2 = "0.20.3"
|
||||||
glob = "0.3.3"
|
|
||||||
indicatif = "0.18.3"
|
indicatif = "0.18.3"
|
||||||
keyring = "3.6.3"
|
keyring = "3.6.3"
|
||||||
libc = "0.2.181"
|
libc = "0.2.180"
|
||||||
log = "0.4.29"
|
log = "0.4.29"
|
||||||
md-5 = "0.10.6"
|
md-5 = "0.10.6"
|
||||||
rand = "0.10.0"
|
once_cell = "1.20"
|
||||||
regex = "1.12.3"
|
rand = "0.9.2"
|
||||||
reqwest = { version = "0.13.2", features = [ "json" ] }
|
regex = "1.12"
|
||||||
semver = "1.0.27"
|
reqwest = { version = "0.13.1", features = [ "json" ] }
|
||||||
serde = { version = "1.0.228", features = [ "derive" ] }
|
serde = { version = "1.0.228", features = [ "derive" ] }
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
sha1 = "0.10.6"
|
sha1 = "0.10.6"
|
||||||
sha2 = "0.10.9"
|
sha2 = "0.10.0"
|
||||||
strsim = "0.11.1"
|
strsim = "0.11.1"
|
||||||
tempfile = "3.25.0"
|
tempfile = "3.24.0"
|
||||||
textwrap = "0.16.2"
|
textwrap = "0.16"
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.17"
|
||||||
tokio = { version = "1.49.0", features = [ "full" ] }
|
tokio = { version = "1.49.0", features = [ "full" ] }
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
yansi = "1.0.1"
|
yansi = "1.0.1"
|
||||||
zip = "7.4.0"
|
zip = "7.1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
mockito = "1.7.2"
|
mockito = "1.7.1"
|
||||||
tempfile = "3.25.0"
|
tempfile = "3.24.0"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "pakker"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
# Optimize crypto stuff. Building them with optimizations makes that build script
|
# Optimize crypto stuff. Building them with optimizations makes that build script
|
||||||
# run ~5x faster, more than offsetting the additional build time added to the
|
# run ~5x faster, more than offsetting the additional build time added to the
|
||||||
|
|
|
||||||
34
src/cli.rs
34
src/cli.rs
|
|
@ -97,20 +97,20 @@ pub struct InitArgs {
|
||||||
pub version: Option<String>,
|
pub version: Option<String>,
|
||||||
|
|
||||||
/// Target platform
|
/// Target platform
|
||||||
#[clap(short, long)]
|
#[clap(short, long, default_value = "multiplatform")]
|
||||||
pub target: Option<String>,
|
pub target: String,
|
||||||
|
|
||||||
/// Minecraft versions (space-separated)
|
/// Minecraft version
|
||||||
#[clap(short, long = "mc-versions", value_delimiter = ' ', num_args = 1..)]
|
#[clap(short, long, default_value = "1.20.1")]
|
||||||
pub mc_versions: Option<Vec<String>>,
|
pub mc_version: String,
|
||||||
|
|
||||||
/// Mod loaders (format: name=version, can be specified multiple times)
|
/// Mod loader
|
||||||
#[clap(short, long = "loaders", value_delimiter = ',')]
|
#[clap(short, long, default_value = "fabric")]
|
||||||
pub loaders: Option<Vec<String>>,
|
pub loader: String,
|
||||||
|
|
||||||
/// Skip interactive prompts (use defaults)
|
/// Mod loader version
|
||||||
#[clap(short, long)]
|
#[clap(short = 'v', long, default_value = "latest")]
|
||||||
pub yes: bool,
|
pub loader_version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
|
@ -214,10 +214,6 @@ pub struct RmArgs {
|
||||||
/// Skip confirmation prompt
|
/// Skip confirmation prompt
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub yes: bool,
|
pub yes: bool,
|
||||||
|
|
||||||
/// Skip removing dependent projects
|
|
||||||
#[clap(short = 'D', long = "no-deps")]
|
|
||||||
pub no_deps: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
|
|
@ -226,10 +222,6 @@ pub struct UpdateArgs {
|
||||||
#[arg(value_name = "PROJECT")]
|
#[arg(value_name = "PROJECT")]
|
||||||
pub inputs: Vec<String>,
|
pub inputs: Vec<String>,
|
||||||
|
|
||||||
/// Update all projects
|
|
||||||
#[arg(short, long)]
|
|
||||||
pub all: bool,
|
|
||||||
|
|
||||||
/// Skip confirmation prompts
|
/// Skip confirmation prompts
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub yes: bool,
|
pub yes: bool,
|
||||||
|
|
@ -352,7 +344,7 @@ pub struct SyncArgs {
|
||||||
#[clap(short = 'R', long)]
|
#[clap(short = 'R', long)]
|
||||||
pub removals: bool,
|
pub removals: bool,
|
||||||
|
|
||||||
/// Sync updates only (apply pending updates)
|
/// Sync updates only
|
||||||
#[clap(short = 'U', long)]
|
#[clap(short = 'U', long)]
|
||||||
pub updates: bool,
|
pub updates: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -379,7 +371,7 @@ pub struct ExportArgs {
|
||||||
|
|
||||||
/// Export modpack without server content
|
/// Export modpack without server content
|
||||||
/// Modrinth: exclude server-overrides and SERVER mods
|
/// Modrinth: exclude server-overrides and SERVER mods
|
||||||
/// `ServerPack`: skip export
|
/// ServerPack: skip export
|
||||||
#[clap(long = "no-server")]
|
#[clap(long = "no-server")]
|
||||||
pub no_server: bool,
|
pub no_server: bool,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ pub async fn execute(
|
||||||
let config_dir = config_path.parent().unwrap_or(Path::new("."));
|
let config_dir = config_path.parent().unwrap_or(Path::new("."));
|
||||||
|
|
||||||
// IPC coordination - prevent concurrent operations on the same modpack
|
// IPC coordination - prevent concurrent operations on the same modpack
|
||||||
let ipc = IpcCoordinator::new(config_dir)?;
|
let ipc = IpcCoordinator::new(&config_dir.to_path_buf())?;
|
||||||
let ipc_timeout = std::time::Duration::from_secs(60);
|
let ipc_timeout = std::time::Duration::from_secs(60);
|
||||||
|
|
||||||
// Check for conflicting export operations
|
// Check for conflicting export operations
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ pub async fn execute(
|
||||||
let operation_id = coordinator.register_operation(OperationType::Fetch)?;
|
let operation_id = coordinator.register_operation(OperationType::Fetch)?;
|
||||||
let _guard = OperationGuard::new(coordinator, operation_id);
|
let _guard = OperationGuard::new(coordinator, operation_id);
|
||||||
|
|
||||||
// Create fetcher with shelve option
|
// Create fetcher
|
||||||
let fetcher = Fetcher::new(".").with_shelve(args.shelve);
|
let fetcher = Fetcher::new(".");
|
||||||
|
|
||||||
// Fetch all projects (progress indicators handled in fetch.rs)
|
// Fetch all projects (progress indicators handled in fetch.rs)
|
||||||
fetcher.fetch_all(&lockfile, &config).await?;
|
fetcher.fetch_all(&lockfile, &config).await?;
|
||||||
|
|
|
||||||
|
|
@ -211,12 +211,13 @@ fn execute_init(
|
||||||
.args(["log", "--limit", "1", "--template", ""])
|
.args(["log", "--limit", "1", "--template", ""])
|
||||||
.current_dir(path)
|
.current_dir(path)
|
||||||
.output()
|
.output()
|
||||||
&& !output.stdout.is_empty()
|
|
||||||
{
|
{
|
||||||
println!(
|
if !output.stdout.is_empty() {
|
||||||
"Note: Jujutsu repository detected. Make sure to run 'jj git \
|
println!(
|
||||||
push' to sync changes with remote if needed."
|
"Note: Jujutsu repository detected. Make sure to run 'jj git \
|
||||||
);
|
push' to sync changes with remote if needed."
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
VcsType::None => {
|
VcsType::None => {
|
||||||
|
|
|
||||||
|
|
@ -134,19 +134,16 @@ async fn import_modrinth(
|
||||||
{
|
{
|
||||||
log::info!("Fetching project: {project_id}");
|
log::info!("Fetching project: {project_id}");
|
||||||
match platform
|
match platform
|
||||||
.request_project_with_files(
|
.request_project_with_files(project_id, &lockfile.mc_versions, &[
|
||||||
project_id,
|
loader.0.clone(),
|
||||||
&lockfile.mc_versions,
|
])
|
||||||
std::slice::from_ref(&loader.0),
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(mut project) => {
|
Ok(mut project) => {
|
||||||
// Select best file
|
// Select best file
|
||||||
if let Err(e) = project.select_file(
|
if let Err(e) =
|
||||||
&lockfile.mc_versions,
|
project.select_file(&lockfile.mc_versions, &[loader.0.clone()])
|
||||||
std::slice::from_ref(&loader.0),
|
{
|
||||||
) {
|
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"Failed to select file for {}: {}",
|
"Failed to select file for {}: {}",
|
||||||
project.get_name(),
|
project.get_name(),
|
||||||
|
|
@ -360,7 +357,7 @@ async fn import_curseforge(
|
||||||
description: None,
|
description: None,
|
||||||
author: manifest["author"]
|
author: manifest["author"]
|
||||||
.as_str()
|
.as_str()
|
||||||
.map(std::string::ToString::to_string),
|
.map(|s| s.to_string()),
|
||||||
overrides: vec!["overrides".to_string()],
|
overrides: vec!["overrides".to_string()],
|
||||||
server_overrides: None,
|
server_overrides: None,
|
||||||
client_overrides: None,
|
client_overrides: None,
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ fn display_project_inspection(
|
||||||
|
|
||||||
// Display project files
|
// Display project files
|
||||||
println!();
|
println!();
|
||||||
display_project_files(&project.files, project)?;
|
display_project_files(&project.files)?;
|
||||||
|
|
||||||
// Display properties
|
// Display properties
|
||||||
println!();
|
println!();
|
||||||
|
|
@ -228,10 +228,7 @@ fn display_project_header(project: &Project) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_project_files(
|
fn display_project_files(files: &[ProjectFile]) -> Result<()> {
|
||||||
files: &[ProjectFile],
|
|
||||||
project: &Project,
|
|
||||||
) -> Result<()> {
|
|
||||||
if files.is_empty() {
|
if files.is_empty() {
|
||||||
println!("{}", "No files available".yellow());
|
println!("{}", "No files available".yellow());
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -253,31 +250,19 @@ fn display_project_files(
|
||||||
format!(" {status}")
|
format!(" {status}")
|
||||||
};
|
};
|
||||||
|
|
||||||
// File path line with optional site URL
|
// File path line
|
||||||
let file_path = format!("{}={}", file.file_type, file.file_name);
|
let file_path = format!("{}={}", file.file_type, file.file_name);
|
||||||
let file_display = if let Some(site_url) = file.get_site_url(project) {
|
table.add_row(vec![
|
||||||
// Create hyperlink for the file
|
Cell::new(format!("{file_path}:{status_text}")).fg(if idx == 0 {
|
||||||
let hyperlink = crate::ui_utils::hyperlink(&site_url, &file_path);
|
Color::Green
|
||||||
format!("{hyperlink}:{status_text}")
|
} else {
|
||||||
} else {
|
Color::White
|
||||||
format!("{file_path}:{status_text}")
|
}),
|
||||||
};
|
]);
|
||||||
|
|
||||||
table.add_row(vec![Cell::new(file_display).fg(if idx == 0 {
|
|
||||||
Color::Green
|
|
||||||
} else {
|
|
||||||
Color::White
|
|
||||||
})]);
|
|
||||||
|
|
||||||
// Date published
|
// Date published
|
||||||
table.add_row(vec![Cell::new(&file.date_published).fg(Color::DarkGrey)]);
|
table.add_row(vec![Cell::new(&file.date_published).fg(Color::DarkGrey)]);
|
||||||
|
|
||||||
// Show site URL if available (for non-hyperlink terminals)
|
|
||||||
if let Some(site_url) = file.get_site_url(project) {
|
|
||||||
table
|
|
||||||
.add_row(vec![Cell::new(format!("URL: {site_url}")).fg(Color::Blue)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty line
|
// Empty line
|
||||||
table.add_row(vec![Cell::new("")]);
|
table.add_row(vec![Cell::new("")]);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,6 @@ use std::path::Path;
|
||||||
|
|
||||||
use crate::{cli::LsArgs, error::Result, model::LockFile};
|
use crate::{cli::LsArgs, error::Result, model::LockFile};
|
||||||
|
|
||||||
/// Truncate a name to fit within `max_len` characters, adding "..." if
|
|
||||||
/// truncated
|
|
||||||
fn truncate_name(name: &str, max_len: usize) -> String {
|
|
||||||
if name.len() <= max_len {
|
|
||||||
name.to_string()
|
|
||||||
} else if max_len > 3 {
|
|
||||||
format!("{}...", &name[..max_len - 3])
|
|
||||||
} else {
|
|
||||||
name[..max_len].to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn execute(args: LsArgs, lockfile_path: &Path) -> Result<()> {
|
pub fn execute(args: LsArgs, lockfile_path: &Path) -> Result<()> {
|
||||||
// Load expects directory path, so get parent directory
|
// Load expects directory path, so get parent directory
|
||||||
let lockfile_dir = lockfile_path.parent().unwrap_or(Path::new("."));
|
let lockfile_dir = lockfile_path.parent().unwrap_or(Path::new("."));
|
||||||
|
|
@ -27,33 +15,10 @@ pub fn execute(args: LsArgs, lockfile_path: &Path) -> Result<()> {
|
||||||
println!("Installed projects ({}):", lockfile.projects.len());
|
println!("Installed projects ({}):", lockfile.projects.len());
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
// Calculate max name length for alignment
|
|
||||||
let max_name_len = args.name_max_length.unwrap_or_else(|| {
|
|
||||||
lockfile
|
|
||||||
.projects
|
|
||||||
.iter()
|
|
||||||
.map(|p| p.get_name().len())
|
|
||||||
.max()
|
|
||||||
.unwrap_or(20)
|
|
||||||
.min(50)
|
|
||||||
});
|
|
||||||
|
|
||||||
for project in &lockfile.projects {
|
for project in &lockfile.projects {
|
||||||
// Check for version mismatch across providers
|
|
||||||
let version_warning = if project.versions_match_across_providers() {
|
|
||||||
""
|
|
||||||
} else {
|
|
||||||
// Use the detailed check_version_mismatch for logging
|
|
||||||
if let Some(mismatch_detail) = project.check_version_mismatch() {
|
|
||||||
log::warn!("{mismatch_detail}");
|
|
||||||
}
|
|
||||||
" [!] versions do not match across providers"
|
|
||||||
};
|
|
||||||
|
|
||||||
if args.detailed {
|
if args.detailed {
|
||||||
let id = project.pakku_id.as_deref().unwrap_or("unknown");
|
let id = project.pakku_id.as_deref().unwrap_or("unknown");
|
||||||
let name = truncate_name(&project.get_name(), max_name_len);
|
println!(" {} ({})", project.get_name(), id);
|
||||||
println!(" {name} ({id}){version_warning}");
|
|
||||||
println!(" Type: {:?}", project.r#type);
|
println!(" Type: {:?}", project.r#type);
|
||||||
println!(" Side: {:?}", project.side);
|
println!(" Side: {:?}", project.side);
|
||||||
|
|
||||||
|
|
@ -65,28 +30,19 @@ pub fn execute(args: LsArgs, lockfile_path: &Path) -> Result<()> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show version details if there's a mismatch
|
|
||||||
if !version_warning.is_empty() {
|
|
||||||
println!(" Provider versions:");
|
|
||||||
for file in &project.files {
|
|
||||||
println!(" {}: {}", file.file_type, file.file_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !project.pakku_links.is_empty() {
|
if !project.pakku_links.is_empty() {
|
||||||
println!(" Dependencies: {}", project.pakku_links.len());
|
println!(" Dependencies: {}", project.pakku_links.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
} else {
|
} else {
|
||||||
let name = truncate_name(&project.get_name(), max_name_len);
|
|
||||||
let file_info = project
|
let file_info = project
|
||||||
.files
|
.files
|
||||||
.first()
|
.first()
|
||||||
.map(|f| format!(" ({})", f.file_name))
|
.map(|f| format!(" ({})", f.file_name))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
println!(" {name}{file_info}{version_warning}");
|
println!(" {}{}", project.get_name(), file_info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::{cli::RemoteUpdateArgs, error::PakkerError, git, model::Config};
|
use crate::{cli::RemoteUpdateArgs, error::PakkerError, git, model::Config};
|
||||||
|
|
||||||
|
|
@ -71,7 +71,7 @@ pub async fn execute(args: RemoteUpdateArgs) -> Result<(), PakkerError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sync override files from remote directory to current directory
|
/// Sync override files from remote directory to current directory
|
||||||
async fn sync_overrides(remote_dir: &Path) -> Result<(), PakkerError> {
|
async fn sync_overrides(remote_dir: &PathBuf) -> Result<(), PakkerError> {
|
||||||
let remote_config_path = remote_dir.join("pakku.json");
|
let remote_config_path = remote_dir.join("pakku.json");
|
||||||
if !remote_config_path.exists() {
|
if !remote_config_path.exists() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use tokio::sync::Semaphore;
|
||||||
use yansi::Paint;
|
use yansi::Paint;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{ErrorSeverity, Result},
|
error::Result,
|
||||||
model::{Config, LockFile, Project},
|
model::{Config, LockFile, Project},
|
||||||
platform::create_platform,
|
platform::create_platform,
|
||||||
};
|
};
|
||||||
|
|
@ -36,42 +36,13 @@ pub async fn execute(
|
||||||
// Display results
|
// Display results
|
||||||
display_update_results(&updates);
|
display_update_results(&updates);
|
||||||
|
|
||||||
// Display errors if any, categorized by severity
|
// Display errors if any
|
||||||
if !errors.is_empty() {
|
if !errors.is_empty() {
|
||||||
println!();
|
println!();
|
||||||
|
println!("{}", "Errors encountered:".red());
|
||||||
// Categorize errors by severity
|
for (project, error) in &errors {
|
||||||
let (warnings, errors_only): (Vec<_>, Vec<_>) =
|
println!(" - {}: {}", project.yellow(), error.red());
|
||||||
errors.iter().partition(|(_, err)| {
|
|
||||||
// Network errors and "not found" are warnings (non-fatal)
|
|
||||||
err.contains("Failed to check") || err.contains("not found")
|
|
||||||
});
|
|
||||||
|
|
||||||
// Display warnings (ErrorSeverity::Warning)
|
|
||||||
if !warnings.is_empty() {
|
|
||||||
let severity = ErrorSeverity::Warning;
|
|
||||||
println!("{}", format_severity_header(severity, "Warnings"));
|
|
||||||
for (project, error) in &warnings {
|
|
||||||
println!(" - {}: {}", project.yellow(), error.dim());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display errors (ErrorSeverity::Error)
|
|
||||||
if !errors_only.is_empty() {
|
|
||||||
let severity = ErrorSeverity::Error;
|
|
||||||
println!("{}", format_severity_header(severity, "Errors"));
|
|
||||||
for (project, error) in &errors_only {
|
|
||||||
println!(" - {}: {}", project.yellow(), error.red());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log info level summary
|
|
||||||
let _info_severity = ErrorSeverity::Info;
|
|
||||||
log::info!(
|
|
||||||
"Update check completed with {} warning(s) and {} error(s)",
|
|
||||||
warnings.len(),
|
|
||||||
errors_only.len()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prompt to update if there are updates available
|
// Prompt to update if there are updates available
|
||||||
|
|
@ -81,7 +52,6 @@ pub async fn execute(
|
||||||
// Call update command programmatically (update all projects)
|
// Call update command programmatically (update all projects)
|
||||||
let update_args = crate::cli::UpdateArgs {
|
let update_args = crate::cli::UpdateArgs {
|
||||||
inputs: vec![],
|
inputs: vec![],
|
||||||
all: true,
|
|
||||||
yes: true, // Auto-yes for status command
|
yes: true, // Auto-yes for status command
|
||||||
};
|
};
|
||||||
crate::cli::commands::update::execute(
|
crate::cli::commands::update::execute(
|
||||||
|
|
@ -398,12 +368,3 @@ fn get_api_key(platform: &str) -> Option<String> {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format severity header with appropriate color
|
|
||||||
fn format_severity_header(severity: ErrorSeverity, label: &str) -> String {
|
|
||||||
match severity {
|
|
||||||
ErrorSeverity::Error => format!("{label}:").red().to_string(),
|
|
||||||
ErrorSeverity::Warning => format!("{label}:").yellow().to_string(),
|
|
||||||
ErrorSeverity::Info => format!("{label}:").cyan().to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cli::UpdateArgs,
|
cli::UpdateArgs,
|
||||||
error::{MultiError, PakkerError},
|
error::PakkerError,
|
||||||
model::{Config, LockFile, UpdateStrategy},
|
model::{Config, LockFile},
|
||||||
platform::create_platform,
|
platform::create_platform,
|
||||||
ui_utils::{prompt_select, prompt_typo_suggestion, prompt_yes_no},
|
ui_utils::prompt_select,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
|
|
@ -33,22 +33,6 @@ pub async fn execute(
|
||||||
platforms.insert("curseforge".to_string(), platform);
|
platforms.insert("curseforge".to_string(), platform);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect all known project identifiers for typo suggestions
|
|
||||||
let all_slugs: Vec<String> = lockfile
|
|
||||||
.projects
|
|
||||||
.iter()
|
|
||||||
.flat_map(|p| {
|
|
||||||
let mut ids = Vec::new();
|
|
||||||
if let Some(ref pakku_id) = p.pakku_id {
|
|
||||||
ids.push(pakku_id.clone());
|
|
||||||
}
|
|
||||||
ids.extend(p.slug.values().cloned());
|
|
||||||
ids.extend(p.name.values().cloned());
|
|
||||||
ids.extend(p.aliases.iter().cloned());
|
|
||||||
ids
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let project_indices: Vec<_> = if args.inputs.is_empty() {
|
let project_indices: Vec<_> = if args.inputs.is_empty() {
|
||||||
(0..lockfile.projects.len()).collect()
|
(0..lockfile.projects.len()).collect()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -62,29 +46,14 @@ pub async fn execute(
|
||||||
{
|
{
|
||||||
indices.push(idx);
|
indices.push(idx);
|
||||||
} else {
|
} else {
|
||||||
// Try typo suggestion
|
|
||||||
if let Ok(Some(suggestion)) = prompt_typo_suggestion(input, &all_slugs)
|
|
||||||
&& let Some((idx, _)) = lockfile
|
|
||||||
.projects
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_, p)| p.matches_input(&suggestion))
|
|
||||||
{
|
|
||||||
log::info!("Using suggested project: {suggestion}");
|
|
||||||
indices.push(idx);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return Err(PakkerError::ProjectNotFound(input.clone()));
|
return Err(PakkerError::ProjectNotFound(input.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
indices
|
indices
|
||||||
};
|
};
|
||||||
|
|
||||||
// Capture count before consuming the iterator
|
|
||||||
let total_projects = project_indices.len();
|
|
||||||
|
|
||||||
// Create progress bar
|
// Create progress bar
|
||||||
let pb = ProgressBar::new(total_projects as u64);
|
let pb = ProgressBar::new(project_indices.len() as u64);
|
||||||
pb.set_style(
|
pb.set_style(
|
||||||
ProgressStyle::default_bar()
|
ProgressStyle::default_bar()
|
||||||
.template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} {msg}")
|
.template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} {msg}")
|
||||||
|
|
@ -92,23 +61,8 @@ pub async fn execute(
|
||||||
.progress_chars("#>-"),
|
.progress_chars("#>-"),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut skipped_pinned = 0;
|
|
||||||
let mut update_errors = MultiError::new();
|
|
||||||
|
|
||||||
for idx in project_indices {
|
for idx in project_indices {
|
||||||
let old_project = &lockfile.projects[idx];
|
let old_project = &lockfile.projects[idx];
|
||||||
|
|
||||||
// Skip projects with UpdateStrategy::None (pinned)
|
|
||||||
if old_project.update_strategy == UpdateStrategy::None {
|
|
||||||
pb.println(format!(
|
|
||||||
" {} - Skipped (update strategy: NONE)",
|
|
||||||
old_project.get_name()
|
|
||||||
));
|
|
||||||
skipped_pinned += 1;
|
|
||||||
pb.inc(1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
pb.set_message(format!("Updating {}...", old_project.get_name()));
|
pb.set_message(format!("Updating {}...", old_project.get_name()));
|
||||||
|
|
||||||
let slug = old_project
|
let slug = old_project
|
||||||
|
|
@ -133,116 +87,54 @@ pub async fn execute(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if updated_project.is_none() {
|
|
||||||
// Failed to fetch update info from any platform
|
|
||||||
update_errors.push(PakkerError::PlatformApiError(format!(
|
|
||||||
"Failed to check updates for '{}'",
|
|
||||||
old_project.get_name()
|
|
||||||
)));
|
|
||||||
pb.inc(1);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(mut updated_project) = updated_project
|
if let Some(mut updated_project) = updated_project
|
||||||
&& !updated_project.files.is_empty()
|
&& !updated_project.files.is_empty()
|
||||||
&& let Some(old_file) = lockfile.projects[idx].files.first()
|
&& let Some(old_file) = lockfile.projects[idx].files.first()
|
||||||
{
|
{
|
||||||
// Clone data needed for comparisons to avoid borrow issues
|
let new_file = updated_project.files.first().unwrap();
|
||||||
let new_file_id = updated_project.files.first().unwrap().id.clone();
|
|
||||||
let new_file_name =
|
|
||||||
updated_project.files.first().unwrap().file_name.clone();
|
|
||||||
let old_file_name = old_file.file_name.clone();
|
|
||||||
let project_name = old_project.get_name();
|
|
||||||
|
|
||||||
if new_file_id == old_file.id {
|
if new_file.id == old_file.id {
|
||||||
pb.println(format!(" {project_name} - Already up to date"));
|
pb.println(format!(
|
||||||
|
" {} - Already up to date",
|
||||||
|
old_project.get_name()
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
// Interactive confirmation and version selection if not using --yes
|
// Interactive version selection if not using --yes flag
|
||||||
// flag
|
if !args.yes && updated_project.files.len() > 1 {
|
||||||
let mut should_update = args.yes || args.all;
|
|
||||||
let mut selected_idx: Option<usize> = None;
|
|
||||||
|
|
||||||
if !args.yes && !args.all {
|
|
||||||
pb.suspend(|| {
|
pb.suspend(|| {
|
||||||
// First, confirm the update
|
let choices: Vec<String> = updated_project
|
||||||
let prompt_msg = format!(
|
.files
|
||||||
"Update '{project_name}' from {old_file_name} to \
|
.iter()
|
||||||
{new_file_name}?"
|
.map(|f| format!("{} ({})", f.file_name, f.id))
|
||||||
);
|
.collect();
|
||||||
should_update = prompt_yes_no(&prompt_msg, true).unwrap_or(false);
|
|
||||||
|
|
||||||
// If confirmed and multiple versions available, offer selection
|
let choice_refs: Vec<&str> =
|
||||||
if should_update && updated_project.files.len() > 1 {
|
choices.iter().map(std::string::String::as_str).collect();
|
||||||
let choices: Vec<String> = updated_project
|
|
||||||
.files
|
|
||||||
.iter()
|
|
||||||
.map(|f| format!("{} ({})", f.file_name, f.id))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let choice_refs: Vec<&str> =
|
if let Ok(selected_idx) = prompt_select(
|
||||||
choices.iter().map(std::string::String::as_str).collect();
|
&format!("Select version for {}:", old_project.get_name()),
|
||||||
|
&choice_refs,
|
||||||
if let Ok(idx) = prompt_select(
|
) {
|
||||||
&format!("Select version for {project_name}:"),
|
// Move selected file to front
|
||||||
&choice_refs,
|
if selected_idx > 0 {
|
||||||
) {
|
updated_project.files.swap(0, selected_idx);
|
||||||
selected_idx = Some(idx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply file selection outside the closure
|
let selected_file = updated_project.files.first().unwrap();
|
||||||
if let Some(idx) = selected_idx
|
pb.println(format!(
|
||||||
&& idx > 0
|
" {} -> {}",
|
||||||
{
|
old_file.file_name, selected_file.file_name
|
||||||
updated_project.files.swap(0, idx);
|
));
|
||||||
}
|
lockfile.projects[idx] = updated_project;
|
||||||
|
|
||||||
if should_update {
|
|
||||||
let selected_file = updated_project.files.first().unwrap();
|
|
||||||
pb.println(format!(
|
|
||||||
" {} -> {}",
|
|
||||||
old_file_name, selected_file.file_name
|
|
||||||
));
|
|
||||||
lockfile.projects[idx] = updated_project;
|
|
||||||
} else {
|
|
||||||
pb.println(format!(" {project_name} - Skipped by user"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pb.inc(1);
|
pb.inc(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if skipped_pinned > 0 {
|
pb.finish_with_message("Update complete");
|
||||||
pb.finish_with_message(format!(
|
|
||||||
"Update complete ({skipped_pinned} pinned projects skipped)"
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
pb.finish_with_message("Update complete");
|
|
||||||
}
|
|
||||||
lockfile.save(lockfile_dir)?;
|
lockfile.save(lockfile_dir)?;
|
||||||
|
|
||||||
// Report any errors that occurred during updates
|
|
||||||
if !update_errors.is_empty() {
|
|
||||||
let error_list = update_errors.errors();
|
|
||||||
log::warn!(
|
|
||||||
"{} project(s) encountered errors during update check",
|
|
||||||
error_list.len()
|
|
||||||
);
|
|
||||||
for err in error_list {
|
|
||||||
log::warn!(" - {err}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extend with any additional collected errors and check if we should fail
|
|
||||||
let all_errors = update_errors.into_errors();
|
|
||||||
if all_errors.len() == total_projects {
|
|
||||||
// All projects failed - return error
|
|
||||||
let mut multi = MultiError::new();
|
|
||||||
multi.extend(all_errors);
|
|
||||||
return multi.into_result(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -288,9 +288,10 @@ pub fn detect_vcs_type<P: AsRef<Path>>(path: P) -> VcsType {
|
||||||
.args(["root"])
|
.args(["root"])
|
||||||
.current_dir(path)
|
.current_dir(path)
|
||||||
.output()
|
.output()
|
||||||
&& output.status.success()
|
|
||||||
{
|
{
|
||||||
return VcsType::Jujutsu;
|
if output.status.success() {
|
||||||
|
return VcsType::Jujutsu;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for git
|
// Check for git
|
||||||
|
|
@ -298,9 +299,10 @@ pub fn detect_vcs_type<P: AsRef<Path>>(path: P) -> VcsType {
|
||||||
.args(["rev-parse", "--show-toplevel"])
|
.args(["rev-parse", "--show-toplevel"])
|
||||||
.current_dir(path)
|
.current_dir(path)
|
||||||
.output()
|
.output()
|
||||||
&& output.status.success()
|
|
||||||
{
|
{
|
||||||
return VcsType::Git;
|
if output.status.success() {
|
||||||
|
return VcsType::Git;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VcsType::None
|
VcsType::None
|
||||||
|
|
@ -331,7 +333,7 @@ pub fn repo_has_uncommitted_changes<P: AsRef<Path>>(path: P) -> Result<bool> {
|
||||||
.current_dir(path)
|
.current_dir(path)
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
PakkerError::GitError(format!("Failed to run jj status: {e}"))
|
PakkerError::GitError(format!("Failed to run jj status: {}", e))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let output_str = String::from_utf8_lossy(&output.stdout);
|
let output_str = String::from_utf8_lossy(&output.stdout);
|
||||||
|
|
|
||||||
16
src/http.rs
16
src/http.rs
|
|
@ -1,16 +0,0 @@
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use reqwest::Client;
|
|
||||||
|
|
||||||
pub fn create_http_client() -> Client {
|
|
||||||
Client::builder()
|
|
||||||
.pool_max_idle_per_host(10)
|
|
||||||
.pool_idle_timeout(Duration::from_secs(30))
|
|
||||||
.tcp_keepalive(Duration::from_secs(60))
|
|
||||||
.tcp_nodelay(true)
|
|
||||||
.connect_timeout(Duration::from_secs(15))
|
|
||||||
.timeout(Duration::from_secs(30))
|
|
||||||
.user_agent("Pakker/0.1.0")
|
|
||||||
.build()
|
|
||||||
.expect("Failed to build HTTP client")
|
|
||||||
}
|
|
||||||
11
src/main.rs
11
src/main.rs
|
|
@ -1,15 +1,8 @@
|
||||||
// Allow pre-existing clippy warnings for functions with many arguments
|
|
||||||
// and complex types that would require significant refactoring
|
|
||||||
#![allow(clippy::too_many_arguments)]
|
|
||||||
#![allow(clippy::type_complexity)]
|
|
||||||
#![allow(clippy::large_enum_variant)]
|
|
||||||
|
|
||||||
mod cli;
|
mod cli;
|
||||||
mod error;
|
mod error;
|
||||||
mod export;
|
mod export;
|
||||||
mod fetch;
|
mod fetch;
|
||||||
mod git;
|
mod git;
|
||||||
mod http;
|
|
||||||
mod ipc;
|
mod ipc;
|
||||||
mod model;
|
mod model;
|
||||||
mod platform;
|
mod platform;
|
||||||
|
|
@ -24,6 +17,8 @@ use clap::Parser;
|
||||||
use cli::{Cli, Commands};
|
use cli::{Cli, Commands};
|
||||||
use error::PakkerError;
|
use error::PakkerError;
|
||||||
|
|
||||||
|
use crate::rate_limiter::RateLimiter;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), PakkerError> {
|
async fn main() -> Result<(), PakkerError> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
@ -47,6 +42,8 @@ async fn main() -> Result<(), PakkerError> {
|
||||||
let lockfile_path = working_dir.join("pakker-lock.json");
|
let lockfile_path = working_dir.join("pakker-lock.json");
|
||||||
let config_path = working_dir.join("pakker.json");
|
let config_path = working_dir.join("pakker.json");
|
||||||
|
|
||||||
|
let _rate_limiter = std::sync::Arc::new(RateLimiter::new(None));
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Commands::Init(args) => {
|
Commands::Init(args) => {
|
||||||
cli::commands::init::execute(args, &lockfile_path, &config_path).await
|
cli::commands::init::execute(args, &lockfile_path, &config_path).await
|
||||||
|
|
|
||||||
|
|
@ -192,29 +192,15 @@ impl Project {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare semantic versions extracted from file names
|
// Check if all providers have the same latest file name
|
||||||
let parse_version = |name: &str| {
|
// (simplified check - in reality would compare semantic versions)
|
||||||
// Try to extract version from patterns like "mod-1.0.0.jar" or
|
let file_names: Vec<_> = versions_by_provider
|
||||||
// "mod_v1.0.0"
|
|
||||||
let version_str = name
|
|
||||||
.rsplit_once('-')
|
|
||||||
.and_then(|(_, v)| v.strip_suffix(".jar"))
|
|
||||||
.or_else(|| {
|
|
||||||
name
|
|
||||||
.rsplit_once('_')
|
|
||||||
.and_then(|(_, v)| v.strip_suffix(".jar"))
|
|
||||||
})
|
|
||||||
.unwrap_or(name);
|
|
||||||
semver::Version::parse(version_str).ok()
|
|
||||||
};
|
|
||||||
|
|
||||||
let versions: Vec<_> = versions_by_provider
|
|
||||||
.values()
|
.values()
|
||||||
.filter_map(|files| files.first().copied().and_then(parse_version))
|
.filter_map(|files| files.first().copied())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// All versions should be the same
|
// All file names should be the same for versions to match
|
||||||
versions.windows(2).all(|w| w[0] == w[1])
|
file_names.windows(2).all(|w| w[0] == w[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if versions do NOT match across providers.
|
/// Check if versions do NOT match across providers.
|
||||||
|
|
|
||||||
|
|
@ -8,19 +8,13 @@ use std::sync::Arc;
|
||||||
pub use curseforge::CurseForgePlatform;
|
pub use curseforge::CurseForgePlatform;
|
||||||
pub use github::GitHubPlatform;
|
pub use github::GitHubPlatform;
|
||||||
pub use modrinth::ModrinthPlatform;
|
pub use modrinth::ModrinthPlatform;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
pub use traits::PlatformClient;
|
pub use traits::PlatformClient;
|
||||||
|
|
||||||
use crate::{error::Result, http, rate_limiter::RateLimiter};
|
use crate::{error::Result, rate_limiter::RateLimiter};
|
||||||
|
|
||||||
static HTTP_CLIENT: std::sync::LazyLock<Arc<reqwest::Client>> =
|
static RATE_LIMITER: Lazy<Arc<RateLimiter>> =
|
||||||
std::sync::LazyLock::new(|| Arc::new(http::create_http_client()));
|
Lazy::new(|| Arc::new(RateLimiter::new(None)));
|
||||||
|
|
||||||
static RATE_LIMITER: std::sync::LazyLock<Arc<RateLimiter>> =
|
|
||||||
std::sync::LazyLock::new(|| Arc::new(RateLimiter::new(None)));
|
|
||||||
|
|
||||||
pub fn get_http_client() -> Arc<reqwest::Client> {
|
|
||||||
HTTP_CLIENT.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_platform(
|
pub fn create_platform(
|
||||||
platform: &str,
|
platform: &str,
|
||||||
|
|
@ -40,21 +34,9 @@ fn create_client(
|
||||||
api_key: Option<String>,
|
api_key: Option<String>,
|
||||||
) -> Result<Box<dyn PlatformClient>> {
|
) -> Result<Box<dyn PlatformClient>> {
|
||||||
match platform {
|
match platform {
|
||||||
"modrinth" => {
|
"modrinth" => Ok(Box::new(ModrinthPlatform::new())),
|
||||||
Ok(Box::new(ModrinthPlatform::with_client(get_http_client())))
|
"curseforge" => Ok(Box::new(CurseForgePlatform::new(api_key))),
|
||||||
},
|
"github" => Ok(Box::new(GitHubPlatform::new(api_key))),
|
||||||
"curseforge" => {
|
|
||||||
Ok(Box::new(CurseForgePlatform::with_client(
|
|
||||||
get_http_client(),
|
|
||||||
api_key,
|
|
||||||
)))
|
|
||||||
},
|
|
||||||
"github" => {
|
|
||||||
Ok(Box::new(GitHubPlatform::with_client(
|
|
||||||
get_http_client(),
|
|
||||||
api_key,
|
|
||||||
)))
|
|
||||||
},
|
|
||||||
_ => {
|
_ => {
|
||||||
Err(crate::error::PakkerError::ConfigError(format!(
|
Err(crate::error::PakkerError::ConfigError(format!(
|
||||||
"Unknown platform: {platform}"
|
"Unknown platform: {platform}"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
|
@ -12,30 +12,21 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
const CURSEFORGE_API_BASE: &str = "https://api.curseforge.com/v1";
|
const CURSEFORGE_API_BASE: &str = "https://api.curseforge.com/v1";
|
||||||
/// CurseForge game version type ID for loader versions (e.g., "fabric",
|
|
||||||
/// "forge")
|
|
||||||
const LOADER_VERSION_TYPE_ID: i32 = 68441;
|
const LOADER_VERSION_TYPE_ID: i32 = 68441;
|
||||||
/// CurseForge relation type ID for "required dependency" (mod embeds or
|
|
||||||
/// requires another mod)
|
|
||||||
const DEPENDENCY_RELATION_TYPE_REQUIRED: u32 = 3;
|
|
||||||
|
|
||||||
pub struct CurseForgePlatform {
|
pub struct CurseForgePlatform {
|
||||||
client: Arc<Client>,
|
client: Client,
|
||||||
api_key: Option<String>,
|
api_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CurseForgePlatform {
|
impl CurseForgePlatform {
|
||||||
pub fn new(api_key: Option<String>) -> Self {
|
pub fn new(api_key: Option<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
client: Arc::new(Client::new()),
|
client: Client::new(),
|
||||||
api_key,
|
api_key,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_client(client: Arc<Client>, api_key: Option<String>) -> Self {
|
|
||||||
Self { client, api_key }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_headers(&self) -> Result<reqwest::header::HeaderMap> {
|
fn get_headers(&self) -> Result<reqwest::header::HeaderMap> {
|
||||||
let mut headers = reqwest::header::HeaderMap::new();
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
|
|
||||||
|
|
@ -75,81 +66,11 @@ impl CurseForgePlatform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine project side based on `CurseForge` categories.
|
|
||||||
/// `CurseForge` doesn't have explicit client/server fields like Modrinth,
|
|
||||||
/// so we infer from category names and IDs.
|
|
||||||
fn detect_side_from_categories(
|
|
||||||
categories: &[CurseForgeCategory],
|
|
||||||
) -> ProjectSide {
|
|
||||||
// Known client-only category indicators (slugs and partial name matches)
|
|
||||||
const CLIENT_INDICATORS: &[&str] = &[
|
|
||||||
"client",
|
|
||||||
"hud",
|
|
||||||
"gui",
|
|
||||||
"cosmetic",
|
|
||||||
"shader",
|
|
||||||
"optifine",
|
|
||||||
"resource-pack",
|
|
||||||
"texture",
|
|
||||||
"minimap",
|
|
||||||
"tooltip",
|
|
||||||
"inventory",
|
|
||||||
"quality-of-life", // Often client-side QoL
|
|
||||||
];
|
|
||||||
|
|
||||||
// Known server-only category indicators
|
|
||||||
const SERVER_INDICATORS: &[&str] = &[
|
|
||||||
"server-utility",
|
|
||||||
"bukkit",
|
|
||||||
"spigot",
|
|
||||||
"paper",
|
|
||||||
"admin-tools",
|
|
||||||
"anti-grief",
|
|
||||||
"economy",
|
|
||||||
"permissions",
|
|
||||||
"chat",
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut client_score = 0;
|
|
||||||
let mut server_score = 0;
|
|
||||||
|
|
||||||
for category in categories {
|
|
||||||
let slug_lower = category.slug.to_lowercase();
|
|
||||||
let name_lower = category.name.to_lowercase();
|
|
||||||
|
|
||||||
for indicator in CLIENT_INDICATORS {
|
|
||||||
if slug_lower.contains(indicator) || name_lower.contains(indicator) {
|
|
||||||
client_score += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for indicator in SERVER_INDICATORS {
|
|
||||||
if slug_lower.contains(indicator) || name_lower.contains(indicator) {
|
|
||||||
server_score += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only assign a specific side if there's clear indication
|
|
||||||
// and not conflicting signals
|
|
||||||
if client_score > 0 && server_score == 0 {
|
|
||||||
ProjectSide::Client
|
|
||||||
} else if server_score > 0 && client_score == 0 {
|
|
||||||
ProjectSide::Server
|
|
||||||
} else {
|
|
||||||
// Default to Both - works on both client and server
|
|
||||||
ProjectSide::Both
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn convert_project(&self, cf_project: CurseForgeProject) -> Project {
|
fn convert_project(&self, cf_project: CurseForgeProject) -> Project {
|
||||||
let pakku_id = generate_pakku_id();
|
let pakku_id = generate_pakku_id();
|
||||||
let project_type = Self::map_class_id(cf_project.class_id.unwrap_or(6));
|
let project_type = Self::map_class_id(cf_project.class_id.unwrap_or(6));
|
||||||
|
|
||||||
// Detect side from categories
|
let mut project = Project::new(pakku_id, project_type, ProjectSide::Both);
|
||||||
let side = Self::detect_side_from_categories(&cf_project.categories);
|
|
||||||
|
|
||||||
let mut project = Project::new(pakku_id, project_type, side);
|
|
||||||
|
|
||||||
project.add_platform(
|
project.add_platform(
|
||||||
"curseforge".to_string(),
|
"curseforge".to_string(),
|
||||||
|
|
@ -203,7 +124,7 @@ impl CurseForgePlatform {
|
||||||
required_dependencies: cf_file
|
required_dependencies: cf_file
|
||||||
.dependencies
|
.dependencies
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|d| d.relation_type == DEPENDENCY_RELATION_TYPE_REQUIRED)
|
.filter(|d| d.relation_type == 3)
|
||||||
.map(|d| d.mod_id.to_string())
|
.map(|d| d.mod_id.to_string())
|
||||||
.collect(),
|
.collect(),
|
||||||
size: cf_file.file_length,
|
size: cf_file.file_length,
|
||||||
|
|
@ -396,20 +317,11 @@ impl PlatformClient for CurseForgePlatform {
|
||||||
// CurseForge API models
|
// CurseForge API models
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
struct CurseForgeProject {
|
struct CurseForgeProject {
|
||||||
id: u32,
|
id: u32,
|
||||||
name: String,
|
name: String,
|
||||||
slug: String,
|
slug: String,
|
||||||
#[serde(rename = "classId")]
|
#[serde(rename = "classId")]
|
||||||
class_id: Option<u32>,
|
class_id: Option<u32>,
|
||||||
#[serde(default)]
|
|
||||||
categories: Vec<CurseForgeCategory>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
struct CurseForgeCategory {
|
|
||||||
id: u32,
|
|
||||||
name: String,
|
|
||||||
slug: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
|
@ -469,112 +381,3 @@ struct CurseForgeFilesResponse {
|
||||||
struct CurseForgeSearchResponse {
|
struct CurseForgeSearchResponse {
|
||||||
data: Vec<CurseForgeProject>,
|
data: Vec<CurseForgeProject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
fn make_category(id: u32, name: &str, slug: &str) -> CurseForgeCategory {
|
|
||||||
CurseForgeCategory {
|
|
||||||
id,
|
|
||||||
name: name.to_string(),
|
|
||||||
slug: slug.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_detect_side_client_only() {
|
|
||||||
// HUD mod should be client-only
|
|
||||||
let categories = vec![
|
|
||||||
make_category(1, "HUD Mods", "hud"),
|
|
||||||
make_category(2, "Fabric", "fabric"),
|
|
||||||
];
|
|
||||||
let side = CurseForgePlatform::detect_side_from_categories(&categories);
|
|
||||||
assert_eq!(side, ProjectSide::Client);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_detect_side_server_only() {
|
|
||||||
// Server utility should be server-only
|
|
||||||
let categories = vec![
|
|
||||||
make_category(1, "Server Utility", "server-utility"),
|
|
||||||
make_category(2, "Bukkit Plugins", "bukkit"),
|
|
||||||
];
|
|
||||||
let side = CurseForgePlatform::detect_side_from_categories(&categories);
|
|
||||||
assert_eq!(side, ProjectSide::Server);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_detect_side_both() {
|
|
||||||
// Generic mod categories should be both
|
|
||||||
let categories = vec![
|
|
||||||
make_category(1, "Technology", "technology"),
|
|
||||||
make_category(2, "Fabric", "fabric"),
|
|
||||||
];
|
|
||||||
let side = CurseForgePlatform::detect_side_from_categories(&categories);
|
|
||||||
assert_eq!(side, ProjectSide::Both);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_detect_side_conflicting_signals() {
|
|
||||||
// Mixed categories should default to both
|
|
||||||
let categories = vec![
|
|
||||||
make_category(1, "Client HUD", "client-hud"),
|
|
||||||
make_category(2, "Server Utility", "server-utility"),
|
|
||||||
];
|
|
||||||
let side = CurseForgePlatform::detect_side_from_categories(&categories);
|
|
||||||
assert_eq!(side, ProjectSide::Both);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_detect_side_empty_categories() {
|
|
||||||
let categories = vec![];
|
|
||||||
let side = CurseForgePlatform::detect_side_from_categories(&categories);
|
|
||||||
assert_eq!(side, ProjectSide::Both);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_detect_side_gui_client() {
|
|
||||||
let categories =
|
|
||||||
vec![make_category(1, "GUI Enhancement", "gui-enhancement")];
|
|
||||||
let side = CurseForgePlatform::detect_side_from_categories(&categories);
|
|
||||||
assert_eq!(side, ProjectSide::Client);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_detect_side_permissions_server() {
|
|
||||||
let categories = vec![make_category(1, "Permissions", "permissions")];
|
|
||||||
let side = CurseForgePlatform::detect_side_from_categories(&categories);
|
|
||||||
assert_eq!(side, ProjectSide::Server);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_map_class_id() {
|
|
||||||
assert_eq!(CurseForgePlatform::map_class_id(6), ProjectType::Mod);
|
|
||||||
assert_eq!(
|
|
||||||
CurseForgePlatform::map_class_id(12),
|
|
||||||
ProjectType::ResourcePack
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
CurseForgePlatform::map_class_id(6945),
|
|
||||||
ProjectType::DataPack
|
|
||||||
);
|
|
||||||
assert_eq!(CurseForgePlatform::map_class_id(6552), ProjectType::Shader);
|
|
||||||
assert_eq!(CurseForgePlatform::map_class_id(17), ProjectType::World);
|
|
||||||
assert_eq!(CurseForgePlatform::map_class_id(9999), ProjectType::Mod); // Unknown
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_map_release_type() {
|
|
||||||
assert_eq!(
|
|
||||||
CurseForgePlatform::map_release_type(1),
|
|
||||||
ReleaseType::Release
|
|
||||||
);
|
|
||||||
assert_eq!(CurseForgePlatform::map_release_type(2), ReleaseType::Beta);
|
|
||||||
assert_eq!(CurseForgePlatform::map_release_type(3), ReleaseType::Alpha);
|
|
||||||
assert_eq!(
|
|
||||||
CurseForgePlatform::map_release_type(99),
|
|
||||||
ReleaseType::Release
|
|
||||||
); // Unknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|
@ -20,9 +20,9 @@ pub struct GitHubPlatform {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GitHubPlatform {
|
impl GitHubPlatform {
|
||||||
pub fn with_client(client: Arc<Client>, token: Option<String>) -> Self {
|
pub fn new(token: Option<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
client: (*client).clone(),
|
client: Client::new(),
|
||||||
token,
|
token,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
|
@ -14,76 +14,16 @@ use crate::{
|
||||||
const MODRINTH_API_BASE: &str = "https://api.modrinth.com/v2";
|
const MODRINTH_API_BASE: &str = "https://api.modrinth.com/v2";
|
||||||
|
|
||||||
pub struct ModrinthPlatform {
|
pub struct ModrinthPlatform {
|
||||||
client: Arc<Client>,
|
client: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModrinthPlatform {
|
impl ModrinthPlatform {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
client: Arc::new(Client::new()),
|
client: Client::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_client(client: Arc<Client>) -> Self {
|
|
||||||
Self { client }
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn request_project_url(&self, url: &str) -> Result<Project> {
|
|
||||||
let response = self.client.get(url).send().await?;
|
|
||||||
if !response.status().is_success() {
|
|
||||||
return Err(PakkerError::ProjectNotFound(url.to_string()));
|
|
||||||
}
|
|
||||||
let mr_project: ModrinthProject = response.json().await?;
|
|
||||||
Ok(self.convert_project(mr_project))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn request_project_files_url(
|
|
||||||
&self,
|
|
||||||
url: &str,
|
|
||||||
) -> Result<Vec<ProjectFile>> {
|
|
||||||
let response = self.client.get(url).send().await?;
|
|
||||||
if !response.status().is_success() {
|
|
||||||
return Err(PakkerError::ProjectNotFound(url.to_string()));
|
|
||||||
}
|
|
||||||
let mr_versions: Vec<ModrinthVersion> = response.json().await?;
|
|
||||||
let project_id = url
|
|
||||||
.split('/')
|
|
||||||
.nth(4)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
PakkerError::InvalidResponse(
|
|
||||||
"Cannot parse project ID from URL".to_string(),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.to_string();
|
|
||||||
Ok(
|
|
||||||
mr_versions
|
|
||||||
.into_iter()
|
|
||||||
.map(|v| self.convert_version(v, &project_id))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn lookup_by_hash_url(&self, url: &str) -> Result<Option<Project>> {
|
|
||||||
let response = self.client.get(url).send().await?;
|
|
||||||
if response.status().as_u16() == 404 {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
if !response.status().is_success() {
|
|
||||||
return Err(PakkerError::PlatformApiError(format!(
|
|
||||||
"Modrinth API error: {}",
|
|
||||||
response.status()
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
let version_data: serde_json::Value = response.json().await?;
|
|
||||||
let project_id = version_data["project_id"].as_str().ok_or_else(|| {
|
|
||||||
PakkerError::InvalidResponse("Missing project_id".to_string())
|
|
||||||
})?;
|
|
||||||
self
|
|
||||||
.request_project_with_files(project_id, &[], &[])
|
|
||||||
.await
|
|
||||||
.map(Some)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn map_project_type(type_str: &str) -> ProjectType {
|
fn map_project_type(type_str: &str) -> ProjectType {
|
||||||
match type_str {
|
match type_str {
|
||||||
"mod" => ProjectType::Mod,
|
"mod" => ProjectType::Mod,
|
||||||
|
|
@ -183,7 +123,15 @@ impl PlatformClient for ModrinthPlatform {
|
||||||
_loaders: &[String],
|
_loaders: &[String],
|
||||||
) -> Result<Project> {
|
) -> Result<Project> {
|
||||||
let url = format!("{MODRINTH_API_BASE}/project/{identifier}");
|
let url = format!("{MODRINTH_API_BASE}/project/{identifier}");
|
||||||
self.request_project_url(&url).await
|
|
||||||
|
let response = self.client.get(&url).send().await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(PakkerError::ProjectNotFound(identifier.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mr_project: ModrinthProject = response.json().await?;
|
||||||
|
Ok(self.convert_project(mr_project))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn request_project_files(
|
async fn request_project_files(
|
||||||
|
|
@ -222,7 +170,20 @@ impl PlatformClient for ModrinthPlatform {
|
||||||
url.push_str(¶ms.join("&"));
|
url.push_str(¶ms.join("&"));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.request_project_files_url(&url).await
|
let response = self.client.get(&url).send().await?;
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(PakkerError::ProjectNotFound(project_id.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mr_versions: Vec<ModrinthVersion> = response.json().await?;
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
mr_versions
|
||||||
|
.into_iter()
|
||||||
|
.map(|v| self.convert_version(v, project_id))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn request_project_with_files(
|
async fn request_project_with_files(
|
||||||
|
|
@ -252,7 +213,30 @@ impl PlatformClient for ModrinthPlatform {
|
||||||
async fn lookup_by_hash(&self, hash: &str) -> Result<Option<Project>> {
|
async fn lookup_by_hash(&self, hash: &str) -> Result<Option<Project>> {
|
||||||
// Modrinth uses SHA-1 hash for file lookups
|
// Modrinth uses SHA-1 hash for file lookups
|
||||||
let url = format!("{MODRINTH_API_BASE}/version_file/{hash}");
|
let url = format!("{MODRINTH_API_BASE}/version_file/{hash}");
|
||||||
self.lookup_by_hash_url(&url).await
|
|
||||||
|
let response = self.client.get(&url).send().await?;
|
||||||
|
|
||||||
|
if response.status().as_u16() == 404 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return Err(PakkerError::PlatformApiError(format!(
|
||||||
|
"Modrinth API error: {}",
|
||||||
|
response.status()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let version_data: serde_json::Value = response.json().await?;
|
||||||
|
|
||||||
|
let project_id = version_data["project_id"].as_str().ok_or_else(|| {
|
||||||
|
PakkerError::InvalidResponse("Missing project_id".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
self
|
||||||
|
.request_project_with_files(project_id, &[], &[])
|
||||||
|
.await
|
||||||
|
.map(Some)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -296,128 +280,3 @@ struct ModrinthDependency {
|
||||||
project_id: Option<String>,
|
project_id: Option<String>,
|
||||||
dependency_type: String,
|
dependency_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use reqwest::Client;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
impl ModrinthPlatform {
|
|
||||||
fn with_raw_client(client: Client) -> Self {
|
|
||||||
Self {
|
|
||||||
client: Arc::new(client),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_platform_with_mock()
|
|
||||||
-> (ModrinthPlatform, mockito::ServerGuard) {
|
|
||||||
let server = mockito::Server::new_async().await;
|
|
||||||
let client = Client::new();
|
|
||||||
let platform = ModrinthPlatform::with_raw_client(client);
|
|
||||||
(platform, server)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_request_project_success() {
|
|
||||||
let (platform, mut server) = create_platform_with_mock().await;
|
|
||||||
let url = format!("{}/project/test-mod", server.url());
|
|
||||||
|
|
||||||
let _mock = server
|
|
||||||
.mock("GET", "/project/test-mod")
|
|
||||||
.with_status(200)
|
|
||||||
.with_header("content-type", "application/json")
|
|
||||||
.with_body(
|
|
||||||
r#"{
|
|
||||||
"id": "abc123",
|
|
||||||
"slug": "test-mod",
|
|
||||||
"title": "Test Mod",
|
|
||||||
"project_type": "mod",
|
|
||||||
"client_side": "required",
|
|
||||||
"server_side": "required"
|
|
||||||
}"#,
|
|
||||||
)
|
|
||||||
.create();
|
|
||||||
|
|
||||||
let result = platform.request_project_url(&url).await;
|
|
||||||
|
|
||||||
assert!(result.is_ok());
|
|
||||||
let project = result.unwrap();
|
|
||||||
assert!(project.get_platform_id("modrinth").is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_request_project_not_found() {
|
|
||||||
let (platform, mut server) = create_platform_with_mock().await;
|
|
||||||
let url = format!("{}/project/nonexistent", server.url());
|
|
||||||
|
|
||||||
let _mock = server
|
|
||||||
.mock("GET", "/project/nonexistent")
|
|
||||||
.with_status(404)
|
|
||||||
.create();
|
|
||||||
|
|
||||||
let result = platform.request_project_url(&url).await;
|
|
||||||
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_request_project_files() {
|
|
||||||
let (platform, mut server) = create_platform_with_mock().await;
|
|
||||||
let url = format!("{}/project/abc123/version", server.url());
|
|
||||||
|
|
||||||
let _mock = server
|
|
||||||
.mock("GET", "/project/abc123/version")
|
|
||||||
.with_status(200)
|
|
||||||
.with_header("content-type", "application/json")
|
|
||||||
.with_body(
|
|
||||||
r#"[
|
|
||||||
{
|
|
||||||
"id": "v1",
|
|
||||||
"project_id": "abc123",
|
|
||||||
"name": "Test Mod v1.0.0",
|
|
||||||
"version_number": "1.0.0",
|
|
||||||
"game_versions": ["1.20.1"],
|
|
||||||
"version_type": "release",
|
|
||||||
"loaders": ["fabric"],
|
|
||||||
"date_published": "2024-01-01T00:00:00Z",
|
|
||||||
"files": [{
|
|
||||||
"hashes": {"sha1": "abc123def456"},
|
|
||||||
"url": "https://example.com/mod.jar",
|
|
||||||
"filename": "test-mod-1.0.0.jar",
|
|
||||||
"primary": true,
|
|
||||||
"size": 1024
|
|
||||||
}],
|
|
||||||
"dependencies": []
|
|
||||||
}
|
|
||||||
]"#,
|
|
||||||
)
|
|
||||||
.create();
|
|
||||||
|
|
||||||
let result = platform.request_project_files_url(&url).await;
|
|
||||||
|
|
||||||
assert!(result.is_ok());
|
|
||||||
let files = result.unwrap();
|
|
||||||
assert_eq!(files.len(), 1);
|
|
||||||
assert_eq!(files[0].file_name, "test-mod-1.0.0.jar");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_lookup_by_hash_not_found() {
|
|
||||||
let (platform, mut server) = create_platform_with_mock().await;
|
|
||||||
let url = format!("{}/version_file/unknownhash123", server.url());
|
|
||||||
|
|
||||||
let _mock = server
|
|
||||||
.mock("GET", "/version_file/unknownhash123")
|
|
||||||
.with_status(404)
|
|
||||||
.create();
|
|
||||||
|
|
||||||
let result = platform.lookup_by_hash_url(&url).await;
|
|
||||||
|
|
||||||
assert!(result.is_ok());
|
|
||||||
assert!(result.unwrap().is_none());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -80,13 +80,13 @@ impl RateLimiter {
|
||||||
platform_requests
|
platform_requests
|
||||||
.retain(|t| now.duration_since(*t) < Duration::from_secs(60));
|
.retain(|t| now.duration_since(*t) < Duration::from_secs(60));
|
||||||
|
|
||||||
if platform_requests.len() >= burst as usize
|
if platform_requests.len() >= burst as usize {
|
||||||
&& let Some(oldest) = platform_requests.first()
|
if let Some(oldest) = platform_requests.first() {
|
||||||
{
|
let wait_time = interval.saturating_sub(now.duration_since(*oldest));
|
||||||
let wait_time = interval.saturating_sub(now.duration_since(*oldest));
|
if wait_time > Duration::ZERO {
|
||||||
if wait_time > Duration::ZERO {
|
drop(inner);
|
||||||
drop(inner);
|
tokio::time::sleep(wait_time).await;
|
||||||
tokio::time::sleep(wait_time).await;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use rand::RngExt;
|
use rand::Rng;
|
||||||
|
|
||||||
const CHARSET: &[u8] =
|
const CHARSET: &[u8] =
|
||||||
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue