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 = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cpufeatures 0.2.17",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -80,9 +80,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.101"
|
||||
version = "1.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
|
||||
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "assert-json-diff"
|
||||
|
|
@ -211,17 +211,6 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
|
|
@ -234,9 +223,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.58"
|
||||
version = "4.5.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806"
|
||||
checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
|
@ -244,9 +233,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.58"
|
||||
version = "4.5.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2"
|
||||
checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
|
@ -256,9 +245,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.55"
|
||||
version = "4.5.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5"
|
||||
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
|
|
@ -268,9 +257,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.0.0"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
|
||||
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
|
|
@ -332,9 +321,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.4.2"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
||||
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
|
|
@ -371,15 +360,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.3.0"
|
||||
|
|
@ -518,9 +498,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "1.0.0"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a1c3cc8e57274ec99de65301228b537f1e4eedc1b8e0f9411c6caac8ae7308f"
|
||||
checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
|
|
@ -528,9 +508,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.9"
|
||||
version = "0.11.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2daee4ea451f429a58296525ddf28b45a3b64f1acf6587e2067437bb11e218d"
|
||||
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
|
@ -584,12 +564,6 @@ version = "1.0.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
|
|
@ -731,27 +705,11 @@ dependencies = [
|
|||
"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]]
|
||||
name = "git2"
|
||||
version = "0.20.4"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b"
|
||||
checksum = "3e2b37e2f62729cdada11f0e6b3b6fe383c69c29fc619e391223e12856af308c"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"libc",
|
||||
|
|
@ -762,12 +720,6 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.13"
|
||||
|
|
@ -787,15 +739,6 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
|
|
@ -1008,12 +951,6 @@ dependencies = [
|
|||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
|
|
@ -1042,9 +979,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.16.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1173,12 +1108,6 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libbz2-rs-sys"
|
||||
version = "0.2.2"
|
||||
|
|
@ -1187,9 +1116,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.181"
|
||||
version = "0.2.180"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5"
|
||||
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
|
|
@ -1334,9 +1263,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "mockito"
|
||||
version = "1.7.2"
|
||||
version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90820618712cab19cfc46b274c6c22546a82affcb3c3bdf0f29e3db8e1bb92c0"
|
||||
checksum = "7e0603425789b4a70fcc4ac4f5a46a566c116ee3e2a6b768dc623f7719c611de"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"bytes",
|
||||
|
|
@ -1349,7 +1278,7 @@ dependencies = [
|
|||
"hyper-util",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"rand 0.9.2",
|
||||
"rand",
|
||||
"regex",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
|
|
@ -1359,9 +1288,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.2.0"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
|
|
@ -1411,17 +1340,16 @@ dependencies = [
|
|||
"env_logger",
|
||||
"futures",
|
||||
"git2",
|
||||
"glob",
|
||||
"indicatif",
|
||||
"keyring",
|
||||
"libc",
|
||||
"log",
|
||||
"md-5",
|
||||
"mockito",
|
||||
"rand 0.10.0",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"regex",
|
||||
"reqwest",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
|
|
@ -1429,7 +1357,7 @@ dependencies = [
|
|||
"strsim",
|
||||
"tempfile",
|
||||
"textwrap",
|
||||
"thiserror 2.0.18",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"walkdir",
|
||||
"yansi",
|
||||
|
|
@ -1525,9 +1453,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
|||
|
||||
[[package]]
|
||||
name = "ppmd-rust"
|
||||
version = "1.4.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24"
|
||||
checksum = "d558c559f0450f16f2a27a1f017ef38468c1090c9ce63c8e51366232d53717b4"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
|
|
@ -1538,16 +1466,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.105"
|
||||
|
|
@ -1571,7 +1489,7 @@ dependencies = [
|
|||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2",
|
||||
"thiserror 2.0.18",
|
||||
"thiserror 2.0.17",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"web-time",
|
||||
|
|
@ -1587,13 +1505,13 @@ dependencies = [
|
|||
"bytes",
|
||||
"getrandom 0.3.4",
|
||||
"lru-slab",
|
||||
"rand 0.9.2",
|
||||
"rand",
|
||||
"ring",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"slab",
|
||||
"thiserror 2.0.18",
|
||||
"thiserror 2.0.17",
|
||||
"tinyvec",
|
||||
"tracing",
|
||||
"web-time",
|
||||
|
|
@ -1635,18 +1553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[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",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1656,7 +1563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.3",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1668,12 +1575,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
|
|
@ -1685,9 +1586,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
version = "1.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
|
@ -1714,9 +1615,9 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
|||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.13.2"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
|
||||
checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
|
|
@ -1919,12 +1820,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
|
|
@ -1987,7 +1882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
|
|
@ -1998,7 +1893,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
|
|
@ -2136,12 +2031,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.25.0"
|
||||
version = "3.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1"
|
||||
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.4.1",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
|
|
@ -2169,11 +2064,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.18",
|
||||
"thiserror-impl 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2189,9 +2084,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.18"
|
||||
version = "2.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -2200,23 +2095,22 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.47"
|
||||
version = "0.3.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
|
||||
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"js-sys",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
"serde",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.8"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
|
|
@ -2364,12 +2258,6 @@ version = "0.2.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typed-path"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
|
|
@ -2400,12 +2288,6 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "unit-prefix"
|
||||
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"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
dependencies = [
|
||||
"wit-bindgen 0.46.0",
|
||||
]
|
||||
|
||||
[[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",
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2555,40 +2428,6 @@ dependencies = [
|
|||
"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]]
|
||||
name = "web-sys"
|
||||
version = "0.3.83"
|
||||
|
|
@ -2921,94 +2760,6 @@ version = "0.46.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "writeable"
|
||||
version = "0.6.2"
|
||||
|
|
@ -3140,9 +2891,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "7.4.0"
|
||||
version = "7.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc12baa6db2b15a140161ce53d72209dacea594230798c24774139b54ecaa980"
|
||||
checksum = "9013f1222db8a6d680f13a7ccdc60a781199cd09c2fa4eff58e728bb181757fc"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bzip2",
|
||||
|
|
@ -3150,7 +2901,8 @@ dependencies = [
|
|||
"crc32fast",
|
||||
"deflate64",
|
||||
"flate2",
|
||||
"getrandom 0.4.1",
|
||||
"generic-array",
|
||||
"getrandom 0.3.4",
|
||||
"hmac",
|
||||
"indexmap",
|
||||
"lzma-rust2",
|
||||
|
|
@ -3159,7 +2911,6 @@ dependencies = [
|
|||
"ppmd-rust",
|
||||
"sha1",
|
||||
"time",
|
||||
"typed-path",
|
||||
"zeroize",
|
||||
"zopfli",
|
||||
"zstd",
|
||||
|
|
|
|||
44
Cargo.toml
44
Cargo.toml
|
|
@ -2,46 +2,46 @@
|
|||
name = "pakker"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
authors = [ "NotAShelf <raf@notashelf.dev>" ]
|
||||
rust-version = "1.91.0"
|
||||
readme = true
|
||||
|
||||
authors = [ "NotAShelf <raf@notashelf.dev" ]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.101"
|
||||
anyhow = "1.0.100"
|
||||
async-trait = "0.1.89"
|
||||
clap = { version = "4.5.58", features = [ "derive" ] }
|
||||
comfy-table = "7.2.2"
|
||||
clap = { version = "4.5.54", features = [ "derive" ] }
|
||||
comfy-table = "7.1"
|
||||
dialoguer = "0.12.0"
|
||||
env_logger = "0.11.9"
|
||||
env_logger = "0.11.8"
|
||||
futures = "0.3.31"
|
||||
git2 = "0.20.4"
|
||||
glob = "0.3.3"
|
||||
git2 = "0.20.3"
|
||||
indicatif = "0.18.3"
|
||||
keyring = "3.6.3"
|
||||
libc = "0.2.181"
|
||||
libc = "0.2.180"
|
||||
log = "0.4.29"
|
||||
md-5 = "0.10.6"
|
||||
rand = "0.10.0"
|
||||
regex = "1.12.3"
|
||||
reqwest = { version = "0.13.2", features = [ "json" ] }
|
||||
semver = "1.0.27"
|
||||
once_cell = "1.20"
|
||||
rand = "0.9.2"
|
||||
regex = "1.12"
|
||||
reqwest = { version = "0.13.1", features = [ "json" ] }
|
||||
serde = { version = "1.0.228", features = [ "derive" ] }
|
||||
serde_json = "1.0.149"
|
||||
sha1 = "0.10.6"
|
||||
sha2 = "0.10.9"
|
||||
sha2 = "0.10.0"
|
||||
strsim = "0.11.1"
|
||||
tempfile = "3.25.0"
|
||||
textwrap = "0.16.2"
|
||||
thiserror = "2.0.18"
|
||||
tempfile = "3.24.0"
|
||||
textwrap = "0.16"
|
||||
thiserror = "2.0.17"
|
||||
tokio = { version = "1.49.0", features = [ "full" ] }
|
||||
walkdir = "2.5.0"
|
||||
yansi = "1.0.1"
|
||||
zip = "7.4.0"
|
||||
zip = "7.1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
mockito = "1.7.2"
|
||||
tempfile = "3.25.0"
|
||||
mockito = "1.7.1"
|
||||
tempfile = "3.24.0"
|
||||
|
||||
[[bin]]
|
||||
name = "pakker"
|
||||
path = "src/main.rs"
|
||||
|
||||
# Optimize crypto stuff. Building them with optimizations makes that build script
|
||||
# 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>,
|
||||
|
||||
/// Target platform
|
||||
#[clap(short, long)]
|
||||
pub target: Option<String>,
|
||||
#[clap(short, long, default_value = "multiplatform")]
|
||||
pub target: String,
|
||||
|
||||
/// Minecraft versions (space-separated)
|
||||
#[clap(short, long = "mc-versions", value_delimiter = ' ', num_args = 1..)]
|
||||
pub mc_versions: Option<Vec<String>>,
|
||||
/// Minecraft version
|
||||
#[clap(short, long, default_value = "1.20.1")]
|
||||
pub mc_version: String,
|
||||
|
||||
/// Mod loaders (format: name=version, can be specified multiple times)
|
||||
#[clap(short, long = "loaders", value_delimiter = ',')]
|
||||
pub loaders: Option<Vec<String>>,
|
||||
/// Mod loader
|
||||
#[clap(short, long, default_value = "fabric")]
|
||||
pub loader: String,
|
||||
|
||||
/// Skip interactive prompts (use defaults)
|
||||
#[clap(short, long)]
|
||||
pub yes: bool,
|
||||
/// Mod loader version
|
||||
#[clap(short = 'v', long, default_value = "latest")]
|
||||
pub loader_version: String,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
@ -214,10 +214,6 @@ pub struct RmArgs {
|
|||
/// Skip confirmation prompt
|
||||
#[clap(short, long)]
|
||||
pub yes: bool,
|
||||
|
||||
/// Skip removing dependent projects
|
||||
#[clap(short = 'D', long = "no-deps")]
|
||||
pub no_deps: bool,
|
||||
}
|
||||
|
||||
#[derive(Args)]
|
||||
|
|
@ -226,10 +222,6 @@ pub struct UpdateArgs {
|
|||
#[arg(value_name = "PROJECT")]
|
||||
pub inputs: Vec<String>,
|
||||
|
||||
/// Update all projects
|
||||
#[arg(short, long)]
|
||||
pub all: bool,
|
||||
|
||||
/// Skip confirmation prompts
|
||||
#[arg(short, long)]
|
||||
pub yes: bool,
|
||||
|
|
@ -352,7 +344,7 @@ pub struct SyncArgs {
|
|||
#[clap(short = 'R', long)]
|
||||
pub removals: bool,
|
||||
|
||||
/// Sync updates only (apply pending updates)
|
||||
/// Sync updates only
|
||||
#[clap(short = 'U', long)]
|
||||
pub updates: bool,
|
||||
}
|
||||
|
|
@ -379,7 +371,7 @@ pub struct ExportArgs {
|
|||
|
||||
/// Export modpack without server content
|
||||
/// Modrinth: exclude server-overrides and SERVER mods
|
||||
/// `ServerPack`: skip export
|
||||
/// ServerPack: skip export
|
||||
#[clap(long = "no-server")]
|
||||
pub no_server: bool,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ pub async fn execute(
|
|||
let config_dir = config_path.parent().unwrap_or(Path::new("."));
|
||||
|
||||
// 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);
|
||||
|
||||
// Check for conflicting export operations
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ pub async fn execute(
|
|||
let operation_id = coordinator.register_operation(OperationType::Fetch)?;
|
||||
let _guard = OperationGuard::new(coordinator, operation_id);
|
||||
|
||||
// Create fetcher with shelve option
|
||||
let fetcher = Fetcher::new(".").with_shelve(args.shelve);
|
||||
// Create fetcher
|
||||
let fetcher = Fetcher::new(".");
|
||||
|
||||
// Fetch all projects (progress indicators handled in fetch.rs)
|
||||
fetcher.fetch_all(&lockfile, &config).await?;
|
||||
|
|
|
|||
|
|
@ -211,13 +211,14 @@ fn execute_init(
|
|||
.args(["log", "--limit", "1", "--template", ""])
|
||||
.current_dir(path)
|
||||
.output()
|
||||
&& !output.stdout.is_empty()
|
||||
{
|
||||
if !output.stdout.is_empty() {
|
||||
println!(
|
||||
"Note: Jujutsu repository detected. Make sure to run 'jj git \
|
||||
push' to sync changes with remote if needed."
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
VcsType::None => {
|
||||
// No VCS-specific validation needed
|
||||
|
|
|
|||
|
|
@ -134,19 +134,16 @@ async fn import_modrinth(
|
|||
{
|
||||
log::info!("Fetching project: {project_id}");
|
||||
match platform
|
||||
.request_project_with_files(
|
||||
project_id,
|
||||
&lockfile.mc_versions,
|
||||
std::slice::from_ref(&loader.0),
|
||||
)
|
||||
.request_project_with_files(project_id, &lockfile.mc_versions, &[
|
||||
loader.0.clone(),
|
||||
])
|
||||
.await
|
||||
{
|
||||
Ok(mut project) => {
|
||||
// Select best file
|
||||
if let Err(e) = project.select_file(
|
||||
&lockfile.mc_versions,
|
||||
std::slice::from_ref(&loader.0),
|
||||
) {
|
||||
if let Err(e) =
|
||||
project.select_file(&lockfile.mc_versions, &[loader.0.clone()])
|
||||
{
|
||||
log::warn!(
|
||||
"Failed to select file for {}: {}",
|
||||
project.get_name(),
|
||||
|
|
@ -360,7 +357,7 @@ async fn import_curseforge(
|
|||
description: None,
|
||||
author: manifest["author"]
|
||||
.as_str()
|
||||
.map(std::string::ToString::to_string),
|
||||
.map(|s| s.to_string()),
|
||||
overrides: vec!["overrides".to_string()],
|
||||
server_overrides: None,
|
||||
client_overrides: None,
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@ fn display_project_inspection(
|
|||
|
||||
// Display project files
|
||||
println!();
|
||||
display_project_files(&project.files, project)?;
|
||||
display_project_files(&project.files)?;
|
||||
|
||||
// Display properties
|
||||
println!();
|
||||
|
|
@ -228,10 +228,7 @@ fn display_project_header(project: &Project) -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn display_project_files(
|
||||
files: &[ProjectFile],
|
||||
project: &Project,
|
||||
) -> Result<()> {
|
||||
fn display_project_files(files: &[ProjectFile]) -> Result<()> {
|
||||
if files.is_empty() {
|
||||
println!("{}", "No files available".yellow());
|
||||
return Ok(());
|
||||
|
|
@ -253,31 +250,19 @@ fn display_project_files(
|
|||
format!(" {status}")
|
||||
};
|
||||
|
||||
// File path line with optional site URL
|
||||
// File path line
|
||||
let file_path = format!("{}={}", file.file_type, file.file_name);
|
||||
let file_display = if let Some(site_url) = file.get_site_url(project) {
|
||||
// Create hyperlink for the file
|
||||
let hyperlink = crate::ui_utils::hyperlink(&site_url, &file_path);
|
||||
format!("{hyperlink}:{status_text}")
|
||||
} else {
|
||||
format!("{file_path}:{status_text}")
|
||||
};
|
||||
|
||||
table.add_row(vec![Cell::new(file_display).fg(if idx == 0 {
|
||||
table.add_row(vec![
|
||||
Cell::new(format!("{file_path}:{status_text}")).fg(if idx == 0 {
|
||||
Color::Green
|
||||
} else {
|
||||
Color::White
|
||||
})]);
|
||||
}),
|
||||
]);
|
||||
|
||||
// Date published
|
||||
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
|
||||
table.add_row(vec![Cell::new("")]);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,18 +2,6 @@ use std::path::Path;
|
|||
|
||||
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<()> {
|
||||
// Load expects directory path, so get parent directory
|
||||
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!();
|
||||
|
||||
// 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 {
|
||||
// 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 {
|
||||
let id = project.pakku_id.as_deref().unwrap_or("unknown");
|
||||
let name = truncate_name(&project.get_name(), max_name_len);
|
||||
println!(" {name} ({id}){version_warning}");
|
||||
println!(" {} ({})", project.get_name(), id);
|
||||
println!(" Type: {:?}", project.r#type);
|
||||
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() {
|
||||
println!(" Dependencies: {}", project.pakku_links.len());
|
||||
}
|
||||
|
||||
println!();
|
||||
} else {
|
||||
let name = truncate_name(&project.get_name(), max_name_len);
|
||||
let file_info = project
|
||||
.files
|
||||
.first()
|
||||
.map(|f| format!(" ({})", f.file_name))
|
||||
.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};
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ pub async fn execute(args: RemoteUpdateArgs) -> Result<(), PakkerError> {
|
|||
}
|
||||
|
||||
/// 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");
|
||||
if !remote_config_path.exists() {
|
||||
return Ok(());
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use tokio::sync::Semaphore;
|
|||
use yansi::Paint;
|
||||
|
||||
use crate::{
|
||||
error::{ErrorSeverity, Result},
|
||||
error::Result,
|
||||
model::{Config, LockFile, Project},
|
||||
platform::create_platform,
|
||||
};
|
||||
|
|
@ -36,44 +36,15 @@ pub async fn execute(
|
|||
// Display results
|
||||
display_update_results(&updates);
|
||||
|
||||
// Display errors if any, categorized by severity
|
||||
// Display errors if any
|
||||
if !errors.is_empty() {
|
||||
println!();
|
||||
|
||||
// Categorize errors by severity
|
||||
let (warnings, errors_only): (Vec<_>, Vec<_>) =
|
||||
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!("{}", "Errors encountered:".red());
|
||||
for (project, error) in &errors {
|
||||
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
|
||||
if !updates.is_empty() {
|
||||
println!();
|
||||
|
|
@ -81,7 +52,6 @@ pub async fn execute(
|
|||
// Call update command programmatically (update all projects)
|
||||
let update_args = crate::cli::UpdateArgs {
|
||||
inputs: vec![],
|
||||
all: true,
|
||||
yes: true, // Auto-yes for status command
|
||||
};
|
||||
crate::cli::commands::update::execute(
|
||||
|
|
@ -398,12 +368,3 @@ fn get_api_key(platform: &str) -> Option<String> {
|
|||
_ => 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::{
|
||||
cli::UpdateArgs,
|
||||
error::{MultiError, PakkerError},
|
||||
model::{Config, LockFile, UpdateStrategy},
|
||||
error::PakkerError,
|
||||
model::{Config, LockFile},
|
||||
platform::create_platform,
|
||||
ui_utils::{prompt_select, prompt_typo_suggestion, prompt_yes_no},
|
||||
ui_utils::prompt_select,
|
||||
};
|
||||
|
||||
pub async fn execute(
|
||||
|
|
@ -33,22 +33,6 @@ pub async fn execute(
|
|||
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() {
|
||||
(0..lockfile.projects.len()).collect()
|
||||
} else {
|
||||
|
|
@ -62,29 +46,14 @@ pub async fn execute(
|
|||
{
|
||||
indices.push(idx);
|
||||
} 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()));
|
||||
}
|
||||
}
|
||||
indices
|
||||
};
|
||||
|
||||
// Capture count before consuming the iterator
|
||||
let total_projects = project_indices.len();
|
||||
|
||||
// Create progress bar
|
||||
let pb = ProgressBar::new(total_projects as u64);
|
||||
let pb = ProgressBar::new(project_indices.len() as u64);
|
||||
pb.set_style(
|
||||
ProgressStyle::default_bar()
|
||||
.template("{spinner:.green} [{bar:40.cyan/blue}] {pos}/{len} {msg}")
|
||||
|
|
@ -92,23 +61,8 @@ pub async fn execute(
|
|||
.progress_chars("#>-"),
|
||||
);
|
||||
|
||||
let mut skipped_pinned = 0;
|
||||
let mut update_errors = MultiError::new();
|
||||
|
||||
for idx in project_indices {
|
||||
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()));
|
||||
|
||||
let slug = old_project
|
||||
|
|
@ -133,46 +87,21 @@ 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
|
||||
&& !updated_project.files.is_empty()
|
||||
&& let Some(old_file) = lockfile.projects[idx].files.first()
|
||||
{
|
||||
// Clone data needed for comparisons to avoid borrow issues
|
||||
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();
|
||||
let new_file = updated_project.files.first().unwrap();
|
||||
|
||||
if new_file_id == old_file.id {
|
||||
pb.println(format!(" {project_name} - Already up to date"));
|
||||
if new_file.id == old_file.id {
|
||||
pb.println(format!(
|
||||
" {} - Already up to date",
|
||||
old_project.get_name()
|
||||
));
|
||||
} else {
|
||||
// Interactive confirmation and version selection if not using --yes
|
||||
// flag
|
||||
let mut should_update = args.yes || args.all;
|
||||
let mut selected_idx: Option<usize> = None;
|
||||
|
||||
if !args.yes && !args.all {
|
||||
// Interactive version selection if not using --yes flag
|
||||
if !args.yes && updated_project.files.len() > 1 {
|
||||
pb.suspend(|| {
|
||||
// First, confirm the update
|
||||
let prompt_msg = format!(
|
||||
"Update '{project_name}' from {old_file_name} to \
|
||||
{new_file_name}?"
|
||||
);
|
||||
should_update = prompt_yes_no(&prompt_msg, true).unwrap_or(false);
|
||||
|
||||
// If confirmed and multiple versions available, offer selection
|
||||
if should_update && updated_project.files.len() > 1 {
|
||||
let choices: Vec<String> = updated_project
|
||||
.files
|
||||
.iter()
|
||||
|
|
@ -182,67 +111,30 @@ pub async fn execute(
|
|||
let choice_refs: Vec<&str> =
|
||||
choices.iter().map(std::string::String::as_str).collect();
|
||||
|
||||
if let Ok(idx) = prompt_select(
|
||||
&format!("Select version for {project_name}:"),
|
||||
if let Ok(selected_idx) = prompt_select(
|
||||
&format!("Select version for {}:", old_project.get_name()),
|
||||
&choice_refs,
|
||||
) {
|
||||
selected_idx = Some(idx);
|
||||
// Move selected file to front
|
||||
if selected_idx > 0 {
|
||||
updated_project.files.swap(0, selected_idx);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Apply file selection outside the closure
|
||||
if let Some(idx) = selected_idx
|
||||
&& idx > 0
|
||||
{
|
||||
updated_project.files.swap(0, idx);
|
||||
}
|
||||
|
||||
if should_update {
|
||||
let selected_file = updated_project.files.first().unwrap();
|
||||
pb.println(format!(
|
||||
" {} -> {}",
|
||||
old_file_name, selected_file.file_name
|
||||
old_file.file_name, selected_file.file_name
|
||||
));
|
||||
lockfile.projects[idx] = updated_project;
|
||||
} else {
|
||||
pb.println(format!(" {project_name} - Skipped by user"));
|
||||
}
|
||||
}
|
||||
}
|
||||
pb.inc(1);
|
||||
}
|
||||
|
||||
if skipped_pinned > 0 {
|
||||
pb.finish_with_message(format!(
|
||||
"Update complete ({skipped_pinned} pinned projects skipped)"
|
||||
));
|
||||
} else {
|
||||
pb.finish_with_message("Update complete");
|
||||
}
|
||||
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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -288,20 +288,22 @@ pub fn detect_vcs_type<P: AsRef<Path>>(path: P) -> VcsType {
|
|||
.args(["root"])
|
||||
.current_dir(path)
|
||||
.output()
|
||||
&& output.status.success()
|
||||
{
|
||||
if output.status.success() {
|
||||
return VcsType::Jujutsu;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for git
|
||||
if let Ok(output) = std::process::Command::new("git")
|
||||
.args(["rev-parse", "--show-toplevel"])
|
||||
.current_dir(path)
|
||||
.output()
|
||||
&& output.status.success()
|
||||
{
|
||||
if output.status.success() {
|
||||
return VcsType::Git;
|
||||
}
|
||||
}
|
||||
|
||||
VcsType::None
|
||||
}
|
||||
|
|
@ -331,7 +333,7 @@ pub fn repo_has_uncommitted_changes<P: AsRef<Path>>(path: P) -> Result<bool> {
|
|||
.current_dir(path)
|
||||
.output()
|
||||
.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);
|
||||
|
|
|
|||
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 error;
|
||||
mod export;
|
||||
mod fetch;
|
||||
mod git;
|
||||
mod http;
|
||||
mod ipc;
|
||||
mod model;
|
||||
mod platform;
|
||||
|
|
@ -24,6 +17,8 @@ use clap::Parser;
|
|||
use cli::{Cli, Commands};
|
||||
use error::PakkerError;
|
||||
|
||||
use crate::rate_limiter::RateLimiter;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), PakkerError> {
|
||||
let cli = Cli::parse();
|
||||
|
|
@ -47,6 +42,8 @@ async fn main() -> Result<(), PakkerError> {
|
|||
let lockfile_path = working_dir.join("pakker-lock.json");
|
||||
let config_path = working_dir.join("pakker.json");
|
||||
|
||||
let _rate_limiter = std::sync::Arc::new(RateLimiter::new(None));
|
||||
|
||||
match cli.command {
|
||||
Commands::Init(args) => {
|
||||
cli::commands::init::execute(args, &lockfile_path, &config_path).await
|
||||
|
|
|
|||
|
|
@ -192,29 +192,15 @@ impl Project {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Compare semantic versions extracted from file names
|
||||
let parse_version = |name: &str| {
|
||||
// Try to extract version from patterns like "mod-1.0.0.jar" or
|
||||
// "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
|
||||
// Check if all providers have the same latest file name
|
||||
// (simplified check - in reality would compare semantic versions)
|
||||
let file_names: Vec<_> = versions_by_provider
|
||||
.values()
|
||||
.filter_map(|files| files.first().copied().and_then(parse_version))
|
||||
.filter_map(|files| files.first().copied())
|
||||
.collect();
|
||||
|
||||
// All versions should be the same
|
||||
versions.windows(2).all(|w| w[0] == w[1])
|
||||
// All file names should be the same for versions to match
|
||||
file_names.windows(2).all(|w| w[0] == w[1])
|
||||
}
|
||||
|
||||
/// Check if versions do NOT match across providers.
|
||||
|
|
|
|||
|
|
@ -8,19 +8,13 @@ use std::sync::Arc;
|
|||
pub use curseforge::CurseForgePlatform;
|
||||
pub use github::GitHubPlatform;
|
||||
pub use modrinth::ModrinthPlatform;
|
||||
use once_cell::sync::Lazy;
|
||||
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>> =
|
||||
std::sync::LazyLock::new(|| Arc::new(http::create_http_client()));
|
||||
|
||||
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()
|
||||
}
|
||||
static RATE_LIMITER: Lazy<Arc<RateLimiter>> =
|
||||
Lazy::new(|| Arc::new(RateLimiter::new(None)));
|
||||
|
||||
pub fn create_platform(
|
||||
platform: &str,
|
||||
|
|
@ -40,21 +34,9 @@ fn create_client(
|
|||
api_key: Option<String>,
|
||||
) -> Result<Box<dyn PlatformClient>> {
|
||||
match platform {
|
||||
"modrinth" => {
|
||||
Ok(Box::new(ModrinthPlatform::with_client(get_http_client())))
|
||||
},
|
||||
"curseforge" => {
|
||||
Ok(Box::new(CurseForgePlatform::with_client(
|
||||
get_http_client(),
|
||||
api_key,
|
||||
)))
|
||||
},
|
||||
"github" => {
|
||||
Ok(Box::new(GitHubPlatform::with_client(
|
||||
get_http_client(),
|
||||
api_key,
|
||||
)))
|
||||
},
|
||||
"modrinth" => Ok(Box::new(ModrinthPlatform::new())),
|
||||
"curseforge" => Ok(Box::new(CurseForgePlatform::new(api_key))),
|
||||
"github" => Ok(Box::new(GitHubPlatform::new(api_key))),
|
||||
_ => {
|
||||
Err(crate::error::PakkerError::ConfigError(format!(
|
||||
"Unknown platform: {platform}"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use reqwest::Client;
|
||||
|
|
@ -12,30 +12,21 @@ use crate::{
|
|||
};
|
||||
|
||||
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;
|
||||
/// CurseForge relation type ID for "required dependency" (mod embeds or
|
||||
/// requires another mod)
|
||||
const DEPENDENCY_RELATION_TYPE_REQUIRED: u32 = 3;
|
||||
|
||||
pub struct CurseForgePlatform {
|
||||
client: Arc<Client>,
|
||||
client: Client,
|
||||
api_key: Option<String>,
|
||||
}
|
||||
|
||||
impl CurseForgePlatform {
|
||||
pub fn new(api_key: Option<String>) -> Self {
|
||||
Self {
|
||||
client: Arc::new(Client::new()),
|
||||
client: Client::new(),
|
||||
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> {
|
||||
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 {
|
||||
let pakku_id = generate_pakku_id();
|
||||
let project_type = Self::map_class_id(cf_project.class_id.unwrap_or(6));
|
||||
|
||||
// Detect side from categories
|
||||
let side = Self::detect_side_from_categories(&cf_project.categories);
|
||||
|
||||
let mut project = Project::new(pakku_id, project_type, side);
|
||||
let mut project = Project::new(pakku_id, project_type, ProjectSide::Both);
|
||||
|
||||
project.add_platform(
|
||||
"curseforge".to_string(),
|
||||
|
|
@ -203,7 +124,7 @@ impl CurseForgePlatform {
|
|||
required_dependencies: cf_file
|
||||
.dependencies
|
||||
.iter()
|
||||
.filter(|d| d.relation_type == DEPENDENCY_RELATION_TYPE_REQUIRED)
|
||||
.filter(|d| d.relation_type == 3)
|
||||
.map(|d| d.mod_id.to_string())
|
||||
.collect(),
|
||||
size: cf_file.file_length,
|
||||
|
|
@ -401,15 +322,6 @@ struct CurseForgeProject {
|
|||
slug: String,
|
||||
#[serde(rename = "classId")]
|
||||
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)]
|
||||
|
|
@ -469,112 +381,3 @@ struct CurseForgeFilesResponse {
|
|||
struct CurseForgeSearchResponse {
|
||||
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 regex::Regex;
|
||||
|
|
@ -20,9 +20,9 @@ pub struct GitHubPlatform {
|
|||
}
|
||||
|
||||
impl GitHubPlatform {
|
||||
pub fn with_client(client: Arc<Client>, token: Option<String>) -> Self {
|
||||
pub fn new(token: Option<String>) -> Self {
|
||||
Self {
|
||||
client: (*client).clone(),
|
||||
client: Client::new(),
|
||||
token,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use reqwest::Client;
|
||||
|
|
@ -14,76 +14,16 @@ use crate::{
|
|||
const MODRINTH_API_BASE: &str = "https://api.modrinth.com/v2";
|
||||
|
||||
pub struct ModrinthPlatform {
|
||||
client: Arc<Client>,
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl ModrinthPlatform {
|
||||
pub fn new() -> 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 {
|
||||
match type_str {
|
||||
"mod" => ProjectType::Mod,
|
||||
|
|
@ -183,7 +123,15 @@ impl PlatformClient for ModrinthPlatform {
|
|||
_loaders: &[String],
|
||||
) -> Result<Project> {
|
||||
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(
|
||||
|
|
@ -222,7 +170,20 @@ impl PlatformClient for ModrinthPlatform {
|
|||
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(
|
||||
|
|
@ -252,7 +213,30 @@ impl PlatformClient for ModrinthPlatform {
|
|||
async fn lookup_by_hash(&self, hash: &str) -> Result<Option<Project>> {
|
||||
// Modrinth uses SHA-1 hash for file lookups
|
||||
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>,
|
||||
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,15 +80,15 @@ impl RateLimiter {
|
|||
platform_requests
|
||||
.retain(|t| now.duration_since(*t) < Duration::from_secs(60));
|
||||
|
||||
if platform_requests.len() >= burst as usize
|
||||
&& let Some(oldest) = platform_requests.first()
|
||||
{
|
||||
if platform_requests.len() >= burst as usize {
|
||||
if let Some(oldest) = platform_requests.first() {
|
||||
let wait_time = interval.saturating_sub(now.duration_since(*oldest));
|
||||
if wait_time > Duration::ZERO {
|
||||
drop(inner);
|
||||
tokio::time::sleep(wait_time).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut inner = self.inner.lock().await;
|
||||
let platform_requests =
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use rand::RngExt;
|
||||
use rand::Rng;
|
||||
|
||||
const CHARSET: &[u8] =
|
||||
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue