From f3c16224d30d3905dd4330ce043d381a75636671 Mon Sep 17 00:00:00 2001 From: InSync Date: Fri, 4 Jul 2025 09:59:35 +0000 Subject: [PATCH 1/3] Support `self update --output-format=json` --- crates/uv-cli/src/lib.rs | 13 + crates/uv/src/commands/self_update.rs | 343 ++++++++++++++++---------- crates/uv/src/lib.rs | 2 + crates/uv/tests/it/common/mod.rs | 9 + crates/uv/tests/it/self_update.rs | 30 +++ docs/reference/cli.md | 7 +- 6 files changed, 267 insertions(+), 137 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index bf605198f..3845c6d6a 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -58,6 +58,15 @@ pub enum ListFormat { Json, } +#[derive(Debug, Default, Clone, Copy, clap::ValueEnum)] +pub enum SelfUpdateFormat { + /// Output plain text messages. + #[default] + Text, + /// Output result as JSON. + Json, +} + fn extra_name_with_clap_error(arg: &str) -> Result { ExtraName::from_str(arg).map_err(|_err| { anyhow!( @@ -651,6 +660,10 @@ pub struct SelfUpdateArgs { /// Run without performing the update. #[arg(long)] pub dry_run: bool, + + /// The format in which the result would be displayed. + #[arg(long, value_enum, default_value_t = SelfUpdateFormat::default())] + pub output_format: SelfUpdateFormat, } #[derive(Args)] diff --git a/crates/uv/src/commands/self_update.rs b/crates/uv/src/commands/self_update.rs index 4b4fd4830..5a59817a7 100644 --- a/crates/uv/src/commands/self_update.rs +++ b/crates/uv/src/commands/self_update.rs @@ -1,10 +1,13 @@ -use std::fmt::Write; +use std::fmt::{Display, Write}; +use std::path::{Path, PathBuf}; use anyhow::Result; -use axoupdater::{AxoUpdater, AxoupdateError, UpdateRequest}; +use axoupdater::{AxoUpdater, AxoupdateError, UpdateRequest, UpdateResult, Version}; use owo_colors::OwoColorize; +use serde::Serialize; use tracing::debug; +use uv_cli::SelfUpdateFormat; use uv_client::WrappedReqwestError; use uv_fs::Simplified; @@ -12,27 +15,179 @@ use crate::commands::ExitStatus; use crate::printer::Printer; use crate::settings::NetworkSettings; +#[derive(Serialize)] +#[serde(tag = "result", rename_all = "kebab-case")] +enum SelfUpdateOutput { + Offline, + ExternallyInstalled, + MultipleInstallations { + current: AsRef, + other: AsRef, + }, + GitHubRateLimitExceeded, + OnLatest { + version: String, + #[serde(skip_serializing)] + dry_run: bool, + }, + WouldUpdate { + from: String, + to: String, + }, + Updated { + from: Option, + to: Version, + tag: String, + }, +} + +impl SelfUpdateOutput { + fn exit_status(&self) -> ExitStatus { + match self { + Self::Offline => ExitStatus::Failure, + Self::ExternallyInstalled => ExitStatus::Error, + Self::MultipleInstallations { .. } => ExitStatus::Error, + Self::GitHubRateLimitExceeded => ExitStatus::Error, + Self::WouldUpdate { .. } => ExitStatus::Success, + Self::Updated { .. } => ExitStatus::Success, + Self::OnLatest { .. } => ExitStatus::Success, + } + } +} + /// Attempt to update the uv binary. pub(crate) async fn self_update( version: Option, token: Option, dry_run: bool, + output_format: SelfUpdateFormat, printer: Printer, network_settings: NetworkSettings, ) -> Result { - if network_settings.connectivity.is_offline() { - writeln!( - printer.stderr(), - "{}", + let output = self_update_impl( + version, + token, + dry_run, + output_format, + printer, + network_settings, + ) + .await?; + let exit_status = output.exit_status(); + + if output_format == SelfUpdateFormat::Json { + writeln!(printer.stdout(), "{}", serde_json::to_string(&output)?)?; + return exit_status; + } + + let args = match output { + SelfUpdateOutput::Offline => format_args!( + concat!( + "{}{} Self-update is not possible because network connectivity is disabled (i.e., with `--offline`)" + ), + "error".red().bold(), + ":".bold() + ), + SelfUpdateOutput::ExternallyInstalled => format_args!( + concat!( + "{}{} Self-update is only available for uv binaries installed via the standalone installation scripts.", + "\n", + "\n", + "If you installed uv with pip, brew, or another package manager, update uv with `pip install --upgrade`, `brew upgrade`, or similar." + ), + "error".red().bold(), + ":".bold() + ), + SelfUpdateOutput::MultipleInstallations { current, other } => format_args!( + concat!( + "{}{} Self-update is only available for uv binaries installed via the standalone installation scripts.", + "\n", + "\n", + "The current executable is at `{}` but the standalone installer was used to install uv to `{}`. Are multiple copies of uv installed?" + ), + "error".red().bold(), + ":".bold(), + current.simplified_display().bold().cyan(), + other.simplified_display().bold().cyan() + ), + SelfUpdateOutput::GitHubRateLimitExceeded => format_args!( + "{}{} GitHub API rate limit exceeded. Please provide a GitHub token via the {} option.", + "error".red().bold(), + ":".bold(), + "`--token`".green().bold() + ), + + SelfUpdateOutput::OnLatest { version, dry_run } => { + if dry_run { + format_args!( + "You're on the latest version of uv ({})", + format!("v{}", version).bold().white() + ) + } else { + format_args!( + "{}{} You're on the latest version of uv ({})", + "success".green().bold(), + ":".bold(), + format!("v{}", version).bold().cyan() + ) + } + } + + SelfUpdateOutput::WouldUpdate { from, to } => { + let to = if to == "latest" { + "the latest version" + } else { + format!("v{to}") + }; + format_args!( - concat!( - "{}{} Self-update is not possible because network connectivity is disabled (i.e., with `--offline`)" - ), - "error".red().bold(), - ":".bold() + "Would update uv from {} to {}", + format!("v{from}").bold().white(), + to.bold().white(), ) - )?; - return Ok(ExitStatus::Failure); + } + + SelfUpdateOutput::Updated { from, to, tag } => { + let direction = if from.is_some_and(|from| from > to) { + "Downgraded" + } else { + "Upgraded" + }; + + let version_information = if let Some(from) = from { + format!( + "from {} to {}", + format!("v{from}").bold().cyan(), + format!("v{to}").bold().cyan(), + ) + } else { + format!("to {}", format!("v{to}").bold().cyan()) + }; + + format_args!( + "{}{} {direction} uv {}! {}", + "success".green().bold(), + ":".bold(), + version_information, + format!("https://github.com/astral-sh/uv/releases/tag/{}", tag).cyan() + ) + } + }; + + writeln!(printer.stderr(), "{}", args)?; + exit_status +} + +async fn self_update_impl( + version: Option, + token: Option, + dry_run: bool, + output_format: SelfUpdateFormat, + printer: Printer, + network_settings: NetworkSettings, +) -> Result { + if network_settings.connectivity.is_offline() { + return Ok(SelfUpdateOutput::Offline); } let mut updater = AxoUpdater::new_for("uv"); @@ -46,21 +201,7 @@ pub(crate) async fn self_update( // uv was likely installed via a package manager. let Ok(updater) = updater.load_receipt() else { debug!("No receipt found; assuming uv was installed via a package manager"); - writeln!( - printer.stderr(), - "{}", - format_args!( - concat!( - "{}{} Self-update is only available for uv binaries installed via the standalone installation scripts.", - "\n", - "\n", - "If you installed uv with pip, brew, or another package manager, update uv with `pip install --upgrade`, `brew upgrade`, or similar." - ), - "error".red().bold(), - ":".bold() - ) - )?; - return Ok(ExitStatus::Error); + return Ok(SelfUpdateOutput::ExternallyInstalled); }; // If we know what our version is, ignore whatever the receipt thinks it is! @@ -78,35 +219,24 @@ pub(crate) async fn self_update( let current_exe = std::env::current_exe()?; let receipt_prefix = updater.install_prefix_root()?; + return Ok(SelfUpdateOutput::MultipleInstallations { + current: current_exe, + other: receipt_prefix, + }); + } + + if output_format == SelfUpdateFormat::Text { writeln!( printer.stderr(), "{}", format_args!( - concat!( - "{}{} Self-update is only available for uv binaries installed via the standalone installation scripts.", - "\n", - "\n", - "The current executable is at `{}` but the standalone installer was used to install uv to `{}`. Are multiple copies of uv installed?" - ), - "error".red().bold(), - ":".bold(), - current_exe.simplified_display().bold().cyan(), - receipt_prefix.simplified_display().bold().cyan() + "{}{} Checking for updates...", + "info".cyan().bold(), + ":".bold() ) )?; - return Ok(ExitStatus::Error); } - writeln!( - printer.stderr(), - "{}", - format_args!( - "{}{} Checking for updates...", - "info".cyan().bold(), - ":".bold() - ) - )?; - let update_request = if let Some(version) = version { UpdateRequest::SpecificTag(version) } else { @@ -118,108 +248,49 @@ pub(crate) async fn self_update( if dry_run { // TODO(charlie): `updater.fetch_release` isn't public, so we can't say what the latest // version is. - if updater.is_update_needed().await? { + return if updater.is_update_needed().await? { let version = match update_request { UpdateRequest::Latest | UpdateRequest::LatestMaybePrerelease => { - "the latest version".to_string() + "latest".to_string() } UpdateRequest::SpecificTag(version) | UpdateRequest::SpecificVersion(version) => { - format!("v{version}") + version } }; - writeln!( - printer.stderr(), - "Would update uv from {} to {}", - format!("v{}", env!("CARGO_PKG_VERSION")).bold().white(), - version.bold().white(), - )?; + + Ok(SelfUpdateOutput::WouldUpdate { + from: env!("CARGO_PKG_VERSION"), + to: version, + }) } else { - writeln!( - printer.stderr(), - "{}", - format_args!( - "You're on the latest version of uv ({})", - format!("v{}", env!("CARGO_PKG_VERSION")).bold().white() - ) - )?; - } - return Ok(ExitStatus::Success); + Ok(SelfUpdateOutput::OnLatest { + version: env!("CARGO_PKG_VERSION"), + dry_run: true, + }) + }; } // Run the updater. This involves a network request, since we need to determine the latest // available version of uv. match updater.run().await { - Ok(Some(result)) => { - let direction = if result - .old_version - .as_ref() - .is_some_and(|old_version| *old_version > result.new_version) - { - "Downgraded" - } else { - "Upgraded" - }; - - let version_information = if let Some(old_version) = result.old_version { - format!( - "from {} to {}", - format!("v{old_version}").bold().cyan(), - format!("v{}", result.new_version).bold().cyan(), - ) - } else { - format!("to {}", format!("v{}", result.new_version).bold().cyan()) - }; - - writeln!( - printer.stderr(), - "{}", - format_args!( - "{}{} {direction} uv {}! {}", - "success".green().bold(), - ":".bold(), - version_information, - format!( - "https://github.com/astral-sh/uv/releases/tag/{}", - result.new_version_tag - ) - .cyan() - ) - )?; - } - Ok(None) => { - writeln!( - printer.stderr(), - "{}", - format_args!( - "{}{} You're on the latest version of uv ({})", - "success".green().bold(), - ":".bold(), - format!("v{}", env!("CARGO_PKG_VERSION")).bold().cyan() - ) - )?; - } - Err(err) => { - return if let AxoupdateError::Reqwest(err) = err { + Ok(Some(result)) => Ok(SelfUpdateOutput::Updated { + from: result.old_version, + to: result.new_version, + tag: result.new_version_tag, + }), + Ok(None) => Ok(SelfUpdateOutput::OnLatest { + version: env!("CARGO_PKG_VERSION"), + dry_run: false, + }), + Err(err) => match err { + AxoupdateError::Reqwest(err) => { if err.status() == Some(http::StatusCode::FORBIDDEN) && token.is_none() { - writeln!( - printer.stderr(), - "{}", - format_args!( - "{}{} GitHub API rate limit exceeded. Please provide a GitHub token via the {} option.", - "error".red().bold(), - ":".bold(), - "`--token`".green().bold() - ) - )?; - Ok(ExitStatus::Error) + Ok(SelfUpdateOutput::GitHubRateLimitExceeded) } else { Err(WrappedReqwestError::from(err).into()) } - } else { - Err(err.into()) - }; - } + } + _ => Err(err.into()), + }, } - - Ok(ExitStatus::Success) } diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index ab4aee9e9..8e02e236e 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1076,12 +1076,14 @@ async fn run(mut cli: Cli) -> Result { target_version, token, dry_run, + output_format, }), }) => { commands::self_update( target_version, token, dry_run, + output_format, printer, globals.network_settings, ) diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 7b13c49b5..dec5f384a 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -400,6 +400,15 @@ impl TestContext { self } + /// Add a filter that filters out version fields in `self version`'s JSON output. + pub fn with_filtered_version_fields(mut self) -> Self { + self.filters.push(( + r#""(version|from|to)":"[^"]+""#.to_string(), + r#""$1":"""#.to_string(), + )); + self + } + /// Clear filters on `TestContext`. pub fn clear_filters(mut self) -> Self { self.filters.clear(); diff --git a/crates/uv/tests/it/self_update.rs b/crates/uv/tests/it/self_update.rs index 169c7e48e..375767c38 100644 --- a/crates/uv/tests/it/self_update.rs +++ b/crates/uv/tests/it/self_update.rs @@ -57,3 +57,33 @@ fn test_self_update_offline_error() { error: Self-update is not possible because network connectivity is disabled (i.e., with `--offline`) "); } + +#[test] +fn test_self_update_offline_json() { + let context = TestContext::new("3.12"); + + uv_snapshot!(context.self_update().arg("--offline").arg("--output-format=json"), + @r#" + success: false + exit_code: 1 + ----- stdout ----- + {"result":"offline"} + + ----- stderr ----- + "#); +} + +#[test] +fn test_self_update_on_latest_json() { + let context = TestContext::new("3.12").with_filtered_version_fields(); + + uv_snapshot!(context.filters(), context.self_update().arg("--output-format=json"), + @r#" + success: true + exit_code: 0 + ----- stdout ----- + {"result":"on-latest","version":""} + + ----- stderr ----- + "#); +} diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 82fe0fa3d..aae252b78 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -5226,7 +5226,12 @@ uv self update [OPTIONS] [TARGET_VERSION]

