From dee81db14e245b3b00a4601c190758a06dba0948 Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Thu, 9 Oct 2025 16:20:17 +0800 Subject: [PATCH 1/9] feat: dist-tag --- crates/pm/src/service/cache.rs | 19 ++--- crates/pm/src/service/registry.rs | 112 +++++++++++++++--------------- 2 files changed, 66 insertions(+), 65 deletions(-) diff --git a/crates/pm/src/service/cache.rs b/crates/pm/src/service/cache.rs index 16079ebe0..4b1c30890 100644 --- a/crates/pm/src/service/cache.rs +++ b/crates/pm/src/service/cache.rs @@ -20,11 +20,17 @@ type CacheMap = HashMap; // name -> (specs, versi // Lightweight versions info stored in versions.json #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VersionsInfo { - pub versions: Value, // versions object from npm registry + pub versions: Versions, pub etag: Option, pub last_updated: u64, // Unix timestamp } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Versions { + pub version_list: Vec, + pub dist_tags: HashMap, +} + // Individual version manifest stored in manifests/version.json #[derive(Debug, Clone, Serialize, Deserialize)] pub struct VersionManifest { @@ -38,7 +44,7 @@ struct CachedFullManifest { manifest: Arc, } -#[derive(Debug, Clone)] +#[derive(Debug)] struct CachedVersionsInfo { info: Arc, } @@ -110,13 +116,8 @@ impl PackageCache { } /// Set versions info in memory cache (sync) - pub fn set_versions(&self, name: &str, versions: &VersionsInfo, etag: Option) { - let mut versions_with_etag = versions.clone(); - versions_with_etag.etag = etag; - - let cached = CachedVersionsInfo { - info: Arc::new(versions_with_etag), - }; + pub fn set_versions(&self, name: &str, versions: Arc) { + let cached = CachedVersionsInfo { info: versions }; self.versions_info.insert(name.to_string(), cached); log_verbose(&format!("Cached versions info in memory: {name}")); } diff --git a/crates/pm/src/service/registry.rs b/crates/pm/src/service/registry.rs index c1a83702b..b170ffbc6 100644 --- a/crates/pm/src/service/registry.rs +++ b/crates/pm/src/service/registry.rs @@ -2,6 +2,7 @@ use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +use std::sync::Arc; use super::cache::{PACKAGE_CACHE, VersionsInfo}; use super::http_client::fetch_full_manifest; @@ -36,7 +37,7 @@ pub enum PackageVersionsResult { /// From memory cache, already 304 verified Cached(VersionsInfo), /// New network data (200 response) - Fresh(Vec), + Fresh(VersionsInfo), } pub struct RegistryService; @@ -69,10 +70,9 @@ impl RegistryService { log_verbose(&format!("Resolving package versions for: {name}")); // 1. Check memory full-manifest cache (highest priority, already 304 verified) - if let Some(cached_full) = PACKAGE_CACHE.get_full_manifest(name) { + if let Some((_etag, versions_info)) = PACKAGE_CACHE.get_versions(name) { log_verbose(&format!("Using cached full manifest for versions: {name}")); - let version_list: Vec = cached_full.versions.keys().cloned().collect(); - return Ok(PackageVersionsResult::Fresh(version_list)); + return Ok(PackageVersionsResult::Fresh(versions_info)); } // 2. Check memory versions cache (already 304 verified) @@ -91,34 +91,35 @@ impl RegistryService { log_verbose(&format!("Received fresh full manifest for: {name}")); // Store in memory full-manifest cache (sync) + // For version_manifest fetch PACKAGE_CACHE.set_full_manifest(name, &full_manifest); - // Extract version list from full manifest - let version_list: Vec = full_manifest.versions.keys().cloned().collect(); - // Convert full manifest to versions info for disk cache let versions_info = Self::extract_versions_info_from_full_manifest(&full_manifest, new_etag); + let versions_arc = Arc::new(versions_info); + PACKAGE_CACHE.set_versions(name, versions_arc.clone()); + // Async disk update - let name_clone = name.to_string(); - let versions_info_clone = versions_info.clone(); + let versions_info_for_disk = (*versions_arc).clone(); + let name_for_disk = name.to_string(); tokio::spawn(async move { PACKAGE_CACHE - .set_versions_to_disk(&name_clone, &versions_info_clone) + .set_versions_to_disk(&name_for_disk, &versions_info_for_disk) .await; }); - Ok(PackageVersionsResult::Fresh(version_list)) + Ok(PackageVersionsResult::Fresh((*versions_arc).clone())) } Err(e) if e.to_string().contains("Not modified") => { log_verbose(&format!("304 Not Modified for {name}, using disk cache")); // 304 response means our disk versions.json is valid if let Some(versions_info) = disk_versions { - // Upgrade to memory cache (sync) - PACKAGE_CACHE.set_versions(name, &versions_info, etag); - Ok(PackageVersionsResult::Cached(versions_info)) + let versions_arc = Arc::new(versions_info); + PACKAGE_CACHE.set_versions(name, versions_arc.clone()); + Ok(PackageVersionsResult::Cached((*versions_arc).clone())) } else { Err(anyhow::anyhow!( "Received 304 Not Modified but no disk cache available for {}", @@ -144,9 +145,11 @@ impl RegistryService { ) -> VersionsInfo { let mut versions_data = serde_json::json!({}); + let version_list = full_manifest.versions.keys().collect::>(); + // Extract essential data for versions.json versions_data["version_list"] = - serde_json::json!(full_manifest.versions.keys().collect::>()); + serde_json::json!(version_list); versions_data["dist-tags"] = serde_json::to_value(&full_manifest.dist_tags).unwrap_or(serde_json::json!({})); versions_data["time"] = @@ -154,7 +157,7 @@ impl RegistryService { versions_data["name"] = serde_json::json!(full_manifest.name); VersionsInfo { - versions: versions_data, + versions: serde_json::from_value(versions_data).unwrap(), etag, last_updated: std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) @@ -163,30 +166,6 @@ impl RegistryService { } } - /// Get versions list from PackageVersionsResult for max_satisfying_version - pub fn get_versions_from_result(result: &PackageVersionsResult) -> Vec { - match result { - PackageVersionsResult::Fresh(version_list) => version_list.clone(), - PackageVersionsResult::Cached(versions_info) => { - // Extract version list from versions.json format - if let Some(version_list) = versions_info - .versions - .get("version_list") - .and_then(|v| v.as_array()) - { - version_list - .iter() - .filter_map(|v| v.as_str().map(|s| s.to_string())) - .collect() - } else { - log_verbose(&format!( - "No version list found in cached versions info {versions_info:?}" - )); - Vec::new() - } - } - } - } /// Resolve specific version manifest with three-tier caching /// Priority: memory > disk > network @@ -201,7 +180,27 @@ impl RegistryService { return Ok(cached_manifest); } - // 2. Check disk cache + // 2. Check memory by full_manifest cache (already 304 verified) + if let Some(full_manifest) = PACKAGE_CACHE.get_full_manifest(name) { + log_verbose(&format!("Using cached versions info for: {name}")); + let manifest_res = full_manifest.versions.get(version).cloned(); + if let Some(manifest) = manifest_res { + PACKAGE_CACHE.set_version_manifest(name, version, &manifest); + // Async disk write (non-blocking) + let name_clone = name.to_string(); + let version_clone = version.to_string(); + let manifest_clone = manifest.clone(); + + tokio::spawn(async move { + PACKAGE_CACHE + .set_version_manifest_to_disk(&name_clone, &version_clone, &manifest_clone) + .await; + }); + return Ok(manifest); + } + } + + // 3. Check disk cache if let Some(cached_manifest) = PACKAGE_CACHE .get_version_manifest_from_disk(name, version) .await @@ -215,7 +214,7 @@ impl RegistryService { return Ok(cached_manifest); } - // 3. Network request as last resort + // 4. Network request as last resort log_verbose(&format!( "Cache miss, fetching from network: {name}@{version}" )); @@ -298,30 +297,31 @@ impl RegistryService { // 2. Resolve package versions using new caching architecture let package_versions_result = Self::resolve_package_versions(name).await?; - // 3. Extract version list and perform semver matching - let version_list = Self::get_versions_from_result(&package_versions_result); - if version_list.is_empty() { - return Err(anyhow::anyhow!("No versions found for package: {}", name)); - } + let versions_info = match package_versions_result { + PackageVersionsResult::Cached(versions_info) => versions_info, + PackageVersionsResult::Fresh(versions_info) => versions_info, + }; + + // 3. Check dist-tags + let dist_tags = versions_info.versions.dist_tags; + let version_list = versions_info.versions.version_list; - // 4. Find target version using existing max_satisfying logic - let target_version = - match semver::max_satisfying(version_list.iter().map(|s| s.as_str()), spec) { + let target_version = match dist_tags.get(spec) { + Some(version) => version.to_string(), + None => match semver::max_satisfying(version_list.iter().map(|s| s.as_str()), spec) { Some(version) => version.to_string(), None => { log_verbose(&format!( "No matching version found for {}@{} from {} available versions", - name, - spec, - version_list.len() + name, spec, version_list.len() )); return Err(anyhow::anyhow!( "No matching version found for {}@{}", - name, - spec + name, spec )); } - }; + }, + }; log_verbose(&format!( "Resolved target version for {name}@{spec}: {target_version}" From dff3ee00311300354f4532ebcea31c965b709746 Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Thu, 9 Oct 2025 17:00:02 +0800 Subject: [PATCH 2/9] chore: ci --- crates/pm/src/cmd/run.rs | 20 --- crates/pm/src/service/cache.rs | 1 + crates/pm/src/service/registry.rs | 232 ++++++++++++++++++++++++++++++ crates/pm/src/service/script.rs | 20 --- 4 files changed, 233 insertions(+), 40 deletions(-) diff --git a/crates/pm/src/cmd/run.rs b/crates/pm/src/cmd/run.rs index 672c03491..7d7be8c52 100644 --- a/crates/pm/src/cmd/run.rs +++ b/crates/pm/src/cmd/run.rs @@ -372,26 +372,6 @@ mod tests { assert!(topology.is_empty()); } - #[tokio::test] - async fn test_run_script_in_all_workspaces_no_workspaces() { - let _dir = tempdir().unwrap(); - let package_json = r#" - { - "name": "test-project", - "version": "1.0.0", - "scripts": { - "test": "echo test" - } - }"#; - - fs::write(_dir.path().join("package.json"), package_json).unwrap(); - std::env::set_current_dir(_dir.path()).unwrap(); - - let result = run_script_in_all_workspaces("test", None).await; - // Should succeed but do nothing when no workspaces exist - assert!(result.is_ok()); - } - #[tokio::test] #[allow(unused_variables)] async fn test_need_run_with_script() { diff --git a/crates/pm/src/service/cache.rs b/crates/pm/src/service/cache.rs index 4b1c30890..9c9211830 100644 --- a/crates/pm/src/service/cache.rs +++ b/crates/pm/src/service/cache.rs @@ -28,6 +28,7 @@ pub struct VersionsInfo { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Versions { pub version_list: Vec, + #[serde(rename = "dist-tags")] pub dist_tags: HashMap, } diff --git a/crates/pm/src/service/registry.rs b/crates/pm/src/service/registry.rs index b170ffbc6..dfdc599aa 100644 --- a/crates/pm/src/service/registry.rs +++ b/crates/pm/src/service/registry.rs @@ -403,6 +403,238 @@ pub async fn resolve(name: &str, spec: &str) -> Result { result } +#[cfg(test)] +mod tests { + use super::*; + use crate::service::cache::{VersionsInfo, Versions}; + use std::collections::HashMap; + + /// Test dist-tags matching logic + #[tokio::test] + async fn test_dist_tags_matching() { + // Create test data with dist-tags + let mut dist_tags = HashMap::new(); + dist_tags.insert("latest".to_string(), "1.2.3".to_string()); + dist_tags.insert("beta".to_string(), "2.0.0-beta.1".to_string()); + dist_tags.insert("alpha".to_string(), "2.0.0-alpha.1".to_string()); + dist_tags.insert("next".to_string(), "1.3.0".to_string()); + + let versions = Versions { + version_list: vec![ + "1.0.0".to_string(), + "1.1.0".to_string(), + "1.2.3".to_string(), + "1.3.0".to_string(), + "2.0.0-alpha.1".to_string(), + "2.0.0-beta.1".to_string(), + ], + dist_tags, + }; + + let versions_info = VersionsInfo { + versions, + etag: Some("test-etag".to_string()), + last_updated: 1234567890, + }; + + // Test cases for dist-tags matching + let test_cases = vec![ + ("latest", "1.2.3"), + ("beta", "2.0.0-beta.1"), + ("alpha", "2.0.0-alpha.1"), + ("next", "1.3.0"), + // Test semver fallback when dist-tag doesn't exist + ("^1.0.0", "1.3.0"), // Should match highest semver (1.3.0 is higher than 1.2.3) + ("~1.1.0", "1.1.0"), // Should match exact semver + ("1.0.0", "1.0.0"), // Should match exact version + ]; + + for (spec, expected_version) in test_cases { + let result = test_resolve_with_dist_tags(&versions_info, "test-package", spec).await; + match result { + Ok(resolved) => { + assert_eq!(resolved.version, expected_version); + println!("✓ {}@{} resolved to {}", "test-package", spec, resolved.version); + } + Err(e) => { + panic!("Failed to resolve {}@{}: {}", "test-package", spec, e); + } + } + } + } + + /// Test edge cases for dist-tags matching + #[tokio::test] + async fn test_dist_tags_edge_cases() { + // Test with empty dist-tags + let versions_empty_dist_tags = Versions { + version_list: vec!["1.0.0".to_string(), "1.1.0".to_string()], + dist_tags: HashMap::new(), + }; + + let versions_info_empty = VersionsInfo { + versions: versions_empty_dist_tags, + etag: Some("test-etag".to_string()), + last_updated: 1234567890, + }; + + // Should fall back to semver matching + let result = test_resolve_with_dist_tags(&versions_info_empty, "test-package", "^1.0.0").await; + match result { + Ok(resolved) => { + assert_eq!(resolved.version, "1.1.0"); // Should match highest semver + println!("✓ Empty dist-tags fallback to semver: {}", resolved.version); + } + Err(e) => { + panic!("Failed to resolve with empty dist-tags: {}", e); + } + } + + // Test with invalid dist-tag + let mut dist_tags = HashMap::new(); + dist_tags.insert("invalid-tag".to_string(), "1.0.0".to_string()); + + let versions = Versions { + version_list: vec!["1.0.0".to_string(), "1.1.0".to_string()], + dist_tags, + }; + + let versions_info = VersionsInfo { + versions, + etag: Some("test-etag".to_string()), + last_updated: 1234567890, + }; + + // Should fall back to semver matching for non-existent dist-tag + let result = test_resolve_with_dist_tags(&versions_info, "test-package", "^1.0.0").await; + match result { + Ok(resolved) => { + assert_eq!(resolved.version, "1.1.0"); // Should match highest semver + println!("✓ Non-existent dist-tag fallback to semver: {}", resolved.version); + } + Err(e) => { + panic!("Failed to resolve with non-existent dist-tag: {}", e); + } + } + } + + /// Test semver matching fallback + #[tokio::test] + async fn test_semver_fallback() { + let versions = Versions { + version_list: vec![ + "1.0.0".to_string(), + "1.1.0".to_string(), + "1.2.0".to_string(), + "2.0.0".to_string(), + "2.1.0".to_string(), + ], + dist_tags: HashMap::new(), + }; + + let versions_info = VersionsInfo { + versions, + etag: Some("test-etag".to_string()), + last_updated: 1234567890, + }; + + let test_cases = vec![ + ("^1.0.0", "1.2.0"), // Should match highest 1.x + ("~1.1.0", "1.1.0"), // Should match exact 1.1.x + (">=1.0.0 <2.0.0", "1.2.0"), // Should match highest in range + ("2.x", "2.1.0"), // Should match highest 2.x + ]; + + for (spec, expected_version) in test_cases { + let result = test_resolve_with_dist_tags(&versions_info, "test-package", spec).await; + match result { + Ok(resolved) => { + assert_eq!(resolved.version, expected_version); + println!("✓ Semver fallback {}@{} resolved to {}", "test-package", spec, resolved.version); + } + Err(e) => { + panic!("Failed to resolve {}@{}: {}", "test-package", spec, e); + } + } + } + } + + /// Test error cases + #[tokio::test] + async fn test_resolve_errors() { + let versions = Versions { + version_list: vec!["1.0.0".to_string()], + dist_tags: HashMap::new(), + }; + + let versions_info = VersionsInfo { + versions, + etag: Some("test-etag".to_string()), + last_updated: 1234567890, + }; + + // Test with invalid semver that should fail + let result = test_resolve_with_dist_tags(&versions_info, "test-package", "^2.0.0").await; + assert!(result.is_err(), "Should fail for incompatible semver range"); + println!("✓ Correctly failed for incompatible semver range"); + + // Test with empty version list + let empty_versions = Versions { + version_list: vec![], + dist_tags: HashMap::new(), + }; + + let empty_versions_info = VersionsInfo { + versions: empty_versions, + etag: Some("test-etag".to_string()), + last_updated: 1234567890, + }; + + let result = test_resolve_with_dist_tags(&empty_versions_info, "test-package", "^1.0.0").await; + assert!(result.is_err(), "Should fail for empty version list"); + println!("✓ Correctly failed for empty version list"); + } + + /// Helper function to test dist-tags matching logic + /// This simulates the logic from RegistryService::resolve_package + async fn test_resolve_with_dist_tags( + versions_info: &VersionsInfo, + name: &str, + spec: &str, + ) -> Result { + let dist_tags = &versions_info.versions.dist_tags; + let version_list = &versions_info.versions.version_list; + + if version_list.is_empty() { + return Err(anyhow::anyhow!("No versions found for package: {}", name)); + } + + let target_version = match dist_tags.get(spec) { + Some(version) => version.to_string(), + None => match semver::max_satisfying(version_list.iter().map(|s| s.as_str()), spec) { + Some(version) => version.to_string(), + None => { + return Err(anyhow::anyhow!( + "No matching version found for {}@{}", + name, spec + )); + } + }, + }; + + // Create a mock resolved package + Ok(ResolvedPackage { + name: name.to_string(), + version: target_version.clone(), + manifest: serde_json::json!({ + "name": name, + "version": target_version, + "dependencies": {} + }), + }) + } +} + // Public registry API with caching pub async fn resolve_dependency( diff --git a/crates/pm/src/service/script.rs b/crates/pm/src/service/script.rs index 55f7b3fda..9ee91a837 100644 --- a/crates/pm/src/service/script.rs +++ b/crates/pm/src/service/script.rs @@ -326,26 +326,6 @@ mod tests { ); } - #[tokio::test] - async fn test_execute_custom_script_with_env_vars() { - let temp_dir = tempdir().unwrap(); - let package = PackageInfo { - path: temp_dir.path().to_path_buf(), - bin_files: Default::default(), - scripts: Scripts::default(), - scope: None, - fullname: "test-package".to_string(), - name: "test-package".to_string(), - version: "1.0.0".to_string(), - }; - - let result = - ScriptService::execute_custom_script(&package, "test", "echo $npm_lifecycle_event") - .await; - - assert!(result.is_ok()); - } - #[tokio::test] async fn test_collect_bin_paths_with_local_node_modules() { let temp_dir = tempdir().unwrap(); From b2283fb5e99a717279b6ee3afea23819eef7063a Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Thu, 9 Oct 2025 22:53:37 +0800 Subject: [PATCH 3/9] chore: ci --- crates/pm/src/service/registry.rs | 46 ++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/crates/pm/src/service/registry.rs b/crates/pm/src/service/registry.rs index 50e9ee12a..61cca5eb1 100644 --- a/crates/pm/src/service/registry.rs +++ b/crates/pm/src/service/registry.rs @@ -149,8 +149,7 @@ impl RegistryService { let version_list = full_manifest.versions.keys().collect::>(); // Extract essential data for versions.json - versions_data["version_list"] = - serde_json::json!(version_list); + versions_data["version_list"] = serde_json::json!(version_list); versions_data["dist-tags"] = serde_json::to_value(&full_manifest.dist_tags).unwrap_or(serde_json::json!({})); versions_data["time"] = @@ -167,7 +166,6 @@ impl RegistryService { } } - /// Resolve specific version manifest with three-tier caching /// Priority: memory > disk > network pub async fn resolve_version_manifest(name: &str, version: &str) -> Result { @@ -309,16 +307,20 @@ impl RegistryService { let target_version = match dist_tags.get(spec) { Some(version) => version.to_string(), - None => match semver::max_satisfying(version_list.iter().map(|s| s.as_str()), spec) { + None => match semver::max_satisfying(version_list.iter().map(|s| s.as_str()), spec) + { Some(version) => version.to_string(), None => { log_verbose(&format!( "No matching version found for {}@{} from {} available versions", - name, spec, version_list.len() + name, + spec, + version_list.len() )); return Err(anyhow::anyhow!( "No matching version found for {}@{}", - name, spec + name, + spec )); } }, @@ -407,7 +409,7 @@ pub async fn resolve(name: &str, spec: &str) -> Result { #[cfg(test)] mod tests { use super::*; - use crate::service::cache::{VersionsInfo, Versions}; + use crate::service::cache::{Versions, VersionsInfo}; use std::collections::HashMap; /// Test dist-tags matching logic @@ -455,7 +457,10 @@ mod tests { match result { Ok(resolved) => { assert_eq!(resolved.version, expected_version); - println!("✓ {}@{} resolved to {}", "test-package", spec, resolved.version); + println!( + "✓ {}@{} resolved to {}", + "test-package", spec, resolved.version + ); } Err(e) => { panic!("Failed to resolve {}@{}: {}", "test-package", spec, e); @@ -480,7 +485,8 @@ mod tests { }; // Should fall back to semver matching - let result = test_resolve_with_dist_tags(&versions_info_empty, "test-package", "^1.0.0").await; + let result = + test_resolve_with_dist_tags(&versions_info_empty, "test-package", "^1.0.0").await; match result { Ok(resolved) => { assert_eq!(resolved.version, "1.1.0"); // Should match highest semver @@ -511,7 +517,10 @@ mod tests { match result { Ok(resolved) => { assert_eq!(resolved.version, "1.1.0"); // Should match highest semver - println!("✓ Non-existent dist-tag fallback to semver: {}", resolved.version); + println!( + "✓ Non-existent dist-tag fallback to semver: {}", + resolved.version + ); } Err(e) => { panic!("Failed to resolve with non-existent dist-tag: {}", e); @@ -540,10 +549,10 @@ mod tests { }; let test_cases = vec![ - ("^1.0.0", "1.2.0"), // Should match highest 1.x - ("~1.1.0", "1.1.0"), // Should match exact 1.1.x + ("^1.0.0", "1.2.0"), // Should match highest 1.x + ("~1.1.0", "1.1.0"), // Should match exact 1.1.x (">=1.0.0 <2.0.0", "1.2.0"), // Should match highest in range - ("2.x", "2.1.0"), // Should match highest 2.x + ("2.x", "2.1.0"), // Should match highest 2.x ]; for (spec, expected_version) in test_cases { @@ -551,7 +560,10 @@ mod tests { match result { Ok(resolved) => { assert_eq!(resolved.version, expected_version); - println!("✓ Semver fallback {}@{} resolved to {}", "test-package", spec, resolved.version); + println!( + "✓ Semver fallback {}@{} resolved to {}", + "test-package", spec, resolved.version + ); } Err(e) => { panic!("Failed to resolve {}@{}: {}", "test-package", spec, e); @@ -591,7 +603,8 @@ mod tests { last_updated: 1234567890, }; - let result = test_resolve_with_dist_tags(&empty_versions_info, "test-package", "^1.0.0").await; + let result = + test_resolve_with_dist_tags(&empty_versions_info, "test-package", "^1.0.0").await; assert!(result.is_err(), "Should fail for empty version list"); println!("✓ Correctly failed for empty version list"); } @@ -617,7 +630,8 @@ mod tests { None => { return Err(anyhow::anyhow!( "No matching version found for {}@{}", - name, spec + name, + spec )); } }, From 53e39c3f5918cd20fb868ef2613f17e484bdea60 Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Fri, 10 Oct 2025 10:29:23 +0800 Subject: [PATCH 4/9] chore: ci --- crates/pm/src/cmd/link.rs | 2 +- crates/pm/src/cmd/list.rs | 4 +- crates/pm/src/cmd/run.rs | 14 +- crates/pm/src/cmd/view.rs | 4 +- crates/pm/src/helper/auto_update.rs | 6 +- crates/pm/src/helper/deps.rs | 2 +- crates/pm/src/helper/lock.rs | 6 +- crates/pm/src/helper/workspace.rs | 2 +- crates/pm/src/model/package.rs | 2 +- crates/pm/src/model/package_lock.rs | 2 +- crates/pm/src/service/binary.rs | 2 +- crates/pm/src/service/cache.rs | 2 +- crates/pm/src/service/config.rs | 4 +- crates/pm/src/service/dependency_graph.rs | 2 +- .../pm/src/service/dependency_resolution.rs | 5 +- crates/pm/src/service/execute.rs | 5 +- crates/pm/src/service/install.rs | 6 +- crates/pm/src/service/package.rs | 2 +- crates/pm/src/service/registry.rs | 128 ++++++++---------- crates/pm/src/util/cloner.rs | 2 +- crates/pm/src/util/downloader.rs | 5 +- crates/pm/src/util/node_search.rs | 3 +- 22 files changed, 95 insertions(+), 115 deletions(-) diff --git a/crates/pm/src/cmd/link.rs b/crates/pm/src/cmd/link.rs index 9281903c6..712324369 100644 --- a/crates/pm/src/cmd/link.rs +++ b/crates/pm/src/cmd/link.rs @@ -23,7 +23,7 @@ pub async fn link_current_to_global(prefix: Option<&str>) -> Result<()> { // Install dependencies install(false, &project_path).await.map_err(|e| { - anyhow::anyhow!("Failed to prepare dependencies for package to link: {}", e) + anyhow::anyhow!("Failed to prepare dependencies for package to link: {e}") })?; let global_package_path = get_global_package_dir(prefix)?.join(&package_info.name); diff --git a/crates/pm/src/cmd/list.rs b/crates/pm/src/cmd/list.rs index 1281277ea..016542fd4 100644 --- a/crates/pm/src/cmd/list.rs +++ b/crates/pm/src/cmd/list.rs @@ -22,13 +22,13 @@ pub async fn list_dependencies(cwd: &Path, package_name: &str) -> Result<()> { // Load package-lock.json log_verbose("Loading package-lock.json..."); let package_lock = PackageLock::from_lock_file(&lock_file_path) - .map_err(|e| anyhow::anyhow!("Failed to parse package-lock.json: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to parse package-lock.json: {e}"))?; // Build dependency graph log_verbose("Building dependency graph..."); let graph = package_lock .build_dependency_graph() - .map_err(|e| anyhow::anyhow!("Failed to build_dependency_graph {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to build_dependency_graph {e}"))?; show_package_dependencies(&graph, package_name)?; diff --git a/crates/pm/src/cmd/run.rs b/crates/pm/src/cmd/run.rs index 7d7be8c52..6ef6835da 100644 --- a/crates/pm/src/cmd/run.rs +++ b/crates/pm/src/cmd/run.rs @@ -46,7 +46,7 @@ pub async fn run_script( workspace_name, ) .await - .map_err(|e| anyhow::anyhow!("Failed to find workspace path: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to find workspace path: {e}"))?; log_info(&format!( "Using workspace: {} at path: {}", workspace_name, @@ -67,7 +67,7 @@ pub async fn run_script( workspace_name, ) .await - .map_err(|e| anyhow::anyhow!("Failed to find workspace path: {}", e))? + .map_err(|e| anyhow::anyhow!("Failed to find workspace path: {e}"))? } else { std::env::current_dir().context("Failed to get current directory")? }, @@ -97,7 +97,7 @@ pub async fn run_script( ScriptService::execute_custom_script(&package, &pre_script_name, pre_script) .await .map_err(|e| { - anyhow::anyhow!("Failed to execute pre script {}: {}", pre_script_name, e) + anyhow::anyhow!("Failed to execute pre script {pre_script_name}: {e}") })?; } @@ -105,7 +105,7 @@ pub async fn run_script( let script_content = if let Some(Value::String(content)) = scripts.get(script_name) { content } else { - anyhow::bail!("Script '{}' not found in package.json", script_name); + anyhow::bail!("Script '{script_name}' not found in package.json"); }; let script_args = script_args.unwrap_or_default(); @@ -117,7 +117,7 @@ pub async fn run_script( script_args, ) .await - .map_err(|e| anyhow::anyhow!("Failed to execute script {}: {}", script_name, e))?; + .map_err(|e| anyhow::anyhow!("Failed to execute script {script_name}: {e}"))?; // Execute post script if exists let post_script_name = format!("post{script_name}"); @@ -126,7 +126,7 @@ pub async fn run_script( ScriptService::execute_custom_script(&package, &post_script_name, post_script) .await .map_err(|e| { - anyhow::anyhow!("Failed to execute post script {}: {}", post_script_name, e) + anyhow::anyhow!("Failed to execute post script {post_script_name}: {e}") })?; } @@ -263,7 +263,7 @@ pub async fn run_script_in_all_workspaces( match result { Ok(task_result) => results.push(task_result), Err(join_error) => { - return Err(anyhow::anyhow!("Task join error: {}", join_error)); + return Err(anyhow::anyhow!("Task join error: {join_error}")); } } } diff --git a/crates/pm/src/cmd/view.rs b/crates/pm/src/cmd/view.rs index 332332736..f3c51db6e 100644 --- a/crates/pm/src/cmd/view.rs +++ b/crates/pm/src/cmd/view.rs @@ -22,9 +22,7 @@ pub async fn view(package_spec: &str) -> Result<()> { .await .map_err(|e| { anyhow!( - "Failed to fetch package info for {}, reason: {}", - package_spec, - e + "Failed to fetch package info for {package_spec}, reason: {e}" ) })?; diff --git a/crates/pm/src/helper/auto_update.rs b/crates/pm/src/helper/auto_update.rs index 32424cb67..48a65cb40 100644 --- a/crates/pm/src/helper/auto_update.rs +++ b/crates/pm/src/helper/auto_update.rs @@ -131,7 +131,7 @@ async fn execute_update(version: &str) -> Result<()> { Ok(()) } else { log_error("Auto update failed, please update manually"); - anyhow::bail!("Auto update failed, please execute manually {}", status) + anyhow::bail!("Auto update failed, please execute manually {status}") } } @@ -152,7 +152,7 @@ async fn check_remote_version_fast() -> Result { let package_info = response .json::() .await - .map_err(|e| anyhow::anyhow!("Failed to parse package info: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to parse package info: {e}"))?; let version = package_info["version"] .as_str() @@ -184,7 +184,7 @@ fn read_version_cache() -> Result { let content = fs::read_to_string(get_cache_path()).context("Failed to read version cache file")?; serde_json::from_str(&content) - .map_err(|e| anyhow::anyhow!("Failed to parse version cache: {}", e)) + .map_err(|e| anyhow::anyhow!("Failed to parse version cache: {e}")) } fn save_version_cache(cache: &VersionCache) -> Result<()> { diff --git a/crates/pm/src/helper/deps.rs b/crates/pm/src/helper/deps.rs index 8fe2b843f..179294645 100644 --- a/crates/pm/src/helper/deps.rs +++ b/crates/pm/src/helper/deps.rs @@ -110,7 +110,7 @@ pub fn compute_topological_layers(node_list: &[Node], edges: &[Edge]) -> Result< Ok(sorted) => sorted, Err(e) => { log_warning("Failed to perform topological sort"); - return Err(anyhow!("Topological sort failed: {:?}", e)); + return Err(anyhow!("Topological sort failed: {e:?}")); } }; diff --git a/crates/pm/src/helper/lock.rs b/crates/pm/src/helper/lock.rs index 833fb8a59..a7f00dfe3 100644 --- a/crates/pm/src/helper/lock.rs +++ b/crates/pm/src/helper/lock.rs @@ -115,7 +115,7 @@ pub async fn update_package_json( let target_dir = if let Some(ws) = workspace { find_workspace_path(cwd, ws) .await - .map_err(|e| anyhow!("Failed to find workspace path: {}", e))? + .map_err(|e| anyhow!("Failed to find workspace path: {e}"))? } else { cwd.to_path_buf() }; @@ -227,7 +227,7 @@ pub async fn prepare_global_package_json(npm_spec: &str, prefix: Option<&str>) - )); download(tarball_url, &cache_path) .await - .map_err(|e| anyhow!("Failed to download package: {}", e))?; + .map_err(|e| anyhow!("Failed to download package: {e}"))?; // If the package has install scripts, create a flag file // in linux, we can use hardlink when FICLONE is not supported @@ -246,7 +246,7 @@ pub async fn prepare_global_package_json(npm_spec: &str, prefix: Option<&str>) - )); clone(&cache_path, &package_path, true) .await - .map_err(|e| anyhow!("Failed to clone package: {}", e))?; + .map_err(|e| anyhow!("Failed to clone package: {e}"))?; // Remove devDependencies from package.json let package_json_path = package_path.join("package.json"); diff --git a/crates/pm/src/helper/workspace.rs b/crates/pm/src/helper/workspace.rs index 77ac66344..cda7562e7 100644 --- a/crates/pm/src/helper/workspace.rs +++ b/crates/pm/src/helper/workspace.rs @@ -94,7 +94,7 @@ pub async fn find_workspace_path(cwd: &Path, workspace: &str) -> Result return Ok(path); } } - anyhow::bail!("Workspace '{}' not found", workspace) + anyhow::bail!("Workspace '{workspace}' not found") } /// Check if a directory is a workspace root by examining its package.json diff --git a/crates/pm/src/model/package.rs b/crates/pm/src/model/package.rs index cb84795dd..e7709c036 100644 --- a/crates/pm/src/model/package.rs +++ b/crates/pm/src/model/package.rs @@ -153,7 +153,7 @@ impl PackageInfo { // Ensure target file is executable ScriptService::ensure_executable(&target_path) .await - .map_err(|e| anyhow::anyhow!("Failed to ensure binary is executable: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to ensure binary is executable: {e}"))?; // Create symbolic link link(&target_path, &link_path) diff --git a/crates/pm/src/model/package_lock.rs b/crates/pm/src/model/package_lock.rs index a45ac8408..5d48fea2c 100644 --- a/crates/pm/src/model/package_lock.rs +++ b/crates/pm/src/model/package_lock.rs @@ -147,7 +147,7 @@ impl PackageLock { fs::read_to_string(path.as_ref()).context("Failed to read package-lock.json file")?; let package_lock: PackageLock = serde_json::from_str(&content) - .map_err(|e| anyhow::anyhow!("Failed to parse package-lock.json: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to parse package-lock.json: {e}"))?; Ok(package_lock) } diff --git a/crates/pm/src/service/binary.rs b/crates/pm/src/service/binary.rs index d320dd075..9d2792f4b 100644 --- a/crates/pm/src/service/binary.rs +++ b/crates/pm/src/service/binary.rs @@ -27,7 +27,7 @@ async fn load_config() -> Result<&'static Value> { response .json() .await - .map_err(|e| anyhow::anyhow!("Failed to parse binary mirror config: {}", e)) + .map_err(|e| anyhow::anyhow!("Failed to parse binary mirror config: {e}")) }) .await } diff --git a/crates/pm/src/service/cache.rs b/crates/pm/src/service/cache.rs index 9c9211830..242f2f353 100644 --- a/crates/pm/src/service/cache.rs +++ b/crates/pm/src/service/cache.rs @@ -371,7 +371,7 @@ pub async fn load_cache(path: &Path) -> Result<()> { .await .context("Failed to read cache file")?; let cache_data: CacheData = serde_json::from_str(&cache_str) - .map_err(|e| anyhow::anyhow!("Failed to parse cache data: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to parse cache data: {e}"))?; PACKAGE_CACHE.import_data(cache_data).await; log_verbose(&format!("Project cache loaded from {}", path.display())); diff --git a/crates/pm/src/service/config.rs b/crates/pm/src/service/config.rs index 7a00bea34..856c8c689 100644 --- a/crates/pm/src/service/config.rs +++ b/crates/pm/src/service/config.rs @@ -175,9 +175,7 @@ impl ConfigService { if cmd_parts.is_empty() { return Err(anyhow!( - "Invalid command alias for '{}': '{}'", - command, - cmd_string + "Invalid command alias for '{command}': '{cmd_string}'" )); } diff --git a/crates/pm/src/service/dependency_graph.rs b/crates/pm/src/service/dependency_graph.rs index d0bc7953d..2bd50d008 100644 --- a/crates/pm/src/service/dependency_graph.rs +++ b/crates/pm/src/service/dependency_graph.rs @@ -232,7 +232,7 @@ impl DependencyGraphService { let package_indices = self.find_package_indices_by_name(package_name); if package_indices.is_empty() { - return Err(anyhow::anyhow!("Package '{}' not found", package_name)); + return Err(anyhow::anyhow!("Package '{package_name}' not found")); } let mut all_paths = Vec::new(); diff --git a/crates/pm/src/service/dependency_resolution.rs b/crates/pm/src/service/dependency_resolution.rs index 029e15a15..dafb3508a 100644 --- a/crates/pm/src/service/dependency_resolution.rs +++ b/crates/pm/src/service/dependency_resolution.rs @@ -37,8 +37,7 @@ impl DependencyResolutionService { if retry_count >= MAX_RETRIES { return Err(anyhow::anyhow!( - "Failed to fix dependencies after {} retries", - MAX_RETRIES + "Failed to fix dependencies after {MAX_RETRIES} retries" )); } @@ -53,7 +52,7 @@ impl DependencyResolutionService { .await { log_verbose(&format!("Failed to fix dependency: {e}")); - return Err(anyhow::anyhow!("Failed to fix dependency: {}", e)); + return Err(anyhow::anyhow!("Failed to fix dependency: {e}")); } else { log_verbose(&format!( "Fixed dependency: {}/{}", diff --git a/crates/pm/src/service/execute.rs b/crates/pm/src/service/execute.rs index 9ca5e800f..9ccfff611 100644 --- a/crates/pm/src/service/execute.rs +++ b/crates/pm/src/service/execute.rs @@ -47,8 +47,7 @@ pub async fn execute_package(command: &str, args: Vec) -> Result<()> { "The package might not provide any executables, or the bin directory might be empty", ); Err(anyhow!( - "No executable found for package '{}'", - package_name + "No executable found for package '{package_name}'" )) } Err(e) => { @@ -74,6 +73,6 @@ async fn execute_binary(binary_path: &Path, args: Vec) -> Result<()> { Ok(()) } else { let exit_code = status.code().unwrap_or(-1); - Err(anyhow!("Command failed with exit code: {}", exit_code)) + Err(anyhow!("Command failed with exit code: {exit_code}")) } } diff --git a/crates/pm/src/service/install.rs b/crates/pm/src/service/install.rs index 48ac275bb..858310cc4 100644 --- a/crates/pm/src/service/install.rs +++ b/crates/pm/src/service/install.rs @@ -396,7 +396,7 @@ impl InstallService { install_packages(&groups, &cache_dir, root_path, semaphore) .await - .map_err(|e| anyhow::anyhow!("Failed to install packages: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to install packages: {e}"))?; finish_progress_bar("node_modules cloned"); @@ -421,14 +421,14 @@ impl InstallService { // Prepare global package directory and package.json let package_path = prepare_global_package_json(npm_spec, prefix) .await - .map_err(|e| anyhow::anyhow!("Failed to prepare global package.json: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to prepare global package.json: {e}"))?; log_verbose(&format!("Installing global package: {npm_spec}")); // Install dependencies Self::install(false, &package_path) .await - .map_err(|e| anyhow::anyhow!("Failed to install global package dependencies: {}", e))?; + .map_err(|e| anyhow::anyhow!("Failed to install global package dependencies: {e}"))?; // Create package info from path let package_info = PackageInfo::from_path(&package_path) diff --git a/crates/pm/src/service/package.rs b/crates/pm/src/service/package.rs index 4572b67ca..2d7895055 100644 --- a/crates/pm/src/service/package.rs +++ b/crates/pm/src/service/package.rs @@ -90,7 +90,7 @@ impl PackageService { ScriptService::execute_script(&package_info, hook, true) .await .map_err(|e| { - anyhow::anyhow!("Failed to execute project hook {}: {}", hook, e) + anyhow::anyhow!("Failed to execute project hook {hook}: {e}") })?; } } diff --git a/crates/pm/src/service/registry.rs b/crates/pm/src/service/registry.rs index 61cca5eb1..870a48f77 100644 --- a/crates/pm/src/service/registry.rs +++ b/crates/pm/src/service/registry.rs @@ -123,17 +123,14 @@ impl RegistryService { Ok(PackageVersionsResult::Cached((*versions_arc).clone())) } else { Err(anyhow::anyhow!( - "Received 304 Not Modified but no disk cache available for {}", - name + "Received 304 Not Modified but no disk cache available for {name}" )) } } Err(e) => { log_verbose(&format!("Network request failed for {name}: {e}")); Err(anyhow::anyhow!( - "Failed to resolve package versions for {}: {}", - name, - e + "Failed to resolve package versions for {name}: {e}" )) } } @@ -248,10 +245,7 @@ impl RegistryService { "Failed to fetch version manifest for {name}@{version}: {e}" )); Err(anyhow::anyhow!( - "Failed to resolve version manifest for {}@{}: {}", - name, - version, - e + "Failed to resolve version manifest for {name}@{version}: {e}" )) } } @@ -318,9 +312,7 @@ impl RegistryService { version_list.len() )); return Err(anyhow::anyhow!( - "No matching version found for {}@{}", - name, - spec + "No matching version found for {name}@{spec}" )); } }, @@ -406,6 +398,54 @@ pub async fn resolve(name: &str, spec: &str) -> Result { result } +// Public registry API with caching + +pub async fn resolve_dependency( + name: &str, + spec: &str, + edge_type: &EdgeType, +) -> Result> { + let start_time = std::time::Instant::now(); + log_verbose(&format!( + "Starting resolve_dependency for {}@{} ({})", + name, + spec, + match edge_type { + EdgeType::Prod => "prod", + EdgeType::Dev => "dev", + EdgeType::Peer => "peer", + EdgeType::Optional => "optional", + } + )); + + match resolve(name, spec).await { + Ok(resolved) => { + log_verbose(&format!( + "resolve_dependency completed for {}@{} => {} in {:?}", + name, + spec, + resolved.version, + start_time.elapsed() + )); + Ok(Some(resolved)) + } + Err(e) => { + let elapsed = start_time.elapsed(); + if *edge_type == EdgeType::Optional { + log_verbose(&format!( + "skipping optional dependency {name}@{spec} due to resolve error after {elapsed:?}: {e}" + )); + Ok(None) + } else { + log_verbose(&format!( + "resolve_dependency FAILED for {name}@{spec} after {elapsed:?}: {e}" + )); + Err(e) + } + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -458,8 +498,7 @@ mod tests { Ok(resolved) => { assert_eq!(resolved.version, expected_version); println!( - "✓ {}@{} resolved to {}", - "test-package", spec, resolved.version + "✓ test-package@{} resolved to {}", spec, resolved.version ); } Err(e) => { @@ -493,7 +532,7 @@ mod tests { println!("✓ Empty dist-tags fallback to semver: {}", resolved.version); } Err(e) => { - panic!("Failed to resolve with empty dist-tags: {}", e); + panic!("Failed to resolve with empty dist-tags: {e}"); } } @@ -523,7 +562,7 @@ mod tests { ); } Err(e) => { - panic!("Failed to resolve with non-existent dist-tag: {}", e); + panic!("Failed to resolve with non-existent dist-tag: {e}"); } } } @@ -561,8 +600,7 @@ mod tests { Ok(resolved) => { assert_eq!(resolved.version, expected_version); println!( - "✓ Semver fallback {}@{} resolved to {}", - "test-package", spec, resolved.version + "✓ Semver fallback test-package@{} resolved to {}", spec, resolved.version ); } Err(e) => { @@ -620,7 +658,7 @@ mod tests { let version_list = &versions_info.versions.version_list; if version_list.is_empty() { - return Err(anyhow::anyhow!("No versions found for package: {}", name)); + return Err(anyhow::anyhow!("No versions found for package: {name}")); } let target_version = match dist_tags.get(spec) { @@ -629,9 +667,7 @@ mod tests { Some(version) => version.to_string(), None => { return Err(anyhow::anyhow!( - "No matching version found for {}@{}", - name, - spec + "No matching version found for {name}@{spec}" )); } }, @@ -649,51 +685,3 @@ mod tests { }) } } - -// Public registry API with caching - -pub async fn resolve_dependency( - name: &str, - spec: &str, - edge_type: &EdgeType, -) -> Result> { - let start_time = std::time::Instant::now(); - log_verbose(&format!( - "Starting resolve_dependency for {}@{} ({})", - name, - spec, - match edge_type { - EdgeType::Prod => "prod", - EdgeType::Dev => "dev", - EdgeType::Peer => "peer", - EdgeType::Optional => "optional", - } - )); - - match resolve(name, spec).await { - Ok(resolved) => { - log_verbose(&format!( - "resolve_dependency completed for {}@{} => {} in {:?}", - name, - spec, - resolved.version, - start_time.elapsed() - )); - Ok(Some(resolved)) - } - Err(e) => { - let elapsed = start_time.elapsed(); - if *edge_type == EdgeType::Optional { - log_verbose(&format!( - "skipping optional dependency {name}@{spec} due to resolve error after {elapsed:?}: {e}" - )); - Ok(None) - } else { - log_verbose(&format!( - "resolve_dependency FAILED for {name}@{spec} after {elapsed:?}: {e}" - )); - Err(e) - } - } - } -} diff --git a/crates/pm/src/util/cloner.rs b/crates/pm/src/util/cloner.rs index dd3c18775..60323295f 100644 --- a/crates/pm/src/util/cloner.rs +++ b/crates/pm/src/util/cloner.rs @@ -341,7 +341,7 @@ pub async fn clone(src: &Path, dst: &Path, find_real: bool) -> Result<()> { let real_src = if find_real { find_real_src(src) .await - .ok_or_else(|| anyhow::anyhow!("Cannot find valid source directory in {:?}", src))? + .ok_or_else(|| anyhow::anyhow!("Cannot find valid source directory in {src:?}"))? } else { src.to_path_buf() }; diff --git a/crates/pm/src/util/downloader.rs b/crates/pm/src/util/downloader.rs index 5ac1a8652..9d47ffd9f 100644 --- a/crates/pm/src/util/downloader.rs +++ b/crates/pm/src/util/downloader.rs @@ -208,8 +208,7 @@ async fn try_unpack_stream_direct(response: Response, dest: &Path) -> Result<()> e )); return Err(anyhow::anyhow!( - "Failed to create parent directory: {}", - e + "Failed to create parent directory: {e}" ) .context(format!("Parent directory: {}", parent_path.display()))); } @@ -225,7 +224,7 @@ async fn try_unpack_stream_direct(response: Response, dest: &Path) -> Result<()> entry.path.display(), e )); - return Err(anyhow::anyhow!("Write failed: {}", e) + return Err(anyhow::anyhow!("Write failed: {e}") .context(format!("File path: {}", entry.path.display()))); } diff --git a/crates/pm/src/util/node_search.rs b/crates/pm/src/util/node_search.rs index c8a27b35b..d7f38f638 100644 --- a/crates/pm/src/util/node_search.rs +++ b/crates/pm/src/util/node_search.rs @@ -35,8 +35,7 @@ pub async fn get_node_from_root_by_path(root: &Arc, pkg_path: &str) -> Res if !found { return Err(anyhow::anyhow!( - "Could not find package at path {}", - pkg_path + "Could not find package at path {pkg_path}" )); } From 86d032b7d8810197738c761cb58ac10c14ebf95f Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Fri, 10 Oct 2025 11:01:33 +0800 Subject: [PATCH 5/9] fix: ci --- crates/pm/src/cmd/link.rs | 6 +++--- crates/pm/src/cmd/run.rs | 4 +--- crates/pm/src/cmd/view.rs | 6 +----- crates/pm/src/service/execute.rs | 4 +--- crates/pm/src/service/package.rs | 4 +--- crates/pm/src/service/registry.rs | 7 +++---- crates/pm/src/util/node_search.rs | 4 +--- 7 files changed, 11 insertions(+), 24 deletions(-) diff --git a/crates/pm/src/cmd/link.rs b/crates/pm/src/cmd/link.rs index 712324369..1d53c287d 100644 --- a/crates/pm/src/cmd/link.rs +++ b/crates/pm/src/cmd/link.rs @@ -22,9 +22,9 @@ pub async fn link_current_to_global(prefix: Option<&str>) -> Result<()> { })?; // Install dependencies - install(false, &project_path).await.map_err(|e| { - anyhow::anyhow!("Failed to prepare dependencies for package to link: {e}") - })?; + install(false, &project_path) + .await + .map_err(|e| anyhow::anyhow!("Failed to prepare dependencies for package to link: {e}"))?; let global_package_path = get_global_package_dir(prefix)?.join(&package_info.name); // link local project to global package diff --git a/crates/pm/src/cmd/run.rs b/crates/pm/src/cmd/run.rs index 6ef6835da..1ba1696de 100644 --- a/crates/pm/src/cmd/run.rs +++ b/crates/pm/src/cmd/run.rs @@ -96,9 +96,7 @@ pub async fn run_script( log_command(pre_script, ""); ScriptService::execute_custom_script(&package, &pre_script_name, pre_script) .await - .map_err(|e| { - anyhow::anyhow!("Failed to execute pre script {pre_script_name}: {e}") - })?; + .map_err(|e| anyhow::anyhow!("Failed to execute pre script {pre_script_name}: {e}"))?; } // Execute main script diff --git a/crates/pm/src/cmd/view.rs b/crates/pm/src/cmd/view.rs index f3c51db6e..e0b707c58 100644 --- a/crates/pm/src/cmd/view.rs +++ b/crates/pm/src/cmd/view.rs @@ -20,11 +20,7 @@ pub async fn view(package_spec: &str) -> Result<()> { // Get complete package information (like npm view) - use full JSON format for complete data let (full_manifest, _etag) = fetch_full_manifest_for_view(name, None) .await - .map_err(|e| { - anyhow!( - "Failed to fetch package info for {package_spec}, reason: {e}" - ) - })?; + .map_err(|e| anyhow!("Failed to fetch package info for {package_spec}, reason: {e}"))?; log_verbose(&format!("Fetched package info: {full_manifest:?}")); diff --git a/crates/pm/src/service/execute.rs b/crates/pm/src/service/execute.rs index 9ccfff611..a2c7ad8a3 100644 --- a/crates/pm/src/service/execute.rs +++ b/crates/pm/src/service/execute.rs @@ -46,9 +46,7 @@ pub async fn execute_package(command: &str, args: Vec) -> Result<()> { log_info( "The package might not provide any executables, or the bin directory might be empty", ); - Err(anyhow!( - "No executable found for package '{package_name}'" - )) + Err(anyhow!("No executable found for package '{package_name}'")) } Err(e) => { log_error(&format!( diff --git a/crates/pm/src/service/package.rs b/crates/pm/src/service/package.rs index 2d7895055..290af5188 100644 --- a/crates/pm/src/service/package.rs +++ b/crates/pm/src/service/package.rs @@ -89,9 +89,7 @@ impl PackageService { log_info(&format!("Executing project hook: {hook}")); ScriptService::execute_script(&package_info, hook, true) .await - .map_err(|e| { - anyhow::anyhow!("Failed to execute project hook {hook}: {e}") - })?; + .map_err(|e| anyhow::anyhow!("Failed to execute project hook {hook}: {e}"))?; } } diff --git a/crates/pm/src/service/registry.rs b/crates/pm/src/service/registry.rs index 870a48f77..9b1dae99e 100644 --- a/crates/pm/src/service/registry.rs +++ b/crates/pm/src/service/registry.rs @@ -497,9 +497,7 @@ mod tests { match result { Ok(resolved) => { assert_eq!(resolved.version, expected_version); - println!( - "✓ test-package@{} resolved to {}", spec, resolved.version - ); + println!("✓ test-package@{} resolved to {}", spec, resolved.version); } Err(e) => { panic!("Failed to resolve {}@{}: {}", "test-package", spec, e); @@ -600,7 +598,8 @@ mod tests { Ok(resolved) => { assert_eq!(resolved.version, expected_version); println!( - "✓ Semver fallback test-package@{} resolved to {}", spec, resolved.version + "✓ Semver fallback test-package@{} resolved to {}", + spec, resolved.version ); } Err(e) => { diff --git a/crates/pm/src/util/node_search.rs b/crates/pm/src/util/node_search.rs index d7f38f638..8b5720a5a 100644 --- a/crates/pm/src/util/node_search.rs +++ b/crates/pm/src/util/node_search.rs @@ -34,9 +34,7 @@ pub async fn get_node_from_root_by_path(root: &Arc, pkg_path: &str) -> Res }; if !found { - return Err(anyhow::anyhow!( - "Could not find package at path {pkg_path}" - )); + return Err(anyhow::anyhow!("Could not find package at path {pkg_path}")); } current_node = next_node.unwrap(); From 9da69022083fd829c19fee746c5f170013aebda8 Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Fri, 10 Oct 2025 11:36:36 +0800 Subject: [PATCH 6/9] chore: clone --- crates/pm/src/service/registry.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/pm/src/service/registry.rs b/crates/pm/src/service/registry.rs index 9b1dae99e..64154edd7 100644 --- a/crates/pm/src/service/registry.rs +++ b/crates/pm/src/service/registry.rs @@ -99,11 +99,11 @@ impl RegistryService { let versions_info = Self::extract_versions_info_from_full_manifest(&full_manifest, new_etag); - let versions_arc = Arc::new(versions_info); - PACKAGE_CACHE.set_versions(name, versions_arc.clone()); + let versions_arc = Arc::new(versions_info.clone()); + PACKAGE_CACHE.set_versions(name, versions_arc); // Async disk update - let versions_info_for_disk = (*versions_arc).clone(); + let versions_info_for_disk = versions_info.clone(); let name_for_disk = name.to_string(); tokio::spawn(async move { PACKAGE_CACHE @@ -111,16 +111,16 @@ impl RegistryService { .await; }); - Ok(PackageVersionsResult::Fresh((*versions_arc).clone())) + Ok(PackageVersionsResult::Fresh(versions_info)) } Err(e) if e.to_string().contains("Not modified") => { log_verbose(&format!("304 Not Modified for {name}, using disk cache")); // 304 response means our disk versions.json is valid if let Some(versions_info) = disk_versions { - let versions_arc = Arc::new(versions_info); - PACKAGE_CACHE.set_versions(name, versions_arc.clone()); - Ok(PackageVersionsResult::Cached((*versions_arc).clone())) + let versions_arc = Arc::new(versions_info.clone()); + PACKAGE_CACHE.set_versions(name, versions_arc); + Ok(PackageVersionsResult::Cached(versions_info)) } else { Err(anyhow::anyhow!( "Received 304 Not Modified but no disk cache available for {name}" From cc5946a57ba0d6fee3ec8fd27567bf2e34bff67d Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Fri, 10 Oct 2025 14:08:17 +0800 Subject: [PATCH 7/9] chore: remove arc clone --- crates/pm/src/service/cache.rs | 116 +++++++++--------------------- crates/pm/src/service/registry.rs | 78 ++++++++------------ 2 files changed, 62 insertions(+), 132 deletions(-) diff --git a/crates/pm/src/service/cache.rs b/crates/pm/src/service/cache.rs index 242f2f353..ec195909c 100644 --- a/crates/pm/src/service/cache.rs +++ b/crates/pm/src/service/cache.rs @@ -6,7 +6,6 @@ use serde_json::Value; use std::collections::HashMap; use std::path::Path; use std::sync::Arc; -use std::time::{SystemTime, UNIX_EPOCH}; use crate::model::manifest::{FullManifest, VersionManifest as ModelVersionManifest}; use crate::util::cache::{get_package_manifest_cache_file, get_package_versions_cache_file}; @@ -32,28 +31,6 @@ pub struct Versions { pub dist_tags: HashMap, } -// Individual version manifest stored in manifests/version.json -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct VersionManifest { - pub manifest: Value, // specific version manifest data - pub last_updated: u64, // Unix timestamp -} - -// Memory cache structures for high-performance sync access -#[derive(Debug, Clone)] -struct CachedFullManifest { - manifest: Arc, -} - -#[derive(Debug)] -struct CachedVersionsInfo { - info: Arc, -} - -#[derive(Debug, Clone)] -struct CachedVersionManifest { - manifest: Arc, -} pub static PACKAGE_CACHE: Lazy = Lazy::new(PackageCache::new); @@ -62,10 +39,10 @@ pub struct PackageCache { // Project-level cache (async for backward compatibility) project_cache: Arc>, - // High-performance sync memory caches - full_manifests: Arc>, - versions_info: Arc>, - version_manifests: Arc>, // key: "name@version" + // High-performance sync memory caches - Arc is internal implementation detail + full_manifests: DashMap>, + versions_info: DashMap>, + version_manifests: DashMap>, // key: "name@version" } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -83,66 +60,65 @@ impl PackageCache { pub fn new() -> Self { Self { project_cache: Arc::new(tokio::sync::RwLock::new(HashMap::new())), - full_manifests: Arc::new(DashMap::new()), - versions_info: Arc::new(DashMap::new()), - version_manifests: Arc::new(DashMap::new()), + full_manifests: DashMap::new(), + versions_info: DashMap::new(), + version_manifests: DashMap::new(), } } // === Synchronous memory cache operations === /// Get full manifest from memory cache (sync, high performance) - pub fn get_full_manifest(&self, name: &str) -> Option { - self.full_manifests.get(name).map(|cached| { + /// Returns Arc for efficient sharing, auto-derefs for reading + pub fn get_full_manifest(&self, name: &str) -> Option> { + self.full_manifests.get(name).map(|entry| { log_verbose(&format!("Memory cache hit for full manifest: {name}")); - (*cached.manifest).clone() + Arc::clone(entry.value()) }) } /// Set full manifest in memory cache (sync) - pub fn set_full_manifest(&self, name: &str, manifest: &FullManifest) { - let cached = CachedFullManifest { - manifest: Arc::new(manifest.clone()), - }; - self.full_manifests.insert(name.to_string(), cached); + /// Accepts ownership to avoid clone + pub fn set_full_manifest(&self, name: String, manifest: FullManifest) { + self.full_manifests.insert(name.clone(), Arc::new(manifest)); log_verbose(&format!("Cached full manifest in memory: {name}")); } /// Get versions info from memory cache (sync) - pub fn get_versions(&self, name: &str) -> Option<(Option, VersionsInfo)> { - self.versions_info.get(name).map(|cached| { + /// Returns Arc for efficient sharing + pub fn get_versions(&self, name: &str) -> Option> { + self.versions_info.get(name).map(|entry| { log_verbose(&format!("Memory cache hit for versions: {name}")); - (cached.info.etag.clone(), (*cached.info).clone()) + Arc::clone(entry.value()) }) } /// Set versions info in memory cache (sync) - pub fn set_versions(&self, name: &str, versions: Arc) { - let cached = CachedVersionsInfo { info: versions }; - self.versions_info.insert(name.to_string(), cached); + /// Accepts ownership to avoid clone + pub fn set_versions(&self, name: String, info: VersionsInfo) { + self.versions_info.insert(name.clone(), Arc::new(info)); log_verbose(&format!("Cached versions info in memory: {name}")); } /// Get version manifest from memory cache (sync) - pub fn get_version_manifest(&self, name: &str, version: &str) -> Option { + /// Returns Arc for efficient sharing + pub fn get_version_manifest(&self, name: &str, version: &str) -> Option> { let key = format!("{name}@{version}"); - self.version_manifests.get(&key).map(|cached| { + self.version_manifests.get(&key).map(|entry| { log_verbose(&format!( "Memory cache hit for version manifest: {name}@{version}" )); - (*cached.manifest).clone() + Arc::clone(entry.value()) }) } /// Set version manifest in memory cache (sync) - pub fn set_version_manifest(&self, name: &str, version: &str, manifest: &ModelVersionManifest) { + /// Accepts ownership to avoid clone + pub fn set_version_manifest(&self, name: String, version: String, manifest: ModelVersionManifest) { let key = format!("{name}@{version}"); - let cached = CachedVersionManifest { - manifest: Arc::new(manifest.clone()), - }; - self.version_manifests.insert(key, cached); + self.version_manifests.insert(key.clone(), Arc::new(manifest)); log_verbose(&format!( - "Cached version manifest in memory: {name}@{version}" + "Cached version manifest in memory: {key}" )); } @@ -216,23 +192,10 @@ impl PackageCache { } match tokio::fs::read_to_string(&manifest_file).await { - Ok(content) => match serde_json::from_str::(&content) { - Ok(cached_manifest) => { + Ok(content) => match serde_json::from_str::(&content) { + Ok(manifest) => { log_verbose(&format!("Loaded manifest from disk: {name}@{version}")); - // Convert to ModelVersionManifest - Some( - serde_json::from_value(cached_manifest.manifest).unwrap_or_else(|e| { - log_verbose(&format!( - "Failed to convert cached manifest for {name}@{version}: {e}" - )); - // Return a minimal version manifest as fallback - ModelVersionManifest { - name: name.to_string(), - version: version.to_string(), - ..Default::default() - } - }), - ) + Some(manifest) } Err(e) => { log_verbose(&format!( @@ -264,20 +227,7 @@ impl PackageCache { let _ = tokio::fs::create_dir_all(parent).await; } - let version_manifest = VersionManifest { - manifest: serde_json::to_value(manifest).unwrap_or_else(|e| { - log_verbose(&format!( - "Failed to serialize manifest for {name}@{version}: {e}" - )); - serde_json::json!({}) - }), - last_updated: SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_secs(), - }; - - match serde_json::to_string_pretty(&version_manifest) { + match serde_json::to_string_pretty(manifest) { Ok(content) => { if let Err(e) = tokio::fs::write(&manifest_file, content).await { log_verbose(&format!( diff --git a/crates/pm/src/service/registry.rs b/crates/pm/src/service/registry.rs index 64154edd7..899225ce3 100644 --- a/crates/pm/src/service/registry.rs +++ b/crates/pm/src/service/registry.rs @@ -32,14 +32,6 @@ pub struct ResolvedPackage { pub version: String, } -/// Result types for new caching architecture -#[derive(Debug, Clone)] -pub enum PackageVersionsResult { - /// From memory cache, already 304 verified - Cached(VersionsInfo), - /// New network data (200 response) - Fresh(VersionsInfo), -} pub struct RegistryService; @@ -66,61 +58,53 @@ impl RegistryService { } /// Resolve package versions with smart caching strategy - /// Priority: memory full-manifest > memory versions > disk versions.json > network - pub async fn resolve_package_versions(name: &str) -> Result { + /// Priority: memory versions > disk versions.json > network + /// Returns Arc for efficient sharing without cloning + pub async fn resolve_package_versions(name: &str) -> Result> { log_verbose(&format!("Resolving package versions for: {name}")); - // 1. Check memory full-manifest cache (highest priority, already 304 verified) - if let Some((_etag, versions_info)) = PACKAGE_CACHE.get_versions(name) { - log_verbose(&format!("Using cached full manifest for versions: {name}")); - return Ok(PackageVersionsResult::Fresh(versions_info)); - } - - // 2. Check memory versions cache (already 304 verified) - if let Some((_etag, cached_versions)) = PACKAGE_CACHE.get_versions(name) { + // 1. Check memory versions cache (already 304 verified) + if let Some(versions_info_arc) = PACKAGE_CACHE.get_versions(name) { log_verbose(&format!("Using cached versions info for: {name}")); - return Ok(PackageVersionsResult::Cached(cached_versions)); + return Ok(versions_info_arc); } - // 3. Load from disk and make network request with etag + // 2. Load from disk and make network request with etag let (etag, disk_versions) = PACKAGE_CACHE.get_versions_from_disk(name).await; log_verbose(&format!("Loaded etag from disk for {name}: {etag:?}")); - // 4. Network request with etag for 304 validation + // 3. Network request with etag for 304 validation match fetch_full_manifest(name, etag.as_deref()).await { Ok((full_manifest, new_etag)) => { log_verbose(&format!("Received fresh full manifest for: {name}")); - // Store in memory full-manifest cache (sync) - // For version_manifest fetch - PACKAGE_CACHE.set_full_manifest(name, &full_manifest); - // Convert full manifest to versions info for disk cache let versions_info = Self::extract_versions_info_from_full_manifest(&full_manifest, new_etag); - let versions_arc = Arc::new(versions_info.clone()); - PACKAGE_CACHE.set_versions(name, versions_arc); + // Store in memory caches (sync) - accept ownership to avoid clone + PACKAGE_CACHE.set_full_manifest(name.to_string(), full_manifest); + PACKAGE_CACHE.set_versions(name.to_string(), versions_info.clone()); // Async disk update - let versions_info_for_disk = versions_info.clone(); let name_for_disk = name.to_string(); + let versions_info_for_disk = versions_info.clone(); tokio::spawn(async move { PACKAGE_CACHE .set_versions_to_disk(&name_for_disk, &versions_info_for_disk) .await; }); - Ok(PackageVersionsResult::Fresh(versions_info)) + // Return Arc from cache (just set) + Ok(PACKAGE_CACHE.get_versions(name).unwrap()) } Err(e) if e.to_string().contains("Not modified") => { log_verbose(&format!("304 Not Modified for {name}, using disk cache")); // 304 response means our disk versions.json is valid if let Some(versions_info) = disk_versions { - let versions_arc = Arc::new(versions_info.clone()); - PACKAGE_CACHE.set_versions(name, versions_arc); - Ok(PackageVersionsResult::Cached(versions_info)) + PACKAGE_CACHE.set_versions(name.to_string(), versions_info); + Ok(PACKAGE_CACHE.get_versions(name).unwrap()) } else { Err(anyhow::anyhow!( "Received 304 Not Modified but no disk cache available for {name}" @@ -169,24 +153,25 @@ impl RegistryService { log_verbose(&format!("Resolving version manifest for: {name}@{version}")); // 1. Check memory cache (sync, highest performance) - if let Some(cached_manifest) = PACKAGE_CACHE.get_version_manifest(name, version) { + if let Some(cached_manifest_arc) = PACKAGE_CACHE.get_version_manifest(name, version) { log_verbose(&format!( "Memory cache hit for version manifest: {name}@{version}" )); - return Ok(cached_manifest); + return Ok((*cached_manifest_arc).clone()); } // 2. Check memory by full_manifest cache (already 304 verified) - if let Some(full_manifest) = PACKAGE_CACHE.get_full_manifest(name) { + if let Some(full_manifest_arc) = PACKAGE_CACHE.get_full_manifest(name) { log_verbose(&format!("Using cached versions info for: {name}")); - let manifest_res = full_manifest.versions.get(version).cloned(); + let manifest_res = full_manifest_arc.versions.get(version).cloned(); if let Some(manifest) = manifest_res { - PACKAGE_CACHE.set_version_manifest(name, version, &manifest); - // Async disk write (non-blocking) + // Update memory cache - async disk write (non-blocking) let name_clone = name.to_string(); let version_clone = version.to_string(); let manifest_clone = manifest.clone(); + PACKAGE_CACHE.set_version_manifest(name.to_string(), version.to_string(), manifest.clone()); + tokio::spawn(async move { PACKAGE_CACHE .set_version_manifest_to_disk(&name_clone, &version_clone, &manifest_clone) @@ -206,7 +191,7 @@ impl RegistryService { )); // Update memory cache immediately (sync) - PACKAGE_CACHE.set_version_manifest(name, version, &cached_manifest); + PACKAGE_CACHE.set_version_manifest(name.to_string(), version.to_string(), cached_manifest.clone()); return Ok(cached_manifest); } @@ -225,7 +210,7 @@ impl RegistryService { )); // 1. Update memory cache immediately (sync) - PACKAGE_CACHE.set_version_manifest(name, version, &manifest); + PACKAGE_CACHE.set_version_manifest(name.to_string(), version.to_string(), manifest.clone()); // 2. Async disk write (non-blocking) let name_clone = name.to_string(); @@ -288,16 +273,11 @@ impl RegistryService { log_verbose(&format!("Using non-semver registry for: {name}@{spec}")); // 2. Resolve package versions using new caching architecture - let package_versions_result = Self::resolve_package_versions(name).await?; - - let versions_info = match package_versions_result { - PackageVersionsResult::Cached(versions_info) => versions_info, - PackageVersionsResult::Fresh(versions_info) => versions_info, - }; + let versions_info_arc = Self::resolve_package_versions(name).await?; - // 3. Check dist-tags - let dist_tags = versions_info.versions.dist_tags; - let version_list = versions_info.versions.version_list; + // 3. Check dist-tags (Arc auto-derefs) + let dist_tags = &versions_info_arc.versions.dist_tags; + let version_list = &versions_info_arc.versions.version_list; let target_version = match dist_tags.get(spec) { Some(version) => version.to_string(), From badb0ce4055ac6af04c8604b28e07ed7a0d11b40 Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Fri, 10 Oct 2025 14:09:57 +0800 Subject: [PATCH 8/9] chore: fmt --- crates/pm/src/service/cache.rs | 21 ++++++++++++++------- crates/pm/src/service/registry.rs | 19 +++++++++++++++---- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/crates/pm/src/service/cache.rs b/crates/pm/src/service/cache.rs index ec195909c..96cc2daab 100644 --- a/crates/pm/src/service/cache.rs +++ b/crates/pm/src/service/cache.rs @@ -31,7 +31,6 @@ pub struct Versions { pub dist_tags: HashMap, } - pub static PACKAGE_CACHE: Lazy = Lazy::new(PackageCache::new); #[derive(Debug)] @@ -102,7 +101,11 @@ impl PackageCache { /// Get version manifest from memory cache (sync) /// Returns Arc for efficient sharing - pub fn get_version_manifest(&self, name: &str, version: &str) -> Option> { + pub fn get_version_manifest( + &self, + name: &str, + version: &str, + ) -> Option> { let key = format!("{name}@{version}"); self.version_manifests.get(&key).map(|entry| { log_verbose(&format!( @@ -114,12 +117,16 @@ impl PackageCache { /// Set version manifest in memory cache (sync) /// Accepts ownership to avoid clone - pub fn set_version_manifest(&self, name: String, version: String, manifest: ModelVersionManifest) { + pub fn set_version_manifest( + &self, + name: String, + version: String, + manifest: ModelVersionManifest, + ) { let key = format!("{name}@{version}"); - self.version_manifests.insert(key.clone(), Arc::new(manifest)); - log_verbose(&format!( - "Cached version manifest in memory: {key}" - )); + self.version_manifests + .insert(key.clone(), Arc::new(manifest)); + log_verbose(&format!("Cached version manifest in memory: {key}")); } // === Async disk operations === diff --git a/crates/pm/src/service/registry.rs b/crates/pm/src/service/registry.rs index 899225ce3..66c63f00d 100644 --- a/crates/pm/src/service/registry.rs +++ b/crates/pm/src/service/registry.rs @@ -32,7 +32,6 @@ pub struct ResolvedPackage { pub version: String, } - pub struct RegistryService; impl RegistryService { @@ -170,7 +169,11 @@ impl RegistryService { let version_clone = version.to_string(); let manifest_clone = manifest.clone(); - PACKAGE_CACHE.set_version_manifest(name.to_string(), version.to_string(), manifest.clone()); + PACKAGE_CACHE.set_version_manifest( + name.to_string(), + version.to_string(), + manifest.clone(), + ); tokio::spawn(async move { PACKAGE_CACHE @@ -191,7 +194,11 @@ impl RegistryService { )); // Update memory cache immediately (sync) - PACKAGE_CACHE.set_version_manifest(name.to_string(), version.to_string(), cached_manifest.clone()); + PACKAGE_CACHE.set_version_manifest( + name.to_string(), + version.to_string(), + cached_manifest.clone(), + ); return Ok(cached_manifest); } @@ -210,7 +217,11 @@ impl RegistryService { )); // 1. Update memory cache immediately (sync) - PACKAGE_CACHE.set_version_manifest(name.to_string(), version.to_string(), manifest.clone()); + PACKAGE_CACHE.set_version_manifest( + name.to_string(), + version.to_string(), + manifest.clone(), + ); // 2. Async disk write (non-blocking) let name_clone = name.to_string(); From 8c9680f670d12853d6b7a80cc0500bda1aa3317f Mon Sep 17 00:00:00 2001 From: elrrrrrrr Date: Fri, 10 Oct 2025 14:35:50 +0800 Subject: [PATCH 9/9] chore: arc --- crates/pm/src/service/cache.rs | 25 +++++---- crates/pm/src/service/registry.rs | 91 +++++++++++++++++++------------ 2 files changed, 70 insertions(+), 46 deletions(-) diff --git a/crates/pm/src/service/cache.rs b/crates/pm/src/service/cache.rs index 96cc2daab..d10478bdb 100644 --- a/crates/pm/src/service/cache.rs +++ b/crates/pm/src/service/cache.rs @@ -77,10 +77,12 @@ impl PackageCache { } /// Set full manifest in memory cache (sync) - /// Accepts ownership to avoid clone - pub fn set_full_manifest(&self, name: String, manifest: FullManifest) { - self.full_manifests.insert(name.clone(), Arc::new(manifest)); + /// Accepts ownership to avoid clone, returns Arc for immediate use + pub fn set_full_manifest(&self, name: String, manifest: FullManifest) -> Arc { + let arc = Arc::new(manifest); + self.full_manifests.insert(name.clone(), Arc::clone(&arc)); log_verbose(&format!("Cached full manifest in memory: {name}")); + arc } /// Get versions info from memory cache (sync) @@ -93,10 +95,12 @@ impl PackageCache { } /// Set versions info in memory cache (sync) - /// Accepts ownership to avoid clone - pub fn set_versions(&self, name: String, info: VersionsInfo) { - self.versions_info.insert(name.clone(), Arc::new(info)); + /// Accepts ownership to avoid clone, returns Arc for immediate use + pub fn set_versions(&self, name: String, info: VersionsInfo) -> Arc { + let arc = Arc::new(info); + self.versions_info.insert(name.clone(), Arc::clone(&arc)); log_verbose(&format!("Cached versions info in memory: {name}")); + arc } /// Get version manifest from memory cache (sync) @@ -116,17 +120,18 @@ impl PackageCache { } /// Set version manifest in memory cache (sync) - /// Accepts ownership to avoid clone + /// Accepts ownership to avoid clone, returns Arc for immediate use pub fn set_version_manifest( &self, name: String, version: String, manifest: ModelVersionManifest, - ) { + ) -> Arc { let key = format!("{name}@{version}"); - self.version_manifests - .insert(key.clone(), Arc::new(manifest)); + let arc = Arc::new(manifest); + self.version_manifests.insert(key.clone(), Arc::clone(&arc)); log_verbose(&format!("Cached version manifest in memory: {key}")); + arc } // === Async disk operations === diff --git a/crates/pm/src/service/registry.rs b/crates/pm/src/service/registry.rs index 66c63f00d..3d435290c 100644 --- a/crates/pm/src/service/registry.rs +++ b/crates/pm/src/service/registry.rs @@ -81,29 +81,30 @@ impl RegistryService { let versions_info = Self::extract_versions_info_from_full_manifest(&full_manifest, new_etag); - // Store in memory caches (sync) - accept ownership to avoid clone + // Store in memory caches (sync) - take ownership, zero clone + // set_* methods return Arc, no unwrap needed! PACKAGE_CACHE.set_full_manifest(name.to_string(), full_manifest); - PACKAGE_CACHE.set_versions(name.to_string(), versions_info.clone()); + let versions_arc = PACKAGE_CACHE.set_versions(name.to_string(), versions_info); - // Async disk update + // Async disk update - Arc clone is cheap let name_for_disk = name.to_string(); - let versions_info_for_disk = versions_info.clone(); + let versions_arc_for_disk = Arc::clone(&versions_arc); tokio::spawn(async move { PACKAGE_CACHE - .set_versions_to_disk(&name_for_disk, &versions_info_for_disk) + .set_versions_to_disk(&name_for_disk, &versions_arc_for_disk) .await; }); - // Return Arc from cache (just set) - Ok(PACKAGE_CACHE.get_versions(name).unwrap()) + // Return Arc (zero-cost) + Ok(versions_arc) } Err(e) if e.to_string().contains("Not modified") => { log_verbose(&format!("304 Not Modified for {name}, using disk cache")); // 304 response means our disk versions.json is valid if let Some(versions_info) = disk_versions { - PACKAGE_CACHE.set_versions(name.to_string(), versions_info); - Ok(PACKAGE_CACHE.get_versions(name).unwrap()) + let versions_arc = PACKAGE_CACHE.set_versions(name.to_string(), versions_info); + Ok(versions_arc) } else { Err(anyhow::anyhow!( "Received 304 Not Modified but no disk cache available for {name}" @@ -148,7 +149,11 @@ impl RegistryService { /// Resolve specific version manifest with three-tier caching /// Priority: memory > disk > network - pub async fn resolve_version_manifest(name: &str, version: &str) -> Result { + /// Returns Arc for efficient sharing without cloning + pub async fn resolve_version_manifest( + name: &str, + version: &str, + ) -> Result> { log_verbose(&format!("Resolving version manifest for: {name}@{version}")); // 1. Check memory cache (sync, highest performance) @@ -156,31 +161,37 @@ impl RegistryService { log_verbose(&format!( "Memory cache hit for version manifest: {name}@{version}" )); - return Ok((*cached_manifest_arc).clone()); + return Ok(cached_manifest_arc); } // 2. Check memory by full_manifest cache (already 304 verified) if let Some(full_manifest_arc) = PACKAGE_CACHE.get_full_manifest(name) { log_verbose(&format!("Using cached versions info for: {name}")); - let manifest_res = full_manifest_arc.versions.get(version).cloned(); - if let Some(manifest) = manifest_res { - // Update memory cache - async disk write (non-blocking) - let name_clone = name.to_string(); - let version_clone = version.to_string(); - let manifest_clone = manifest.clone(); - - PACKAGE_CACHE.set_version_manifest( + if let Some(manifest) = full_manifest_arc.versions.get(version) { + // Update memory cache (takes ownership via clone from HashMap) + // set_version_manifest returns Arc, no unwrap needed! + let manifest_arc = PACKAGE_CACHE.set_version_manifest( name.to_string(), version.to_string(), - manifest.clone(), + manifest.clone(), // Clone from HashMap value ); + let name_for_disk = name.to_string(); + let version_for_disk = version.to_string(); + let manifest_arc_for_disk = Arc::clone(&manifest_arc); + // Async disk write (non-blocking) tokio::spawn(async move { PACKAGE_CACHE - .set_version_manifest_to_disk(&name_clone, &version_clone, &manifest_clone) + .set_version_manifest_to_disk( + &name_for_disk, + &version_for_disk, + &manifest_arc_for_disk, + ) .await; }); - return Ok(manifest); + + // Return Arc (zero-cost) + return Ok(manifest_arc); } } @@ -193,13 +204,16 @@ impl RegistryService { "Disk cache hit for version manifest: {name}@{version}" )); - // Update memory cache immediately (sync) - PACKAGE_CACHE.set_version_manifest( + // Update memory cache immediately (takes ownership) + // set_version_manifest returns Arc, no unwrap needed! + let manifest_arc = PACKAGE_CACHE.set_version_manifest( name.to_string(), version.to_string(), - cached_manifest.clone(), + cached_manifest, ); - return Ok(cached_manifest); + + // Return Arc (zero-cost) + return Ok(manifest_arc); } // 4. Network request as last resort @@ -216,25 +230,30 @@ impl RegistryService { "Successfully fetched version manifest: {name}@{version}" )); - // 1. Update memory cache immediately (sync) - PACKAGE_CACHE.set_version_manifest( + // Update memory cache (takes ownership, zero clone) + // set_version_manifest returns Arc, no unwrap needed! + let manifest_arc = PACKAGE_CACHE.set_version_manifest( name.to_string(), version.to_string(), - manifest.clone(), + manifest, ); + let name_for_disk = name.to_string(); + let version_for_disk = version.to_string(); + let manifest_arc_for_disk = Arc::clone(&manifest_arc); - // 2. Async disk write (non-blocking) - let name_clone = name.to_string(); - let version_clone = version.to_string(); - let manifest_clone = manifest.clone(); - + // Async disk write (non-blocking) tokio::spawn(async move { PACKAGE_CACHE - .set_version_manifest_to_disk(&name_clone, &version_clone, &manifest_clone) + .set_version_manifest_to_disk( + &name_for_disk, + &version_for_disk, + &manifest_arc_for_disk, + ) .await; }); - Ok(manifest) + // Return Arc (zero-cost) + Ok(manifest_arc) } Err(e) => { log_verbose(&format!(