fix: npm dashboard shows versions and sizes from metadata.json

This commit is contained in:
2026-01-31 09:16:24 +00:00
parent e6bd9b6ead
commit f76c6d6075

View File

@@ -574,69 +574,109 @@ pub async fn get_maven_detail(storage: &Storage, path: &str) -> MavenDetail {
pub async fn get_npm_packages(storage: &Storage) -> Vec<RepoInfo> { pub async fn get_npm_packages(storage: &Storage) -> Vec<RepoInfo> {
let keys = storage.list("npm/").await; let keys = storage.list("npm/").await;
let mut packages: HashMap<String, (RepoInfo, u64)> = HashMap::new(); let mut packages: HashMap<String, RepoInfo> = HashMap::new();
// Find all metadata.json files
for key in &keys { for key in &keys {
if let Some(rest) = key.strip_prefix("npm/") { if key.ends_with("/metadata.json") {
let parts: Vec<_> = rest.split('/').collect(); if let Some(name) = key
if !parts.is_empty() { .strip_prefix("npm/")
let name = parts[0].to_string(); .and_then(|s| s.strip_suffix("/metadata.json"))
let entry = packages.entry(name.clone()).or_insert_with(|| { {
( // Parse metadata to get version count and info
RepoInfo { if let Ok(data) = storage.get(key).await {
name, if let Ok(metadata) = serde_json::from_slice::<serde_json::Value>(&data) {
versions: 0, let versions_count = metadata
size: 0, .get("versions")
updated: "N/A".to_string(), .and_then(|v| v.as_object())
}, .map(|v| v.len())
0, .unwrap_or(0);
)
});
if parts.len() >= 3 && parts[1] == "tarballs" { // Calculate total size from dist.unpackedSize or estimate
entry.0.versions += 1; let total_size: u64 = metadata
if let Some(meta) = storage.stat(key).await { .get("versions")
entry.0.size += meta.size; .and_then(|v| v.as_object())
if meta.modified > entry.1 { .map(|versions| {
entry.1 = meta.modified; versions
entry.0.updated = format_timestamp(meta.modified); .values()
} .filter_map(|v| {
v.get("dist")
.and_then(|d| d.get("unpackedSize"))
.and_then(|s| s.as_u64())
})
.sum()
})
.unwrap_or(0);
// Get latest version time for "updated"
let updated = metadata
.get("time")
.and_then(|t| t.get("modified"))
.and_then(|m| m.as_str())
.map(|s| s[..10].to_string()) // Take just date part
.unwrap_or_else(|| "N/A".to_string());
packages.insert(
name.to_string(),
RepoInfo {
name: name.to_string(),
versions: versions_count,
size: total_size,
updated,
},
);
} }
} }
} }
} }
} }
let mut result: Vec<_> = packages.into_values().map(|(r, _)| r).collect(); let mut result: Vec<_> = packages.into_values().collect();
result.sort_by(|a, b| a.name.cmp(&b.name)); result.sort_by(|a, b| a.name.cmp(&b.name));
result result
} }
pub async fn get_npm_detail(storage: &Storage, name: &str) -> PackageDetail { pub async fn get_npm_detail(storage: &Storage, name: &str) -> PackageDetail {
let prefix = format!("npm/{}/tarballs/", name); let metadata_key = format!("npm/{}/metadata.json", name);
let keys = storage.list(&prefix).await;
let mut versions = Vec::new(); let mut versions = Vec::new();
for key in &keys {
if let Some(tarball) = key.strip_prefix(&prefix) { // Parse metadata.json for version info
if let Some(version) = tarball if let Ok(data) = storage.get(&metadata_key).await {
.strip_prefix(&format!("{}-", name)) if let Ok(metadata) = serde_json::from_slice::<serde_json::Value>(&data) {
.and_then(|s| s.strip_suffix(".tgz")) if let Some(versions_obj) = metadata.get("versions").and_then(|v| v.as_object()) {
{ let time_obj = metadata.get("time").and_then(|t| t.as_object());
let (size, published) = if let Some(meta) = storage.stat(key).await {
(meta.size, format_timestamp(meta.modified)) for (version, info) in versions_obj {
} else { let size = info
(0, "N/A".to_string()) .get("dist")
}; .and_then(|d| d.get("unpackedSize"))
versions.push(VersionInfo { .and_then(|s| s.as_u64())
version: version.to_string(), .unwrap_or(0);
size,
published, let published = time_obj
}); .and_then(|t| t.get(version))
.and_then(|p| p.as_str())
.map(|s| s[..10].to_string())
.unwrap_or_else(|| "N/A".to_string());
versions.push(VersionInfo {
version: version.clone(),
size,
published,
});
}
} }
} }
} }
// Sort by version (semver-like, newest first)
versions.sort_by(|a, b| {
let a_parts: Vec<u32> = a.version.split('.').filter_map(|s| s.parse().ok()).collect();
let b_parts: Vec<u32> = b.version.split('.').filter_map(|s| s.parse().ok()).collect();
b_parts.cmp(&a_parts)
});
PackageDetail { versions } PackageDetail { versions }
} }