Skip to content

Commit 8db6de5

Browse files
committed
fix: npm dashboard shows versions and sizes from metadata.json
1 parent 059b7c6 commit 8db6de5

File tree

1 file changed

+84
-44
lines changed

1 file changed

+84
-44
lines changed

nora-registry/src/ui/api.rs

Lines changed: 84 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -574,69 +574,109 @@ pub async fn get_maven_detail(storage: &Storage, path: &str) -> MavenDetail {
574574
pub async fn get_npm_packages(storage: &Storage) -> Vec<RepoInfo> {
575575
let keys = storage.list("npm/").await;
576576

577-
let mut packages: HashMap<String, (RepoInfo, u64)> = HashMap::new();
577+
let mut packages: HashMap<String, RepoInfo> = HashMap::new();
578578

579+
// Find all metadata.json files
579580
for key in &keys {
580-
if let Some(rest) = key.strip_prefix("npm/") {
581-
let parts: Vec<_> = rest.split('/').collect();
582-
if !parts.is_empty() {
583-
let name = parts[0].to_string();
584-
let entry = packages.entry(name.clone()).or_insert_with(|| {
585-
(
586-
RepoInfo {
587-
name,
588-
versions: 0,
589-
size: 0,
590-
updated: "N/A".to_string(),
591-
},
592-
0,
593-
)
594-
});
595-
596-
if parts.len() >= 3 && parts[1] == "tarballs" {
597-
entry.0.versions += 1;
598-
if let Some(meta) = storage.stat(key).await {
599-
entry.0.size += meta.size;
600-
if meta.modified > entry.1 {
601-
entry.1 = meta.modified;
602-
entry.0.updated = format_timestamp(meta.modified);
603-
}
581+
if key.ends_with("/metadata.json") {
582+
if let Some(name) = key
583+
.strip_prefix("npm/")
584+
.and_then(|s| s.strip_suffix("/metadata.json"))
585+
{
586+
// Parse metadata to get version count and info
587+
if let Ok(data) = storage.get(key).await {
588+
if let Ok(metadata) = serde_json::from_slice::<serde_json::Value>(&data) {
589+
let versions_count = metadata
590+
.get("versions")
591+
.and_then(|v| v.as_object())
592+
.map(|v| v.len())
593+
.unwrap_or(0);
594+
595+
// Calculate total size from dist.unpackedSize or estimate
596+
let total_size: u64 = metadata
597+
.get("versions")
598+
.and_then(|v| v.as_object())
599+
.map(|versions| {
600+
versions
601+
.values()
602+
.filter_map(|v| {
603+
v.get("dist")
604+
.and_then(|d| d.get("unpackedSize"))
605+
.and_then(|s| s.as_u64())
606+
})
607+
.sum()
608+
})
609+
.unwrap_or(0);
610+
611+
// Get latest version time for "updated"
612+
let updated = metadata
613+
.get("time")
614+
.and_then(|t| t.get("modified"))
615+
.and_then(|m| m.as_str())
616+
.map(|s| s[..10].to_string()) // Take just date part
617+
.unwrap_or_else(|| "N/A".to_string());
618+
619+
packages.insert(
620+
name.to_string(),
621+
RepoInfo {
622+
name: name.to_string(),
623+
versions: versions_count,
624+
size: total_size,
625+
updated,
626+
},
627+
);
604628
}
605629
}
606630
}
607631
}
608632
}
609633

610-
let mut result: Vec<_> = packages.into_values().map(|(r, _)| r).collect();
634+
let mut result: Vec<_> = packages.into_values().collect();
611635
result.sort_by(|a, b| a.name.cmp(&b.name));
612636
result
613637
}
614638

615639
pub async fn get_npm_detail(storage: &Storage, name: &str) -> PackageDetail {
616-
let prefix = format!("npm/{}/tarballs/", name);
617-
let keys = storage.list(&prefix).await;
640+
let metadata_key = format!("npm/{}/metadata.json", name);
618641

619642
let mut versions = Vec::new();
620-
for key in &keys {
621-
if let Some(tarball) = key.strip_prefix(&prefix) {
622-
if let Some(version) = tarball
623-
.strip_prefix(&format!("{}-", name))
624-
.and_then(|s| s.strip_suffix(".tgz"))
625-
{
626-
let (size, published) = if let Some(meta) = storage.stat(key).await {
627-
(meta.size, format_timestamp(meta.modified))
628-
} else {
629-
(0, "N/A".to_string())
630-
};
631-
versions.push(VersionInfo {
632-
version: version.to_string(),
633-
size,
634-
published,
635-
});
643+
644+
// Parse metadata.json for version info
645+
if let Ok(data) = storage.get(&metadata_key).await {
646+
if let Ok(metadata) = serde_json::from_slice::<serde_json::Value>(&data) {
647+
if let Some(versions_obj) = metadata.get("versions").and_then(|v| v.as_object()) {
648+
let time_obj = metadata.get("time").and_then(|t| t.as_object());
649+
650+
for (version, info) in versions_obj {
651+
let size = info
652+
.get("dist")
653+
.and_then(|d| d.get("unpackedSize"))
654+
.and_then(|s| s.as_u64())
655+
.unwrap_or(0);
656+
657+
let published = time_obj
658+
.and_then(|t| t.get(version))
659+
.and_then(|p| p.as_str())
660+
.map(|s| s[..10].to_string())
661+
.unwrap_or_else(|| "N/A".to_string());
662+
663+
versions.push(VersionInfo {
664+
version: version.clone(),
665+
size,
666+
published,
667+
});
668+
}
636669
}
637670
}
638671
}
639672

673+
// Sort by version (semver-like, newest first)
674+
versions.sort_by(|a, b| {
675+
let a_parts: Vec<u32> = a.version.split('.').filter_map(|s| s.parse().ok()).collect();
676+
let b_parts: Vec<u32> = b.version.split('.').filter_map(|s| s.parse().ok()).collect();
677+
b_parts.cmp(&a_parts)
678+
});
679+
640680
PackageDetail { versions }
641681
}
642682

0 commit comments

Comments
 (0)