diff --git a/src/backend/github.rs b/src/backend/github.rs index 5866b1446b..317545b0c3 100644 --- a/src/backend/github.rs +++ b/src/backend/github.rs @@ -2,7 +2,7 @@ use crate::backend::asset_detector; use crate::backend::backend_type::BackendType; use crate::backend::static_helpers::lookup_platform_key; use crate::backend::static_helpers::{ - get_filename_from_url, install_artifact, template_string, verify_artifact, + get_filename_from_url, install_artifact, template_string, try_with_v_prefix, verify_artifact, }; use crate::cli::args::BackendArg; use crate::config::Config; @@ -206,56 +206,6 @@ impl UnifiedGitBackend { } } - /// Helper to try both prefixed and non-prefixed tags for a resolver function - async fn try_with_v_prefix(&self, version: &str, resolver: F) -> Result - where - F: Fn(String) -> Fut, - Fut: std::future::Future>, - { - let mut errors = vec![]; - let opts = self.ba.opts(); - - // Generate candidates based on version prefix configuration - let candidates = if let Some(prefix) = opts.get("version_prefix") { - // If a custom prefix is configured, try both prefixed and non-prefixed versions - if version.starts_with(prefix) { - vec![ - version.to_string(), - version.trim_start_matches(prefix).to_string(), - ] - } else { - vec![format!("{}{}", prefix, version), version.to_string()] - } - } else { - // Fall back to 'v' prefix logic - if version.starts_with('v') { - vec![ - version.to_string(), - version.trim_start_matches('v').to_string(), - ] - } else { - vec![format!("v{version}"), version.to_string()] - } - }; - - for candidate in candidates { - match resolver(candidate.clone()).await { - Ok(url) => return Ok(url), - Err(e) => { - let is_404 = crate::http::error_code(&e) == Some(404); - if is_404 { - errors.push(e); - } else { - return Err(e); - } - } - } - } - Err(errors - .pop() - .unwrap_or_else(|| eyre::eyre!("No matching release found for {version}"))) - } - /// Resolves the asset URL using either explicit patterns or auto-detection async fn resolve_asset_url( &self, @@ -270,14 +220,15 @@ impl UnifiedGitBackend { } let version = &tv.version; + let version_prefix = opts.get("version_prefix").map(|s| s.as_str()); if self.is_gitlab() { - self.try_with_v_prefix(version, |candidate| async move { + try_with_v_prefix(version, version_prefix, |candidate| async move { self.resolve_gitlab_asset_url(tv, opts, repo, api_url, &candidate) .await }) .await } else { - self.try_with_v_prefix(version, |candidate| async move { + try_with_v_prefix(version, version_prefix, |candidate| async move { self.resolve_github_asset_url(tv, opts, repo, api_url, &candidate) .await }) diff --git a/src/backend/static_helpers.rs b/src/backend/static_helpers.rs index 8da21e8d8c..82a8e6193f 100644 --- a/src/backend/static_helpers.rs +++ b/src/backend/static_helpers.rs @@ -7,6 +7,59 @@ use crate::ui::progress_report::SingleReport; use eyre::{Result, bail}; use std::path::Path; +/// Helper to try both prefixed and non-prefixed tags for a resolver function +pub async fn try_with_v_prefix( + version: &str, + version_prefix: Option<&str>, + resolver: F, +) -> Result +where + F: Fn(String) -> Fut, + Fut: std::future::Future>, +{ + let mut errors = vec![]; + + // Generate candidates based on version prefix configuration + let candidates = if let Some(prefix) = version_prefix { + // If a custom prefix is configured, try both prefixed and non-prefixed versions + if version.starts_with(prefix) { + vec![ + version.to_string(), + version.trim_start_matches(prefix).to_string(), + ] + } else { + vec![format!("{}{}", prefix, version), version.to_string()] + } + } else { + // Fall back to 'v' prefix logic + if version.starts_with('v') { + vec![ + version.to_string(), + version.trim_start_matches('v').to_string(), + ] + } else { + vec![format!("v{version}"), version.to_string()] + } + }; + + for candidate in candidates { + match resolver(candidate.clone()).await { + Ok(res) => return Ok(res), + Err(e) => { + let is_404 = crate::http::error_code(&e) == Some(404); + if is_404 { + errors.push(e); + } else { + return Err(e); + } + } + } + } + Err(errors + .pop() + .unwrap_or_else(|| eyre::eyre!("No matching release found for {version}"))) +} + /// Returns all possible aliases for the current platform (os, arch), /// with the preferred spelling first (macos/x64, linux/x64, etc). pub fn platform_aliases() -> Vec<(String, String)> { diff --git a/src/backend/ubi.rs b/src/backend/ubi.rs index 6c67c0d232..dfb61cefaf 100644 --- a/src/backend/ubi.rs +++ b/src/backend/ubi.rs @@ -1,4 +1,5 @@ use crate::backend::backend_type::BackendType; +use crate::backend::static_helpers::try_with_v_prefix; use crate::cli::args::BackendArg; use crate::config::{Config, Settings}; use crate::env::{ @@ -108,19 +109,8 @@ impl Backend for UbiBackend { ctx: &InstallContext, mut tv: ToolVersion, ) -> eyre::Result { - let mut v = tv.version.to_string(); + let v = tv.version.to_string(); let opts = tv.request.options(); - let forge = match opts.get("provider") { - Some(forge) => ForgeType::from_str(forge)?, - None => ForgeType::default(), - }; - let api_url = match opts.get("api_url") { - Some(api_url) => api_url.strip_suffix("/").unwrap_or(api_url), - None => match forge { - ForgeType::GitHub => github::API_URL, - ForgeType::GitLab => gitlab::API_URL, - }, - }; let bin_path = opts .get("bin_path") .cloned() @@ -128,37 +118,24 @@ impl Backend for UbiBackend { let extract_all = opts.get("extract_all").is_some_and(|v| v == "true"); let bin_dir = tv.install_path(); - if !name_is_url(&self.tool_name()) { - let release: Result<_, eyre::Report> = match forge { - ForgeType::GitHub => github::get_release_for_url(api_url, &self.tool_name(), &v) - .await - .map(|_| "github"), - ForgeType::GitLab => gitlab::get_release_for_url(api_url, &self.tool_name(), &v) + if name_is_url(&self.tool_name()) { + install(&self.tool_name(), &v, &bin_dir, extract_all, &opts).await?; + } else { + try_with_v_prefix(&v, None, |candidate| { + let opts = opts.clone(); + let bin_dir = bin_dir.clone(); + async move { + install( + &self.tool_name(), + &candidate, + &bin_dir, + extract_all, + &opts, + ) .await - .map(|_| "gitlab"), - }; - if let Err(err) = release { - // this can fail with a rate limit error or 404, either way, try prefixing and if it fails, try without the prefix - // if http::error_code(&err) == Some(404) { - debug!( - "Failed to get release for {}, trying with 'v' prefix: {}", - tv, err - ); - v = format!("v{v}"); - // } - } - } - - if let Err(err) = install(&self.tool_name(), &v, &bin_dir, extract_all, &opts).await { - debug!( - "Failed to install with ubi version '{}': {}, trying with '{}'", - v, err, tv - ); - if let Err(err) = - install(&self.tool_name(), &tv.version, &bin_dir, extract_all, &opts).await - { - bail!("Failed to install with ubi '{}': {}", tv, err); - } + } + }) + .await?; } let mut possible_exes = vec![