Add hint to use uv self version when uv version cannot find a project (#14738)

When users run `uv version` in a directory without a `pyproject.toml`
file, they often intend to check uv's own version rather than a
project's version. This change adds a helpful hint to guide users to the
correct command.

**Before:**
```
❯ uv version
error: No `pyproject.toml` found in current directory or any parent directory
```

**After:**
```
❯ uv version
error: No `pyproject.toml` found in current directory or any parent directory

hint: If you meant to view uv's version, use `uv self version` instead
```

## Changes

- Modified `find_target()` function in
`crates/uv/src/commands/project/version.rs` to catch
`WorkspaceError::MissingPyprojectToml` specifically and enhance the
error message with a helpful hint
- Added import for `WorkspaceError` to access the specific error type
- Updated existing tests to expect the new hint message in error output
- Added new test case `version_get_missing_with_hint()` to verify
behavior

The hint appears consistently across all scenarios where `uv version`
fails to find a project:
- `uv version` (normal mode)
- `uv version --project .` (explicit project mode)
- `uv version --preview` (preview mode)

The change maintains all existing functionality - when a
`pyproject.toml` is found, `uv version` continues to work normally
without showing the hint.

Fixes #14730.

<!-- START COPILOT CODING AGENT TIPS -->
---

💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: zanieb <2586601+zanieb@users.noreply.github.com>
This commit is contained in:
Copilot
2025-07-22 08:32:45 -05:00
committed by GitHub
parent e49d61db1f
commit 96b889bce3
3 changed files with 36 additions and 10 deletions
+28 -5
View File
@@ -21,7 +21,7 @@ use uv_python::{PythonDownloads, PythonPreference, PythonRequest};
use uv_settings::PythonInstallMirrors;
use uv_workspace::pyproject_mut::Error;
use uv_workspace::{
DiscoveryOptions, WorkspaceCache,
DiscoveryOptions, WorkspaceCache, WorkspaceError,
pyproject_mut::{DependencyTarget, PyProjectTomlMut},
};
use uv_workspace::{VirtualProject, Workspace};
@@ -59,6 +59,7 @@ pub(crate) async fn project_version(
output_format: VersionFormat,
project_dir: &Path,
package: Option<PackageName>,
explicit_project: bool,
dry_run: bool,
locked: bool,
frozen: bool,
@@ -78,7 +79,7 @@ pub(crate) async fn project_version(
preview: PreviewMode,
) -> Result<ExitStatus> {
// Read the metadata
let project = find_target(project_dir, package.as_ref()).await?;
let project = find_target(project_dir, package.as_ref(), explicit_project).await?;
let pyproject_path = project.root().join("pyproject.toml");
let Some(name) = project.project_name().cloned() else {
@@ -325,10 +326,30 @@ pub(crate) async fn project_version(
Ok(status)
}
/// Add hint to use `uv self version` when workspace discovery fails due to missing pyproject.toml
/// and --project was not explicitly passed
fn hint_uv_self_version(err: WorkspaceError, explicit_project: bool) -> anyhow::Error {
if matches!(err, WorkspaceError::MissingPyprojectToml) && !explicit_project {
anyhow!(
"{}\n\n{}{} If you meant to view uv's version, use `{}` instead",
err,
"hint".bold().cyan(),
":".bold(),
"uv self version".green()
)
} else {
err.into()
}
}
/// Find the pyproject.toml we're modifying
///
/// Note that `uv version` never needs to support PEP 723 scripts, as those are unversioned.
async fn find_target(project_dir: &Path, package: Option<&PackageName>) -> Result<VirtualProject> {
async fn find_target(
project_dir: &Path,
package: Option<&PackageName>,
explicit_project: bool,
) -> Result<VirtualProject> {
// Find the project in the workspace.
// No workspace caching since `uv version` changes the workspace definition.
let project = if let Some(package) = package {
@@ -338,7 +359,8 @@ async fn find_target(project_dir: &Path, package: Option<&PackageName>) -> Resul
&DiscoveryOptions::default(),
&WorkspaceCache::default(),
)
.await?
.await
.map_err(|err| hint_uv_self_version(err, explicit_project))?
.with_current_project(package.clone())
.with_context(|| format!("Package `{package}` not found in workspace"))?,
)
@@ -348,7 +370,8 @@ async fn find_target(project_dir: &Path, package: Option<&PackageName>) -> Resul
&DiscoveryOptions::default(),
&WorkspaceCache::default(),
)
.await?
.await
.map_err(|err| hint_uv_self_version(err, explicit_project))?
};
Ok(project)
}
+3
View File
@@ -1058,6 +1058,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
script,
globals,
cli.top_level.no_config,
cli.top_level.global_args.project.is_some(),
filesystem,
cache,
printer,
@@ -1659,6 +1660,7 @@ async fn run_project(
globals: GlobalSettings,
// TODO(zanieb): Determine a better story for passing `no_config` in here
no_config: bool,
explicit_project: bool,
filesystem: Option<FilesystemOptions>,
cache: Cache,
printer: Printer,
@@ -2050,6 +2052,7 @@ async fn run_project(
args.output_format,
project_dir,
args.package,
explicit_project,
args.dry_run,
args.locked,
args.frozen,
+5 -5
View File
@@ -1607,20 +1607,20 @@ fn version_get_fallback_missing_strict() -> Result<()> {
Ok(())
}
// Should error if this pyproject.toml is missing
// and --preview was passed explicitly.
/// Should error with hint if pyproject.toml is missing in normal mode
#[test]
fn version_get_fallback_missing_strict_preview() -> Result<()> {
fn version_get_missing_with_hint() -> Result<()> {
let context = TestContext::new("3.12");
uv_snapshot!(context.filters(), context.version()
.arg("--preview"), @r"
uv_snapshot!(context.filters(), context.version(), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No `pyproject.toml` found in current directory or any parent directory
hint: If you meant to view uv's version, use `uv self version` instead
");
Ok(())