May also be set with the UV_NO_PROGRESS environment variable.

--no-python-downloads

Disable automatic downloads of Python.

--offline

Disable network access.

When disabled, uv will only use locally cached data and locally available files.

-

May also be set with the UV_OFFLINE environment variable.

--project project

Run the command within the given project directory.

+

May also be set with the UV_OFFLINE environment variable.

--output-format output-format

The format in which the result would be displayed

+

[default: text]

Possible values:

+
    +
  • text: Output plain text messages
  • +
  • json: Output result as JSON
  • +
--project project

Run the command within the given project directory.

All pyproject.toml, uv.toml, and .python-version files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (.venv).

Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.

See --directory to change the working directory entirely.

From e727f7544449999b6e50003b2798e7cf0afe2971 Mon Sep 17 00:00:00 2001 From: InSync Date: Fri, 4 Jul 2025 11:09:36 +0000 Subject: [PATCH 2/3] Fix --- crates/uv/src/commands/self_update.rs | 88 +++++++++++++++++---------- crates/uv/tests/it/self_update.rs | 15 ----- 2 files changed, 56 insertions(+), 47 deletions(-) diff --git a/crates/uv/src/commands/self_update.rs b/crates/uv/src/commands/self_update.rs index 5a59817a7..9804f8114 100644 --- a/crates/uv/src/commands/self_update.rs +++ b/crates/uv/src/commands/self_update.rs @@ -1,10 +1,9 @@ use std::fmt::{Display, Write}; -use std::path::{Path, PathBuf}; use anyhow::Result; -use axoupdater::{AxoUpdater, AxoupdateError, UpdateRequest, UpdateResult, Version}; +use axoupdater::{AxoUpdater, AxoupdateError, UpdateRequest, Version}; use owo_colors::OwoColorize; -use serde::Serialize; +use serde::{Serialize, Serializer}; use tracing::debug; use uv_cli::SelfUpdateFormat; @@ -15,15 +14,40 @@ use crate::commands::ExitStatus; use crate::printer::Printer; use crate::settings::NetworkSettings; +#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +struct VersionWrapper(Version); + +impl Display for VersionWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.to_string()) + } +} + +impl From for VersionWrapper { + fn from(value: Version) -> VersionWrapper { + VersionWrapper(value) + } +} + +impl Serialize for VersionWrapper { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.0.to_string()) + } +} + #[derive(Serialize)] #[serde(tag = "result", rename_all = "kebab-case")] enum SelfUpdateOutput { Offline, ExternallyInstalled, MultipleInstallations { - current: AsRef, - other: AsRef, + current: String, + other: String, }, + #[serde(rename = "github-rate-limit-exceeded")] GitHubRateLimitExceeded, OnLatest { version: String, @@ -35,8 +59,8 @@ enum SelfUpdateOutput { to: String, }, Updated { - from: Option, - to: Version, + from: Option, + to: VersionWrapper, tag: String, }, } @@ -75,20 +99,20 @@ pub(crate) async fn self_update( .await?; let exit_status = output.exit_status(); - if output_format == SelfUpdateFormat::Json { + if matches!(output_format, SelfUpdateFormat::Json) { writeln!(printer.stdout(), "{}", serde_json::to_string(&output)?)?; - return exit_status; + return Ok(exit_status); } - let args = match output { - SelfUpdateOutput::Offline => format_args!( + let message = match output { + SelfUpdateOutput::Offline => format!( concat!( "{}{} Self-update is not possible because network connectivity is disabled (i.e., with `--offline`)" ), "error".red().bold(), ":".bold() ), - SelfUpdateOutput::ExternallyInstalled => format_args!( + SelfUpdateOutput::ExternallyInstalled => format!( concat!( "{}{} Self-update is only available for uv binaries installed via the standalone installation scripts.", "\n", @@ -98,7 +122,7 @@ pub(crate) async fn self_update( "error".red().bold(), ":".bold() ), - SelfUpdateOutput::MultipleInstallations { current, other } => format_args!( + SelfUpdateOutput::MultipleInstallations { current, other } => format!( concat!( "{}{} Self-update is only available for uv binaries installed via the standalone installation scripts.", "\n", @@ -107,10 +131,10 @@ pub(crate) async fn self_update( ), "error".red().bold(), ":".bold(), - current.simplified_display().bold().cyan(), - other.simplified_display().bold().cyan() + current.bold().cyan(), + other.bold().cyan() ), - SelfUpdateOutput::GitHubRateLimitExceeded => format_args!( + SelfUpdateOutput::GitHubRateLimitExceeded => format!( "{}{} GitHub API rate limit exceeded. Please provide a GitHub token via the {} option.", "error".red().bold(), ":".bold(), @@ -119,12 +143,12 @@ pub(crate) async fn self_update( SelfUpdateOutput::OnLatest { version, dry_run } => { if dry_run { - format_args!( + format!( "You're on the latest version of uv ({})", format!("v{}", version).bold().white() ) } else { - format_args!( + format!( "{}{} You're on the latest version of uv ({})", "success".green().bold(), ":".bold(), @@ -135,12 +159,12 @@ pub(crate) async fn self_update( SelfUpdateOutput::WouldUpdate { from, to } => { let to = if to == "latest" { - "the latest version" + "the latest version".to_string() } else { format!("v{to}") }; - format_args!( + format!( "Would update uv from {} to {}", format!("v{from}").bold().white(), to.bold().white(), @@ -148,7 +172,7 @@ pub(crate) async fn self_update( } SelfUpdateOutput::Updated { from, to, tag } => { - let direction = if from.is_some_and(|from| from > to) { + let direction = if from.as_ref().is_some_and(|from| *from > to) { "Downgraded" } else { "Upgraded" @@ -164,7 +188,7 @@ pub(crate) async fn self_update( format!("to {}", format!("v{to}").bold().cyan()) }; - format_args!( + format!( "{}{} {direction} uv {}! {}", "success".green().bold(), ":".bold(), @@ -174,8 +198,8 @@ pub(crate) async fn self_update( } }; - writeln!(printer.stderr(), "{}", args)?; - exit_status + writeln!(printer.stderr(), "{}", message)?; + Ok(exit_status) } async fn self_update_impl( @@ -220,12 +244,12 @@ async fn self_update_impl( let receipt_prefix = updater.install_prefix_root()?; return Ok(SelfUpdateOutput::MultipleInstallations { - current: current_exe, - other: receipt_prefix, + current: current_exe.simplified_display().to_string(), + other: receipt_prefix.simplified_display().to_string(), }); } - if output_format == SelfUpdateFormat::Text { + if matches!(output_format, SelfUpdateFormat::Text) { writeln!( printer.stderr(), "{}", @@ -259,12 +283,12 @@ async fn self_update_impl( }; Ok(SelfUpdateOutput::WouldUpdate { - from: env!("CARGO_PKG_VERSION"), + from: env!("CARGO_PKG_VERSION").to_string(), to: version, }) } else { Ok(SelfUpdateOutput::OnLatest { - version: env!("CARGO_PKG_VERSION"), + version: env!("CARGO_PKG_VERSION").to_string(), dry_run: true, }) }; @@ -274,12 +298,12 @@ async fn self_update_impl( // available version of uv. match updater.run().await { Ok(Some(result)) => Ok(SelfUpdateOutput::Updated { - from: result.old_version, - to: result.new_version, + from: result.old_version.map(VersionWrapper), + to: result.new_version.into(), tag: result.new_version_tag, }), Ok(None) => Ok(SelfUpdateOutput::OnLatest { - version: env!("CARGO_PKG_VERSION"), + version: env!("CARGO_PKG_VERSION").to_string(), dry_run: false, }), Err(err) => match err { diff --git a/crates/uv/tests/it/self_update.rs b/crates/uv/tests/it/self_update.rs index 375767c38..3f364a435 100644 --- a/crates/uv/tests/it/self_update.rs +++ b/crates/uv/tests/it/self_update.rs @@ -72,18 +72,3 @@ fn test_self_update_offline_json() { ----- stderr ----- "#); } - -#[test] -fn test_self_update_on_latest_json() { - let context = TestContext::new("3.12").with_filtered_version_fields(); - - uv_snapshot!(context.filters(), context.self_update().arg("--output-format=json"), - @r#" - success: true - exit_code: 0 - ----- stdout ----- - {"result":"on-latest","version":""} - - ----- stderr ----- - "#); -} From ce9ed3b3cbaae5b43d9b32f3b2a24b6153c4ba1f Mon Sep 17 00:00:00 2001 From: InSync Date: Fri, 4 Jul 2025 11:28:02 +0000 Subject: [PATCH 3/3] Clippy --- crates/uv/src/commands/self_update.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/uv/src/commands/self_update.rs b/crates/uv/src/commands/self_update.rs index 9804f8114..e5517f695 100644 --- a/crates/uv/src/commands/self_update.rs +++ b/crates/uv/src/commands/self_update.rs @@ -19,7 +19,7 @@ struct VersionWrapper(Version); impl Display for VersionWrapper { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0.to_string()) + write!(f, "{}", self.0) } } @@ -145,14 +145,14 @@ pub(crate) async fn self_update( if dry_run { format!( "You're on the latest version of uv ({})", - format!("v{}", version).bold().white() + format!("v{version}").bold().white() ) } else { format!( "{}{} You're on the latest version of uv ({})", "success".green().bold(), ":".bold(), - format!("v{}", version).bold().cyan() + format!("v{version}").bold().cyan() ) } } @@ -193,12 +193,12 @@ pub(crate) async fn self_update( "success".green().bold(), ":".bold(), version_information, - format!("https://github.com/astral-sh/uv/releases/tag/{}", tag).cyan() + format!("https://github.com/astral-sh/uv/releases/tag/{tag}").cyan() ) } }; - writeln!(printer.stderr(), "{}", message)?; + writeln!(printer.stderr(), "{message}")?; Ok(exit_status) }