From 5b456cdb0d4b379214375c4d9574bc4911165219 Mon Sep 17 00:00:00 2001 From: Miles Johnson Date: Wed, 18 Dec 2024 11:19:41 -0800 Subject: [PATCH] new: Implement new UI output powered by iocraft. (#671) --- CHANGELOG.md | 14 + Cargo.lock | 620 ++++++++++++--------- Cargo.toml | 6 +- crates/cli/Cargo.toml | 3 +- crates/cli/src/app.rs | 4 +- crates/cli/src/commands/activate.rs | 34 +- crates/cli/src/commands/alias.rs | 30 +- crates/cli/src/commands/bin.rs | 23 +- crates/cli/src/commands/clean.rs | 108 ++-- crates/cli/src/commands/completions.rs | 14 +- crates/cli/src/commands/debug/config.rs | 57 +- crates/cli/src/commands/debug/env.rs | 233 ++++++-- crates/cli/src/commands/diagnose.rs | 243 ++++---- crates/cli/src/commands/install.rs | 108 ++-- crates/cli/src/commands/list.rs | 66 ++- crates/cli/src/commands/list_remote.rs | 59 +- crates/cli/src/commands/outdated.rs | 156 +++--- crates/cli/src/commands/pin.rs | 44 +- crates/cli/src/commands/plugin/add.rs | 70 +-- crates/cli/src/commands/plugin/info.rs | 469 ++++++++++------ crates/cli/src/commands/plugin/list.rs | 197 +++---- crates/cli/src/commands/plugin/remove.rs | 45 +- crates/cli/src/commands/plugin/search.rs | 146 +++-- crates/cli/src/commands/regen.rs | 10 +- crates/cli/src/commands/run.rs | 8 +- crates/cli/src/commands/status.rs | 132 +++-- crates/cli/src/commands/unalias.rs | 51 +- crates/cli/src/commands/uninstall.rs | 34 +- crates/cli/src/commands/unpin.rs | 44 +- crates/cli/src/commands/upgrade.rs | 95 ++-- crates/cli/src/components/aliases_map.rs | 35 ++ crates/cli/src/components/env_var.rs | 44 ++ crates/cli/src/components/issues_list.rs | 52 ++ crates/cli/src/components/locator.rs | 79 +++ crates/cli/src/components/mod.rs | 16 + crates/cli/src/components/versions_map.rs | 70 +++ crates/cli/src/helpers.rs | 19 +- crates/cli/src/main.rs | 4 +- crates/cli/src/printer.rs | 205 ------- crates/cli/src/session.rs | 127 ++++- crates/cli/src/systems.rs | 25 +- crates/cli/src/utils/install_graph.rs | 5 +- crates/cli/src/utils/mod.rs | 1 + crates/cli/src/utils/tool_record.rs | 68 +++ crates/cli/tests/alias_test.rs | 3 +- crates/cli/tests/install_uninstall_test.rs | 10 +- crates/cli/tests/outdated_test.rs | 2 + crates/cli/tests/pin_test.rs | 27 +- crates/cli/tests/plugin_add_test.rs | 3 +- crates/cli/tests/plugin_remove_test.rs | 6 +- crates/cli/tests/plugin_search_test.rs | 6 +- crates/cli/tests/run_test.rs | 14 +- crates/cli/tests/status_test.rs | 2 + crates/cli/tests/unalias_test.rs | 3 +- crates/cli/tests/uninstall_test.rs | 2 +- crates/cli/tests/unpin_test.rs | 2 +- crates/core/src/flow/setup.rs | 6 +- crates/core/src/layout/store.rs | 4 +- crates/core/src/proto.rs | 16 +- crates/core/src/proto_config.rs | 8 +- crates/core/tests/proto_config_test.rs | 6 +- 61 files changed, 2429 insertions(+), 1564 deletions(-) create mode 100644 crates/cli/src/components/aliases_map.rs create mode 100644 crates/cli/src/components/env_var.rs create mode 100644 crates/cli/src/components/issues_list.rs create mode 100644 crates/cli/src/components/locator.rs create mode 100644 crates/cli/src/components/mod.rs create mode 100644 crates/cli/src/components/versions_map.rs delete mode 100644 crates/cli/src/printer.rs create mode 100644 crates/cli/src/utils/tool_record.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 35ebd1217..334a3bffd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,16 @@ - [Rust](https://github.com/moonrepo/tools/blob/master/tools/rust/CHANGELOG.md) - [Schema (TOML, JSON, YAML)](https://github.com/moonrepo/tools/blob/master/tools/internal-schema/CHANGELOG.md) +## Unreleased + +#### 💥 Breaking + +- Removed the `--global` option from `proto alias`, `unalias`, `pin`, and `unpin`, use `--to` or `--from` instead. + +## Unreleased + +- Added a `--yes` option to `proto outdated`, that skips confirmation prompts. + ## 0.43.2 #### 🐞 Fixes @@ -102,6 +112,10 @@ - Deprecated `LocateExecutablesOutput.primary` and `LocateExecutablesOutput.secondary` (use `exes` instead). - Updated `ToolMetadataOutput.plugin_version` to a `Version` type instead of `String`. +#### 💥 Breaking + +- Removed `--global` arg from `proto plugin add|remove`. + #### 🚀 Updates - Added support for JSON and YAML based configurations for non-WASM schema based plugins. This is an alternative to TOML, but supports all the same settings. diff --git a/Cargo.lock b/Cargo.lock index 912ae6fa0..cceb14a5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,7 +25,7 @@ checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", - "version_check", + "version_check 0.9.5", "zerocopy", ] @@ -61,9 +61,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -76,36 +76,46 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys 0.59.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "any_key" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d21bb2cdab8087ed9d69411dd99c608dbede1df847c255b4d609f0399a3cb452" +dependencies = [ + "debugit", + "mopa", ] [[package]] @@ -116,13 +126,19 @@ checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + [[package]] name = "assert_cmd" version = "2.0.16" @@ -250,12 +266,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.8", "serde", ] @@ -267,9 +283,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.20.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" [[package]] name = "byteorder" @@ -279,9 +295,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "bzip2" @@ -325,7 +341,7 @@ dependencies = [ "sha2", "ssri", "tempfile", - "thiserror 1.0.69", + "thiserror 1.0.64", "tokio", "tokio-stream", "walkdir", @@ -333,21 +349,21 @@ dependencies = [ [[package]] name = "cap-fs-ext" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16619ada836f12897a72011fe99b03f0025b87a8dbbea4f3c9f89b458a23bf3" +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.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fa6c3f9773feab88d844aa50035a33fb6e7e7426105d2f4bb7aadc42a5f89a" +checksum = "8fc15faeed2223d8b8e8cc1857f5861935a06d06713c4ac106b722ae9ce3c369" dependencies = [ "ambient-authority", "fs-set-times", @@ -356,15 +372,15 @@ dependencies = [ "ipnet", "maybe-owned", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", "winx", ] [[package]] name = "cap-rand" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53774d49369892b70184f8312e50c1b87edccb376691de4485b0ff554b27c36c" +checksum = "dea13372b49df066d1ae654e5c6e41799c1efd9f6b36794b921e877ea4037977" dependencies = [ "ambient-authority", "rand", @@ -372,9 +388,9 @@ dependencies = [ [[package]] name = "cap-std" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f71b70818556b4fe2a10c7c30baac3f5f45e973f49fc2673d7c75c39d0baf5b" +checksum = "c3dbd3e8e8d093d6ccb4b512264869e1281cdb032f7940bd50b2894f96f25609" dependencies = [ "cap-primitives", "io-extras", @@ -384,9 +400,9 @@ dependencies = [ [[package]] name = "cap-time-ext" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69dd48afa2363f746c93f961c211f6f099fb594a3446b8097bc5f79db51b6816" +checksum = "bd736b20fc033f564a1995fb82fc349146de43aabba19c7368b4cb17d8f9ea53" dependencies = [ "ambient-authority", "cap-primitives", @@ -425,9 +441,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.1" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" dependencies = [ "jobserver", "libc", @@ -440,12 +456,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "chrono" version = "0.4.38" @@ -462,9 +472,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" dependencies = [ "clap_builder", "clap_derive", @@ -472,9 +482,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" dependencies = [ "anstream", "anstyle", @@ -515,9 +525,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "clean-path" @@ -533,21 +543,9 @@ checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" [[package]] name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - -[[package]] -name = "comfy-table" -version = "7.1.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f165e7b643266ea80cb858aed492ad9280e3e05ce24d4a99d7d7b889b6a4d9" -dependencies = [ - "crossterm", - "strum", - "strum_macros", - "unicode-width 0.2.0", -] +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "command-group" @@ -639,16 +637,6 @@ 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" @@ -666,9 +654,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -821,8 +809,12 @@ checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags", "crossterm_winapi", + "futures-core", + "mio", "parking_lot", "rustix", + "signal-hook", + "signal-hook-mio", "winapi", ] @@ -889,6 +881,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "debugit" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63c2f7e3034df2b09f750327e23c1adfe33301e6b7388f05bb4fcc0fa46825e3" +dependencies = [ + "version_check 0.1.5", +] + [[package]] name = "deranged" version = "0.3.11" @@ -900,9 +901,9 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", @@ -918,7 +919,7 @@ dependencies = [ "console", "shell-words", "tempfile", - "thiserror 1.0.69", + "thiserror 1.0.64", "zeroize", ] @@ -1061,9 +1062,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.35" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -1184,9 +1185,9 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" -version = "2.2.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fd-lock" @@ -1213,9 +1214,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.35" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide", @@ -1230,6 +1231,18 @@ dependencies = [ "num-traits", ] +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1408,6 +1421,15 @@ dependencies = [ "syn", ] +[[package]] +name = "generational-box" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557cf2cbacd0504c6bf8c29f52f8071e0de1d9783346713dc6121d7fa1e5d0e0" +dependencies = [ + "parking_lot", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1415,7 +1437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", - "version_check", + "version_check 0.9.5", ] [[package]] @@ -1457,7 +1479,7 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.9", + "regex-automata 0.4.8", "regex-syntax 0.8.5", ] @@ -1472,11 +1494,17 @@ dependencies = [ "walkdir", ] +[[package]] +name = "grid" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be136d9dacc2a13cc70bb6c8f902b414fb2641f8db1314637c6b7933411a8f82" + [[package]] name = "h2" -version = "0.4.7" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", "bytes", @@ -1503,9 +1531,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" dependencies = [ "foldhash", ] @@ -1643,9 +1671,9 @@ checksum = "140a09c9305e6d5e557e2ed7cbc68e05765a7d4213975b87cb04920689cc6219" [[package]] name = "hyper" -version = "1.5.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", @@ -1672,7 +1700,7 @@ dependencies = [ "hyper", "hyper-util", "rustls", - "rustls-native-certs 0.8.1", + "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", "tokio-rustls", @@ -1682,9 +1710,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -1883,7 +1911,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.8", "same-file", "walkdir", "winapi-util", @@ -1896,7 +1924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.0", "serde", ] @@ -1928,12 +1956,12 @@ dependencies = [ [[package]] name = "io-extras" -version = "0.18.3" +version = "0.18.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d45fd7584f9b67ac37bc041212d06bfac0700b36456b05890d36a3b626260eb" +checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" dependencies = [ "io-lifetimes", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1942,6 +1970,37 @@ version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c" +[[package]] +name = "iocraft" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28afcf27882ff64c5270876fcbc6ceb702f1d5d543ed314d4934c978dceff9a4" +dependencies = [ + "any_key", + "bitflags", + "crossterm", + "futures", + "generational-box", + "indexmap", + "iocraft-macros", + "taffy", + "textwrap", + "unicode-width 0.1.14", + "uuid", +] + +[[package]] +name = "iocraft-macros" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d0e726dcb066433b8c3f9c3fb49df64428fbd33777099be6426ab691fa38e33" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "uuid", +] + [[package]] name = "ipnet" version = "2.10.1" @@ -1989,9 +2048,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "ittapi" @@ -2024,9 +2083,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "0cb94a0ffd3f3ee755c20f7d8752f45cac88605a4dcf808abcff72873296ec7b" dependencies = [ "wasm-bindgen", ] @@ -2054,15 +2113,15 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.165" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb4d3d38eab6c5239a362fa8bae48c03baf980a6e7079f063942d563ef3533e" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libm" -version = "0.2.11" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libredox" @@ -2089,9 +2148,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" @@ -2214,7 +2273,7 @@ checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" dependencies = [ "miette-derive 5.10.0", "once_cell", - "thiserror 1.0.69", + "thiserror 1.0.64", "unicode-width 0.1.14", ] @@ -2234,7 +2293,7 @@ dependencies = [ "supports-unicode", "terminal_size", "textwrap", - "thiserror 1.0.69", + "thiserror 1.0.64", "unicode-width 0.1.14", ] @@ -2295,10 +2354,26 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", + "log", "wasi", "windows-sys 0.52.0", ] +[[package]] +name = "mopa" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a785740271256c230f57462d3b83e52f998433a7062fc18f96d5999474a9f915" + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + [[package]] name = "nix" version = "0.27.1" @@ -2373,7 +2448,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "crc32fast", - "hashbrown 0.15.2", + "hashbrown 0.15.0", "indexmap", "memchr", ] @@ -2454,9 +2529,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -2481,9 +2556,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.10.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "postcard" @@ -2614,12 +2689,12 @@ dependencies = [ "clap", "clap_complete", "clap_complete_nushell", - "comfy-table", "dialoguer", "dirs 5.0.1", "extism", "indexmap", "indicatif", + "iocraft", "miette 7.4.0", "proto_core", "proto_installer", @@ -2634,6 +2709,7 @@ dependencies = [ "shared_child", "sigpipe", "starbase", + "starbase_console", "starbase_sandbox", "starbase_shell", "starbase_styles", @@ -2757,9 +2833,9 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.24" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200b9ff220857e53e184257720a14553b2f4aa02577d2ed9842d45d4b9654810" +checksum = "aa37f80ca58604976033fae9515a8a2989fc13797d953f7c04fb8fa36a11f205" dependencies = [ "cc", ] @@ -2777,9 +2853,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", "pin-project-lite", @@ -2788,38 +2864,34 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.4", + "thiserror 1.0.64", "tokio", "tracing", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", - "getrandom", "rand", "ring", "rustc-hash", "rustls", - "rustls-pki-types", "slab", - "thiserror 2.0.4", + "thiserror 1.0.64", "tinyvec", "tracing", - "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.7" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ - "cfg_aliases", "libc", "once_cell", "socket2", @@ -2903,7 +2975,7 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", - "thiserror 1.0.69", + "thiserror 1.0.64", ] [[package]] @@ -2938,7 +3010,7 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", + "regex-automata 0.4.8", "regex-syntax 0.8.5", ] @@ -2953,9 +3025,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -3001,7 +3073,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", - "rustls-native-certs 0.8.1", + "rustls-native-certs 0.8.0", "rustls-pemfile", "rustls-pki-types", "serde", @@ -3033,7 +3105,7 @@ dependencies = [ "http", "reqwest", "serde", - "thiserror 1.0.69", + "thiserror 1.0.64", "tower-service", ] @@ -3048,7 +3120,7 @@ dependencies = [ "http", "reqwest", "serde", - "thiserror 1.0.69", + "thiserror 1.0.64", "tower-service", ] @@ -3106,7 +3178,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e98097f62769f92dbf95fb51f71c0a68ec18a4ee2e70e0d3e4f47ac005d63e9" dependencies = [ "shellexpand 3.1.0", - "thiserror 1.0.69", + "thiserror 1.0.64", ] [[package]] @@ -3167,19 +3239,20 @@ dependencies = [ "rustls-pemfile", "rustls-pki-types", "schannel", - "security-framework 2.11.1", + "security-framework", ] [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", + "rustls-pemfile", "rustls-pki-types", "schannel", - "security-framework 3.0.1", + "security-framework", ] [[package]] @@ -3196,9 +3269,6 @@ name = "rustls-pki-types" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" -dependencies = [ - "web-time", -] [[package]] name = "rustls-webpki" @@ -3213,9 +3283,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" @@ -3243,9 +3313,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ "windows-sys 0.59.0", ] @@ -3318,9 +3388,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sdd" -version = "3.0.4" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49c1eeaf4b6a87c7479688c6d52b9f1153cedd3c489300564f932b065c6eab95" +checksum = "60a7b59a5d9b0099720b417b6325d91a52cbf5b3dcb5041d864be53eefa58abc" [[package]] name = "security-framework" @@ -3329,20 +3399,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1415a607e92bec364ea2cf9264646dcce0f91e6d65281bd6f2819cca3bf39c8" -dependencies = [ - "bitflags", - "core-foundation 0.10.0", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -3350,9 +3407,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -3528,6 +3585,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -3573,6 +3651,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check 0.9.5", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -3582,6 +3669,12 @@ dependencies = [ "serde", ] +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + [[package]] name = "socket2" version = "0.5.7" @@ -3597,6 +3690,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "sptr" @@ -3617,7 +3713,7 @@ dependencies = [ "serde", "sha-1", "sha2", - "thiserror 1.0.69", + "thiserror 1.0.64", "xxhash-rust", ] @@ -3663,6 +3759,22 @@ dependencies = [ "zstd", ] +[[package]] +name = "starbase_console" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03357bf3b22d03d5f8009b45ff300e643bf51e757f7317fb2ebdb9f9ef6d7a63" +dependencies = [ + "crossterm", + "flume", + "iocraft", + "miette 7.4.0", + "parking_lot", + "starbase_styles", + "tokio", + "tracing", +] + [[package]] name = "starbase_sandbox" version = "0.8.0" @@ -3680,9 +3792,9 @@ dependencies = [ [[package]] name = "starbase_shell" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d7e46a3a3138225aa6b2efa124bd7694ec55084efb010903ccb2621c1339f4f" +checksum = "37c6866901d54e6c7f427ef2d87ccc002c8afb015863e993622836d93fb89301" dependencies = [ "miette 7.4.0", "regex", @@ -3693,9 +3805,9 @@ dependencies = [ [[package]] name = "starbase_styles" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1315c7a42b187ea3006f6b94bc4fed5c98de6077e6651ab295c6122fa7c8af05" +checksum = "81baa090153ac22b9e2666a01899a1797e88e4ae1fee84a0440862887d299219" dependencies = [ "dirs 5.0.1", "miette 7.4.0", @@ -3740,25 +3852,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.26.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" - -[[package]] -name = "strum_macros" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - [[package]] name = "subtle" version = "2.6.1" @@ -3788,9 +3881,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "2.0.89" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -3799,9 +3892,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" dependencies = [ "futures-core", ] @@ -3837,7 +3930,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags", - "core-foundation 0.9.4", + "core-foundation", "system-configuration-sys", ] @@ -3878,6 +3971,19 @@ dependencies = [ "thiserror 2.0.4", ] +[[package]] +name = "taffy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb893bff0f80ae17d3a57e030622a967b8dbc90e38284d9b4b1442e23873c94" +dependencies = [ + "arrayvec", + "grid", + "num-traits", + "serde", + "slotmap", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -3886,9 +3992,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.14.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -3928,17 +4034,18 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ + "smawk", "unicode-linebreak", "unicode-width 0.1.14", ] [[package]] name = "thiserror" -version = "1.0.69" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ - "thiserror-impl 1.0.69", + "thiserror-impl 1.0.64", ] [[package]] @@ -3952,9 +4059,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", @@ -3984,9 +4091,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -4005,9 +4112,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -4236,9 +4343,9 @@ checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-linebreak" @@ -4284,13 +4391,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.11.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30e6f97efe1fa43535ee241ee76967d3ff6ff3953ebb430d8d55c5393029e7b" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" dependencies = [ "base64 0.22.1", "flate2", - "litemap", "log", "once_cell", "rustls", @@ -4298,8 +4404,6 @@ dependencies = [ "rustls-pki-types", "url", "webpki-roots", - "yoke", - "zerofrom", ] [[package]] @@ -4347,6 +4451,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + [[package]] name = "version_check" version = "0.9.5" @@ -4473,7 +4583,7 @@ dependencies = [ "once_cell", "rustix", "system-interface", - "thiserror 1.0.69", + "thiserror 1.0.64", "tracing", "wasmtime", "wiggle", @@ -4482,9 +4592,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "ef073ced962d62984fb38a36e5fdc1a2b23c9e0e1fa0689bb97afa4202ef6887" dependencies = [ "cfg-if", "once_cell", @@ -4493,9 +4603,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "c4bfab14ef75323f4eb75fa52ee0a3fb59611977fd3240da19b2cf36ff85030e" dependencies = [ "bumpalo", "log", @@ -4508,9 +4618,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "65471f79c1022ffa5291d33520cbbb53b7687b01c2f8e83b57d102eed7ed479d" dependencies = [ "cfg-if", "js-sys", @@ -4520,9 +4630,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "a7bec9830f60924d9ceb3ef99d55c155be8afa76954edffbb5936ff4509474e7" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4530,9 +4640,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "4c74f6e152a76a2ad448e223b0fc0b6b5747649c3d769cc6bf45737bf97d0ed6" dependencies = [ "proc-macro2", "quote", @@ -4543,9 +4653,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "a42f6c679374623f295a8623adfe63d9284091245c3504bde47c17a3ce2777d9" [[package]] name = "wasm-encoder" @@ -4558,19 +4668,19 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.220.0" +version = "0.219.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebf48234b389415b226a4daef6562933d38c7b28a8b8f64c5c4130dad1561ab7" +checksum = "29cbbd772edcb8e7d524a82ee8cef8dd046fc14033796a754c3ad246d019fa54" dependencies = [ "leb128", - "wasmparser 0.220.0", + "wasmparser 0.219.1", ] [[package]] name = "wasm-streams" -version = "0.4.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" dependencies = [ "futures-util", "js-sys", @@ -4595,9 +4705,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.220.0" +version = "0.219.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e246c2772ce3ebc83f89a2d4487ac5794cad6c309b2071818a88c7db7c36d87b" +checksum = "5c771866898879073c53b565a6c7b49953795159836714ac56a5befb581227c5" dependencies = [ "bitflags", "indexmap", @@ -4740,7 +4850,7 @@ dependencies = [ "object", "smallvec", "target-lexicon", - "thiserror 1.0.69", + "thiserror 1.0.64", "wasmparser 0.218.0", "wasmtime-environ", "wasmtime-versioned-export-macros", @@ -4869,24 +4979,24 @@ dependencies = [ [[package]] name = "wast" -version = "220.0.0" +version = "219.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e708c8de08751fd66e70961a32bae9d71901f14a70871e181cb8461a3bb3165" +checksum = "4f79a9d9df79986a68689a6b40bcc8d5d40d807487b235bebc2ac69a242b54a1" dependencies = [ "bumpalo", "leb128", "memchr", - "unicode-width 0.2.0", - "wasm-encoder 0.220.0", + "unicode-width 0.1.14", + "wasm-encoder 0.219.1", ] [[package]] name = "wat" -version = "1.220.0" +version = "1.219.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de4f1d7d59614ba690541360102b995c4eb1b9ed373701d5102cc1a968b1c5a3" +checksum = "8bc3cf014fb336883a411cd662f987abf6a1d2a27f2f0008616a0070bbf6bd0d" dependencies = [ - "wast 220.0.0", + "wast 219.0.1", ] [[package]] @@ -4900,15 +5010,15 @@ dependencies = [ "nom", "pori", "regex", - "thiserror 1.0.69", + "thiserror 1.0.64", "walkdir", ] [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "44188d185b5bdcae1052d08bcbcf9091a5524038d4572cc4f4f2bb9d5554ddd9" dependencies = [ "js-sys", "wasm-bindgen", @@ -4926,9 +5036,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.7" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" dependencies = [ "rustls-pki-types", ] @@ -4942,7 +5052,7 @@ dependencies = [ "anyhow", "async-trait", "bitflags", - "thiserror 1.0.69", + "thiserror 1.0.64", "tracing", "wasmtime", "wiggle-macro", @@ -5364,7 +5474,7 @@ checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" dependencies = [ "anyhow", "log", - "thiserror 1.0.69", + "thiserror 1.0.64", "wast 35.0.2", ] @@ -5414,9 +5524,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -5459,9 +5569,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] diff --git a/Cargo.toml b/Cargo.toml index 37ba36da2..1192fce9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ extism-pdk = "1.3.0" http-cache-reqwest = "0.15.0" human-sort = "0.2.2" indexmap = "2.7.0" +iocraft = "0.5.0" miette = "7.4.0" once_cell = "1.20.2" regex = { version = "1.11.1", default-features = false, features = ["std"] } @@ -49,10 +50,11 @@ starbase_archive = { version = "0.9.0", features = [ "zip", "zip-deflate", ] } +starbase_console = { version = "0.1.2", features = ["ui"] } starbase_events = { version = "0.6.3" } starbase_sandbox = { version = "0.8.0" } -starbase_shell = { version = "0.6.7", features = ["miette"] } -starbase_styles = { version = "0.4.7" } +starbase_shell = { version = "0.6.8", features = ["miette"] } +starbase_styles = { version = "0.4.9" } starbase_utils = { version = "0.9.1", default-features = false, features = [ "json", "miette", diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index ffdbe49d5..bfd3b51cf 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -43,11 +43,11 @@ chrono = "0.4.38" clap = { workspace = true, features = ["derive", "env"] } clap_complete = { workspace = true } clap_complete_nushell = "4.5.4" -comfy-table = "7.1.3" dialoguer = "0.11.0" dirs = { workspace = true } indexmap = { workspace = true } indicatif = { version = "0.17.9", features = ["improved_unicode"] } +iocraft = { workspace = true } miette = { workspace = true } regex = { workspace = true } reqwest = { workspace = true, features = ["rustls-tls-native-roots"] } @@ -55,6 +55,7 @@ rustc-hash = { workspace = true } semver = { workspace = true } serde = { workspace = true } starbase = { workspace = true } +starbase_console = { workspace = true } starbase_shell = { workspace = true } starbase_styles = { workspace = true } starbase_utils = { workspace = true } diff --git a/crates/cli/src/app.rs b/crates/cli/src/app.rs index dcb59d16e..86d70762c 100644 --- a/crates/cli/src/app.rs +++ b/crates/cli/src/app.rs @@ -1,5 +1,5 @@ use crate::commands::{ - debug::DebugConfigArgs, + debug::{DebugConfigArgs, DebugEnvArgs}, plugin::{AddPluginArgs, InfoPluginArgs, ListPluginsArgs, RemovePluginArgs, SearchPluginArgs}, ActivateArgs, AliasArgs, BinArgs, CleanArgs, CompletionsArgs, DiagnoseArgs, InstallArgs, ListArgs, ListRemoteArgs, MigrateArgs, OutdatedArgs, PinArgs, RegenArgs, RunArgs, SetupArgs, @@ -288,7 +288,7 @@ pub enum DebugCommands { Config(DebugConfigArgs), #[command(name = "env", about = "Debug the current proto environment and store.")] - Env, + Env(DebugEnvArgs), } #[derive(Clone, Debug, Subcommand)] diff --git a/crates/cli/src/commands/activate.rs b/crates/cli/src/commands/activate.rs index 02c02e602..49c385f15 100644 --- a/crates/cli/src/commands/activate.rs +++ b/crates/cli/src/commands/activate.rs @@ -2,7 +2,7 @@ use crate::session::ProtoSession; use clap::Args; use indexmap::IndexMap; use miette::IntoDiagnostic; -use proto_core::{detect_version, ConfigMode, Id, UnresolvedVersionSpec}; +use proto_core::{detect_version, Id, UnresolvedVersionSpec}; use serde::Serialize; use starbase::AppResult; use starbase_shell::{Hook, ShellType, Statement}; @@ -78,7 +78,7 @@ pub async fn activate(session: ProtoSession, args: ActivateArgs) -> AppResult { // If not exporting data, just print the activation syntax immediately if !args.export && !args.json { - return print_activation_hook(&shell_type, &args, session.cli.config_mode.as_ref()); + return print_activation_hook(&session, &shell_type, &args); } // Pre-load configuration @@ -120,7 +120,7 @@ pub async fn activate(session: ProtoSession, args: ActivateArgs) -> AppResult { } } - item.id = tool.id; + item.id = tool.id.clone(); Ok(item) }); @@ -167,13 +167,13 @@ pub async fn activate(session: ProtoSession, args: ActivateArgs) -> AppResult { // Output/export the information for the chosen shell if args.export { - print_activation_exports(&shell_type, info)?; + print_activation_exports(&session, &shell_type, info)?; return Ok(None); } if args.json { - println!("{}", json::format(&info, true)?); + session.console.out.write_line(json::format(&info, true)?)?; return Ok(None); } @@ -182,13 +182,13 @@ pub async fn activate(session: ProtoSession, args: ActivateArgs) -> AppResult { } fn print_activation_hook( + session: &ProtoSession, shell_type: &ShellType, args: &ActivateArgs, - config_mode: Option<&ConfigMode>, ) -> AppResult { let mut command = format!("proto activate {}", shell_type); - if let Some(mode) = config_mode { + if let Some(mode) = &session.cli.config_mode { command.push_str(" --config-mode "); command.push_str(&mode.to_string()); } @@ -212,22 +212,26 @@ fn print_activation_hook( } }; - println!( - "{}", - shell_type.build().format_hook(Hook::OnChangeDir { + session + .console + .out + .write_line(shell_type.build().format_hook(Hook::OnChangeDir { command, function: "_proto_activate_hook".into(), - })? - ); + })?)?; if args.on_init { - println!("\n_proto_activate_hook"); + session.console.out.write_line("\n_proto_activate_hook")?; } Ok(None) } -fn print_activation_exports(shell_type: &ShellType, info: ActivateInfo) -> AppResult { +fn print_activation_exports( + session: &ProtoSession, + shell_type: &ShellType, + info: ActivateInfo, +) -> AppResult { let shell = shell_type.build(); let mut output = vec![]; @@ -253,7 +257,7 @@ fn print_activation_exports(shell_type: &ShellType, info: ActivateInfo) -> AppRe })); } - println!("{}", output.join("\n")); + session.console.out.write_line(output.join("\n"))?; Ok(None) } diff --git a/crates/cli/src/commands/alias.rs b/crates/cli/src/commands/alias.rs index 372be505a..ececabdf8 100644 --- a/crates/cli/src/commands/alias.rs +++ b/crates/cli/src/commands/alias.rs @@ -2,9 +2,10 @@ use crate::error::ProtoCliError; use crate::helpers::{map_pin_type, PinOption}; use crate::session::ProtoSession; use clap::Args; +use iocraft::prelude::element; use proto_core::{is_alias_name, Id, ProtoConfig, UnresolvedVersionSpec}; use starbase::AppResult; -use starbase_styles::color; +use starbase_console::ui::*; #[derive(Args, Clone, Debug)] pub struct AliasArgs { @@ -17,10 +18,7 @@ pub struct AliasArgs { #[arg(required = true, help = "Version or alias to associate with")] spec: UnresolvedVersionSpec, - #[arg(long, group = "pin", help = "Add to the global ~/.proto/.prototools")] - global: bool, - - #[arg(long, group = "pin", help = "Location of .prototools to add to")] + #[arg(long, help = "Location of .prototools to add to")] to: Option, } @@ -42,8 +40,7 @@ pub async fn alias(session: ProtoSession, args: AliasArgs) -> AppResult { let tool = session.load_tool(&args.id).await?; let config_path = ProtoConfig::update( - tool.proto - .get_config_dir(map_pin_type(args.global, args.to)), + tool.proto.get_config_dir(map_pin_type(false, args.to)), |config| { let tool_configs = config.tools.get_or_insert(Default::default()); @@ -56,12 +53,19 @@ pub async fn alias(session: ProtoSession, args: AliasArgs) -> AppResult { }, )?; - println!( - "Added alias {} ({}) to config {}", - color::id(&args.alias), - color::muted_light(args.spec.to_string()), - color::path(config_path) - ); + session.console.render(element! { + Notice(variant: Variant::Success) { + StyledText( + content: format!( + "Added {} alias {} ({}) to config {}", + args.id, + args.alias, + args.spec.to_string(), + config_path.display() + ), + ) + } + })?; Ok(None) } diff --git a/crates/cli/src/commands/bin.rs b/crates/cli/src/commands/bin.rs index 28b034f34..089367a27 100644 --- a/crates/cli/src/commands/bin.rs +++ b/crates/cli/src/commands/bin.rs @@ -22,12 +22,12 @@ pub struct BinArgs { #[tracing::instrument(skip_all)] pub async fn bin(session: ProtoSession, args: BinArgs) -> AppResult { if args.id == PROTO_PLUGIN_KEY { - println!( - "{}", + session.console.out.write_line( locate_proto_exe("proto") .unwrap_or(session.env.store.bin_dir.join(get_exe_file_name("proto"))) .display() - ); + .to_string(), + )?; return Ok(None); } @@ -42,7 +42,11 @@ pub async fn bin(session: ProtoSession, args: BinArgs) -> AppResult { for bin in tool.resolve_bin_locations(false).await? { if bin.config.primary { - println!("{}", bin.path.display()); + session + .console + .out + .write_line(bin.path.display().to_string())?; + return Ok(None); } } @@ -53,13 +57,20 @@ pub async fn bin(session: ProtoSession, args: BinArgs) -> AppResult { for shim in tool.resolve_shim_locations().await? { if shim.config.primary { - println!("{}", shim.path.display()); + session + .console + .out + .write_line(shim.path.display().to_string())?; + return Ok(None); } } } - println!("{}", tool.locate_exe_file().await?.display()); + session + .console + .out + .write_line(tool.locate_exe_file().await?.display().to_string())?; Ok(None) } diff --git a/crates/cli/src/commands/clean.rs b/crates/cli/src/commands/clean.rs index 6ede55695..714e02d2f 100644 --- a/crates/cli/src/commands/clean.rs +++ b/crates/cli/src/commands/clean.rs @@ -1,13 +1,11 @@ -use crate::helpers::create_theme; use crate::session::ProtoSession; use clap::Args; -use dialoguer::Confirm; -use miette::IntoDiagnostic; -use proto_core::PROTO_PLUGIN_KEY; -use proto_core::{Id, ProtoError, Tool, VersionSpec}; +use iocraft::prelude::element; +use proto_core::{Id, ProtoError, Tool, VersionSpec, PROTO_PLUGIN_KEY}; use proto_shim::get_exe_file_name; use rustc_hash::FxHashSet; use starbase::AppResult; +use starbase_console::ui::*; use starbase_styles::color; use starbase_utils::fs; use std::io::stdout; @@ -47,7 +45,13 @@ fn is_older_than_days(now: u128, other: u128, days: u8) -> bool { } #[tracing::instrument(skip_all)] -pub async fn clean_tool(mut tool: Tool, now: u128, days: u8, yes: bool) -> miette::Result { +pub async fn clean_tool( + session: &ProtoSession, + mut tool: Tool, + now: u128, + days: u8, + yes: bool, +) -> miette::Result { println!("Checking {}", color::shell(tool.get_name())); if tool.metadata.inventory.override_dir.is_some() { @@ -135,6 +139,7 @@ pub async fn clean_tool(mut tool: Tool, now: u128, days: u8, yes: bool) -> miett let count = versions_to_clean.len(); let mut clean_count = 0; + let mut confirmed = false; if count == 0 { debug!("No versions to remove, continuing to next tool"); @@ -142,20 +147,25 @@ pub async fn clean_tool(mut tool: Tool, now: u128, days: u8, yes: bool) -> miett return Ok(0); } - if yes - || Confirm::with_theme(&create_theme()) - .with_prompt(format!( - "Found {} versions, remove {}?", - count, - versions_to_clean - .iter() - .map(|v| color::hash(v.to_string())) - .collect::>() - .join(", ") - )) - .interact() - .into_diagnostic()? - { + session + .console + .render_interactive(element! { + Confirm( + label: format!( + "Found {} versions, remove {}?", + count, + versions_to_clean + .iter() + .map(|v| format!("{v}")) + .collect::>() + .join(", ") + ), + value: &mut confirmed, + ) + }) + .await?; + + if yes || confirmed { for version in versions_to_clean { tool.set_version(version); tool.teardown().await?; @@ -237,17 +247,23 @@ pub async fn clean_proto(session: &ProtoSession, days: u64) -> miette::Result miette::Result { let tool = session.load_tool(id).await?; let inventory_dir = tool.get_inventory_dir(); - - if yes - || Confirm::with_theme(&create_theme()) - .with_prompt(format!( - "Purge all of {} at {}?", - tool.get_name(), - color::path(&inventory_dir) - )) - .interact() - .into_diagnostic()? - { + let mut confirmed = false; + + session + .console + .render_interactive(element! { + Confirm( + label: format!( + "Purge all of {} at {}?", + tool.get_name(), + inventory_dir.display() + ), + value: &mut confirmed, + ) + }) + .await?; + + if yes || confirmed { // Delete inventory fs::remove_dir_all(inventory_dir)?; @@ -264,22 +280,28 @@ pub async fn purge_tool(session: &ProtoSession, id: &Id, yes: bool) -> miette::R println!("Purged {}", tool.get_name()); } - Ok(tool) + Ok(tool.tool) } #[tracing::instrument(skip_all)] pub async fn purge_plugins(session: &ProtoSession, yes: bool) -> AppResult { let plugins_dir = &session.env.store.plugins_dir; - - if yes - || Confirm::with_theme(&create_theme()) - .with_prompt(format!( - "Purge all plugins in {}?", - color::path(plugins_dir) - )) - .interact() - .into_diagnostic()? - { + let mut confirmed = false; + + session + .console + .render_interactive(element! { + Confirm( + label: format!( + "Purge all plugins in {}?", + plugins_dir.display() + ), + value: &mut confirmed, + ) + }) + .await?; + + if yes || confirmed { fs::remove_dir_all(plugins_dir)?; fs::create_dir_all(plugins_dir)?; @@ -305,7 +327,7 @@ pub async fn internal_clean( debug!("Finding installed tools to clean up..."); for tool in session.load_tools().await? { - clean_count += clean_tool(tool, now, days, yes).await?; + clean_count += clean_tool(session, tool.tool, now, days, yes).await?; } clean_count += clean_proto(session, days as u64).await?; diff --git a/crates/cli/src/commands/completions.rs b/crates/cli/src/commands/completions.rs index 7c146bf1e..83fe4232a 100644 --- a/crates/cli/src/commands/completions.rs +++ b/crates/cli/src/commands/completions.rs @@ -3,7 +3,9 @@ use crate::session::ProtoSession; use clap::{Args, CommandFactory}; use clap_complete::{generate, Shell}; use clap_complete_nushell::Nushell; +use iocraft::prelude::element; use starbase::AppResult; +use starbase_console::ui::*; use starbase_shell::ShellType; #[derive(Args, Clone, Debug)] @@ -13,7 +15,7 @@ pub struct CompletionsArgs { } #[tracing::instrument(skip_all)] -pub async fn completions(_session: ProtoSession, args: CompletionsArgs) -> AppResult { +pub async fn completions(session: ProtoSession, args: CompletionsArgs) -> AppResult { let shell = match args.shell { Some(value) => value, None => ShellType::try_detect()?, @@ -34,7 +36,15 @@ pub async fn completions(_session: ProtoSession, args: CompletionsArgs) -> AppRe return Ok(None); } unsupported => { - eprintln!("{unsupported} does not currently support completions"); + session.console.render(element! { + Notice(variant: Variant::Caution) { + StyledText( + content: format!( + "{unsupported} does not currently support completions", + ), + ) + } + })?; return Ok(Some(1)); } diff --git a/crates/cli/src/commands/debug/config.rs b/crates/cli/src/commands/debug/config.rs index 47a72547d..4fcf1259e 100644 --- a/crates/cli/src/commands/debug/config.rs +++ b/crates/cli/src/commands/debug/config.rs @@ -1,27 +1,27 @@ -use crate::session::ProtoSession; +use crate::session::{ProtoConsole, ProtoSession}; use clap::Args; +use iocraft::prelude::*; use proto_core::{ProtoConfig, ProtoConfigFile}; use serde::Serialize; use starbase::AppResult; -use starbase_styles::color::{self, OwoStyle}; +use starbase_console::ui::*; +use starbase_styles::color; use starbase_utils::{json, toml}; #[derive(Serialize)] -pub struct DebugConfigResult<'a> { +struct DebugConfigResult<'a> { config: &'a ProtoConfig, files: Vec<&'a ProtoConfigFile>, } #[derive(Args, Clone, Debug)] pub struct DebugConfigArgs { - #[arg(long, help = "Print the list in JSON format")] + #[arg(long, help = "Print the data in JSON format")] json: bool, } -fn print_toml(value: impl Serialize) -> miette::Result<()> { - let contents = toml::format(&value, true)?; - - let contents = contents +fn print_toml(console: &ProtoConsole, value: impl Serialize) -> miette::Result<()> { + let contents = toml::format(&value, true)? .lines() .map(|line| { let indented_line = format!(" {line}"); @@ -35,7 +35,11 @@ fn print_toml(value: impl Serialize) -> miette::Result<()> { .collect::>() .join("\n"); - println!("{}", contents); + // TOML output is far too large to render with iocraft, + // so we unfortunately need to do all this manually + console.out.write_newline()?; + console.out.write_line(contents)?; + console.out.write_newline()?; Ok(()) } @@ -52,7 +56,10 @@ pub async fn config(session: ProtoSession, args: DebugConfigArgs) -> AppResult { files: manager.files.iter().rev().collect::>(), }; - println!("{}", json::format(&result, true)?); + session + .console + .out + .write_line(json::format(&result, true)?)?; return Ok(None); } @@ -62,18 +69,28 @@ pub async fn config(session: ProtoSession, args: DebugConfigArgs) -> AppResult { continue; } - println!(); - println!("{}", OwoStyle::new().bold().style(color::path(&file.path))); - print_toml(&file.config)?; + session.console.render(element! { + Container { + Section( + title: file.path.to_string_lossy(), + title_color: style_to_color(Style::Path) + ) + } + })?; + + print_toml(&session.console, &file.config)?; } - println!(); - println!( - "{}", - OwoStyle::new().bold().style(color::id("Configuration")) - ); - print_toml(config)?; - println!(); + session.console.render(element! { + Container { + Section( + title: "Final configuration", + title_color: style_to_color(Style::Shell), // pink brand + ) + } + })?; + + print_toml(&session.console, config)?; Ok(None) } diff --git a/crates/cli/src/commands/debug/env.rs b/crates/cli/src/commands/debug/env.rs index e9083fb16..79fd73657 100644 --- a/crates/cli/src/commands/debug/env.rs +++ b/crates/cli/src/commands/debug/env.rs @@ -1,78 +1,197 @@ -use crate::printer::{format_env_var, Printer}; +use crate::components::is_path_like; use crate::session::ProtoSession; +use clap::Args; +use iocraft::prelude::*; +use proto_core::layout::Store; use proto_pdk_api::{HostArch, HostOS}; +use serde::Serialize; use starbase::AppResult; -use starbase_styles::color; +use starbase_console::ui::*; +use starbase_utils::json; +use std::collections::BTreeMap; use std::env; +use std::path::PathBuf; -#[tracing::instrument(skip_all)] -pub async fn env(session: ProtoSession) -> AppResult { - let env = &session.env; - let manager = env.load_config_manager()?; - let mut printer = Printer::new(); - - // STORE +#[derive(Serialize)] +struct EnvironmentInfo { + arch: HostArch, + configs: Vec, + os: HostOS, + proto_version: String, + vars: BTreeMap, + virtual_paths: BTreeMap, +} - printer.named_section("Store", |p| { - p.entry("Root", color::path(&env.store.dir)); - p.entry("Bins", color::path(&env.store.bin_dir)); - p.entry("Shims", color::path(&env.store.shims_dir)); - p.entry("Plugins", color::path(&env.store.plugins_dir)); - p.entry("Tools", color::path(&env.store.inventory_dir)); - p.entry("Temp", color::path(&env.store.temp_dir)); +#[derive(Serialize)] +struct DebugEnvResult<'a> { + store: &'a Store, + env: EnvironmentInfo, +} - Ok(()) - })?; +#[derive(Args, Clone, Debug)] +pub struct DebugEnvArgs { + #[arg(long, help = "Print the data in JSON format")] + json: bool, +} - // ENV +#[tracing::instrument(skip_all)] +pub async fn env(session: ProtoSession, args: DebugEnvArgs) -> AppResult { + let env = &session.env; + let manager = env.load_config_manager()?; - printer.named_section("Environment", |p| { - p.entry( - "Proto version", - color::muted_light(env!("CARGO_PKG_VERSION")), - ); - p.entry( - "Operating system", - color::muted_light(HostOS::from_env().to_string()), - ); - p.entry( - "Architecture", - color::muted_light(HostArch::from_env().to_string()), - ); - p.entry_map( - "Virtual paths", - env.get_virtual_paths() - .iter() - .map(|(h, g)| (color::file(g.to_string_lossy()), color::path(h))), - None, - ); - p.entry_list( - "Configs", - manager.files.iter().filter_map(|f| { - if f.exists { - Some(color::path(&f.path)) + let environment = EnvironmentInfo { + arch: HostArch::from_env(), + configs: manager + .files + .iter() + .filter_map(|file| { + if file.exists { + Some(file.path.to_path_buf()) } else { None } - }), - None, - ); - p.entry_map( - "Variables", - env::vars().filter_map(|(k, v)| { + }) + .collect(), + os: HostOS::from_env(), + proto_version: env!("CARGO_PKG_VERSION").into(), + vars: env::vars() + .filter_map(|(k, v)| { if k.starts_with("PROTO_") { - Some((color::property(k), format_env_var(&v))) + Some((k, v)) } else { None } - }), - None, - ); + }) + .collect(), + virtual_paths: env.get_virtual_paths(), + }; - Ok(()) - })?; + if args.json { + let result = DebugEnvResult { + store: &env.store, + env: environment, + }; - printer.flush(); + session + .console + .out + .write_line(json::format(&result, true)?)?; + + return Ok(None); + } + + let store_paths = vec![ + ("Root", &env.store.dir), + ("Bins", &env.store.bin_dir), + ("Shims", &env.store.shims_dir), + ("Plugins", &env.store.plugins_dir), + ("Tools", &env.store.inventory_dir), + ("Temp", &env.store.temp_dir), + ]; + + session.console.render(element! { + Container { + Section(title: "Store") { + #(store_paths.into_iter().map(|(name, path)| { + element! { + Entry( + name, + value: element! { + StyledText(content: path.to_string_lossy(), style: Style::Path) + }.into_any(), + ) + } + })) + } + Section(title: "Environment") { + Entry( + name: "Proto version", + content: environment.proto_version, + ) + Entry( + name: "Operating system", + content: environment.os.to_string(), + ) + Entry( + name: "Architecture", + content: environment.arch.to_string(), + ) + Entry( + name: "Config sources", + no_children: environment.configs.is_empty(), + ) { + List { + #(environment.configs.into_iter().map(|file| { + element! { + ListItem { + StyledText( + content: file.to_string_lossy(), + style: Style::Path, + ) + } + } + })) + } + } + Entry( + name: "Virtual paths", + no_children: environment.virtual_paths.is_empty(), + ) { + Map { + #(environment.virtual_paths.into_iter().map(|(host, guest)| { + let name = element! { + StyledText( + content: guest.to_string_lossy(), + style: Style::File + ) + }.into_any(); + + let value = element! { + StyledText( + content: host.to_string_lossy(), + style: Style::Path + ) + }.into_any(); + + element! { + MapItem(name, value) + } + })) + } + } + Entry( + name: "Environment variables", + no_children: environment.vars.is_empty(), + ) { + Map { + #(environment.vars.into_iter().map(|(name, value)| { + let name = element! { + StyledText( + content: name, + style: Style::Property + ) + }.into_any(); + + let value = element! { + StyledText( + content: &value, + style: if is_path_like(&value) { + Style::Path + } else { + Style::MutedLight + } + ) + }.into_any(); + + element! { + MapItem(name, value) + } + })) + } + } + } + } + })?; Ok(None) } diff --git a/crates/cli/src/commands/diagnose.rs b/crates/cli/src/commands/diagnose.rs index 9a557078f..c85b6dab5 100644 --- a/crates/cli/src/commands/diagnose.rs +++ b/crates/cli/src/commands/diagnose.rs @@ -1,12 +1,12 @@ +use crate::components::{Issue, IssuesList}; use crate::helpers::fetch_latest_version; -use crate::printer::Printer; use crate::session::ProtoSession; use clap::Args; -use semver::Version; +use iocraft::prelude::{element, Box, Text}; use serde::Serialize; use starbase::AppResult; +use starbase_console::ui::*; use starbase_shell::ShellType; -use starbase_styles::color; use starbase_utils::json; use std::env; use std::path::PathBuf; @@ -20,13 +20,6 @@ pub struct DiagnoseArgs { json: bool, } -#[derive(Serialize)] -struct Issue { - issue: String, - resolution: Option, - comment: Option, -} - #[derive(Serialize)] struct Diagnosis { shell: String, @@ -62,79 +55,99 @@ pub async fn diagnose(session: ProtoSession, args: DiagnoseArgs) -> AppResult { .load_preferred_profile()? .unwrap_or_else(|| shell.get_env_path(&session.env.home)); - println!( - "{}", - json::format( - &Diagnosis { - shell: shell_type.to_string(), - shell_profile: shell_path, - errors, - warnings, - tips, - }, - true - )? - ); + session.console.out.write_line(json::format( + &Diagnosis { + shell: shell_type.to_string(), + shell_profile: shell_path, + errors, + warnings, + tips, + }, + true, + )?)?; return Ok(None); } if errors.is_empty() && warnings.is_empty() { - println!( - "{}", - color::success("No issues detected with your proto installation!") - ); + session.console.render(element! { + Notice(variant: Variant::Success) { + Text(content: "No issues detected with your proto installation!") + } + })?; return Ok(None); } + let has_errors = !errors.is_empty(); let shell = shell_type.build(); - let mut printer = Printer::new(); - - printer.line(); - printer.entry("Shell", color::id(shell_type.to_string())); - printer.entry( - "Shell profile", - color::path( - session - .env - .store - .load_preferred_profile()? - .unwrap_or_else(|| shell.get_env_path(&session.env.home)), - ), - ); - - if !errors.is_empty() { - printer.named_section(color::failure("Errors"), |p| { - print_issues(&errors, p); - - Ok(()) - })?; - } - - if !warnings.is_empty() { - printer.named_section(color::caution("Warnings"), |p| { - print_issues(&warnings, p); - - Ok(()) - })?; - } - - if !tips.is_empty() { - printer.named_section(color::label("Tips"), |p| { - p.list(tips); - - Ok(()) - })?; - } - - printer.flush(); - - if !errors.is_empty() { - return Ok(Some(1)); - } + let shell_path = session + .env + .store + .load_preferred_profile()? + .unwrap_or_else(|| shell.get_env_path(&session.env.home)); + + session.console.render(element! { + Container { + Box(margin_bottom: 1) { + Entry( + name: "Shell", + value: element! { + StyledText( + content: shell_type.to_string(), + style: Style::Id, + ) + }.into_any() + ) + Entry( + name: "Shell profile", + value: element! { + StyledText( + content: shell_path.to_string_lossy(), + style: Style::Path, + ) + }.into_any() + ) + } + #(if errors.is_empty() { + None + } else { + Some(element! { + Section(title: "Errors", variant: Variant::Failure) { + IssuesList(issues: errors) + } + }) + }) + #(if warnings.is_empty() { + None + } else { + Some(element! { + Section(title: "Warnings", variant: Variant::Caution) { + IssuesList(issues: warnings) + } + }) + }) + #(if tips.is_empty() { + None + } else { + Some(element! { + Section(title: "Tips", variant: Variant::Info) { + List { + #(tips.into_iter().map(|tip| { + element! { + ListItem { + StyledText(content: tip) + } + } + })) + } + } + }) + }) + } + })?; - Ok(None) + Ok(if has_errors { Some(1) } else { None }) } async fn gather_errors( @@ -162,10 +175,9 @@ async fn gather_errors( if !has_shims_before_bins && found_shims && found_bin { errors.push(Issue { issue: format!( - "Bin directory ({}) was found BEFORE the shims directory ({}) on {}", - color::path(&session.env.store.bin_dir), - color::path(&session.env.store.shims_dir), - color::property("PATH") + "Bin directory {} was found BEFORE the shims directory {} on PATH", + session.env.store.bin_dir.display(), + session.env.store.shims_dir.display(), ), resolution: Some( "Ensure the shims path comes before the bin path in your shell".into(), @@ -189,30 +201,23 @@ async fn gather_warnings( let current_version = &session.cli_version; let latest_version = fetch_latest_version().await?; - if Version::parse(current_version).unwrap() < Version::parse(&latest_version).unwrap() { + if current_version < &latest_version { warnings.push(Issue { issue: format!( - "Current proto version {current_version} is outdated, latest is {latest_version}", + "Current proto version {current_version} is outdated, latest is {latest_version}", ), - resolution: Some(format!("Run {} to update", color::shell("proto upgrade"))), + resolution: Some("Run proto upgrade to update".into()), comment: None, }); } if env::var("PROTO_HOME").is_err() { warnings.push(Issue { - issue: format!( - "Missing {} environment variable", - color::property("PROTO_HOME") + issue: "Missing PROTO_HOME environment variable".into(), + resolution: Some( + "Export PROTO_HOME=\"$HOME/.proto\" from your shell".into(), ), - resolution: Some(format!( - "Export {} from your shell", - color::label("PROTO_HOME=\"$HOME/.proto\"") - )), - comment: Some(format!( - "Will default to {} if not defined", - color::file("~/.proto") - )), + comment: Some("Will default to ~/.proto if not defined".into()), }); } @@ -223,14 +228,13 @@ async fn gather_warnings( if !has_shims_on_path { warnings.push(Issue { issue: format!( - "Shims directory ({}) not found on {}", - color::path(&session.env.store.shims_dir), - color::property("PATH") + "Shims directory {} not found on PATH", + session.env.store.shims_dir.display(), + ), + resolution: Some( + "Append $PROTO_HOME/shims to PATH in your shell" + .into(), ), - resolution: Some(format!( - "Append {} to path in your shell", - color::label("$PROTO_HOME/shims") - )), comment: Some("If not using shims on purpose, ignore this warning".into()), }) } @@ -240,51 +244,20 @@ async fn gather_warnings( if !has_bins_on_path { warnings.push(Issue { issue: format!( - "Bin directory ({}) not found on {}", - color::path(&session.env.store.bin_dir), - color::property("PATH") + "Bin directory {} not found on PATH", + session.env.store.bin_dir.display() + ), + resolution: Some( + "Append $PROTO_HOME/bin to PATH in your shell" + .into(), ), - resolution: Some(format!( - "Append {} to path in your shell", - color::label("$PROTO_HOME/bin") - )), comment: None, }) } if !warnings.is_empty() { - tips.push(format!( - "Run {} to resolve some of these issues!", - color::shell("proto setup") - )); + tips.push("Run proto setup to resolve some of these issues!".into()); } Ok(warnings) } - -fn print_issues(issues: &[Issue], printer: &mut Printer) { - let length = issues.len() - 1; - - for (index, issue) in issues.iter().enumerate() { - printer.entry( - color::muted_light("Issue"), - format!( - "{} {}", - &issue.issue, - if let Some(comment) = &issue.comment { - color::muted_light(format!("({})", comment)) - } else { - "".into() - } - ), - ); - - if let Some(resolution) = &issue.resolution { - printer.entry(color::muted_light("Resolution"), resolution); - } - - if index != length { - printer.line(); - } - } -} diff --git a/crates/cli/src/commands/install.rs b/crates/cli/src/commands/install.rs index 0f6d2c517..8a2004181 100644 --- a/crates/cli/src/commands/install.rs +++ b/crates/cli/src/commands/install.rs @@ -7,10 +7,14 @@ use crate::telemetry::{track_usage, Metric}; use crate::utils::install_graph::*; use clap::Args; use indicatif::ProgressBar; +use iocraft::prelude::element; use proto_core::flow::install::{InstallOptions, InstallPhase}; -use proto_core::{Id, PinType, Tool, UnresolvedVersionSpec, VersionSpec, PROTO_PLUGIN_KEY}; +use proto_core::{ + ConfigMode, Id, PinLocation, Tool, UnresolvedVersionSpec, VersionSpec, PROTO_PLUGIN_KEY, +}; use proto_pdk_api::{InstallHook, SyncShellProfileInput, SyncShellProfileOutput}; use starbase::AppResult; +use starbase_console::ui::*; use starbase_shell::ShellType; use starbase_styles::color; use std::collections::BTreeMap; @@ -60,11 +64,11 @@ pub struct InstallArgs { } impl InstallArgs { - fn get_pin_type(&self) -> Option { + fn get_pin_location(&self) -> Option { self.pin.as_ref().map(|pin| match pin { - Some(PinOption::Global) => PinType::Global, - Some(PinOption::User) => PinType::User, - _ => PinType::Local, + Some(PinOption::Global) => PinLocation::Global, + Some(PinOption::User) => PinLocation::User, + _ => PinLocation::Local, }) } @@ -97,7 +101,7 @@ pub fn enforce_requirements( async fn pin_version( tool: &mut Tool, initial_version: &UnresolvedVersionSpec, - arg_pin_type: &Option, + arg_pin_to: &Option, ) -> miette::Result { // Don't pin the proto tool itself as it's internal only if tool.id.as_str() == PROTO_PLUGIN_KEY { @@ -106,30 +110,30 @@ async fn pin_version( let config = tool.proto.load_config()?; let spec = tool.get_resolved_version().to_unresolved_spec(); - let mut pin_type = PinType::Local; + let mut pin_to = PinLocation::Local; let mut pin = false; // via `--pin` arg - if let Some(custom_type) = arg_pin_type { - pin_type = *custom_type; + if let Some(custom_type) = arg_pin_to { + pin_to = *custom_type; pin = true; } // Or the first time being installed - else if !config.versions.contains_key(&tool.id) { - pin_type = PinType::Global; + else if !tool.inventory.dir.exists() { + pin_to = PinLocation::Global; pin = true; } // via `pin-latest` setting if initial_version.is_latest() { if let Some(custom_type) = &config.settings.pin_latest { - pin_type = *custom_type; + pin_to = *custom_type; pin = true; } } if pin { - internal_pin(tool, &spec, pin_type).await?; + internal_pin(tool, &spec, pin_to).await?; } Ok(pin) @@ -202,7 +206,7 @@ async fn update_shell(tool: &Tool, passthrough_args: Vec) -> miette::Res &exported_content, &output.check_var, )? { - println!( + debug!( "Added {} to shell profile {}", color::property(output.check_var), color::path(updated_profile) @@ -220,7 +224,7 @@ pub async fn do_install( pb: &ProgressBar, ) -> miette::Result { let version = args.get_unresolved_spec(); - let pin_type = args.get_pin_type(); + let pin_type = args.get_pin_location(); let name = tool.get_name().to_owned(); let finish_pb = |installed: bool, resolved_version: &VersionSpec| { @@ -390,29 +394,41 @@ async fn install_one(session: &ProtoSession, id: &Id, args: InstallArgs) -> miet // Load config including global versions, // so that our requirements can be satisfied - let config = session.env.load_config_manager()?.get_merged_config()?; + let config = session.load_config_with_mode(ConfigMode::UpwardsGlobal)?; enforce_requirements(&tool, &config.versions)?; let pb = create_progress_bar(format!("Installing {}", tool.get_name())); if do_install(&mut tool, args, &pb).await? { - println!( - "{} {} has been installed to {}!", - tool.get_name(), - tool.get_resolved_version(), - color::path(tool.get_product_dir()), - ); + session.console.render(element! { + Notice(variant: Variant::Success) { + StyledText( + content: format!( + "{} {} has been installed to {}!", + tool.get_name(), + tool.get_resolved_version(), + tool.get_product_dir().display(), + ), + ) + } + })?; } else { - println!( - "{} {} has already been installed at {}", - tool.get_name(), - tool.get_resolved_version(), - color::path(tool.get_product_dir()), - ); + session.console.render(element! { + Notice(variant: Variant::Info) { + StyledText( + content: format!( + "{} {} has already been installed at {}!", + tool.get_name(), + tool.get_resolved_version(), + tool.get_product_dir().display(), + ), + ) + } + })?; } - Ok(tool) + Ok(tool.tool) } #[instrument(skip_all)] @@ -423,7 +439,7 @@ pub async fn install_all(session: &ProtoSession) -> AppResult { debug!("Detecting tool versions to install"); - let mut versions = session.env.load_config()?.versions.to_owned(); + let mut versions = session.load_config()?.versions.to_owned(); versions.remove(PROTO_PLUGIN_KEY); for tool in &tools { @@ -439,7 +455,27 @@ pub async fn install_all(session: &ProtoSession) -> AppResult { } if versions.is_empty() { - eprintln!("No versions have been configured, nothing to install!"); + session.console.render(element! { + Notice(variant: Variant::Caution) { + StyledText( + content: "No versions have been configured, nothing to install!", + ) + #(if session.env.config_mode == ConfigMode::UpwardsGlobal { + None + } else { + Some(element! { + View(margin_top: 1) { + StyledText( + content: format!( + "Configuration has been loaded in {} mode. Try changing the mode with --config-mode to include other pinned versions.", + session.env.config_mode + ) + ) + } + }) + }) + } + })?; return Ok(Some(1)); } @@ -564,11 +600,17 @@ pub async fn install_all(session: &ProtoSession) -> AppResult { // When no TTY, we should display something to the user! if mpb.is_hidden() { if installed_count > 0 { - println!("Successfully installed {} tools!", installed_count); + session + .console + .out + .write_line(format!("Successfully installed {} tools!", installed_count))?; } if failed_count > 0 { - println!("Failed to install {} tools!", failed_count); + session + .console + .out + .write_line(format!("Failed to install {} tools!", failed_count))?; } } diff --git a/crates/cli/src/commands/list.rs b/crates/cli/src/commands/list.rs index 260be9bd1..2e0696b6d 100644 --- a/crates/cli/src/commands/list.rs +++ b/crates/cli/src/commands/list.rs @@ -1,7 +1,9 @@ -use crate::session::ProtoSession; +use crate::session::{LoadToolOptions, ProtoSession}; use clap::Args; +use iocraft::prelude::element; use proto_core::Id; use starbase::AppResult; +use starbase_console::ui::*; use tracing::debug; #[derive(Args, Clone, Debug)] @@ -15,45 +17,49 @@ pub struct ListArgs { #[tracing::instrument(skip_all)] pub async fn list(session: ProtoSession, args: ListArgs) -> AppResult { - let tool = session.load_tool(&args.id).await?; + let tool = session + .load_tool_with_options( + &args.id, + LoadToolOptions { + inherit_local: true, + ..Default::default() + }, + ) + .await?; debug!(manifest = ?tool.inventory.manifest.path, "Using versions from manifest"); - let mut versions = Vec::from_iter(tool.inventory.manifest.installed_versions); - - if versions.is_empty() { - eprintln!("No versions installed"); + if tool.installed_versions.is_empty() { + session.console.render(element! { + Notice(variant: Variant::Failure) { + StyledText( + content: format!( + "No versions installed locally, try installing the latest version with proto install {}", + args.id + ), + ) + } + })?; return Ok(Some(1)); } - versions.sort(); - - println!( - "{}", - versions + session.console.out.write_line( + tool.installed_versions .iter() .map(|v| v.to_string()) .collect::>() - .join("\n") - ); - - if args.aliases { - let config = session.env.load_config()?; - - if let Some(tool_config) = config.tools.get(&tool.id) { - if !tool_config.aliases.is_empty() { - println!( - "{}", - tool_config - .aliases - .iter() - .map(|(k, v)| format!("{k} -> {v}")) - .collect::>() - .join("\n") - ); - } - } + .join("\n"), + )?; + + if args.aliases && !tool.local_aliases.is_empty() { + session.console.out.write_line( + tool.local_aliases + .iter() + .map(|(k, v)| format!("{k} -> {v}")) + .collect::>() + .join("\n"), + )?; } Ok(None) diff --git a/crates/cli/src/commands/list_remote.rs b/crates/cli/src/commands/list_remote.rs index a929e304e..b7ecc7613 100644 --- a/crates/cli/src/commands/list_remote.rs +++ b/crates/cli/src/commands/list_remote.rs @@ -1,7 +1,9 @@ -use crate::session::ProtoSession; +use crate::session::{LoadToolOptions, ProtoSession}; use clap::Args; -use proto_core::{Id, UnresolvedVersionSpec}; +use iocraft::prelude::element; +use proto_core::Id; use starbase::AppResult; +use starbase_console::ui::*; use tracing::debug; #[derive(Args, Clone, Debug)] @@ -15,43 +17,46 @@ pub struct ListRemoteArgs { #[tracing::instrument(skip_all)] pub async fn list_remote(session: ProtoSession, args: ListRemoteArgs) -> AppResult { - let mut tool = session.load_tool(&args.id).await?; - tool.disable_caching(); - - debug!("Loading versions"); - - let resolver = tool - .load_version_resolver(&UnresolvedVersionSpec::default()) + let tool = session + .load_tool_with_options( + &args.id, + LoadToolOptions { + inherit_remote: true, + ..Default::default() + }, + ) .await?; - let mut versions = resolver.versions; - if versions.is_empty() { - eprintln!("No versions available"); + debug!("Loading versions from remote"); + + if tool.remote_versions.is_empty() { + session.console.render(element! { + Notice(variant: Variant::Failure) { + StyledText( + content: "No versions available from remote registry" + ) + } + })?; return Ok(Some(1)); } - versions.sort(); - - println!( - "{}", - versions + session.console.out.write_line( + tool.remote_versions .iter() .map(|v| v.to_string()) .collect::>() - .join("\n") - ); - - if args.aliases && !resolver.aliases.is_empty() { - println!( - "{}", - resolver - .aliases + .join("\n"), + )?; + + if args.aliases && !tool.remote_aliases.is_empty() { + session.console.out.write_line( + tool.remote_aliases .iter() .map(|(k, v)| format!("{k} -> {v}")) .collect::>() - .join("\n") - ); + .join("\n"), + )?; } Ok(None) diff --git a/crates/cli/src/commands/outdated.rs b/crates/cli/src/commands/outdated.rs index a5df6c48a..3b411a61b 100644 --- a/crates/cli/src/commands/outdated.rs +++ b/crates/cli/src/commands/outdated.rs @@ -1,20 +1,16 @@ use crate::error::ProtoCliError; -use crate::helpers::create_theme; use crate::session::ProtoSession; use clap::Args; -use comfy_table::presets::NOTHING; -use comfy_table::{Attribute, Cell, Color, ContentArrangement, Table}; -use dialoguer::Confirm; +use iocraft::prelude::{element, Size}; use miette::IntoDiagnostic; use proto_core::{Id, ProtoConfig, UnresolvedVersionSpec, VersionSpec, PROTO_PLUGIN_KEY}; use rustc_hash::FxHashSet; use semver::VersionReq; use serde::Serialize; use starbase::AppResult; -use starbase_styles::color::{self, Style}; +use starbase_console::ui::*; use starbase_utils::json; use std::collections::BTreeMap; -use std::io::{stdout, IsTerminal}; use std::path::PathBuf; use tokio::spawn; use tracing::debug; @@ -35,6 +31,9 @@ pub struct OutdatedArgs { help = "Update and write the versions to their respective configuration" )] update: bool, + + #[arg(long, help = "Avoid and force confirm prompts", env = "PROTO_YES")] + yes: bool, } #[derive(Serialize)] @@ -150,7 +149,7 @@ pub async fn outdated(session: ProtoSession, args: OutdatedArgs) -> AppResult { .await?; Result::<_, miette::Report>::Ok(( - tool.id, + tool.id.clone(), OutdatedItem { is_latest: current_version == latest_version, is_outdated: newest_version > current_version @@ -175,63 +174,97 @@ pub async fn outdated(session: ProtoSession, args: OutdatedArgs) -> AppResult { // Dump all the data as JSON if args.json { - println!("{}", json::format(&items, true)?); + session + .console + .out + .write_line(json::format(&items, true)?)?; return Ok(None); } // Print all the data in a table - let mut table = Table::new(); - table.load_preset(NOTHING); - table.set_content_arrangement(ContentArrangement::Dynamic); - - table.set_header(vec![ - Cell::new("Tool").add_attribute(Attribute::Bold), - Cell::new("Current").add_attribute(Attribute::Bold), - Cell::new("Newest").add_attribute(Attribute::Bold), - Cell::new("Latest").add_attribute(Attribute::Bold), - Cell::new("Config").add_attribute(Attribute::Bold), - ]); - - for (id, item) in &items { - table.add_row(vec![ - Cell::new(id).fg(Color::AnsiValue(Style::Id.color() as u8)), - Cell::new(&item.current_version), - if item.newest_version == item.current_version { - Cell::new(&item.newest_version) - .fg(Color::AnsiValue(Style::MutedLight.color() as u8)) - } else { - Cell::new(&item.newest_version).fg(Color::AnsiValue(Style::Success.color() as u8)) - }, - if item.latest_version == item.current_version { - Cell::new(&item.latest_version) - .fg(Color::AnsiValue(Style::MutedLight.color() as u8)) - } else if item.latest_version == item.newest_version { - Cell::new(&item.latest_version).fg(Color::AnsiValue(Style::Success.color() as u8)) - } else { - Cell::new(&item.latest_version).fg(Color::AnsiValue(Style::Failure.color() as u8)) - }, - Cell::new(item.config_source.to_string_lossy()) - .fg(Color::AnsiValue(Style::Path.color() as u8)), - ]); - } - - println!("\n{table}\n"); + session.console.render(element! { + Container { + Table( + headers: vec![ + TableHeader::new("Tool", Size::Percent(10.0)), + TableHeader::new("Current", Size::Percent(8.0)), + TableHeader::new("Newest", Size::Percent(8.0)), + TableHeader::new("Latest", Size::Percent(8.0)), + TableHeader::new("Config", Size::Percent(66.0)), + ] + ) { + #(items.iter().enumerate().map(|(i, (id, item))| { + element! { + TableRow(row: i as i32) { + TableCol(col: 0) { + StyledText( + content: id.to_string(), + style: Style::Id + ) + } + TableCol(col: 1) { + StyledText( + content: item.current_version.to_string(), + ) + } + TableCol(col: 2) { + StyledText( + content: item.newest_version.to_string(), + style: if item.newest_version == item.current_version { + Style::MutedLight + } else { + Style::Success + } + ) + } + TableCol(col: 3) { + StyledText( + content: item.latest_version.to_string(), + style: if item.latest_version == item.current_version { + Style::MutedLight + } else if item.latest_version == item.newest_version { + Style::Success + } else { + Style::Failure + } + ) + } + TableCol(col: 4) { + StyledText( + content: item.config_source.to_string_lossy(), + style: Style::Path + ) + } + } + } + })) + } + } + })?; // If updating versions, batch the changes based on config paths - let theme = create_theme(); + if !args.update { + return Ok(None); + } - if args.update - && (!stdout().is_terminal() - || Confirm::with_theme(&theme) - .with_prompt(if args.latest { + let mut confirmed = false; + + session + .console + .render_interactive(element! { + Confirm( + label: if args.latest { "Update config files with latest versions?" } else { "Update config files with newest versions?" - }) - .interact() - .into_diagnostic()?) - { + }, + value: &mut confirmed, + ) + }) + .await?; + + if args.yes || confirmed { let mut updates: BTreeMap> = BTreeMap::new(); for (id, item) in &items { @@ -249,12 +282,6 @@ pub async fn outdated(session: ProtoSession, args: OutdatedArgs) -> AppResult { } for (config_path, updated_versions) in updates { - println!( - "Updating {} with {} versions", - color::path(&config_path), - updated_versions.len() - ); - debug!( config = ?config_path, versions = ?updated_versions @@ -272,10 +299,13 @@ pub async fn outdated(session: ProtoSession, args: OutdatedArgs) -> AppResult { })?; } - println!( - "Update complete! Run {} to install these new versions.", - color::shell("proto use") - ); + session.console.render(element! { + Notice(variant: Variant::Success) { + StyledText( + content: "Update complete! Run proto install to install these new versions." + ) + } + })?; } Ok(None) diff --git a/crates/cli/src/commands/pin.rs b/crates/cli/src/commands/pin.rs index fa59ca84f..47619dc60 100644 --- a/crates/cli/src/commands/pin.rs +++ b/crates/cli/src/commands/pin.rs @@ -1,9 +1,10 @@ use crate::helpers::{map_pin_type, PinOption}; use crate::session::ProtoSession; use clap::Args; -use proto_core::{Id, PinType, ProtoConfig, Tool, UnresolvedVersionSpec}; +use iocraft::prelude::element; +use proto_core::{Id, PinLocation, ProtoConfig, Tool, UnresolvedVersionSpec}; use starbase::AppResult; -use starbase_styles::color; +use starbase_console::ui::*; use std::collections::BTreeMap; use std::path::PathBuf; use tracing::debug; @@ -16,22 +17,19 @@ pub struct PinArgs { #[arg(required = true, help = "Version or alias of tool")] pub spec: UnresolvedVersionSpec, - #[arg(long, group = "pin", help = "Pin to the global ~/.proto/.prototools")] - pub global: bool, - #[arg(long, help = "Resolve the version before pinning")] pub resolve: bool, - #[arg(long, group = "pin", help = "Location of .prototools to pin to")] + #[arg(long, help = "Location of .prototools to pin to")] pub to: Option, } pub async fn internal_pin( tool: &mut Tool, spec: &UnresolvedVersionSpec, - pin: PinType, + pin_to: PinLocation, ) -> miette::Result { - let config_path = ProtoConfig::update(tool.proto.get_config_dir(pin), |config| { + let config_path = ProtoConfig::update(tool.proto.get_config_dir(pin_to), |config| { config .versions .get_or_insert(BTreeMap::default()) @@ -59,14 +57,30 @@ pub async fn pin(session: ProtoSession, args: PinArgs) -> AppResult { args.spec.clone() }; - let config_path = internal_pin(&mut tool, &spec, map_pin_type(args.global, args.to)).await?; + let config_path = internal_pin(&mut tool, &spec, map_pin_type(false, args.to)).await?; - println!( - "Pinned {} to {} in {}", - tool.get_name(), - color::hash(args.spec.to_string()), - color::path(config_path), - ); + session.console.render(element! { + Notice(variant: Variant::Success) { + StyledText( + content: if spec != args.spec { + format!( + "Pinned {} version {} (resolved from {}) to config {}", + args.id, + spec, + args.spec, + config_path.display() + ) + } else { + format!( + "Pinned {} version {} to config {}", + args.id, + args.spec, + config_path.display() + ) + }, + ) + } + })?; Ok(None) } diff --git a/crates/cli/src/commands/plugin/add.rs b/crates/cli/src/commands/plugin/add.rs index 5a076b978..872b3cf7a 100644 --- a/crates/cli/src/commands/plugin/add.rs +++ b/crates/cli/src/commands/plugin/add.rs @@ -1,9 +1,9 @@ -use crate::helpers::{map_pin_type, PinOption}; use crate::session::ProtoSession; use clap::Args; -use proto_core::{Id, PluginLocator, ProtoConfig}; +use iocraft::prelude::element; +use proto_core::{Id, PinLocation, PluginLocator, ProtoConfig}; use starbase::AppResult; -use starbase_styles::color; +use starbase_console::ui::*; #[derive(Args, Clone, Debug)] pub struct AddPluginArgs { @@ -13,26 +13,18 @@ pub struct AddPluginArgs { #[arg(required = true, help = "Locator string to find and load the plugin")] plugin: PluginLocator, - #[arg(long, group = "pin", help = "Add to the global ~/.proto/.prototools")] - global: bool, - - #[arg(long, group = "pin", help = "Location of .prototools to add to")] - to: Option, + #[arg(long, default_value_t, help = "Location of .prototools to add to")] + to: PinLocation, } #[tracing::instrument(skip_all)] pub async fn add(session: ProtoSession, args: AddPluginArgs) -> AppResult { - let config_path = ProtoConfig::update( - session - .env - .get_config_dir(map_pin_type(args.global, args.to)), - |config| { - config - .plugins - .get_or_insert(Default::default()) - .insert(args.id.clone(), args.plugin.clone()); - }, - )?; + let config_path = ProtoConfig::update(session.env.get_config_dir(args.to), |config| { + config + .plugins + .get_or_insert(Default::default()) + .insert(args.id.clone(), args.plugin.clone()); + })?; // Load the tool and verify it works. We can't load the tool with the // session as the config has already been cached, and doesn't reflect @@ -45,25 +37,33 @@ pub async fn add(session: ProtoSession, args: AddPluginArgs) -> AppResult { let tool = load_tool_from_locator(&args.id, &session.env, &args.plugin).await?; if !tool.metadata.deprecations.is_empty() { - let mut output = color::caution("Deprecation notices from the plugin:\n"); - - for msg in &tool.metadata.deprecations { - output.push_str(" "); - output.push_str(&color::muted("-")); - output.push(' '); - output.push_str(&apply_style_tags(msg)); - output.push('\n'); - } - - println!("{output}"); + session.console.render(element! { + Notice(title: "Deprecations", variant: Variant::Info) { + List { + #(tool.metadata.deprecations.iter().map(|message| { + element! { + ListItem { + StyledText(content: message) + } + } + })) + } + } + })?; } } - println!( - "Added plugin {} to config {}", - color::id(&args.id), - color::path(config_path) - ); + session.console.render(element! { + Notice(variant: Variant::Success) { + StyledText( + content: format!( + "Added {} plugin to config {}", + args.id, + config_path.display(), + ), + ) + } + })?; Ok(None) } diff --git a/crates/cli/src/commands/plugin/info.rs b/crates/cli/src/commands/plugin/info.rs index 28c8ec7ce..d907905ec 100644 --- a/crates/cli/src/commands/plugin/info.rs +++ b/crates/cli/src/commands/plugin/info.rs @@ -1,19 +1,20 @@ -use crate::printer::{format_env_var, format_value, Printer}; -use crate::session::ProtoSession; +use crate::components::*; +use crate::session::{LoadToolOptions, ProtoSession}; use clap::Args; +use iocraft::prelude::element; use proto_core::{ - detect_version, flow::locate::ExecutableLocation, EnvVar, Id, PluginLocator, ProtoToolConfig, - ToolManifest, UnresolvedVersionSpec, + flow::locate::ExecutableLocation, ConfigMode, Id, PluginLocator, ProtoToolConfig, ToolManifest, }; use proto_pdk_api::ToolMetadataOutput; use serde::Serialize; use starbase::AppResult; -use starbase_styles::color::{self, apply_style_tags}; +use starbase_console::ui::*; use starbase_utils::json; +use std::collections::BTreeMap; use std::path::PathBuf; #[derive(Serialize)] -pub struct PluginInfo { +struct PluginInfo { bins: Vec, config: ProtoToolConfig, exe_file: PathBuf, @@ -40,212 +41,334 @@ pub struct InfoPluginArgs { #[tracing::instrument(skip_all)] pub async fn info(session: ProtoSession, args: InfoPluginArgs) -> AppResult { - let mut tool = session.load_tool(&args.id).await?; - let version = detect_version(&tool, None) - .await - .unwrap_or_else(|_| UnresolvedVersionSpec::parse("*").unwrap()); - - tool.resolve_version(&version, false).await?; + let global_config = session.load_config_with_mode(ConfigMode::Global)?; + + let mut tool = session + .load_tool_with_options( + &args.id, + LoadToolOptions { + detect_version: true, + inherit_local: true, + inherit_remote: true, + ..Default::default() + }, + ) + .await?; - let mut config = session.env.load_config()?.to_owned(); - let tool_config = config.tools.remove(&tool.id).unwrap_or_default(); let bins = tool.resolve_bin_locations(true).await?; let shims = tool.resolve_shim_locations().await?; if args.json { let info = PluginInfo { bins, - config: tool_config, + config: tool.config.clone(), exe_file: tool.locate_exe_file().await?, exes_dir: tool.locate_exes_dir().await?, globals_dirs: tool.locate_globals_dirs().await?, globals_prefix: tool.locate_globals_prefix().await?, inventory_dir: tool.get_inventory_dir(), shims, - id: tool.id, + id: tool.id.clone(), name: tool.metadata.name.clone(), - manifest: tool.inventory.manifest, - metadata: tool.metadata, - plugin: tool.locator.unwrap(), + manifest: tool.inventory.manifest.clone(), + metadata: tool.metadata.clone(), + plugin: tool.locator.clone().unwrap(), }; - println!("{}", json::format(&info, true)?); + session.console.out.write_line(json::format(&info, true)?)?; return Ok(None); } - let mut printer = Printer::new(); - printer.header(&tool.id, &tool.metadata.name); - // PLUGIN - printer.named_section("Plugin", |p| { - if let Some(version) = &tool.metadata.plugin_version { - p.entry("Version", color::hash(version.to_string())); - } - - if let Some(locator) = &tool.locator { - p.locator(locator); - } - - if !tool.metadata.requires.is_empty() { - p.entry_list( - "Requires", - tool.metadata.requires.iter().map(color::id), - None, - ); - } - - if !tool.metadata.deprecations.is_empty() { - p.entry_list( - "Deprecations", - tool.metadata.deprecations.iter().map(apply_style_tags), - None, - ); + session.console.render(element! { + Container { + Section(title: "Plugin") { + Entry( + name: "ID", + value: element! { + StyledText( + content: tool.id.to_string(), + style: Style::Id + ) + }.into_any() + ) + Entry( + name: "Name", + content: tool.metadata.name.clone(), + ) + Entry( + name: "Type", + content: format!("{:?}", tool.metadata.type_of), + ) + #(tool.metadata.plugin_version.as_ref().map(|version| { + element! { + Entry( + name: "Version", + value: element! { + StyledText( + content: version.to_string(), + style: Style::Hash + ) + }.into_any() + ) + } + })) + #(tool.locator.as_ref().map(|locator| { + element! { + Locator(value: locator) + } + })) + #(if tool.metadata.requires.is_empty() { + None + } else { + Some(element! { + Entry(name: "Requires") { + List { + #(tool.metadata.requires.iter().map(|req_id| { + element! { + ListItem { + StyledText( + content: req_id, + style: Style::Id + ) + } + } + })) + } + } + }) + }) + #(if tool.metadata.deprecations.is_empty() { + None + } else { + Some(element! { + Entry(name: "Deprecations") { + List { + #(tool.metadata.deprecations.iter().map(|content| { + element! { + ListItem { + StyledText(content) + } + } + })) + } + } + }) + }) + } } - - Ok(()) })?; // INVENTORY let exe_file = tool.locate_exe_file().await?; let exes_dir = tool.locate_exes_dir().await?; - let globals_dirs = tool.locate_globals_dir().await?; + let globals_dir = tool.locate_globals_dir().await?; let globals_prefix = tool.locate_globals_prefix().await?; - let mut version_resolver = tool - .load_version_resolver(&UnresolvedVersionSpec::default()) - .await?; - version_resolver.aliases.extend(tool_config.aliases.clone()); - - printer.named_section("Inventory", |p| { - p.entry("Store", color::path(tool.get_inventory_dir())); - - p.entry( - "Detected version", - color::symbol(tool.get_resolved_version().to_string()), - ); - - p.entry("Executable", color::path(exe_file)); - - if let Some(dir) = exes_dir { - p.entry("Executables directory", color::path(dir)); - } - - if let Some(prefix) = globals_prefix { - p.entry("Global packages prefix", color::property(prefix)); - } - - p.entry_list( - "Global packages directories", - globals_dirs.iter().map(color::path), - Some(color::failure("None")), - ); - - p.entry_list( - "Shims", - shims.into_iter().map(|shim| { - format!( - "{} {}", - color::path(shim.path), - if shim.config.primary { - format_value("(primary)") - } else { - "".into() + session.console.render(element! { + Container { + Section(title: "Inventory") { + Entry( + name: "Detected version", + value: element! { + StyledText( + content: tool.get_resolved_version().to_string(), + style: Style::Hash + ) + }.into_any() + ) + #(global_config.versions.get(&tool.id).map(|version| { + element! { + Entry( + name: "Fallback version", + value: element! { + StyledText( + content: version.to_string(), + style: Style::Hash + ) + }.into_any() + ) } + })) + Entry( + name: "Store directory", + value: element! { + StyledText( + content: tool.get_inventory_dir().to_string_lossy(), + style: Style::Path + ) + }.into_any() ) - }), - Some(color::failure("None")), - ); - - p.entry_list( - "Binaries", - bins.into_iter().map(|bin| color::path(bin.path)), - Some(color::failure("None")), - ); - - let mut versions = tool - .inventory - .manifest - .installed_versions - .iter() - .collect::>(); - versions.sort(); - - p.entry_list( - "Installed versions", - versions - .iter() - .map(|version| color::hash(version.to_string())), - Some(color::failure("None")), - ); - - if !version_resolver.aliases.is_empty() { - p.entry_map( - "Aliases", - version_resolver - .aliases - .iter() - .map(|(k, v)| (color::hash(k), format_value(v.to_string()))) - .collect::>(), - None, - ); + Entry( + name: "Executable file", + value: element! { + StyledText( + content: exe_file.to_string_lossy(), + style: Style::Path + ) + }.into_any() + ) + #(exes_dir.map(|dir| { + element! { + Entry( + name: "Executables directory", + value: element! { + StyledText( + content: dir.to_string_lossy(), + style: Style::Path + ) + }.into_any() + ) + } + })) + #(globals_prefix.map(|prefix| { + element! { + Entry( + name: "Global packages prefix", + value: element! { + StyledText( + content: prefix, + style: Style::Property + ) + }.into_any() + ) + } + })) + #(globals_dir.map(|dir| { + element! { + Entry( + name: "Global packages directory", + value: element! { + StyledText( + content: dir.to_string_lossy(), + style: Style::Path + ) + }.into_any() + ) + } + })) + Entry( + name: "Shims", + no_children: shims.is_empty() + ) { + List { + #(shims.into_iter().map(|shim| { + element! { + ListItem { + StyledText( + content: shim.path.to_string_lossy(), + style: Style::Path + ) + } + } + })) + } + } + Entry( + name: "Binaries", + no_children: bins.is_empty() + ) { + List { + #(bins.into_iter().map(|bin| { + element! { + ListItem { + StyledText( + content: bin.path.to_string_lossy(), + style: Style::Path + ) + } + } + })) + } + } + Entry( + name: "Installed versions", + no_children: tool.installed_versions.is_empty() + ) { + VersionsMap( + default_version: global_config.versions.get(&tool.id), + inventory: &tool.inventory, + versions: tool.installed_versions.iter().collect::>(), + ) + } + Entry( + name: "Remote aliases", + no_children: tool.remote_aliases.is_empty() + ) { + AliasesMap( + aliases: tool.remote_aliases.iter().collect::>() + ) + } + } } - - Ok(()) })?; // CONFIG - if !tool_config.aliases.is_empty() - || !tool_config.env.is_empty() - || !tool_config.config.is_empty() - { - printer.named_section("Configuration", |p| { - p.entry_map( - "Aliases", - tool_config - .aliases - .iter() - .map(|(k, v)| (color::hash(k), format_value(v.to_string()))), - None, - ); - - p.entry_map( - "Environment variables", - tool_config.env.iter().map(|(k, v)| { - ( - color::property(k), - match v { - EnvVar::State(state) => { - if *state { - format_value("true") - } else { - color::muted("(removed)") - } - } - EnvVar::Value(value) => format_env_var(value), - }, + session.console.render(element! { + Container { + Section(title: "Configuration") { + Entry( + name: "Local aliases", + no_children: tool.local_aliases.is_empty() + ) { + AliasesMap( + aliases: tool.local_aliases.iter().collect::>() ) - }), - None, - ); - - p.entry_map( - "Settings", - tool_config - .config - .iter() - .map(|(k, v)| (k, format_value(v.to_string()))), - None, - ); - - Ok(()) - })?; - } - - printer.flush(); + } + Entry( + name: "Environment variables", + no_children: tool.config.env.is_empty() + ) { + Map { + #(tool.config.env.iter().map(|(key, value)| { + element! { + MapItem( + name: element! { + StyledText( + content: key, + style: Style::Property + ) + }.into_any(), + value: element! { + EnvVar(value: value) + }.into_any() + ) + } + })) + } + } + Entry( + name: "Settings", + no_children: tool.config.config.is_empty() + ) { + Map { + #(tool.config.config.iter().map(|(key, value)| { + element! { + MapItem( + name: element! { + StyledText( + content: key, + style: Style::Property + ) + }.into_any(), + value: element! { + StyledText( + content: value.to_string(), + style: Style::MutedLight + ) + }.into_any() + ) + } + })) + } + } + } + } + })?; Ok(None) } diff --git a/crates/cli/src/commands/plugin/list.rs b/crates/cli/src/commands/plugin/list.rs index d18f5b9a3..6da382090 100644 --- a/crates/cli/src/commands/plugin/list.rs +++ b/crates/cli/src/commands/plugin/list.rs @@ -1,20 +1,20 @@ -use crate::printer::{format_value, Printer}; -use crate::session::ProtoSession; -use chrono::{DateTime, NaiveDateTime}; +use crate::components::{AliasesMap, Locator, VersionsMap}; +use crate::session::{LoadToolOptions, ProtoSession}; use clap::Args; -use proto_core::{Id, PluginLocator, ProtoToolConfig, ToolManifest, UnresolvedVersionSpec}; +use iocraft::prelude::element; +use proto_core::{ConfigMode, Id, PluginLocator, ProtoToolConfig, ToolManifest}; use rustc_hash::{FxHashMap, FxHashSet}; use serde::Serialize; use starbase::AppResult; -use starbase_styles::color; +use starbase_console::ui::*; use starbase_utils::json; -use tokio::sync::Mutex; +use std::collections::BTreeMap; #[derive(Serialize)] -pub struct PluginItem<'a> { +struct PluginItem { name: String, locator: Option, - config: Option<&'a ProtoToolConfig>, + config: ProtoToolConfig, manifest: ToolManifest, } @@ -35,10 +35,15 @@ pub struct ListPluginsArgs { #[tracing::instrument(skip_all)] pub async fn list(session: ProtoSession, args: ListPluginsArgs) -> AppResult { - let mut config = session.env.load_config()?.to_owned(); + let global_config = session.load_config_with_mode(ConfigMode::Global)?; let mut tools = session - .load_tools_with_filters(FxHashSet::from_iter(&args.ids)) + .load_tools_with_options(LoadToolOptions { + ids: FxHashSet::from_iter(args.ids.clone()), + inherit_local: true, + inherit_remote: true, + ..Default::default() + }) .await?; tools.sort_by(|a, d| a.id.cmp(&d.id)); @@ -47,126 +52,94 @@ pub async fn list(session: ProtoSession, args: ListPluginsArgs) -> AppResult { if args.json { let items = tools .into_iter() - .map(|t| { - let tool_config = config.tools.get(&t.id); - let name = t.get_name().to_owned(); - + .map(|tool| { ( - t.id, + tool.id.clone(), PluginItem { - name, - locator: t.locator, - config: tool_config, - manifest: t.inventory.manifest, + name: tool.get_name().to_owned(), + locator: tool.locator.clone(), + config: tool.config.clone(), + manifest: tool.inventory.manifest.clone(), }, ) }) .collect::>(); - println!("{}", json::format(&items, true)?); + session + .console + .out + .write_line(json::format(&items, true)?)?; return Ok(None); } - let printer = Mutex::new(Printer::new()); - let latest_version = UnresolvedVersionSpec::default(); - for tool in tools { - let tool_config = config.tools.remove(&tool.id).unwrap_or_default(); - - let mut versions = tool.load_version_resolver(&latest_version).await?; - versions.aliases.extend(tool_config.aliases); - - let mut printer = printer.lock().await; - - printer.line(); - printer.header(&tool.id, &tool.metadata.name); - - printer.section(|p| { - p.entry("Store", color::path(tool.get_inventory_dir())); - - if let Some(locator) = &tool.locator { - p.locator(locator); - } - - // --aliases - if args.aliases { - p.entry_map( - "Aliases", - versions - .aliases - .iter() - .map(|(k, v)| (color::hash(k), format_value(v.to_string()))) - .collect::>(), - None, - ); - } - - // --versions - if args.versions { - let mut versions = tool - .inventory - .manifest - .installed_versions - .iter() - .collect::>(); - versions.sort(); - - p.entry_map( - "Versions", - versions - .iter() - .map(|version| { - let mut comments = vec![]; - let mut is_default = false; - - if let Some(meta) = &tool.inventory.manifest.versions.get(version) { - if let Some(at) = create_datetime(meta.installed_at) { - comments.push(format!("installed {}", at.format("%x"))); - } - - if let Ok(Some(last_used)) = - tool.inventory.create_product(version).load_used_at() - { - if let Some(at) = create_datetime(last_used) { - comments.push(format!("last used {}", at.format("%x"))); - } - } + let mut aliases = BTreeMap::default(); + aliases.extend(&tool.remote_aliases); + aliases.extend(&tool.local_aliases); + + session.console.render(element! { + Container { + Section(title: &tool.metadata.name) { + Entry( + name: "ID", + value: element! { + StyledText( + content: tool.id.to_string(), + style: Style::Id + ) + }.into_any() + ) + + #(tool.locator.as_ref().map(|locator| { + element! { + Locator(value: locator) + } + })) + + Entry( + name: "Store directory", + value: element! { + StyledText( + content: tool.get_inventory_dir().to_string_lossy(), + style: Style::Path + ) + }.into_any() + ) + + #(if args.aliases { + Some(element! { + Entry( + name: "Aliases", + no_children: aliases.is_empty() + ) { + AliasesMap(aliases) } - - if config - .versions - .get(&tool.id) - .is_some_and(|dv| *dv == version.to_unresolved_spec()) - { - comments.push("default version".into()); - is_default = true; + }) + } else { + None + }) + + #(if args.versions { + Some(element! { + Entry( + name: "Versions", + no_children: tool.installed_versions.is_empty() + ) { + VersionsMap( + default_version: global_config.versions.get(&tool.id), + inventory: &tool.inventory, + versions: tool.installed_versions.iter().collect::>(), + ) } - - ( - if is_default { - color::invalid(version.to_string()) - } else { - color::hash(version.to_string()) - }, - format_value(comments.join(", ")), - ) }) - .collect::>(), - None, - ); + } else { + None + }) + } } - - Ok(()) })?; } - printer.lock().await.flush(); - Ok(None) } - -fn create_datetime(millis: u128) -> Option { - DateTime::from_timestamp((millis / 1000) as i64, ((millis % 1000) * 1_000_000) as u32) - .map(|dt| dt.naive_local()) -} diff --git a/crates/cli/src/commands/plugin/remove.rs b/crates/cli/src/commands/plugin/remove.rs index d0520d839..cd55179a5 100644 --- a/crates/cli/src/commands/plugin/remove.rs +++ b/crates/cli/src/commands/plugin/remove.rs @@ -1,32 +1,23 @@ use crate::error::ProtoCliError; -use crate::helpers::{map_pin_type, PinOption}; use crate::session::ProtoSession; use clap::Args; -use proto_core::{Id, ProtoConfig, PROTO_CONFIG_NAME}; +use iocraft::prelude::element; +use proto_core::{Id, PinLocation, ProtoConfig, PROTO_CONFIG_NAME}; use starbase::AppResult; -use starbase_styles::color; +use starbase_console::ui::*; #[derive(Args, Clone, Debug)] pub struct RemovePluginArgs { #[arg(required = true, help = "ID of plugin")] id: Id, - #[arg( - long, - group = "pin", - help = "Remove from the global ~/.proto/.prototools" - )] - global: bool, - - #[arg(long, group = "pin", help = "Location of .prototools to remove from")] - from: Option, + #[arg(long, default_value_t, help = "Location of .prototools to remove from")] + from: PinLocation, } #[tracing::instrument(skip_all)] pub async fn remove(session: ProtoSession, args: RemovePluginArgs) -> AppResult { - let config_dir = session - .env - .get_config_dir(map_pin_type(args.global, args.from)); + let config_dir = session.env.get_config_dir(args.from); let config_path = config_dir.join(PROTO_CONFIG_NAME); if !config_path.exists() { @@ -37,13 +28,27 @@ pub async fn remove(session: ProtoSession, args: RemovePluginArgs) -> AppResult if let Some(plugins) = &mut config.plugins { plugins.remove(&args.id); } + + if let Some(tools) = &mut config.tools { + tools.remove(&args.id); + } + + if let Some(versions) = &mut config.versions { + versions.remove(&args.id); + } })?; - println!( - "Removed plugin {} from config {}", - color::id(&args.id), - color::path(config_path) - ); + session.console.render(element! { + Notice(variant: Variant::Success) { + StyledText( + content: format!( + "Removed {} plugin from config {}", + args.id, + config_path.display(), + ), + ) + } + })?; Ok(None) } diff --git a/crates/cli/src/commands/plugin/search.rs b/crates/cli/src/commands/plugin/search.rs index 3ebc78696..db47baf7e 100644 --- a/crates/cli/src/commands/plugin/search.rs +++ b/crates/cli/src/commands/plugin/search.rs @@ -1,11 +1,10 @@ -use crate::printer::Printer; use crate::session::ProtoSession; use clap::Args; -use comfy_table::presets::NOTHING; -use comfy_table::{Attribute, Cell, Color, ContentArrangement, Table}; +use iocraft::prelude::{element, Box, FlexDirection, Size, Text}; use proto_core::registry::{PluginAuthor, PluginFormat}; +use proto_core::PluginLocator; use starbase::AppResult; -use starbase_styles::color::{self, Style}; +use starbase_console::ui::*; use starbase_utils::json; #[derive(Args, Clone, Debug)] @@ -32,74 +31,101 @@ pub async fn search(session: ProtoSession, args: SearchPluginArgs) -> AppResult }) .collect::>(); - // Dump all the data as JSON if args.json { - println!("{}", json::format(&queried_plugins, true)?); + session + .console + .out + .write_line(json::format(&queried_plugins, true)?)?; return Ok(None); } if queried_plugins.is_empty() { - eprintln!("No plugins available for query \"{query}\""); + session.console.render(element! { + Notice(title: "No results".to_owned(), variant: Variant::Caution) { + StyledText( + content: format!("Please try again, there are no plugins found in the registry for the query {query}"), + ) + } + })?; return Ok(Some(1)); } - // Print all the data in a table - let mut printer = Printer::new(); - - printer.named_section("Plugins", |p| { - p.write(format!("Available for query: {}\n", color::property(query))); - - let mut table = Table::new(); - table.load_preset(NOTHING); - table.set_content_arrangement(ContentArrangement::Dynamic); - - table.set_header(vec![ - Cell::new("Plugin").add_attribute(Attribute::Bold), - Cell::new("Author").add_attribute(Attribute::Bold), - Cell::new("Format").add_attribute(Attribute::Bold), - Cell::new("Description").add_attribute(Attribute::Bold), - Cell::new("Locator").add_attribute(Attribute::Bold), - ]); - - for plugin in queried_plugins { - table.add_row(vec![ - Cell::new(&plugin.name).fg(Color::AnsiValue(Style::Id.color() as u8)), - Cell::new(match &plugin.author { - PluginAuthor::String(name) => name, - PluginAuthor::Object(author) => &author.name, - }), - Cell::new(match plugin.format { - PluginFormat::Json => "JSON", - PluginFormat::Toml => "TOML", - PluginFormat::Wasm => "WASM", - PluginFormat::Yaml => "YAML", - }), - Cell::new(&plugin.description), - Cell::new(plugin.locator.to_string()) - .fg(Color::AnsiValue(Style::Path.color() as u8)), - ]); + session.console.render(element! { + Container { + Box(padding_top: 1, padding_left: 1, flex_direction: FlexDirection::Column) { + StyledText( + content: format!("Search results for: "), + ) + StyledText( + content: "Learn more about plugins: https://moonrepo.dev/docs/proto/plugins" + ) + } + Table( + headers: vec![ + TableHeader::new("Plugin", Size::Percent(10.0)), + TableHeader::new("Author", Size::Percent(8.0)), + TableHeader::new("Format", Size::Percent(5.0)), + TableHeader::new("Description", Size::Percent(20.0)), + TableHeader::new("Locator", Size::Percent(57.0)), + ] + ) { + #(queried_plugins.into_iter().enumerate().map(|(i, plugin)| { + element! { + TableRow(row: i as i32) { + TableCol(col: 0) { + StyledText( + content: &plugin.name, + style: Style::Id + ) + } + TableCol(col: 1) { + Text( + content: match &plugin.author { + PluginAuthor::String(name) => name, + PluginAuthor::Object(author) => &author.name, + } + ) + } + TableCol(col: 2) { + Text( + content: match plugin.format { + PluginFormat::Json => "JSON", + PluginFormat::Toml => "TOML", + PluginFormat::Wasm => "WASM", + PluginFormat::Yaml => "YAML", + } + ) + } + TableCol(col: 3) { + Text(content: &plugin.description) + } + TableCol(col: 4) { + StyledText( + content: plugin.locator.to_string(), + style: match plugin.locator { + PluginLocator::File(_) => Style::Path, + PluginLocator::Url(_) => Style::Url, + _ => Style::File, + } + ) + } + } + } + })) + } + Box(padding_bottom: 1, padding_left: 1, flex_direction: FlexDirection::Row) { + StyledText( + content: "Find a plugin above that you want to use? Enable it with: ", + ) + StyledText( + content: "proto plugin add [id] [locator]", + style: Style::Shell + ) + } } - - p.write(format!("{table}")); - - Ok(()) })?; - printer.named_section("Usage", |p| { - p.write("Find a plugin above that you want to use? Enable it with the command below."); - p.write(format!( - "Learn more about plugins: {}", - color::url("https://moonrepo.dev/docs/proto/plugins") - )); - p.line(); - p.write(color::shell(" proto plugin add ")); - - Ok(()) - })?; - - printer.flush(); - Ok(None) } diff --git a/crates/cli/src/commands/regen.rs b/crates/cli/src/commands/regen.rs index b66fb8cfe..9b33efa12 100644 --- a/crates/cli/src/commands/regen.rs +++ b/crates/cli/src/commands/regen.rs @@ -14,11 +14,11 @@ pub struct RegenArgs { pub async fn regen(session: ProtoSession, args: RegenArgs) -> AppResult { let store = &session.env.store; - if args.bin { - println!("Regenerating bins and shims..."); + session.console.out.write_line(if args.bin { + "Regenerating bins and shims..." } else { - println!("Regenerating shims..."); - } + "Regenerating shims..." + })?; // Delete all shims debug!("Removing old shims"); @@ -67,7 +67,7 @@ pub async fn regen(session: ProtoSession, args: RegenArgs) -> AppResult { } } - println!("Regeneration complete!"); + session.console.out.write_line("Regeneration complete!")?; Ok(None) } diff --git a/crates/cli/src/commands/run.rs b/crates/cli/src/commands/run.rs index 7a3ea33ef..bfb9a095c 100644 --- a/crates/cli/src/commands/run.rs +++ b/crates/cli/src/commands/run.rs @@ -180,11 +180,11 @@ pub async fn run(session: ProtoSession, args: RunArgs) -> AppResult { } // Install the tool - println!( + session.console.out.write_line(format!( "Auto-install is enabled, attempting to install {} {}", tool.get_name(), resolved_version, - ); + ))?; let install_args = InstallArgs { id: Some(tool.id.clone()), @@ -196,11 +196,11 @@ pub async fn run(session: ProtoSession, args: RunArgs) -> AppResult { do_install(&mut tool, install_args, &pb).await?; - println!( + session.console.out.write_line(format!( "{} {} has been installed, continuing execution...", tool.get_name(), resolved_version, - ); + ))?; } // Determine the binary path to execute diff --git a/crates/cli/src/commands/status.rs b/crates/cli/src/commands/status.rs index 3d2367b6a..1d7ff57cd 100644 --- a/crates/cli/src/commands/status.rs +++ b/crates/cli/src/commands/status.rs @@ -1,14 +1,13 @@ use crate::error::ProtoCliError; use crate::session::ProtoSession; use clap::Args; -use comfy_table::presets::NOTHING; -use comfy_table::{Attribute, Cell, Color, ContentArrangement, Table}; +use iocraft::prelude::{element, Size}; use miette::IntoDiagnostic; use proto_core::{Id, UnresolvedVersionSpec, VersionSpec, PROTO_PLUGIN_KEY}; use rustc_hash::FxHashSet; use serde::Serialize; use starbase::AppResult; -use starbase_styles::color::Style; +use starbase_console::ui::*; use starbase_utils::json; use std::collections::BTreeMap; use std::path::PathBuf; @@ -16,14 +15,8 @@ use std::sync::Arc; use tokio::task::JoinSet; use tracing::debug; -#[derive(Args, Clone, Debug)] -pub struct StatusArgs { - #[arg(long, help = "Print the active tools in JSON format")] - json: bool, -} - #[derive(Debug, Default, Serialize)] -pub struct StatusItem { +struct StatusItem { is_installed: bool, config_source: PathBuf, config_version: UnresolvedVersionSpec, @@ -31,6 +24,12 @@ pub struct StatusItem { product_dir: Option, } +#[derive(Args, Clone, Debug)] +pub struct StatusArgs { + #[arg(long, help = "Print the active tools in JSON format")] + json: bool, +} + fn find_versions_in_configs( session: &ProtoSession, items: &mut BTreeMap, @@ -79,7 +78,7 @@ async fn find_versions_from_ecosystem( set.spawn(async move { if let Ok(Some(detected)) = tool.detect_version_from(&env.cwd).await { - return Some((tool.id, detected.0, detected.1)); + return Some((tool.id.clone(), detected.0, detected.1)); } None @@ -132,7 +131,7 @@ async fn resolve_item_versions( } } - (tool.id, resolved_version, product_dir) + (tool.id.clone(), resolved_version, product_dir) }); } @@ -171,44 +170,87 @@ pub async fn status(session: ProtoSession, args: StatusArgs) -> AppResult { // Dump all the data as JSON if args.json { - println!("{}", json::format(&items, true)?); + session + .console + .out + .write_line(json::format(&items, true)?)?; return Ok(None); } // Print all the data in a table - let mut table = Table::new(); - table.load_preset(NOTHING); - table.set_content_arrangement(ContentArrangement::Dynamic); - - table.set_header(vec![ - Cell::new("Tool").add_attribute(Attribute::Bold), - Cell::new("Configured").add_attribute(Attribute::Bold), - Cell::new("Resolved").add_attribute(Attribute::Bold), - Cell::new("Installed").add_attribute(Attribute::Bold), - Cell::new("Config").add_attribute(Attribute::Bold), - ]); - - for (id, item) in items { - table.add_row(vec![ - Cell::new(id).fg(Color::AnsiValue(Style::Id.color() as u8)), - Cell::new(&item.config_version), - if let Some(version) = item.resolved_version { - Cell::new(version.to_string()).fg(Color::AnsiValue(Style::Success.color() as u8)) - } else { - Cell::new("Invalid").fg(Color::AnsiValue(Style::MutedLight.color() as u8)) - }, - if let Some(dir) = item.product_dir { - Cell::new(dir.to_string_lossy()).fg(Color::AnsiValue(Style::Path.color() as u8)) - } else { - Cell::new("No").fg(Color::AnsiValue(Style::MutedLight.color() as u8)) - }, - Cell::new(item.config_source.to_string_lossy()) - .fg(Color::AnsiValue(Style::Path.color() as u8)), - ]); - } - - println!("\n{table}\n"); + session.console.render(element! { + Container { + Table( + headers: vec![ + TableHeader::new("Tool", Size::Percent(10.0)), + TableHeader::new("Configured", Size::Percent(10.0)), + TableHeader::new("Resolved", Size::Percent(10.0)), + TableHeader::new("Installed", Size::Percent(35.0)), + TableHeader::new("Config", Size::Percent(35.0)), + ] + ) { + #(items.into_iter().enumerate().map(|(i, (id, item))| { + element! { + TableRow(row: i as i32) { + TableCol(col: 0) { + StyledText( + content: id.to_string(), + style: Style::Id + ) + } + TableCol(col: 1) { + StyledText( + content: item.config_version.to_string(), + style: Style::Hash + ) + } + TableCol(col: 2) { + #(if let Some(version) = item.resolved_version { + element! { + StyledText( + content: version.to_string(), + style: Style::Shell + ) + } + } else { + element! { + StyledText( + content: "Invalid", + style: Style::MutedLight + ) + } + }) + } + TableCol(col: 3) { + #(if let Some(dir) = item.product_dir { + element! { + StyledText( + content: dir.to_string_lossy(), + style: Style::Path + ) + } + } else { + element! { + StyledText( + content: "No", + style: Style::MutedLight + ) + } + }) + } + TableCol(col: 4) { + StyledText( + content: item.config_source.to_string_lossy(), + style: Style::Path + ) + } + } + } + })) + } + } + })?; Ok(None) } diff --git a/crates/cli/src/commands/unalias.rs b/crates/cli/src/commands/unalias.rs index 068501166..9387ee833 100644 --- a/crates/cli/src/commands/unalias.rs +++ b/crates/cli/src/commands/unalias.rs @@ -1,9 +1,10 @@ use crate::helpers::{map_pin_type, PinOption}; use crate::session::ProtoSession; use clap::Args; +use iocraft::element; use proto_core::{Id, ProtoConfig}; use starbase::AppResult; -use starbase_styles::color; +use starbase_console::ui::*; #[derive(Args, Clone, Debug)] pub struct UnaliasArgs { @@ -13,14 +14,7 @@ pub struct UnaliasArgs { #[arg(required = true, help = "Alias name")] alias: String, - #[arg( - long, - group = "pin", - help = "Remove from the global ~/.proto/.prototools" - )] - global: bool, - - #[arg(long, group = "pin", help = "Location of .prototools to remove from")] + #[arg(long, help = "Location of .prototools to remove from")] from: Option, } @@ -30,8 +24,7 @@ pub async fn unalias(session: ProtoSession, args: UnaliasArgs) -> AppResult { let mut value = None; let config_path = ProtoConfig::update( - tool.proto - .get_config_dir(map_pin_type(args.global, args.from)), + tool.proto.get_config_dir(map_pin_type(false, args.from)), |config| { if let Some(tool_configs) = &mut config.tools { if let Some(tool_config) = tool_configs.get_mut(&tool.id) { @@ -44,21 +37,35 @@ pub async fn unalias(session: ProtoSession, args: UnaliasArgs) -> AppResult { )?; let Some(value) = value else { - eprintln!( - "Alias {} not found in config {}", - color::id(&args.alias), - color::path(config_path) - ); + session.console.render(element! { + Notice(variant: Variant::Caution) { + StyledText( + content: format!( + "Alias for {} not found in config {}", + args.alias, + args.id, + config_path.display() + ), + ) + } + })?; return Ok(Some(1)); }; - println!( - "Removed alias {} ({}) from config {}", - color::id(&args.alias), - color::muted_light(value.to_string()), - color::path(config_path) - ); + session.console.render(element! { + Notice(variant: Variant::Success) { + StyledText( + content: format!( + "Removed {} alias ({}) from config {}", + args.id, + args.alias, + value.to_string(), + config_path.display() + ), + ) + } + })?; Ok(None) } diff --git a/crates/cli/src/commands/uninstall.rs b/crates/cli/src/commands/uninstall.rs index 0cdab9abf..048cb21b9 100644 --- a/crates/cli/src/commands/uninstall.rs +++ b/crates/cli/src/commands/uninstall.rs @@ -3,8 +3,10 @@ use crate::helpers::create_progress_spinner; use crate::session::ProtoSession; use crate::telemetry::{track_usage, Metric}; use clap::Args; +use iocraft::element; use proto_core::{Id, ProtoConfig, Tool, UnresolvedVersionSpec}; use starbase::AppResult; +use starbase_console::ui::*; use tracing::debug; #[derive(Args, Clone, Debug)] @@ -63,11 +65,17 @@ pub async fn uninstall(session: ProtoSession, args: UninstallArgs) -> AppResult let mut tool = session.load_tool(&args.id).await?; if !tool.is_setup(spec).await? { - eprintln!( - "{} {} has not been installed locally", - tool.get_name(), - tool.get_resolved_version(), - ); + session.console.render(element! { + Notice(variant: Variant::Caution) { + StyledText( + content: format!( + "{} {} has not been installed locally", + tool.get_name(), + tool.get_resolved_version(), + ), + ) + } + })?; return Ok(Some(1)); } @@ -93,11 +101,17 @@ pub async fn uninstall(session: ProtoSession, args: UninstallArgs) -> AppResult // Track usage metrics track_uninstall(&tool, false).await?; - println!( - "{} {} has been uninstalled!", - tool.get_name(), - tool.get_resolved_version(), - ); + session.console.render(element! { + Notice(variant: Variant::Success) { + StyledText( + content: format!( + "{} {} has been uninstalled!", + tool.get_name(), + tool.get_resolved_version(), + ), + ) + } + })?; Ok(None) } diff --git a/crates/cli/src/commands/unpin.rs b/crates/cli/src/commands/unpin.rs index 68cc389fc..1e1786d3d 100644 --- a/crates/cli/src/commands/unpin.rs +++ b/crates/cli/src/commands/unpin.rs @@ -1,23 +1,17 @@ use crate::helpers::{map_pin_type, PinOption}; use crate::session::ProtoSession; use clap::Args; +use iocraft::prelude::element; use proto_core::{Id, ProtoConfig}; use starbase::AppResult; -use starbase_styles::color; +use starbase_console::ui::*; #[derive(Args, Clone, Debug)] pub struct UnpinArgs { #[arg(required = true, help = "ID of tool")] pub id: Id, - #[arg( - long, - group = "pin", - help = "Unpin from the global ~/.proto/.prototools" - )] - pub global: bool, - - #[arg(long, group = "pin", help = "Location of .prototools to unpin from")] + #[arg(long, help = "Location of .prototools to unpin from")] pub from: Option, } @@ -27,8 +21,7 @@ pub async fn unpin(session: ProtoSession, args: UnpinArgs) -> AppResult { let mut value = None; let config_path = ProtoConfig::update( - tool.proto - .get_config_dir(map_pin_type(args.global, args.from)), + tool.proto.get_config_dir(map_pin_type(false, args.from)), |config| { if let Some(versions) = &mut config.versions { value = versions.remove(&tool.id); @@ -42,16 +35,33 @@ pub async fn unpin(session: ProtoSession, args: UnpinArgs) -> AppResult { )?; let Some(value) = value else { - eprintln!("No version pinned in config {}", color::path(config_path)); + session.console.render(element! { + Notice(variant: Variant::Caution) { + StyledText( + content: format!( + "No version pinned for {} in config {}", + args.id, + config_path.display() + ), + ) + } + })?; return Ok(Some(1)); }; - println!( - "Removed version {} from config {}", - color::hash(value.to_string()), - color::path(config_path) - ); + session.console.render(element! { + Notice(variant: Variant::Success) { + StyledText( + content: format!( + "Removed {} version {} from config {}", + args.id, + value.to_string(), + config_path.display() + ), + ) + } + })?; Ok(None) } diff --git a/crates/cli/src/commands/upgrade.rs b/crates/cli/src/commands/upgrade.rs index ae3ad5002..0be183026 100644 --- a/crates/cli/src/commands/upgrade.rs +++ b/crates/cli/src/commands/upgrade.rs @@ -4,11 +4,13 @@ use crate::helpers::{create_progress_bar, fetch_latest_version}; use crate::session::ProtoSession; use crate::telemetry::{track_usage, Metric}; use clap::Args; +use iocraft::prelude::element; use proto_core::{is_offline, Id, SemVer, UnresolvedVersionSpec}; use proto_installer::*; use semver::Version; use serde::Serialize; use starbase::AppResult; +use starbase_console::ui::*; use starbase_styles::color; use starbase_utils::json; use std::env; @@ -44,15 +46,14 @@ pub async fn upgrade(session: ProtoSession, args: UpgradeArgs) -> AppResult { return Err(ProtoCliError::CannotUpgradeProtoRunning { pid: pid.as_u32() }.into()); } - let latest = fetch_latest_version().await?; + let latest_version = fetch_latest_version().await?; + let latest = latest_version.to_string(); - let current_version = Version::parse(&session.cli_version).unwrap(); + let current_version = session.cli_version.clone(); let current = current_version.to_string(); let has_explicit_target = args.target.is_some(); - let target_version = args - .target - .unwrap_or_else(|| Version::parse(&latest).unwrap()); + let target_version = args.target.unwrap_or(latest_version); let target = target_version.to_string(); debug!( @@ -66,55 +67,51 @@ pub async fn upgrade(session: ProtoSession, args: UpgradeArgs) -> AppResult { // Output in JSON so other tools can utilize it if args.json { - println!( - "{}", - json::format( - &UpgradeInfo { - available: !not_available, - current_version: current, - latest_version: latest, - target_version: target, - }, - true - )? - ); + session.console.out.write_line(json::format( + &UpgradeInfo { + available: !not_available, + current_version: current, + latest_version: latest, + target_version: target, + }, + true, + )?)?; return Ok(None); } // Only compare versions instead of upgrading if args.check { - let target_chain = format!( - "{}{}{}", - color::hash(¤t), - color::muted_light(" -> "), - color::hash(&target), - ); - - if target_version == current_version { - println!( - "You're already on version {} of proto!", - color::hash(¤t) - ); + let target_chain = + format!("{current} {target}"); + + let content = if target_version == current_version { + format!("You're already on version {current} of proto!") } else if has_explicit_target { - println!( - "An explicit version of proto will be used: {}", - target_chain - ); + format!("An explicit version of proto will be used: {target_chain}") } else if target_version > current_version { - println!("A newer version of proto is available: {}", target_chain); - } else if target_version < current_version { - println!("An older version of proto is available: {}", target_chain); - } + format!("A newer version of proto is available: {target_chain}") + } else { + format!("An older version of proto is available: {target_chain}") + }; + + session.console.render(element! { + Notice(variant: Variant::Info) { + StyledText(content) + } + })?; return Ok(None); } if not_available { - println!( - "You're already on version {} of proto!", - color::hash(¤t) - ); + session.console.render(element! { + Notice(variant: Variant::Info) { + StyledText( + content: format!("You're already on version {current} of proto!") + ) + } + })?; return Ok(None); } @@ -160,11 +157,17 @@ pub async fn upgrade(session: ProtoSession, args: UpgradeArgs) -> AppResult { .await?; if upgraded { - if target_version > current_version { - println!("Upgraded proto to {}!", color::hash(&target)); - } else { - println!("Downgraded proto to {}!", color::hash(&target)); - } + session.console.render(element! { + Notice(variant: Variant::Success) { + StyledText( + content: if target_version > current_version { + format!("Upgraded proto to {target}!") + } else { + format!("Downgraded proto to {target}!") + } + ) + } + })?; return Ok(None); } diff --git a/crates/cli/src/components/aliases_map.rs b/crates/cli/src/components/aliases_map.rs new file mode 100644 index 000000000..547a7b144 --- /dev/null +++ b/crates/cli/src/components/aliases_map.rs @@ -0,0 +1,35 @@ +use iocraft::prelude::*; +use proto_core::UnresolvedVersionSpec; +use starbase_console::ui::{Map, MapItem, Style, StyledText}; +use std::collections::BTreeMap; + +#[derive(Default, Props)] +pub struct AliasesMapProps<'a> { + pub aliases: BTreeMap<&'a String, &'a UnresolvedVersionSpec>, +} + +#[component] +pub fn AliasesMap<'a>(props: &AliasesMapProps<'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::Hash + ) + }.into_any() + ) + } + })) + } + } +} diff --git a/crates/cli/src/components/env_var.rs b/crates/cli/src/components/env_var.rs new file mode 100644 index 000000000..5c3529b36 --- /dev/null +++ b/crates/cli/src/components/env_var.rs @@ -0,0 +1,44 @@ +use super::is_path_like; +use iocraft::prelude::*; +use proto_core::EnvVar as EnvVarConfig; +use starbase_console::ui::{Style, StyledText}; + +#[derive(Default, Props)] +pub struct EnvVarProps<'a> { + pub value: Option<&'a EnvVarConfig>, +} + +#[component] +pub fn EnvVar<'a>(props: &EnvVarProps<'a>) -> impl Into> { + match props.value.as_ref().expect("`value` prop is required!") { + EnvVarConfig::State(state) => { + if *state { + element! { + StyledText( + content: "true", + style: Style::Symbol + ) + } + } else { + element! { + StyledText( + content: "(removed)", + style: Style::Caution + ) + } + } + } + EnvVarConfig::Value(value) => { + element! { + StyledText( + content: value, + style: if is_path_like(value) { + Style::Path + } else { + Style::MutedLight + } + ) + } + } + } +} diff --git a/crates/cli/src/components/issues_list.rs b/crates/cli/src/components/issues_list.rs new file mode 100644 index 000000000..0645ba1ad --- /dev/null +++ b/crates/cli/src/components/issues_list.rs @@ -0,0 +1,52 @@ +use iocraft::prelude::*; +use serde::Serialize; +use starbase_console::ui::{List, ListItem, Style, StyledText}; + +#[derive(Debug, Serialize)] +pub struct Issue { + pub issue: String, + pub resolution: Option, + pub comment: Option, +} + +#[derive(Default, Props)] +pub struct IssuesListProps { + pub issues: Vec, +} + +#[component] +pub fn IssuesList<'a>(props: &IssuesListProps) -> impl Into> { + element! { + List { + #(props.issues.iter().map(|issue| { + element! { + ListItem { + Box { + StyledText(content: "Issue: ", style: Style::MutedLight) + StyledText(content: &issue.issue) + } + #(issue.resolution.as_ref().map(|resolution| { + element! { + Box { + StyledText(content: "Resolution: ", style: Style::MutedLight) + StyledText(content: resolution) + } + } + })) + #(issue.comment.as_ref().map(|comment| { + element! { + Box { + StyledText(content: "Comment: ", style: Style::MutedLight) + StyledText(content: comment) + } + } + })) + + // Gap between items + Box(padding_bottom: 1) + } + } + })) + } + } +} diff --git a/crates/cli/src/components/locator.rs b/crates/cli/src/components/locator.rs new file mode 100644 index 000000000..feca72b69 --- /dev/null +++ b/crates/cli/src/components/locator.rs @@ -0,0 +1,79 @@ +use iocraft::prelude::*; +use proto_core::PluginLocator; +use starbase_console::ui::{Entry, Style, StyledText}; + +#[derive(Default, Props)] +pub struct LocatorProps<'a> { + pub value: Option<&'a PluginLocator>, +} + +#[component] +pub fn Locator<'a>(props: &LocatorProps<'a>) -> impl Into> { + match props.value.as_ref().expect("`value` prop is required!") { + PluginLocator::File(file) => element! { + Entry( + name: "Source file", + value: element! { + StyledText( + content: file.get_resolved_path().to_string_lossy(), + style: Style::Path + ) + }.into_any() + ) + } + .into_any(), + PluginLocator::GitHub(github) => element! { + Entry( + name: "Source", + content: "GitHub".to_owned() + ) { + Box(flex_direction: FlexDirection::Column) { + Entry( + name: "Repository", + value: element! { + StyledText( + content: &github.repo_slug, + style: Style::Id + ) + }.into_any() + ) + #(github.project_name.as_ref().map(|name| { + element! { + Entry( + name: "Project", + value: element! { + StyledText( + content: name, + style: Style::Label + ) + }.into_any() + ) + } + })) + Entry( + name: "Tag", + value: element! { + StyledText( + content: github.tag.as_deref().unwrap_or("latest"), + style: Style::Hash + ) + }.into_any() + ) + } + } + } + .into_any(), + PluginLocator::Url(url) => element! { + Entry( + name: "Source URL", + value: element! { + StyledText( + content: &url.url, + style: Style::Url + ) + }.into_any() + ) + } + .into_any(), + } +} diff --git a/crates/cli/src/components/mod.rs b/crates/cli/src/components/mod.rs new file mode 100644 index 000000000..3847c9b05 --- /dev/null +++ b/crates/cli/src/components/mod.rs @@ -0,0 +1,16 @@ +mod aliases_map; +mod env_var; +mod issues_list; +mod locator; +mod versions_map; + +pub use aliases_map::*; +pub use env_var::*; +pub use issues_list::*; +pub use locator::*; +pub use versions_map::*; + +pub fn is_path_like(value: impl AsRef) -> bool { + let value = value.as_ref(); + value.contains('/') || value.contains("\\") +} diff --git a/crates/cli/src/components/versions_map.rs b/crates/cli/src/components/versions_map.rs new file mode 100644 index 000000000..e14943632 --- /dev/null +++ b/crates/cli/src/components/versions_map.rs @@ -0,0 +1,70 @@ +use chrono::{DateTime, NaiveDateTime}; +use iocraft::prelude::*; +use proto_core::layout::Inventory; +use proto_core::{UnresolvedVersionSpec, VersionSpec}; +use starbase_console::ui::{Map, MapItem, Style, StyledText}; + +#[derive(Default, Props)] +pub struct VersionsMapProps<'a> { + pub default_version: Option<&'a UnresolvedVersionSpec>, + pub inventory: Option<&'a Inventory>, + pub versions: Vec<&'a VersionSpec>, +} + +#[component] +pub fn VersionsMap<'a>(props: &VersionsMapProps<'a>) -> impl Into> { + let inventory = props.inventory.expect("`inventory` prop is required!"); + + element! { + Map { + #(props.versions.iter().map(|version| { + let mut comments = vec![]; + let mut is_default = false; + + if let Some(meta) = inventory.manifest.versions.get(version) { + if let Some(at) = create_datetime(meta.installed_at) { + comments.push(format!("installed {}", at.format("%x"))); + } + + if let Ok(Some(last_used)) = inventory.create_product(version).load_used_at() { + if let Some(at) = create_datetime(last_used) { + comments.push(format!("last used {}", at.format("%x"))); + } + } + } + + if props.default_version.is_some_and(|dv| dv == &version.to_unresolved_spec()) { + comments.push("fallback version".into()); + is_default = true; + } + + element! { + MapItem( + name: element! { + StyledText( + content: version.to_string(), + style: if is_default { + Style::Shell + } else { + Style::Hash + } + ) + }.into_any(), + value: element! { + StyledText( + content: comments.join(", "), + style: Style::MutedLight + ) + }.into_any(), + separator: "-".to_owned(), + ) + } + })) + } + } +} + +fn create_datetime(millis: u128) -> Option { + DateTime::from_timestamp((millis / 1000) as i64, ((millis % 1000) * 1_000_000) as u32) + .map(|dt| dt.naive_local()) +} diff --git a/crates/cli/src/helpers.rs b/crates/cli/src/helpers.rs index d3db42c12..5167279f9 100644 --- a/crates/cli/src/helpers.rs +++ b/crates/cli/src/helpers.rs @@ -5,7 +5,8 @@ use dialoguer::{ }; use indicatif::{MultiProgress, ProgressBar, ProgressDrawTarget, ProgressStyle}; use miette::IntoDiagnostic; -use proto_core::PinType; +use proto_core::PinLocation; +use semver::Version; use starbase_styles::color::{self, Color}; use starbase_utils::env::bool_var; use std::{io::IsTerminal, time::Duration}; @@ -22,19 +23,19 @@ pub enum PinOption { User, } -pub fn map_pin_type(global: bool, option: Option) -> PinType { +pub fn map_pin_type(global: bool, option: Option) -> PinLocation { if let Some(option) = option { return match option { - PinOption::Global => PinType::Global, - PinOption::Local => PinType::Local, - PinOption::User => PinType::User, + PinOption::Global => PinLocation::Global, + PinOption::Local => PinLocation::Local, + PinOption::User => PinLocation::User, }; } if global { - PinType::Global + PinLocation::Global } else { - PinType::Local + PinLocation::Local } } @@ -176,7 +177,7 @@ pub fn print_progress_state(pb: &ProgressBar, message: String) { } } -pub async fn fetch_latest_version() -> miette::Result { +pub async fn fetch_latest_version() -> miette::Result { let version = reqwest::get("https://raw.githubusercontent.com/moonrepo/proto/master/version") .await .into_diagnostic()? @@ -188,5 +189,5 @@ pub async fn fetch_latest_version() -> miette::Result { debug!("Found latest version {}", color::hash(&version)); - Ok(version) + Ok(Version::parse(&version).unwrap()) } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index bcf868f3d..ba947a90c 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -1,8 +1,8 @@ mod app; mod commands; +mod components; mod error; mod helpers; -mod printer; mod session; mod shell; mod systems; @@ -81,7 +81,7 @@ async fn main() -> MainResult { Commands::Completions(args) => commands::completions(session, args).await, Commands::Debug { command } => match command { DebugCommands::Config(args) => commands::debug::config(session, args).await, - DebugCommands::Env => commands::debug::env(session).await, + DebugCommands::Env(args) => commands::debug::env(session, args).await, }, Commands::Diagnose(args) => commands::diagnose(session, args).await, Commands::Install(args) => commands::install(session, args).await, diff --git a/crates/cli/src/printer.rs b/crates/cli/src/printer.rs deleted file mode 100644 index 64a8b9e85..000000000 --- a/crates/cli/src/printer.rs +++ /dev/null @@ -1,205 +0,0 @@ -use proto_core::PluginLocator; -use starbase_styles::color::{self, OwoStyle}; -use std::io::{BufWriter, StdoutLock, Write}; - -pub struct Printer<'std> { - buffer: BufWriter>, - depth: u8, -} - -unsafe impl Send for Printer<'_> {} -unsafe impl Sync for Printer<'_> {} - -impl Printer<'_> { - pub fn new() -> Self { - let stdout = std::io::stdout(); - let buffer = BufWriter::new(stdout.lock()); - - Printer { buffer, depth: 0 } - } - - pub fn flush(&mut self) { - self.line(); - self.buffer.flush().unwrap(); - } - - pub fn write(&mut self, data: impl AsRef) { - writeln!(&mut self.buffer, "{}", data.as_ref()).unwrap(); - } - - pub fn line(&mut self) { - writeln!(&mut self.buffer).unwrap(); - } - - pub fn header, V: AsRef>(&mut self, id: K, name: V) { - self.indent(); - - writeln!( - &mut self.buffer, - "{} {} {}", - OwoStyle::new().bold().style(color::id(id.as_ref())), - color::muted("-"), - color::muted_light(name.as_ref()), - ) - .unwrap(); - } - - pub fn section( - &mut self, - func: impl FnOnce(&mut Printer) -> miette::Result<()>, - ) -> miette::Result<()> { - self.depth += 1; - func(self)?; - self.depth -= 1; - - Ok(()) - } - - pub fn named_section>( - &mut self, - name: T, - func: impl FnOnce(&mut Printer) -> miette::Result<()>, - ) -> miette::Result<()> { - writeln!(&mut self.buffer).unwrap(); - - self.indent(); - - writeln!( - &mut self.buffer, - "{}", - OwoStyle::new() - .bold() - .style(color::muted_light(name.as_ref())) - ) - .unwrap(); - - self.section(func)?; - - Ok(()) - } - - pub fn indent(&mut self) { - if self.depth > 0 { - write!(&mut self.buffer, "{}", " ".repeat(self.depth as usize)).unwrap(); - } - } - - pub fn entry, V: AsRef>(&mut self, key: K, value: V) { - self.indent(); - - writeln!(&mut self.buffer, "{}: {}", key.as_ref(), value.as_ref()).unwrap(); - } - - pub fn entry_list, I: IntoIterator, V: AsRef>( - &mut self, - key: K, - list: I, - empty: Option, - ) { - let items = list.into_iter().collect::>(); - - if items.is_empty() { - if let Some(fallback) = empty { - self.entry(key, fallback); - } - } else { - self.indent(); - - writeln!(&mut self.buffer, "{}:", key.as_ref()).unwrap(); - - self.depth += 1; - - self.list(items); - - self.depth -= 1; - } - } - - pub fn entry_map< - K: AsRef, - I: IntoIterator, - V1: AsRef + Ord, - V2: AsRef + Ord, - >( - &mut self, - key: K, - map: I, - empty: Option, - ) { - let mut items = map.into_iter().collect::>(); - - items.sort(); - - if items.is_empty() { - if let Some(fallback) = empty { - self.entry(key, fallback); - } - } else { - self.indent(); - - writeln!(&mut self.buffer, "{}:", key.as_ref()).unwrap(); - - self.depth += 1; - - for item in items { - self.indent(); - - writeln!( - &mut self.buffer, - "{} {} {}", - item.0.as_ref(), - color::muted("-"), - item.1.as_ref() - ) - .unwrap(); - } - - self.depth -= 1; - } - } - - pub fn list, V: AsRef>(&mut self, list: I) { - let items = list.into_iter().collect::>(); - - for item in items { - self.indent(); - - writeln!(&mut self.buffer, "{} {}", color::muted("-"), item.as_ref()).unwrap(); - } - } - - pub fn locator>(&mut self, locator: L) { - match locator.as_ref() { - PluginLocator::File(file) => { - self.entry("File", color::path(file.get_resolved_path())); - } - PluginLocator::GitHub(github) => { - self.entry("GitHub", color::label(&github.repo_slug)); - - if let Some(name) = &github.project_name { - self.entry("Project", color::label(name)); - } - - self.entry( - "Tag", - color::hash(github.tag.as_deref().unwrap_or("latest")), - ); - } - PluginLocator::Url(url) => { - self.entry("URL", color::url(&url.url)); - } - }; - } -} - -pub fn format_value(value: impl AsRef) -> String { - color::muted_light(value) -} - -pub fn format_env_var(value: &str) -> String { - if value.contains('/') || value.contains('\\') { - color::path(value) - } else { - format_value(value) - } -} diff --git a/crates/cli/src/session.rs b/crates/cli/src/session.rs index c50997fe6..cec0b3aac 100644 --- a/crates/cli/src/session.rs +++ b/crates/cli/src/session.rs @@ -1,31 +1,53 @@ use crate::app::{App as CLI, Commands}; use crate::commands::clean::{internal_clean, CleanArgs}; use crate::systems::*; +use crate::utils::tool_record::ToolRecord; 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, Id, - ProtoEnvironment, Tool, PROTO_PLUGIN_KEY, SCHEMA_PLUGIN_KEY, + detect_version, load_schema_plugin_with_proto, load_tool_from_locator, load_tool_with_proto, + ConfigMode, Id, ProtoConfig, ProtoEnvironment, Tool, UnresolvedVersionSpec, PROTO_PLUGIN_KEY, + SCHEMA_PLUGIN_KEY, }; use rustc_hash::FxHashSet; +use semver::Version; use starbase::{AppResult, AppSession}; +use starbase_console::ui::{style_to_color, ConsoleTheme}; +use starbase_console::{Console, EmptyReporter}; +use starbase_styles::Style; use std::sync::Arc; use tokio::task::JoinSet; use tracing::debug; +#[derive(Debug, Default)] +pub struct LoadToolOptions { + pub detect_version: bool, + pub ids: FxHashSet, + pub inherit_local: bool, + pub inherit_remote: bool, +} + +pub type ProtoConsole = Console; + #[derive(Clone)] pub struct ProtoSession { pub cli: CLI, - pub cli_version: String, + pub cli_version: Version, + pub console: ProtoConsole, pub env: Arc, } impl ProtoSession { pub fn new(cli: CLI) -> Self { + let mut console = Console::::new(false); + console.set_theme(ConsoleTheme::branded(style_to_color(Style::Shell))); + console.set_reporter(EmptyReporter); + Self { cli, - cli_version: env!("CARGO_PKG_VERSION").to_owned(), + cli_version: Version::parse(env!("CARGO_PKG_VERSION")).unwrap(), + console, env: Arc::new(ProtoEnvironment::default()), } } @@ -47,26 +69,67 @@ impl ProtoSession { ProtoRegistry::new(Arc::clone(&self.env)) } - pub async fn load_tool(&self, id: &Id) -> miette::Result { - load_tool_with_proto(id, &self.env).await + pub fn load_config(&self) -> miette::Result<&ProtoConfig> { + self.env.load_config() } - pub async fn load_tools(&self) -> miette::Result> { - self.load_tools_with_filters(FxHashSet::default()).await + pub fn load_config_with_mode(&self, mode: ConfigMode) -> miette::Result<&ProtoConfig> { + self.env.load_config_with_mode(mode) } - // pub async fn load_tools_for_versions(&self) -> miette::Result> { - // let config = self.env.load_config()?; - // let filters = FxHashSet::from_iter(config.versions.keys()); + pub async fn load_tool(&self, id: &Id) -> miette::Result { + self.load_tool_with_options(id, LoadToolOptions::default()) + .await + } + + #[tracing::instrument(name = "load_tool", skip(self))] + pub async fn load_tool_with_options( + &self, + id: &Id, + options: LoadToolOptions, + ) -> miette::Result { + let mut record = ToolRecord::new(load_tool_with_proto(id, &self.env).await?); + + if options.inherit_remote { + record.inherit_from_remote().await?; + } + + if options.inherit_local { + record.inherit_from_local(self.load_config()?); + } - // self.load_tools_with_filters(filters).await - // } + if options.detect_version { + let version = detect_version(&record.tool, None) + .await + .unwrap_or_else(|_| UnresolvedVersionSpec::parse("*").unwrap()); + + record.tool.resolve_version(&version, false).await?; + } + + Ok(record) + } + + pub async fn load_tools(&self) -> miette::Result> { + self.load_tools_with_options(LoadToolOptions::default()) + .await + } - #[tracing::instrument(name = "load_tools", skip_all)] pub async fn load_tools_with_filters( &self, - filter: FxHashSet<&Id>, - ) -> miette::Result> { + 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, + options: LoadToolOptions, + ) -> miette::Result> { let config = self.env.load_config()?; // Download the schema plugin before loading plugins. @@ -75,11 +138,12 @@ impl ProtoSession { // collide when attempting to download the schema plugin! load_schema_plugin_with_proto(&self.env).await?; - let mut set = JoinSet::new(); - let mut tools = vec![]; + let mut set = JoinSet::>::new(); + let mut records = vec![]; + let inherit_remote = options.inherit_remote; for (id, locator) in &config.plugins { - if !filter.is_empty() && !filter.contains(id) { + if !options.ids.is_empty() && !options.ids.contains(id) { continue; } @@ -92,14 +156,28 @@ impl ProtoSession { let locator = locator.to_owned(); let proto = Arc::clone(&self.env); - set.spawn(async move { load_tool_from_locator(id, proto, locator).await }); + set.spawn(async move { + let mut record = ToolRecord::new(load_tool_from_locator(id, proto, locator).await?); + + if inherit_remote { + record.inherit_from_remote().await?; + } + + Ok(record) + }); } while let Some(result) = set.join_next().await { - tools.push(result.into_diagnostic()??); + let mut record: ToolRecord = result.into_diagnostic()??; + + if options.inherit_local { + record.inherit_from_local(config); + } + + records.push(record); } - Ok(tools) + Ok(records) } pub async fn load_proto_tool(&self) -> miette::Result { @@ -131,7 +209,7 @@ impl AppSession for ProtoSession { clean_proto_backups(&self.env)?; if self.should_check_for_new_version() { - check_for_new_version(Arc::clone(&self.env)).await?; + check_for_new_version(Arc::clone(&self.env), &self.cli_version).await?; } Ok(None) @@ -144,6 +222,9 @@ impl AppSession for ProtoSession { internal_clean(self, CleanArgs::default(), true, false).await?; } + self.console.out.flush()?; + self.console.err.flush()?; + Ok(None) } } diff --git a/crates/cli/src/systems.rs b/crates/cli/src/systems.rs index 41a7e2407..7690376db 100644 --- a/crates/cli/src/systems.rs +++ b/crates/cli/src/systems.rs @@ -1,7 +1,6 @@ use crate::app::{App as CLI, Commands}; use crate::helpers::fetch_latest_version; use crate::session::ProtoSession; -use miette::IntoDiagnostic; use proto_core::flow::install::InstallOptions; use proto_core::{ is_offline, now, ConfigMode, ProtoEnvironment, UnresolvedVersionSpec, PROTO_CONFIG_NAME, @@ -101,7 +100,10 @@ pub fn clean_proto_backups(env: &ProtoEnvironment) -> miette::Result<()> { } #[instrument(skip_all)] -pub async fn check_for_new_version(env: Arc) -> miette::Result<()> { +pub async fn check_for_new_version( + env: Arc, + local_version: &Version, +) -> miette::Result<()> { if // Don't check when running tests env.test_only || @@ -130,19 +132,20 @@ pub async fn check_for_new_version(env: Arc) -> miette::Result } // Otherwise fetch and compare versions - let current_version = env!("CARGO_PKG_VERSION"); - - debug!(current_version, "Checking for a new version of proto"); + debug!( + current_version = local_version.to_string(), + "Checking for a new version of proto" + ); - let Ok(latest_version) = fetch_latest_version().await else { + let Ok(remote_version) = fetch_latest_version().await else { return Ok(()); }; - let local_version = Version::parse(current_version).into_diagnostic()?; - let remote_version = Version::parse(&latest_version).into_diagnostic()?; - - if remote_version > local_version { - debug!(latest_version = &latest_version, "Found a newer version"); + if local_version < &remote_version { + debug!( + latest_version = remote_version.to_string(), + "Found a newer version" + ); println!( "✨ There's a new version of proto available, {} (currently on {})", diff --git a/crates/cli/src/utils/install_graph.rs b/crates/cli/src/utils/install_graph.rs index 26f3e06f9..ec4a6f848 100644 --- a/crates/cli/src/utils/install_graph.rs +++ b/crates/cli/src/utils/install_graph.rs @@ -1,4 +1,5 @@ -use proto_core::{Id, Tool}; +use super::tool_record::ToolRecord; +use proto_core::Id; use rustc_hash::{FxHashMap, FxHashSet}; use std::sync::Arc; use tokio::sync::RwLock; @@ -25,7 +26,7 @@ pub struct InstallGraph { } impl InstallGraph { - pub fn new(tools: &[Tool]) -> Self { + pub fn new(tools: &[ToolRecord]) -> Self { let mut ids = FxHashSet::default(); let mut requires = FxHashMap::default(); diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 4f405fa3c..039d5c773 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -1 +1,2 @@ pub mod install_graph; +pub mod tool_record; diff --git a/crates/cli/src/utils/tool_record.rs b/crates/cli/src/utils/tool_record.rs new file mode 100644 index 000000000..d9c34be4d --- /dev/null +++ b/crates/cli/src/utils/tool_record.rs @@ -0,0 +1,68 @@ +use core::ops::{Deref, DerefMut}; +use proto_core::{ProtoConfig, ProtoToolConfig, Tool, UnresolvedVersionSpec, VersionSpec}; +use std::collections::BTreeMap; + +pub struct ToolRecord { + pub tool: Tool, + pub config: ProtoToolConfig, + pub installed_versions: Vec, + pub local_aliases: BTreeMap, + pub remote_aliases: BTreeMap, + pub remote_versions: Vec, +} + +impl ToolRecord { + pub fn new(tool: Tool) -> Self { + let mut versions = tool + .inventory + .manifest + .installed_versions + .iter() + .cloned() + .collect::>(); + versions.sort(); + + Self { + tool, + config: ProtoToolConfig::default(), + local_aliases: BTreeMap::default(), + remote_aliases: BTreeMap::default(), + installed_versions: versions, + remote_versions: vec![], + } + } + + pub fn inherit_from_local(&mut self, config: &ProtoConfig) { + if let Some(tool_config) = config.tools.get(&self.id).map(|c| c.to_owned()) { + self.local_aliases.extend(tool_config.aliases.clone()); + self.config = tool_config; + } + } + + pub async fn inherit_from_remote(&mut self) -> miette::Result<()> { + let version_resolver = self + .tool + .load_version_resolver(&UnresolvedVersionSpec::default()) + .await?; + + self.remote_aliases.extend(version_resolver.aliases); + self.remote_versions.extend(version_resolver.versions); + self.remote_versions.sort(); + + Ok(()) + } +} + +impl Deref for ToolRecord { + type Target = Tool; + + fn deref(&self) -> &Self::Target { + &self.tool + } +} + +impl DerefMut for ToolRecord { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.tool + } +} diff --git a/crates/cli/tests/alias_test.rs b/crates/cli/tests/alias_test.rs index 602522980..71fe7f485 100644 --- a/crates/cli/tests/alias_test.rs +++ b/crates/cli/tests/alias_test.rs @@ -129,7 +129,8 @@ mod alias_global { .arg("node") .arg("example") .arg("19.0.0") - .arg("--global"); + .arg("--to") + .arg("global"); }) .success(); diff --git a/crates/cli/tests/install_uninstall_test.rs b/crates/cli/tests/install_uninstall_test.rs index 114336edd..be13c0359 100644 --- a/crates/cli/tests/install_uninstall_test.rs +++ b/crates/cli/tests/install_uninstall_test.rs @@ -1,6 +1,6 @@ mod utils; -use proto_core::{Id, PinType, ProtoConfig, ToolManifest, UnresolvedVersionSpec, VersionSpec}; +use proto_core::{Id, PinLocation, ProtoConfig, ToolManifest, UnresolvedVersionSpec, VersionSpec}; use rustc_hash::FxHashSet; use starbase_sandbox::predicates::prelude::*; use std::{fs, time::SystemTime}; @@ -477,7 +477,8 @@ mod install_uninstall { // Local ProtoConfig::update(sandbox.path(), |config| { - config.settings.get_or_insert(Default::default()).pin_latest = Some(PinType::Local); + config.settings.get_or_insert(Default::default()).pin_latest = + Some(PinLocation::Local); config.versions.get_or_insert(Default::default()).insert( Id::raw("node"), @@ -526,7 +527,7 @@ mod install_uninstall { // Local ProtoConfig::update(sandbox.path(), |config| { config.settings.get_or_insert(Default::default()).pin_latest = - Some(PinType::Global); + Some(PinLocation::Global); config.versions.get_or_insert(Default::default()).insert( Id::raw("node"), @@ -574,7 +575,8 @@ mod install_uninstall { // Local ProtoConfig::update(sandbox.path(), |config| { - config.settings.get_or_insert(Default::default()).pin_latest = Some(PinType::Local); + config.settings.get_or_insert(Default::default()).pin_latest = + Some(PinLocation::Local); }) .unwrap(); diff --git a/crates/cli/tests/outdated_test.rs b/crates/cli/tests/outdated_test.rs index b6ed2fcff..d7c320747 100644 --- a/crates/cli/tests/outdated_test.rs +++ b/crates/cli/tests/outdated_test.rs @@ -120,6 +120,7 @@ mod outdated { .arg("--update") .arg("--config-mode") .arg("upwards-global") + .arg("--yes") .current_dir(sandbox.path().join("a/b")); }) .success(); @@ -145,6 +146,7 @@ mod outdated { .arg("--config-mode") .arg("upwards-global") .arg("--latest") + .arg("--yes") .current_dir(sandbox.path().join("a/b")); }) .success(); diff --git a/crates/cli/tests/pin_test.rs b/crates/cli/tests/pin_test.rs index 71ce40ca6..1fda2a4fe 100644 --- a/crates/cli/tests/pin_test.rs +++ b/crates/cli/tests/pin_test.rs @@ -151,7 +151,11 @@ mod pin_global { sandbox .run_bin(|cmd| { - cmd.arg("pin").arg("--global").arg("node").arg("19.0.0"); + cmd.arg("pin") + .arg("--to") + .arg("global") + .arg("node") + .arg("19.0.0"); }) .success(); @@ -174,7 +178,11 @@ mod pin_global { sandbox .run_bin(|cmd| { - cmd.arg("pin").arg("--global").arg("npm").arg("bundled"); + cmd.arg("pin") + .arg("--to") + .arg("global") + .arg("npm") + .arg("bundled"); }) .success(); @@ -197,7 +205,11 @@ mod pin_global { sandbox .run_bin(|cmd| { - cmd.arg("pin").arg("--global").arg("npm").arg("1.2"); + cmd.arg("pin") + .arg("--to") + .arg("global") + .arg("npm") + .arg("1.2"); }) .success(); @@ -221,7 +233,8 @@ mod pin_global { sandbox .run_bin(|cmd| { cmd.arg("pin") - .arg("--global") + .arg("--to") + .arg("global") .arg("npm") .arg("6") .arg("--resolve"); @@ -244,7 +257,11 @@ mod pin_global { sandbox .run_bin(|cmd| { - cmd.arg("pin").arg("--global").arg("node").arg("20"); + cmd.arg("pin") + .arg("--to") + .arg("global") + .arg("node") + .arg("20"); }) .success(); diff --git a/crates/cli/tests/plugin_add_test.rs b/crates/cli/tests/plugin_add_test.rs index c12fbb254..9493bcfcf 100644 --- a/crates/cli/tests/plugin_add_test.rs +++ b/crates/cli/tests/plugin_add_test.rs @@ -66,7 +66,8 @@ mod plugin_add { .arg("add") .arg("id") .arg("https://github.com/moonrepo/tools/releases/latest/download/example_plugin.wasm") - .arg("--global"); + .arg("--to") + .arg("global"); }) .success(); diff --git a/crates/cli/tests/plugin_remove_test.rs b/crates/cli/tests/plugin_remove_test.rs index 64af64797..7c5cb141e 100644 --- a/crates/cli/tests/plugin_remove_test.rs +++ b/crates/cli/tests/plugin_remove_test.rs @@ -67,7 +67,11 @@ mod plugin_remove { sandbox .run_bin(|cmd| { - cmd.arg("plugin").arg("remove").arg("id").arg("--global"); + cmd.arg("plugin") + .arg("remove") + .arg("id") + .arg("--from") + .arg("global"); }) .success(); diff --git a/crates/cli/tests/plugin_search_test.rs b/crates/cli/tests/plugin_search_test.rs index 439bc5d4a..80ad923de 100644 --- a/crates/cli/tests/plugin_search_test.rs +++ b/crates/cli/tests/plugin_search_test.rs @@ -31,8 +31,8 @@ mod plugin_search { }) .failure(); - assert.stderr(predicate::str::contains( - "No plugins available for query \"gibberish\"", + assert.stdout(predicate::str::contains( + "no plugins found in the registry for the query gibberish", )); } @@ -46,7 +46,7 @@ mod plugin_search { }) .success(); - assert.stdout(predicate::str::contains("Available for query: zig")); + assert.stdout(predicate::str::contains("Search results for: zig")); } #[test] diff --git a/crates/cli/tests/run_test.rs b/crates/cli/tests/run_test.rs index df266854c..d6f49e977 100644 --- a/crates/cli/tests/run_test.rs +++ b/crates/cli/tests/run_test.rs @@ -190,9 +190,7 @@ mod run { }) .success(); - assert.stdout(predicate::str::contains( - "Node.js 19.0.0 has been installed", - )); + assert.stdout(predicate::str::contains("Node.js 19.0.0 installed")); } #[test] @@ -211,9 +209,7 @@ mod run { }) .success(); - assert.stdout(predicate::str::contains( - "Node.js 19.0.0 has been installed", - )); + assert.stdout(predicate::str::contains("Node.js 19.0.0 installed")); env::remove_var("PROTO_AUTO_INSTALL"); } @@ -251,9 +247,7 @@ mod run { }) .success(); - assert.stdout(predicate::str::contains( - "Node.js 19.0.0 has been installed", - )); + assert.stdout(predicate::str::contains("Node.js 19.0.0 installed")); let assert = sandbox .run_bin(|cmd| { @@ -265,7 +259,7 @@ mod run { }) .success(); - assert.stdout(predicate::str::contains("Node.js 19.0.0 has been installed").not()); + assert.stdout(predicate::str::contains("Node.js 19.0.0 installed").not()); } #[test] diff --git a/crates/cli/tests/status_test.rs b/crates/cli/tests/status_test.rs index 329cfa5b9..adbe94acc 100644 --- a/crates/cli/tests/status_test.rs +++ b/crates/cli/tests/status_test.rs @@ -77,6 +77,8 @@ mod status { .current_dir(sandbox.path().join("a/b/c")); }); + assert.debug(); + let output = assert.output(); assert!(predicate::str::contains("node").eval(&output)); diff --git a/crates/cli/tests/unalias_test.rs b/crates/cli/tests/unalias_test.rs index 25b2e5ad2..6b000ae09 100644 --- a/crates/cli/tests/unalias_test.rs +++ b/crates/cli/tests/unalias_test.rs @@ -112,7 +112,8 @@ mod unalias_global { cmd.arg("unalias") .arg("node") .arg("example") - .arg("--global"); + .arg("--from") + .arg("global"); }) .success(); diff --git a/crates/cli/tests/uninstall_test.rs b/crates/cli/tests/uninstall_test.rs index 92e67ab8f..94f2d94a4 100644 --- a/crates/cli/tests/uninstall_test.rs +++ b/crates/cli/tests/uninstall_test.rs @@ -14,7 +14,7 @@ mod uninstall { cmd.arg("uninstall").arg("node").arg("19.0.0"); }); - assert.inner.stderr(predicate::str::contains( + assert.inner.stdout(predicate::str::contains( "Node.js 19.0.0 has not been installed locally", )); } diff --git a/crates/cli/tests/unpin_test.rs b/crates/cli/tests/unpin_test.rs index 7cc7b3ea8..7b35582f0 100644 --- a/crates/cli/tests/unpin_test.rs +++ b/crates/cli/tests/unpin_test.rs @@ -88,7 +88,7 @@ mod unpin_global { sandbox .run_bin(|cmd| { - cmd.arg("unpin").arg("node").arg("--global"); + cmd.arg("unpin").arg("node").arg("--from").arg("global"); }) .success(); diff --git a/crates/core/src/flow/setup.rs b/crates/core/src/flow/setup.rs index 84643ad39..f7e0fdebd 100644 --- a/crates/core/src/flow/setup.rs +++ b/crates/core/src/flow/setup.rs @@ -1,6 +1,6 @@ use crate::flow::install::InstallOptions; use crate::layout::BinManager; -use crate::proto_config::{PinType, ProtoConfig}; +use crate::proto_config::{PinLocation, ProtoConfig}; use crate::tool::Tool; use crate::tool_manifest::ToolManifestVersion; use proto_pdk_api::*; @@ -74,7 +74,7 @@ impl Tool { manifest.save()?; // Pin the global version - ProtoConfig::update(self.proto.get_config_dir(PinType::Global), |config| { + ProtoConfig::update(self.proto.get_config_dir(PinLocation::Global), |config| { config .versions .get_or_insert(Default::default()) @@ -139,7 +139,7 @@ impl Tool { } // Unpin global version if a match - ProtoConfig::update(self.proto.get_config_dir(PinType::Global), |config| { + ProtoConfig::update(self.proto.get_config_dir(PinLocation::Global), |config| { if let Some(versions) = &mut config.versions { if versions.get(&self.id).is_some_and(|v| v == &version) { debug!("Unpinning global version"); diff --git a/crates/core/src/layout/store.rs b/crates/core/src/layout/store.rs index 58f8b5e0d..0d1027584 100644 --- a/crates/core/src/layout/store.rs +++ b/crates/core/src/layout/store.rs @@ -5,6 +5,7 @@ use miette::IntoDiagnostic; use once_cell::sync::OnceCell; use proto_pdk_api::ToolInventoryMetadata; use proto_shim::{create_shim, locate_proto_exe}; +use serde::Serialize; use starbase_utils::fs; use std::fmt; use std::path::{Path, PathBuf}; @@ -12,7 +13,7 @@ use std::sync::Arc; use tracing::instrument; use warpgate::Id; -#[derive(Clone, Default)] +#[derive(Clone, Default, Serialize)] pub struct Store { pub dir: PathBuf, pub bin_dir: PathBuf, @@ -22,6 +23,7 @@ pub struct Store { pub shims_dir: PathBuf, pub temp_dir: PathBuf, + #[serde(skip)] shim_binary: Arc>>, } diff --git a/crates/core/src/proto.rs b/crates/core/src/proto.rs index 3e7b34535..c384dc260 100644 --- a/crates/core/src/proto.rs +++ b/crates/core/src/proto.rs @@ -2,7 +2,7 @@ use crate::error::ProtoError; use crate::helpers::is_offline; use crate::layout::Store; use crate::proto_config::{ - ConfigMode, PinType, ProtoConfig, ProtoConfigFile, ProtoConfigManager, PROTO_CONFIG_NAME, + ConfigMode, PinLocation, ProtoConfig, ProtoConfigFile, ProtoConfigManager, PROTO_CONFIG_NAME, }; use once_cell::sync::OnceCell; use starbase_utils::dirs::home_dir; @@ -71,11 +71,11 @@ impl ProtoEnvironment { }) } - pub fn get_config_dir(&self, pin: PinType) -> &Path { + pub fn get_config_dir(&self, pin: PinLocation) -> &Path { match pin { - PinType::Global => &self.root, - PinType::Local => &self.cwd, - PinType::User => &self.home, + PinLocation::Global => &self.root, + PinLocation::Local => &self.cwd, + PinLocation::User => &self.home, } } @@ -110,9 +110,13 @@ impl ProtoEnvironment { } pub fn load_config(&self) -> miette::Result<&ProtoConfig> { + self.load_config_with_mode(self.config_mode) + } + + pub fn load_config_with_mode(&self, mode: ConfigMode) -> miette::Result<&ProtoConfig> { let manager = self.load_config_manager()?; - match self.config_mode { + match mode { ConfigMode::Global => manager.get_global_config(), ConfigMode::Local => manager.get_local_config(&self.cwd), ConfigMode::Upwards => manager.get_merged_config_without_global(), diff --git a/crates/core/src/proto_config.rs b/crates/core/src/proto_config.rs index 47b06f2b0..eba46f019 100644 --- a/crates/core/src/proto_config.rs +++ b/crates/core/src/proto_config.rs @@ -124,10 +124,12 @@ derive_enum!( ); derive_enum!( - #[derive(Copy, ConfigEnum)] - pub enum PinType { + #[derive(Copy, ConfigEnum, Default)] + #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] + pub enum PinLocation { #[serde(alias = "store")] Global, + #[default] #[serde(alias = "cwd")] Local, #[serde(alias = "home")] @@ -222,7 +224,7 @@ pub struct ProtoSettingsConfig { pub offline: ProtoOfflineConfig, #[setting(env = "PROTO_PIN_LATEST")] - pub pin_latest: Option, + pub pin_latest: Option, #[setting(default = true)] pub telemetry: bool, diff --git a/crates/core/tests/proto_config_test.rs b/crates/core/tests/proto_config_test.rs index d0295207c..e208aa4b0 100644 --- a/crates/core/tests/proto_config_test.rs +++ b/crates/core/tests/proto_config_test.rs @@ -1,6 +1,6 @@ use indexmap::IndexMap; use proto_core::{ - DetectStrategy, EnvVar, PartialEnvVar, PartialProtoSettingsConfig, PinType, ProtoConfig, + DetectStrategy, EnvVar, PartialEnvVar, PartialProtoSettingsConfig, PinLocation, ProtoConfig, ProtoConfigManager, }; use schematic::ConfigError; @@ -77,7 +77,7 @@ pin-latest = "global" PartialProtoSettingsConfig { auto_clean: Some(true), auto_install: Some(true), - pin_latest: Some(PinType::Global), + pin_latest: Some(PinLocation::Global), ..Default::default() } ); @@ -103,7 +103,7 @@ pin-latest = "global" config.settings.detect_strategy, DetectStrategy::PreferPrototools ); - assert_eq!(config.settings.pin_latest, Some(PinType::Local)); + assert_eq!(config.settings.pin_latest, Some(PinLocation::Local)); env::remove_var("PROTO_AUTO_CLEAN"); env::remove_var("PROTO_AUTO_INSTALL");