diff --git a/Cargo.lock b/Cargo.lock index eada31bdc5..2521afba24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -894,7 +894,7 @@ dependencies = [ "bitflags", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.10.5", "log", "prettyplease", "proc-macro2", @@ -1364,7 +1364,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -1565,9 +1565,9 @@ dependencies = [ [[package]] name = "crc" -version = "3.4.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] @@ -2048,7 +2048,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2284,7 +2284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4004,7 +4004,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.2", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -4090,7 +4090,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.62.2", + "windows-core", ] [[package]] @@ -4442,7 +4442,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4551,7 +4551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "642883fdc81cf2da15ee8183fa1d2c7da452414dd41541a0f3e1428069345447" dependencies = [ "scopeguard", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -4868,20 +4868,14 @@ dependencies = [ [[package]] name = "lzma-rust2" -version = "0.13.0" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" +checksum = "1670343e58806300d87950e3401e820b519b9384281bbabfb15e3636689ffd69" dependencies = [ "crc", "sha2", ] -[[package]] -name = "lzma-rust2" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1670343e58806300d87950e3401e820b519b9384281bbabfb15e3636689ffd69" - [[package]] name = "lzma-sys" version = "0.1.20" @@ -5334,7 +5328,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -5455,7 +5449,7 @@ version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51e219e79014df21a225b1860a479e2dcd7cbd9130f4defd4bd0e191ea31d67d" dependencies = [ - "base64 0.22.1", + "base64 0.21.7", "chrono", "getrandom 0.2.17", "http 1.4.0", @@ -5696,7 +5690,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.45.0", ] [[package]] @@ -6215,7 +6209,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ "heck", - "itertools 0.14.0", + "itertools 0.10.5", "log", "multimap", "once_cell", @@ -6235,7 +6229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.114", @@ -6330,7 +6324,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls 0.23.36", - "socket2 0.6.2", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -6368,9 +6362,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.2", + "socket2 0.5.10", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.59.0", ] [[package]] @@ -6617,6 +6611,8 @@ dependencies = [ "rustls 0.23.36", "rustls-pki-types", "rustls-platform-verifier", + "serde", + "serde_json", "sync_wrapper", "tokio", "tokio-native-tls", @@ -6857,7 +6853,7 @@ dependencies = [ "errno 0.3.14", "libc", "linux-raw-sys", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -6928,7 +6924,7 @@ dependencies = [ "security-framework 3.5.1", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -7435,7 +7431,7 @@ checksum = "414256b6bb303a2a38ad800096664c3b332295c17b7ef0674bf0acf44284036c" dependencies = [ "crc32fast", "js-sys", - "lzma-rust2 0.15.7", + "lzma-rust2", "wasm-bindgen", ] @@ -7552,7 +7548,7 @@ version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ - "errno 0.3.14", + "errno 0.2.8", "libc", ] @@ -7992,7 +7988,7 @@ dependencies = [ "getrandom 0.3.4", "once_cell", "rustix", - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -8141,6 +8137,7 @@ checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" dependencies = [ "deranged", "itoa", + "js-sys", "num-conv", "powerfmt", "serde_core", @@ -8372,7 +8369,7 @@ dependencies = [ "tempfile", "tokio", "tokio-util", - "typed-path", + "typed-path 0.9.3", "untrusted 0.7.1", "url", "walkdir", @@ -8521,6 +8518,12 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82205ffd44a9697e34fc145491aa47310f9871540bb7909eaa9365e0a9a46607" +[[package]] +name = "typed-path" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3015e6ce46d5ad8751e4a772543a30c7511468070e98e64e20165f8f81155b64" + [[package]] name = "typeid" version = "1.0.3" @@ -9047,7 +9050,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -9072,7 +9075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", - "windows-core 0.61.2", + "windows-core", "windows-future", "windows-link 0.1.3", "windows-numerics", @@ -9084,7 +9087,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core 0.61.2", + "windows-core", ] [[package]] @@ -9100,26 +9103,13 @@ dependencies = [ "windows-strings 0.4.2", ] -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - [[package]] name = "windows-future" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core 0.61.2", + "windows-core", "windows-link 0.1.3", "windows-threading", ] @@ -9164,7 +9154,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.61.2", + "windows-core", "windows-link 0.1.3", ] @@ -9632,9 +9622,9 @@ checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" [[package]] name = "xx" -version = "2.3.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704f7d8fce796d9206ab2f0a04a0c59c54394105b838b236d6687e8eef721440" +checksum = "82a174a1d4ba003417df5f1ac4f66fa5a198aa08abcc9c0b67bb2e4b8a9bf9b5" dependencies = [ "bzip2 0.6.1", "duct 1.1.1", @@ -9646,13 +9636,17 @@ dependencies = [ "miette", "rand 0.9.2", "regex", - "reqwest 0.12.28", + "reqwest 0.13.1", + "serde", + "serde_json", + "serde_urlencoded", "sha2", + "strsim", "tar", "thiserror 2.0.18", "tokio", "xz2", - "zip 6.0.0", + "zip 7.2.0", ] [[package]] @@ -9833,26 +9827,27 @@ dependencies = [ [[package]] name = "zip" -version = "6.0.0" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" +checksum = "c42e33efc22a0650c311c2ef19115ce232583abbe80850bc8b66509ebef02de0" dependencies = [ "aes", - "arbitrary", "bzip2 0.6.1", "constant_time_eq 0.3.1", "crc32fast", "deflate64", "flate2", + "generic-array", "getrandom 0.3.4", "hmac", "indexmap 2.13.0", - "lzma-rust2 0.13.0", + "lzma-rust2", "memchr", "pbkdf2", "ppmd-rust", "sha1", "time", + "typed-path 0.12.2", "zeroize", "zopfli", "zstd", diff --git a/crates/aqua-registry/src/lib.rs b/crates/aqua-registry/src/lib.rs index 44c1ff8290..e38e064f07 100644 --- a/crates/aqua-registry/src/lib.rs +++ b/crates/aqua-registry/src/lib.rs @@ -10,7 +10,7 @@ mod types; // Re-export only what's needed by the main mise crate pub use registry::{ AQUA_STANDARD_REGISTRY_FILES, AquaRegistry, DefaultRegistryFetcher, FileCacheStore, - NoOpCacheStore, + NoOpCacheStore, package_ids, }; pub use types::{ AquaChecksum, AquaChecksumType, AquaMinisignType, AquaPackage, AquaPackageType, RegistryYaml, diff --git a/crates/aqua-registry/src/registry.rs b/crates/aqua-registry/src/registry.rs index 7b138e45e3..8da4f510d2 100644 --- a/crates/aqua-registry/src/registry.rs +++ b/crates/aqua-registry/src/registry.rs @@ -41,6 +41,11 @@ pub struct FileCacheStore { pub static AQUA_STANDARD_REGISTRY_FILES: LazyLock> = LazyLock::new(|| include!(concat!(env!("OUT_DIR"), "/aqua_standard_registry.rs"))); +/// Returns all package IDs from the baked-in aqua registry. +pub fn package_ids() -> Vec<&'static str> { + AQUA_STANDARD_REGISTRY_FILES.keys().copied().collect() +} + impl AquaRegistry { /// Create a new AquaRegistry with the given configuration pub fn new(config: AquaRegistryConfig) -> Self { diff --git a/e2e/cli/test_did_you_mean b/e2e/cli/test_did_you_mean new file mode 100644 index 0000000000..90c94334c6 --- /dev/null +++ b/e2e/cli/test_did_you_mean @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Test "Did you mean?" suggestions for unknown tools + +set -euo pipefail +export RUST_BACKTRACE=0 +export MISE_FRIENDLY_ERROR=1 + +# Test: Typo of a known mise registry tool should suggest the correct name +assert_fail "mise use nod" "Did you mean?" +assert_fail "mise use nod" "node" + +# Test: Typo of python should suggest python +assert_fail "mise use pythn" "python" + +# Test: Completely unrelated name should not show suggestions +assert_fail "mise use somethingnonexistent" "not found in mise tool registry" diff --git a/e2e/cli/test_install_inactive_hint b/e2e/cli/test_install_inactive_hint new file mode 100644 index 0000000000..606d409fdc --- /dev/null +++ b/e2e/cli/test_install_inactive_hint @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Test that `mise install` warns when installing tools not in any config file + +set -euo pipefail +export RUST_BACKTRACE=0 +export MISE_FRIENDLY_ERROR=1 + +# Test: Installing a tool not in config should show a "mise use" hint +assert_contains "mise install tiny@3.1.0 2>&1" "not activated" +assert_contains "mise install tiny@3.1.0 2>&1" "mise use tiny" + +# Test: Installing a tool that IS in config should NOT show the hint +mise use dummy@1.0.0 +assert_not_contains "mise install dummy 2>&1" "not activated" + +# Clean up +mise use --rm dummy diff --git a/src/aqua/aqua_registry_wrapper.rs b/src/aqua/aqua_registry_wrapper.rs index a064621a8d..bd855aa382 100644 --- a/src/aqua/aqua_registry_wrapper.rs +++ b/src/aqua/aqua_registry_wrapper.rs @@ -116,6 +116,39 @@ fn fetch_latest_repo(repo: &Git) -> Result<()> { Ok(()) } +/// Search aqua packages by tool name, returning "owner/name" IDs +/// where the name part is similar to the query. +pub fn aqua_suggest(query: &str) -> Vec { + use std::collections::HashMap; + + let ids = aqua_registry::package_ids(); + // Build a map from tool name to full IDs for O(1) lookup + let mut name_to_ids: HashMap<&str, Vec<&str>> = HashMap::new(); + for id in &ids { + if let Some((_, name)) = id.rsplit_once('/') { + name_to_ids.entry(name).or_default().push(id); + } + } + let names: Vec<&str> = name_to_ids.keys().copied().collect(); + + // Use a higher threshold (0.8) to avoid noisy suggestions + let similar_names = xx::suggest::similar_n_with_threshold(query, &names, 5, 0.8); + + // Map back to full IDs + let mut results = Vec::new(); + for matched_name in &similar_names { + if let Some(full_ids) = name_to_ids.get(matched_name.as_str()) { + for full_id in full_ids { + results.push(full_id.to_string()); + if results.len() >= 5 { + return results; + } + } + } + } + results +} + // Re-export types and static for compatibility pub use aqua_registry::{ AquaChecksum, AquaChecksumType, AquaMinisignType, AquaPackage, AquaPackageType, diff --git a/src/cli/args/backend_arg.rs b/src/cli/args/backend_arg.rs index 69704ed363..94405d7db2 100644 --- a/src/cli/args/backend_arg.rs +++ b/src/cli/args/backend_arg.rs @@ -175,7 +175,32 @@ impl BackendArg { bail!("{plugin_name} is not a valid plugin name"); } } else { - bail!("{self} not found in mise tool registry"); + let registry_shorts: Vec<&str> = REGISTRY.keys().copied().collect(); + let mut suggestions: Vec = + xx::suggest::similar_n_with_threshold(&self.short, ®istry_shorts, 3, 0.8) + .into_iter() + .map(|s| s.to_string()) + .collect(); + + let mise_names: HashSet = suggestions.iter().cloned().collect(); + for aqua_id in crate::aqua::aqua_registry_wrapper::aqua_suggest(&self.short) { + // Skip aqua suggestions whose tool name matches an existing mise suggestion + let name = aqua_id + .rsplit_once('/') + .map_or(aqua_id.as_str(), |(_, n)| n); + if !mise_names.contains(name) { + suggestions.push(format!("aqua:{aqua_id}")); + } + } + + let mut msg = format!("{self} not found in mise tool registry"); + if !suggestions.is_empty() { + msg.push_str("\n\nDid you mean?"); + for s in suggestions.iter().take(5) { + msg.push_str(&format!("\n {s}")); + } + } + bail!("{msg}"); } } diff --git a/src/cli/install.rs b/src/cli/install.rs index bc7e8a99c0..4516756b47 100644 --- a/src/cli/install.rs +++ b/src/cli/install.rs @@ -121,6 +121,16 @@ impl Install { .iter() .map(|ta| ta.ba.short.clone()) .collect(); + // Collect inactive tool names before trs borrow is consumed + let inactive_tools: Vec = expanded_runtimes + .iter() + .filter(|ta| { + trs.sources + .get(ta.ba.as_ref()) + .is_none_or(|s| s.is_argument()) + }) + .map(|ta| ta.ba.short.clone()) + .collect(); let mut ts: Toolset = trs.filter_by_tool(tools).into(); let tool_versions = self.get_requested_tool_versions(&ts, &expanded_runtimes)?; let mut versions = if tool_versions.is_empty() { @@ -146,6 +156,25 @@ impl Install { if !self.dry_run { config::rebuild_shims_and_runtime_symlinks(&config, ts, &versions).await?; } + + // Warn about tools that were installed but not in any config file + if !self.dry_run && !inactive_tools.is_empty() { + let tool_list = inactive_tools.join(", "); + let use_cmds: Vec = inactive_tools + .iter() + .map(|t| format!(" mise use {t}")) + .collect(); + warn!( + "{tool_list} installed but not activated — {} not in a mise.toml config file.\nTo install and activate, run:\n{}", + if inactive_tools.len() == 1 { + "it is" + } else { + "they are" + }, + use_cmds.join("\n"), + ); + } + Ok(()) }