pkgrisk 0.1.0

5-second package health and risk analysis for npm, PyPI and crates.io dependencies
use super::PackageMetadata;
use crate::http_client::HttpClient;
use serde_json::Value;

pub async fn fetch_metadata(client: &HttpClient, package: &str) -> anyhow::Result<PackageMetadata> {
    let url = format!("https://registry.npmjs.org/{}", package);
    let data: Value = client.get_json(&url).await?;

    let latest_version = data["dist-tags"]["latest"]
        .as_str()
        .unwrap_or("unknown")
        .to_string();

    let latest_data = &data["versions"][&latest_version];

    let mut dependencies = Vec::new();
    if let Some(deps) = latest_data["dependencies"].as_object() {
        for (k, _) in deps {
            dependencies.push(k.clone());
        }
    }

    let repo_url = data["repository"]["url"].as_str().map(|s| {
        s.replace("git+", "").replace("git://", "https://")
    });

    let time_obj = data["time"].as_object();
    let release_date = time_obj.and_then(|t| t.get(&latest_version)).and_then(|s| s.as_str()).map(|s| s.to_string());

    Ok(PackageMetadata {
        name: package.to_string(),
        version: latest_version,
        description: data["description"].as_str().map(|s| s.to_string()),
        license: data["license"].as_str().map(|s| s.to_string()),
        repository_url: repo_url,
        latest_release_date: release_date,
        dependencies,
        weekly_downloads: fetch_downloads(client, package).await.ok(),
    })
}

async fn fetch_downloads(client: &HttpClient, package: &str) -> anyhow::Result<u64> {
    let url = format!("https://api.npmjs.org/downloads/point/last-week/{}", package);
    let data: Value = client.get_json(&url).await?;
    Ok(data["downloads"].as_u64().unwrap_or(0))
}