diff --git a/src/backend/platform_target.rs b/src/backend/platform_target.rs index 531e780974..8430f0de9b 100644 --- a/src/backend/platform_target.rs +++ b/src/backend/platform_target.rs @@ -31,3 +31,39 @@ impl PlatformTarget { self.platform.to_key() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_platform_target_creation() { + let platform = Platform::parse("linux-x64").unwrap(); + let target = PlatformTarget::new(platform.clone()); + + assert_eq!(target.platform, platform); + assert_eq!(target.os_name(), "linux"); + assert_eq!(target.arch_name(), "x64"); + assert_eq!(target.qualifier(), None); + assert_eq!(target.to_key(), "linux-x64"); + } + + #[test] + fn test_platform_target_with_qualifier() { + let platform = Platform::parse("linux-x64-musl").unwrap(); + let target = PlatformTarget::new(platform); + + assert_eq!(target.os_name(), "linux"); + assert_eq!(target.arch_name(), "x64"); + assert_eq!(target.qualifier(), Some("musl")); + assert_eq!(target.to_key(), "linux-x64-musl"); + } + + #[test] + fn test_from_current() { + let target = PlatformTarget::from_current(); + let current_platform = Platform::current(); + + assert_eq!(target.platform, current_platform); + } +} diff --git a/src/plugins/core/bun.rs b/src/plugins/core/bun.rs index 46af233ed6..e221b498c7 100644 --- a/src/plugins/core/bun.rs +++ b/src/plugins/core/bun.rs @@ -15,7 +15,10 @@ use crate::http::HTTP; use crate::install_context::InstallContext; use crate::toolset::ToolVersion; use crate::ui::progress_report::SingleReport; -use crate::{backend::Backend, config::Config}; +use crate::{ + backend::{Backend, GitHubReleaseInfo, ReleaseType, platform_target::PlatformTarget}, + config::Config, +}; use crate::{file, github, plugins}; #[derive(Debug)] @@ -116,44 +119,108 @@ impl Backend for BunPlugin { Ok(tv) } -} -fn os() -> &'static str { - if cfg!(target_os = "macos") { - "darwin" - } else if cfg!(target_os = "linux") { - "linux" - } else { - &OS + // ========== Lockfile Metadata Fetching Implementation ========== + + async fn get_github_release_info( + &self, + tv: &ToolVersion, + target: &PlatformTarget, + ) -> Result> { + let version = &tv.version; + + // Build the asset pattern for Bun's GitHub releases + // Pattern: bun-{os}-{arch}.zip (where arch may include variants like -musl, -baseline) + let os_name = Self::map_os_to_bun(target.os_name()); + let arch_name = Self::get_bun_arch_for_target(target); + let asset_pattern = format!("bun-{os_name}-{arch_name}.zip"); + + Ok(Some(GitHubReleaseInfo { + repo: "oven-sh/bun".to_string(), + asset_pattern: Some(asset_pattern), + api_url: Some(format!( + "https://github.com/oven-sh/bun/releases/download/bun-v{version}" + )), + release_type: ReleaseType::GitHub, + })) } } -fn arch() -> &'static str { - if cfg!(target_arch = "x86_64") { - if cfg!(target_env = "musl") { - if cfg!(target_feature = "avx2") { - "x64-musl" - } else { - "x64-musl-baseline" +impl BunPlugin { + /// Map our platform OS names to Bun's naming convention + fn map_os_to_bun(os: &str) -> &str { + match os { + "macos" => "darwin", + "linux" => "linux", + "windows" => "windows", + other => other, + } + } + + /// Map our platform arch names to Bun's naming convention + /// Note: This handles simple cases. Complex musl/baseline variants are handled in arch() + fn map_arch_to_bun(arch: &str) -> &str { + match arch { + "x64" => "x64", + "arm64" | "aarch64" => "aarch64", + other => other, + } + } + + /// Get the full Bun arch string for a target platform + /// This handles musl, baseline, and other variants based on platform qualifiers + fn get_bun_arch_for_target(target: &PlatformTarget) -> String { + let base_arch = Self::map_arch_to_bun(target.arch_name()); + + // Handle qualifiers like musl, baseline, etc. + if let Some(qualifier) = target.qualifier() { + match qualifier { + "musl" => format!("{}-musl", base_arch), + "musl-baseline" => format!("{}-musl-baseline", base_arch), + "baseline" => format!("{}-baseline", base_arch), + other => format!("{}-{}", base_arch, other), } - } else if cfg!(target_feature = "avx2") { - "x64" } else { - "x64-baseline" + base_arch.to_string() } - } else if cfg!(target_arch = "aarch64") { - if cfg!(target_env = "musl") { - "aarch64-musl" - } else if cfg!(windows) { - "x64" + } + + /// Get the full Bun arch string with variants (musl, baseline, etc.) + fn get_bun_arch_with_variants() -> &'static str { + if cfg!(target_arch = "x86_64") { + if cfg!(target_env = "musl") { + if cfg!(target_feature = "avx2") { + "x64-musl" + } else { + "x64-musl-baseline" + } + } else if cfg!(target_feature = "avx2") { + "x64" + } else { + "x64-baseline" + } + } else if cfg!(target_arch = "aarch64") { + if cfg!(target_env = "musl") { + "aarch64-musl" + } else if cfg!(windows) { + "x64" + } else { + "aarch64" + } } else { - "aarch64" + &ARCH } - } else { - &ARCH } } +fn os() -> &'static str { + BunPlugin::map_os_to_bun(&OS) +} + +fn arch() -> &'static str { + BunPlugin::get_bun_arch_with_variants() +} + fn bun_bin_name() -> &'static str { if cfg!(windows) { "bun.exe" } else { "bun" } } diff --git a/src/plugins/core/node.rs b/src/plugins/core/node.rs index d72ef4cc39..a5971b8ade 100644 --- a/src/plugins/core/node.rs +++ b/src/plugins/core/node.rs @@ -1,4 +1,4 @@ -use crate::backend::{Backend, VersionCacheManager}; +use crate::backend::{Backend, VersionCacheManager, platform_target::PlatformTarget}; use crate::build_time::built_info; use crate::cache::CacheManagerBuilder; use crate::cli::args::BackendArg; @@ -534,6 +534,75 @@ impl Backend for NodePlugin { }) .clone() } + + // ========== Lockfile Metadata Fetching Implementation ========== + + async fn get_tarball_url( + &self, + tv: &ToolVersion, + target: &PlatformTarget, + ) -> Result> { + let version = &tv.version; + let settings = Settings::get(); + + // Build platform-specific filename like Node.js does + let slug = self.build_platform_slug(version, target); + let filename = if target.os_name() == "windows" { + format!("{slug}.zip") + } else { + format!("{slug}.tar.gz") + }; + + // Use Node.js mirror URL to construct download URL + let url = settings + .node + .mirror_url() + .join(&format!("v{version}/{filename}")) + .map_err(|e| eyre::eyre!("Failed to construct Node.js download URL: {e}"))?; + + Ok(Some(url.to_string())) + } +} + +impl NodePlugin { + /// Map OS name from Platform to Node.js convention + fn map_os(os_name: &str) -> &str { + match os_name { + "macos" => "darwin", + "linux" => "linux", + "windows" => "win", + other => other, + } + } + + /// Map arch name from Platform to Node.js convention + fn map_arch(arch_name: &str) -> &str { + match arch_name { + "x86" => "x86", + "x64" => "x64", + "arm" => "armv7l", + "arm64" => "arm64", + "aarch64" => "arm64", + "loongarch64" => "loong64", + "riscv64" => "riscv64", + other => other, + } + } + + /// Build platform-specific slug for Node.js downloads + /// This mirrors the logic from BuildOpts::new() and slug() function + fn build_platform_slug(&self, version: &str, target: &PlatformTarget) -> String { + let settings = Settings::get(); + + let os = Self::map_os(target.os_name()); + let arch = Self::map_arch(target.arch_name()); + + if let Some(flavor) = &settings.node.flavor { + format!("node-v{version}-{os}-{arch}-{flavor}") + } else { + format!("node-v{version}-{os}-{arch}") + } + } } #[derive(Debug)] @@ -620,38 +689,16 @@ fn make_install_cmd() -> String { } fn os() -> &'static str { - if cfg!(target_os = "linux") { - "linux" - } else if cfg!(target_os = "macos") { - "darwin" - } else if cfg!(target_os = "windows") { - "win" - } else { - built_info::CFG_OS - } + NodePlugin::map_os(built_info::CFG_OS) } fn arch(settings: &Settings) -> &str { let arch = settings.arch(); - if arch == "x86" { - "x86" - } else if arch == "x64" { - "x64" - } else if arch == "arm" { - if cfg!(target_feature = "v6") { - "armv6l" - } else { - "armv7l" - } - } else if arch == "loongarch64" { - "loong64" - } else if arch == "riscv64" { - "riscv64" - } else if arch == "aarch64" { - "arm64" - } else { - arch + // Special handling for ARM with target features + if arch == "arm" && cfg!(target_feature = "v6") { + return "armv6l"; } + NodePlugin::map_arch(arch) } fn slug(v: &str) -> String {