diff --git a/CHANGELOG.md b/CHANGELOG.md index 6135bfdb0..649bb5e4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,51 @@ - [Rust](https://github.com/moonrepo/plugins/blob/master/tools/rust/CHANGELOG.md) - [Schema (TOML, JSON, YAML)](https://github.com/moonrepo/plugins/blob/master/tools/internal-schema/CHANGELOG.md) +## Unreleased + +#### 💥 Breaking + +- WASM API + - Removed deprecated `fetch`, `fetch_url`, `fetch_url_bytes`, `fetch_url_text`, and `fetch_url_with_cache` helper functions. Use the newer fetch functions instead. + +#### 🚀 Updates + +- Added `$XDG_DATA_HOME` support when detecting the proto store. Will be used if `$PROTO_HOME` is not set, and will fallback to `$HOME/.proto`. +- Added `settings.build` configuration, allowing aspects of the build from source process to be customized. +- Updated `proto install` to log checkpoints instead of rendering progress bars in a non-TTY environment. +- Updated `proto install` to support build from source when installing multiple tools. + - If the build fails, a markdown compatible log file will be written to the current directory. +- Disabled HTTP request caching when in a Docker container. +- Improved our errors implementation, rewriting some error messages, and updating error codes. +- Started on a new "backend" plugin system, allowing third-party tools to be used as proto plugins. + - For example, asdf on Unix, and scoop on Windows. This will land in the next release. +- WASM API + - Added `BuildInstruction::RemoveAllExcept` variant. + - Added `register_backend` plugin function. + - Added `fetch`, `exec`, `exec_captured`, `exec_streamed`, `get_host_env_var`, `set_host_env_var`, `add_host_paths`, `into_real_path`, and `into_virtual_path` helper functions. + - Added `generate_build_install_tests!` test macro. + - Renamed `ToolMetadataInput` to `RegisterToolInput`. + - Renamed `ToolMetadataOutput` to `RegisterToolOutput`. + +#### 🧩 Plugins + +- Updated `bun_tool` to v0.15. +- Updated `deno_tool` to v0.15. +- Updated `go_tool` to v0.16. +- Updated `moon_tool` v0.3. +- Updated `node_tool` to v0.16. +- Updated `node_depman_tool` to v0.15. +- Updated `python_tool` to v0.14. +- Updated `python_uv_tool` v0.2. +- Updated `ruby_tool` v0.2. +- Updated `rust_tool` to v0.13. +- Updated `schema_tool` to v0.17. + +#### ⚙️ Internal + +- Updated dependencies. +- Updated wasmtime to v29 (from v26). + ## 0.45.2 #### 🚀 Updates diff --git a/Cargo.lock b/Cargo.lock index 937cb6da0..b29aea4cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "ambient-authority" version = "0.0.2" @@ -278,9 +284,12 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +dependencies = [ + "allocator-api2", +] [[package]] name = "bytemuck" @@ -296,9 +305,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "bzip2" @@ -312,9 +321,9 @@ dependencies = [ [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.12+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "72ebc2f1a417f01e1da30ef264ee86ae31d2dcd2d603ea283d3c244a883ca2a9" dependencies = [ "cc", "libc", @@ -424,9 +433,9 @@ dependencies = [ [[package]] name = "cbindgen" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" +checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" dependencies = [ "heck 0.4.1", "indexmap", @@ -442,15 +451,21 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.10" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" +checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" dependencies = [ "jobserver", "libc", "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.0" @@ -479,9 +494,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.28" +version = "4.5.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff" +checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184" dependencies = [ "clap_builder", "clap_derive", @@ -489,9 +504,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.27" +version = "4.5.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" +checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9" dependencies = [ "anstream", "anstyle", @@ -554,6 +569,16 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "compact_str" version = "0.8.1" @@ -647,27 +672,27 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "cranelift-bforest" -version = "0.113.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "540b193ff98b825a1f250a75b3118911af918a734154c69d80bcfcf91e7e9522" +checksum = "e15d04a0ce86cb36ead88ad68cf693ffd6cda47052b9e0ac114bc47fd9cd23c4" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-bitset" -version = "0.113.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7cb269598b9557ab942d687d3c1086d77c4b50dcf35813f3a65ba306fd42279" +checksum = "7c6e3969a7ce267259ce244b7867c5d3bc9e65b0a87e81039588dfdeaede9f34" dependencies = [ "serde", "serde_derive", @@ -675,9 +700,9 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.113.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46566d7c83a8bff4150748d66020f4c7224091952aa4b4df1ec4959c39d937a1" +checksum = "2c22032c4cb42558371cf516bb47f26cdad1819d3475c133e93c49f50ebf304e" dependencies = [ "bumpalo", "cranelift-bforest", @@ -692,39 +717,40 @@ dependencies = [ "log", "regalloc2", "rustc-hash", + "serde", "smallvec", "target-lexicon", ] [[package]] name = "cranelift-codegen-meta" -version = "0.113.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2df8a86a34236cc75a8a6a271973da779c2aeb36c43b6e14da474cf931317082" +checksum = "c904bc71c61b27fc57827f4a1379f29de64fe95653b620a3db77d59655eee0b8" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.113.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf75340b6a57b7c7c1b74f10d3d90883ee6d43a554be8131a4046c2ebcf5eb65" +checksum = "40180f5497572f644ce88c255480981ae2ec1d7bb4d8e0c0136a13b87a2f2ceb" [[package]] name = "cranelift-control" -version = "0.113.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e84495bc5d23d86aad8c86f8ade4af765b94882af60d60e271d3153942f1978" +checksum = "26d132c6d0bd8a489563472afc171759da0707804a65ece7ceb15a8c6d7dd5ef" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.113.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963c17147b80df351965e57c04d20dbedc85bcaf44c3436780a59a3f1ff1b1c2" +checksum = "4b2d0d9618275474fbf679dd018ac6e009acbd6ae6850f6a67be33fb3b00b323" dependencies = [ "cranelift-bitset", "serde", @@ -733,9 +759,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.113.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727f02acbc4b4cb2ba38a6637101d579db50190df1dd05168c68e762851a3dd5" +checksum = "4fac41e16729107393174b0c9e3730fb072866100e1e64e80a1a963b2e484d57" dependencies = [ "cranelift-codegen", "log", @@ -745,15 +771,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.113.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b00cc2e03c748f2531eea01c871f502b909d30295fdcad43aec7bf5c5b4667" +checksum = "1ca20d576e5070044d0a72a9effc2deacf4d6aa650403189d8ea50126483944d" [[package]] name = "cranelift-native" -version = "0.113.1" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbeaf978dc7c1a2de8bbb9162510ed218eb156697bc45590b8fbdd69bb08e8de" +checksum = "b8dee82f3f1f2c4cba9177f1cc5e350fe98764379bcd29340caa7b01f85076c7" dependencies = [ "cranelift-codegen", "libc", @@ -1033,9 +1059,9 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dyn-clone" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" [[package]] name = "either" @@ -1088,9 +1114,9 @@ dependencies = [ [[package]] name = "extism" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f03e2cac5668dead4088aa9da25c9985f1a1b72edd3e31b201d2c044647b56f2" +checksum = "b06c5ae419bb79082d6ad22fc907717b4a0655c4ff532c09346b4df28abdacba" dependencies = [ "anyhow", "cbindgen", @@ -1114,9 +1140,9 @@ dependencies = [ [[package]] name = "extism-convert" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33423cbb1226c483f47a9cad883ee804caf45d8b0d2e5c03cd33d9e43ca5561" +checksum = "6630b9ecf2628b1ef32e3f9bb888ffc17e0eed5927ba11e72e8bc06e59fa9cf6" dependencies = [ "anyhow", "base64 0.22.1", @@ -1130,9 +1156,9 @@ dependencies = [ [[package]] name = "extism-convert-macros" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb132f6e20aab7e8eb3715e26ff8893c611abba73242e00771d740a0fbcb384" +checksum = "e030d6ea834ef4004764b4809832e7197469442e92feb7ca4307e827db7c98b8" dependencies = [ "manyhow", "proc-macro-crate", @@ -1143,9 +1169,9 @@ dependencies = [ [[package]] name = "extism-manifest" -version = "1.9.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9c8b50558af0a75ce08b8ef90a37fb018d99cc99a5f7365c33ba008afdbfb4" +checksum = "65df2772243a6e44caf547142196381a4947cf390cb1252c0205d9337aa0f237" dependencies = [ "base64 0.22.1", "serde", @@ -1527,7 +1553,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", - "serde", ] [[package]] @@ -1537,6 +1562,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "foldhash", + "serde", ] [[package]] @@ -1619,7 +1645,7 @@ dependencies = [ "http-cache", "http-cache-semantics", "reqwest", - "reqwest-middleware 0.4.0", + "reqwest-middleware", "serde", "url", ] @@ -1648,9 +1674,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -1666,9 +1692,9 @@ checksum = "140a09c9305e6d5e557e2ed7cbc68e05765a7d4213975b87cb04920689cc6219" [[package]] name = "hyper" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -1925,13 +1951,14 @@ dependencies = [ [[package]] name = "insta" -version = "1.42.0" +version = "1.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513e4067e16e69ed1db5ab56048ed65db32d10ba5fc1217f5393f8f17d8b5a5" +checksum = "71c1b125e30d93896b365e156c33dadfffab45ee8400afcbba4752f59de08a86" dependencies = [ "console", "linked-hash-map", "once_cell", + "pin-project", "similar", ] @@ -2051,6 +2078,26 @@ dependencies = [ "cc", ] +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.32" @@ -2091,6 +2138,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.169" @@ -2209,9 +2262,9 @@ dependencies = [ [[package]] name = "markdown" -version = "1.0.0-alpha.21" +version = "1.0.0-alpha.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6491e6c702bf7e3b24e769d800746d5f2c06a6c6a2db7992612e0f429029e81" +checksum = "790c11786cb51d02938e3eb38276575e1658b1dd8555875f5788a40670a33934" dependencies = [ "unicode-id", ] @@ -2329,9 +2382,9 @@ checksum = "6367d84fb54d4242af283086402907277715b8fe46976963af5ebf173f8efba3" [[package]] name = "miniz_oxide" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" +checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" dependencies = [ "adler2", ] @@ -2401,12 +2454,31 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2430,15 +2502,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "option-ext" @@ -2502,6 +2574,26 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2686,6 +2778,7 @@ dependencies = [ "semver", "serde", "shared_child", + "shell-words", "sigpipe", "starbase", "starbase_console", @@ -2716,7 +2809,7 @@ dependencies = [ [[package]] name = "proto_core" -version = "0.45.4" +version = "0.47.0" dependencies = [ "clap", "convert_case", @@ -2731,6 +2824,7 @@ dependencies = [ "regex", "reqwest", "rustc-hash", + "scc", "schematic", "semver", "serde", @@ -2754,7 +2848,7 @@ dependencies = [ [[package]] name = "proto_installer" -version = "0.9.0" +version = "0.11.0" dependencies = [ "miette 7.5.0", "reqwest", @@ -2768,7 +2862,7 @@ dependencies = [ [[package]] name = "proto_pdk" -version = "0.26.2" +version = "0.27.0" dependencies = [ "extism-pdk", "proto_pdk_api", @@ -2779,7 +2873,7 @@ dependencies = [ [[package]] name = "proto_pdk_api" -version = "0.25.3" +version = "0.26.0" dependencies = [ "proto_pdk_api", "rustc-hash", @@ -2795,7 +2889,7 @@ dependencies = [ [[package]] name = "proto_pdk_test_utils" -version = "0.32.2" +version = "0.34.4" dependencies = [ "proto_core", "proto_pdk_api", @@ -2824,13 +2918,14 @@ dependencies = [ [[package]] name = "pulley-interpreter" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33e7f8a43ccc7f93b330fef4baf271764674926f3f4d40f4a196d54de8af26" +checksum = "62d95f8575df49a2708398182f49a888cf9dc30210fb1fd2df87c889edcee75d" dependencies = [ "cranelift-bitset", "log", "sptr", + "wasmtime-math", ] [[package]] @@ -2989,14 +3084,15 @@ dependencies = [ [[package]] name = "regalloc2" -version = "0.10.2" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12908dbeb234370af84d0579b9f68258a0f67e201412dd9a2814e6f45b2fc0f0" +checksum = "145c1c267e14f20fb0f88aa76a1c5ffec42d592c1d28b3cd9148ae35916158d3" dependencies = [ - "hashbrown 0.14.5", + "allocator-api2", + "bumpalo", + "hashbrown 0.15.2", "log", "rustc-hash", - "slice-group-by", "smallvec", ] @@ -3093,21 +3189,6 @@ dependencies = [ "windows-registry", ] -[[package]] -name = "reqwest-middleware" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562ceb5a604d3f7c885a792d42c199fd8af239d0a51b2fa6a78aafa092452b04" -dependencies = [ - "anyhow", - "async-trait", - "http", - "reqwest", - "serde", - "thiserror 1.0.69", - "tower-service", -] - [[package]] name = "reqwest-middleware" version = "0.4.0" @@ -3125,11 +3206,11 @@ dependencies = [ [[package]] name = "reqwest-netrc" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3000378f31e2aff16946083125d5e317f89bd12ca8160ef4cff21c445c2376f" +checksum = "ae09db8d40fdf48baa5400ef133a7481516a9818b4d55573241c936312af359b" dependencies = [ - "reqwest-middleware 0.3.3", + "reqwest-middleware", "rust-netrc", ] @@ -3215,9 +3296,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.21" +version = "0.23.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8" +checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" dependencies = [ "log", "once_cell", @@ -3264,13 +3345,40 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" dependencies = [ "web-time", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" +dependencies = [ + "core-foundation 0.9.4", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs 0.7.3", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 2.11.1", + "security-framework-sys", + "webpki-roots", + "winapi", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.102.8" @@ -3290,9 +3398,9 @@ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "same-file" @@ -3389,9 +3497,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sdd" -version = "3.0.5" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478f121bb72bbf63c52c93011ea1791dca40140dfe13f8336c4c5ac952c33aa9" +checksum = "b07779b9b918cc05650cb30f404d4d7835d26df37c235eded8a6832e2fb82cca" [[package]] name = "security-framework" @@ -3403,6 +3511,7 @@ dependencies = [ "core-foundation 0.9.4", "core-foundation-sys", "libc", + "num-bigint", "security-framework-sys", ] @@ -3661,12 +3770,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "slice-group-by" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" - [[package]] name = "slotmap" version = "1.0.7" @@ -3774,9 +3877,9 @@ dependencies = [ [[package]] name = "starbase_console" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb2fd7146c0e55b53ab44aa4a178d4f27f2fd5ce33718054207cf8aaeacc020" +checksum = "f667f7b971e933e5298c15233bbc0caed7b49492fc73cd148dc6802576bfcc0d" dependencies = [ "crossterm", "iocraft", @@ -3817,9 +3920,9 @@ dependencies = [ [[package]] name = "starbase_styles" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65b486cfc419b43fee8b42a87f8d654c1c1d439df34cdf1bfa152cc6bdf8456d" +checksum = "f947bf66e858922ddd27eef15656f4ac4fa11401cfdccbc4140674ea68ee6465" dependencies = [ "dirs 6.0.0", "miette 7.5.0", @@ -3829,9 +3932,9 @@ dependencies = [ [[package]] name = "starbase_utils" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1823e3b2cc103e71a5c04501cea963dada8488620760c92028bc6770eee33bfd" +checksum = "e22b27b7f476b5b6beffe16e694ff4ae8d38f808612b7eef9e279c3cf47eb856" dependencies = [ "async-trait", "dirs 6.0.0", @@ -3892,9 +3995,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "2.0.96" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -3973,7 +4076,7 @@ dependencies = [ [[package]] name = "system_env" -version = "0.7.1" +version = "0.7.2" dependencies = [ "regex", "schematic", @@ -3998,19 +4101,19 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" [[package]] name = "tempfile" -version = "3.15.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", - "getrandom 0.2.15", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -4224,9 +4327,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -4245,9 +4348,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "serde", @@ -4356,6 +4459,17 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "trait-variant" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -4376,9 +4490,9 @@ checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-linebreak" @@ -4418,21 +4532,35 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.12.1" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +checksum = "b2916852be768844b6e9cbe107358b5bc40a696bd6dc8e036c9f80c731242c9c" dependencies = [ "base64 0.22.1", "flate2", "log", - "once_cell", + "percent-encoding", "rustls", - "rustls-native-certs 0.7.3", + "rustls-pemfile", "rustls-pki-types", - "url", + "rustls-platform-verifier", + "ureq-proto", + "utf-8", "webpki-roots", ] +[[package]] +name = "ureq-proto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c51fe73e1d8c4e06bb2698286f7e7453c6fc90528d6d2e7fc36bb4e87fe09b1" +dependencies = [ + "base64 0.22.1", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.4" @@ -4445,6 +4573,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf16_iter" version = "1.0.5" @@ -4492,7 +4626,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "version_spec" -version = "0.7.2" +version = "0.8.0" dependencies = [ "compact_str", "human-sort", @@ -4505,9 +4639,9 @@ dependencies = [ [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] @@ -4533,7 +4667,7 @@ dependencies = [ [[package]] name = "warpgate" -version = "0.21.1" +version = "0.22.1" dependencies = [ "async-trait", "compact_str", @@ -4543,9 +4677,8 @@ dependencies = [ "once_cell", "regex", "reqwest", - "reqwest-middleware 0.4.0", + "reqwest-middleware", "reqwest-netrc", - "rust-netrc", "scc", "schematic", "serde", @@ -4565,7 +4698,7 @@ dependencies = [ [[package]] name = "warpgate_api" -version = "0.11.1" +version = "0.12.0" dependencies = [ "anyhow", "rustc-hash", @@ -4578,7 +4711,7 @@ dependencies = [ [[package]] name = "warpgate_pdk" -version = "0.9.1" +version = "0.11.0" dependencies = [ "extism-pdk", "serde", @@ -4602,9 +4735,9 @@ dependencies = [ [[package]] name = "wasi-common" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165a969c7b4ac223150e2819df36d58b8f24b06320dc314503f90300e5e18bc1" +checksum = "fe3101bd34deeb64225431f8b1b1793c87e7cad94383464878b3f90da6995977" dependencies = [ "anyhow", "bitflags", @@ -4616,7 +4749,6 @@ dependencies = [ "io-extras", "io-lifetimes", "log", - "once_cell", "rustix", "system-interface", "thiserror 1.0.69", @@ -4699,21 +4831,22 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.218.0" +version = "0.221.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22b896fa8ceb71091ace9bcb81e853f54043183a1c9667cf93422c40252ffa0a" +checksum = "dc8444fe4920de80a4fe5ab564fff2ae58b6b73166b89751f8c6c93509da32e5" dependencies = [ "leb128", + "wasmparser 0.221.3", ] [[package]] name = "wasm-encoder" -version = "0.224.0" +version = "0.225.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7249cf8cb0c6b9cb42bce90c0a5feb276fbf963fa385ff3d818ab3d90818ed6" +checksum = "6f7eac0445cac73bcf09e6a97f83248d64356dccf9f2b100199769b6b42464e5" dependencies = [ - "leb128", - "wasmparser 0.224.0", + "leb128fmt", + "wasmparser 0.225.0", ] [[package]] @@ -4731,13 +4864,12 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.218.0" +version = "0.221.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09e46c7fceceaa72b2dd1a8a137ea7fd8f93dfaa69806010a709918e496c5dc" +checksum = "d06bfa36ab3ac2be0dee563380147a5b81ba10dd8885d7fbbc9eb574be67d185" dependencies = [ - "ahash", "bitflags", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "indexmap", "semver", "serde", @@ -4745,9 +4877,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.224.0" +version = "0.225.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65881a664fdd43646b647bb27bf186ab09c05bf56779d40aed4c6dce47d423f5" +checksum = "36e5456165f81e64cb9908a0fe9b9d852c2c74582aa3fe2be3c2da57f937d3ae" dependencies = [ "bitflags", "indexmap", @@ -4756,20 +4888,20 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.218.0" +version = "0.221.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ace089155491837b75f474bf47c99073246d1b737393fe722d6dee311595ddc" +checksum = "7343c42a97f2926c7819ff81b64012092ae954c5d83ddd30c9fcdefd97d0b283" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.218.0", + "wasmparser 0.221.3", ] [[package]] name = "wasmtime" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e762e163fd305770c6c341df3290f0cabb3c264e7952943018e9a1ced8d917" +checksum = "11976a250672556d1c4c04c6d5d7656ac9192ac9edc42a4587d6c21460010e69" dependencies = [ "addr2line", "anyhow", @@ -4785,7 +4917,6 @@ dependencies = [ "indexmap", "ittapi", "libc", - "libm", "log", "mach2", "memfd", @@ -4804,8 +4935,9 @@ dependencies = [ "smallvec", "sptr", "target-lexicon", - "wasm-encoder 0.218.0", - "wasmparser 0.218.0", + "trait-variant", + "wasm-encoder 0.221.3", + "wasmparser 0.221.3", "wasmtime-asm-macros", "wasmtime-cache", "wasmtime-component-macro", @@ -4815,6 +4947,7 @@ dependencies = [ "wasmtime-fiber", "wasmtime-jit-debug", "wasmtime-jit-icache-coherence", + "wasmtime-math", "wasmtime-slab", "wasmtime-versioned-export-macros", "wasmtime-winch", @@ -4824,18 +4957,18 @@ dependencies = [ [[package]] name = "wasmtime-asm-macros" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63caa7aebb546374e26257a1900fb93579171e7c02514cde26805b9ece3ef812" +checksum = "1f178b0d125201fbe9f75beaf849bd3e511891f9e45ba216a5b620802ccf64f2" dependencies = [ "cfg-if", ] [[package]] name = "wasmtime-cache" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7192f71e3afe32e858729454d9d90d6e927bd92427d688a9507d8220bddb256" +checksum = "8b1161c8f62880deea07358bc40cceddc019f1c81d46007bc390710b2fe24ffc" dependencies = [ "anyhow", "base64 0.21.7", @@ -4853,9 +4986,9 @@ dependencies = [ [[package]] name = "wasmtime-component-macro" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61a4b5ce2ad9c15655e830f0eac0c38b8def30c74ecac71f452d3901e491b68" +checksum = "d74de6592ed945d0a602f71243982a304d5d02f1e501b638addf57f42d57dfaf" dependencies = [ "anyhow", "proc-macro2", @@ -4868,15 +5001,15 @@ dependencies = [ [[package]] name = "wasmtime-component-util" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e87a1212270dbb84a49af13d82594e00a92769d6952b0ea7fc4366c949f6ad" +checksum = "707dc7b3c112ab5a366b30cfe2fb5b2f8e6a0f682f16df96a5ec582bfe6f056e" [[package]] name = "wasmtime-cranelift" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb40dddf38c6a5eefd5ce7c1baf43b00fe44eada11a319fab22e993a960262f" +checksum = "366be722674d4bf153290fbcbc4d7d16895cc82fb3e869f8d550ff768f9e9e87" dependencies = [ "anyhow", "cfg-if", @@ -4892,16 +5025,16 @@ dependencies = [ "smallvec", "target-lexicon", "thiserror 1.0.69", - "wasmparser 0.218.0", + "wasmparser 0.221.3", "wasmtime-environ", "wasmtime-versioned-export-macros", ] [[package]] name = "wasmtime-environ" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8613075e89e94a48c05862243c2b718eef1b9c337f51493ebf951e149a10fa19" +checksum = "cdadc1af7097347aa276a4f008929810f726b5b46946971c660b6d421e9994ad" dependencies = [ "anyhow", "cpp_demangle", @@ -4918,17 +5051,17 @@ dependencies = [ "serde_derive", "smallvec", "target-lexicon", - "wasm-encoder 0.218.0", - "wasmparser 0.218.0", + "wasm-encoder 0.221.3", + "wasmparser 0.221.3", "wasmprinter", "wasmtime-component-util", ] [[package]] name = "wasmtime-fiber" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77acabfbcd89a4d47ad117fb31e340c824e2f49597105402c3127457b6230995" +checksum = "ccba90d4119f081bca91190485650730a617be1fff5228f8c4757ce133d21117" dependencies = [ "anyhow", "cc", @@ -4941,21 +5074,20 @@ dependencies = [ [[package]] name = "wasmtime-jit-debug" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02a0118d471de665565ed200bc56673eaa10cc8e223dfe2cef5d50ed0d9d143" +checksum = "3e7b61488a5ee00c35c8c22de707c36c0aecacf419a3be803a6a2ba5e860f56a" dependencies = [ "object", - "once_cell", "rustix", "wasmtime-versioned-export-macros", ] [[package]] name = "wasmtime-jit-icache-coherence" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da47fba49af72581bc0dc67c8faaf5ee550e6f106e285122a184a675193701a5" +checksum = "ec5e8552e01692e6c2e5293171704fed8abdec79d1a6995a0870ab190e5747d1" dependencies = [ "anyhow", "cfg-if", @@ -4963,17 +5095,26 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "wasmtime-math" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29210ec2aa25e00f4d54605cedaf080f39ec01a872c5bd520ad04c67af1dde17" +dependencies = [ + "libm", +] + [[package]] name = "wasmtime-slab" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770e10cdefb15f2b6304152978e115bd062753c1ebe7221c0b6b104fa0419ff6" +checksum = "fcb5821a96fa04ac14bc7b158bb3d5cd7729a053db5a74dad396cd513a5e5ccf" [[package]] name = "wasmtime-versioned-export-macros" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db8efb877c9e5e67239d4553bb44dd2a34ae5cfb728f3cf2c5e64439c6ca6ee7" +checksum = "86ff86db216dc0240462de40c8290887a613dddf9685508eb39479037ba97b5b" dependencies = [ "proc-macro2", "quote", @@ -4982,16 +5123,16 @@ dependencies = [ [[package]] name = "wasmtime-winch" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f7a267367382ceec3e7f7ace63a63b83d86f4a680846743dead644e10f08150" +checksum = "fdbabfb8f20502d5e1d81092b9ead3682ae59988487aafcd7567387b7a43cf8f" dependencies = [ "anyhow", "cranelift-codegen", "gimli", "object", "target-lexicon", - "wasmparser 0.218.0", + "wasmparser 0.221.3", "wasmtime-cranelift", "wasmtime-environ", "winch-codegen", @@ -4999,9 +5140,9 @@ dependencies = [ [[package]] name = "wasmtime-wit-bindgen" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bef2a726fd8d1ee9b0144655e16c492dc32eb4c7c9f7e3309fcffe637870933" +checksum = "8358319c2dd1e4db79e3c1c5d3a5af84956615343f9f89f4e4996a36816e06e6" dependencies = [ "anyhow", "heck 0.5.0", @@ -5020,24 +5161,24 @@ dependencies = [ [[package]] name = "wast" -version = "224.0.0" +version = "225.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d722a51e62b669d17e5a9f6bc8ec210178b37d869114355aa46989686c5c6391" +checksum = "c61496027ff707f9fa9e0b22c34ec163eb7adb1070df565e32a9180a76e4300b" dependencies = [ "bumpalo", - "leb128", + "leb128fmt", "memchr", "unicode-width 0.2.0", - "wasm-encoder 0.224.0", + "wasm-encoder 0.225.0", ] [[package]] name = "wat" -version = "1.224.0" +version = "1.225.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71dece6a7dd5bcbcf8d256606c7fb3faa36286d46bf3f98185407719a5ceede2" +checksum = "89e72a33942234fd0794bcdac30e43b448de3187512414267678e511c6755f11" dependencies = [ - "wast 224.0.0", + "wast 225.0.0", ] [[package]] @@ -5077,18 +5218,18 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.7" +version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" dependencies = [ "rustls-pki-types", ] [[package]] name = "wiggle" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0f25588cf5ea16f56c1af13244486d50c5a2cf67cc0c4e990c665944d741546" +checksum = "4b9af35bc9629c52c261465320a9a07959164928b4241980ba1cf923b9e6751d" dependencies = [ "anyhow", "async-trait", @@ -5102,9 +5243,9 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28ff23bed568b335dac6a324b8b167318a0c60555199445fcc89745a5eb42452" +checksum = "2cf267dd05673912c8138f4b54acabe6bd53407d9d1536f0fadb6520dd16e101" dependencies = [ "anyhow", "heck 0.5.0", @@ -5117,9 +5258,9 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f13be83541aa0b033ac5ec8a8b59c9a8d8b32305845b8466dd066e722cb0004" +checksum = "08c5c473d4198e6c2d377f3809f713ff0c110cab88a0805ae099a82119ee250c" dependencies = [ "proc-macro2", "quote", @@ -5160,9 +5301,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "26.0.1" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07ab957fc71a36c63834b9b51cc2e087c4260d5ff810a5309ab99f7fbeb19567" +checksum = "2f849ef2c5f46cb0a20af4b4487aaa239846e52e2c03f13fa3c784684552859c" dependencies = [ "anyhow", "cranelift-codegen", @@ -5170,7 +5311,8 @@ dependencies = [ "regalloc2", "smallvec", "target-lexicon", - "wasmparser 0.218.0", + "thiserror 1.0.69", + "wasmparser 0.221.3", "wasmtime-cranelift", "wasmtime-environ", ] @@ -5544,9 +5686,9 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.6.24" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" +checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" dependencies = [ "memchr", ] @@ -5582,9 +5724,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.218.0" +version = "0.221.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3d1066ab761b115f97fef2b191090faabcb0f37b555b758d3caf42d4ed9e55" +checksum = "896112579ed56b4a538b07a3d16e562d101ff6265c46b515ce0c701eef16b2ac" dependencies = [ "anyhow", "id-arena", @@ -5595,7 +5737,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.218.0", + "wasmparser 0.221.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 263333835..c9bdd122a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ default-members = ["crates/cli"] [workspace.dependencies] anyhow = "1.0.95" async-trait = "0.1.86" -clap = "4.5.28" +clap = "4.5.29" clap_complete = "4.5.44" compact_str = { version = "0.8.1", default-features = false, features = [ "serde", @@ -20,7 +20,7 @@ indexmap = "2.7.0" iocraft = "0.6.2" # iocraft = { git = "https://github.com/ccbrown/iocraft.git", branch = "main" } miette = "7.5.0" -once_cell = "1.20.2" +once_cell = "1.20.3" regex = { version = "1.11.1", default-features = false, features = ["std"] } reqwest = { version = "0.12.12", default-features = false, features = [ "charset", @@ -31,7 +31,7 @@ reqwest-middleware = { version = "0.4.0", default-features = false, features = [ "charset", "http2", ] } -reqwest-netrc = "0.1.2" +reqwest-netrc = "0.1.3" rustc-hash = "2.1.1" scc = "2.3.3" schematic = { version = "0.17.11", default-features = false } @@ -51,13 +51,11 @@ starbase_archive = { version = "0.9.4", features = [ "zip", "zip-deflate", ] } -starbase_console = { version = "0.4.6", features = [ - "ui", -] } # , path = "../starbase/crates/console" } +starbase_console = { version = "0.4.7", features = ["ui"] } starbase_sandbox = { version = "0.8.2" } starbase_shell = { version = "0.6.13", features = ["miette"] } -starbase_styles = { version = "0.4.11" } -starbase_utils = { version = "0.10.1", default-features = false, features = [ +starbase_styles = { version = "0.4.13" } +starbase_utils = { version = "0.10.2", default-features = false, features = [ "json", "miette", "net", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 0fe358a3e..4355f30f2 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -32,11 +32,11 @@ name = "proto-shim" path = "src/main_shim.rs" [dependencies] -proto_core = { version = "0.45.4", path = "../core", features = ["clap"] } -proto_installer = { version = "0.9.0", path = "../installer" } -proto_pdk_api = { version = "0.25.3", path = "../pdk-api" } +proto_core = { version = "0.47.0", path = "../core", features = ["clap"] } +proto_installer = { version = "0.11.0", path = "../installer" } +proto_pdk_api = { version = "0.26.0", path = "../pdk-api" } proto_shim = { version = "0.6.0", path = "../shim" } -system_env = { version = "0.7.1", path = "../system-env" } +system_env = { version = "0.7.2", path = "../system-env" } anyhow = { workspace = true } async-trait = { workspace = true } chrono = "0.4.39" @@ -52,6 +52,7 @@ reqwest = { workspace = true, features = ["rustls-tls-native-roots"] } rustc-hash = { workspace = true } semver = { workspace = true } serde = { workspace = true } +shell-words = { workspace = true } starbase = { workspace = true } starbase_console = { workspace = true } starbase_shell = { workspace = true } @@ -65,14 +66,14 @@ tokio = { workspace = true } tracing = { workspace = true } # For the shim binary -rust_json = "0.1.5" +rust_json = "0.1.6" sigpipe = "0.1.3" # For extism/wastime -extism = "1.9.1" -wasmtime = "~26.0.1" -wasi-common = "~26.0.1" -wiggle = "~26.0.1" +extism = "1.10.0" +wasmtime = "~29.0.1" +wasi-common = "~29.0.1" +wiggle = "~29.0.1" [dev-dependencies] starbase_sandbox = { workspace = true } diff --git a/crates/cli/src/commands/activate.rs b/crates/cli/src/commands/activate.rs index 1efaf27bb..7cc8ff682 100644 --- a/crates/cli/src/commands/activate.rs +++ b/crates/cli/src/commands/activate.rs @@ -138,7 +138,9 @@ pub async fn activate(session: ProtoSession, args: ActivateArgs) -> AppResult { ); } - if let Some(UnresolvedVersionSpec::Semantic(version)) = config.versions.get("proto") { + if let Some(UnresolvedVersionSpec::Semantic(version)) = + config.versions.get("proto").map(|spec| &spec.req) + { info.env .insert("PROTO_VERSION".into(), Some(version.to_string())); diff --git a/crates/cli/src/commands/alias.rs b/crates/cli/src/commands/alias.rs index 0cc603280..2202b889e 100644 --- a/crates/cli/src/commands/alias.rs +++ b/crates/cli/src/commands/alias.rs @@ -2,7 +2,7 @@ use crate::error::ProtoCliError; use crate::session::ProtoSession; use clap::Args; use iocraft::prelude::element; -use proto_core::{is_alias_name, Id, PinLocation, ProtoConfig, UnresolvedVersionSpec}; +use proto_core::{is_alias_name, Id, PinLocation, ProtoConfig, ToolSpec, UnresolvedVersionSpec}; use starbase::AppResult; use starbase_console::ui::*; @@ -14,8 +14,8 @@ pub struct AliasArgs { #[arg(required = true, help = "Alias name")] alias: String, - #[arg(required = true, help = "Version or alias to associate with")] - spec: UnresolvedVersionSpec, + #[arg(required = true, help = "Version specification to alias")] + spec: ToolSpec, #[arg(long, default_value_t, help = "Location of .prototools to add to")] to: PinLocation, @@ -23,20 +23,20 @@ pub struct AliasArgs { #[tracing::instrument(skip_all)] pub async fn alias(session: ProtoSession, args: AliasArgs) -> AppResult { - if let UnresolvedVersionSpec::Alias(inner_alias) = &args.spec { + if let UnresolvedVersionSpec::Alias(inner_alias) = &args.spec.req { if args.alias == inner_alias { - return Err(ProtoCliError::NoMatchingAliasToVersion.into()); + return Err(ProtoCliError::AliasNoMatchingToVersion.into()); } } if !is_alias_name(&args.alias) { - return Err(ProtoCliError::InvalidAliasName { + return Err(ProtoCliError::AliasInvalidName { alias: args.alias.clone(), } .into()); } - let tool = session.load_tool(&args.id).await?; + let tool = session.load_tool(&args.id, args.spec.backend).await?; let config_path = ProtoConfig::update(tool.proto.get_config_dir(args.to), |config| { let tool_configs = config.tools.get_or_insert(Default::default()); diff --git a/crates/cli/src/commands/bin.rs b/crates/cli/src/commands/bin.rs index 089367a27..9aeeb2d38 100644 --- a/crates/cli/src/commands/bin.rs +++ b/crates/cli/src/commands/bin.rs @@ -1,6 +1,6 @@ use crate::session::ProtoSession; use clap::Args; -use proto_core::{detect_version, Id, UnresolvedVersionSpec, PROTO_PLUGIN_KEY}; +use proto_core::{detect_version_with_spec, Id, ToolSpec, PROTO_PLUGIN_KEY}; use proto_shim::{get_exe_file_name, locate_proto_exe}; use starbase::AppResult; @@ -12,8 +12,8 @@ pub struct BinArgs { #[arg(long, help = "Display symlinked binary path when available")] bin: bool, - #[arg(help = "Version or alias of tool")] - spec: Option, + #[arg(help = "Version specification to locate")] + spec: Option, #[arg(long, help = "Display shim path when available")] shim: bool, @@ -32,10 +32,12 @@ pub async fn bin(session: ProtoSession, args: BinArgs) -> AppResult { return Ok(None); } - let mut tool = session.load_tool(&args.id).await?; - let version = detect_version(&tool, args.spec.clone()).await?; + let mut tool = session + .load_tool(&args.id, args.spec.clone().and_then(|spec| spec.backend)) + .await?; + let spec = detect_version_with_spec(&tool, args.spec.clone()).await?; - tool.resolve_version(&version, true).await?; + tool.resolve_version_with_spec(&spec, true).await?; if args.bin { tool.symlink_bins(true).await?; diff --git a/crates/cli/src/commands/clean.rs b/crates/cli/src/commands/clean.rs index 8b447153a..a0e300edb 100644 --- a/crates/cli/src/commands/clean.rs +++ b/crates/cli/src/commands/clean.rs @@ -1,7 +1,7 @@ use crate::session::ProtoSession; use clap::{Args, ValueEnum}; use iocraft::prelude::element; -use proto_core::{ProtoError, Tool, VersionSpec, PROTO_PLUGIN_KEY}; +use proto_core::{flow::resolve::ProtoResolveError, Tool, VersionSpec, PROTO_PLUGIN_KEY}; use proto_shim::get_exe_file_name; use rustc_hash::FxHashSet; use serde::Serialize; @@ -109,11 +109,12 @@ pub async fn clean_tool( continue; } - let version = - VersionSpec::parse(&dir_name).map_err(|error| ProtoError::VersionSpec { + let version = VersionSpec::parse(&dir_name).map_err(|error| { + ProtoResolveError::InvalidVersionSpec { version: dir_name, error: Box::new(error), - })?; + } + })?; if !tool.inventory.manifest.versions.contains_key(&version) { debug!( @@ -227,9 +228,11 @@ pub async fn clean_proto_tool( let proto_file = tool_dir.join(get_exe_file_name("proto")); let dir_name = fs::file_name(&tool_dir); - let version = VersionSpec::parse(&dir_name).map_err(|error| ProtoError::VersionSpec { - version: dir_name, - error: Box::new(error), + let version = VersionSpec::parse(&dir_name).map_err(|error| { + ProtoResolveError::InvalidVersionSpec { + version: dir_name, + error: Box::new(error), + } })?; let is_stale = if proto_file.exists() { diff --git a/crates/cli/src/commands/install.rs b/crates/cli/src/commands/install.rs index 3e81f8be4..a33c1b9ee 100644 --- a/crates/cli/src/commands/install.rs +++ b/crates/cli/src/commands/install.rs @@ -1,23 +1,21 @@ -use crate::components::{InstallAllProgress, InstallProgress, InstallProgressProps}; use crate::error::ProtoCliError; use crate::session::{LoadToolOptions, ProtoSession}; use crate::utils::install_graph::*; -use crate::workflows::{InstallOutcome, InstallWorkflow, InstallWorkflowParams}; +use crate::utils::tool_record::ToolRecord; +use crate::workflows::{InstallOutcome, InstallWorkflowManager, InstallWorkflowParams}; use clap::Args; use iocraft::prelude::element; -use miette::IntoDiagnostic; -use proto_core::{ConfigMode, Id, PinLocation, Tool, UnresolvedVersionSpec}; +use proto_core::{ConfigMode, Id, PinLocation, Tool, ToolSpec, UnresolvedVersionSpec}; use proto_pdk_api::InstallStrategy; use starbase::AppResult; use starbase_console::ui::*; use starbase_console::utils::formats::format_duration; use starbase_styles::color; use std::collections::BTreeMap; -use std::sync::Arc; use std::time::{Duration, Instant}; -use tokio::task::{spawn, JoinSet}; +use tokio::task::JoinSet; use tokio::time::sleep; -use tracing::{debug, instrument, trace}; +use tracing::{debug, info, instrument, trace}; #[derive(Args, Clone, Debug, Default)] pub struct InstallArgs { @@ -26,10 +24,10 @@ pub struct InstallArgs { #[arg( default_value = "latest", - help = "When installing one tool, the version or alias to install", + help = "When installing one tool, the version specification to install", group = "version-type" )] - pub spec: Option, + pub spec: Option, #[arg( long, @@ -52,7 +50,7 @@ pub struct InstallArgs { )] pub canary: bool, - #[arg(long, help = "Force reinstallation even if it is already installed")] + #[arg(long, help = "Force reinstallation even if already installed")] pub force: bool, #[arg(long, help = "Pin the resolved version to .prototools")] @@ -71,6 +69,30 @@ pub struct InstallArgs { } impl InstallArgs { + async fn filter_tools(&self, tools: Vec) -> Vec { + let mut list = vec![]; + + if self.build { + info!("Build mode enabled. Only tools that support build from source will install."); + + for tool in tools { + if tool.plugin.has_func("build_instructions").await { + list.push(tool); + } + } + } else if self.no_build { + info!("Prebuilt mode enabled. Only tools that support prebuilts will install."); + + for tool in tools { + if tool.plugin.has_func("download_prebuilt").await { + list.push(tool); + } + } + } + + list + } + fn get_strategy(&self) -> Option { if self.build { Some(InstallStrategy::BuildFromSource) @@ -85,22 +107,19 @@ impl InstallArgs { self.pin.as_ref().map(|pin| pin.unwrap_or_default()) } - fn get_unresolved_spec(&self) -> UnresolvedVersionSpec { + fn get_tool_spec(&self) -> ToolSpec { if self.canary { - UnresolvedVersionSpec::Canary + ToolSpec::new(UnresolvedVersionSpec::Canary) } else { self.spec.clone().unwrap_or_default() } } } -pub fn enforce_requirements( - tool: &Tool, - versions: &BTreeMap, -) -> miette::Result<()> { +pub fn enforce_requirements(tool: &Tool, versions: &BTreeMap) -> miette::Result<()> { for require_id in &tool.metadata.requires { if !versions.contains_key(require_id.as_str()) { - return Err(ProtoCliError::ToolRequiresNotMet { + return Err(ProtoCliError::InstallRequirementsNotMet { tool: tool.get_name().to_owned(), requires: require_id.to_owned(), } @@ -115,7 +134,8 @@ pub fn enforce_requirements( pub async fn install_one(session: ProtoSession, args: InstallArgs, id: Id) -> AppResult { debug!(id = id.as_str(), "Loading tool"); - let tool = session.load_tool(&id).await?; + let spec = args.get_tool_spec(); + let tool = session.load_tool(&id, spec.backend).await?; // Load config including global versions, // so that our requirements can be satisfied @@ -126,10 +146,9 @@ pub async fn install_one(session: ProtoSession, args: InstallArgs, id: Id) -> Ap } // Create our workflow and setup the progress reporter - let mut workflow = InstallWorkflow::new(tool, session.console.clone()); - let mut handle = None; + let mut workflow_manager = InstallWorkflowManager::new(session.console.clone()); + let mut workflow = workflow_manager.create_workflow(tool); - // Only show the progress bars when not building if workflow.is_build(args.get_strategy()) { session.console.render(element! { Notice(variant: Variant::Caution) { @@ -142,24 +161,12 @@ pub async fn install_one(session: ProtoSession, args: InstallArgs, id: Id) -> Ap } })?; } else { - let reporter = workflow.progress_reporter.clone(); - let console = session.console.clone(); - - handle = Some(spawn(async move { - console - .render_loop(element! { - InstallProgress(reporter) - }) - .await - })); - - // Wait a bit for the component to be rendered - sleep(Duration::from_millis(50)).await; + workflow_manager.render_single_progress().await; } let result = workflow .install( - args.get_unresolved_spec(), + spec, InstallWorkflowParams { pin_to: args.get_pin_location(), strategy: args.get_strategy(), @@ -171,16 +178,15 @@ pub async fn install_one(session: ProtoSession, args: InstallArgs, id: Id) -> Ap ) .await; - workflow.progress_reporter.exit(); - - if let Some(handle) = handle { - handle.await.into_diagnostic()??; - } + workflow_manager.stop_rendering().await?; let outcome = result?; let tool = workflow.tool; if args.internal { + session.console.err.flush()?; + session.console.out.flush()?; + return Ok(None); } @@ -235,11 +241,22 @@ async fn install_all(session: ProtoSession, args: InstallArgs) -> AppResult { if let Some(candidate) = &tool.detected_version { debug!("Detected version {} for {}", candidate, tool.get_name()); - versions.insert(tool.id.clone(), candidate.to_owned()); + versions.insert(tool.id.clone(), ToolSpec::new(candidate.to_owned())); } } - if versions.is_empty() { + // Filter down tools to only those that have a version + let mut tools = tools + .into_iter() + .filter(|tool| versions.contains_key(&tool.id)) + .collect::>(); + + // And handle build/prebuilt modes + if args.build || args.no_build { + tools = args.filter_tools(tools).await; + } + + if tools.is_empty() { session.console.render(element! { Notice(variant: Variant::Caution) { StyledText( @@ -265,20 +282,9 @@ async fn install_all(session: ProtoSession, args: InstallArgs) -> AppResult { return Ok(Some(1)); } - // Filter down tools to only those that have a version - let tools = tools - .into_iter() - .filter(|tool| versions.contains_key(&tool.id)) - .collect::>(); - - // Determine longest ID for use within progress bars - let longest_id = versions - .keys() - .fold(0, |acc, id| acc.max(id.as_str().len())); - // Then install each tool in parallel! let mut topo_graph = InstallGraph::new(&tools); - let mut progress_rows = BTreeMap::default(); + let mut workflow_manager = InstallWorkflowManager::new(session.console.clone()); let mut set = JoinSet::new(); let started = Instant::now(); let force = args.force; @@ -296,19 +302,7 @@ async fn install_all(session: ProtoSession, args: InstallArgs) -> AppResult { let tool_id = tool.id.clone(); let initial_version = version.clone(); let topo_graph = topo_graph.clone(); - let mut workflow = InstallWorkflow::new(tool, session.console.clone()); - - // Clone the progress reporters so that we can render - // multiple progress bars in parallel - progress_rows.insert( - tool_id.clone(), - InstallProgressProps { - default_message: Some(format!("Preparing {} install…", workflow.tool.get_name())), - reporter: Some(OwnedOrShared::Shared(Arc::new( - workflow.progress_reporter.clone(), - ))), - }, - ); + let mut workflow = workflow_manager.create_workflow(tool); let handle = set.spawn(async move { while let Some(status) = topo_graph.check_install_status(&workflow.tool.id).await { @@ -377,26 +371,7 @@ async fn install_all(session: ProtoSession, args: InstallArgs) -> AppResult { ); } - let reporter = ProgressReporter::default(); - let reporter_clone = reporter.clone(); - let console = session.console.clone(); - - let handle = spawn(async move { - console - .render_loop(element! { - InstallAllProgress( - reporter: reporter_clone, - tools: progress_rows, - id_width: longest_id, - ) - }) - .await - }); - - // Wait a bit for the component to be rendered - sleep(Duration::from_millis(50)).await; - - // Start installing tools after the component has rendered! + workflow_manager.render_multiple_progress().await; topo_graph.proceed(); let mut installed_count = 0; @@ -425,8 +400,7 @@ async fn install_all(session: ProtoSession, args: InstallArgs) -> AppResult { }; } - reporter.exit(); - handle.await.into_diagnostic()??; + workflow_manager.stop_rendering().await?; session.console.render(element! { Notice( diff --git a/crates/cli/src/commands/migrate/mod.rs b/crates/cli/src/commands/migrate/mod.rs index c62046443..c261b8208 100644 --- a/crates/cli/src/commands/migrate/mod.rs +++ b/crates/cli/src/commands/migrate/mod.rs @@ -22,7 +22,7 @@ pub async fn migrate(_session: ProtoSession, args: MigrateArgs) -> AppResult { // } // } - return Err(ProtoCliError::UnknownMigration { + return Err(ProtoCliError::MigrateUnknownOperation { op: args.operation.to_owned(), } .into()); diff --git a/crates/cli/src/commands/outdated.rs b/crates/cli/src/commands/outdated.rs index e2638d70d..59aacd72c 100644 --- a/crates/cli/src/commands/outdated.rs +++ b/crates/cli/src/commands/outdated.rs @@ -3,7 +3,9 @@ use crate::session::{LoadToolOptions, ProtoSession}; use clap::Args; use iocraft::prelude::{element, Size}; use miette::IntoDiagnostic; -use proto_core::{Id, ProtoConfig, UnresolvedVersionSpec, VersionSpec, PROTO_CONFIG_NAME}; +use proto_core::{ + Id, ProtoConfig, ToolSpec, UnresolvedVersionSpec, VersionSpec, PROTO_CONFIG_NAME, +}; use semver::VersionReq; use serde::Serialize; use starbase::AppResult; @@ -289,10 +291,17 @@ pub async fn outdated(session: ProtoSession, args: OutdatedArgs) -> AppResult { ); ProtoConfig::update(config_path, |config| { - config - .versions - .get_or_insert(Default::default()) - .extend(updated_versions); + let versions = config.versions.get_or_insert(Default::default()); + + // Try and preserve any spec backend's + for (id, updated_version) in updated_versions { + versions + .entry(id) + .and_modify(|spec| { + spec.req = updated_version.clone(); + }) + .or_insert_with(|| ToolSpec::new(updated_version)); + } })?; } diff --git a/crates/cli/src/commands/pin.rs b/crates/cli/src/commands/pin.rs index 3b61cea81..b0423e08d 100644 --- a/crates/cli/src/commands/pin.rs +++ b/crates/cli/src/commands/pin.rs @@ -1,7 +1,7 @@ use crate::session::ProtoSession; use clap::Args; use iocraft::prelude::element; -use proto_core::{Id, PinLocation, ProtoConfig, Tool, UnresolvedVersionSpec}; +use proto_core::{Id, PinLocation, ProtoConfig, Tool, ToolSpec}; use starbase::AppResult; use starbase_console::ui::*; use std::collections::BTreeMap; @@ -13,8 +13,8 @@ pub struct PinArgs { #[arg(required = true, help = "ID of tool")] pub id: Id, - #[arg(required = true, help = "Version or alias of tool")] - pub spec: UnresolvedVersionSpec, + #[arg(required = true, help = "Version specification to pin")] + pub spec: ToolSpec, #[arg(long, help = "Resolve the version before pinning")] pub resolve: bool, @@ -25,7 +25,7 @@ pub struct PinArgs { pub async fn internal_pin( tool: &mut Tool, - spec: &UnresolvedVersionSpec, + spec: &ToolSpec, pin_to: PinLocation, ) -> miette::Result { let config_path = ProtoConfig::update(tool.proto.get_config_dir(pin_to), |config| { @@ -46,15 +46,15 @@ pub async fn internal_pin( #[tracing::instrument(skip_all)] pub async fn pin(session: ProtoSession, args: PinArgs) -> AppResult { - let mut tool = session.load_tool(&args.id).await?; + let mut tool = session.load_tool(&args.id, None).await?; + let mut spec = args.spec.clone(); - let spec = if args.resolve { - tool.resolve_version(&args.spec, false) + if args.resolve { + spec.req = tool + .resolve_version_with_spec(&spec, false) .await? - .to_unresolved_spec() - } else { - args.spec.clone() - }; + .to_unresolved_spec(); + } let config_path = internal_pin(&mut tool, &spec, args.to).await?; diff --git a/crates/cli/src/commands/plugin/info.rs b/crates/cli/src/commands/plugin/info.rs index 363d58a66..20b2f2f4d 100644 --- a/crates/cli/src/commands/plugin/info.rs +++ b/crates/cli/src/commands/plugin/info.rs @@ -4,8 +4,8 @@ use clap::Args; use iocraft::prelude::element; use proto_core::{ flow::locate::ExecutableLocation, ConfigMode, Id, PluginLocator, ProtoToolConfig, ToolManifest, + ToolMetadata, }; -use proto_pdk_api::ToolMetadataOutput; use serde::Serialize; use starbase::AppResult; use starbase_console::ui::*; @@ -24,7 +24,7 @@ struct InfoPluginResult { id: Id, inventory_dir: PathBuf, manifest: ToolManifest, - metadata: ToolMetadataOutput, + metadata: ToolMetadata, name: String, plugin: PluginLocator, shims: Vec, @@ -43,6 +43,7 @@ pub async fn info(session: ProtoSession, args: InfoPluginArgs) -> AppResult { let mut tool = session .load_tool_with_options( &args.id, + None, LoadToolOptions { detect_version: true, inherit_local: true, @@ -289,7 +290,7 @@ pub async fn info(session: ProtoSession, args: InfoPluginArgs) -> AppResult { no_children: tool.installed_versions.is_empty() ) { VersionsMap( - default_version: global_config.versions.get(&tool.id), + default_version: global_config.versions.get(&tool.id).map(|spec| &spec.req), inventory: &tool.inventory, versions: tool.installed_versions.iter().collect::>(), ) @@ -298,7 +299,7 @@ pub async fn info(session: ProtoSession, args: InfoPluginArgs) -> AppResult { name: "Remote aliases", no_children: tool.remote_aliases.is_empty() ) { - AliasesMap( + SpecAliasesMap( aliases: tool.remote_aliases.iter().collect::>() ) } @@ -315,7 +316,7 @@ pub async fn info(session: ProtoSession, args: InfoPluginArgs) -> AppResult { name: "Local aliases", no_children: tool.local_aliases.is_empty() ) { - AliasesMap( + SpecAliasesMap( aliases: tool.local_aliases.iter().collect::>() ) } diff --git a/crates/cli/src/commands/plugin/list.rs b/crates/cli/src/commands/plugin/list.rs index 97eb21220..9237cec84 100644 --- a/crates/cli/src/commands/plugin/list.rs +++ b/crates/cli/src/commands/plugin/list.rs @@ -1,4 +1,4 @@ -use crate::components::{AliasesMap, Locator, VersionsMap}; +use crate::components::{Locator, SpecAliasesMap, VersionsMap}; use crate::session::{LoadToolOptions, ProtoSession}; use clap::Args; use iocraft::prelude::element; @@ -115,7 +115,7 @@ pub async fn list(session: ProtoSession, args: ListPluginsArgs) -> AppResult { name: "Aliases", no_children: aliases.is_empty() ) { - AliasesMap(aliases) + SpecAliasesMap(aliases) } }) } else { @@ -129,7 +129,7 @@ pub async fn list(session: ProtoSession, args: ListPluginsArgs) -> AppResult { no_children: tool.installed_versions.is_empty() ) { VersionsMap( - default_version: global_config.versions.get(&tool.id), + default_version: global_config.versions.get(&tool.id).map(|spec| &spec.req), inventory: &tool.inventory, versions: tool.installed_versions.iter().collect::>(), ) diff --git a/crates/cli/src/commands/run.rs b/crates/cli/src/commands/run.rs index 696394ef8..9347bf615 100644 --- a/crates/cli/src/commands/run.rs +++ b/crates/cli/src/commands/run.rs @@ -3,7 +3,7 @@ use crate::error::ProtoCliError; use crate::session::ProtoSession; use clap::Args; use miette::IntoDiagnostic; -use proto_core::{detect_version, Id, ProtoError, Tool, UnresolvedVersionSpec}; +use proto_core::{detect_version_with_spec, Id, Tool, ToolSpec}; use proto_pdk_api::{ExecutableConfig, RunHook, RunHookResult}; use proto_shim::exec_command_and_replace; use starbase::AppResult; @@ -19,10 +19,14 @@ pub struct RunArgs { #[arg(required = true, help = "ID of tool")] id: Id, - #[arg(help = "Version or alias of tool")] - spec: Option, + #[arg(help = "Version specification to run")] + spec: Option, - #[arg(long, help = "Name of an alternate (secondary) binary to run")] + #[arg( + long, + hide = true, + help = "Name of an alternate (secondary) binary to run" + )] alt: Option, // Passthrough args (after --) @@ -38,14 +42,24 @@ fn is_trying_to_self_upgrade(tool: &Tool, args: &[String]) -> bool { return false; } - for arg in args { - // Find first non-option arg - if arg.starts_with('-') { - continue; + // Expand "self upgrade" string into ["self", "upgrade"] list + let mut match_groups = vec![]; + + for arg_string in &tool.metadata.self_upgrade_commands { + if let Ok(arg_list) = shell_words::split(arg_string) { + match_groups.push(arg_list); + } + } + + // Then match the args in sequence + 'outer: for match_list in match_groups { + for (index, match_arg) in match_list.into_iter().enumerate() { + if args.get(index).is_some_and(|arg| arg != &match_arg) { + continue 'outer; + } } - // And then check if an upgrade command - return tool.metadata.self_upgrade_commands.contains(arg); + return true; } false @@ -79,7 +93,7 @@ async fn get_executable(tool: &Tool, args: &RunArgs) -> miette::Result, A: AsRef>( #[tracing::instrument(skip_all)] pub async fn run(session: ProtoSession, args: RunArgs) -> AppResult { - let mut tool = session.load_tool(&args.id).await?; + let mut tool = session + .load_tool(&args.id, args.spec.clone().and_then(|spec| spec.backend)) + .await?; // Avoid running the tool's native self-upgrade as it conflicts with proto if is_trying_to_self_upgrade(&tool, &args.passthrough) { - return Err(ProtoCliError::NoSelfUpgrade { + return Err(ProtoCliError::RunNoSelfUpgrade { command: format!("proto install {} --pin", tool.id), tool: tool.get_name().to_owned(), } .into()); } - let version = detect_version(&tool, args.spec.clone()).await?; + let spec = detect_version_with_spec(&tool, args.spec.clone()).await?; // Check if installed or need to install - if !tool.is_setup(&version).await? { + if !tool.is_setup_with_spec(&spec).await? { let config = tool.proto.load_config()?; let resolved_version = tool.get_resolved_version(); @@ -161,18 +177,18 @@ pub async fn run(session: ProtoSession, args: RunArgs) -> AppResult { let command = format!("proto install {} {}", tool.id, resolved_version); if let Ok(source) = env::var(format!("{}_DETECTED_FROM", tool.get_env_var_prefix())) { - return Err(ProtoError::MissingToolForRunWithSource { + return Err(ProtoCliError::RunMissingToolWithSource { tool: tool.get_name().to_owned(), - version: version.to_string(), + version: spec.to_string(), command, path: source.into(), } .into()); } - return Err(ProtoError::MissingToolForRun { + return Err(ProtoCliError::RunMissingTool { tool: tool.get_name().to_owned(), - version: version.to_string(), + version: spec.to_string(), command, } .into()); @@ -189,7 +205,11 @@ pub async fn run(session: ProtoSession, args: RunArgs) -> AppResult { session.clone(), InstallArgs { internal: true, - spec: Some(resolved_version.to_unresolved_spec()), + spec: Some(ToolSpec { + backend: spec.backend, + req: resolved_version.to_unresolved_spec(), + res: Some(resolved_version.clone()), + }), ..Default::default() }, tool.id.clone(), diff --git a/crates/cli/src/commands/setup.rs b/crates/cli/src/commands/setup.rs index 421a32da2..e1f4e38e8 100644 --- a/crates/cli/src/commands/setup.rs +++ b/crates/cli/src/commands/setup.rs @@ -93,7 +93,14 @@ pub async fn setup(session: ProtoSession, args: SetupArgs) -> AppResult { vec![ Export::Var( "PROTO_HOME".into(), - env::var("PROTO_HOME").unwrap_or_else(|_| "$HOME/.proto".into()), + env::var("PROTO_HOME").unwrap_or_else(|_| { + if env::var("XDG_DATA_HOME").is_ok() { + "$XDG_DATA_HOME/proto" + } else { + "$HOME/.proto" + } + .into() + }), ), Export::Path(vec!["$PROTO_HOME/shims".into(), "$PROTO_HOME/bin".into()]), ], diff --git a/crates/cli/src/commands/unalias.rs b/crates/cli/src/commands/unalias.rs index 009e3efe8..12c0dbe46 100644 --- a/crates/cli/src/commands/unalias.rs +++ b/crates/cli/src/commands/unalias.rs @@ -19,7 +19,7 @@ pub struct UnaliasArgs { #[tracing::instrument(skip_all)] pub async fn unalias(session: ProtoSession, args: UnaliasArgs) -> AppResult { - let tool = session.load_tool(&args.id).await?; + let tool = session.load_tool(&args.id, None).await?; let mut value = None; let config_path = ProtoConfig::update(tool.proto.get_config_dir(args.from), |config| { diff --git a/crates/cli/src/commands/uninstall.rs b/crates/cli/src/commands/uninstall.rs index c3680d114..666ca8fca 100644 --- a/crates/cli/src/commands/uninstall.rs +++ b/crates/cli/src/commands/uninstall.rs @@ -2,7 +2,7 @@ use crate::session::ProtoSession; use crate::telemetry::{track_usage, Metric}; use clap::Args; use iocraft::element; -use proto_core::{Id, ProtoConfig, Tool, UnresolvedVersionSpec}; +use proto_core::{Id, ProtoConfig, Tool, ToolSpec}; use starbase::AppResult; use starbase_console::ui::*; use starbase_utils::fs; @@ -13,8 +13,8 @@ pub struct UninstallArgs { #[arg(required = true, help = "ID of tool")] id: Id, - #[arg(help = "Version or alias of tool")] - spec: Option, + #[arg(help = "Version specification to uninstall")] + spec: Option, } fn unpin_version(session: &ProtoSession, args: &UninstallArgs) -> miette::Result<()> { @@ -65,7 +65,7 @@ async fn track_uninstall(tool: &Tool, all: bool) -> miette::Result<()> { #[instrument(skip(session))] async fn uninstall_all(session: ProtoSession, args: UninstallArgs) -> AppResult { - let mut tool = session.load_tool(&args.id).await?; + let mut tool = session.load_tool(&args.id, None).await?; let inventory_dir = tool.get_inventory_dir(); let version_count = tool.inventory.manifest.installed_versions.len(); let skip_prompts = session.should_skip_prompts(); @@ -145,14 +145,10 @@ async fn uninstall_all(session: ProtoSession, args: UninstallArgs) -> AppResult } #[instrument(skip(session))] -async fn uninstall_one( - session: ProtoSession, - args: UninstallArgs, - spec: UnresolvedVersionSpec, -) -> AppResult { - let mut tool = session.load_tool(&args.id).await?; - - if !tool.is_setup(&spec).await? { +async fn uninstall_one(session: ProtoSession, args: UninstallArgs, spec: ToolSpec) -> AppResult { + let mut tool = session.load_tool(&args.id, spec.backend).await?; + + if !tool.is_setup_with_spec(&spec).await? { session.console.render(element! { Notice(variant: Variant::Caution) { StyledText( diff --git a/crates/cli/src/commands/unpin.rs b/crates/cli/src/commands/unpin.rs index 84c94bf7c..5a4efb2a5 100644 --- a/crates/cli/src/commands/unpin.rs +++ b/crates/cli/src/commands/unpin.rs @@ -16,7 +16,7 @@ pub struct UnpinArgs { #[tracing::instrument(skip_all)] pub async fn unpin(session: ProtoSession, args: UnpinArgs) -> AppResult { - let tool = session.load_tool(&args.id).await?; + let tool = session.load_tool(&args.id, None).await?; let mut value = None; let config_path = ProtoConfig::update(tool.proto.get_config_dir(args.from), |config| { diff --git a/crates/cli/src/commands/upgrade.rs b/crates/cli/src/commands/upgrade.rs index 329a8e1e6..8f6f4a91b 100644 --- a/crates/cli/src/commands/upgrade.rs +++ b/crates/cli/src/commands/upgrade.rs @@ -148,9 +148,7 @@ pub async fn upgrade(session: ProtoSession, args: UpgradeArgs) -> AppResult { session.clone(), InstallArgs { internal: true, - spec: Some(UnresolvedVersionSpec::Semantic(SemVer( - target_version.clone(), - ))), + spec: Some(UnresolvedVersionSpec::Semantic(SemVer(target_version.clone())).into()), ..Default::default() }, Id::raw(PROTO_PLUGIN_KEY), diff --git a/crates/cli/src/commands/versions.rs b/crates/cli/src/commands/versions.rs index 2f304ce46..a991f8618 100644 --- a/crates/cli/src/commands/versions.rs +++ b/crates/cli/src/commands/versions.rs @@ -3,7 +3,7 @@ use crate::session::{LoadToolOptions, ProtoSession}; use clap::Args; use indexmap::IndexMap; use iocraft::prelude::{element, View}; -use proto_core::{Id, UnresolvedVersionSpec, VersionSpec}; +use proto_core::{Id, ToolSpec, VersionSpec}; use serde::Serialize; use starbase::AppResult; use starbase_console::ui::*; @@ -33,8 +33,8 @@ pub struct VersionItem { #[derive(Serialize)] pub struct VersionsResult { versions: Vec, - local_aliases: BTreeMap, - remote_aliases: BTreeMap, + local_aliases: BTreeMap, + remote_aliases: BTreeMap, } #[tracing::instrument(skip_all)] @@ -42,6 +42,7 @@ pub async fn versions(session: ProtoSession, args: VersionsArgs) -> AppResult { let tool = session .load_tool_with_options( &args.id, + None, LoadToolOptions { inherit_local: true, inherit_remote: true, @@ -101,7 +102,7 @@ pub async fn versions(session: ProtoSession, args: VersionsArgs) -> AppResult { return Ok(None); } - let mut aliases = IndexMap::<&String, &UnresolvedVersionSpec>::default(); + let mut aliases = IndexMap::<&String, &ToolSpec>::default(); if args.aliases && !args.installed { aliases.extend(&tool.remote_aliases); @@ -135,7 +136,7 @@ pub async fn versions(session: ProtoSession, args: VersionsArgs) -> AppResult { #(aliases.into_iter().map(|(alias, version)| { element! { View { - StyledText(content: format!("{alias} {version}")) + StyledText(content: format!("{alias} {}", version.req)) } } })) diff --git a/crates/cli/src/components/mod.rs b/crates/cli/src/components/mod.rs index 55dc31d32..cff232dec 100644 --- a/crates/cli/src/components/mod.rs +++ b/crates/cli/src/components/mod.rs @@ -1,19 +1,21 @@ -mod aliases_map; +// mod aliases_map; mod code_block; mod env_var; mod install_all_progress; mod install_progress; mod issues_list; mod locator; +mod spec_aliases_map; mod versions_map; -pub use aliases_map::*; +// pub use aliases_map::*; pub use code_block::*; pub use env_var::*; pub use install_all_progress::*; pub use install_progress::*; pub use issues_list::*; pub use locator::*; +pub use spec_aliases_map::*; pub use versions_map::*; use chrono::{DateTime, NaiveDateTime}; diff --git a/crates/cli/src/components/spec_aliases_map.rs b/crates/cli/src/components/spec_aliases_map.rs new file mode 100644 index 000000000..b16d08bf9 --- /dev/null +++ b/crates/cli/src/components/spec_aliases_map.rs @@ -0,0 +1,35 @@ +use iocraft::prelude::*; +use proto_core::ToolSpec; +use starbase_console::ui::{Map, MapItem, Style, StyledText}; +use std::collections::BTreeMap; + +#[derive(Default, Props)] +pub struct SpecAliasesMapProps<'a> { + pub aliases: BTreeMap<&'a String, &'a ToolSpec>, +} + +#[component] +pub fn SpecAliasesMap<'a>(props: &SpecAliasesMapProps<'a>) -> impl Into> { + element! { + Map { + #(props.aliases.iter().map(|(alias, version)| { + element! { + MapItem( + name: element! { + StyledText( + content: alias.to_owned(), + style: Style::Id + ) + }.into_any(), + value: element! { + StyledText( + content: version.to_string(), + style: Style::Invalid + ) + }.into_any() + ) + } + })) + } + } +} diff --git a/crates/cli/src/error.rs b/crates/cli/src/error.rs index 1363f684a..94862b7a2 100644 --- a/crates/cli/src/error.rs +++ b/crates/cli/src/error.rs @@ -4,13 +4,15 @@ use starbase_styles::{Style, Stylize}; use std::path::PathBuf; use thiserror::Error; +// Convention: + #[derive(Error, Debug, Diagnostic)] pub enum ProtoCliError { - #[diagnostic(code(proto::cli::invalid_alias))] - #[error("Invalid alias name {}. Use alpha-numeric words instead.", .alias.style(Style::Id))] - InvalidAliasName { alias: String }, + #[diagnostic(code(proto::no_configured_tools))] + #[error("No tools have been configured in {}.", PROTO_CONFIG_NAME.style(Style::File))] + NoConfiguredTools, - #[diagnostic(code(proto::cli::missing_tools_config))] + #[diagnostic(code(proto::missing_tools_config))] #[error( "No {} has been found in current directory. Attempted to find at {}.", PROTO_CONFIG_NAME.style(Style::File), @@ -18,50 +20,83 @@ pub enum ProtoCliError { )] MissingToolsConfigInCwd { path: PathBuf }, - #[diagnostic(code(proto::cli::missing_alternate_binary))] + // ALIAS + #[diagnostic(code(proto::commands::alias::invalid))] + #[error("Invalid alias name {}. Use alpha-numeric words instead.", .alias.style(Style::Id))] + AliasInvalidName { alias: String }, + + #[diagnostic(code(proto::commands::alias::no_mapping))] + #[error("Cannot map an alias to itself.")] + AliasNoMatchingToVersion, + + // INSTALL + #[diagnostic( + code(proto::commands::install::requirements_not_met), + help("Try configuring a version of the required tool in .prototools") + )] + #[error( + "{} requires {} to function correctly, but it has not been installed.", + .tool, + .requires.style(Style::Id) + )] + InstallRequirementsNotMet { tool: String, requires: String }, + + // MIGRATE + #[diagnostic(code(proto::commands::migrate::unknown))] + #[error("Unknown migration operation {}.", .op.style(Style::Symbol))] + MigrateUnknownOperation { op: String }, + + // RUN + #[diagnostic(code(proto::commands::run::missing_alternate_binary))] #[error( "Unable to run, alternate binary {} does not exist. Attempted to find at {}.", .bin.style(Style::File), .path.style(Style::Path), )] - MissingRunAltBin { bin: String, path: PathBuf }, + RunMissingAltBin { bin: String, path: PathBuf }, - #[diagnostic(code(proto::cli::no_configured_tools))] - #[error("No tools have been configured in {}.", PROTO_CONFIG_NAME.style(Style::File))] - NoConfiguredTools, - - #[diagnostic(code(proto::cli::no_mapped_alias))] - #[error("Cannot map an alias to itself.")] - NoMatchingAliasToVersion, - - #[diagnostic(code(proto::cli::no_self_upgrade))] + #[diagnostic(code(proto::commands::run::missing_tool))] #[error( - "Self upgrading {} is not supported in proto, as it conflicts with proto's managed inventory.\nUse {} instead to upgrade to the latest version.", - .tool, - .command.style(Style::Shell) + "This project requires {tool} {}, but this version has not been installed. Install it with {}, or enable the {} setting to automatically install missing versions!", + .version.style(Style::Hash), + .command.style(Style::Shell), + "auto-install".style(Style::Property), )] - NoSelfUpgrade { command: String, tool: String }, + RunMissingTool { + tool: String, + version: String, + command: String, + }, - #[diagnostic( - code(proto::cli::requirements_not_met), - help("Try configuring a version of the required tool in .prototools") + #[diagnostic(code(proto::commands::run::missing_tool))] + #[error( + "This project requires {tool} {} (detected from {}), but this version has not been installed. Install it with {}, or enable the {} setting to automatically install missing versions!", + .version.style(Style::Hash), + .path.style(Style::Path), + .command.style(Style::Shell), + "auto-install".style(Style::Property), )] + RunMissingToolWithSource { + tool: String, + version: String, + command: String, + path: PathBuf, + }, + + #[diagnostic(code(proto::commands::run::no_self_upgrade))] #[error( - "{} requires {} to function correctly, but it has not been installed.", + "Self upgrading {} is not supported in proto, as it conflicts with proto's managed inventory.\nUse {} instead to upgrade to the latest version.", .tool, - .requires.style(Style::Id) + .command.style(Style::Shell) )] - ToolRequiresNotMet { tool: String, requires: String }, + RunNoSelfUpgrade { command: String, tool: String }, - #[diagnostic(code(proto::cli::upgrade_failed))] - #[error("Failed to upgrade proto, {} could not be located after download!", .bin.style(Style::Shell))] + // UPGRADE + #[diagnostic(code(proto::commands::upgrade::failed))] + #[error("Failed to upgrade proto, {} binary could not be located after download!", .bin.style(Style::Shell))] UpgradeFailed { bin: String }, - #[diagnostic(code(proto::cli::offline))] + #[diagnostic(code(proto::commands::upgrade::offline))] #[error("Upgrading proto requires an internet connection!")] UpgradeRequiresInternet, - - #[diagnostic(code(proto::cli::unknown_migration))] - #[error("Unknown migration operation {}.", .op.style(Style::Symbol))] - UnknownMigration { op: String }, } diff --git a/crates/cli/src/main_shim.rs b/crates/cli/src/main_shim.rs index 69b659fdd..579043f1c 100644 --- a/crates/cli/src/main_shim.rs +++ b/crates/cli/src/main_shim.rs @@ -37,6 +37,14 @@ fn get_proto_home() -> Result { return Ok(root.into()); } + if let Ok(root) = env::var("XDG_DATA_HOME") { + let xdg_dir = PathBuf::from(root).join("proto"); + + debug(|| format!("Found in `XDG_DATA_HOME` environment variable: {xdg_dir:?}")); + + return Ok(xdg_dir); + } + let home_dir = dirs::home_dir() .ok_or_else(|| anyhow!("Unable to determine user home directory."))? .join(".proto"); diff --git a/crates/cli/src/session.rs b/crates/cli/src/session.rs index 7119302e2..5c236684f 100644 --- a/crates/cli/src/session.rs +++ b/crates/cli/src/session.rs @@ -8,8 +8,8 @@ use async_trait::async_trait; use miette::IntoDiagnostic; use proto_core::registry::ProtoRegistry; use proto_core::{ - load_schema_plugin_with_proto, load_tool_from_locator, load_tool_with_proto, ConfigMode, Id, - ProtoConfig, ProtoEnvironment, Tool, UnresolvedVersionSpec, PROTO_PLUGIN_KEY, + load_schema_plugin_with_proto, load_tool, load_tool_from_locator, Backend, ConfigMode, Id, + ProtoConfig, ProtoEnvironment, Tool, ToolSpec, UnresolvedVersionSpec, PROTO_PLUGIN_KEY, SCHEMA_PLUGIN_KEY, }; use rustc_hash::FxHashSet; @@ -80,8 +80,8 @@ impl ProtoSession { self.env.load_config_with_mode(mode) } - pub async fn load_tool(&self, id: &Id) -> miette::Result { - self.load_tool_with_options(id, LoadToolOptions::default()) + pub async fn load_tool(&self, id: &Id, backend: Option) -> miette::Result { + self.load_tool_with_options(id, backend, LoadToolOptions::default()) .await } @@ -89,9 +89,10 @@ impl ProtoSession { pub async fn load_tool_with_options( &self, id: &Id, + backend: Option, options: LoadToolOptions, ) -> miette::Result { - let mut record = ToolRecord::new(load_tool_with_proto(id, &self.env).await?); + let mut record = ToolRecord::new(load_tool(id, &self.env, backend).await?); if options.inherit_remote { record.inherit_from_remote().await?; @@ -104,12 +105,14 @@ impl ProtoSession { if options.detect_version { record.detect_version().await; - let version = record - .detected_version - .clone() - .unwrap_or_else(|| UnresolvedVersionSpec::parse("*").unwrap()); + let spec = ToolSpec::new( + record + .detected_version + .clone() + .unwrap_or_else(|| UnresolvedVersionSpec::parse("*").unwrap()), + ); - record.tool.resolve_version(&version, false).await?; + record.tool.resolve_version_with_spec(&spec, false).await?; } Ok(record) @@ -120,18 +123,6 @@ impl ProtoSession { .await } - #[allow(dead_code)] - pub async fn load_tools_with_filters( - &self, - filters: FxHashSet<&Id>, - ) -> miette::Result> { - self.load_tools_with_options(LoadToolOptions { - ids: filters.into_iter().cloned().collect(), - ..Default::default() - }) - .await - } - #[tracing::instrument(name = "load_tools", skip(self))] pub async fn load_tools_with_options( &self, @@ -139,6 +130,13 @@ impl ProtoSession { ) -> miette::Result> { let config = self.env.load_config()?; + // Gather the IDs of all possible tools. We can't just use the + // `plugins` map, because some tools may not have a plugin entry, + // for example, those using backends. + let mut ids = vec![]; + ids.extend(config.plugins.keys()); + ids.extend(config.versions.keys()); + // If no filter IDs provided, inherit the IDs from the current // config for every tool that has a version. Otherwise, we'll // load all tools, even built-ins, when the user isn't using them. @@ -158,7 +156,7 @@ impl ProtoSession { let opt_inherit_remote = options.inherit_remote; let opt_detect_version = options.detect_version; - for (id, locator) in &config.plugins { + for id in ids { if !options.ids.is_empty() && !options.ids.contains(id) { continue; } @@ -169,11 +167,10 @@ impl ProtoSession { } let id = id.to_owned(); - let locator = locator.to_owned(); let proto = Arc::clone(&self.env); set.spawn(async move { - let mut record = ToolRecord::new(load_tool_from_locator(id, proto, locator).await?); + let mut record = ToolRecord::new(load_tool(&id, &proto, None).await?); if opt_inherit_remote { record.inherit_from_remote().await?; diff --git a/crates/cli/src/systems.rs b/crates/cli/src/systems.rs index cd9aa5426..16142e9ab 100644 --- a/crates/cli/src/systems.rs +++ b/crates/cli/src/systems.rs @@ -62,10 +62,10 @@ pub async fn download_versioned_proto_tool(session: &ProtoSession) -> miette::Re .load_config_manager()? .get_merged_config_without_global()?; - if let Some(version) = config.versions.get(PROTO_PLUGIN_KEY) { + if let Some(spec) = config.versions.get(PROTO_PLUGIN_KEY) { // Only support fully-qualified versions as we need to prepend the // tool directory into PATH, which doesn't support requirements - if !matches!(version, UnresolvedVersionSpec::Semantic(_)) { + if !matches!(spec.req, UnresolvedVersionSpec::Semantic(_)) { return Ok(()); } @@ -73,11 +73,12 @@ pub async fn download_versioned_proto_tool(session: &ProtoSession) -> miette::Re if !tool.is_installed() { debug!( - version = version.to_string(), + version = spec.to_string(), "Downloading a versioned proto because it was configured in {}", PROTO_CONFIG_NAME ); - tool.setup(version, InstallOptions::default()).await?; + tool.setup_with_spec(spec, InstallOptions::default()) + .await?; } } diff --git a/crates/cli/src/utils/tool_record.rs b/crates/cli/src/utils/tool_record.rs index de8cc8301..8609232e4 100644 --- a/crates/cli/src/utils/tool_record.rs +++ b/crates/cli/src/utils/tool_record.rs @@ -1,6 +1,7 @@ use core::ops::{Deref, DerefMut}; use proto_core::{ - detect_version, ProtoConfig, ProtoToolConfig, Tool, UnresolvedVersionSpec, VersionSpec, + detect_version, ProtoConfig, ProtoToolConfig, Tool, ToolSpec, UnresolvedVersionSpec, + VersionSpec, }; use std::collections::BTreeMap; use std::path::PathBuf; @@ -11,8 +12,8 @@ pub struct ToolRecord { pub detected_source: Option, pub detected_version: Option, pub installed_versions: Vec, - pub local_aliases: BTreeMap, - pub remote_aliases: BTreeMap, + pub local_aliases: BTreeMap, + pub remote_aliases: BTreeMap, pub remote_versions: Vec, } @@ -62,7 +63,12 @@ impl ToolRecord { .load_version_resolver(&UnresolvedVersionSpec::default()) .await?; - self.remote_aliases.extend(version_resolver.aliases); + self.remote_aliases.extend( + version_resolver + .aliases + .into_iter() + .map(|(k, v)| (k, ToolSpec::new(v))), + ); self.remote_versions.extend(version_resolver.versions); self.remote_versions.sort(); diff --git a/crates/cli/src/workflows/install_workflow.rs b/crates/cli/src/workflows/install_workflow.rs index b283b08af..ef8d238e7 100644 --- a/crates/cli/src/workflows/install_workflow.rs +++ b/crates/cli/src/workflows/install_workflow.rs @@ -1,25 +1,33 @@ use crate::commands::pin::internal_pin; +use crate::components::{InstallAllProgress, InstallProgress, InstallProgressProps}; use crate::session::ProtoConsole; use crate::shell::{self, Export}; use crate::telemetry::*; use crate::utils::tool_record::ToolRecord; +use iocraft::element; +use miette::IntoDiagnostic; use proto_core::flow::install::{InstallOptions, InstallPhase}; -use proto_core::{PinLocation, UnresolvedVersionSpec, PROTO_PLUGIN_KEY}; +use proto_core::{Id, PinLocation, ToolSpec, PROTO_PLUGIN_KEY}; use proto_pdk_api::{ InstallHook, InstallStrategy, Switch, SyncShellProfileInput, SyncShellProfileOutput, }; -use starbase_console::ui::{ProgressDisplay, ProgressReporter}; +use starbase_console::ui::{OwnedOrShared, ProgressDisplay, ProgressReporter, ProgressState}; use starbase_console::utils::formats::format_duration; use starbase_shell::ShellType; +use starbase_styles::color::{self, apply_style_tags}; +use starbase_utils::env::bool_var; +use std::collections::BTreeMap; use std::env; +use std::sync::Arc; use std::time::{Duration, Instant}; +use tokio::task::JoinHandle; +use tokio::time::sleep; use tracing::{debug, warn}; pub enum InstallOutcome { AlreadyInstalled, Installed, FailedToInstall, - NotInstalled, } pub struct InstallWorkflowParams { @@ -67,31 +75,23 @@ impl InstallWorkflow { pub async fn install( &mut self, - initial_version: UnresolvedVersionSpec, + spec: ToolSpec, params: InstallWorkflowParams, ) -> miette::Result { let started = Instant::now(); - if params.multiple && self.is_build(params.strategy) { - self.progress_reporter.set_message( - "Build from source is currently not supported in the multi-install workflow", - ); - - return Ok(InstallOutcome::NotInstalled); - } - self.progress_reporter.set_message(format!( "Installing {} with specification {}", self.tool.get_name(), - initial_version + spec )); // Disable version caching and always use the latest when installing self.tool.disable_caching(); // Check if already installed, or if forced, overwrite previous install - if !params.force && self.tool.is_setup(&initial_version).await? { - self.pin_version(&initial_version, ¶ms.pin_to).await?; + if !params.force && self.tool.is_setup_with_spec(&spec).await? { + self.pin_version(&spec, ¶ms.pin_to).await?; self.finish_progress(started); return Ok(InstallOutcome::AlreadyInstalled); @@ -101,13 +101,13 @@ impl InstallWorkflow { self.pre_install(¶ms).await?; // Run install - let installed = self.do_install(&initial_version, ¶ms).await?; + let installed = self.do_install(&spec, ¶ms).await?; if !installed { return Ok(InstallOutcome::FailedToInstall); } - let pinned = self.pin_version(&initial_version, ¶ms.pin_to).await?; + let pinned = self.pin_version(&spec, ¶ms.pin_to).await?; self.finish_progress(started); // Run post-install hooks @@ -125,7 +125,7 @@ impl InstallWorkflow { .map(|loc| loc.to_string()) .unwrap_or_default(), version: self.tool.get_resolved_version().to_string(), - version_candidate: initial_version.to_string(), + version_candidate: spec.req.to_string(), pinned, }, ) @@ -157,16 +157,16 @@ impl InstallWorkflow { async fn do_install( &mut self, - initial_version: &UnresolvedVersionSpec, + spec: &ToolSpec, params: &InstallWorkflowParams, ) -> miette::Result { - self.tool.resolve_version(initial_version, false).await?; + self.tool.resolve_version_with_spec(spec, false).await?; let resolved_version = self.tool.get_resolved_version(); let default_strategy = self.tool.metadata.default_install_strategy; self.progress_reporter.set_message( - if initial_version == &resolved_version.to_unresolved_spec() { + if spec.req == resolved_version.to_unresolved_spec() { format!( "Installing {} {}", self.tool.get_name(), @@ -177,7 +177,7 @@ impl InstallWorkflow { "Installing {} {} (from specification {})", self.tool.get_name(), resolved_version, - initial_version + spec ) } ); @@ -204,33 +204,44 @@ impl InstallWorkflow { .set_value(0); } + // Use the suffix for progress tokens so that they don't appear + // in the normal message. This helps with non-TTY scenarios + if matches!(phase, InstallPhase::Download { .. }) { + pb.set_suffix(" | {bytes} / {total_bytes} | {bytes_per_sec}"); + } else { + pb.set_suffix(""); + } + pb.set_message(match phase { InstallPhase::Native => "Installing natively".to_owned(), - InstallPhase::Verify { file, .. } => format!("Verifying checksum against {file}"), + InstallPhase::Verify { file, .. } => { + format!("Verifying checksum against {file}") + } InstallPhase::Unpack { file } => format!("Unpacking archive {file}"), - InstallPhase::Download { file, .. } => format!("Downloading pre-built archive {file} | {{bytes}} / {{total_bytes}} | {{bytes_per_sec}}"), + InstallPhase::Download { file, .. } => { + format!("Downloading pre-built archive {file}") + } InstallPhase::InstallDeps => "Installing system dependencies".into(), InstallPhase::CheckRequirements => "Checking requirements".into(), InstallPhase::ExecuteInstructions => "Executing build instructions".into(), - InstallPhase::CloneRepository { url } => format!("Cloning repository {url}") + InstallPhase::CloneRepository { url } => { + format!("Cloning repository {url}") + } }); }); self.tool - .setup( - initial_version, + .setup_with_spec( + spec, InstallOptions { - // When installing multiple tools, we can't render the nice - // UI for the build flow, so rely on the progress bars - console: if params.multiple { - None - } else { - Some(self.console.clone()) - }, + console: Some(self.console.clone()), on_download_chunk: Some(on_download_chunk), on_phase_change: Some(on_phase_change), force: params.force, skip_prompts: params.skip_prompts, + // When installing multiple tools, we can't render the nice + // UI for the build flow, so rely on the progress bars + skip_ui: params.multiple, strategy: params.strategy.unwrap_or(default_strategy), }, ) @@ -260,7 +271,7 @@ impl InstallWorkflow { async fn pin_version( &mut self, - initial_version: &UnresolvedVersionSpec, + spec: &ToolSpec, arg_pin_to: &Option, ) -> miette::Result { // Don't pin the proto tool itself as it's internal only @@ -284,7 +295,7 @@ impl InstallWorkflow { } // via `pin-latest` setting - if initial_version.is_latest() { + if spec.req.is_latest() { if let Some(custom_type) = &config.settings.pin_latest { pin_to = *custom_type; pin = true; @@ -292,14 +303,10 @@ impl InstallWorkflow { } if pin { - let resolved_version = self.tool.get_resolved_version(); + let resolved_spec = + ToolSpec::new(self.tool.get_resolved_version().to_unresolved_spec()); - internal_pin( - &mut self.tool.tool, - &resolved_version.to_unresolved_spec(), - pin_to, - ) - .await?; + internal_pin(&mut self.tool.tool, &resolved_spec, pin_to).await?; } Ok(pin) @@ -385,17 +392,170 @@ impl InstallWorkflow { Ok(()) } - fn finish_progress(&self, started: Instant) { + fn finish_progress(&mut self, started: Instant) { + let duration = format_duration(started.elapsed(), true); + let mut message = format!( + "{} {} installed", + self.tool.get_name(), + self.tool.get_resolved_version(), + ); + + if duration != "0s" { + message.push(' '); + message.push_str(&format!("({duration})")); + } + self.progress_reporter - .set_message(format!( - "{} {} installed ({})!", - self.tool.get_name(), - self.tool.get_resolved_version(), - format_duration(started.elapsed(), true) - )) + .set_message(message) .set_display(ProgressDisplay::Bar) .set_tick(None) .set_max(100) .set_value(100); } } + +pub struct InstallWorkflowManager { + pub console: ProtoConsole, + pub progress_reporters: BTreeMap, + + monitor_handles: Vec>, + render_handle: Option>>, +} + +impl InstallWorkflowManager { + pub fn new(console: ProtoConsole) -> Self { + Self { + console, + progress_reporters: BTreeMap::default(), + monitor_handles: vec![], + render_handle: None, + } + } + + pub fn create_workflow(&mut self, tool: ToolRecord) -> InstallWorkflow { + let workflow = InstallWorkflow::new(tool, self.console.clone()); + + self.progress_reporters + .insert(workflow.tool.id.clone(), workflow.progress_reporter.clone()); + + workflow + } + + pub fn is_tty(&self) -> bool { + !bool_var("NO_TTY") && self.console.out.is_terminal() + } + + pub async fn render_single_progress(&mut self) { + if !self.is_tty() { + self.monitor_messages(); + return; + } + + let reporter = self.progress_reporters.values().next().unwrap().clone(); + let console = self.console.clone(); + + self.render_handle = Some(tokio::spawn(async move { + console + .render_loop(element! { + InstallProgress(reporter) + }) + .await + })); + + // Wait a bit for the component to be rendered + sleep(Duration::from_millis(50)).await; + } + + pub async fn render_multiple_progress(&mut self) { + if !self.is_tty() { + self.monitor_messages(); + return; + } + + let reporter = ProgressReporter::default(); + let reporter_clone = reporter.clone(); + let console = self.console.clone(); + + let tools = self + .progress_reporters + .iter() + .map(|(id, reporter)| { + ( + id.to_owned(), + InstallProgressProps { + default_message: Some("Preparing install…".into()), + reporter: Some(OwnedOrShared::Shared(Arc::new(reporter.clone()))), + }, + ) + }) + .collect::>(); + + let id_width = self + .progress_reporters + .keys() + .fold(0, |acc, id| acc.max(id.as_str().len())); + + self.render_handle = Some(tokio::spawn(async move { + console + .render_loop(element! { + InstallAllProgress( + reporter: reporter_clone, + tools, + id_width, + ) + }) + .await + })); + + // Wait a bit for the component to be rendered + sleep(Duration::from_millis(50)).await; + } + + pub fn monitor_messages(&mut self) { + for (id, reporter) in &self.progress_reporters { + let reporter = reporter.clone(); + let console = self.console.clone(); + let prefix = color::muted_light(format!("[{}] ", id)); + + self.monitor_handles.push(tokio::spawn(async move { + let mut receiver = reporter.subscribe(); + + while let Ok(state) = receiver.recv().await { + match state { + ProgressState::Exit => { + break; + } + ProgressState::Message(message) => { + let _ = console.out.write_line_with_prefix( + apply_style_tags( + // Compatibility with the UI theme + message + .replace("version>", "hash>") + .replace("versionalt>", "symbol>"), + ), + &prefix, + ); + } + _ => {} + } + } + })); + } + } + + pub async fn stop_rendering(mut self) -> miette::Result<()> { + self.progress_reporters.values().for_each(|reporter| { + reporter.exit(); + }); + + for handle in self.monitor_handles { + handle.await.into_diagnostic()?; + } + + if let Some(handle) = self.render_handle.take() { + handle.await.into_diagnostic()??; + } + + Ok(()) + } +} diff --git a/crates/cli/tests/alias_test.rs b/crates/cli/tests/alias_test.rs index 71fe7f485..25611cc92 100644 --- a/crates/cli/tests/alias_test.rs +++ b/crates/cli/tests/alias_test.rs @@ -18,7 +18,7 @@ mod alias_local { assert .inner - .stderr(predicate::str::contains("unknown is not a built-in tool")); + .stderr(predicate::str::contains("unknown is not a built-in plugin")); } #[test] @@ -46,7 +46,7 @@ mod alias_local { config.tools.get("node").unwrap().aliases, BTreeMap::from_iter([( "example".into(), - UnresolvedVersionSpec::parse("19.0.0").unwrap() + UnresolvedVersionSpec::parse("19.0.0").unwrap().into() )]) ); } @@ -61,7 +61,7 @@ mod alias_local { PartialProtoToolConfig { aliases: Some(BTreeMap::from_iter([( "example".into(), - UnresolvedVersionSpec::parse("19.0.0").unwrap(), + UnresolvedVersionSpec::parse("19.0.0").unwrap().into(), )])), ..Default::default() }, @@ -81,7 +81,7 @@ mod alias_local { config.tools.get("node").unwrap().aliases, BTreeMap::from_iter([( "example".into(), - UnresolvedVersionSpec::parse("20.0.0").unwrap() + UnresolvedVersionSpec::parse("20.0.0").unwrap().into() )]) ); } @@ -142,7 +142,7 @@ mod alias_global { config.tools.get("node").unwrap().aliases, BTreeMap::from_iter([( "example".into(), - UnresolvedVersionSpec::parse("19.0.0").unwrap() + UnresolvedVersionSpec::parse("19.0.0").unwrap().into() )]) ); } @@ -177,7 +177,7 @@ mod alias_user { config.tools.get("node").unwrap().aliases, BTreeMap::from_iter([( "example".into(), - UnresolvedVersionSpec::parse("19.0.0").unwrap() + UnresolvedVersionSpec::parse("19.0.0").unwrap().into() )]) ); } diff --git a/crates/cli/tests/install_uninstall_test.rs b/crates/cli/tests/install_uninstall_test.rs index be13c0359..b43e56380 100644 --- a/crates/cli/tests/install_uninstall_test.rs +++ b/crates/cli/tests/install_uninstall_test.rs @@ -297,7 +297,7 @@ mod install_uninstall { ProtoConfig::update(sandbox.path(), |config| { config.versions.get_or_insert(Default::default()).insert( Id::raw("node"), - UnresolvedVersionSpec::parse("18.0.0").unwrap(), + UnresolvedVersionSpec::parse("18.0.0").unwrap().into(), ); }) .unwrap(); @@ -343,7 +343,7 @@ mod install_uninstall { ProtoConfig::update(sandbox.path().join(".proto"), |config| { config.versions.get_or_insert(Default::default()).insert( Id::raw("node"), - UnresolvedVersionSpec::parse("18.0.0").unwrap(), + UnresolvedVersionSpec::parse("18.0.0").unwrap().into(), ); }) .unwrap(); @@ -390,7 +390,7 @@ mod install_uninstall { ProtoConfig::update(sandbox.path(), |config| { config.versions.get_or_insert(Default::default()).insert( Id::raw("node"), - UnresolvedVersionSpec::parse("18.0.0").unwrap(), + UnresolvedVersionSpec::parse("18.0.0").unwrap().into(), ); }) .unwrap(); @@ -447,7 +447,7 @@ mod install_uninstall { ProtoConfig::update(sandbox.path(), |config| { config.versions.get_or_insert(Default::default()).insert( Id::raw("node"), - UnresolvedVersionSpec::parse("18.0.0").unwrap(), + UnresolvedVersionSpec::parse("18.0.0").unwrap().into(), ); }) .unwrap(); @@ -482,7 +482,7 @@ mod install_uninstall { config.versions.get_or_insert(Default::default()).insert( Id::raw("node"), - UnresolvedVersionSpec::parse("16.0.0").unwrap(), + UnresolvedVersionSpec::parse("16.0.0").unwrap().into(), ); }) .unwrap(); @@ -491,7 +491,7 @@ mod install_uninstall { ProtoConfig::update(sandbox.path().join(".proto"), |config| { config.versions.get_or_insert(Default::default()).insert( Id::raw("node"), - UnresolvedVersionSpec::parse("18.0.0").unwrap(), + UnresolvedVersionSpec::parse("18.0.0").unwrap().into(), ); }) .unwrap(); @@ -531,7 +531,7 @@ mod install_uninstall { config.versions.get_or_insert(Default::default()).insert( Id::raw("node"), - UnresolvedVersionSpec::parse("16.0.0").unwrap(), + UnresolvedVersionSpec::parse("16.0.0").unwrap().into(), ); }) .unwrap(); @@ -540,7 +540,7 @@ mod install_uninstall { ProtoConfig::update(sandbox.path().join(".proto"), |config| { config.versions.get_or_insert(Default::default()).insert( Id::raw("node"), - UnresolvedVersionSpec::parse("18.0.0").unwrap(), + UnresolvedVersionSpec::parse("18.0.0").unwrap().into(), ); }) .unwrap(); diff --git a/crates/cli/tests/plugins_test.rs b/crates/cli/tests/plugins_test.rs index 2dcbba42a..ae8ddb00d 100644 --- a/crates/cli/tests/plugins_test.rs +++ b/crates/cli/tests/plugins_test.rs @@ -159,8 +159,6 @@ mod plugins { mod builtins { use super::*; - // macos is very flaky! - #[cfg(not(target_os = "macos"))] #[test] fn supports_bun() { let sandbox = create_empty_proto_sandbox(); diff --git a/crates/cli/tests/run_test.rs b/crates/cli/tests/run_test.rs index 158d3e7ce..4dd4d8d72 100644 --- a/crates/cli/tests/run_test.rs +++ b/crates/cli/tests/run_test.rs @@ -190,10 +190,7 @@ mod run { }) .success(); - // Output on macos is truncated - if cfg!(not(target_os = "macos")) { - assert.stdout(predicate::str::contains("installed")); - } + assert.stdout(predicate::str::contains("installed")); } #[test] @@ -212,10 +209,7 @@ mod run { }) .success(); - // Output on macos is truncated - if cfg!(not(target_os = "macos")) { - assert.stdout(predicate::str::contains("installed")); - } + assert.stdout(predicate::str::contains("installed")); env::remove_var("PROTO_AUTO_INSTALL"); } @@ -253,10 +247,7 @@ mod run { }) .success(); - // Output on macos is truncated - if cfg!(not(target_os = "macos")) { - assert.stdout(predicate::str::contains("Node.js 19.0.0 installed")); - } + assert.stdout(predicate::str::contains("Node.js 19.0.0 installed")); let assert = sandbox .run_bin(|cmd| { @@ -268,10 +259,7 @@ mod run { }) .success(); - // Output on macos is truncated - if cfg!(not(target_os = "macos")) { - assert.stdout(predicate::str::contains("Node.js 19.0.0 installed").not()); - } + assert.stdout(predicate::str::contains("Node.js 19.0.0 installed").not()); } #[test] @@ -285,7 +273,7 @@ mod run { .failure(); assert.stderr(predicate::str::contains( - "plugin-name is not a built-in tool", + "plugin-name is not a built-in plugin", )); } diff --git a/crates/cli/tests/unalias_test.rs b/crates/cli/tests/unalias_test.rs index 6b000ae09..3daa39d39 100644 --- a/crates/cli/tests/unalias_test.rs +++ b/crates/cli/tests/unalias_test.rs @@ -18,7 +18,7 @@ mod unalias_local { assert .inner - .stderr(predicate::str::contains("unknown is not a built-in tool")); + .stderr(predicate::str::contains("unknown is not a built-in plugin")); } #[test] @@ -31,7 +31,7 @@ mod unalias_local { PartialProtoToolConfig { aliases: Some(BTreeMap::from_iter([( "example".into(), - UnresolvedVersionSpec::parse("19.0.0").unwrap(), + UnresolvedVersionSpec::parse("19.0.0").unwrap().into(), )])), ..Default::default() }, @@ -60,7 +60,7 @@ mod unalias_local { PartialProtoToolConfig { aliases: Some(BTreeMap::from_iter([( "example".into(), - UnresolvedVersionSpec::parse("19.0.0").unwrap(), + UnresolvedVersionSpec::parse("19.0.0").unwrap().into(), )])), ..Default::default() }, @@ -80,7 +80,7 @@ mod unalias_local { config.tools.get("node").unwrap().aliases, BTreeMap::from_iter([( "example".into(), - UnresolvedVersionSpec::parse("19.0.0").unwrap() + UnresolvedVersionSpec::parse("19.0.0").unwrap().into() )]) ); } @@ -99,7 +99,7 @@ mod unalias_global { PartialProtoToolConfig { aliases: Some(BTreeMap::from_iter([( "example".into(), - UnresolvedVersionSpec::parse("19.0.0").unwrap(), + UnresolvedVersionSpec::parse("19.0.0").unwrap().into(), )])), ..Default::default() }, @@ -136,7 +136,7 @@ mod unalias_user { PartialProtoToolConfig { aliases: Some(BTreeMap::from_iter([( "example".into(), - UnresolvedVersionSpec::parse("19.0.0").unwrap(), + UnresolvedVersionSpec::parse("19.0.0").unwrap().into(), )])), ..Default::default() }, diff --git a/crates/cli/tests/uninstall_test.rs b/crates/cli/tests/uninstall_test.rs index bafda5f66..0223c0bb2 100644 --- a/crates/cli/tests/uninstall_test.rs +++ b/crates/cli/tests/uninstall_test.rs @@ -96,7 +96,7 @@ mod uninstall { sandbox .run_bin(|cmd| { - cmd.arg("uninstall").arg("node").arg("19.0.0"); + cmd.arg("uninstall").arg("node").arg("19.0.0").arg("--yes"); }) .success(); diff --git a/crates/cli/tests/unpin_test.rs b/crates/cli/tests/unpin_test.rs index 7b35582f0..4a7702f00 100644 --- a/crates/cli/tests/unpin_test.rs +++ b/crates/cli/tests/unpin_test.rs @@ -18,7 +18,7 @@ mod unpin_local { assert .inner - .stderr(predicate::str::contains("unknown is not a built-in tool")); + .stderr(predicate::str::contains("unknown is not a built-in plugin")); } #[test] @@ -29,7 +29,7 @@ mod unpin_local { config .versions .get_or_insert(Default::default()) - .insert(Id::raw("node"), UnresolvedVersionSpec::Canary); + .insert(Id::raw("node"), UnresolvedVersionSpec::Canary.into()); }) .unwrap(); @@ -52,7 +52,7 @@ mod unpin_local { config .versions .get_or_insert(Default::default()) - .insert(Id::raw("bun"), UnresolvedVersionSpec::Canary); + .insert(Id::raw("bun"), UnresolvedVersionSpec::Canary.into()); }) .unwrap(); @@ -66,7 +66,7 @@ mod unpin_local { assert_eq!( config.versions, - BTreeMap::from_iter([(Id::raw("bun"), UnresolvedVersionSpec::Canary)]) + BTreeMap::from_iter([(Id::raw("bun"), UnresolvedVersionSpec::Canary.into())]) ); } } @@ -82,7 +82,7 @@ mod unpin_global { config .versions .get_or_insert(Default::default()) - .insert(Id::raw("node"), UnresolvedVersionSpec::Canary); + .insert(Id::raw("node"), UnresolvedVersionSpec::Canary.into()); }) .unwrap(); @@ -109,7 +109,7 @@ mod unpin_user { config .versions .get_or_insert(Default::default()) - .insert(Id::raw("node"), UnresolvedVersionSpec::Canary); + .insert(Id::raw("node"), UnresolvedVersionSpec::Canary.into()); }) .unwrap(); diff --git a/crates/codegen/Cargo.toml b/crates/codegen/Cargo.toml index 69e4d024a..bce5bffda 100644 --- a/crates/codegen/Cargo.toml +++ b/crates/codegen/Cargo.toml @@ -9,8 +9,8 @@ publish = false dist = false [dependencies] -proto_core = { version = "0.45.4", path = "../core" } -proto_pdk_api = { version = "0.25.3", path = "../pdk-api", features = [ +proto_core = { version = "0.47.0", path = "../core" } +proto_pdk_api = { version = "0.26.0", path = "../pdk-api", features = [ "schematic", ] } schematic = { workspace = true, features = [ diff --git a/crates/codegen/src/main.rs b/crates/codegen/src/main.rs index ecd163c1a..d92e75650 100644 --- a/crates/codegen/src/main.rs +++ b/crates/codegen/src/main.rs @@ -13,6 +13,7 @@ fn generate_types() { generator.add::(); generator.add::(); generator.add::(); + generator.add::(); // version_spec generator.add::(); @@ -31,9 +32,11 @@ fn generate_types() { // proto generator.add::(); generator.add::(); - generator.add::(); generator.add::(); - generator.add::(); + generator.add::(); + generator.add::(); + generator.add::(); + generator.add::(); generator.add::(); generator.add::(); generator.add::(); diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 9a8e5cf62..be4173c80 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_core" -version = "0.45.4" +version = "0.47.0" edition = "2021" license = "MIT" description = "Core proto APIs." @@ -8,15 +8,15 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -proto_pdk_api = { version = "0.25.3", path = "../pdk-api", features = [ +proto_pdk_api = { version = "0.26.0", path = "../pdk-api", features = [ "schematic", ] } proto_shim = { version = "0.6.0", path = "../shim" } -system_env = { version = "0.7.1", path = "../system-env" } -version_spec = { version = "0.7.2", path = "../version-spec", features = [ +system_env = { version = "0.7.2", path = "../system-env" } +version_spec = { version = "0.8.0", path = "../version-spec", features = [ "schematic", ] } -warpgate = { version = "0.21.1", path = "../warpgate", features = [ +warpgate = { version = "0.22.1", path = "../warpgate", features = [ "schematic", ] } clap = { workspace = true, optional = true } @@ -30,6 +30,7 @@ once_cell = { workspace = true } regex = { workspace = true } reqwest = { workspace = true } rustc-hash = { workspace = true } +scc = { workspace = true } schematic = { workspace = true, features = [ "config", "env", diff --git a/crates/core/src/checksum/checksum_error.rs b/crates/core/src/checksum/checksum_error.rs new file mode 100644 index 000000000..c836d9124 --- /dev/null +++ b/crates/core/src/checksum/checksum_error.rs @@ -0,0 +1,19 @@ +use miette::Diagnostic; +use starbase_styles::{Style, Stylize}; +use thiserror::Error; + +#[derive(Error, Debug, Diagnostic)] +pub enum ProtoChecksumError { + #[diagnostic(code(proto::checksum::minisign))] + #[error("Failed to verify minisign checksum.")] + Minisign { + #[source] + error: Box, + }, + + #[diagnostic(code(proto::checksum::missing_public_key))] + #[error( + "A {} is required to verify this tool. This setting must be implemented in the plugin.", "checksum_public_key".style(Style::Property) + )] + MissingChecksumPublicKey, +} diff --git a/crates/core/src/checksum/minisign.rs b/crates/core/src/checksum/minisign.rs index 49659114b..ec2ee4ac1 100644 --- a/crates/core/src/checksum/minisign.rs +++ b/crates/core/src/checksum/minisign.rs @@ -1,4 +1,4 @@ -use crate::error::ProtoError; +use super::checksum_error::ProtoChecksumError; use minisign_verify::*; use starbase_utils::fs; use std::path::Path; @@ -9,7 +9,7 @@ pub fn verify_checksum( checksum_file: &Path, checksum_public_key: &str, ) -> miette::Result { - let handle_error = |error: Error| ProtoError::Minisign { + let handle_error = |error: Error| ProtoChecksumError::Minisign { error: Box::new(error), }; diff --git a/crates/core/src/checksum/mod.rs b/crates/core/src/checksum/mod.rs index 186e3b580..8d68382a9 100644 --- a/crates/core/src/checksum/mod.rs +++ b/crates/core/src/checksum/mod.rs @@ -1,7 +1,8 @@ +mod checksum_error; mod minisign; mod sha256; -use crate::error::ProtoError; +pub use checksum_error::*; use std::path::Path; #[tracing::instrument(skip_all)] @@ -14,7 +15,7 @@ pub fn verify_checksum( Some("minisig" | "minisign") => minisign::verify_checksum( download_file, checksum_file, - checksum_public_key.ok_or_else(|| ProtoError::MissingChecksumPublicKey)?, + checksum_public_key.ok_or(ProtoChecksumError::MissingChecksumPublicKey)?, ), _ => sha256::verify_checksum(download_file, checksum_file), } diff --git a/crates/core/src/proto_config.rs b/crates/core/src/config.rs similarity index 94% rename from crates/core/src/proto_config.rs rename to crates/core/src/config.rs index 010e800f5..e20272837 100644 --- a/crates/core/src/proto_config.rs +++ b/crates/core/src/config.rs @@ -1,5 +1,6 @@ -use crate::error::ProtoError; +use crate::config_error::ProtoConfigError; use crate::helpers::ENV_VAR_SUB; +use crate::tool_spec::{Backend, ToolSpec}; use indexmap::IndexMap; use once_cell::sync::OnceCell; use rustc_hash::FxHashMap; @@ -20,8 +21,8 @@ use std::fmt::Debug; use std::hash::Hash; use std::path::{Path, PathBuf}; use std::sync::Arc; +use system_env::{SystemOS, SystemPackageManager}; use tracing::{debug, instrument}; -use version_spec::*; use warpgate::{HttpOptions, Id, PluginLocator, UrlLocator}; pub const PROTO_CONFIG_NAME: &str = ".prototools"; @@ -173,13 +174,28 @@ fn default_builtin_plugins(_context: &()) -> DefaultValueResult Ok(Some(BuiltinPlugins::Enabled(true))) } +#[derive(Clone, Config, Debug, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct ProtoBuildConfig { + pub exclude_packages: Vec, + + #[setting(default = true)] + pub install_system_packages: bool, + + pub system_package_manager: FxHashMap>, + + pub write_log_file: bool, +} + #[derive(Clone, Config, Debug, Serialize)] #[config(allow_unknown_fields)] #[serde(rename_all = "kebab-case")] pub struct ProtoToolConfig { #[setting(merge = merge::merge_btreemap)] #[serde(skip_serializing_if = "BTreeMap::is_empty")] - pub aliases: BTreeMap, + pub aliases: BTreeMap, + + pub backend: Option, #[setting(nested, merge = merge_indexmap)] #[serde(skip_serializing_if = "IndexMap::is_empty")] @@ -215,6 +231,9 @@ pub struct ProtoSettingsConfig { #[setting(env = "PROTO_AUTO_INSTALL", parse_env = env::parse_bool)] pub auto_install: bool, + #[setting(nested)] + pub build: ProtoBuildConfig, + #[setting(default = default_builtin_plugins)] pub builtin_plugins: BuiltinPlugins, @@ -254,7 +273,7 @@ pub struct ProtoConfig { #[setting(merge = merge::merge_btreemap)] #[serde(flatten)] - pub versions: BTreeMap, + pub versions: BTreeMap, #[setting(merge = merge_fxhashmap)] #[serde(flatten, skip_serializing)] @@ -306,7 +325,7 @@ impl ProtoConfig { pub fn builtin_proto_plugin(&self) -> PluginLocator { PluginLocator::Url(Box::new(UrlLocator { - url: "https://github.com/moonrepo/plugins/releases/download/proto_tool-v0.4.0/proto_tool.wasm".into() + url: "https://github.com/moonrepo/plugins/releases/download/proto_tool-v0.5.0/proto_tool.wasm".into() })) } @@ -320,7 +339,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw("bun"), PluginLocator::Url(Box::new(UrlLocator { - url: "https://github.com/moonrepo/plugins/releases/download/bun_tool-v0.14.1/bun_tool.wasm".into() + url: "https://github.com/moonrepo/plugins/releases/download/bun_tool-v0.15.0/bun_tool.wasm".into() })) ); } @@ -329,7 +348,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw("deno"), PluginLocator::Url(Box::new(UrlLocator { - url: "https://github.com/moonrepo/plugins/releases/download/deno_tool-v0.14.0/deno_tool.wasm".into() + url: "https://github.com/moonrepo/plugins/releases/download/deno_tool-v0.15.0/deno_tool.wasm".into() })) ); } @@ -338,7 +357,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw("go"), PluginLocator::Url(Box::new(UrlLocator { - url: "https://github.com/moonrepo/plugins/releases/download/go_tool-v0.15.0/go_tool.wasm".into() + url: "https://github.com/moonrepo/plugins/releases/download/go_tool-v0.16.0/go_tool.wasm".into() })) ); } @@ -347,7 +366,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw("moon"), PluginLocator::Url(Box::new(UrlLocator { - url: "https://github.com/moonrepo/plugins/releases/download/moon_tool-v0.1.0/moon_tool.wasm".into() + url: "https://github.com/moonrepo/plugins/releases/download/moon_tool-v0.3.0/moon_tool.wasm".into() })) ); } @@ -356,7 +375,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw("node"), PluginLocator::Url(Box::new(UrlLocator { - url: "https://github.com/moonrepo/plugins/releases/download/node_tool-v0.14.0/node_tool.wasm".into() + url: "https://github.com/moonrepo/plugins/releases/download/node_tool-v0.16.0/node_tool.wasm".into() })) ); } @@ -366,7 +385,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw(depman), PluginLocator::Url(Box::new(UrlLocator { - url: "https://github.com/moonrepo/plugins/releases/download/node_depman_tool-v0.14.2/node_depman_tool.wasm".into() + url: "https://github.com/moonrepo/plugins/releases/download/node_depman_tool-v0.15.0/node_depman_tool.wasm".into() })) ); } @@ -376,7 +395,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw("python"), PluginLocator::Url(Box::new(UrlLocator { - url: "https://github.com/moonrepo/plugins/releases/download/python_tool-v0.13.1/python_tool.wasm".into() + url: "https://github.com/moonrepo/plugins/releases/download/python_tool-v0.14.0/python_tool.wasm".into() })) ); } @@ -385,7 +404,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw("uv"), PluginLocator::Url(Box::new(UrlLocator { - url: "https://github.com/moonrepo/plugins/releases/download/python_uv_tool-v0.1.0/python_uv_tool.wasm".into() + url: "https://github.com/moonrepo/plugins/releases/download/python_uv_tool-v0.2.0/python_uv_tool.wasm".into() })) ); } @@ -394,7 +413,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw("ruby"), PluginLocator::Url(Box::new(UrlLocator { - url: "https://github.com/moonrepo/plugins/releases/download/ruby_tool-v0.1.0/ruby_tool.wasm".into() + url: "https://github.com/moonrepo/plugins/releases/download/ruby_tool-v0.2.0/ruby_tool.wasm".into() })) ); } @@ -403,7 +422,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw("rust"), PluginLocator::Url(Box::new(UrlLocator { - url: "https://github.com/moonrepo/plugins/releases/download/rust_tool-v0.12.1/rust_tool.wasm".into() + url: "https://github.com/moonrepo/plugins/releases/download/rust_tool-v0.13.0/rust_tool.wasm".into() })) ); } @@ -412,7 +431,7 @@ impl ProtoConfig { self.plugins.insert( Id::raw(SCHEMA_PLUGIN_KEY), PluginLocator::Url(Box::new(UrlLocator { - url: "https://github.com/moonrepo/plugins/releases/download/schema_tool-v0.16.4/schema_tool.wasm".into() + url: "https://github.com/moonrepo/plugins/releases/download/schema_tool-v0.17.0/schema_tool.wasm".into() })) ); } @@ -550,7 +569,7 @@ impl ProtoConfig { let env_file_path = make_absolute(env_file, path); if !env_file_path.exists() { - return Err(ProtoError::MissingEnvFile { + return Err(ProtoConfigError::MissingEnvFile { path: env_file_path, config: env_file.to_owned(), config_path: path.to_path_buf(), @@ -693,7 +712,7 @@ impl ProtoConfig { error: Box::new(inner), } .into(), - other => ProtoError::EnvFileParseFailed { + other => ProtoConfigError::FailedParseEnvFile { path: path.to_path_buf(), error: Box::new(other), } diff --git a/crates/core/src/config_error.rs b/crates/core/src/config_error.rs new file mode 100644 index 000000000..3e5d48bd1 --- /dev/null +++ b/crates/core/src/config_error.rs @@ -0,0 +1,31 @@ +use miette::Diagnostic; +use starbase_styles::{Style, Stylize}; +use std::path::PathBuf; +use thiserror::Error; + +#[derive(Error, Debug, Diagnostic)] +pub enum ProtoConfigError { + #[diagnostic(code(proto::config::env_parse_failed))] + #[error( + "Failed to parse .env file {}.", + .path.style(Style::Path), + )] + FailedParseEnvFile { + path: PathBuf, + #[source] + error: Box, + }, + + #[diagnostic(code(proto::config::missing_env_file))] + #[error( + "The .env file {} does not exist. This was configured as {} in the config {}.", + .path.style(Style::Path), + .config.style(Style::File), + .config_path.style(Style::Path), + )] + MissingEnvFile { + path: PathBuf, + config: String, + config_path: PathBuf, + }, +} diff --git a/crates/core/src/proto.rs b/crates/core/src/env.rs similarity index 93% rename from crates/core/src/proto.rs rename to crates/core/src/env.rs index 008fb4177..ae347277e 100644 --- a/crates/core/src/proto.rs +++ b/crates/core/src/env.rs @@ -1,9 +1,9 @@ -use crate::error::ProtoError; -use crate::helpers::is_offline; -use crate::layout::Store; -use crate::proto_config::{ +use crate::config::{ ConfigMode, PinLocation, ProtoConfig, ProtoConfigFile, ProtoConfigManager, PROTO_CONFIG_NAME, }; +use crate::env_error::ProtoEnvError; +use crate::helpers::is_offline; +use crate::layout::Store; use once_cell::sync::OnceCell; use starbase_console::{Console, EmptyReporter}; use starbase_utils::dirs::home_dir; @@ -33,8 +33,10 @@ pub struct ProtoEnvironment { impl ProtoEnvironment { pub fn new() -> miette::Result { - let home = home_dir().ok_or(ProtoError::MissingHomeDir)?; - let mut root = path_var("PROTO_HOME").unwrap_or_else(|| home.join(".proto")); + let home = home_dir().ok_or(ProtoEnvError::MissingHomeDir)?; + let mut root = path_var("PROTO_HOME") + .or_else(|| path_var("XDG_DATA_HOME").map(|xdg| xdg.join("proto"))) + .unwrap_or_else(|| home.join(".proto")); if let Ok(rel_root) = root.strip_prefix("~") { root = home.join(rel_root); @@ -62,8 +64,7 @@ impl ProtoEnvironment { Ok(ProtoEnvironment { config_mode: ConfigMode::Upwards, - working_dir: env::current_dir() - .expect("Unable to determine current working directory!"), + working_dir: env::current_dir().map_err(|_| ProtoEnvError::MissingWorkingDir)?, env_mode: env::var("PROTO_ENV").ok(), home_dir: home.to_owned(), config_manager: Arc::new(OnceCell::new()), diff --git a/crates/core/src/env_error.rs b/crates/core/src/env_error.rs new file mode 100644 index 000000000..567445d9e --- /dev/null +++ b/crates/core/src/env_error.rs @@ -0,0 +1,17 @@ +use miette::Diagnostic; +use thiserror::Error; + +#[derive(Error, Debug, Diagnostic)] +pub enum ProtoEnvError { + #[diagnostic(code(proto::env::home_dir))] + #[error("Unable to determine your home directory.")] + MissingHomeDir, + + #[diagnostic(code(proto::env::working_dir))] + #[error("Unable to determine current working directory!")] + MissingWorkingDir, + + #[diagnostic(code(proto::offline))] + #[error("Internet connection required, unable to download, install, or run tools.")] + RequiredInternetConnection, +} diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs deleted file mode 100644 index 778200a49..000000000 --- a/crates/core/src/error.rs +++ /dev/null @@ -1,212 +0,0 @@ -use crate::proto_config::PROTO_CONFIG_NAME; -use miette::Diagnostic; -use starbase_styles::{Style, Stylize}; -use std::path::PathBuf; -use thiserror::Error; -use warpgate::Id; - -#[derive(Error, Debug, Diagnostic)] -pub enum ProtoError { - #[error("{0}")] - Message(String), - - #[diagnostic(code(proto::tool::invalid_dir))] - #[error("{tool} inventory directory has been overridden with {} but it's not an absolute path. Only absolute paths are supported.", .dir.style(Style::Path))] - AbsoluteInventoryDir { tool: String, dir: PathBuf }, - - #[diagnostic(code(proto::tool::install_failed))] - #[error("Failed to install {tool}. {error}")] - InstallFailed { tool: String, error: String }, - - #[diagnostic(code(proto::tool::build_failed))] - #[error("Failed to build {tool} from {}: {status}", .url.style(Style::Url))] - BuildFailed { - tool: String, - url: String, - status: String, - }, - - #[diagnostic(code(proto::misc::offline))] - #[error("Internet connection required, unable to download, install, or run tools.")] - InternetConnectionRequired, - - #[diagnostic(code(proto::misc::offline_version_required))] - #[error( - "Internet connection required to load and resolve a valid version. To work around this:\n - Pass a semantic version explicitly: {}\n - Execute the non-shim binaries instead: {}", - .command.style(Style::Shell), - .bin_dir.style(Style::Path) - )] - InternetConnectionRequiredForVersion { command: String, bin_dir: PathBuf }, - - #[diagnostic(code(proto::verify::missing_public_key))] - #[error( - "A {} is required to verify this tool.", "checksum_public_key".style(Style::Property) - )] - MissingChecksumPublicKey, - - #[diagnostic(code(proto::verify::invalid_checksum))] - #[error( - "Checksum has failed for {}, which was verified using {}.", .download.style(Style::Path), .checksum.style(Style::Path) - )] - InvalidChecksum { - checksum: PathBuf, - download: PathBuf, - }, - - #[diagnostic(code(proto::minimum_version_requirement))] - #[error( - "Unable to use the {tool} plugin with identifier {}, as it requires a minimum proto version of {}, but found {} instead.", - .id.to_string().style(Style::Id), - .expected.style(Style::Hash), - .actual.style(Style::Hash) - )] - InvalidMinimumVersion { - tool: String, - id: Id, - expected: String, - actual: String, - }, - - #[diagnostic(code(proto::env::home_dir))] - #[error("Unable to determine your home directory.")] - MissingHomeDir, - - #[diagnostic(code(proto::shim::missing_binary))] - #[error( - "Unable to create shims as the {} binary cannot be found.\nLooked in the {} environment variable and {} directory.", - "proto-shim".style(Style::Id), - "PROTO_HOME".style(Style::Property), - .bin_dir.style(Style::Path), - )] - MissingShimBinary { bin_dir: PathBuf }, - - #[diagnostic(code(proto::execute::missing_file))] - #[error("Unable to find an executable for {tool}, expected file {} does not exist.", .path.style(Style::Path))] - MissingToolExecutable { tool: String, path: PathBuf }, - - #[diagnostic(code(proto::tool::required))] - #[error( - "This project requires {tool} {}, but this version has not been installed. Install it with {}, or enable the {} setting to automatically install missing versions!", - .version.style(Style::Hash), - .command.style(Style::Shell), - "auto-install".style(Style::Property), - )] - MissingToolForRun { - tool: String, - version: String, - command: String, - }, - - #[diagnostic(code(proto::tool::required))] - #[error( - "This project requires {tool} {} (detected from {}), but this version has not been installed. Install it with {}, or enable the {} setting to automatically install missing versions!", - .version.style(Style::Hash), - .path.style(Style::Path), - .command.style(Style::Shell), - "auto-install".style(Style::Property), - )] - MissingToolForRunWithSource { - tool: String, - version: String, - command: String, - path: PathBuf, - }, - - #[diagnostic(code(proto::tool::uninstall_failed))] - #[error("Failed to uninstall {tool}. {error}")] - UninstallFailed { tool: String, error: String }, - - #[diagnostic(code(proto::tool::unknown))] - #[error( - "Unable to proceed, {} is not a built-in tool and has not been configured with {} in a {} file.\n\nLearn more about plugins: {}\nSearch community plugins: {}", - .id.to_string().style(Style::Id), - "[plugins]".style(Style::Property), - PROTO_CONFIG_NAME.style(Style::File), - "https://moonrepo.dev/docs/proto/plugins".style(Style::Url), - format!("proto plugin search {}", .id).style(Style::Shell), - )] - UnknownTool { id: Id }, - - #[diagnostic(code(proto::prebuilt::unsupported))] - #[error("Downloading a pre-built is not supported for {tool}. Try building from source by passing {}.", "--build".style(Style::Shell))] - UnsupportedDownloadPrebuilt { tool: String }, - - #[diagnostic(code(proto::build::unsupported))] - #[error("Building from source is not supported for {tool}. Try downloading a pre-built by passing {}.", "--no-build".style(Style::Shell))] - UnsupportedBuildFromSource { tool: String }, - - #[diagnostic( - code(proto::version::undetected), - help = "Has the tool been installed?" - )] - #[error( - "Failed to detect an applicable version to run {tool} with. Try pinning a version with {} or passing the version as an argument.", - "proto pin".style(Style::Shell), - )] - VersionDetectFailed { tool: String }, - - #[diagnostic( - code(proto::version::unresolved), - help = "Does this version exist and has it been released?" - )] - #[error( - "Failed to resolve {} to a valid supported version for {tool}.", - .version.style(Style::Hash), - )] - VersionResolveFailed { tool: String, version: String }, - - #[diagnostic(code(proto::http))] - #[error("Failed to request {}.", .url.style(Style::Url))] - Http { - url: String, - #[source] - error: Box, - }, - - #[diagnostic(code(proto::verify::minisign))] - #[error("Failed to verify minisign checksum.")] - Minisign { - #[source] - error: Box, - }, - - #[diagnostic(code(proto::version::invalid))] - #[error("Invalid version or requirement {}.", .version.style(Style::Hash))] - VersionSpec { - version: String, - #[source] - error: Box, - }, - - #[diagnostic(code(proto::shim::create_failed))] - #[error("Failed to create shim {}.", .path.style(Style::Path))] - CreateShimFailed { - path: PathBuf, - #[source] - error: Box, - }, - - #[diagnostic(code(proto::env::missing_file))] - #[error( - "The .env file {} does not exist. This was configured as {} in the config {}.", - .path.style(Style::Path), - .config.style(Style::File), - .config_path.style(Style::Path), - )] - MissingEnvFile { - path: PathBuf, - config: String, - config_path: PathBuf, - }, - - #[diagnostic(code(proto::env::parse_failed))] - #[error( - "Failed to parse .env file {}.", - .path.style(Style::Path), - )] - EnvFileParseFailed { - path: PathBuf, - #[source] - error: Box, - }, -} diff --git a/crates/core/src/flow/build.rs b/crates/core/src/flow/build.rs index fce29820c..537dda3c5 100644 --- a/crates/core/src/flow/build.rs +++ b/crates/core/src/flow/build.rs @@ -1,75 +1,104 @@ use super::build_error::*; use super::install::{InstallPhase, OnPhaseFn}; +use crate::config::ProtoBuildConfig; +use crate::env::{ProtoConsole, ProtoEnvironment}; use crate::helpers::extract_filename_from_url; -use crate::proto::{ProtoConsole, ProtoEnvironment}; +use crate::utils::process::{self, ProcessResult}; +use crate::utils::{archive, git}; use iocraft::prelude::{element, FlexDirection, View}; use miette::IntoDiagnostic; use proto_pdk_api::{ BuildInstruction, BuildInstructionsOutput, BuildRequirement, GitSource, SourceLocation, }; use rustc_hash::FxHashMap; -use schematic::color::apply_style_tags; +use schematic::color::{apply_style_tags, remove_style_tags}; use semver::{Version, VersionReq}; -use starbase_archive::Archiver; use starbase_console::ui::{ Confirm, Container, Entry, ListCheck, ListItem, Section, Select, SelectOption, Style, StyledText, }; -use starbase_styles::color; -use starbase_utils::{fs, net}; +use starbase_utils::fs::LOCK_FILE; +use starbase_utils::{env::is_ci, fs, net}; use std::env; use std::path::{Path, PathBuf}; -use std::process::{Output, Stdio}; +use std::sync::{Arc, OnceLock}; use system_env::{ find_command_on_path, is_command_on_path, DependencyConfig, DependencyName, System, + SystemPackageManager, }; use tokio::process::Command; -use tracing::{debug, error, trace}; +use tokio::sync::{Mutex, OwnedMutexGuard}; +use tracing::{debug, error}; use version_spec::{get_semver_regex, VersionSpec}; use warpgate::HttpClient; -pub struct InstallBuildOptions<'a> { - pub console: Option<&'a ProtoConsole>, +static BUILD_LOCKS: OnceLock>>> = OnceLock::new(); + +pub struct BuilderOptions<'a> { + pub config: &'a ProtoBuildConfig, + pub console: &'a ProtoConsole, pub http_client: &'a HttpClient, pub install_dir: &'a Path, pub on_phase_change: Option, pub skip_prompts: bool, + pub skip_ui: bool, pub system: System, pub temp_dir: &'a Path, pub version: VersionSpec, } -struct StepManager<'a> { +enum BuilderStepOperation { + Checkpoint(String), + Command(Arc), +} + +struct BuilderStep { + title: String, + ops: Vec, +} + +pub struct Builder<'a> { + pub options: BuilderOptions<'a>, errors: u8, - options: &'a InstallBuildOptions<'a>, + steps: Vec, } -impl StepManager<'_> { - pub fn new<'a>(options: &'a InstallBuildOptions<'a>) -> StepManager<'a> { - StepManager { errors: 0, options } +impl Builder<'_> { + pub fn new(options: BuilderOptions<'_>) -> Builder<'_> { + Builder { + errors: 0, + options, + steps: vec![], + } } - pub fn has_errors(&self) -> bool { - self.errors > 0 + pub fn get_system(&self) -> &System { + &self.options.system } - #[allow(dead_code)] - pub fn is_ci(&self) -> bool { - env::var("CI").is_ok_and(|v| !v.is_empty()) + pub fn has_errors(&self) -> bool { + self.errors > 0 } - pub fn render_header(&self, title: impl AsRef) -> miette::Result<()> { + pub fn render_header(&mut self, title: impl AsRef) -> miette::Result<()> { let title = title.as_ref(); - if let Some(console) = &self.options.console { + self.errors = 0; + self.steps.push(BuilderStep { + title: title.to_owned(), + ops: vec![], + }); + + if self.options.skip_ui { + debug!("{title}"); + } else { + let console = &self.options.console; console.out.write_newline()?; console.render(element! { Container { Section(title) } })?; - } else { - debug!("{title}"); } Ok(()) @@ -78,13 +107,7 @@ impl StepManager<'_> { pub fn render_check(&mut self, message: impl AsRef, passed: bool) -> miette::Result<()> { let message = message.as_ref(); - if let Some(console) = &self.options.console { - console.render(element! { - ListCheck(checked: passed) { - StyledText(content: message) - } - })?; - } else { + if self.options.skip_ui { let message = apply_style_tags(message); if passed { @@ -92,6 +115,12 @@ impl StepManager<'_> { } else { error!("{message}"); } + } else { + self.options.console.render(element! { + ListCheck(checked: passed) { + StyledText(content: message) + } + })?; } if !passed { @@ -101,39 +130,43 @@ impl StepManager<'_> { Ok(()) } - pub fn render_checkpoint(&self, message: impl AsRef) -> miette::Result<()> { + pub fn render_checkpoint(&mut self, message: impl AsRef) -> miette::Result<()> { let message = message.as_ref(); - if let Some(console) = &self.options.console { - console.render(element! { + if let Some(step) = &mut self.steps.last_mut() { + step.ops + .push(BuilderStepOperation::Checkpoint(message.to_owned())); + } + + if self.options.skip_ui { + debug!("{}", apply_style_tags(message)); + } else { + self.options.console.render(element! { ListItem(bullet: "❯".to_owned()) { StyledText(content: message) } })?; - } else { - debug!("{}", apply_style_tags(message)); } Ok(()) } pub async fn prompt_continue(&self, label: &str) -> miette::Result<()> { - if self.options.skip_prompts { + if self.options.skip_prompts || self.options.skip_ui { return Ok(()); } - if let Some(console) = &self.options.console { - let mut confirmed = false; + let mut confirmed = false; - console - .render_interactive(element! { - Confirm(label, on_confirm: &mut confirmed) - }) - .await?; + self.options + .console + .render_interactive(element! { + Confirm(label, on_confirm: &mut confirmed) + }) + .await?; - if !confirmed { - return Err(ProtoBuildError::Cancelled.into()); - } + if !confirmed { + return Err(ProtoBuildError::Cancelled.into()); } Ok(()) @@ -147,172 +180,174 @@ impl StepManager<'_> { ) -> miette::Result { let mut selected_index = default_index; - if self.options.skip_prompts { + if self.options.skip_prompts || self.options.skip_ui { return Ok(selected_index); } - if let Some(console) = &self.options.console { - console - .render_interactive(element! { - Select(label, options, on_index: &mut selected_index) - }) - .await?; - } + self.options + .console + .render_interactive(element! { + Select(label, options, default_index, on_index: &mut selected_index) + }) + .await?; Ok(selected_index) } -} -async fn spawn_command(command: &mut Command) -> std::io::Result { - let child = command.spawn()?; - let output = child.wait_with_output().await?; + pub async fn exec_command( + &mut self, + command: &mut Command, + piped: bool, + ) -> miette::Result> { + self.handle_process_result(if self.options.skip_ui || piped { + process::exec_command_piped(command).await? + } else { + process::exec_command(command).await? + }) + } - Ok(output) -} + pub async fn exec_command_with_privileges( + &mut self, + command: &mut Command, + elevated_program: Option<&str>, + piped: bool, + ) -> miette::Result> { + self.handle_process_result(if self.options.skip_ui || piped { + process::exec_command_with_privileges_piped(command, elevated_program).await? + } else { + process::exec_command_with_privileges(command, elevated_program).await? + }) + } -async fn exec_command(command: &mut Command) -> miette::Result { - let inner = command.as_std(); - let command_line = format!( - "{} {}", - inner.get_program().to_string_lossy(), - shell_words::join( - inner - .get_args() - .map(|arg| arg.to_string_lossy()) - .collect::>() - ) - ); + fn handle_process_result( + &mut self, + result: ProcessResult, + ) -> miette::Result> { + let result = Arc::new(result); - trace!( - cwd = ?inner.get_current_dir(), - env = ?inner.get_envs() - .filter_map(|(key, val)| val.map(|v| (key, v.to_string_lossy()))) - .collect::>(), - "Running command {}", color::shell(&command_line) - ); + if let Some(step) = &mut self.steps.last_mut() { + step.ops.push(BuilderStepOperation::Command(result.clone())); + } - let output = spawn_command(command) - .await - .map_err(|error| ProtoBuildError::CommandFailed { - command: command_line.clone(), - error: Box::new(error), - })?; + if result.exit_code > 0 { + return Err(process::ProtoProcessError::FailedCommandNonZeroExit { + command: result.command.clone(), + code: result.exit_code, + } + .into()); + } - let stderr = String::from_utf8(output.stderr).into_diagnostic()?; - let stdout = String::from_utf8(output.stdout).into_diagnostic()?; - let code = output.status.code().unwrap_or(-1); + Ok(result) + } - trace!( - code, - stderr, - stdout, - "Ran command {}", - color::shell(&command_line) - ); + pub fn write_log_file(&self, log_path: PathBuf) -> miette::Result<()> { + let mut output = vec![]; - if !output.status.success() { - return Err(ProtoBuildError::CommandNonZeroExit { - command: command_line, - code, - } - .into()); - } + for (i, step) in self.steps.iter().enumerate() { + output.push(format!("# Step {}: {}", i + 1, step.title)); + output.push("".into()); - Ok(stdout) -} + for op in &step.ops { + match op { + BuilderStepOperation::Checkpoint(title) => { + output.push(format!("## {}", remove_style_tags(title))); + } + BuilderStepOperation::Command(result) => { + output.push(format!("### `{}`", result.command)); + output.push("".into()); -async fn exec_command_with_privileges( - command: &mut Command, - elevated_program: Option<&str>, -) -> miette::Result { - match elevated_program { - Some(program) => { - let inner = command.as_std(); - - let mut sudo_command = Command::new(program); - sudo_command.arg(inner.get_program()); - sudo_command.args(inner.get_args()); - - for (key, value) in inner.get_envs() { - if let Some(value) = value { - sudo_command.env(key, value); - } else { - sudo_command.env_remove(key); - } - } + if let Some(cwd) = &result.working_dir { + output.push(format!("WORKING DIR: {}", cwd.display())); + output.push("".into()); + } - if let Some(dir) = inner.get_current_dir() { - sudo_command.current_dir(dir); - } + output.push(format!("EXIT CODE: {}", result.exit_code)); + output.push("".into()); + + output.push("STDERR:".into()); + + if result.stderr.is_empty() { + output.push("".into()); + } else { + output.push("```".into()); + output.push(result.stderr.trim().to_owned()); + output.push("```".into()); + } + + output.push("STDOUT:".into()); - exec_command(&mut sudo_command).await + if result.stdout.is_empty() { + output.push("".into()); + } else { + output.push("```".into()); + output.push(result.stdout.trim().to_owned()); + output.push("```".into()); + } + } + }; + + output.push("".into()); + } } - None => exec_command(command).await, + + fs::write_file(log_path, output.join("\n"))?; + + Ok(()) } -} -async fn exec_command_piped(command: &mut Command) -> miette::Result { - exec_command(command.stderr(Stdio::piped()).stdout(Stdio::piped())).await + pub async fn acquire_lock(&self, pm: &SystemPackageManager) -> OwnedMutexGuard<()> { + let locks = BUILD_LOCKS.get_or_init(scc::HashMap::default); + let entry = locks.entry(pm.to_string()).or_default(); + + entry.get().clone().lock_owned().await + } } async fn checkout_git_repo( git: &GitSource, cwd: &Path, - step: &StepManager<'_>, + builder: &mut Builder<'_>, ) -> miette::Result<()> { if cwd.join(".git").exists() { - exec_command( - Command::new("git") - .args(["pull", "--ff", "--prune"]) - .current_dir(cwd), - ) - .await?; + builder.exec_command(&mut git::new_pull(cwd), false).await?; return Ok(()); } fs::create_dir_all(cwd)?; - exec_command( - Command::new("git") - .args(if git.submodules { - vec!["clone", "--recurse-submodules"] - } else { - vec!["clone"] - }) - .args(["--depth", "1"]) - .arg(&git.url) - .arg(".") - .current_dir(cwd), - ) - .await?; + builder + .exec_command(&mut git::new_clone(git, cwd), false) + .await?; if let Some(reference) = &git.reference { - step.render_checkpoint(format!("Checking out reference {}", reference))?; - - exec_command( - Command::new("git") - .arg("checkout") - .arg(reference) - .current_dir(cwd), - ) - .await?; + builder.render_checkpoint(format!("Checking out reference {}", reference))?; + + builder + .exec_command(&mut git::new_checkout(reference, cwd), false) + .await?; } Ok(()) } -// STEP 1 +// STEP 0 -pub async fn install_system_dependencies( +pub fn log_build_information( + builder: &mut Builder, build: &BuildInstructionsOutput, - options: &InstallBuildOptions<'_>, ) -> miette::Result<()> { - let mut step = StepManager::new(options); - let system = &options.system; + let system = &builder.options.system; - if let Some(console) = &options.console { - console.render(element! { + if builder.options.skip_ui { + debug!( + os = ?system.os, + arch = ?system.arch, + pm = ?system.manager, + "Gathering system information", + ); + } else { + builder.options.console.render(element! { Container { Section(title: "Build information") View(padding_left: 2, flex_direction: FlexDirection::Column) { @@ -323,8 +358,8 @@ pub async fn install_system_dependencies( Entry(name: "Package manager", content: pm.to_string()) } })) - Entry(name: "Version", value: element! { - StyledText(content: options.version.to_string(), style: Style::Hash) + Entry(name: "Target version", value: element! { + StyledText(content: builder.options.version.to_string(), style: Style::Hash) }.into_any()) #(build.help_url.as_ref().map(|url| { element! { @@ -336,28 +371,26 @@ pub async fn install_system_dependencies( } } })?; - } else { - debug!( - os = ?system.os, - arch = ?system.arch, - pm = ?system.manager, - "Gathering system information", - ); } - let Some(pm) = system.manager else { - return Ok(()); - }; + Ok(()) +} - step.render_header("Installing system dependencies")?; +// STEP 1 - options.on_phase_change.as_ref().inspect(|func| { - func(InstallPhase::InstallDeps); - }); +pub async fn install_system_dependencies( + builder: &mut Builder<'_>, + build: &BuildInstructionsOutput, +) -> miette::Result<()> { + let Some(pm) = builder.options.system.manager else { + return Ok(()); + }; // Determine packages to install let pm_config = pm.get_config(); - let dep_configs = system.resolve_dependencies(&build.system_dependencies); + let dep_configs = builder + .get_system() + .resolve_dependencies(&build.system_dependencies); // 1) Check if packages have already been installed let mut not_installed_packages = FxHashMap::from_iter( @@ -367,15 +400,33 @@ pub async fn install_system_dependencies( .flatten(), ); - if let Some(mut list_args) = system - .get_list_packages_command(!options.skip_prompts) + for excluded in &builder.options.config.exclude_packages { + not_installed_packages.remove(excluded); + } + + if not_installed_packages.is_empty() { + return Ok(()); + } + + builder.render_header("Installing system dependencies")?; + + builder.options.on_phase_change.as_ref().inspect(|func| { + func(InstallPhase::InstallDeps); + }); + + if let Some(mut list_args) = builder + .get_system() + .get_list_packages_command(!builder.options.skip_prompts) .into_diagnostic()? { - step.render_checkpoint(format!("Checking {pm} installed packages"))?; + let _lock = builder.acquire_lock(&pm).await; + + builder.render_checkpoint(format!("Checking {pm} installed packages"))?; - let list_output = - exec_command_piped(Command::new(list_args.remove(0)).args(list_args)).await?; - let installed_packages = pm_config.list_parser.parse(&list_output); + let list_output = builder + .exec_command(Command::new(list_args.remove(0)).args(list_args), true) + .await?; + let installed_packages = pm_config.list_parser.parse(&list_output.stdout); let mut skipped_packages = FxHashMap::default(); not_installed_packages.retain(|name, constraint| { @@ -409,7 +460,7 @@ pub async fn install_system_dependencies( // Print packages that are already installed for (package, version) in skipped_packages { - step.render_check( + builder.render_check( match version { Some(version) => { format!("{package} v{version} already installed") @@ -423,7 +474,7 @@ pub async fn install_system_dependencies( // Print the packages that are not installed for (package, version) in ¬_installed_packages { - step.render_check( + builder.render_check( match version { Some(version) => { format!("{package} v{version} is not installed") @@ -434,6 +485,10 @@ pub async fn install_system_dependencies( )?; } + if not_installed_packages.is_empty() { + return Ok(()); + } + // 2) Prompt the user to choose an install strategy let mut elevated_command = pm.get_elevated_command(); let mut select_options = vec![ @@ -449,12 +504,12 @@ pub async fn install_system_dependencies( ))); // Always run with elevated in CI - if env::var("CI").is_ok() { + if is_ci() { default_index += 1; } } - match step + match builder .prompt_select("Install missing packages?", select_options, default_index) .await? { @@ -471,17 +526,22 @@ pub async fn install_system_dependencies( } // 3) Update the current registry index - if let Some(mut index_args) = system - .get_update_index_command(!options.skip_prompts) + if let Some(mut index_args) = builder + .get_system() + .get_update_index_command(!builder.options.skip_prompts) .into_diagnostic()? { - step.render_checkpoint("Updating package manager index")?; + let _lock = builder.acquire_lock(&pm).await; - exec_command_with_privileges( - Command::new(index_args.remove(0)).args(index_args), - elevated_command, - ) - .await?; + builder.render_checkpoint("Updating package manager index")?; + + builder + .exec_command_with_privileges( + Command::new(index_args.remove(0)).args(index_args), + elevated_command, + false, + ) + .await?; } // Recreate the dep configs since they've been filtered @@ -495,17 +555,22 @@ pub async fn install_system_dependencies( .collect::>(); // 4) Install the missing packages - if let Some(mut install_args) = system - .get_install_packages_command(&dep_configs, !options.skip_prompts) + if let Some(mut install_args) = builder + .get_system() + .get_install_packages_command(&dep_configs, !builder.options.skip_prompts) .into_diagnostic()? { - step.render_checkpoint(format!("Installing {pm} packages",))?; + let _lock = builder.acquire_lock(&pm).await; - exec_command_with_privileges( - Command::new(install_args.remove(0)).args(install_args), - elevated_command, - ) - .await?; + builder.render_checkpoint(format!("Installing {pm} packages",))?; + + builder + .exec_command_with_privileges( + Command::new(install_args.remove(0)).args(install_args), + elevated_command, + false, + ) + .await?; } Ok(()) @@ -513,20 +578,26 @@ pub async fn install_system_dependencies( // STEP 2 -async fn get_command_version(cmd: &str, version_arg: &str) -> miette::Result { - let output = exec_command_piped(Command::new(cmd).arg(version_arg)).await?; +async fn get_command_version( + cmd: &str, + version_arg: &str, + builder: &mut Builder<'_>, +) -> miette::Result { + let output = builder + .exec_command(Command::new(cmd).arg(version_arg), true) + .await?; // Remove leading ^ and trailing $ let base_pattern = get_semver_regex().as_str(); let pattern = regex::Regex::new(&base_pattern[1..(base_pattern.len() - 1)]).unwrap(); let value = pattern - .find(&output) + .find(&output.stdout) .map(|res| res.as_str()) - .unwrap_or(&output); + .unwrap_or(&output.stdout); Ok( - Version::parse(value).map_err(|error| ProtoBuildError::VersionParseFailed { + Version::parse(value).map_err(|error| ProtoBuildError::FailedVersionParse { value: value.to_owned(), error: Box::new(error), })?, @@ -534,18 +605,16 @@ async fn get_command_version(cmd: &str, version_arg: &str) -> miette::Result, build: &BuildInstructionsOutput, - options: &InstallBuildOptions<'_>, ) -> miette::Result<()> { if build.requirements.is_empty() { return Ok(()); } - let mut step = StepManager::new(options); + builder.render_header("Checking requirements")?; - step.render_header("Checking requirements")?; - - options.on_phase_change.as_ref().inspect(|func| { + builder.options.on_phase_change.as_ref().inspect(|func| { func(InstallPhase::CheckRequirements); }); @@ -555,7 +624,7 @@ pub async fn check_requirements( debug!(cmd, "Checking if a command exists on PATH"); if let Some(cmd_path) = find_command_on_path(cmd) { - step.render_check( + builder.render_check( format!( "Command {cmd} exists on PATH: {}", cmd_path.display() @@ -563,7 +632,7 @@ pub async fn check_requirements( true, )?; } else { - step.render_check( + builder.render_check( format!("Command {cmd} does NOT exist on PATH, please install it and try again"), false, )?; @@ -576,35 +645,38 @@ pub async fn check_requirements( ); if is_command_on_path(cmd) { - let version = - get_command_version(cmd, version_arg.as_deref().unwrap_or("--version")) - .await?; + let version = get_command_version( + cmd, + version_arg.as_deref().unwrap_or("--version"), + builder, + ) + .await?; if version_req.matches(&version) { - step.render_check( + builder.render_check( format!("Command {cmd} meets the minimum required version of {version_req}"), true, )?; } else { - step.render_check( + builder.render_check( format!("Command {cmd} does NOT meet the minimum required version of {version_req}, found {version}"), false, )?; } } else { - step.render_check( + builder.render_check( format!("Command {cmd} does NOT exist on PATH, please install it and try again"), false, )?; } } BuildRequirement::ManualIntercept(url) => { - step.render_check( + builder.render_check( format!("Please read the following documentation before proceeding: {url}"), true, )?; - step.prompt_continue("Continue install?").await?; + builder.prompt_continue("Continue install?").await?; } BuildRequirement::GitConfigSetting(config_key, expected_value) => { debug!( @@ -612,17 +684,21 @@ pub async fn check_requirements( expected_value, "Checking if a Git config setting has the expected value" ); - let actual_value = - exec_command_piped(Command::new("git").args(["config", "--get", config_key])) - .await?; + let result = builder + .exec_command( + Command::new("git").args(["config", "--get", config_key]), + true, + ) + .await?; + let actual_value = &result.stdout; - if &actual_value == expected_value { - step.render_check( + if actual_value == expected_value { + builder.render_check( format!("Git config {config_key} matches the required value of {expected_value}"), true, )?; } else { - step.render_check( + builder.render_check( format!("Git config {config_key} does NOT match the required value or {expected_value}, found {actual_value}"), false, )?; @@ -631,39 +707,40 @@ pub async fn check_requirements( BuildRequirement::GitVersion(version_req) => { debug!("Checking if Git meets the required version of {version_req}"); - let version = get_command_version("git", "--version").await?; + let version = get_command_version("git", "--version", builder).await?; if version_req.matches(&version) { - step.render_check( + builder.render_check( format!("Git meets the minimum required version of {version_req}"), true, )?; } else { - step.render_check( + builder.render_check( format!("Git does NOT meet the minimum required version of {version_req}, found {version}"), false, )?; } } BuildRequirement::XcodeCommandLineTools => { - if options.system.os.is_mac() { + if builder.get_system().os.is_mac() { debug!("Checking if Xcode command line tools are installed"); - let result = - exec_command_piped(Command::new("xcode-select").arg("--version")).await; + let result = builder + .exec_command(Command::new("xcode-select").arg("--version"), true) + .await; - if result.is_err() || result.is_ok_and(|out| out.is_empty()) { - step.render_check( + if result.is_err() || result.is_ok_and(|out| out.stdout.is_empty()) { + builder.render_check( "Xcode command line tools are NOT installed, install them with xcode-select --install", false, )?; } else { - step.render_check("Xcode command line tools are installed", true)?; + builder.render_check("Xcode command line tools are installed", true)?; } } } BuildRequirement::WindowsDeveloperMode => { - if options.system.os.is_windows() { + if builder.get_system().os.is_windows() { debug!("Checking if Windows developer mode is enabled"); // Is this possible from the command line? @@ -672,7 +749,7 @@ pub async fn check_requirements( }; } - if step.has_errors() { + if builder.has_errors() { return Err(ProtoBuildError::RequirementsNotMet.into()); } @@ -682,78 +759,71 @@ pub async fn check_requirements( // STEP 3 pub async fn download_sources( + builder: &mut Builder<'_>, build: &BuildInstructionsOutput, - options: &InstallBuildOptions<'_>, ) -> miette::Result<()> { // Ensure the install directory is empty, otherwise Git will fail and // we also want to avoid colliding/stale artifacts. This should also // run if there's no source, as it's required for instructions! - fs::remove_dir_all(options.install_dir)?; - fs::create_dir_all(options.install_dir)?; + fs::remove_dir_all(builder.options.install_dir)?; + fs::create_dir_all(builder.options.install_dir)?; let Some(source) = &build.source else { return Ok(()); }; - let step = StepManager::new(options); - - step.render_header("Acquiring source files")?; + builder.render_header("Acquiring source files")?; match source { SourceLocation::Archive(archive) => { - let filename = extract_filename_from_url(&archive.url)?; - let download_file = options.temp_dir.join(&filename); - - // Download - options.on_phase_change.as_ref().inspect(|func| { - func(InstallPhase::Download { - url: archive.url.clone(), - file: filename.clone(), + if archive::should_unpack(archive, builder.options.install_dir)? { + let filename = extract_filename_from_url(&archive.url)?; + + // Download + builder.options.on_phase_change.as_ref().inspect(|func| { + func(InstallPhase::Download { + url: archive.url.clone(), + file: filename.clone(), + }); }); - }); - step.render_checkpoint(format!( - "Downloading archive from {}", - archive.url - ))?; + builder.render_checkpoint(format!( + "Downloading archive from {}", + archive.url + ))?; - net::download_from_url_with_client( - &archive.url, - &download_file, - options.http_client.to_inner(), - ) - .await?; + let download_file = archive::download( + archive, + builder.options.temp_dir, + builder.options.http_client.to_inner(), + ) + .await?; - // Unpack - options.on_phase_change.as_ref().inspect(|func| { - func(InstallPhase::Unpack { - file: filename.clone(), + // Unpack + builder.options.on_phase_change.as_ref().inspect(|func| { + func(InstallPhase::Unpack { + file: filename.clone(), + }); }); - }); - - step.render_checkpoint(format!( - "Unpacking archive to {}", - options.install_dir.display() - ))?; - let mut archiver = Archiver::new(options.install_dir, &download_file); + builder.render_checkpoint(format!( + "Unpacking archive to {}", + builder.options.install_dir.display() + ))?; - if let Some(prefix) = &archive.prefix { - archiver.set_prefix(prefix); + archive::unpack(archive, builder.options.install_dir, &download_file)?; } - - archiver.unpack_from_ext()?; } SourceLocation::Git(git) => { - options.on_phase_change.as_ref().inspect(|func| { + builder.options.on_phase_change.as_ref().inspect(|func| { func(InstallPhase::CloneRepository { url: git.url.clone(), }); }); - step.render_checkpoint(format!("Cloning repository {}", git.url))?; + builder.render_checkpoint(format!("Cloning repository {}", git.url))?; - checkout_git_repo(git, options.install_dir, &step).await?; + checkout_git_repo(git, builder.options.install_dir, builder).await?; } }; @@ -763,19 +833,17 @@ pub async fn download_sources( // STEP 4 pub async fn execute_instructions( + builder: &mut Builder<'_>, build: &BuildInstructionsOutput, - options: &InstallBuildOptions<'_>, proto: &ProtoEnvironment, ) -> miette::Result<()> { if build.instructions.is_empty() { return Ok(()); } - let step = StepManager::new(options); + builder.render_header("Executing build instructions")?; - step.render_header("Executing build instructions")?; - - options.on_phase_change.as_ref().inspect(|func| { + builder.options.on_phase_change.as_ref().inspect(|func| { func(InstallPhase::ExecuteInstructions); }); @@ -783,7 +851,7 @@ pub async fn execute_instructions( if path.is_absolute() { path.to_path_buf() } else { - options.install_dir.join(path) + builder.options.install_dir.join(path) } }; @@ -796,20 +864,20 @@ pub async fn execute_instructions( let prefix = format!("[{}/{total}]", index + 1); match instruction { - BuildInstruction::InstallBuilder(builder) => { - step.render_checkpoint(format!( + BuildInstruction::InstallBuilder(item) => { + builder.render_checkpoint(format!( "{prefix} Installing {} builder ({})", - builder.id, builder.git.url + item.id, item.git.url ))?; - let builder_dir = proto.store.builders_dir.join(&builder.id); + let builder_dir = proto.store.builders_dir.join(&item.id); - checkout_git_repo(&builder.git, &builder_dir, &step).await?; + checkout_git_repo(&item.git, &builder_dir, builder).await?; let main_exe_name = String::new(); let mut exes = FxHashMap::default(); - exes.extend(&builder.exes); - exes.insert(&main_exe_name, &builder.exe); + exes.extend(&item.exes); + exes.insert(&main_exe_name, &item.exe); for (exe_name, exe_rel_path) in exes { let exe_abs_path = builder_dir.join(exe_rel_path); @@ -817,7 +885,7 @@ pub async fn execute_instructions( if !exe_abs_path.exists() { return Err(ProtoBuildError::MissingBuilderExe { exe: exe_abs_path, - id: builder.id.clone(), + id: item.id.clone(), } .into()); } @@ -826,9 +894,9 @@ pub async fn execute_instructions( builder_exes.insert( if exe_name.is_empty() { - builder.id.clone() + item.id.clone() } else { - format!("{}:{exe_name}", builder.id) + format!("{}:{exe_name}", item.id) }, exe_abs_path, ); @@ -837,7 +905,7 @@ pub async fn execute_instructions( BuildInstruction::MakeExecutable(file) => { let file = make_absolute(file); - step.render_checkpoint(format!( + builder.render_checkpoint(format!( "{prefix} Making file {} executable", file.display() ))?; @@ -848,7 +916,7 @@ pub async fn execute_instructions( let from = make_absolute(from); let to = make_absolute(to); - step.render_checkpoint(format!( + builder.render_checkpoint(format!( "{prefix} Moving {} to {}", from.display(), to.display(), @@ -856,10 +924,31 @@ pub async fn execute_instructions( fs::rename(from, to)?; } + BuildInstruction::RemoveAllExcept(exceptions) => { + let dir = builder.options.install_dir; + + builder.render_checkpoint(format!( + "{prefix} Removing directory {} except for {}", + dir.display(), + exceptions + .iter() + .map(|p| format!("{}", p.display())) + .collect::>() + .join(", ") + ))?; + + let mut exclude = exceptions.to_owned(); + + // If we don't exclude the lock, it will trigger a permissions error + // when we attempt to remove it, failing the entire build + exclude.push(LOCK_FILE.into()); + + fs::remove_dir_all_except(dir, exclude)?; + } BuildInstruction::RemoveDir(dir) => { let dir = make_absolute(dir); - step.render_checkpoint(format!( + builder.render_checkpoint(format!( "{prefix} Removing directory {}", dir.display() ))?; @@ -869,7 +958,7 @@ pub async fn execute_instructions( BuildInstruction::RemoveFile(file) => { let file = make_absolute(file); - step.render_checkpoint(format!( + builder.render_checkpoint(format!( "{prefix} Removing file {}", file.display() ))?; @@ -878,18 +967,19 @@ pub async fn execute_instructions( } BuildInstruction::RequestScript(url) => { let filename = extract_filename_from_url(url)?; - let download_file = options.temp_dir.join(&filename); + let download_file = builder.options.temp_dir.join(&filename); - step.render_checkpoint(format!("{prefix} Requesting script {url}"))?; + builder + .render_checkpoint(format!("{prefix} Requesting script {url}"))?; net::download_from_url_with_client( url, &download_file, - options.http_client.to_inner(), + builder.options.http_client.to_inner(), ) .await?; - fs::rename(download_file, options.install_dir.join(filename))?; + fs::rename(download_file, builder.options.install_dir.join(filename))?; } BuildInstruction::RunCommand(cmd) => { let exe = if cmd.builder { @@ -902,27 +992,29 @@ pub async fn execute_instructions( PathBuf::from(&cmd.bin) }; - step.render_checkpoint(format!( + builder.render_checkpoint(format!( "{prefix} Running command {} {}", exe.file_name().unwrap().to_str().unwrap(), shell_words::join(&cmd.args) ))?; - exec_command( - Command::new(exe) - .args(&cmd.args) - .envs(&cmd.env) - .current_dir( - cmd.cwd - .as_deref() - .map(make_absolute) - .unwrap_or_else(|| options.install_dir.to_path_buf()), - ), - ) - .await?; + builder + .exec_command( + Command::new(exe) + .args(&cmd.args) + .envs(&cmd.env) + .current_dir( + cmd.cwd + .as_deref() + .map(make_absolute) + .unwrap_or_else(|| builder.options.install_dir.to_path_buf()), + ), + false, + ) + .await?; } BuildInstruction::SetEnvVar(key, value) => { - step.render_checkpoint(format!( + builder.render_checkpoint(format!( "{prefix} Setting environment variable {key} to {value}", ))?; diff --git a/crates/core/src/flow/build_error.rs b/crates/core/src/flow/build_error.rs index 7a2efad43..998619886 100644 --- a/crates/core/src/flow/build_error.rs +++ b/crates/core/src/flow/build_error.rs @@ -1,23 +1,18 @@ use miette::Diagnostic; use starbase_styles::{Style, Stylize}; -use std::io; use std::path::PathBuf; use thiserror::Error; #[derive(Error, Debug, Diagnostic)] pub enum ProtoBuildError { - #[diagnostic(code(proto::install::build::command_failed))] - #[error("Failed to execute command {}.", .command.style(Style::Shell))] - CommandFailed { - command: String, + #[diagnostic(code(proto::install::build::parse_version_failed))] + #[error("Failed to parse version from {}.", .value.style(Style::Symbol))] + FailedVersionParse { + value: String, #[source] - error: Box, + error: Box, }, - #[diagnostic(code(proto::install::build::command_failed))] - #[error("Command {} returned a {code} exit code.", .command.style(Style::Shell))] - CommandNonZeroExit { command: String, code: i32 }, - #[diagnostic(code(proto::install::build::missing_builder))] #[error("Builder {} has not been installed.", .id.style(Style::Id))] MissingBuilder { id: String }, @@ -26,14 +21,6 @@ pub enum ProtoBuildError { #[error("Executable {} from builder {} does not exist.", .exe.style(Style::Path), .id.style(Style::Id))] MissingBuilderExe { exe: PathBuf, id: String }, - #[diagnostic(code(proto::install::build::parse_version_failed))] - #[error("Failed to parse version from {}.", .value.style(Style::Symbol))] - VersionParseFailed { - value: String, - #[source] - error: Box, - }, - #[diagnostic(code(proto::install::build::unmet_requirements))] #[error("Build requirements have not been met, unable to proceed.\nPlease satisfy the requirements before attempting the build again.")] RequirementsNotMet, diff --git a/crates/core/src/flow/install.rs b/crates/core/src/flow/install.rs index 1f30a6850..4c825a3d9 100644 --- a/crates/core/src/flow/install.rs +++ b/crates/core/src/flow/install.rs @@ -1,12 +1,15 @@ use super::build::*; +pub use super::build_error::ProtoBuildError; +pub use super::install_error::ProtoInstallError; use crate::checksum::verify_checksum; -use crate::error::ProtoError; +use crate::env::ProtoConsole; +use crate::env_error::ProtoEnvError; use crate::helpers::{extract_filename_from_url, is_archive_file, is_offline}; -use crate::proto::ProtoConsole; use crate::tool::Tool; +use crate::utils::archive; use proto_pdk_api::*; use proto_shim::*; -use starbase_archive::Archiver; +use starbase_styles::color; use starbase_utils::net::DownloadOptions; use starbase_utils::{fs, net}; use std::path::Path; @@ -37,6 +40,7 @@ pub struct InstallOptions { pub on_download_chunk: Option, pub on_phase_change: Option, pub skip_prompts: bool, + pub skip_ui: bool, pub strategy: InstallStrategy, } @@ -98,7 +102,7 @@ impl Tool { return Ok(true); } - Err(ProtoError::InvalidChecksum { + Err(ProtoInstallError::InvalidChecksum { checksum: checksum_file.to_path_buf(), download: download_file.to_path_buf(), } @@ -120,7 +124,7 @@ impl Tool { ); if !self.plugin.has_func("build_instructions").await { - return Err(ProtoError::UnsupportedBuildFromSource { + return Err(ProtoInstallError::UnsupportedBuildFromSource { tool: self.get_name().to_owned(), } .into()); @@ -136,32 +140,95 @@ impl Tool { ) .await?; - let build_options = InstallBuildOptions { - console: options.console.as_ref(), + let mut system = System::default(); + let config = self.proto.load_config()?; + + if let Some(pm) = config.settings.build.system_package_manager.get(&system.os) { + if let Some(pm) = pm { + system.manager = Some(*pm); + + debug!( + "Overwriting system package manager to {} for {}", + pm, system.os + ); + } else { + system.manager = None; + + debug!( + "Disabling system package manager because {} was disabled for {}", + color::property("settings.build.system-package-manager"), + system.os + ); + } + } + + let mut builder = Builder::new(BuilderOptions { + config: &config.settings.build, + console: options + .console + .as_ref() + .expect("Console required for builder!"), install_dir, http_client: self.proto.get_plugin_loader()?.get_client()?, on_phase_change: options.on_phase_change.take(), skip_prompts: options.skip_prompts, - system: System::default(), + skip_ui: options.skip_ui, + system, temp_dir, version: self.get_resolved_version(), + }); + + // If any step in the build process fails, we should write + // a log file so that the user can debug it, otherwise the + // piped commands are hidden from the user + let handle_error = |result: miette::Result<()>, instance: &Builder| { + if + // Always write + instance.options.config.write_log_file || + // Only write if an error and no direct UI + result.is_err() && instance.options.skip_ui + { + instance.write_log_file( + self.proto + .working_dir + .join(format!("proto-{}-build.log", self.id)), + )?; + } + + result }; // The build process may require using itself to build itself, // so allow proto to use any available version instead of failing std::env::set_var(format!("{}_VERSION", self.get_env_var_prefix()), "*"); + // Step 0 + handle_error(log_build_information(&mut builder, &output), &builder)?; + // Step 1 - install_system_dependencies(&output, &build_options).await?; + if config.settings.build.install_system_packages { + handle_error( + install_system_dependencies(&mut builder, &output).await, + &builder, + )?; + } else { + debug!( + "Not installing system dependencies because {} was disabled", + color::property("settings.build.install-system-packages"), + ); + } // Step 2 - check_requirements(&output, &build_options).await?; + handle_error(check_requirements(&mut builder, &output).await, &builder)?; // Step 3 - download_sources(&output, &build_options).await?; + handle_error(download_sources(&mut builder, &output).await, &builder)?; // Step 4 - execute_instructions(&output, &build_options, &self.proto).await?; + handle_error( + execute_instructions(&mut builder, &output, &self.proto).await, + &builder, + )?; Ok(()) } @@ -181,7 +248,7 @@ impl Tool { ); if !self.plugin.has_func("download_prebuilt").await { - return Err(ProtoError::UnsupportedDownloadPrebuilt { + return Err(ProtoInstallError::UnsupportedDownloadPrebuilt { tool: self.get_name().to_owned(), } .into()); @@ -296,13 +363,11 @@ impl Tool { }); }); - let mut archiver = Archiver::new(install_dir, &download_file); - - if let Some(prefix) = &output.archive_prefix { - archiver.set_prefix(prefix); - } - - let (ext, unpacked_path) = archiver.unpack_from_ext()?; + let (ext, unpacked_path) = archive::unpack_raw( + install_dir, + &download_file, + output.archive_prefix.as_deref(), + )?; // If the archive was `.gz` without tar or other formats, // it's a single file, so assume a binary and update perms @@ -335,7 +400,7 @@ impl Tool { } if is_offline() { - return Err(ProtoError::InternetConnectionRequired.into()); + return Err(ProtoEnvError::RequiredInternetConnection.into()); } let temp_dir = self.get_temp_dir(); @@ -368,7 +433,7 @@ impl Tool { .await?; if !output.installed && !output.skip_install { - return Err(ProtoError::InstallFailed { + return Err(ProtoInstallError::FailedInstall { tool: self.get_name().to_owned(), error: output.error.unwrap_or_default(), } @@ -446,7 +511,7 @@ impl Tool { .await?; if !output.uninstalled && !output.skip_uninstall { - return Err(ProtoError::UninstallFailed { + return Err(ProtoInstallError::FailedUninstall { tool: self.get_name().to_owned(), error: output.error.unwrap_or_default(), } diff --git a/crates/core/src/flow/install_error.rs b/crates/core/src/flow/install_error.rs new file mode 100644 index 000000000..b835d281c --- /dev/null +++ b/crates/core/src/flow/install_error.rs @@ -0,0 +1,34 @@ +use miette::Diagnostic; +use starbase_styles::{Style, Stylize}; +use std::path::PathBuf; +use thiserror::Error; + +#[derive(Error, Debug, Diagnostic)] +pub enum ProtoInstallError { + #[diagnostic(code(proto::install::failed))] + #[error("Failed to install {tool}. {error}")] + FailedInstall { tool: String, error: String }, + + #[diagnostic(code(proto::uninstall::failed))] + #[error("Failed to uninstall {tool}. {error}")] + FailedUninstall { tool: String, error: String }, + + #[diagnostic(code(proto::install::invalid_checksum))] + #[error( + "Checksum has failed for {}, which was verified using {}.", + .download.style(Style::Path), + .checksum.style(Style::Path), + )] + InvalidChecksum { + checksum: PathBuf, + download: PathBuf, + }, + + #[diagnostic(code(proto::install::prebuilt_unsupported))] + #[error("Downloading a pre-built is not supported for {tool}. Try building from source by passing {}.", "--build".style(Style::Shell))] + UnsupportedDownloadPrebuilt { tool: String }, + + #[diagnostic(code(proto::install::build_unsupported))] + #[error("Building from source is not supported for {tool}. Try downloading a pre-built by passing {}.", "--no-build".style(Style::Shell))] + UnsupportedBuildFromSource { tool: String }, +} diff --git a/crates/core/src/flow/link.rs b/crates/core/src/flow/link.rs index 28e7ae53d..73d7532d6 100644 --- a/crates/core/src/flow/link.rs +++ b/crates/core/src/flow/link.rs @@ -77,11 +77,14 @@ impl Tool { registry.insert(shim.name.clone(), shim_entry); } - // Only lock the directory and create shims if necessary + // Only create shims if necessary if !to_create.is_empty() { - // let _lock = fs::lock_directory(&self.proto.store.shims_dir)?; fs::create_dir_all(&self.proto.store.shims_dir)?; + // Lock for our tests because of race conditions + #[cfg(debug_assertions)] + let _lock = fs::lock_directory(&self.proto.store.shims_dir)?; + for shim_path in to_create { self.proto.store.create_shim(&shim_path)?; @@ -154,11 +157,14 @@ impl Tool { to_create.push((input_path, output_path)); } - // Only lock the directory and create bins if necessary + // Only create bins if necessary if !to_create.is_empty() { - // let _lock = fs::lock_directory(&self.proto.store.bin_dir)?; fs::create_dir_all(&self.proto.store.bin_dir)?; + // Lock for our tests because of race conditions + #[cfg(debug_assertions)] + let _lock = fs::lock_directory(&self.proto.store.bin_dir)?; + for (input_path, output_path) in to_create { debug!( tool = self.id.as_str(), diff --git a/crates/core/src/flow/locate.rs b/crates/core/src/flow/locate.rs index 2dc4ce3ba..fbf7c9bc5 100644 --- a/crates/core/src/flow/locate.rs +++ b/crates/core/src/flow/locate.rs @@ -1,4 +1,4 @@ -use crate::error::ProtoError; +pub use super::locate_error::ProtoLocateError; use crate::helpers::ENV_VAR; use crate::layout::BinManager; use crate::tool::Tool; @@ -245,6 +245,11 @@ impl Tool { Ok(locations) } + /// Return an absolute path to the primary executable file, after it has been located. + pub fn get_exe_file(&self) -> Option<&Path> { + self.exe_file.as_deref() + } + /// Locate the primary executable from the tool directory. #[instrument(skip_all)] pub async fn locate_exe_file(&mut self) -> miette::Result { @@ -271,7 +276,7 @@ impl Tool { return Ok(exe_file); } - Err(ProtoError::MissingToolExecutable { + Err(ProtoLocateError::MissingToolExecutable { tool: self.get_name().to_owned(), path: exe_file, } @@ -391,7 +396,7 @@ impl Tool { let replace_with = match find_by { "$CWD" | "$PWD" => self.proto.working_dir.clone(), - "$HOME" | "$USERHOME" => self.proto.home_dir.clone(), + "$HOME" | "$USERHOME" | "$USERPROFILE" => self.proto.home_dir.clone(), "$PROTO_HOME" | "$PROTO_ROOT" => self.proto.store.dir.clone(), "$TOOL_DIR" => install_dir.clone(), _ => match env::var_os(cap.get(1).unwrap().as_str()) { diff --git a/crates/core/src/flow/locate_error.rs b/crates/core/src/flow/locate_error.rs new file mode 100644 index 000000000..e0db883a5 --- /dev/null +++ b/crates/core/src/flow/locate_error.rs @@ -0,0 +1,14 @@ +use miette::Diagnostic; +use starbase_styles::{Style, Stylize}; +use std::path::PathBuf; +use thiserror::Error; + +#[derive(Error, Debug, Diagnostic)] +pub enum ProtoLocateError { + #[diagnostic(code(proto::locate::missing_executable))] + #[error( + "Unable to find an executable for {tool}, expected file {} does not exist.", + .path.style(Style::Path), + )] + MissingToolExecutable { tool: String, path: PathBuf }, +} diff --git a/crates/core/src/flow/mod.rs b/crates/core/src/flow/mod.rs index c420485f7..cc381898f 100644 --- a/crates/core/src/flow/mod.rs +++ b/crates/core/src/flow/mod.rs @@ -1,7 +1,10 @@ mod build; mod build_error; pub mod install; +mod install_error; pub mod link; pub mod locate; +mod locate_error; pub mod resolve; +mod resolve_error; pub mod setup; diff --git a/crates/core/src/flow/resolve.rs b/crates/core/src/flow/resolve.rs index 81d5688f6..85da6590e 100644 --- a/crates/core/src/flow/resolve.rs +++ b/crates/core/src/flow/resolve.rs @@ -1,6 +1,7 @@ -use crate::error::ProtoError; +pub use super::resolve_error::ProtoResolveError; use crate::helpers::is_offline; use crate::tool::Tool; +use crate::tool_spec::{Backend, ToolSpec}; use crate::version_resolver::VersionResolver; use proto_pdk_api::*; use starbase_utils::fs; @@ -29,7 +30,7 @@ impl Tool { // Nothing cached, so load from the plugin if !cached { if is_offline() { - return Err(ProtoError::InternetConnectionRequiredForVersion { + return Err(ProtoResolveError::RequiredInternetConnectionForVersion { command: format!("{}_VERSION=1.2.3 {}", self.get_env_var_prefix(), self.id), bin_dir: self.proto.store.bin_dir.clone(), } @@ -66,6 +67,23 @@ impl Tool { Ok(resolver) } + /// Given a custom backend, resolve and register it to acquire necessary files. + pub async fn resolve_backend(&mut self, backend: Option) -> miette::Result<()> { + self.backend = backend; + self.register_backend().await?; + + Ok(()) + } + + pub async fn resolve_version_with_spec( + &mut self, + spec: &ToolSpec, + short_circuit: bool, + ) -> miette::Result { + self.resolve_backend(spec.backend).await?; + self.resolve_version(&spec.req, short_circuit).await + } + /// Given an initial version, resolve it to a fully qualifed and semantic version /// (or alias) according to the tool's ecosystem. #[instrument(skip(self))] @@ -139,7 +157,7 @@ impl Tool { resolver.resolve_without_manifest(candidate) }; - result.ok_or_else(|| ProtoError::VersionResolveFailed { + result.ok_or_else(|| ProtoResolveError::FailedVersionResolve { tool: self.get_name().to_owned(), version: candidate.to_string(), }) @@ -240,9 +258,12 @@ impl Tool { output.version.unwrap() } else { - UnresolvedVersionSpec::parse(&content).map_err(|error| ProtoError::VersionSpec { - version: content, - error: Box::new(error), + UnresolvedVersionSpec::parse(&content).map_err(|error| { + ProtoResolveError::InvalidDetectedVersionSpec { + error: Box::new(error), + path: file_path.clone(), + version: content, + } })? }; diff --git a/crates/core/src/flow/resolve_error.rs b/crates/core/src/flow/resolve_error.rs new file mode 100644 index 000000000..f52f404b3 --- /dev/null +++ b/crates/core/src/flow/resolve_error.rs @@ -0,0 +1,56 @@ +use miette::Diagnostic; +use starbase_styles::{Style, Stylize}; +use std::path::PathBuf; +use thiserror::Error; + +#[derive(Error, Debug, Diagnostic)] +pub enum ProtoResolveError { + #[diagnostic(code(proto::resolve::offline::version_required))] + #[error( + "Internet connection required to load and resolve a valid version. To work around this:\n - Pass a fully-qualified version explicitly: {}\n - Execute the non-shim binaries instead: {}", + .command.style(Style::Shell), + .bin_dir.style(Style::Path) + )] + RequiredInternetConnectionForVersion { command: String, bin_dir: PathBuf }, + + #[diagnostic(code(proto::resolve::invalid_detected_version))] + #[error( + "Invalid version or requirement {} detected from {}.", + .version.style(Style::Hash), + .path.style(Style::Path), + )] + InvalidDetectedVersionSpec { + #[source] + error: Box, + path: PathBuf, + version: String, + }, + + #[diagnostic(code(proto::resolve::invalid_version))] + #[error("Invalid version or requirement {}.", .version.style(Style::Hash))] + InvalidVersionSpec { + version: String, + #[source] + error: Box, + }, + + #[diagnostic( + code(proto::resolve::undetected_version), + help = "Has the tool been installed?" + )] + #[error( + "Failed to detect an applicable version to run {tool} with. Try pinning a version with {} or explicitly passing the version as an argument or environment variable.", + "proto pin".style(Style::Shell), + )] + FailedVersionDetect { tool: String }, + + #[diagnostic( + code(proto::resolve::unresolved_version), + help = "Does this version exist and has it been released?" + )] + #[error( + "Failed to resolve {} to a valid supported version for {tool}.", + .version.style(Style::Hash), + )] + FailedVersionResolve { tool: String, version: String }, +} diff --git a/crates/core/src/flow/setup.rs b/crates/core/src/flow/setup.rs index f7e0fdebd..8652990f0 100644 --- a/crates/core/src/flow/setup.rs +++ b/crates/core/src/flow/setup.rs @@ -1,13 +1,20 @@ +use crate::config::{PinLocation, ProtoConfig}; use crate::flow::install::InstallOptions; use crate::layout::BinManager; -use crate::proto_config::{PinLocation, ProtoConfig}; use crate::tool::Tool; use crate::tool_manifest::ToolManifestVersion; +use crate::tool_spec::ToolSpec; use proto_pdk_api::*; use starbase_utils::fs; use tracing::{debug, instrument}; impl Tool { + #[instrument(skip(self))] + pub async fn is_setup_with_spec(&mut self, spec: &ToolSpec) -> miette::Result { + self.resolve_version_with_spec(spec, true).await?; + self.is_setup(&spec.req).await + } + /// Return true if the tool has been setup (installed and binaries are located). #[instrument(skip(self))] pub async fn is_setup( @@ -45,6 +52,16 @@ impl Tool { Ok(false) } + #[instrument(skip(self, options))] + pub async fn setup_with_spec( + &mut self, + spec: &ToolSpec, + options: InstallOptions, + ) -> miette::Result { + self.resolve_version_with_spec(spec, false).await?; + self.setup(&spec.req, options).await + } + /// Setup the tool by resolving a semantic version, installing the tool, /// locating binaries, creating shims, and more. #[instrument(skip(self, options))] @@ -68,9 +85,13 @@ impl Tool { // Add version to manifest let manifest = &mut self.inventory.manifest; manifest.installed_versions.insert(version.clone()); - manifest - .versions - .insert(version.clone(), ToolManifestVersion::default()); + manifest.versions.insert( + version.clone(), + ToolManifestVersion { + backend: self.backend, + ..Default::default() + }, + ); manifest.save()?; // Pin the global version @@ -79,7 +100,7 @@ impl Tool { .versions .get_or_insert(Default::default()) .entry(self.id.clone()) - .or_insert(default_version); + .or_insert(ToolSpec::new(default_version)); })?; // Allow plugins to override manifest diff --git a/crates/core/src/layout/layout_error.rs b/crates/core/src/layout/layout_error.rs new file mode 100644 index 000000000..0d3d25ec5 --- /dev/null +++ b/crates/core/src/layout/layout_error.rs @@ -0,0 +1,24 @@ +use miette::Diagnostic; +use starbase_styles::{Style, Stylize}; +use std::path::PathBuf; +use thiserror::Error; + +#[derive(Error, Debug, Diagnostic)] +pub enum ProtoLayoutError { + #[diagnostic(code(proto::store::shim::create_failed))] + #[error("Failed to create shim {}.", .path.style(Style::Path))] + FailedCreateShim { + path: PathBuf, + #[source] + error: Box, + }, + + #[diagnostic(code(proto::store::shim::missing_binary))] + #[error( + "Unable to create shims as the {} binary cannot be found.\nLooked in the {} environment variable and {} directory.", + "proto-shim".style(Style::Id), + "PROTO_HOME".style(Style::Property), + .bin_dir.style(Style::Path), + )] + MissingShimBinary { bin_dir: PathBuf }, +} diff --git a/crates/core/src/layout/mod.rs b/crates/core/src/layout/mod.rs index be293dc8e..cceebaa12 100644 --- a/crates/core/src/layout/mod.rs +++ b/crates/core/src/layout/mod.rs @@ -1,11 +1,13 @@ mod bin_manager; mod inventory; +mod layout_error; mod product; mod shim_registry; mod store; pub use bin_manager::*; pub use inventory::*; +pub use layout_error::*; pub use product::*; pub use shim_registry::*; pub use store::*; diff --git a/crates/core/src/layout/store.rs b/crates/core/src/layout/store.rs index ecc454079..f56eecd79 100644 --- a/crates/core/src/layout/store.rs +++ b/crates/core/src/layout/store.rs @@ -1,5 +1,5 @@ use super::inventory::Inventory; -use crate::error::ProtoError; +use super::layout_error::ProtoLayoutError; use crate::tool_manifest::ToolManifest; use once_cell::sync::OnceCell; use proto_pdk_api::ToolInventoryMetadata; @@ -15,6 +15,7 @@ use warpgate::Id; #[derive(Clone, Default, Serialize)] pub struct Store { pub dir: PathBuf, + pub backends_dir: PathBuf, pub bin_dir: PathBuf, pub builders_dir: PathBuf, pub cache_dir: PathBuf, @@ -32,6 +33,7 @@ impl Store { pub fn new(dir: &Path) -> Self { Self { dir: dir.to_path_buf(), + backends_dir: dir.join("backends"), bin_dir: dir.join("bin"), builders_dir: dir.join("builders"), cache_dir: dir.join("cache"), @@ -89,8 +91,10 @@ impl Store { pub fn load_shim_binary(&self) -> miette::Result<&Vec> { self.shim_binary.get_or_try_init(|| { Ok(fs::read_file_bytes( - locate_proto_exe("proto-shim").ok_or_else(|| ProtoError::MissingShimBinary { - bin_dir: self.bin_dir.clone(), + locate_proto_exe("proto-shim").ok_or_else(|| { + ProtoLayoutError::MissingShimBinary { + bin_dir: self.bin_dir.clone(), + } })?, )?) }) @@ -141,7 +145,7 @@ impl Store { #[instrument(skip(self))] pub fn create_shim(&self, shim_path: &Path) -> miette::Result<()> { create_shim(self.load_shim_binary()?, shim_path).map_err(|error| { - ProtoError::CreateShimFailed { + ProtoLayoutError::FailedCreateShim { path: shim_path.to_owned(), error: Box::new(error), } diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 509941f3b..361186022 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,24 +1,31 @@ pub mod checksum; -mod error; +mod config; +mod config_error; +mod env; +mod env_error; pub mod flow; mod helpers; pub mod layout; -mod proto; -mod proto_config; pub mod registry; mod tool; +mod tool_error; mod tool_loader; mod tool_manifest; +mod tool_spec; +mod utils; mod version_detector; mod version_resolver; -pub use error::*; +pub use config::*; +pub use config_error::*; +pub use env::*; +pub use env_error::*; pub use helpers::*; -pub use proto::*; -pub use proto_config::*; pub use tool::*; +pub use tool_error::*; pub use tool_loader::*; pub use tool_manifest::*; +pub use tool_spec::*; pub use version_detector::*; pub use version_resolver::*; pub use version_spec::*; diff --git a/crates/core/src/registry/registry.rs b/crates/core/src/registry/registry.rs index afd87644f..3845b0374 100644 --- a/crates/core/src/registry/registry.rs +++ b/crates/core/src/registry/registry.rs @@ -1,4 +1,4 @@ -use crate::proto::ProtoEnvironment; +use crate::env::ProtoEnvironment; use crate::registry::data::{PluginEntry, PluginRegistryDocument}; use crate::registry::registry_error::ProtoRegistryError; use starbase_utils::{fs, json}; @@ -95,13 +95,13 @@ impl ProtoRegistry { let data: PluginRegistryDocument = reqwest::get(&data_url) .await - .map_err(|error| ProtoRegistryError::RequestFailed { + .map_err(|error| ProtoRegistryError::FailedRequest { url: data_url, error: Box::new(error), })? .json() .await - .map_err(|error| ProtoRegistryError::ParseFailed { + .map_err(|error| ProtoRegistryError::FailedParse { error: Box::new(error), })?; diff --git a/crates/core/src/registry/registry_error.rs b/crates/core/src/registry/registry_error.rs index 8af6b39dc..89a3a60e9 100644 --- a/crates/core/src/registry/registry_error.rs +++ b/crates/core/src/registry/registry_error.rs @@ -4,16 +4,16 @@ use thiserror::Error; #[derive(Error, Debug, Diagnostic)] pub enum ProtoRegistryError { - #[diagnostic(code(proto_registry::parse_failed))] + #[diagnostic(code(proto::registry::parse_failed))] #[error("Failed to parse registry plugin data.")] - ParseFailed { + FailedParse { #[source] error: Box, }, - #[diagnostic(code(proto_registry::request_failed))] + #[diagnostic(code(proto::registry::request_failed))] #[error("Failed to request plugins from registry {}.", .url.style(Style::Url))] - RequestFailed { + FailedRequest { url: String, #[source] error: Box, diff --git a/crates/core/src/tool.rs b/crates/core/src/tool.rs index 37f98dceb..18213faa8 100644 --- a/crates/core/src/tool.rs +++ b/crates/core/src/tool.rs @@ -1,10 +1,13 @@ -use crate::error::ProtoError; +use crate::env::ProtoEnvironment; use crate::helpers::get_proto_version; use crate::layout::{Inventory, Product}; -use crate::proto::ProtoEnvironment; +use crate::tool_error::ProtoToolError; +use crate::tool_spec::Backend; +use crate::utils::{archive, git}; use proto_pdk_api::*; use rustc_hash::{FxHashMap, FxHashSet}; use starbase_styles::color; +use starbase_utils::fs; use std::fmt; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -15,10 +18,13 @@ use warpgate::{ Id, PluginContainer, PluginLocator, PluginManifest, VirtualPath, Wasm, }; +pub type ToolMetadata = RegisterToolOutput; + pub struct Tool { + pub backend: Option, pub id: Id, - pub metadata: ToolMetadataOutput, pub locator: Option, + pub metadata: ToolMetadata, pub plugin: Arc, pub proto: Arc, pub version: Option, @@ -28,6 +34,7 @@ pub struct Tool { pub product: Product, // Cache + pub(crate) backend_registered: bool, pub(crate) cache: bool, pub(crate) exe_file: Option, pub(crate) exes_dir: Option, @@ -48,6 +55,8 @@ impl Tool { ); let mut tool = Tool { + backend: None, + backend_registered: false, cache: true, exe_file: None, exes_dir: None, @@ -57,7 +66,7 @@ impl Tool { id, inventory: Inventory::default(), locator: None, - metadata: ToolMetadataOutput::default(), + metadata: ToolMetadata::default(), plugin, product: Product::default(), proto, @@ -200,11 +209,11 @@ impl Tool { /// Register the tool by loading initial metadata and persisting it. #[instrument(skip_all)] pub async fn register_tool(&mut self) -> miette::Result<()> { - let metadata: ToolMetadataOutput = self + let metadata: RegisterToolOutput = self .plugin .cache_func_with( "register_tool", - ToolMetadataInput { + RegisterToolInput { id: self.id.to_string(), }, ) @@ -215,7 +224,7 @@ impl Tool { let actual_version = get_proto_version(); if actual_version < expected_version { - return Err(ProtoError::InvalidMinimumVersion { + return Err(ProtoToolError::InvalidMinimumVersion { tool: metadata.name, id: self.id.clone(), expected: expected_version.to_string(), @@ -243,7 +252,7 @@ impl Tool { if override_dir_path.is_none() || override_dir_path.as_ref().is_some_and(|p| p.is_relative()) { - return Err(ProtoError::AbsoluteInventoryDir { + return Err(ProtoToolError::RequiredAbsoluteInventoryDir { tool: metadata.name.clone(), dir: override_dir_path.unwrap_or_else(|| PathBuf::from("")), } @@ -261,6 +270,77 @@ impl Tool { Ok(()) } + /// Register the backend by acquiring necessary source files. + #[instrument(skip_all)] + pub async fn register_backend(&mut self) -> miette::Result<()> { + if !self.plugin.has_func("register_backend").await + || self.backend.is_none() + || self.backend_registered + { + return Ok(()); + } + + let metadata: RegisterBackendOutput = self + .plugin + .cache_func_with( + "register_backend", + RegisterBackendInput { + context: self.create_context(), + id: self.id.to_string(), + }, + ) + .await?; + + let Some(source) = metadata.source else { + panic!("Source is required for backends!"); + }; + + let backend_id = metadata.backend_id; + let backend_dir = self.proto.store.backends_dir.join(&backend_id); + + debug!( + tool = self.id.as_str(), + backend_id, + backend_dir = ?backend_dir, + "Acquiring backend sources", + ); + + match source { + SourceLocation::Archive(src) => { + debug!( + tool = self.id.as_str(), + url = &src.url, + "Downloading backend archive", + ); + + archive::download_and_unpack( + &src, + &backend_dir, + &self.proto.store.temp_dir, + self.proto.get_plugin_loader()?.get_client()?.to_inner(), + ) + .await?; + } + SourceLocation::Git(src) => { + debug!( + tool = self.id.as_str(), + url = &src.url, + "Cloning backend repository", + ); + + git::clone_or_pull_repo(&src, &backend_dir).await?; + } + }; + + for exe in metadata.exes { + fs::update_perms(backend_dir.join(exe), None)?; + } + + self.backend_registered = true; + + Ok(()) + } + /// Sync the local tool manifest with changes from the plugin. #[instrument(skip_all)] pub async fn sync_manifest(&mut self) -> miette::Result<()> { diff --git a/crates/core/src/tool_error.rs b/crates/core/src/tool_error.rs new file mode 100644 index 000000000..b75bc5a1d --- /dev/null +++ b/crates/core/src/tool_error.rs @@ -0,0 +1,58 @@ +use crate::config::PROTO_CONFIG_NAME; +use crate::tool_spec::Backend; +use miette::Diagnostic; +use starbase_styles::{Style, Stylize}; +use std::path::PathBuf; +use thiserror::Error; +use warpgate::Id; + +#[derive(Error, Debug, Diagnostic)] +pub enum ProtoToolError { + #[diagnostic(code(proto::tool::minimum_version_requirement))] + #[error( + "Unable to use the {tool} plugin with identifier {}, as it requires a minimum proto version of {}, but found {} instead.", + .id.to_string().style(Style::Id), + .expected.style(Style::Hash), + .actual.style(Style::Hash) + )] + InvalidMinimumVersion { + tool: String, + id: Id, + expected: String, + actual: String, + }, + + #[diagnostic(code(proto::tool::invalid_spec))] + #[error("Invalid version or requirement in tool specification {}.", .spec.style(Style::Hash))] + InvalidVersionSpec { + spec: String, + #[source] + error: Box, + }, + + #[diagnostic(code(proto::tool::invalid_inventory_dir))] + #[error("{tool} inventory directory has been overridden with {} but it's not an absolute path. Only absolute paths are supported.", .dir.style(Style::Path))] + RequiredAbsoluteInventoryDir { tool: String, dir: PathBuf }, + + #[diagnostic(code(proto::tool::unknown_backend))] + #[error( + "Unknown backend in tool specification {}. Only {} are supported.", + .spec.style(Style::Hash), + .backends.iter().map(|be| be.to_string().style(Style::Id)).collect::>().join(", ") + )] + UnknownBackend { + backends: Vec, + spec: String, + }, + + #[diagnostic(code(proto::tool::unknown_id))] + #[error( + "Unable to proceed, {} is not a built-in plugin and has not been configured with {} in a {} file.\n\nLearn more about plugins: {}\nSearch community plugins: {}", + .id.to_string().style(Style::Id), + "[plugins]".style(Style::Property), + PROTO_CONFIG_NAME.style(Style::File), + "https://moonrepo.dev/docs/proto/plugins".style(Style::Url), + format!("proto plugin search {}", .id).style(Style::Shell), + )] + UnknownTool { id: Id }, +} diff --git a/crates/core/src/tool_loader.rs b/crates/core/src/tool_loader.rs index 5270bfac5..559ae0e5a 100644 --- a/crates/core/src/tool_loader.rs +++ b/crates/core/src/tool_loader.rs @@ -1,7 +1,8 @@ -use crate::error::ProtoError; -use crate::proto::ProtoEnvironment; -use crate::proto_config::SCHEMA_PLUGIN_KEY; +use crate::config::SCHEMA_PLUGIN_KEY; +use crate::env::ProtoEnvironment; use crate::tool::Tool; +use crate::tool_error::ProtoToolError; +use crate::tool_spec::Backend; use convert_case::{Case, Casing}; use starbase_utils::{json, toml, yaml}; use std::fmt::Debug; @@ -64,7 +65,7 @@ pub fn locate_tool(id: &Id, proto: &ProtoEnvironment) -> miette::Result miette::Result { let mut is_toml = false; - let mut schema: json::JsonValue = match plugin_path - .extension() - .and_then(|ext| ext.to_str()) - .unwrap() - { - "toml" => { + let mut schema: json::JsonValue = match plugin_path.extension().and_then(|ext| ext.to_str()) { + Some("toml") => { is_toml = true; toml::read_file(plugin_path)? } - "json" | "jsonc" => json::read_file(plugin_path)?, - "yaml" | "yml" => yaml::read_file(plugin_path)?, + Some("json" | "jsonc") => json::read_file(plugin_path)?, + Some("yaml" | "yml") => yaml::read_file(plugin_path)?, _ => unimplemented!(), }; @@ -149,12 +146,9 @@ pub async fn load_tool_from_locator( let locator = locator.as_ref(); let plugin_path = proto.get_plugin_loader()?.load_plugin(id, locator).await?; - let plugin_ext = plugin_path - .extension() - .and_then(|ext| ext.to_str()) - .map(|ext| ext.to_owned()); + let plugin_ext = plugin_path.extension().and_then(|ext| ext.to_str()); - let mut manifest = match plugin_ext.as_deref() { + let mut manifest = match plugin_ext { Some("wasm") => { debug!(source = ?plugin_path, "Loading WASM plugin"); @@ -188,6 +182,35 @@ pub async fn load_tool_from_locator( Ok(tool) } -pub async fn load_tool_with_proto(id: &Id, proto: &ProtoEnvironment) -> miette::Result { - load_tool_from_locator(id, proto, locate_tool(id, proto)?).await +pub async fn load_tool( + id: &Id, + proto: &ProtoEnvironment, + mut backend: Option, +) -> miette::Result { + // Determine the backend plugin to use + if backend.is_none() { + let config = proto.load_config()?; + + // Check the version spec first, as that takes priority + if let Some(spec) = config.versions.get(id) { + backend = spec.backend; + } + + // Otherwise fallback to the tool config + if backend.is_none() { + backend = config.tools.get(id).and_then(|cfg| cfg.backend); + } + } + + // If backend is proto, use the tool's plugin, + // otherwise use the backend plugin itself + let locator_id = match backend { + Some(be) => Id::raw(be.to_string()), + None => id.to_owned(), + }; + + let mut tool = load_tool_from_locator(id, proto, locate_tool(&locator_id, proto)?).await?; + tool.resolve_backend(backend).await?; + + Ok(tool) } diff --git a/crates/core/src/tool_manifest.rs b/crates/core/src/tool_manifest.rs index bc43e0d9c..e664fd0f0 100644 --- a/crates/core/src/tool_manifest.rs +++ b/crates/core/src/tool_manifest.rs @@ -1,4 +1,5 @@ use crate::helpers::{now, read_json_file_with_lock, write_json_file_with_lock}; +use crate::tool_spec::Backend; use rustc_hash::{FxHashMap, FxHashSet}; use serde::{Deserialize, Serialize}; use starbase_utils::env::bool_var; @@ -14,6 +15,7 @@ pub const MANIFEST_NAME: &str = "manifest.json"; #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(default)] pub struct ToolManifestVersion { + pub backend: Option, pub no_clean: bool, pub installed_at: u128, } @@ -21,6 +23,7 @@ pub struct ToolManifestVersion { impl Default for ToolManifestVersion { fn default() -> Self { Self { + backend: None, no_clean: bool_var("PROTO_NO_CLEAN"), installed_at: now(), } diff --git a/crates/core/src/tool_spec.rs b/crates/core/src/tool_spec.rs new file mode 100644 index 000000000..01fe55769 --- /dev/null +++ b/crates/core/src/tool_spec.rs @@ -0,0 +1,146 @@ +use crate::tool_error::ProtoToolError; +use schematic::{derive_enum, ConfigEnum}; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::str::FromStr; +use version_spec::{UnresolvedVersionSpec, VersionSpec}; + +derive_enum!( + #[derive(Copy, ConfigEnum, Hash)] + pub enum Backend { + Asdf, + } +); + +#[derive(Clone, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[serde(into = "String", try_from = "String")] +pub struct ToolSpec { + pub backend: Option, + + // Requested version/requirement + pub req: UnresolvedVersionSpec, + + // Resolved version + pub res: Option, +} + +impl ToolSpec { + pub fn new(req: UnresolvedVersionSpec) -> Self { + Self { + backend: None, + req, + res: None, + } + } + + pub fn parse>(value: T) -> Result { + Self::from_str(value.as_ref()) + } + + pub fn resolve(&mut self, res: VersionSpec) { + self.res = Some(res); + } +} + +impl FromStr for ToolSpec { + type Err = ProtoToolError; + + fn from_str(value: &str) -> Result { + let (backend, spec) = if let Some((prefix, suffix)) = value.split_once(':') { + let backend = if prefix == "proto" { + None + } else if prefix == "asdf" { + Some(Backend::Asdf) + } else { + return Err(ProtoToolError::UnknownBackend { + backends: Backend::variants(), + spec: value.to_owned(), + }); + }; + + (backend, suffix) + } else { + (None, value) + }; + + Ok(Self { + backend, + req: UnresolvedVersionSpec::parse(spec).map_err(|error| { + ProtoToolError::InvalidVersionSpec { + spec: value.to_owned(), + error: Box::new(error), + } + })?, + res: None, + }) + } +} + +impl TryFrom for ToolSpec { + type Error = ProtoToolError; + + fn try_from(value: String) -> Result { + Self::from_str(&value) + } +} + +#[allow(clippy::from_over_into)] +impl Into for ToolSpec { + fn into(self) -> String { + self.to_string() + } +} + +impl From for ToolSpec { + fn from(value: UnresolvedVersionSpec) -> Self { + Self::new(value) + } +} + +impl PartialEq for ToolSpec { + fn eq(&self, other: &UnresolvedVersionSpec) -> bool { + &self.req == other + } +} + +impl PartialEq for ToolSpec { + fn eq(&self, other: &VersionSpec) -> bool { + &self.req == other + } +} + +impl AsRef for ToolSpec { + fn as_ref(&self) -> &ToolSpec { + self + } +} + +impl AsRef for ToolSpec { + fn as_ref(&self) -> &UnresolvedVersionSpec { + &self.req + } +} + +impl fmt::Display for ToolSpec { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(backend) = self.backend { + match backend { + Backend::Asdf => { + write!(f, "asdf:")?; + } + }; + } + + write!(f, "{}", self.req) + } +} + +impl schematic::Schematic for ToolSpec { + fn schema_name() -> Option { + Some("ToolSpec".into()) + } + + fn build_schema(mut schema: schematic::SchemaBuilder) -> schematic::Schema { + schema.string_default() + } +} diff --git a/crates/core/src/utils/archive.rs b/crates/core/src/utils/archive.rs new file mode 100644 index 000000000..65f20656e --- /dev/null +++ b/crates/core/src/utils/archive.rs @@ -0,0 +1,80 @@ +use crate::helpers::extract_filename_from_url; +use proto_pdk_api::ArchiveSource; +use starbase_archive::Archiver; +use starbase_utils::{fs, net}; +use std::path::{Path, PathBuf}; + +pub fn should_unpack(src: &ArchiveSource, target_dir: &Path) -> miette::Result { + let url_file = target_dir.join(".archive-url"); + let mut unpack = true; + + // If the URLs have changed at some point, we need to remove + // the current files, and download new ones + if url_file.exists() { + let previous_url = fs::read_file(&url_file)?; + + if src.url.trim() == previous_url.trim() { + unpack = false; + } else { + fs::remove_dir_all(target_dir)?; + } + } + + fs::create_dir_all(target_dir)?; + + Ok(unpack) +} + +pub async fn download( + src: &ArchiveSource, + temp_dir: &Path, + client: &reqwest::Client, +) -> miette::Result { + let filename = extract_filename_from_url(&src.url)?; + let archive_file = temp_dir.join(&filename); + + net::download_from_url_with_client(&src.url, &archive_file, client).await?; + + Ok(archive_file) +} + +pub fn unpack( + src: &ArchiveSource, + target_dir: &Path, + archive_file: &Path, +) -> miette::Result<(String, PathBuf)> { + let result = unpack_raw(target_dir, archive_file, src.prefix.as_deref()); + + fs::write_file(target_dir.join(".archive-url"), &src.url)?; + + result +} + +pub fn unpack_raw( + target_dir: &Path, + archive_file: &Path, + prefix: Option<&str>, +) -> miette::Result<(String, PathBuf)> { + let mut archiver = Archiver::new(target_dir, archive_file); + + if let Some(prefix) = prefix { + archiver.set_prefix(prefix); + } + + archiver.unpack_from_ext() +} + +pub async fn download_and_unpack( + src: &ArchiveSource, + target_dir: &Path, + temp_dir: &Path, + client: &reqwest::Client, +) -> miette::Result<()> { + if should_unpack(src, target_dir)? { + let archive_file = download(src, temp_dir, client).await?; + + unpack(src, target_dir, &archive_file)?; + } + + Ok(()) +} diff --git a/crates/core/src/utils/git.rs b/crates/core/src/utils/git.rs new file mode 100644 index 000000000..100e1f6bb --- /dev/null +++ b/crates/core/src/utils/git.rs @@ -0,0 +1,47 @@ +use super::process::exec_command_piped; +use proto_pdk_api::GitSource; +use starbase_utils::fs; +use std::path::Path; +use tokio::process::Command; + +pub fn new_clone(git: &GitSource, cwd: &Path) -> Command { + let mut cmd = Command::new("git"); + cmd.args(if git.submodules { + vec!["clone", "--recurse-submodules"] + } else { + vec!["clone"] + }) + .args(["--depth", "1"]) + .arg(&git.url) + .arg(".") + .current_dir(cwd); + cmd +} + +pub fn new_checkout(reference: &str, cwd: &Path) -> Command { + let mut cmd = Command::new("git"); + cmd.arg("checkout").arg(reference).current_dir(cwd); + cmd +} + +pub fn new_pull(cwd: &Path) -> Command { + let mut cmd = Command::new("git"); + cmd.args(["pull", "--ff", "--prune"]).current_dir(cwd); + cmd +} + +pub async fn clone_or_pull_repo(src: &GitSource, target_dir: &Path) -> miette::Result<()> { + fs::create_dir_all(target_dir)?; + + if target_dir.join(".git").exists() { + exec_command_piped(&mut new_pull(target_dir)).await?; + } else { + exec_command_piped(&mut new_clone(src, target_dir)).await?; + + if let Some(reference) = &src.reference { + exec_command_piped(&mut new_checkout(reference, target_dir)).await?; + } + } + + Ok(()) +} diff --git a/crates/core/src/utils/mod.rs b/crates/core/src/utils/mod.rs new file mode 100644 index 000000000..9a5b1f818 --- /dev/null +++ b/crates/core/src/utils/mod.rs @@ -0,0 +1,3 @@ +pub mod archive; +pub mod git; +pub mod process; diff --git a/crates/core/src/utils/process.rs b/crates/core/src/utils/process.rs new file mode 100644 index 000000000..673d56284 --- /dev/null +++ b/crates/core/src/utils/process.rs @@ -0,0 +1,144 @@ +use miette::{Diagnostic, IntoDiagnostic}; +use rustc_hash::FxHashMap; +use starbase_styles::{color, Style, Stylize}; +use std::io; +use std::path::PathBuf; +use std::process::{Output, Stdio}; +use thiserror::Error; +use tokio::process::Command; +use tracing::trace; + +#[derive(Error, Debug, Diagnostic)] +pub enum ProtoProcessError { + #[diagnostic(code(proto::process::command_failed))] + #[error("Failed to execute command {}.", .command.style(Style::Shell))] + FailedCommand { + command: String, + #[source] + error: Box, + }, + + #[diagnostic(code(proto::process::command_failed))] + #[error("Command {} returned a {code} exit code.", .command.style(Style::Shell))] + FailedCommandNonZeroExit { command: String, code: i32 }, +} + +#[allow(dead_code)] +pub struct ProcessResult { + pub command: String, + pub exit_code: i32, + pub stderr: String, + pub stdout: String, + pub working_dir: Option, +} + +async fn spawn_command(command: &mut Command) -> std::io::Result { + let child = command.spawn()?; + let output = child.wait_with_output().await?; + + Ok(output) +} + +pub async fn exec_command(command: &mut Command) -> miette::Result { + let inner = command.as_std(); + let command_line = format!( + "{} {}", + inner.get_program().to_string_lossy(), + shell_words::join( + inner + .get_args() + .map(|arg| arg.to_string_lossy()) + .collect::>() + ) + ); + + trace!( + cwd = ?inner.get_current_dir(), + env = ?inner.get_envs() + .filter_map(|(key, val)| val.map(|v| (key, v.to_string_lossy()))) + .collect::>(), + "Running command {}", color::shell(&command_line) + ); + + let working_dir = inner.get_current_dir().map(PathBuf::from); + let output = + spawn_command(command) + .await + .map_err(|error| ProtoProcessError::FailedCommand { + command: command_line.clone(), + error: Box::new(error), + })?; + + let stderr = String::from_utf8(output.stderr).into_diagnostic()?; + let stdout = String::from_utf8(output.stdout).into_diagnostic()?; + let code = output.status.code().unwrap_or(-1); + + trace!( + code, + stderr = if stderr.len() > 250 { + "" + } else { + &stderr + }, + stdout = if stdout.len() > 250 { + "" + } else { + &stdout + }, + "Ran command {}", + color::shell(&command_line) + ); + + Ok(ProcessResult { + command: command_line, + stderr, + stdout, + exit_code: code, + working_dir, + }) +} + +pub async fn exec_command_piped(command: &mut Command) -> miette::Result { + exec_command(command.stderr(Stdio::piped()).stdout(Stdio::piped())).await +} + +pub async fn exec_command_with_privileges( + command: &mut Command, + elevated_program: Option<&str>, +) -> miette::Result { + match elevated_program { + Some(program) => { + let inner = command.as_std(); + + let mut sudo_command = Command::new(program); + sudo_command.arg(inner.get_program()); + sudo_command.args(inner.get_args()); + + for (key, value) in inner.get_envs() { + if let Some(value) = value { + sudo_command.env(key, value); + } else { + sudo_command.env_remove(key); + } + } + + if let Some(dir) = inner.get_current_dir() { + sudo_command.current_dir(dir); + } + + exec_command(&mut sudo_command).await + } + None => exec_command(command).await, + } +} + +pub async fn exec_command_with_privileges_piped( + command: &mut Command, + elevated_program: Option<&str>, +) -> miette::Result { + exec_command_with_privileges( + command.stderr(Stdio::piped()).stdout(Stdio::piped()), + elevated_program, + ) + .await +} diff --git a/crates/core/src/version_detector.rs b/crates/core/src/version_detector.rs index 3a7076080..2ad484759 100644 --- a/crates/core/src/version_detector.rs +++ b/crates/core/src/version_detector.rs @@ -1,6 +1,6 @@ -use crate::error::ProtoError; -use crate::proto_config::*; +use crate::flow::resolve::ProtoResolveError; use crate::tool::Tool; +use crate::{config::*, ToolSpec}; use std::env; use std::path::Path; use tracing::instrument; @@ -28,7 +28,7 @@ pub async fn detect_version_first_available( set_detected_env_var(tool.get_env_var_prefix(), &file.path); - return Ok(Some(version.to_owned())); + return Ok(Some(version.req.to_owned())); } } @@ -68,7 +68,7 @@ pub async fn detect_version_only_prototools( set_detected_env_var(tool.get_env_var_prefix(), &file.path); - return Ok(Some(version.to_owned())); + return Ok(Some(version.req.to_owned())); } } } @@ -136,7 +136,7 @@ pub async fn detect_version( return Ok( UnresolvedVersionSpec::parse(&session_version).map_err(|error| { - ProtoError::VersionSpec { + ProtoResolveError::InvalidVersionSpec { version: session_version, error: Box::new(error), } @@ -172,8 +172,21 @@ pub async fn detect_version( } // We didn't find anything! - Err(ProtoError::VersionDetectFailed { + Err(ProtoResolveError::FailedVersionDetect { tool: tool.get_name().to_owned(), } .into()) } + +#[instrument(skip_all)] +pub async fn detect_version_with_spec( + tool: &Tool, + forced_spec: Option, +) -> miette::Result { + let detected = detect_version(tool, forced_spec.clone().map(|spec| spec.req)).await?; + + let mut spec = forced_spec.unwrap_or_default(); + spec.req = detected; + + Ok(spec) +} diff --git a/crates/core/src/version_resolver.rs b/crates/core/src/version_resolver.rs index c3b1b3468..a74e8e79b 100644 --- a/crates/core/src/version_resolver.rs +++ b/crates/core/src/version_resolver.rs @@ -1,4 +1,4 @@ -use crate::proto_config::ProtoToolConfig; +use crate::config::ProtoToolConfig; use crate::tool_manifest::ToolManifest; use proto_pdk_api::LoadVersionsOutput; use semver::VersionReq; @@ -120,7 +120,7 @@ pub fn resolve_version( let mut alias_value = None; if let Some(config) = config { - alias_value = config.aliases.get(alias.as_str()); + alias_value = config.aliases.get(alias.as_str()).map(|spec| &spec.req); } if alias_value.is_none() { diff --git a/crates/core/tests/proto_config_test.rs b/crates/core/tests/proto_config_test.rs index b9dd15d47..9565d69b7 100644 --- a/crates/core/tests/proto_config_test.rs +++ b/crates/core/tests/proto_config_test.rs @@ -1,7 +1,7 @@ use indexmap::IndexMap; use proto_core::{ - DetectStrategy, EnvVar, PartialEnvVar, PartialProtoSettingsConfig, PinLocation, ProtoConfig, - ProtoConfigManager, + Backend, DetectStrategy, EnvVar, PartialEnvVar, PartialProtoSettingsConfig, PinLocation, + ProtoConfig, ProtoConfigManager, ToolSpec, }; use schematic::ConfigError; use starbase_sandbox::create_empty_sandbox; @@ -111,6 +111,23 @@ pin-latest = "global" env::remove_var("PROTO_PIN_LATEST"); } + #[test] + fn can_set_backend_with_version() { + let sandbox = create_empty_sandbox(); + sandbox.create_file(".prototools", r#"node = "asdf:20.0.0""#); + + let config = ProtoConfig::load_from(sandbox.path(), false).unwrap(); + + assert_eq!( + config.versions.unwrap().get("node").unwrap(), + &ToolSpec { + backend: Some(Backend::Asdf), + req: UnresolvedVersionSpec::parse("20.0.0").unwrap(), + res: None + } + ); + } + #[test] fn can_set_env() { let sandbox = create_empty_sandbox(); @@ -270,11 +287,11 @@ kebab-case = "file://./camel.toml" BTreeMap::from_iter([ ( Id::raw("node"), - UnresolvedVersionSpec::parse("12.0.0").unwrap() + UnresolvedVersionSpec::parse("12.0.0").unwrap().into() ), ( Id::raw("rust"), - UnresolvedVersionSpec::Alias("stable".into()) + UnresolvedVersionSpec::Alias("stable".into()).into() ), ]) ); @@ -308,11 +325,11 @@ kebab-case = "file://./camel.toml" versions.insert( Id::raw("node"), - UnresolvedVersionSpec::parse("12.0.0").unwrap(), + UnresolvedVersionSpec::parse("12.0.0").unwrap().into(), ); versions.insert( Id::raw("rust"), - UnresolvedVersionSpec::Alias("stable".into()), + UnresolvedVersionSpec::Alias("stable".into()).into(), ); let plugins = config.plugins.get_or_insert(Default::default()); @@ -664,6 +681,62 @@ builtin-plugins = [] use super::*; use rustc_hash::FxHashMap; + #[test] + fn can_set_backend() { + let sandbox = create_empty_sandbox(); + sandbox.create_file( + ".prototools", + r#" +[tools.node] +backend = "asdf" +"#, + ); + + let config = ProtoConfig::load_from(sandbox.path(), false).unwrap(); + + assert_eq!( + config + .tools + .unwrap() + .get("node") + .unwrap() + .backend + .as_ref() + .unwrap(), + &Backend::Asdf + ); + } + + #[test] + fn can_set_backend_with_aliases() { + let sandbox = create_empty_sandbox(); + sandbox.create_file( + ".prototools", + r#" +[tools.node.aliases] +value = "asdf:4.5.6" +"#, + ); + + let config = ProtoConfigManager::load(sandbox.path(), None, None) + .unwrap() + .get_merged_config() + .unwrap() + .to_owned(); + + assert_eq!( + config.tools.get("node").unwrap().aliases, + BTreeMap::from_iter([( + "value".to_owned(), + ToolSpec { + backend: Some(Backend::Asdf), + req: UnresolvedVersionSpec::parse("4.5.6").unwrap(), + res: None + } + ),]) + ); + } + #[test] fn can_set_extra_settings() { let sandbox = create_empty_sandbox(); @@ -773,11 +846,11 @@ value = "4.5.6" BTreeMap::from_iter([ ( "stable".to_owned(), - UnresolvedVersionSpec::parse("1.0.0").unwrap() + UnresolvedVersionSpec::parse("1.0.0").unwrap().into() ), ( "value".to_owned(), - UnresolvedVersionSpec::parse("1.2.3").unwrap() + UnresolvedVersionSpec::parse("1.2.3").unwrap().into() ), ]) ); @@ -953,15 +1026,15 @@ deno = "7.8.9" BTreeMap::from_iter([ ( Id::raw("node"), - UnresolvedVersionSpec::parse("1.2.3").unwrap() + UnresolvedVersionSpec::parse("1.2.3").unwrap().into() ), ( Id::raw("bun"), - UnresolvedVersionSpec::parse("4.5.6").unwrap() + UnresolvedVersionSpec::parse("4.5.6").unwrap().into() ), ( Id::raw("deno"), - UnresolvedVersionSpec::parse("7.8.9").unwrap() + UnresolvedVersionSpec::parse("7.8.9").unwrap().into() ), ]) ); @@ -1018,11 +1091,11 @@ bun = "1.2.3" BTreeMap::from_iter([ ( Id::raw("node"), - UnresolvedVersionSpec::parse("1.2.3").unwrap() + UnresolvedVersionSpec::parse("1.2.3").unwrap().into() ), ( Id::raw("deno"), - UnresolvedVersionSpec::parse("7.8.9").unwrap() + UnresolvedVersionSpec::parse("7.8.9").unwrap().into() ), ]) ); @@ -1064,7 +1137,7 @@ bun = "1.2.3" config.versions, BTreeMap::from_iter([( Id::raw("node"), - UnresolvedVersionSpec::parse("1.2.3").unwrap() + UnresolvedVersionSpec::parse("1.2.3").unwrap().into() ),]) ); } @@ -1097,11 +1170,11 @@ deno = "7.8.9" BTreeMap::from_iter([ ( Id::raw("node"), - UnresolvedVersionSpec::parse("1.2.3").unwrap() + UnresolvedVersionSpec::parse("1.2.3").unwrap().into() ), ( Id::raw("deno"), - UnresolvedVersionSpec::parse("7.8.9").unwrap() + UnresolvedVersionSpec::parse("7.8.9").unwrap().into() ), ]) ); @@ -1134,11 +1207,11 @@ deno = "7.8.9" BTreeMap::from_iter([ ( Id::raw("node"), - UnresolvedVersionSpec::parse("7.8.9").unwrap() + UnresolvedVersionSpec::parse("7.8.9").unwrap().into() ), ( Id::raw("deno"), - UnresolvedVersionSpec::parse("7.8.9").unwrap() + UnresolvedVersionSpec::parse("7.8.9").unwrap().into() ), ]) ); @@ -1173,11 +1246,11 @@ deno = "7.8.9" BTreeMap::from_iter([ ( Id::raw("node"), - UnresolvedVersionSpec::parse("7.8.9").unwrap() + UnresolvedVersionSpec::parse("7.8.9").unwrap().into() ), ( Id::raw("deno"), - UnresolvedVersionSpec::parse("7.8.9").unwrap() + UnresolvedVersionSpec::parse("7.8.9").unwrap().into() ), ]) ); diff --git a/crates/core/tests/tool_spec.rs b/crates/core/tests/tool_spec.rs new file mode 100644 index 000000000..e9d813f8d --- /dev/null +++ b/crates/core/tests/tool_spec.rs @@ -0,0 +1,177 @@ +use proto_core::{Backend, ToolSpec, UnresolvedVersionSpec}; + +mod tool_spec { + use super::*; + + #[test] + #[should_panic(expected = "UnknownBackend")] + fn errors_unknown_backend() { + ToolSpec::parse("fake:123").unwrap(); + } + + #[test] + #[should_panic(expected = "InvalidVersionSpec")] + fn errors_invalid_spec() { + ToolSpec::parse("asdf:1.a.2").unwrap(); + } + + #[test] + fn parses_latest() { + assert_eq!( + ToolSpec::parse("latest").unwrap(), + ToolSpec { + backend: None, + req: UnresolvedVersionSpec::Alias("latest".into()), + res: None, + } + ); + } + + #[test] + fn parses_latest_with_backend() { + assert_eq!( + ToolSpec::parse("asdf:latest").unwrap(), + ToolSpec { + backend: Some(Backend::Asdf), + req: UnresolvedVersionSpec::Alias("latest".into()), + res: None, + } + ); + assert_eq!( + ToolSpec::parse("proto:latest").unwrap(), + ToolSpec { + backend: None, + req: UnresolvedVersionSpec::Alias("latest".into()), + res: None, + } + ); + } + + #[test] + fn parses_canary() { + assert_eq!( + ToolSpec::parse("canary").unwrap(), + ToolSpec { + backend: None, + req: UnresolvedVersionSpec::parse("canary").unwrap(), + res: None, + } + ); + } + + #[test] + fn parses_canary_with_backend() { + assert_eq!( + ToolSpec::parse("asdf:canary").unwrap(), + ToolSpec { + backend: Some(Backend::Asdf), + req: UnresolvedVersionSpec::Canary, + res: None, + } + ); + assert_eq!( + ToolSpec::parse("proto:canary").unwrap(), + ToolSpec { + backend: None, + req: UnresolvedVersionSpec::Canary, + res: None, + } + ); + } + + #[test] + fn parses_calver() { + assert_eq!( + ToolSpec::parse("2025-01-01").unwrap(), + ToolSpec { + backend: None, + req: UnresolvedVersionSpec::parse("2025-01-01").unwrap(), + res: None, + } + ); + } + + #[test] + fn parses_calver_with_backend() { + assert_eq!( + ToolSpec::parse("asdf:2025-01-01").unwrap(), + ToolSpec { + backend: Some(Backend::Asdf), + req: UnresolvedVersionSpec::parse("2025-01-01").unwrap(), + res: None, + } + ); + assert_eq!( + ToolSpec::parse("proto:2025-01-01").unwrap(), + ToolSpec { + backend: None, + req: UnresolvedVersionSpec::parse("2025-01-01").unwrap(), + res: None, + } + ); + } + + #[test] + fn parses_semver() { + assert_eq!( + ToolSpec::parse("1.2.3").unwrap(), + ToolSpec { + backend: None, + req: UnresolvedVersionSpec::parse("1.2.3").unwrap(), + res: None, + } + ); + } + + #[test] + fn parses_semver_with_backend() { + assert_eq!( + ToolSpec::parse("asdf:1.2.3").unwrap(), + ToolSpec { + backend: Some(Backend::Asdf), + req: UnresolvedVersionSpec::parse("1.2.3").unwrap(), + res: None, + } + ); + assert_eq!( + ToolSpec::parse("proto:1.2.3").unwrap(), + ToolSpec { + backend: None, + req: UnresolvedVersionSpec::parse("1.2.3").unwrap(), + res: None, + } + ); + } + + #[test] + fn parses_version_req() { + assert_eq!( + ToolSpec::parse("^2").unwrap(), + ToolSpec { + backend: None, + req: UnresolvedVersionSpec::parse("^2").unwrap(), + res: None, + } + ); + } + + #[test] + fn parses_version_req_with_backend() { + assert_eq!( + ToolSpec::parse("asdf:^2").unwrap(), + ToolSpec { + backend: Some(Backend::Asdf), + req: UnresolvedVersionSpec::parse("^2").unwrap(), + res: None, + } + ); + assert_eq!( + ToolSpec::parse("proto:~1.2").unwrap(), + ToolSpec { + backend: None, + req: UnresolvedVersionSpec::parse("~1.2").unwrap(), + res: None, + } + ); + } +} diff --git a/crates/core/tests/version_resolver_test.rs b/crates/core/tests/version_resolver_test.rs index 67347d501..95d38e5ad 100644 --- a/crates/core/tests/version_resolver_test.rs +++ b/crates/core/tests/version_resolver_test.rs @@ -60,11 +60,11 @@ mod version_resolver { config.aliases.insert( "latest-manifest".into(), - UnresolvedVersionSpec::Semantic(SemVer(Version::new(8, 0, 0))), + UnresolvedVersionSpec::Semantic(SemVer(Version::new(8, 0, 0))).into(), ); config.aliases.insert( "stable-manifest".into(), - UnresolvedVersionSpec::Alias("stable".into()), + UnresolvedVersionSpec::Alias("stable".into()).into(), ); config diff --git a/crates/installer/Cargo.toml b/crates/installer/Cargo.toml index 1e98ff231..833471f35 100644 --- a/crates/installer/Cargo.toml +++ b/crates/installer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_installer" -version = "0.9.0" +version = "0.11.0" edition = "2021" license = "MIT" description = "Download and install proto." @@ -8,7 +8,7 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -system_env = { version = "0.7.1", path = "../system-env" } +system_env = { version = "0.7.2", path = "../system-env" } miette = { workspace = true } reqwest = { workspace = true, features = ["stream"] } starbase_archive = { workspace = true } diff --git a/crates/installer/src/error.rs b/crates/installer/src/error.rs index c59971842..c29b21c10 100644 --- a/crates/installer/src/error.rs +++ b/crates/installer/src/error.rs @@ -14,7 +14,7 @@ pub enum ProtoInstallerError { #[diagnostic(code(proto::installer::download_failed))] #[error("Failed to download archive {}.", .url.style(Style::Url))] - DownloadFailed { + FailedDownload { url: String, #[source] error: Box, diff --git a/crates/installer/src/lib.rs b/crates/installer/src/lib.rs index 82bebc4a2..d1b233da6 100644 --- a/crates/installer/src/lib.rs +++ b/crates/installer/src/lib.rs @@ -84,7 +84,7 @@ pub async fn download_release( ) .await .map_err(|error| match error { - NetError::Http { url, error } => ProtoInstallerError::DownloadFailed { url, error }, + NetError::Http { url, error } => ProtoInstallerError::FailedDownload { url, error }, NetError::DownloadFailed { status, .. } => ProtoInstallerError::DownloadNotAvailable { version: version.to_owned(), status, diff --git a/crates/pdk-api/Cargo.toml b/crates/pdk-api/Cargo.toml index ba3e718e3..eef6a8b42 100644 --- a/crates/pdk-api/Cargo.toml +++ b/crates/pdk-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_pdk_api" -version = "0.25.3" +version = "0.26.0" edition = "2021" license = "MIT" description = "Core APIs for creating proto WASM plugins." @@ -8,9 +8,9 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -system_env = { version = "0.7.1", path = "../system-env" } -version_spec = { version = "0.7.2", path = "../version-spec" } -warpgate_api = { version = "0.11.1", path = "../warpgate-api" } +system_env = { version = "0.7.2", path = "../system-env" } +version_spec = { version = "0.8.0", path = "../version-spec" } +warpgate_api = { version = "0.12.0", path = "../warpgate-api" } rustc-hash = { workspace = true } schematic = { workspace = true, features = [ "schema", diff --git a/crates/pdk-api/src/api/build_source.rs b/crates/pdk-api/src/api/build.rs similarity index 80% rename from crates/pdk-api/src/api/build_source.rs rename to crates/pdk-api/src/api/build.rs index 32fac13d5..2215ef602 100644 --- a/crates/pdk-api/src/api/build_source.rs +++ b/crates/pdk-api/src/api/build.rs @@ -1,4 +1,4 @@ -use super::is_false; +use super::source::*; use crate::ToolContext; use rustc_hash::FxHashMap; use semver::VersionReq; @@ -14,48 +14,6 @@ api_struct!( } ); -api_struct!( - /// Source code is contained in an archive. - pub struct ArchiveSource { - /// The URL to download the archive from. - pub url: String, - - /// A path prefix within the archive to remove. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub prefix: Option, - } -); - -api_struct!( - /// Source code is located in a Git repository. - pub struct GitSource { - /// The URL of the Git remote. - pub url: String, - - /// The branch/commit/tag to checkout. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub reference: Option, - - /// Include submodules during checkout. - #[serde(default, skip_serializing_if = "is_false")] - pub submodules: bool, - } -); - -api_enum!( - /// The location in which source code can be acquired. - #[serde(tag = "type", rename_all = "kebab-case")] - pub enum SourceLocation { - /// Downloaded from an archive. - #[cfg_attr(feature = "schematic", schema(nested))] - Archive(ArchiveSource), - - /// Cloned from a Git repository. - #[cfg_attr(feature = "schematic", schema(nested))] - Git(GitSource), - } -); - api_struct!( /// A builder and its parameters for installing the builder. pub struct BuilderInstruction { @@ -133,6 +91,9 @@ api_enum!( /// Move a file from source to destination. MoveFile(PathBuf, PathBuf), + /// Remove all files except those matching the provided list. + RemoveAllExcept(Vec), + /// Remove a directory. RemoveDir(PathBuf), diff --git a/crates/pdk-api/src/api/mod.rs b/crates/pdk-api/src/api/mod.rs index f352c4d0d..56d1b9506 100644 --- a/crates/pdk-api/src/api/mod.rs +++ b/crates/pdk-api/src/api/mod.rs @@ -1,4 +1,5 @@ -mod build_source; +mod build; +mod source; use crate::shapes::*; use rustc_hash::FxHashMap; @@ -6,8 +7,9 @@ use std::path::PathBuf; use version_spec::{CalVer, SemVer, SpecError, UnresolvedVersionSpec, VersionSpec}; use warpgate_api::*; -pub use build_source::*; +pub use build::*; pub use semver::{Version, VersionReq}; +pub use source::*; pub(crate) fn is_false(value: &bool) -> bool { !(*value) @@ -48,12 +50,15 @@ api_unit_enum!( api_struct!( /// Input passed to the `register_tool` function. - pub struct ToolMetadataInput { + pub struct RegisterToolInput { /// ID of the tool, as it was configured. pub id: String, } ); +#[deprecated(note = "Use `RegisterToolInput` instead.")] +pub type ToolMetadataInput = RegisterToolInput; + api_struct!( /// Controls aspects of the tool inventory. #[serde(default)] @@ -82,7 +87,7 @@ api_unit_enum!( api_struct!( /// Output returned by the `register_tool` function. - pub struct ToolMetadataOutput { + pub struct RegisterToolOutput { /// Schema shape of the tool's configuration. #[serde(default, skip_serializing_if = "Option::is_none")] pub config_schema: Option, @@ -134,6 +139,39 @@ api_struct!( } ); +#[deprecated(note = "Use `RegisterToolOutput` instead.")] +pub type ToolMetadataOutput = RegisterToolOutput; + +// BACKEND + +api_struct!( + /// Input passed to the `register_backend` function. + pub struct RegisterBackendInput { + /// Current tool context. + pub context: ToolContext, + + /// ID of the tool, as it was configured. + pub id: String, + } +); + +api_struct!( + /// Output returned by the `register_backend` function. + pub struct RegisterBackendOutput { + /// Unique identifier for this backend. Will be used as the folder name. + pub backend_id: String, + + /// List of executables, relative from the backend directory, + /// that will be executed in the context of proto. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub exes: Vec, + + /// Location in which to acquire source files for the backend. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub source: Option, + } +); + // VERSION DETECTION api_struct!( diff --git a/crates/pdk-api/src/api/source.rs b/crates/pdk-api/src/api/source.rs new file mode 100644 index 000000000..2b61cf58d --- /dev/null +++ b/crates/pdk-api/src/api/source.rs @@ -0,0 +1,44 @@ +use super::is_false; +use warpgate_api::{api_enum, api_struct}; + +api_struct!( + /// Source code is contained in an archive. + pub struct ArchiveSource { + /// The URL to download the archive from. + pub url: String, + + /// A path prefix within the archive to remove. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub prefix: Option, + } +); + +api_struct!( + /// Source code is located in a Git repository. + pub struct GitSource { + /// The URL of the Git remote. + pub url: String, + + /// The branch/commit/tag to checkout. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub reference: Option, + + /// Include submodules during checkout. + #[serde(default, skip_serializing_if = "is_false")] + pub submodules: bool, + } +); + +api_enum!( + /// The location in which source code can be acquired. + #[serde(tag = "type", rename_all = "kebab-case")] + pub enum SourceLocation { + /// Downloaded from an archive. + #[cfg_attr(feature = "schematic", schema(nested))] + Archive(ArchiveSource), + + /// Cloned from a Git repository. + #[cfg_attr(feature = "schematic", schema(nested))] + Git(GitSource), + } +); diff --git a/crates/pdk-test-utils/Cargo.toml b/crates/pdk-test-utils/Cargo.toml index e9ca1abed..ecfa48e15 100644 --- a/crates/pdk-test-utils/Cargo.toml +++ b/crates/pdk-test-utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_pdk_test_utils" -version = "0.32.2" +version = "0.34.4" edition = "2021" license = "MIT" description = "Utilities for testing proto WASM plugins." @@ -8,9 +8,9 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -proto_core = { version = "0.45.4", path = "../core" } -proto_pdk_api = { version = "0.25.3", path = "../pdk-api" } -warpgate = { version = "0.21.1", path = "../warpgate" } +proto_core = { version = "0.47.0", path = "../core" } +proto_pdk_api = { version = "0.26.0", path = "../pdk-api" } +warpgate = { version = "0.22.1", path = "../warpgate" } # extism = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/pdk-test-utils/src/lib.rs b/crates/pdk-test-utils/src/lib.rs index c14a6b798..2fa5d8469 100644 --- a/crates/pdk-test-utils/src/lib.rs +++ b/crates/pdk-test-utils/src/lib.rs @@ -5,8 +5,8 @@ mod wrapper; pub use config_builder::*; pub use proto_core::{ - flow, Id, ProtoConfig, ProtoEnvironment, Tool, ToolManifest, UnresolvedVersionSpec, Version, - VersionReq, VersionSpec, + flow, Id, ProtoConfig, ProtoConsole, ProtoEnvironment, Tool, ToolManifest, + UnresolvedVersionSpec, Version, VersionReq, VersionSpec, }; pub use proto_pdk_api::*; pub use sandbox::*; diff --git a/crates/pdk-test-utils/src/macros.rs b/crates/pdk-test-utils/src/macros.rs index 72e2b24ea..5184354f4 100644 --- a/crates/pdk-test-utils/src/macros.rs +++ b/crates/pdk-test-utils/src/macros.rs @@ -1,3 +1,68 @@ +#[macro_export] +macro_rules! generate_build_install_tests { + ($id:literal, $version:literal) => { + generate_build_install_tests!($id, $version, None); + }; + ($id:literal, $version:literal, $schema:expr) => { + #[tokio::test(flavor = "multi_thread")] + async fn builds_installs_tool_from_source() { + let sandbox = create_empty_proto_sandbox(); + let mut plugin = if let Some(schema) = $schema { + sandbox.create_schema_plugin($id, schema).await + } else { + sandbox.create_plugin($id).await + }; + let spec = UnresolvedVersionSpec::parse($version).unwrap(); + + let result = plugin + .tool + .setup( + &spec, + proto_pdk_test_utils::flow::install::InstallOptions { + console: Some(proto_pdk_test_utils::ProtoConsole::new_testing()), + strategy: proto_pdk_test_utils::InstallStrategy::BuildFromSource, + skip_prompts: true, + skip_ui: true, + ..Default::default() + }, + ) + .await; + + // Print the log so we can debug + if result.is_err() { + println!( + "{}", + std::fs::read_to_string( + sandbox.path().join(format!("proto-{}-build.log", $id)) + ) + .unwrap() + ); + } + + result.unwrap(); + + // Check install dir exists + let base_dir = sandbox.proto_dir.join("tools").join($id).join($version); + let tool_dir = plugin.tool.get_product_dir(); + + assert_eq!(tool_dir, base_dir); + assert!(base_dir.exists()); + + // Check bin path exists (would panic) + plugin.tool.locate_exe_file().await.unwrap(); + + // Check things exist + for bin in plugin.tool.resolve_bin_locations(true).await.unwrap() { + assert!(bin.path.exists()); + } + + for shim in plugin.tool.resolve_shim_locations().await.unwrap() { + assert!(shim.path.exists()); + } + } + }; +} + #[macro_export] macro_rules! generate_download_install_tests { ($id:literal, $version:literal) => { diff --git a/crates/pdk-test-utils/src/wrapper.rs b/crates/pdk-test-utils/src/wrapper.rs index a6f75fcc0..22ee92789 100644 --- a/crates/pdk-test-utils/src/wrapper.rs +++ b/crates/pdk-test-utils/src/wrapper.rs @@ -114,7 +114,17 @@ impl WasmTestWrapper { .unwrap(); } - pub async fn register_tool(&self, input: ToolMetadataInput) -> ToolMetadataOutput { + pub async fn register_backend(&self, mut input: RegisterBackendInput) -> RegisterBackendOutput { + input.context = self.prepare_context(input.context); + + self.tool + .plugin + .call_func_with("register_backend", input) + .await + .unwrap() + } + + pub async fn register_tool(&self, input: RegisterToolInput) -> RegisterToolOutput { self.tool .plugin .call_func_with("register_tool", input) diff --git a/crates/pdk/Cargo.toml b/crates/pdk/Cargo.toml index 8d2229b89..4b5d4b96f 100644 --- a/crates/pdk/Cargo.toml +++ b/crates/pdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "proto_pdk" -version = "0.26.2" +version = "0.27.0" edition = "2021" license = "MIT" description = "A plugin development kit for creating proto WASM plugins." @@ -8,8 +8,8 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -proto_pdk_api = { version = "0.25.3", path = "../pdk-api" } -warpgate_pdk = { version = "0.9.1", path = "../warpgate-pdk" } +proto_pdk_api = { version = "0.26.0", path = "../pdk-api" } +warpgate_pdk = { version = "0.11.0", path = "../warpgate-pdk" } extism-pdk = { workspace = true } rustc-hash = { workspace = true } serde = { workspace = true } diff --git a/crates/system-env/Cargo.toml b/crates/system-env/Cargo.toml index 56e9b3b1a..682beea8f 100644 --- a/crates/system-env/Cargo.toml +++ b/crates/system-env/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "system_env" -version = "0.7.1" +version = "0.7.2" edition = "2021" license = "MIT" description = "Information about the system environment: operating system, architecture, package manager, etc." diff --git a/crates/system-env/src/deps.rs b/crates/system-env/src/deps.rs index 229e1d7f0..ec457697d 100644 --- a/crates/system-env/src/deps.rs +++ b/crates/system-env/src/deps.rs @@ -42,6 +42,10 @@ pub struct DependencyConfig { /// The dependency name or name(s) to install. pub dep: DependencyName, + /// The name of the executable (without ext) that this + /// dependency installs, and can be found when searching `PATH`. + pub exe_name: Option, + /// Only install with this package manager. #[serde(skip_serializing_if = "Option::is_none")] pub manager: Option, @@ -192,6 +196,14 @@ impl SystemDependency { })) } + /// Convert into the `Config` variant. + pub fn into_config(self) -> SystemDependency { + match self { + Self::Config(config) => SystemDependency::Config(config), + _ => SystemDependency::Config(Box::new(self.to_config())), + } + } + /// Convert and expand to a dependency configuration. pub fn to_config(&self) -> DependencyConfig { match self { @@ -214,4 +226,17 @@ impl SystemDependency { Self::Config(config) => (**config).to_owned(), } } + + /// Convert to the `Config` variant and allow the [`DependencyConfig`] + /// to be mutated through a callback function. + pub fn with_config(self, mut op: impl FnMut(&mut DependencyConfig)) -> SystemDependency { + let mut config = match self { + Self::Config(config) => *config, + _ => self.to_config(), + }; + + op(&mut config); + + Self::Config(Box::new(config)) + } } diff --git a/crates/version-spec/Cargo.toml b/crates/version-spec/Cargo.toml index e0280a978..b99db7081 100644 --- a/crates/version-spec/Cargo.toml +++ b/crates/version-spec/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "version_spec" -version = "0.7.2" +version = "0.8.0" edition = "2021" license = "MIT" description = "A specification for working with partial, full, or aliased versions. Supports semver and calver." diff --git a/crates/version-spec/src/resolved_spec.rs b/crates/version-spec/src/resolved_spec.rs index c56fe866c..2a2b2ebfc 100644 --- a/crates/version-spec/src/resolved_spec.rs +++ b/crates/version-spec/src/resolved_spec.rs @@ -120,7 +120,7 @@ impl FromStr for VersionSpec { return Ok(VersionSpec::Semantic(SemVer::parse(&value)?)); } - Err(SpecError::ResolvedUnknownFormat(value.to_owned())) + Err(SpecError::UnknownResolvedFormat(value.to_owned())) } } diff --git a/crates/version-spec/src/spec_error.rs b/crates/version-spec/src/spec_error.rs index 191b5e594..b4e3b49da 100644 --- a/crates/version-spec/src/spec_error.rs +++ b/crates/version-spec/src/spec_error.rs @@ -1,19 +1,19 @@ #[derive(thiserror::Error, Debug)] pub enum SpecError { #[error("Invalid calver (calendar version) format.")] - CalverInvalidFormat, + InvalidCalverFormat, - #[error("Unknown version format `{0}`. Must be a semantic or calendar based format.")] - ResolvedUnknownFormat(String), + #[error("Requirement operator found in an invalid position.")] + InvalidParseRequirement, - #[error("Requirement operator found in an invalid position")] - ParseInvalidReq, + #[error("Missing major number for semantic versions, or year for calendar versions.")] + MissingParseMajorPart, - #[error("Unknown character `{0}` in version string!")] - ParseUnknownChar(char), + #[error("Unknown version format `{0}`. Must be a semantic or calendar based format.")] + UnknownResolvedFormat(String), - #[error("Missing major number for semantic versions, or year for calendar versions.")] - ParseMissingMajorPart, + #[error("Unknown character `{0}` in version string!")] + UnknownParseChar(char), #[error(transparent)] Semver(#[from] semver::Error), diff --git a/crates/version-spec/src/unresolved_parser.rs b/crates/version-spec/src/unresolved_parser.rs index e1319f043..1da593712 100644 --- a/crates/version-spec/src/unresolved_parser.rs +++ b/crates/version-spec/src/unresolved_parser.rs @@ -62,7 +62,7 @@ impl UnresolvedParser { // Requirement operator '=' | '~' | '^' | '>' | '<' => { if self.in_part != ParsePart::Start && self.in_part != ParsePart::ReqPrefix { - return Err(SpecError::ParseInvalidReq); + return Err(SpecError::InvalidParseRequirement); } self.in_part = ParsePart::ReqPrefix; @@ -100,7 +100,7 @@ impl UnresolvedParser { if ch == 'v' || ch == 'V' { continue; } else { - return Err(SpecError::ParseUnknownChar(ch)); + return Err(SpecError::UnknownParseChar(ch)); } } }, @@ -219,7 +219,7 @@ impl UnresolvedParser { } } _ => { - return Err(SpecError::ParseUnknownChar(ch)); + return Err(SpecError::UnknownParseChar(ch)); } } } @@ -286,7 +286,7 @@ impl UnresolvedParser { output.push_str(year); } } else if self.major_year.is_empty() { - return Err(SpecError::ParseMissingMajorPart); + return Err(SpecError::MissingParseMajorPart); } else { output.push_str(self.get_part(&self.major_year)); } diff --git a/crates/version-spec/src/version_types.rs b/crates/version-spec/src/version_types.rs index cdc5f044c..b060e97b4 100644 --- a/crates/version-spec/src/version_types.rs +++ b/crates/version-spec/src/version_types.rs @@ -40,7 +40,7 @@ impl CalVer { /// so that we can utilize the [`semver::Version`] type. pub fn parse(value: &str) -> Result { let Some(caps) = get_calver_regex().captures(value) else { - return Err(SpecError::CalverInvalidFormat); + return Err(SpecError::InvalidCalverFormat); }; // Short years (less than 4 characters) are relative diff --git a/crates/version-spec/tests/resolved_spec_test.rs b/crates/version-spec/tests/resolved_spec_test.rs index 71763cebf..1ca8792c9 100644 --- a/crates/version-spec/tests/resolved_spec_test.rs +++ b/crates/version-spec/tests/resolved_spec_test.rs @@ -65,7 +65,7 @@ mod resolved_spec { } #[test] - #[should_panic(expected = "ResolvedUnknownFormat")] + #[should_panic(expected = "UnknownResolvedFormat")] fn error_invalid_char() { VersionSpec::parse("%").unwrap(); } diff --git a/crates/warpgate-api/Cargo.toml b/crates/warpgate-api/Cargo.toml index 5f350002e..95cdb94bc 100644 --- a/crates/warpgate-api/Cargo.toml +++ b/crates/warpgate-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "warpgate_api" -version = "0.11.1" +version = "0.12.0" edition = "2021" license = "MIT" description = "APIs for working with Warpgate plugins." @@ -8,7 +8,7 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -system_env = { version = "0.7.1", path = "../system-env" } +system_env = { version = "0.7.2", path = "../system-env" } anyhow = { workspace = true } rustc-hash = { workspace = true } schematic = { workspace = true, optional = true, features = ["schema", "json"] } diff --git a/crates/warpgate-api/src/host_funcs.rs b/crates/warpgate-api/src/host_funcs.rs index ccc96cb6c..d0188c7c9 100644 --- a/crates/warpgate-api/src/host_funcs.rs +++ b/crates/warpgate-api/src/host_funcs.rs @@ -50,15 +50,17 @@ impl From for HostLogInput { api_struct!( /// Input passed to the `exec_command` host function. + #[serde(default)] pub struct ExecCommandInput { /// The command or script to execute. pub command: String, /// Arguments to pass to the command. + #[serde(skip_serializing_if = "Vec::is_empty")] pub args: Vec, /// Environment variables to pass to the command. - #[serde(default)] + #[serde(skip_serializing_if = "FxHashMap::is_empty")] pub env: FxHashMap, /// Mark the command as executable before executing. @@ -66,11 +68,10 @@ api_struct!( pub set_executable: bool, /// Stream the output instead of capturing it. - #[serde(default)] pub stream: bool, /// Override the current working directory. - #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub working_dir: Option, } ); diff --git a/crates/warpgate-api/src/locator.rs b/crates/warpgate-api/src/locator.rs index 94c08c32e..ee2755403 100644 --- a/crates/warpgate-api/src/locator.rs +++ b/crates/warpgate-api/src/locator.rs @@ -164,7 +164,7 @@ impl TryFrom for PluginLocator { }))), "github" => { if !location.contains('/') { - return Err(PluginLocatorError::GitHubMissingOrg); + return Err(PluginLocatorError::MissingGitHubOrg); } let mut github = GitHubLocator::default(); diff --git a/crates/warpgate-api/src/locator_error.rs b/crates/warpgate-api/src/locator_error.rs index 829823354..2eeb9b549 100644 --- a/crates/warpgate-api/src/locator_error.rs +++ b/crates/warpgate-api/src/locator_error.rs @@ -4,7 +4,7 @@ pub enum PluginLocatorError { #[error( "GitHub release locator requires a repository name with organization scope (org/repo)." )] - GitHubMissingOrg, + MissingGitHubOrg, #[error("Missing plugin location (after protocol).")] MissingLocation, diff --git a/crates/warpgate-api/tests/locator_test.rs b/crates/warpgate-api/tests/locator_test.rs index f059ff9ef..cb3c619c9 100644 --- a/crates/warpgate-api/tests/locator_test.rs +++ b/crates/warpgate-api/tests/locator_test.rs @@ -160,7 +160,7 @@ mod locator { use super::*; #[test] - #[should_panic(expected = "GitHubMissingOrg")] + #[should_panic(expected = "MissingGitHubOrg")] fn errors_no_slug() { PluginLocator::try_from("github://moonrepo".to_string()).unwrap(); } diff --git a/crates/warpgate-pdk/Cargo.toml b/crates/warpgate-pdk/Cargo.toml index 57dd5ebc8..816dbbcee 100644 --- a/crates/warpgate-pdk/Cargo.toml +++ b/crates/warpgate-pdk/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "warpgate_pdk" -version = "0.9.1" +version = "0.11.0" edition = "2021" license = "MIT" description = "Reusable WASM macros and functions for plugin developer kits." @@ -8,6 +8,6 @@ homepage = "https://moonrepo.dev/proto" repository = "https://github.com/moonrepo/proto" [dependencies] -warpgate_api = { version = "0.11.1", path = "../warpgate-api" } +warpgate_api = { version = "0.12.0", path = "../warpgate-api" } extism-pdk = { workspace = true } serde = { workspace = true } diff --git a/crates/warpgate-pdk/src/api.rs b/crates/warpgate-pdk/src/api.rs index 31e352f8b..9421e6f96 100644 --- a/crates/warpgate-pdk/src/api.rs +++ b/crates/warpgate-pdk/src/api.rs @@ -1,6 +1,7 @@ use extism_pdk::{memory::internal::load, MemoryHandle}; use warpgate_api::SendRequestOutput; +#[doc(hidden)] pub fn populate_send_request_output(output: &mut SendRequestOutput) { if output.body.is_empty() { let handle = unsafe { MemoryHandle::new(output.body_offset, output.body_length) }; diff --git a/crates/warpgate-pdk/src/funcs.rs b/crates/warpgate-pdk/src/funcs.rs index d1f252060..5437c34a0 100644 --- a/crates/warpgate-pdk/src/funcs.rs +++ b/crates/warpgate-pdk/src/funcs.rs @@ -1,114 +1,29 @@ use crate::api::populate_send_request_output; use crate::{exec_command, send_request}; -use extism_pdk::http::request; use extism_pdk::*; use serde::de::DeserializeOwned; +use std::ffi::OsStr; +use std::path::PathBuf; use std::vec; use warpgate_api::{ anyhow, AnyResult, ExecCommandInput, ExecCommandOutput, HostEnvironment, HostOS, - SendRequestInput, SendRequestOutput, TestEnvironment, + SendRequestInput, SendRequestOutput, TestEnvironment, VirtualPath, }; #[host_fn] extern "ExtismHost" { fn exec_command(input: Json) -> Json; + fn from_virtual_path(input: String) -> String; + fn get_env_var(key: String) -> String; fn send_request(input: Json) -> Json; + fn set_env_var(name: String, value: String); + fn to_virtual_path(input: String) -> Json; } -/// Fetch the provided request and return a response object. -#[deprecated(note = "Use `fetch_*` instead.")] -pub fn fetch(req: HttpRequest, body: Option) -> AnyResult { - debug!("Fetching {}", req.url); - - request(&req, body) - .map_err(|error| error.context(format!("Failed to make request to {}", req.url))) -} - -/// Fetch the provided URL and deserialize the response as JSON. -#[allow(deprecated)] -#[deprecated(note = "Use `fetch_json` instead.")] -pub fn fetch_url(url: U) -> AnyResult -where - R: DeserializeOwned, - U: AsRef, -{ - fetch(HttpRequest::new(url.as_ref()), None)?.json() -} - -/// Fetch the provided URL and deserialize the response as bytes. -#[allow(deprecated)] -#[deprecated(note = "Use `fetch_bytes` instead.")] -pub fn fetch_url_bytes(url: U) -> AnyResult> -where - U: AsRef, -{ - Ok(fetch(HttpRequest::new(url.as_ref()), None)?.body()) -} - -/// Fetch the provided URL and return the text response. -#[allow(deprecated)] -#[deprecated(note = "Use `fetch_text` instead.")] -pub fn fetch_url_text(url: U) -> AnyResult -where - U: AsRef, -{ - String::from_bytes(&fetch_url_bytes(url)?) -} - -/// Fetch the provided URL, deserialize the response as JSON, -/// and cache the response in memory for subsequent WASM function calls. -#[allow(deprecated)] -#[deprecated(note = "Use `fetch_*` instead.")] -pub fn fetch_url_with_cache(url: U) -> AnyResult -where - R: DeserializeOwned, - U: AsRef, -{ - let url = url.as_ref(); - let req = HttpRequest::new(url); - - // Only cache GET requests - let cache = req.method.is_none() - || req - .method - .as_ref() - .is_some_and(|m| m.to_uppercase() == "GET"); - - if cache { - if let Some(body) = var::get::>(url)? { - debug!( - "Reading {} from cache (length = {})", - url, - body.len() - ); - - return Ok(json::from_slice(&body)?); - } - } - - let res = fetch(req, None)?; - - if cache { - let body = res.body(); - - debug!( - "Writing {} to cache (length = {})", - url, - body.len() - ); - - var::set(url, body)?; - } - - res.json() -} - -fn do_fetch(url: U) -> AnyResult -where - U: AsRef, -{ - let url = url.as_ref(); - let response = send_request!(url); +/// Fetch the requested input and return a response. +pub fn fetch(input: SendRequestInput) -> AnyResult { + let url = input.url.clone(); + let response = send_request!(input, input); let status = response.status; if status != 200 { @@ -137,7 +52,7 @@ pub fn fetch_bytes(url: U) -> AnyResult> where U: AsRef, { - Ok(do_fetch(url)?.body) + Ok(fetch(SendRequestInput::new(url))?.body) } /// Fetch the provided URL and deserialize the response as JSON. @@ -146,7 +61,7 @@ where U: AsRef, R: DeserializeOwned, { - do_fetch(url)?.json() + fetch(SendRequestInput::new(url))?.json() } /// Fetch the provided URL and return the response as text. @@ -154,11 +69,36 @@ pub fn fetch_text(url: U) -> AnyResult where U: AsRef, { - do_fetch(url)?.text() + fetch(SendRequestInput::new(url))?.text() +} + +/// Execute a command on the host with the provided input. +pub fn exec(input: ExecCommandInput) -> AnyResult { + Ok(exec_command!(input, input)) +} + +/// Execute a command on the host and capture its output (pipe). +pub fn exec_captured(command: C, args: I) -> AnyResult +where + C: AsRef, + I: IntoIterator, + A: AsRef, +{ + exec(ExecCommandInput::pipe(command, args)) +} + +/// Execute a command on the host and stream its output to the console (inherit). +pub fn exec_streamed(command: C, args: I) -> AnyResult +where + C: AsRef, + I: IntoIterator, + A: AsRef, +{ + exec(ExecCommandInput::inherit(command, args)) } /// Load all Git tags from the provided remote URL. -/// The `git` binary must exist on the current machine. +/// The `git` binary must exist on the host machine. pub fn load_git_tags(url: U) -> AnyResult> where U: AsRef, @@ -167,13 +107,11 @@ where debug!("Loading Git tags from remote {}", url); - let output = exec_command!( - pipe, - "git", - ["ls-remote", "--tags", "--sort", "version:refname", url] - ); - let mut tags: Vec = vec![]; + let output = exec_captured( + "git", + ["ls-remote", "--tags", "--sort", "version:refname", url], + )?; if output.exit_code != 0 { debug!("Failed to load Git tags"); @@ -211,16 +149,15 @@ pub fn command_exists(env: &HostEnvironment, command: &str) -> bool { ); let result = if env.os == HostOS::Windows { - exec_command!( - raw, + exec_captured( "powershell", - ["-Command", format!("Get-Command {command}").as_str()] + ["-Command", format!("Get-Command {command}").as_str()], ) } else { - exec_command!(raw, "which", [command]) + exec_captured("which", [command]) }; - if result.is_ok_and(|res| res.0.exit_code == 0) { + if result.is_ok_and(|res| res.exit_code == 0) { debug!("Command does exist"); return true; @@ -231,6 +168,63 @@ pub fn command_exists(env: &HostEnvironment, command: &str) -> bool { false } +/// Return the value of an environment variable on the host machine. +pub fn get_host_env_var(key: K) -> AnyResult> +where + K: AsRef, +{ + let inner = unsafe { get_env_var(key.as_ref().into())? }; + + Ok(if inner.is_empty() { None } else { Some(inner) }) +} + +/// Set the value of an environment variable on the host machine. +pub fn set_host_env_var(key: K, value: V) -> AnyResult<()> +where + K: AsRef, + V: AsRef, +{ + unsafe { set_env_var(key.as_ref().into(), value.as_ref().into())? }; + + Ok(()) +} + +/// Append paths to the `PATH` environment variable on the host machine. +pub fn add_host_paths(paths: I) -> AnyResult<()> +where + I: IntoIterator, + P: AsRef, +{ + let paths = paths + .into_iter() + .map(|p| p.as_ref().to_owned()) + .collect::>(); + + set_host_env_var("PATH", paths.join(":")) +} + +/// Convert the provided path into a [`PathBuf`] instance, +/// with the prefix resolved absolutely to the host. +pub fn into_real_path

(path: P) -> AnyResult +where + P: AsRef, +{ + Ok(PathBuf::from(unsafe { + from_virtual_path(path.as_ref().to_string_lossy().into())? + })) +} + +/// Convert the provided path into a [`VirtualPath`] instance, +/// with the prefix resolved to the WASM virtual whitelist. +pub fn into_virtual_path

(path: P) -> AnyResult +where + P: AsRef, +{ + let data = unsafe { to_virtual_path(path.as_ref().to_string_lossy().into())? }; + + Ok(data.0) +} + /// Return the ID for the current plugin. pub fn get_plugin_id() -> AnyResult { Ok(config::get("plugin_id")?.expect("Missing plugin ID!")) diff --git a/crates/warpgate-pdk/src/macros.rs b/crates/warpgate-pdk/src/macros.rs index 7d44d23ca..6896ff874 100644 --- a/crates/warpgate-pdk/src/macros.rs +++ b/crates/warpgate-pdk/src/macros.rs @@ -1,4 +1,4 @@ -/// Return an error message wrapped in [`WithReturnCode`], for use within `#[plugin_fn]`. +/// Return an error message wrapped in `WithReturnCode` , for use within `#[plugin_fn]`. #[macro_export] macro_rules! plugin_err { (code = $code:expr, $($arg:tt)+) => { @@ -99,7 +99,7 @@ macro_rules! host_env { } else { Some(inner) } - }; + } }; } diff --git a/crates/warpgate/Cargo.toml b/crates/warpgate/Cargo.toml index b39063b8c..49bd1ce45 100644 --- a/crates/warpgate/Cargo.toml +++ b/crates/warpgate/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "warpgate" -version = "0.21.1" +version = "0.22.1" edition = "2021" license = "MIT" description = "Download, resolve, and manage Extism WASM plugins." repository = "https://github.com/moonrepo/proto" [dependencies] -system_env = { version = "0.7.1", path = "../system-env" } -warpgate_api = { version = "0.11.1", path = "../warpgate-api" } +system_env = { version = "0.7.2", path = "../system-env" } +warpgate_api = { version = "0.12.0", path = "../warpgate-api" } async-trait = { workspace = true } compact_str = { workspace = true } extism = { workspace = true, features = ["http"] } @@ -19,7 +19,6 @@ regex = { workspace = true } reqwest = { workspace = true, features = ["json", "rustls-tls-native-roots"] } reqwest-middleware = { workspace = true, features = ["json", "rustls-tls"] } reqwest-netrc = { workspace = true } -rust-netrc = "0.1.2" schematic = { workspace = true, optional = true, features = ["schema"] } scc = { workspace = true } serde = { workspace = true } @@ -33,7 +32,7 @@ tokio = { workspace = true } tracing = { workspace = true } # Enabling certs for extism! -ureq = { version = "2.12.1", features = ["native-certs"] } +ureq = { version = "3.0.5", features = ["rustls", "platform-verifier"] } [dev-dependencies] starbase_sandbox = { workspace = true } diff --git a/crates/warpgate/src/client.rs b/crates/warpgate/src/client.rs index 4dae3e19b..25eb2cdc2 100644 --- a/crates/warpgate/src/client.rs +++ b/crates/warpgate/src/client.rs @@ -1,18 +1,20 @@ -use crate::error::WarpgateError; +use crate::client_error::WarpgateClientError; use async_trait::async_trait; use core::ops::Deref; use miette::IntoDiagnostic; -use netrc::Netrc; use reqwest::{Client, Response, Url}; -use reqwest_middleware::{ClientBuilder, ClientWithMiddleware, RequestBuilder, RequestInitialiser}; +use reqwest_middleware::{ClientBuilder, ClientWithMiddleware}; +use reqwest_netrc::NetrcMiddleware; use serde::{Deserialize, Serialize}; use starbase_utils::{ + env::is_docker, fs, net::{Downloader, NetError}, }; use std::path::PathBuf; use tracing::{debug, trace, warn}; +/// A downloader that uses our internal HTTP(S) client. pub struct HttpDownloader { client: HttpClient, } @@ -42,6 +44,8 @@ impl Downloader for HttpDownloader { // `ClientWithMiddleware` doesn't allow access to their inner `Client`, // so we unfortunately need to keep a reference to both. // https://github.com/TrueLayer/reqwest-middleware/issues/203 + +/// An HTTP(S) client with middleware that wraps [`reqwest::Client`]. #[derive(Clone, Default)] pub struct HttpClient { client: Client, @@ -59,13 +63,13 @@ impl HttpClient { &self.client } - pub fn map_error(url: String, error: reqwest_middleware::Error) -> WarpgateError { + pub fn map_error(url: String, error: reqwest_middleware::Error) -> WarpgateClientError { match error { - reqwest_middleware::Error::Middleware(inner) => WarpgateError::HttpMiddleware { + reqwest_middleware::Error::Middleware(inner) => WarpgateClientError::HttpMiddleware { error: format!("{}", inner), url, }, - reqwest_middleware::Error::Reqwest(inner) => WarpgateError::Http { + reqwest_middleware::Error::Reqwest(inner) => WarpgateClientError::Http { error: Box::new(inner), url, }, @@ -81,7 +85,7 @@ impl Deref for HttpClient { } } -/// Configures the HTTPS client used for making requests. +/// Configures the HTTP(S) client used for making requests. #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(default, rename_all = "kebab-case")] #[cfg_attr(feature = "schematic", derive(schematic::Schematic))] @@ -103,12 +107,12 @@ pub struct HttpOptions { pub root_cert: Option, } -/// Create an HTTP/HTTPS client that'll be used for downloading files. +/// Create an HTTP(S) client that'll be used for downloading files. pub fn create_http_client() -> miette::Result { create_http_client_with_options(&HttpOptions::default()) } -/// Create an HTTP/HTTPS client with the provided options, that'll be +/// Create an HTTP(S) client with the provided options, that'll be /// used for downloading files. pub fn create_http_client_with_options(options: &HttpOptions) -> miette::Result { debug!("Creating HTTP client"); @@ -129,14 +133,22 @@ pub fn create_http_client_with_options(options: &HttpOptions) -> miette::Result< match root_cert.extension().and_then(|ext| ext.to_str()) { Some("der") => { client_builder = client_builder.add_root_certificate( - reqwest::Certificate::from_der(&fs::read_file_bytes(root_cert)?) - .into_diagnostic()?, + reqwest::Certificate::from_der(&fs::read_file_bytes(root_cert)?).map_err( + |error| WarpgateClientError::InvalidCert { + path: root_cert.to_path_buf(), + error: Box::new(error), + }, + )?, ) } Some("pem") => { client_builder = client_builder.add_root_certificate( - reqwest::Certificate::from_pem(&fs::read_file_bytes(root_cert)?) - .into_diagnostic()?, + reqwest::Certificate::from_pem(&fs::read_file_bytes(root_cert)?).map_err( + |error| WarpgateClientError::InvalidCert { + path: root_cert.to_path_buf(), + error: Box::new(error), + }, + )?, ) } _ => { @@ -165,7 +177,13 @@ pub fn create_http_client_with_options(options: &HttpOptions) -> miette::Result< trace!(proxies = ?insecure_proxies, "Adding insecure proxies to client"); for proxy in insecure_proxies { - client_builder = client_builder.proxy(reqwest::Proxy::http(proxy).into_diagnostic()?); + client_builder = + client_builder.proxy(reqwest::Proxy::http(proxy).map_err(|error| { + WarpgateClientError::InvalidProxy { + url: proxy.to_owned(), + error: Box::new(error), + } + })?); } } @@ -173,7 +191,13 @@ pub fn create_http_client_with_options(options: &HttpOptions) -> miette::Result< trace!(proxies = ?secure_proxies, "Adding secure proxies to client"); for proxy in secure_proxies { - client_builder = client_builder.proxy(reqwest::Proxy::https(proxy).into_diagnostic()?); + client_builder = + client_builder.proxy(reqwest::Proxy::https(proxy).map_err(|error| { + WarpgateClientError::InvalidProxy { + url: proxy.to_owned(), + error: Box::new(error), + } + })?); } } @@ -190,26 +214,28 @@ pub fn create_http_client_with_options(options: &HttpOptions) -> miette::Result< } if let Some(cache_dir) = &options.cache_dir { - use http_cache_reqwest::{ - CACacheManager, Cache, CacheMode, CacheOptions, HttpCache, HttpCacheOptions, - }; + if !is_docker() { + use http_cache_reqwest::{ + CACacheManager, Cache, CacheMode, CacheOptions, HttpCache, HttpCacheOptions, + }; - trace!("Adding GET and HEAD request caching"); + trace!("Adding GET and HEAD request caching"); - middleware_builder = middleware_builder.with(Cache(HttpCache { - manager: CACacheManager { - path: cache_dir.to_owned(), - }, - mode: CacheMode::Default, - options: HttpCacheOptions { - // https://github.com/kornelski/rusty-http-cache-semantics - cache_options: Some(CacheOptions { - cache_heuristic: 0.025, + middleware_builder = middleware_builder.with(Cache(HttpCache { + manager: CACacheManager { + path: cache_dir.to_owned(), + }, + mode: CacheMode::Default, + options: HttpCacheOptions { + // https://github.com/kornelski/rusty-http-cache-semantics + cache_options: Some(CacheOptions { + cache_heuristic: 0.025, + ..Default::default() + }), ..Default::default() - }), - ..Default::default() - }, - })); + }, + })); + } } let middleware = middleware_builder.build(); @@ -218,49 +244,3 @@ pub fn create_http_client_with_options(options: &HttpOptions) -> miette::Result< Ok(HttpClient { client, middleware }) } - -// TODO: Temporary until this lands -// https://github.com/gribouille/netrc/issues/9 -pub struct NetrcMiddleware { - nrc: Netrc, -} - -impl NetrcMiddleware { - pub fn new() -> netrc::Result { - Netrc::new().map(|nrc| NetrcMiddleware { nrc }) - } -} - -impl RequestInitialiser for NetrcMiddleware { - fn init(&self, req: RequestBuilder) -> RequestBuilder { - match req.try_clone() { - Some(nr) => req - .try_clone() - .unwrap() - .build() - .ok() - .and_then(|r| { - r.url() - .host_str() - .and_then(|host| { - self.nrc - .hosts - .get(host) - .or_else(|| self.nrc.hosts.get("default")) - }) - .map(|auth| { - nr.basic_auth( - &auth.login, - if auth.password.is_empty() { - None - } else { - Some(&auth.password) - }, - ) - }) - }) - .unwrap_or(req), - None => req, - } - } -} diff --git a/crates/warpgate/src/client_error.rs b/crates/warpgate/src/client_error.rs new file mode 100644 index 000000000..0ce770ea7 --- /dev/null +++ b/crates/warpgate/src/client_error.rs @@ -0,0 +1,40 @@ +use miette::Diagnostic; +use starbase_styles::{Style, Stylize}; +use std::path::PathBuf; +use thiserror::Error; + +/// HTTP(S) client errors. +#[derive(Debug, Diagnostic, Error)] +pub enum WarpgateClientError { + #[diagnostic(code(plugin::http_client::request_failed))] + #[error("Failed to make HTTP request for {}.", .url.style(Style::Url))] + Http { + url: String, + #[source] + error: Box, + }, + + #[diagnostic(code(plugin::http_client::request_failed))] + #[error( + "Failed to make HTTP request for {}: {}", + .url.style(Style::Url), + .error.style(Style::MutedLight), + )] + HttpMiddleware { url: String, error: String }, + + #[diagnostic(code(plugin::http_client::invalid_cert))] + #[error("Invalid certificate {}.", .path.style(Style::Path))] + InvalidCert { + path: PathBuf, + #[source] + error: Box, + }, + + #[diagnostic(code(plugin::http_client::invalid_proxy))] + #[error("Invalid proxy {}.", .url.style(Style::Url))] + InvalidProxy { + url: String, + #[source] + error: Box, + }, +} diff --git a/crates/warpgate/src/endpoints.rs b/crates/warpgate/src/endpoints.rs index cb90c0f97..dae1466dd 100644 --- a/crates/warpgate/src/endpoints.rs +++ b/crates/warpgate/src/endpoints.rs @@ -1,5 +1,5 @@ use crate::client::HttpClient; -use crate::error::WarpgateError; +use crate::client_error::WarpgateClientError; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::env; @@ -42,10 +42,13 @@ pub async fn send_github_request( .await .map_err(|error| HttpClient::map_error(url.to_owned(), error))?; - let data: T = response.json().await.map_err(|error| WarpgateError::Http { - error: Box::new(error), - url: url.to_owned(), - })?; + let data: T = response + .json() + .await + .map_err(|error| WarpgateClientError::Http { + error: Box::new(error), + url: url.to_owned(), + })?; Ok(data) } diff --git a/crates/warpgate/src/error.rs b/crates/warpgate/src/error.rs deleted file mode 100644 index 2cb35ac6b..000000000 --- a/crates/warpgate/src/error.rs +++ /dev/null @@ -1,156 +0,0 @@ -use crate::id::Id; -use miette::Diagnostic; -use starbase_styles::{Style, Stylize}; -use std::path::PathBuf; -use thiserror::Error; - -#[derive(Debug, Diagnostic, Error)] -pub enum WarpgateError { - #[diagnostic(code(plugin::invalid_syntax))] - #[error("{0}")] - Serde(String), - - #[diagnostic(code(plugin::http))] - #[error("Failed to make HTTP request for {}.", .url.style(Style::Url))] - Http { - url: String, - #[source] - error: Box, - }, - - #[diagnostic(code(plugin::http))] - #[error("Failed to make HTTP request for {}: {}", .url.style(Style::Url), .error.style(Style::MutedLight))] - HttpMiddleware { url: String, error: String }, - - #[diagnostic(code(plugin::offline))] - #[error("{message} An internet connection is required to request {}.", .url.style(Style::Url))] - InternetConnectionRequired { message: String, url: String }, - - #[diagnostic(code(plugin::invalid_id))] - #[error( - "Invalid plugin identifier {}. May only contain letters, numbers, dashes, and underscores.", - .0.style(Style::Id), - )] - InvalidID(String), - - #[diagnostic(code(plugin::source::file_missing))] - #[error( - "Cannot load {} plugin, source file {} does not exist.", - .id.to_string().style(Style::Id), - .path.style(Style::Path), - )] - SourceFileMissing { id: Id, path: PathBuf }, - - #[diagnostic(code(plugin::github::asset_missing))] - #[error( - "Cannot download {} plugin from GitHub ({}), no tag found, matched, or provided.", - .id.to_string().style(Style::Id), - .repo_slug.style(Style::Id), - )] - GitHubTagMissing { id: Id, repo_slug: String }, - - #[diagnostic(code(plugin::github::asset_missing))] - #[error( - "Cannot download {} plugin from GitHub ({}), no applicable asset found for release {}.", - .id.to_string().style(Style::Id), - .repo_slug.style(Style::Id), - .tag, - )] - GitHubAssetMissing { - id: Id, - repo_slug: String, - tag: String, - }, - - #[diagnostic(code(plugin::create::failed))] - #[error("Failed to load and create {} plugin: {error}", .id.to_string().style(Style::Id))] - PluginCreateFailed { - id: Id, - #[source] - error: Box, - }, - - #[diagnostic(code(plugin::call_func::failed))] - #[error( - "Failed to call {} plugin function {}:\n{error}", - .id.to_string().style(Style::Id), - .func.style(Style::Property), - )] - PluginCallFailed { id: Id, func: String, error: String }, - - #[diagnostic(code(plugin::call_func::failed))] - #[error("{error}")] - PluginCallFailedRelease { error: String }, - - #[diagnostic(code(plugin::missing_command))] - #[error( - "Command or script {} does not exist. Unable to execute from plugin.", .command.style(Style::Shell) - )] - PluginCommandMissing { command: String }, - - #[diagnostic(code(plugin::call_func::format_input))] - #[error( - "Failed to format input for {} plugin function {} call.", - .id.to_string().style(Style::Id), - .func.style(Style::Property), - )] - FormatInputFailed { - id: Id, - func: String, - #[source] - error: Box, - }, - - #[diagnostic(code(plugin::call_func::parse_output))] - #[error( - "Failed to parse output of {} plugin function {} call.", - .id.to_string().style(Style::Id), - .func.style(Style::Property), - )] - ParseOutputFailed { - id: Id, - func: String, - #[source] - error: Box, - }, - - #[diagnostic( - code(plugin::download::not_found), - help = "Please refer to the plugin's official documentation." - )] - #[error( - "Plugin download {} does not exist. Either this version may not be supported for your current operating system or architecture, or the URL is incorrect or malformed.", - .url.style(Style::Url), - )] - DownloadNotFound { url: String }, - - #[diagnostic(code(plugin::download::no_wasm))] - #[error( - "No applicable {} file could be found in downloaded plugin {}.", - ".wasm".style(Style::File), - .path.style(Style::Path), - )] - DownloadNoWasm { path: PathBuf }, - - #[diagnostic(code(plugin::download::unsupported_extension))] - #[error( - "Unsupported file extension {} for downloaded plugin {}.", - .ext.style(Style::File), - .path.style(Style::Path), - )] - DownloadUnsupportedExtension { ext: String, path: PathBuf }, - - #[diagnostic(code(plugin::download::unknown_type))] - #[error( - "Unsure how to handle downloaded plugin {} as no file extension/type could be derived.", - .path.style(Style::Path), - )] - DownloadUnknownType { path: PathBuf }, - - #[diagnostic(code(plugin::incompatible_runtime))] - #[error( - "The loaded {} plugin is incompatible with the current runtime.\nFor plugin consumers, try upgrading to a newer plugin version.\nFor plugin authors, upgrade to the latest runtime and release a new version.", - .id.to_string().style(Style::Id), - )] - IncompatibleRuntime { id: Id }, -} diff --git a/crates/warpgate/src/helpers.rs b/crates/warpgate/src/helpers.rs index 5e0f6dc27..9f80d04a3 100644 --- a/crates/warpgate/src/helpers.rs +++ b/crates/warpgate/src/helpers.rs @@ -1,5 +1,5 @@ use crate::client::HttpClient; -use crate::error::WarpgateError; +use crate::loader_error::WarpgateLoaderError; use sha2::{Digest, Sha256}; use starbase_archive::{is_supported_archive_extension, Archiver}; use starbase_utils::{fs, glob, net, net::NetError}; @@ -9,6 +9,7 @@ use std::path::{Path, PathBuf}; use tracing::instrument; use warpgate_api::VirtualPath; +/// Create a SHA256 hash key based on the provided URL and seed. pub fn create_cache_key(url: &str, seed: Option<&str>) -> String { let mut sha = Sha256::new(); sha.update(url); @@ -20,20 +21,25 @@ pub fn create_cache_key(url: &str, seed: Option<&str>) -> String { format!("{:x}", sha.finalize()) } +/// Determine the extension to use for a cache file, based on our +/// list of supported extensions. pub fn determine_cache_extension(value: &str) -> Option<&str> { [".toml", ".json", ".jsonc", ".yaml", ".yml", ".wasm", ".txt"] .into_iter() .find(|ext| value.ends_with(ext)) } +/// Download a file from the provided URL, with the provided HTTP(S) +/// client, and save it to a destination location. +#[instrument(skip(client))] pub async fn download_from_url_to_file( source_url: &str, - temp_file: &Path, + dest_file: &Path, client: &HttpClient, ) -> miette::Result<()> { if let Err(error) = net::download_from_url_with_options( source_url, - temp_file, + dest_file, net::DownloadOptions { downloader: Some(Box::new(client.create_downloader())), ..Default::default() @@ -42,7 +48,7 @@ pub async fn download_from_url_to_file( .await { return Err(match error { - NetError::UrlNotFound { url } => WarpgateError::DownloadNotFound { url }.into(), + NetError::UrlNotFound { url } => WarpgateLoaderError::NotFound { url }.into(), _ => error.into(), }); }; @@ -50,6 +56,8 @@ pub async fn download_from_url_to_file( Ok(()) } +/// If the temporary file is an archive, unpack it into the destination, +/// otherwise more the file into the destination. #[instrument] pub fn move_or_unpack_download(temp_file: &Path, dest_file: &Path) -> miette::Result<()> { // Archive supported file extensions @@ -61,7 +69,7 @@ pub fn move_or_unpack_download(temp_file: &Path, dest_file: &Path) -> miette::Re let wasm_files = glob::walk_files(&out_dir, ["**/*.wasm"])?; if wasm_files.is_empty() { - return Err(WarpgateError::DownloadNoWasm { + return Err(WarpgateLoaderError::NoWasmFound { path: temp_file.to_path_buf(), } .into()); @@ -89,7 +97,7 @@ pub fn move_or_unpack_download(temp_file: &Path, dest_file: &Path) -> miette::Re } Some(ext) => { - return Err(WarpgateError::DownloadUnsupportedExtension { + return Err(WarpgateLoaderError::UnsupportedDownloadExtension { ext: ext.to_owned(), path: temp_file.to_path_buf(), } @@ -97,7 +105,7 @@ pub fn move_or_unpack_download(temp_file: &Path, dest_file: &Path) -> miette::Re } None => { - return Err(WarpgateError::DownloadUnknownType { + return Err(WarpgateLoaderError::UnknownDownloadType { path: temp_file.to_path_buf(), } .into()); diff --git a/crates/warpgate/src/host.rs b/crates/warpgate/src/host.rs index 2a7cba1f7..51b7dcf6d 100644 --- a/crates/warpgate/src/host.rs +++ b/crates/warpgate/src/host.rs @@ -1,9 +1,8 @@ use crate::client::HttpClient; -use crate::error::WarpgateError; -// use crate::helpers::{self, create_cache_key, determine_cache_extension}; +use crate::client_error::WarpgateClientError; use crate::helpers; +use crate::plugin_error::WarpgatePluginError; use extism::{CurrentPlugin, Error, Function, UserData, Val, ValType}; -// use reqwest::header; use starbase_styles::color::{self, apply_style_tags}; use starbase_utils::env::paths; use starbase_utils::fs; @@ -11,7 +10,6 @@ use std::collections::BTreeMap; use std::env; use std::path::PathBuf; use std::sync::Arc; -// use std::time::{Duration, SystemTime}; use system_env::{create_process_command, find_command_on_path}; use tokio::runtime::Handle; use tracing::{instrument, trace}; @@ -20,6 +18,7 @@ use warpgate_api::{ SendRequestOutput, }; +/// Data passed to each host function. #[derive(Clone, Default)] pub struct HostData { pub cache_dir: PathBuf, @@ -28,6 +27,7 @@ pub struct HostData { pub working_dir: PathBuf, } +/// Create a list of our built-in host functions. pub fn create_host_functions(data: HostData) -> Vec { vec![ Function::new( @@ -167,7 +167,7 @@ fn exec_command( }; let Some(bin) = &maybe_bin else { - return Err(WarpgateError::PluginCommandMissing { + return Err(WarpgatePluginError::MissingCommand { command: input.command.clone(), } .into()); @@ -288,10 +288,13 @@ fn send_request( let status = response.status().as_u16(); let bytes = Handle::current().block_on(async { - response.bytes().await.map_err(|error| WarpgateError::Http { - url: input.url.clone(), - error: Box::new(error), - }) + response + .bytes() + .await + .map_err(|error| WarpgateClientError::Http { + url: input.url.clone(), + error: Box::new(error), + }) })?; // Create and return our intermediate shapes diff --git a/crates/warpgate/src/id.rs b/crates/warpgate/src/id.rs index 1c46f90d9..49dd5b74a 100644 --- a/crates/warpgate/src/id.rs +++ b/crates/warpgate/src/id.rs @@ -1,10 +1,11 @@ -use crate::error::WarpgateError; +use crate::plugin_error::WarpgatePluginError; use compact_str::CompactString; use regex::Regex; use serde::{de, Deserialize, Deserializer, Serialize}; use std::sync::LazyLock; use std::{borrow::Borrow, fmt, ops::Deref, str::FromStr}; +#[doc(hidden)] pub static ID_PATTERN: LazyLock = LazyLock::new(|| Regex::new("^[a-zA-Z][a-zA-Z0-9-_]*$").unwrap()); @@ -13,11 +14,11 @@ pub static ID_PATTERN: LazyLock = pub struct Id(CompactString); impl Id { - pub fn new>(id: S) -> Result { + pub fn new>(id: S) -> Result { let id = id.as_ref(); if !ID_PATTERN.is_match(id) { - return Err(WarpgateError::InvalidID(id.to_owned())); + return Err(WarpgatePluginError::InvalidID(id.to_owned())); } Ok(Self::raw(id)) @@ -113,7 +114,7 @@ impl Borrow for Id { // Parsing values impl FromStr for Id { - type Err = WarpgateError; + type Err = WarpgatePluginError; fn from_str(s: &str) -> Result { Id::new(s) diff --git a/crates/warpgate/src/lib.rs b/crates/warpgate/src/lib.rs index 0cca909d4..4d91546f4 100644 --- a/crates/warpgate/src/lib.rs +++ b/crates/warpgate/src/lib.rs @@ -1,19 +1,23 @@ mod client; +mod client_error; mod endpoints; -mod error; mod helpers; pub mod host; mod id; mod loader; +mod loader_error; mod plugin; +mod plugin_error; pub mod test_utils; pub use client::*; -pub use error::*; +pub use client_error::*; pub use helpers::*; pub use id::*; pub use loader::*; +pub use loader_error::*; pub use plugin::*; +pub use plugin_error::*; pub use extism::{Manifest as PluginManifest, Wasm}; pub use warpgate_api as api; diff --git a/crates/warpgate/src/loader.rs b/crates/warpgate/src/loader.rs index 63bd9fd1d..f2c3936ad 100644 --- a/crates/warpgate/src/loader.rs +++ b/crates/warpgate/src/loader.rs @@ -1,10 +1,10 @@ use crate::client::{create_http_client_with_options, HttpClient, HttpOptions}; use crate::endpoints::*; -use crate::error::WarpgateError; use crate::helpers::{ create_cache_key, determine_cache_extension, download_from_url_to_file, move_or_unpack_download, }; use crate::id::Id; +use crate::loader_error::WarpgateLoaderError; use once_cell::sync::OnceCell; use starbase_archive::is_supported_archive_extension; use starbase_styles::color; @@ -108,7 +108,7 @@ impl PluginLoader { Ok(path) } else { - Err(WarpgateError::SourceFileMissing { + Err(WarpgateLoaderError::MissingSourceFile { id: id.to_owned(), path: path.to_path_buf(), } @@ -230,7 +230,7 @@ impl PluginLoader { } if self.is_offline() { - return Err(WarpgateError::InternetConnectionRequired { + return Err(WarpgateLoaderError::RequiredInternetConnection { message: "Unable to download plugin.".into(), url: source_url.to_owned(), } @@ -271,7 +271,7 @@ impl PluginLoader { } if self.is_offline() { - return Err(WarpgateError::InternetConnectionRequired { + return Err(WarpgateLoaderError::RequiredInternetConnection { message: format!( "Unable to download plugin {} from GitHub.", PluginLocator::GitHub(Box::new(github.to_owned())) @@ -309,7 +309,7 @@ impl PluginLoader { } let Some(release_tag) = found_tag else { - return Err(WarpgateError::GitHubTagMissing { + return Err(WarpgateLoaderError::MissingGitHubTag { id: id.to_owned(), repo_slug: github.repo_slug.to_owned(), } @@ -373,7 +373,7 @@ impl PluginLoader { } } - Err(WarpgateError::GitHubAssetMissing { + Err(WarpgateLoaderError::MissingGitHubAsset { id: id.to_owned(), repo_slug: github.repo_slug.to_owned(), tag: release_tag, diff --git a/crates/warpgate/src/loader_error.rs b/crates/warpgate/src/loader_error.rs new file mode 100644 index 000000000..778b2bfcb --- /dev/null +++ b/crates/warpgate/src/loader_error.rs @@ -0,0 +1,75 @@ +use crate::id::Id; +use miette::Diagnostic; +use starbase_styles::{Style, Stylize}; +use std::path::PathBuf; +use thiserror::Error; + +/// Loader errors. +#[derive(Debug, Diagnostic, Error)] +pub enum WarpgateLoaderError { + #[diagnostic(code(plugin::loader::github::asset_missing))] + #[error( + "Cannot download {} plugin from GitHub ({}), no applicable asset found for release {}.", + .id.to_string().style(Style::Id), + .repo_slug.style(Style::Id), + .tag, + )] + MissingGitHubAsset { + id: Id, + repo_slug: String, + tag: String, + }, + + #[diagnostic(code(plugin::loader::github::unknown_tag))] + #[error( + "Cannot download {} plugin from GitHub ({}), no tag found, matched, or provided.", + .id.to_string().style(Style::Id), + .repo_slug.style(Style::Id), + )] + MissingGitHubTag { id: Id, repo_slug: String }, + + #[diagnostic(code(plugin::loader::file::missing))] + #[error( + "Cannot load {} plugin, source file {} does not exist.", + .id.to_string().style(Style::Id), + .path.style(Style::Path), + )] + MissingSourceFile { id: Id, path: PathBuf }, + + #[diagnostic(code(plugin::loader::no_wasm))] + #[error( + "No applicable {} file could be found in downloaded plugin {}.", + ".wasm".style(Style::File), + .path.style(Style::Path), + )] + NoWasmFound { path: PathBuf }, + + #[diagnostic( + code(plugin::loader::not_found), + help = "Please refer to the plugin's official documentation." + )] + #[error( + "Plugin download {} does not exist. Either this version may not be supported for your current operating system or architecture, or the URL is incorrect or malformed.", + .url.style(Style::Url), + )] + NotFound { url: String }, + + #[diagnostic(code(plugin::offline))] + #[error("{message} An internet connection is required to request {}.", .url.style(Style::Url))] + RequiredInternetConnection { message: String, url: String }, + + #[diagnostic(code(plugin::loader::unsupported_extension))] + #[error( + "Unsupported file extension {} for downloaded plugin {}.", + .ext.style(Style::File), + .path.style(Style::Path), + )] + UnsupportedDownloadExtension { ext: String, path: PathBuf }, + + #[diagnostic(code(plugin::loader::unknown_type))] + #[error( + "Unsure how to handle downloaded plugin {} as no file extension/type could be derived.", + .path.style(Style::Path), + )] + UnknownDownloadType { path: PathBuf }, +} diff --git a/crates/warpgate/src/plugin.rs b/crates/warpgate/src/plugin.rs index 7f5d553ef..2c760f026 100644 --- a/crates/warpgate/src/plugin.rs +++ b/crates/warpgate/src/plugin.rs @@ -1,7 +1,7 @@ use crate::endpoints::Empty; -use crate::error::WarpgateError; use crate::helpers::{from_virtual_path, to_virtual_path}; use crate::id::Id; +use crate::plugin_error::WarpgatePluginError; use extism::{Error, Function, Manifest, Plugin}; use miette::IntoDiagnostic; use serde::de::DeserializeOwned; @@ -99,9 +99,9 @@ impl PluginContainer { let plugin = Plugin::new(&manifest, functions, true).map_err(|error| { if is_incompatible_runtime(&error) { - WarpgateError::IncompatibleRuntime { id: id.clone() } + WarpgatePluginError::IncompatibleRuntime { id: id.clone() } } else { - WarpgateError::PluginCreateFailed { + WarpgatePluginError::FailedContainer { id: id.clone(), error: Box::new(error), } @@ -236,7 +236,7 @@ impl PluginContainer { let output = block_in_place(|| instance.call(func, input)).map_err(|error| { if is_incompatible_runtime(&error) { - return WarpgateError::IncompatibleRuntime { + return WarpgatePluginError::IncompatibleRuntime { id: self.id.clone(), }; } @@ -254,7 +254,7 @@ impl PluginContainer { // When in debug mode, include more information around errors. #[cfg(debug_assertions)] { - WarpgateError::PluginCallFailed { + WarpgatePluginError::FailedPluginCall { id: self.id.clone(), func: func.to_owned(), error: message, @@ -265,7 +265,7 @@ impl PluginContainer { // previous variant, so this is a special variant that renders as-is. #[cfg(not(debug_assertions))] { - WarpgateError::PluginCallFailedRelease { error: message } + WarpgatePluginError::FailedPluginCallRelease { error: message } } })?; @@ -282,7 +282,7 @@ impl PluginContainer { fn format_input(&self, func: &str, input: I) -> miette::Result { Ok( - serde_json::to_string(&input).map_err(|error| WarpgateError::FormatInputFailed { + serde_json::to_string(&input).map_err(|error| WarpgatePluginError::InvalidInput { id: self.id.clone(), func: func.to_owned(), error: Box::new(error), @@ -292,7 +292,7 @@ impl PluginContainer { fn parse_output(&self, func: &str, data: &[u8]) -> miette::Result { Ok( - serde_json::from_slice(data).map_err(|error| WarpgateError::ParseOutputFailed { + serde_json::from_slice(data).map_err(|error| WarpgatePluginError::InvalidOutput { id: self.id.clone(), func: func.to_owned(), error: Box::new(error), diff --git a/crates/warpgate/src/plugin_error.rs b/crates/warpgate/src/plugin_error.rs new file mode 100644 index 000000000..06c372226 --- /dev/null +++ b/crates/warpgate/src/plugin_error.rs @@ -0,0 +1,74 @@ +use crate::id::Id; +use miette::Diagnostic; +use starbase_styles::{Style, Stylize}; +use thiserror::Error; + +/// Plugin/runtime errors. +#[derive(Debug, Diagnostic, Error)] +pub enum WarpgatePluginError { + #[diagnostic(code(plugin::wasm::failed_container))] + #[error("Failed to load and create {} plugin: {error}", .id.to_string().style(Style::Id))] + FailedContainer { + id: Id, + #[source] + error: Box, + }, + + #[diagnostic(code(plugin::wasm::failed_function_call))] + #[error( + "Failed to call {} plugin function {}:\n{error}", + .id.to_string().style(Style::Id), + .func.style(Style::Property), + )] + FailedPluginCall { id: Id, func: String, error: String }, + + #[diagnostic(code(plugin::wasm::failed_function_call))] + #[error("{error}")] + FailedPluginCallRelease { error: String }, + + #[diagnostic(code(plugin::wasm::incompatible_runtime))] + #[error( + "The loaded {} plugin is incompatible with the current runtime.\nFor plugin consumers, try upgrading to a newer plugin version.\nFor plugin authors, upgrade to the latest runtime and release a new version.", + .id.to_string().style(Style::Id), + )] + IncompatibleRuntime { id: Id }, + + #[diagnostic(code(plugin::wasm::invalid_input))] + #[error( + "Failed to format input for {} plugin function {} call.", + .id.to_string().style(Style::Id), + .func.style(Style::Property), + )] + InvalidInput { + id: Id, + func: String, + #[source] + error: Box, + }, + + #[diagnostic(code(plugin::wasm::invalid_output))] + #[error( + "Failed to parse output of {} plugin function {} call.", + .id.to_string().style(Style::Id), + .func.style(Style::Property), + )] + InvalidOutput { + id: Id, + func: String, + #[source] + error: Box, + }, + + #[diagnostic(code(plugin::invalid_id))] + #[error( + "Invalid plugin identifier {}. May only contain letters, numbers, dashes, and underscores.", + .0.style(Style::Id), + )] + InvalidID(String), + + #[diagnostic(code(plugin::wasm::missing_command))] + #[error( + "Command or script {} does not exist. Unable to execute from plugin.", .command.style(Style::Shell) + )] + MissingCommand { command: String }, +} diff --git a/crates/warpgate/src/test_utils.rs b/crates/warpgate/src/test_utils.rs index 9ea89f0d0..92143454f 100644 --- a/crates/warpgate/src/test_utils.rs +++ b/crates/warpgate/src/test_utils.rs @@ -5,6 +5,7 @@ use std::env; use std::path::{Path, PathBuf}; use warpgate_api::{HostArch, HostEnvironment, HostLibc, HostOS, TestEnvironment, VirtualPath}; +/// Find the WASM compiled target directory. pub fn find_target_dir>(search_dir: T) -> Option { let mut dir = search_dir.as_ref(); let profiles = ["debug", "release"]; @@ -40,6 +41,8 @@ pub fn find_target_dir>(search_dir: T) -> Option { None } +/// Find an applicable WASM file to return tests with. Will attempt to find +/// the file based on the Cargo package name and target directories. pub fn find_wasm_file() -> PathBuf { let wasm_file_name = env::var("CARGO_PKG_NAME").expect("Missing CARGO_PKG_NAME!"); diff --git a/package/src/api-types.ts b/package/src/api-types.ts index f798cecd9..c48ddaa98 100644 --- a/package/src/api-types.ts +++ b/package/src/api-types.ts @@ -11,6 +11,9 @@ export type SystemOS = 'android' | 'dragonfly' | 'freebsd' | 'ios' | 'linux' | ' /** Libc being used in the system environment. */ export type SystemLibc = 'gnu' | 'musl' | 'unknown'; +/** Package manager of the system environment. */ +export type SystemPackageManager = 'pkg' | 'pkgin' | 'apk' | 'apt' | 'dnf' | 'pacman' | 'yum' | 'brew' | 'choco' | 'scoop' | 'all'; + export type VersionSpec = string; export type UnresolvedVersionSpec = string; @@ -86,12 +89,6 @@ export interface ToolContext { /** Supported types of plugins. */ export type PluginType = 'command-line' | 'language' | 'dependency-manager' | 'version-manager'; -/** Input passed to the `register_tool` function. */ -export interface ToolMetadataInput { - /** ID of the tool, as it was configured. */ - id: string; -} - /** Controls aspects of the tool inventory. */ export interface ToolInventoryMetadata { /** @@ -103,13 +100,19 @@ export interface ToolInventoryMetadata { versionSuffix?: string | null; } +/** Input passed to the `register_tool` function. */ +export interface RegisterToolInput { + /** ID of the tool, as it was configured. */ + id: string; +} + /** Supported strategies for installing a tool. */ export type InstallStrategy = 'build-from-source' | 'download-prebuilt'; export type Switch = boolean | string; /** Output returned by the `register_tool` function. */ -export interface ToolMetadataOutput { +export interface RegisterToolOutput { /** Schema shape of the tool's configuration. */ configSchema?: unknown | null; /** @@ -150,6 +153,49 @@ export interface ToolMetadataOutput { unstable?: Switch; } +/** Input passed to the `register_backend` function. */ +export interface RegisterBackendInput { + /** Current tool context. */ + context: ToolContext; + /** ID of the tool, as it was configured. */ + id: string; +} + +/** Source code is contained in an archive. */ +export interface ArchiveSource { + /** A path prefix within the archive to remove. */ + prefix?: string | null; + type: 'archive'; + /** The URL to download the archive from. */ + url: string; +} + +/** Source code is located in a Git repository. */ +export interface GitSource { + /** The branch/commit/tag to checkout. */ + reference?: string | null; + /** Include submodules during checkout. */ + submodules?: boolean; + type: 'git'; + /** The URL of the Git remote. */ + url: string; +} + +export type SourceLocation = ArchiveSource | GitSource; + +/** Output returned by the `register_backend` function. */ +export interface RegisterBackendOutput { + /** Unique identifier for this backend. Will be used as the folder name. */ + backendId: string; + /** + * List of executables, relative from the backend directory, + * that will be executed in the context of proto. + */ + exes?: string[]; + /** Location in which to acquire source files for the backend. */ + source?: SourceLocation | null; +} + /** Output returned by the `detect_version_files` function. */ export interface DetectVersionOutput { /** List of files that should be checked for version information. */ @@ -345,6 +391,8 @@ export interface LocateExecutablesOutput { /** Input passed to the `load_versions` function. */ export interface LoadVersionsInput { + /** Current tool context. */ + context: ToolContext; /** The alias or version currently being resolved. */ initial: UnresolvedVersionSpec; } @@ -363,6 +411,8 @@ export interface LoadVersionsOutput { /** Input passed to the `resolve_version` function. */ export interface ResolveVersionInput { + /** Current tool context. */ + context: ToolContext; /** The alias or version currently being resolved. */ initial: UnresolvedVersionSpec; } @@ -463,35 +513,13 @@ export interface BuildInstructionsInput { context: ToolContext; } -/** Source code is contained in an archive. */ -export interface ArchiveSource { - /** A path prefix within the archive to remove. */ - prefix?: string | null; - type: 'archive'; - /** The URL to download the archive from. */ - url: string; -} - -/** Source code is located in a Git repository. */ -export interface GitSource { - /** The branch/commit/tag to checkout. */ - reference?: string | null; - /** Include submodules during checkout. */ - submodules?: boolean; - type: 'git'; - /** The URL of the Git remote. */ - url: string; -} - -export type SourceLocation = ArchiveSource | GitSource; - export type BuildInstruction = { /** A builder and its parameters for installing the builder. */ instruction: { /** Primary executable, relative from the source root. */ exe: string; /** Secondary executables, relative from the source root. */ - exes: Record; + exes?: Record; /** The Git source location for the builder. */ git: GitSource; /** Unique identifier for this builder. */ @@ -504,6 +532,9 @@ export type BuildInstruction = { } | { instruction: [string, string]; type: 'move-file'; +} | { + instruction: string[]; + type: 'remove-all-except'; } | { instruction: string; type: 'remove-dir'; @@ -556,9 +587,6 @@ export type BuildRequirement = { type: 'windows-developer-mode'; }; -/** Package manager of the system environment. */ -export type SystemPackageManager = 'pkg' | 'pkgin' | 'apk' | 'apt' | 'dnf' | 'pacman' | 'yum' | 'brew' | 'choco' | 'scoop' | 'all'; - export type DependencyName = string | Record | string[] | Record; export type SystemDependency = string | Record | string[] | Record | { @@ -570,8 +598,6 @@ export type SystemDependency = string | Record | s manager?: SystemPackageManager | null; /** Only install on this operating system. */ os?: SystemOS | null; - /** Install using sudo. */ - sudo?: boolean; /** The version to install. */ version?: string | null; }; diff --git a/plugins/Cargo.lock b/plugins/Cargo.lock index 2536904ee..79a1a28cc 100644 --- a/plugins/Cargo.lock +++ b/plugins/Cargo.lock @@ -2,22 +2,13 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli 0.28.1", -] - [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ - "gimli 0.31.1", + "gimli", ] [[package]] @@ -47,6 +38,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "ambient-authority" version = "0.0.2" @@ -64,9 +61,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "any_key" @@ -86,9 +83,9 @@ checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" dependencies = [ "derive_arbitrary", ] @@ -159,7 +156,7 @@ version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ - "addr2line 0.24.2", + "addr2line", "cfg-if", "libc", "miniz_oxide", @@ -202,9 +199,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "block-buffer" @@ -217,26 +214,29 @@ dependencies = [ [[package]] name = "bstr" -version = "1.10.0" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "serde", ] [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +dependencies = [ + "allocator-api2", +] [[package]] name = "bytemuck" -version = "1.18.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" [[package]] name = "byteorder" @@ -246,9 +246,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" [[package]] name = "bzip2" @@ -262,9 +262,9 @@ dependencies = [ [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.12+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "72ebc2f1a417f01e1da30ef264ee86ae31d2dcd2d603ea283d3c244a883ca2a9" dependencies = [ "cc", "libc", @@ -292,7 +292,7 @@ dependencies = [ "sha2", "ssri", "tempfile", - "thiserror 1.0.64", + "thiserror 1.0.69", "tokio", "tokio-stream", "walkdir", @@ -300,21 +300,21 @@ dependencies = [ [[package]] name = "cap-fs-ext" -version = "3.3.0" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712695628f77a28acd7c9135b9f05f9c1563f8eb91b317f63876bac550032403" +checksum = "7f78efdd7378980d79c0f36b519e51191742d2c9f91ffa5e228fba9f3806d2e1" dependencies = [ "cap-primitives", "cap-std", "io-lifetimes", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "cap-primitives" -version = "3.3.0" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff5bcbaf57897c8f14098cc9ad48a78052930a9948119eea01b80ca224070fa6" +checksum = "8fc15faeed2223d8b8e8cc1857f5861935a06d06713c4ac106b722ae9ce3c369" dependencies = [ "ambient-authority", "fs-set-times", @@ -323,15 +323,15 @@ dependencies = [ "ipnet", "maybe-owned", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", "winx", ] [[package]] name = "cap-rand" -version = "3.3.0" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c780812948b31f362c3bab82d23b902529c26705d0e094888bc7fdb9656908" +checksum = "dea13372b49df066d1ae654e5c6e41799c1efd9f6b36794b921e877ea4037977" dependencies = [ "ambient-authority", "rand", @@ -339,9 +339,9 @@ dependencies = [ [[package]] name = "cap-std" -version = "3.3.0" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6cf1a22e6eab501e025a9953532b1e95efb8a18d6364bf8a4a7547b30c49186" +checksum = "c3dbd3e8e8d093d6ccb4b512264869e1281cdb032f7940bd50b2894f96f25609" dependencies = [ "cap-primitives", "io-extras", @@ -351,9 +351,9 @@ dependencies = [ [[package]] name = "cap-time-ext" -version = "3.3.0" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1547a95cd071db92382c649260bcc6721879ef5d1f0f442af33bff75003dd7" +checksum = "bd736b20fc033f564a1995fb82fc349146de43aabba19c7368b4cb17d8f9ea53" dependencies = [ "ambient-authority", "cap-primitives", @@ -374,11 +374,11 @@ dependencies = [ [[package]] name = "cbindgen" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fce8dd7fcfcbf3a0a87d8f515194b49d6135acab73e18bd380d1d93bb1a15eb" +checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" dependencies = [ - "heck", + "heck 0.4.1", "indexmap", "log", "proc-macro2", @@ -392,15 +392,21 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.28" +version = "1.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" +checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" dependencies = [ "jobserver", "libc", "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cfg-if" version = "1.0.0" @@ -425,6 +431,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "compact_str" version = "0.8.1" @@ -442,30 +458,30 @@ dependencies = [ [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] name = "const_format" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" dependencies = [ "proc-macro2", "quote", @@ -491,6 +507,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -508,27 +534,27 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "cranelift-bforest" -version = "0.110.3" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a41b85213deedf877555a7878ca9fb680ccba8183611c4bb8030ed281b2ad83" +checksum = "e15d04a0ce86cb36ead88ad68cf693ffd6cda47052b9e0ac114bc47fd9cd23c4" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-bitset" -version = "0.110.3" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "690d8ae6c73748e5ce3d8fe59034dceadb8823e6c8994ba324141c5eae909b0e" +checksum = "7c6e3969a7ce267259ce244b7867c5d3bc9e65b0a87e81039588dfdeaede9f34" dependencies = [ "serde", "serde_derive", @@ -536,9 +562,9 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.110.3" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce027a7b16f8b86f60ff6819615273635186d607a0c225ee6ac340d7d18f978" +checksum = "2c22032c4cb42558371cf516bb47f26cdad1819d3475c133e93c49f50ebf304e" dependencies = [ "bumpalo", "cranelift-bforest", @@ -548,44 +574,45 @@ dependencies = [ "cranelift-control", "cranelift-entity", "cranelift-isle", - "gimli 0.28.1", + "gimli", "hashbrown 0.14.5", "log", "regalloc2", - "rustc-hash 1.1.0", + "rustc-hash", + "serde", "smallvec", "target-lexicon", ] [[package]] name = "cranelift-codegen-meta" -version = "0.110.3" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a2d2ab65e6cbf91f81781d8da65ec2005510f18300eff21a99526ed6785863" +checksum = "c904bc71c61b27fc57827f4a1379f29de64fe95653b620a3db77d59655eee0b8" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.110.3" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efcff860573cf3db9ae98fbd949240d78b319df686cc306872e7fab60e9c84d7" +checksum = "40180f5497572f644ce88c255480981ae2ec1d7bb4d8e0c0136a13b87a2f2ceb" [[package]] name = "cranelift-control" -version = "0.110.3" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d70e5b75c2d5541ef80a99966ccd97aaa54d2a6af19ea31759a28538e1685a" +checksum = "26d132c6d0bd8a489563472afc171759da0707804a65ece7ceb15a8c6d7dd5ef" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.110.3" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d21d3089714278920030321829090d9482c91e5ff2339f2f697f8425bffdcba3" +checksum = "4b2d0d9618275474fbf679dd018ac6e009acbd6ae6850f6a67be33fb3b00b323" dependencies = [ "cranelift-bitset", "serde", @@ -594,9 +621,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.110.3" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7308482930f2a2fad4fe25a06054f6f9a4ee1ab97264308c661b037cb60001a3" +checksum = "4fac41e16729107393174b0c9e3730fb072866100e1e64e80a1a963b2e484d57" dependencies = [ "cranelift-codegen", "log", @@ -606,37 +633,21 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.110.3" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4c59e259dab0e6958dabcc536b30845574f027ba6e5000498cdaf7e7ed2d30" +checksum = "1ca20d576e5070044d0a72a9effc2deacf4d6aa650403189d8ea50126483944d" [[package]] name = "cranelift-native" -version = "0.110.3" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77ac3dfb61ef3159998105116acdfeaec75e4296c43ee2dcc4ea39838c0080e" +checksum = "b8dee82f3f1f2c4cba9177f1cc5e350fe98764379bcd29340caa7b01f85076c7" dependencies = [ "cranelift-codegen", "libc", "target-lexicon", ] -[[package]] -name = "cranelift-wasm" -version = "0.110.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d883f1b8d3d1dab4797407117bc8a1824f4a1fe86654aee2ee3205613f77d3e" -dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "cranelift-frontend", - "itertools 0.12.1", - "log", - "smallvec", - "wasmparser 0.212.0", - "wasmtime-types", -] - [[package]] name = "crc32fast" version = "1.4.2" @@ -648,9 +659,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -667,9 +678,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" @@ -771,9 +782,9 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.3.2" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", @@ -928,15 +939,15 @@ checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -949,19 +960,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "extism" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c352d53d63c58d66868b65246ec491dab9c78b7425d2d221f0219d97765a3548" +checksum = "b06c5ae419bb79082d6ad22fc907717b4a0655c4ff532c09346b4df28abdacba" dependencies = [ "anyhow", "cbindgen", @@ -985,9 +996,9 @@ dependencies = [ [[package]] name = "extism-convert" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b2042dab1fdb408d7504446cfb10079815da8b45e192a8954bf568c8a43e65d" +checksum = "6630b9ecf2628b1ef32e3f9bb888ffc17e0eed5927ba11e72e8bc06e59fa9cf6" dependencies = [ "anyhow", "base64 0.22.1", @@ -1001,9 +1012,9 @@ dependencies = [ [[package]] name = "extism-convert-macros" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb2f0038f2c3b14daa95b132f4fc1bad786a92946c7c9c06e120985a1fc4028" +checksum = "e030d6ea834ef4004764b4809832e7197469442e92feb7ca4307e827db7c98b8" dependencies = [ "manyhow", "proc-macro-crate", @@ -1014,9 +1025,9 @@ dependencies = [ [[package]] name = "extism-manifest" -version = "1.7.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675c0d7e15bb5e6e2a520ea26c4309c047c30b16de852f26373de1906677a58d" +checksum = "65df2772243a6e44caf547142196381a4947cf390cb1252c0205d9337aa0f237" dependencies = [ "base64 0.22.1", "serde", @@ -1057,9 +1068,9 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fd-lock" @@ -1111,9 +1122,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "form_urlencoded" @@ -1126,13 +1137,13 @@ dependencies = [ [[package]] name = "fs-set-times" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "033b337d725b97690d86893f9de22b67b80dcc4e9ad815f348254c38119db8fb" +checksum = "5e2e6123af26f0f2c51cc66869137080199406754903cc926a7690401ce09cb4" dependencies = [ "io-lifetimes", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1307,8 +1318,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -1325,26 +1338,20 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" dependencies = [ "fallible-iterator", "indexmap", "stable_deref_trait", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "globset" @@ -1355,7 +1362,7 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -1378,9 +1385,9 @@ checksum = "be136d9dacc2a13cc70bb6c8f902b414fb2641f8db1314637c6b7933411a8f82" [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -1395,15 +1402,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash", -] - [[package]] name = "hashbrown" version = "0.14.5" @@ -1411,16 +1409,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", - "serde", ] [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "foldhash", + "serde", ] [[package]] @@ -1430,10 +1428,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] -name = "hermit-abi" -version = "0.3.9" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -1503,7 +1501,7 @@ dependencies = [ "http-cache", "http-cache-semantics", "reqwest", - "reqwest-middleware 0.4.0", + "reqwest-middleware", "serde", "url", ] @@ -1532,9 +1530,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -1550,9 +1548,9 @@ checksum = "140a09c9305e6d5e557e2ed7cbc68e05765a7d4213975b87cb04920689cc6219" [[package]] name = "hyper" -version = "1.4.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -1570,16 +1568,16 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.3" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http", "hyper", "hyper-util", "rustls", - "rustls-native-certs 0.8.0", + "rustls-native-certs 0.8.1", "rustls-pki-types", "tokio", "tokio-rustls", @@ -1790,7 +1788,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "same-file", "walkdir", "winapi-util", @@ -1803,37 +1801,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "serde", ] [[package]] name = "insta" -version = "1.42.0" +version = "1.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513e4067e16e69ed1db5ab56048ed65db32d10ba5fc1217f5393f8f17d8b5a5" +checksum = "71c1b125e30d93896b365e156c33dadfffab45ee8400afcbba4752f59de08a86" dependencies = [ "console", "linked-hash-map", "once_cell", + "pin-project", "similar", ] [[package]] name = "io-extras" -version = "0.18.2" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9f046b9af244f13b3bd939f55d16830ac3a201e8a9ba9661bfcb03e2be72b9b" +checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" dependencies = [ "io-lifetimes", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "io-lifetimes" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" [[package]] name = "iocraft" @@ -1849,7 +1848,7 @@ dependencies = [ "iocraft-macros", "taffy", "textwrap", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] @@ -1866,9 +1865,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is_ci" @@ -1905,9 +1904,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "ittapi" @@ -1929,6 +1928,26 @@ dependencies = [ "cc", ] +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.32" @@ -1940,10 +1959,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.71" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cb94a0ffd3f3ee755c20f7d8752f45cac88605a4dcf808abcff72873296ec7b" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -1968,6 +1988,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" version = "0.2.169" @@ -1976,9 +2002,9 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -2009,9 +2035,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" @@ -2037,9 +2063,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "lzma-sys" @@ -2131,8 +2157,8 @@ checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" dependencies = [ "miette-derive 5.10.0", "once_cell", - "thiserror 1.0.64", - "unicode-width", + "thiserror 1.0.69", + "unicode-width 0.1.14", ] [[package]] @@ -2143,8 +2169,8 @@ checksum = "1a955165f87b37fd1862df2a59547ac542c77ef6d17c666f619d1ad22dd89484" dependencies = [ "cfg-if", "miette-derive 7.5.0", - "thiserror 1.0.64", - "unicode-width", + "thiserror 1.0.69", + "unicode-width 0.1.14", ] [[package]] @@ -2189,20 +2215,19 @@ checksum = "6367d84fb54d4242af283086402907277715b8fe46976963af5ebf173f8efba3" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi", "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", @@ -2253,12 +2278,31 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2270,27 +2314,27 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "crc32fast", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "indexmap", "memchr", ] [[package]] name = "once_cell" -version = "1.20.2" +version = "1.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "option-ext" @@ -2354,11 +2398,31 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -2383,9 +2447,9 @@ dependencies = [ [[package]] name = "postcard" -version = "1.0.10" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7f0a8d620d71c457dd1d47df76bb18960378da56af4527aaa10f515eee732e" +checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" dependencies = [ "cobs", "embedded-io 0.4.0", @@ -2424,15 +2488,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" [[package]] name = "predicates-tree" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" dependencies = [ "predicates-core", "termtree", @@ -2470,9 +2534,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -2491,9 +2555,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" +checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" dependencies = [ "bytes", "prost-derive", @@ -2501,9 +2565,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" +checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", "itertools 0.13.0", @@ -2514,7 +2578,7 @@ dependencies = [ [[package]] name = "proto_core" -version = "0.45.3" +version = "0.45.4" dependencies = [ "convert_case", "dotenvy", @@ -2527,7 +2591,8 @@ dependencies = [ "proto_shim", "regex", "reqwest", - "rustc-hash 2.1.1", + "rustc-hash", + "scc", "schematic", "semver", "serde", @@ -2550,20 +2615,20 @@ dependencies = [ [[package]] name = "proto_pdk" -version = "0.26.1" +version = "0.26.2" dependencies = [ "extism-pdk", "proto_pdk_api", - "rustc-hash 2.1.1", + "rustc-hash", "serde", "warpgate_pdk", ] [[package]] name = "proto_pdk_api" -version = "0.25.2" +version = "0.25.3" dependencies = [ - "rustc-hash 2.1.1", + "rustc-hash", "schematic", "semver", "serde", @@ -2576,7 +2641,7 @@ dependencies = [ [[package]] name = "proto_pdk_test_utils" -version = "0.32.1" +version = "0.32.2" dependencies = [ "proto_core", "proto_pdk_api", @@ -2608,54 +2673,70 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" +checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" dependencies = [ "cc", ] +[[package]] +name = "pulley-interpreter" +version = "29.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d95f8575df49a2708398182f49a888cf9dc30210fb1fd2df87c889edcee75d" +dependencies = [ + "cranelift-bitset", + "log", + "sptr", + "wasmtime-math", +] + [[package]] name = "quinn" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ "bytes", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", "socket2", - "thiserror 1.0.64", + "thiserror 2.0.11", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" dependencies = [ "bytes", + "getrandom 0.2.15", "rand", "ring", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", + "rustls-pki-types", "slab", - "thiserror 1.0.64", + "thiserror 2.0.11", "tinyvec", "tracing", + "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" dependencies = [ + "cfg_aliases", "libc", "once_cell", "socket2", @@ -2665,9 +2746,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -2724,9 +2805,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags", ] @@ -2739,7 +2820,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 1.0.64", + "thiserror 1.0.69", ] [[package]] @@ -2755,25 +2836,27 @@ dependencies = [ [[package]] name = "reflink-copy" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a076c8c302cbd01e62bd6c2f74fc4913c3131aa6fa72dae78047f5535e4fc88" +checksum = "fbd3533fd4222b8337470456ea84d80436b4c91c53db51c372461d5f7e6eb0b4" dependencies = [ "cfg-if", + "libc", "rustix", "windows", ] [[package]] name = "regalloc2" -version = "0.9.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" +checksum = "145c1c267e14f20fb0f88aa76a1c5ffec42d592c1d28b3cd9148ae35916158d3" dependencies = [ - "hashbrown 0.13.2", + "allocator-api2", + "bumpalo", + "hashbrown 0.15.2", "log", - "rustc-hash 1.1.0", - "slice-group-by", + "rustc-hash", "smallvec", ] @@ -2785,7 +2868,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -2800,9 +2883,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -2848,7 +2931,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", - "rustls-native-certs 0.8.0", + "rustls-native-certs 0.8.1", "rustls-pemfile", "rustls-pki-types", "serde", @@ -2868,21 +2951,6 @@ dependencies = [ "windows-registry", ] -[[package]] -name = "reqwest-middleware" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562ceb5a604d3f7c885a792d42c199fd8af239d0a51b2fa6a78aafa092452b04" -dependencies = [ - "anyhow", - "async-trait", - "http", - "reqwest", - "serde", - "thiserror 1.0.64", - "tower-service", -] - [[package]] name = "reqwest-middleware" version = "0.4.0" @@ -2894,17 +2962,17 @@ dependencies = [ "http", "reqwest", "serde", - "thiserror 1.0.64", + "thiserror 1.0.69", "tower-service", ] [[package]] name = "reqwest-netrc" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3000378f31e2aff16946083125d5e317f89bd12ca8160ef4cff21c445c2376f" +checksum = "ae09db8d40fdf48baa5400ef133a7481516a9818b4d55573241c936312af359b" dependencies = [ - "reqwest-middleware 0.3.3", + "reqwest-middleware", "rust-netrc", ] @@ -2952,7 +3020,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e98097f62769f92dbf95fb51f71c0a68ec18a4ee2e70e0d3e4f47ac005d63e9" dependencies = [ "shellexpand 3.1.0", - "thiserror 1.0.64", + "thiserror 1.0.69", ] [[package]] @@ -2961,12 +3029,6 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -2975,9 +3037,9 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", @@ -2985,14 +3047,14 @@ dependencies = [ "libc", "linux-raw-sys", "once_cell", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.20" +version = "0.23.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" dependencies = [ "log", "once_cell", @@ -3013,20 +3075,19 @@ dependencies = [ "rustls-pemfile", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 2.11.1", ] [[package]] name = "rustls-native-certs" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", - "rustls-pemfile", "rustls-pki-types", "schannel", - "security-framework", + "security-framework 3.2.0", ] [[package]] @@ -3040,9 +3101,39 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +dependencies = [ + "web-time", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "afbb878bdfdf63a336a5e63561b1835e7a8c91524f51621db870169eac84b490" +dependencies = [ + "core-foundation 0.9.4", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs 0.7.3", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework 2.11.1", + "security-framework-sys", + "webpki-roots", + "winapi", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" @@ -3057,15 +3148,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "same-file" @@ -3087,9 +3178,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -3149,9 +3240,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sdd" -version = "3.0.3" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a7b59a5d9b0099720b417b6325d91a52cbf5b3dcb5041d864be53eefa58abc" +checksum = "b07779b9b918cc05650cb30f404d4d7835d26df37c235eded8a6832e2fb82cca" [[package]] name = "security-framework" @@ -3160,7 +3251,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "num-bigint", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -3168,9 +3273,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -3376,9 +3481,9 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "similar" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de1d4f81173b03af4c0cbed3c898f6bff5b870e4a7f5d6f4057d62a7a4b686e" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "slab" @@ -3389,12 +3494,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "slice-group-by" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" - [[package]] name = "slotmap" version = "1.0.7" @@ -3421,9 +3520,9 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -3454,7 +3553,7 @@ dependencies = [ "serde", "sha-1", "sha2", - "thiserror 1.0.64", + "thiserror 1.0.69", "xxhash-rust", ] @@ -3474,7 +3573,7 @@ dependencies = [ "bzip2", "flate2", "miette 7.5.0", - "rustc-hash 2.1.1", + "rustc-hash", "starbase_styles", "starbase_utils", "thiserror 2.0.11", @@ -3486,9 +3585,9 @@ dependencies = [ [[package]] name = "starbase_console" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb2fd7146c0e55b53ab44aa4a178d4f27f2fd5ce33718054207cf8aaeacc020" +checksum = "f667f7b971e933e5298c15233bbc0caed7b49492fc73cd148dc6802576bfcc0d" dependencies = [ "crossterm", "iocraft", @@ -3516,9 +3615,9 @@ dependencies = [ [[package]] name = "starbase_styles" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65b486cfc419b43fee8b42a87f8d654c1c1d439df34cdf1bfa152cc6bdf8456d" +checksum = "f947bf66e858922ddd27eef15656f4ac4fa11401cfdccbc4140674ea68ee6465" dependencies = [ "dirs 6.0.0", "owo-colors", @@ -3527,9 +3626,9 @@ dependencies = [ [[package]] name = "starbase_utils" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1823e3b2cc103e71a5c04501cea963dada8488620760c92028bc6770eee33bfd" +checksum = "e22b27b7f476b5b6beffe16e694ff4ae8d38f808612b7eef9e279c3cf47eb856" dependencies = [ "async-trait", "dirs 6.0.0", @@ -3578,9 +3677,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.95" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f71c0377baf4ef1cc3e3402ded576dccc315800fbc62dfc7fe04b009773b4a" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -3589,9 +3688,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] @@ -3614,7 +3713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3630,9 +3729,9 @@ dependencies = [ [[package]] name = "system-interface" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b858526d22750088a9b3cf2e3c2aacebd5377f13adeec02860c30d09113010a6" +checksum = "cc4592f674ce18521c2a81483873a49596655b179f71c5e05d10c1fe66c78745" dependencies = [ "bitflags", "cap-fs-ext", @@ -3640,13 +3739,13 @@ dependencies = [ "fd-lock", "io-lifetimes", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", "winx", ] [[package]] name = "system_env" -version = "0.7.0" +version = "0.7.1" dependencies = [ "regex", "schematic", @@ -3671,18 +3770,19 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.12.16" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" [[package]] name = "tempfile" -version = "3.13.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -3699,9 +3799,9 @@ dependencies = [ [[package]] name = "termtree" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "textwrap" @@ -3711,16 +3811,16 @@ checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ "smawk", "unicode-linebreak", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.64", + "thiserror-impl 1.0.69", ] [[package]] @@ -3734,9 +3834,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", @@ -3807,9 +3907,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -3852,12 +3952,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] @@ -3874,9 +3973,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -3887,9 +3986,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -3908,9 +4007,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "serde", @@ -3992,9 +4091,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -4008,6 +4107,17 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "trait-variant" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "try-lock" version = "0.2.5" @@ -4022,9 +4132,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-linebreak" @@ -4044,6 +4154,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -4058,21 +4174,35 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.12.1" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +checksum = "b2916852be768844b6e9cbe107358b5bc40a696bd6dc8e036c9f80c731242c9c" dependencies = [ "base64 0.22.1", "flate2", "log", - "once_cell", + "percent-encoding", "rustls", - "rustls-native-certs 0.7.3", + "rustls-pemfile", "rustls-pki-types", - "url", + "rustls-platform-verifier", + "ureq-proto", + "utf-8", "webpki-roots", ] +[[package]] +name = "ureq-proto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c51fe73e1d8c4e06bb2698286f7e7453c6fc90528d6d2e7fc36bb4e87fe09b1" +dependencies = [ + "base64 0.22.1", + "http", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.4" @@ -4085,6 +4215,12 @@ dependencies = [ "serde", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "utf16_iter" version = "1.0.5" @@ -4108,9 +4244,9 @@ dependencies = [ [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "version_check" @@ -4139,9 +4275,9 @@ dependencies = [ [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] @@ -4167,7 +4303,7 @@ dependencies = [ [[package]] name = "warpgate" -version = "0.21.0" +version = "0.21.1" dependencies = [ "async-trait", "compact_str", @@ -4177,9 +4313,8 @@ dependencies = [ "once_cell", "regex", "reqwest", - "reqwest-middleware 0.4.0", + "reqwest-middleware", "reqwest-netrc", - "rust-netrc", "scc", "schematic", "serde", @@ -4198,10 +4333,10 @@ dependencies = [ [[package]] name = "warpgate_api" -version = "0.11.0" +version = "0.11.1" dependencies = [ "anyhow", - "rustc-hash 2.1.1", + "rustc-hash", "schematic", "serde", "serde_json", @@ -4211,7 +4346,7 @@ dependencies = [ [[package]] name = "warpgate_pdk" -version = "0.9.0" +version = "0.9.1" dependencies = [ "extism-pdk", "serde", @@ -4235,9 +4370,9 @@ dependencies = [ [[package]] name = "wasi-common" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddca85a537113179aae69f1faf916401113acb66e4b52b86483344bc5ae43fa" +checksum = "fe3101bd34deeb64225431f8b1b1793c87e7cad94383464878b3f90da6995977" dependencies = [ "anyhow", "bitflags", @@ -4249,36 +4384,35 @@ dependencies = [ "io-extras", "io-lifetimes", "log", - "once_cell", "rustix", "system-interface", - "thiserror 1.0.64", + "thiserror 1.0.69", "tracing", "wasmtime", "wiggle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "wasm-bindgen" -version = "0.2.94" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef073ced962d62984fb38a36e5fdc1a2b23c9e0e1fa0689bb97afa4202ef6887" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.94" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4bfab14ef75323f4eb75fa52ee0a3fb59611977fd3240da19b2cf36ff85030e" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -4287,21 +4421,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.44" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65471f79c1022ffa5291d33520cbbb53b7687b01c2f8e83b57d102eed7ed479d" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.94" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7bec9830f60924d9ceb3ef99d55c155be8afa76954edffbb5936ff4509474e7" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4309,9 +4444,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.94" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c74f6e152a76a2ad448e223b0fc0b6b5747649c3d769cc6bf45737bf97d0ed6" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -4322,38 +4457,41 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.94" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a42f6c679374623f295a8623adfe63d9284091245c3504bde47c17a3ce2777d9" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-encoder" -version = "0.212.0" +version = "0.221.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501940df4418b8929eb6d52f1aade1fdd15a5b86c92453cb696e3c906bd3fc33" +checksum = "dc8444fe4920de80a4fe5ab564fff2ae58b6b73166b89751f8c6c93509da32e5" dependencies = [ "leb128", + "wasmparser 0.221.3", ] [[package]] name = "wasm-encoder" -version = "0.219.1" +version = "0.225.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29cbbd772edcb8e7d524a82ee8cef8dd046fc14033796a754c3ad246d019fa54" +checksum = "6f7eac0445cac73bcf09e6a97f83248d64356dccf9f2b100199769b6b42464e5" dependencies = [ - "leb128", - "wasmparser 0.219.1", + "leb128fmt", + "wasmparser 0.225.0", ] [[package]] name = "wasmparser" -version = "0.212.0" +version = "0.221.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d28bc49ba1e5c5b61ffa7a2eace10820443c4b7d1c0b144109261d14570fdf8" +checksum = "d06bfa36ab3ac2be0dee563380147a5b81ba10dd8885d7fbbc9eb574be67d185" dependencies = [ - "ahash", "bitflags", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "indexmap", "semver", "serde", @@ -4361,32 +4499,33 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.219.1" +version = "0.225.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c771866898879073c53b565a6c7b49953795159836714ac56a5befb581227c5" +checksum = "36e5456165f81e64cb9908a0fe9b9d852c2c74582aa3fe2be3c2da57f937d3ae" dependencies = [ "bitflags", "indexmap", + "semver", ] [[package]] name = "wasmprinter" -version = "0.212.0" +version = "0.221.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfac65326cc561112af88c3028f6dfdb140acff67ede33a8e86be2dc6b8956f7" +checksum = "7343c42a97f2926c7819ff81b64012092ae954c5d83ddd30c9fcdefd97d0b283" dependencies = [ "anyhow", "termcolor", - "wasmparser 0.212.0", + "wasmparser 0.221.3", ] [[package]] name = "wasmtime" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe501caefeb9f7b15360bdd7e47ad96e20223846f1c7db485ae5820ba5acc3d2" +checksum = "11976a250672556d1c4c04c6d5d7656ac9192ac9edc42a4587d6c21460010e69" dependencies = [ - "addr2line 0.21.0", + "addr2line", "anyhow", "async-trait", "bitflags", @@ -4395,12 +4534,11 @@ dependencies = [ "cfg-if", "encoding_rs", "fxprof-processed-profile", - "gimli 0.28.1", + "gimli", "hashbrown 0.14.5", "indexmap", "ittapi", "libc", - "libm", "log", "mach2", "memfd", @@ -4409,6 +4547,7 @@ dependencies = [ "paste", "postcard", "psm", + "pulley-interpreter", "rayon", "rustix", "semver", @@ -4418,8 +4557,9 @@ dependencies = [ "smallvec", "sptr", "target-lexicon", - "wasm-encoder 0.212.0", - "wasmparser 0.212.0", + "trait-variant", + "wasm-encoder 0.221.3", + "wasmparser 0.221.3", "wasmtime-asm-macros", "wasmtime-cache", "wasmtime-component-macro", @@ -4429,27 +4569,28 @@ dependencies = [ "wasmtime-fiber", "wasmtime-jit-debug", "wasmtime-jit-icache-coherence", + "wasmtime-math", "wasmtime-slab", "wasmtime-versioned-export-macros", "wasmtime-winch", "wat", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "wasmtime-asm-macros" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c904a057d74bfa0ad9369a3fd99231d81ba0345f059d03c9148c3bb2abbf310f" +checksum = "1f178b0d125201fbe9f75beaf849bd3e511891f9e45ba216a5b620802ccf64f2" dependencies = [ "cfg-if", ] [[package]] name = "wasmtime-cache" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dff4d467d6b5bd0d137f5426f45178222e40b59e49ab3a7361420262b9f00df" +checksum = "8b1161c8f62880deea07358bc40cceddc019f1c81d46007bc390710b2fe24ffc" dependencies = [ "anyhow", "base64 0.21.7", @@ -4461,15 +4602,15 @@ dependencies = [ "serde_derive", "sha2", "toml", - "windows-sys 0.52.0", + "windows-sys 0.59.0", "zstd", ] [[package]] name = "wasmtime-component-macro" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a96185dab1c14ffb986ff2b3a2185d15acf2b801ca7895aa35ee80328e2ce38" +checksum = "d74de6592ed945d0a602f71243982a304d5d02f1e501b638addf57f42d57dfaf" dependencies = [ "anyhow", "proc-macro2", @@ -4482,15 +4623,15 @@ dependencies = [ [[package]] name = "wasmtime-component-util" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71a40200d42a8985edadb4007a0ed320756cbe28065b83e0027e39524c1b1b22" +checksum = "707dc7b3c112ab5a366b30cfe2fb5b2f8e6a0f682f16df96a5ec582bfe6f056e" [[package]] name = "wasmtime-cranelift" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b099ef9b7808fa8d18cad32243e78e9c07a4a8aacfa913d88dc08704b1643c49" +checksum = "366be722674d4bf153290fbcbc4d7d16895cc82fb3e869f8d550ff768f9e9e87" dependencies = [ "anyhow", "cfg-if", @@ -4499,28 +4640,29 @@ dependencies = [ "cranelift-entity", "cranelift-frontend", "cranelift-native", - "cranelift-wasm", - "gimli 0.28.1", + "gimli", + "itertools 0.12.1", "log", "object", + "smallvec", "target-lexicon", - "thiserror 1.0.64", - "wasmparser 0.212.0", + "thiserror 1.0.69", + "wasmparser 0.221.3", "wasmtime-environ", "wasmtime-versioned-export-macros", ] [[package]] name = "wasmtime-environ" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2f1765f6ca1a166927bee13ad4aed7bf18269f34c0cd7d6d523889a0b52e6ee" +checksum = "cdadc1af7097347aa276a4f008929810f726b5b46946971c660b6d421e9994ad" dependencies = [ "anyhow", "cpp_demangle", "cranelift-bitset", "cranelift-entity", - "gimli 0.28.1", + "gimli", "indexmap", "log", "object", @@ -4529,19 +4671,19 @@ dependencies = [ "semver", "serde", "serde_derive", + "smallvec", "target-lexicon", - "wasm-encoder 0.212.0", - "wasmparser 0.212.0", + "wasm-encoder 0.221.3", + "wasmparser 0.221.3", "wasmprinter", "wasmtime-component-util", - "wasmtime-types", ] [[package]] name = "wasmtime-fiber" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "047be22a9ebe0343e583edf52b89b60a87e37bec1bc71dc127d3c7fb287c4471" +checksum = "ccba90d4119f081bca91190485650730a617be1fff5228f8c4757ce133d21117" dependencies = [ "anyhow", "cc", @@ -4549,58 +4691,52 @@ dependencies = [ "rustix", "wasmtime-asm-macros", "wasmtime-versioned-export-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "wasmtime-jit-debug" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2383b29fd973222293b5ff562f81a67c7e558b669685ca13f8cb80d04ea24b2d" +checksum = "3e7b61488a5ee00c35c8c22de707c36c0aecacf419a3be803a6a2ba5e860f56a" dependencies = [ "object", - "once_cell", "rustix", "wasmtime-versioned-export-macros", ] [[package]] name = "wasmtime-jit-icache-coherence" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e1a826e4ccd0803b2f7463289cad104f40d09d06bc8acf1a614230a47b4d96f" +checksum = "ec5e8552e01692e6c2e5293171704fed8abdec79d1a6995a0870ab190e5747d1" dependencies = [ "anyhow", "cfg-if", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] -name = "wasmtime-slab" -version = "23.0.3" +name = "wasmtime-math" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92a137c17c992eb5eaacfa0f0590353471e49dbb4bdbdf9cf7536d66109e63a" +checksum = "29210ec2aa25e00f4d54605cedaf080f39ec01a872c5bd520ad04c67af1dde17" +dependencies = [ + "libm", +] [[package]] -name = "wasmtime-types" -version = "23.0.3" +name = "wasmtime-slab" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6072ac3267866d99ca726b6a4f157df9b733aac8082e902d527368f07c303ba" -dependencies = [ - "anyhow", - "cranelift-entity", - "serde", - "serde_derive", - "smallvec", - "wasmparser 0.212.0", -] +checksum = "fcb5821a96fa04ac14bc7b158bb3d5cd7729a053db5a74dad396cd513a5e5ccf" [[package]] name = "wasmtime-versioned-export-macros" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bde986038b819bc43a21fef0610aeb47aabfe3ea09ca3533a7b81023b84ec6" +checksum = "86ff86db216dc0240462de40c8290887a613dddf9685508eb39479037ba97b5b" dependencies = [ "proc-macro2", "quote", @@ -4609,16 +4745,16 @@ dependencies = [ [[package]] name = "wasmtime-winch" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beb1abdc26ddf1d7c819ea0fcbfccb0808410549d28bb3154c9bdb7d11fbcc58" +checksum = "fdbabfb8f20502d5e1d81092b9ead3682ae59988487aafcd7567387b7a43cf8f" dependencies = [ "anyhow", "cranelift-codegen", - "gimli 0.28.1", + "gimli", "object", "target-lexicon", - "wasmparser 0.212.0", + "wasmparser 0.221.3", "wasmtime-cranelift", "wasmtime-environ", "winch-codegen", @@ -4626,12 +4762,12 @@ dependencies = [ [[package]] name = "wasmtime-wit-bindgen" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f88e49a9b81746ec0cede5505e40a4012c92cb5054cd7ef4300dc57c36f26b1" +checksum = "8358319c2dd1e4db79e3c1c5d3a5af84956615343f9f89f4e4996a36816e06e6" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "indexmap", "wit-parser", ] @@ -4647,24 +4783,24 @@ dependencies = [ [[package]] name = "wast" -version = "219.0.1" +version = "225.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f79a9d9df79986a68689a6b40bcc8d5d40d807487b235bebc2ac69a242b54a1" +checksum = "c61496027ff707f9fa9e0b22c34ec163eb7adb1070df565e32a9180a76e4300b" dependencies = [ "bumpalo", - "leb128", + "leb128fmt", "memchr", - "unicode-width", - "wasm-encoder 0.219.1", + "unicode-width 0.2.0", + "wasm-encoder 0.225.0", ] [[package]] name = "wat" -version = "1.219.1" +version = "1.225.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bc3cf014fb336883a411cd662f987abf6a1d2a27f2f0008616a0070bbf6bd0d" +checksum = "89e72a33942234fd0794bcdac30e43b448de3187512414267678e511c6755f11" dependencies = [ - "wast 219.0.1", + "wast 225.0.0", ] [[package]] @@ -4678,15 +4814,25 @@ dependencies = [ "nom", "pori", "regex", - "thiserror 1.0.64", + "thiserror 1.0.69", "walkdir", ] [[package]] name = "web-sys" -version = "0.3.71" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44188d185b5bdcae1052d08bcbcf9091a5524038d4572cc4f4f2bb9d5554ddd9" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -4694,23 +4840,23 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.6" +version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" dependencies = [ "rustls-pki-types", ] [[package]] name = "wiggle" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "522bdb5756a42b3e01e9e3097f0d8a2a6b00ffdea50c6aa1a301497813c81cf8" +checksum = "4b9af35bc9629c52c261465320a9a07959164928b4241980ba1cf923b9e6751d" dependencies = [ "anyhow", "async-trait", "bitflags", - "thiserror 1.0.64", + "thiserror 1.0.69", "tracing", "wasmtime", "wiggle-macro", @@ -4719,12 +4865,12 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8979d5490e31efec2beb6c8f58435acceedae52cda9c755456005c0b370ca343" +checksum = "2cf267dd05673912c8138f4b54acabe6bd53407d9d1536f0fadb6520dd16e101" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "proc-macro2", "quote", "shellexpand 2.1.2", @@ -4734,9 +4880,9 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "23.0.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f8feabe94ce6f07d62669d1acf469e0d3249f786562b4263dff3537a4e77ae" +checksum = "08c5c473d4198e6c2d377f3809f713ff0c110cab88a0805ae099a82119ee250c" dependencies = [ "proc-macro2", "quote", @@ -4777,17 +4923,18 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "0.21.3" +version = "29.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a666bf2cdb838e68b9b8370d7ebf8806b87ccc0d89a634bfc9ed8ffca1f19591" +checksum = "2f849ef2c5f46cb0a20af4b4487aaa239846e52e2c03f13fa3c784684552859c" dependencies = [ "anyhow", "cranelift-codegen", - "gimli 0.28.1", + "gimli", "regalloc2", "smallvec", "target-lexicon", - "wasmparser 0.212.0", + "thiserror 1.0.69", + "wasmparser 0.221.3", "wasmtime-cranelift", "wasmtime-environ", ] @@ -5108,21 +5255,21 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.6.20" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "59690dea168f2198d1a3b0cac23b8063efcd11012f10ae4698f284808c8ef603" dependencies = [ "memchr", ] [[package]] name = "winx" -version = "0.36.3" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9643b83820c0cd246ecabe5fa454dd04ba4fa67996369466d0747472d337346" +checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" dependencies = [ "bitflags", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5136,9 +5283,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.212.0" +version = "0.221.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceeb0424aa8679f3fcf2d6e3cfa381f3d6fa6179976a2c05a6249dd2bb426716" +checksum = "896112579ed56b4a538b07a3d16e562d101ff6265c46b515ce0c701eef16b2ac" dependencies = [ "anyhow", "id-arena", @@ -5149,7 +5296,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.212.0", + "wasmparser 0.221.3", ] [[package]] @@ -5160,7 +5307,7 @@ checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" dependencies = [ "anyhow", "log", - "thiserror 1.0.64", + "thiserror 1.0.69", "wast 35.0.2", ] @@ -5178,9 +5325,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "xattr" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +checksum = "e105d177a3871454f754b33bb0ee637ecaaac997446375fd3e5d43a2ed00c909" dependencies = [ "libc", "linux-raw-sys",