diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 9d143d521480..f38fe50eaa1e 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2449,7 +2449,7 @@ pub struct RunArgs { /// If the path to a Python script (i.e., ending in `.py`), it will be /// executed with the Python interpreter. #[command(subcommand)] - pub command: ExternalCommand, + pub command: Option, /// Run with the given packages installed. /// diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs index cb8e99f67f62..b13ba983c809 100644 --- a/crates/uv-python/src/lib.rs +++ b/crates/uv-python/src/lib.rs @@ -37,7 +37,7 @@ mod python_version; mod target; mod version_files; mod virtualenv; -mod which; +pub mod which; #[cfg(not(test))] pub(crate) fn current_dir() -> Result { diff --git a/crates/uv-python/src/which.rs b/crates/uv-python/src/which.rs index 352bc14f3b93..26a68203dbf2 100644 --- a/crates/uv-python/src/which.rs +++ b/crates/uv-python/src/which.rs @@ -3,7 +3,7 @@ use std::path::Path; /// Check whether a path in PATH is a valid executable. /// /// Derived from `which`'s `Checker`. -pub(crate) fn is_executable(path: &Path) -> bool { +pub fn is_executable(path: &Path) -> bool { #[cfg(any(unix, target_os = "wasi", target_os = "redox"))] { if rustix::fs::access(path, rustix::fs::Access::EXEC_OK).is_err() { diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 2dc660e34c9c..d27e72753bb7 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -21,6 +21,7 @@ use uv_distribution::LoweredRequirement; use uv_fs::{PythonExt, Simplified}; use uv_installer::{SatisfiesResult, SitePackages}; use uv_normalize::PackageName; +use uv_python::which::is_executable; use uv_python::{ EnvironmentPreference, Interpreter, PythonDownloads, PythonEnvironment, PythonInstallation, PythonPreference, PythonRequest, PythonVersionFile, VersionRequest, @@ -717,6 +718,39 @@ pub(crate) async fn run( .as_ref() .map_or_else(|| &base_interpreter, |env| env.interpreter()); + // Check if any run command is given. + // If not, print the available scripts for the current interpreter. + if let RunCommand::Empty = command { + println!( + "available scripts path: {}", + interpreter.scripts().display() + ); + + interpreter + .scripts() + .read_dir() + .ok() + .into_iter() + .flatten() + .filter_map(|entry| match entry { + Ok(entry) => Some(entry), + Err(err) => { + warn!("Failed to read entry: {}", err); + None + } + }) + .filter(|entry| { + entry + .file_type() + .is_ok_and(|file_type| file_type.is_file() || file_type.is_symlink()) + }) + .map(|entry| entry.path()) + .filter(|path| is_executable(path)) + .for_each(|path| println!("{}", path.display())); + + return Ok(ExitStatus::Success); + }; + debug!("Running `{command}`"); let mut process = command.as_command(interpreter); diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index ada7dc88532d..ad962e694652 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -148,7 +148,10 @@ async fn run(cli: Cli) -> Result { // Parse the external command, if necessary. let run_command = if let Commands::Project(command) = &*cli.command { if let ProjectCommand::Run(uv_cli::RunArgs { command, .. }) = &**command { - Some(RunCommand::try_from(command)?) + match command { + Some(command) => Some(RunCommand::try_from(command)?), + None => Some(RunCommand::Empty), + } } else { None }