diff --git a/crates/vite_global_cli/src/cli.rs b/crates/vite_global_cli/src/cli.rs index f214bef15b..ed873958ea 100644 --- a/crates/vite_global_cli/src/cli.rs +++ b/crates/vite_global_cli/src/cli.rs @@ -727,7 +727,7 @@ async fn managed_update( } current_node_version = get_current_node_version().await?; - for metadata in &all { + for metadata in all.iter().filter(|metadata| !metadata.local) { if !is_same_node_version(&metadata.platform.node, ¤t_node_version) { node_mismatches.push(NodeMismatchPackage { name: metadata.name.clone(), @@ -743,15 +743,17 @@ async fn managed_update( current_node_version = get_current_node_version().await?; for package in packages { - // Always update local packages - if global::is_local_package_spec(package) { - to_update.push(package.clone()); - continue; - } - - // It is not a local package, so `parse_package_spec` there won't return `Err()` - let (package_name, _) = global::parse_package_spec(package).unwrap(); + let (package_name, _) = match global::parse_package_spec(package) { + Ok(parsed) => parsed, + Err(error) => { + output::error(&format!("Failed to update {package}: {error}")); + return Ok(exit_status(1)); + } + }; if let Some(metadata) = PackageMetadata::load(&package_name).await? { + if metadata.local { + continue; + } if !is_same_node_version(&metadata.platform.node, ¤t_node_version) { node_mismatches.push(NodeMismatchPackage { name: package_name, diff --git a/crates/vite_global_cli/src/commands/env/package_metadata.rs b/crates/vite_global_cli/src/commands/env/package_metadata.rs index 8753e1dbbb..8f0eeedfd7 100644 --- a/crates/vite_global_cli/src/commands/env/package_metadata.rs +++ b/crates/vite_global_cli/src/commands/env/package_metadata.rs @@ -46,6 +46,9 @@ pub struct PackageMetadata { /// `corepack`). Updates keep the restriction; explicit installs reset it. #[serde(default, skip_serializing_if = "std::ops::Not::not")] pub bins_restricted: bool, + /// Whether the package was installed from local filesystem content. + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub local: bool, /// Package manager used for installation (npm, yarn, pnpm) pub manager: String, /// Installation timestamp @@ -81,6 +84,7 @@ impl PackageMetadata { bins, js_bins, bins_restricted: false, + local: false, manager, installed_at: Utc::now(), } @@ -253,6 +257,7 @@ mod tests { .unwrap(); assert!(metadata.install_id.is_empty()); + assert!(!metadata.local); } #[test] diff --git a/crates/vite_global_cli/src/commands/global/install.rs b/crates/vite_global_cli/src/commands/global/install.rs index f750221a4d..d4e4049ec8 100644 --- a/crates/vite_global_cli/src/commands/global/install.rs +++ b/crates/vite_global_cli/src/commands/global/install.rs @@ -231,7 +231,7 @@ pub async fn install( // 4. Finalize installed packages. let mut bin_owners = HashMap::::new(); - for (index, (package_name, Package { spec: _, install })) in packages.into_iter().enumerate() { + for (index, (package_name, Package { spec, install })) in packages.into_iter().enumerate() { let lock_file = install_locks.remove(&package_name); let Some(InstalledPackage { installed_version, @@ -403,6 +403,7 @@ pub async fn install( ); metadata.install_id = install_id.clone(); metadata.bins_restricted = bins_restricted; + metadata.local = is_local_package_spec(spec); let mut finalized = true; for bin_name in &stale_bin_names { diff --git a/crates/vite_global_cli/src/commands/global/outdated.rs b/crates/vite_global_cli/src/commands/global/outdated.rs index 47f14f01c0..74e07aa5cd 100644 --- a/crates/vite_global_cli/src/commands/global/outdated.rs +++ b/crates/vite_global_cli/src/commands/global/outdated.rs @@ -53,12 +53,20 @@ pub async fn get_outdated_packages( continue; }; if let Some(metadata) = PackageMetadata::load(&package_name).await? { + if metadata.local { + continue; + } installed.push((metadata, Some(package.clone()))); } } installed } else { - PackageMetadata::list_all().await?.into_iter().map(|package| (package, None)).collect() + PackageMetadata::list_all() + .await? + .into_iter() + .filter(|package| !package.local) + .map(|package| (package, None)) + .collect() }; if installed.is_empty() { @@ -272,3 +280,56 @@ fn exit_status(code: i32) -> ExitStatus { ExitStatus::from_raw(code as u32) } } + +#[cfg(test)] +mod tests { + use std::collections::HashSet; + + use tempfile::TempDir; + + use super::*; + use crate::commands::env::package_metadata::PackageMetadata; + + async fn save_local_metadata(name: &str) { + let mut metadata = PackageMetadata::new( + name.to_string(), + "1.0.0".to_string(), + "22.0.0".to_string(), + None, + vec![name.to_string()], + HashSet::from([name.to_string()]), + "npm".to_string(), + ); + metadata.local = true; + metadata.save().await.unwrap(); + } + + #[tokio::test] + async fn skips_local_packages_when_checking_all_global_updates() { + let temp_dir = TempDir::new().unwrap(); + let _guard = vite_shared::EnvConfig::test_guard( + vite_shared::EnvConfig::for_test_with_home(temp_dir.path()), + ); + + save_local_metadata("some-local-package").await; + + let outdated = get_outdated_packages(&[], 1, true).await.unwrap(); + + assert!(outdated.is_empty()); + } + + #[tokio::test] + async fn skips_local_packages_when_checking_named_global_updates() { + let temp_dir = TempDir::new().unwrap(); + let _guard = vite_shared::EnvConfig::test_guard( + vite_shared::EnvConfig::for_test_with_home(temp_dir.path()), + ); + + save_local_metadata("some-local-package").await; + + let outdated = + get_outdated_packages(&["some-local-package".to_string()], 1, true).await.unwrap(); + + assert!(outdated.is_empty()); + } +}