diff --git a/crates/uv/src/commands/tool/run.rs b/crates/uv/src/commands/tool/run.rs index fc1de8135..06522c8c7 100644 --- a/crates/uv/src/commands/tool/run.rs +++ b/crates/uv/src/commands/tool/run.rs @@ -80,6 +80,20 @@ impl Display for ToolRunCommand { } } +/// Check if the given arguments contain a verbose flag (e.g., `--verbose`, `-v`, `-vv`, etc.) +fn find_verbose_flag(args: &[std::ffi::OsString]) -> Option<&str> { + args.iter().find_map(|arg| { + let arg_str = arg.to_str()?; + if arg_str == "--verbose" { + Some("--verbose") + } else if arg_str.starts_with("-v") && arg_str.chars().skip(1).all(|c| c == 'v') { + Some(arg_str) + } else { + None + } + }) +} + /// Run a command. #[allow(clippy::fn_params_excessive_bools)] pub(crate) async fn run( @@ -309,11 +323,24 @@ pub(crate) async fn run( .map_or(Ok(ExitStatus::Failure), |err| Err(err.into())); } - return diagnostics::OperationDiagnostic::native_tls(client_builder.is_native_tls()) - .with_context("tool") + let diagnostic = + diagnostics::OperationDiagnostic::native_tls(client_builder.is_native_tls()); + let diagnostic = if let Some(verbose_flag) = find_verbose_flag(args) { + diagnostic.with_hint(format!( + "You provided `{}` to `{}`. Did you mean to provide it to `{}`? e.g., `{}`", + verbose_flag.cyan(), + target.cyan(), + invocation_source.to_string().cyan(), + format!("{invocation_source} {verbose_flag} {target}").green() + )) + } else { + diagnostic.with_context("tool") + }; + return diagnostic .report(err) .map_or(Ok(ExitStatus::Failure), |err| Err(err.into())); } + Err(ProjectError::Requirements(err)) => { let err = miette::Report::msg(format!("{err}")) .context("Failed to resolve `--with` requirement"); diff --git a/crates/uv/tests/it/tool_run.rs b/crates/uv/tests/it/tool_run.rs index 98dff2b01..26bd5d636 100644 --- a/crates/uv/tests/it/tool_run.rs +++ b/crates/uv/tests/it/tool_run.rs @@ -2820,6 +2820,78 @@ fn tool_run_with_script_and_from_script() { "); } +/// Test that when a user provides `--verbose` to the subcommand, +/// we show a helpful hint. +#[test] +fn tool_run_verbose_hint() { + let context = TestContext::new("3.12").with_filtered_counts(); + let tool_dir = context.temp_dir.child("tools"); + let bin_dir = context.temp_dir.child("bin"); + + // Test with --verbose flag + uv_snapshot!(context.filters(), context.tool_run() + .arg("nonexistent-package-foo") + .arg("--verbose") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because nonexistent-package-foo was not found in the package registry and you require nonexistent-package-foo, we can conclude that your requirements are unsatisfiable. + help: You provided `--verbose` to `nonexistent-package-foo`. Did you mean to provide it to `uv tool run`? e.g., `uv tool run --verbose nonexistent-package-foo` + "###); + + // Test with -v flag + uv_snapshot!(context.filters(), context.tool_run() + .arg("nonexistent-package-bar") + .arg("-v") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because nonexistent-package-bar was not found in the package registry and you require nonexistent-package-bar, we can conclude that your requirements are unsatisfiable. + help: You provided `-v` to `nonexistent-package-bar`. Did you mean to provide it to `uv tool run`? e.g., `uv tool run -v nonexistent-package-bar` + "###); + + // Test with -vv flag + uv_snapshot!(context.filters(), context.tool_run() + .arg("nonexistent-package-baz") + .arg("-vv") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r###" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because nonexistent-package-baz was not found in the package registry and you require nonexistent-package-baz, we can conclude that your requirements are unsatisfiable. + help: You provided `-vv` to `nonexistent-package-baz`. Did you mean to provide it to `uv tool run`? e.g., `uv tool run -vv nonexistent-package-baz` + "###); + + // Test for false positives + uv_snapshot!(context.filters(), context.tool_run() + .arg("nonexistent-package-quux") + .arg("-version") + .env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str()) + .env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving tool dependencies: + ╰─▶ Because nonexistent-package-quux was not found in the package registry and you require nonexistent-package-quux, we can conclude that your requirements are unsatisfiable. + "); +} + #[test] fn tool_run_with_compatible_build_constraints() -> Result<()> { let context = TestContext::new("3.9")