use anyhow::{bail, Result};
use reqwest::Client;
use semver::Version;
use serde::Deserialize;
use crate::{download_binary, OneConfig, ReleaseDep, RepoProvider};
#[allow(unused)]
#[derive(Debug, Deserialize)]
pub struct GithubRelease {
id: u64,
tag_name: String,
name: Option<String>,
body: Option<String>,
draft: bool,
prerelease: bool,
created_at: String,
published_at: Option<String>,
assets: Vec<GithubAsset>,
}
#[allow(unused)]
#[derive(Debug, Deserialize)]
pub struct GithubAsset {
id: u64,
name: String,
browser_download_url: String,
size: u64,
download_count: u64,
}
pub async fn github_get_release(config: OneConfig) -> Result<ReleaseDep> {
let client = Client::new();
let url_info = &config.url_info;
let mut page = 1;
let per_page = 30; let mut has_next_page = true;
while has_next_page {
let url = format!(
"https://api.github.com/repos/{}/{}/releases?page={}&per_page={}",
url_info.owner, url_info.repo, page, per_page
);
println!("Fetching releases from: {url}");
println!("Processing page {page}");
let response = client
.get(&url)
.header("User-Agent", "release-dep/1.0")
.header("Accept", "application/vnd.github.v3+json")
.send()
.await?;
if !response.status().is_success() {
bail!(
"Failed to fetch releases from GitHub: {}",
response.status()
);
}
has_next_page = if let Some(link_header) = response.headers().get("link") {
if let Ok(link_str) = link_header.to_str() {
link_str.contains("rel=\"next\"")
} else {
false
}
} else {
false
};
let releases: Vec<GithubRelease> = response.json().await?;
if releases.is_empty() {
println!("No more releases found on page {page}");
break;
}
for release in releases {
if release.draft || release.prerelease {
continue;
}
let version_str = if release.tag_name.contains("-v") {
release
.tag_name
.split("-v")
.last()
.unwrap_or(&release.tag_name)
} else if release.tag_name.starts_with('v') {
&release.tag_name[1..]
} else {
&release.tag_name
};
println!(
"Parsing version from tag: {} -> {version_str}",
release.tag_name
);
if let Ok(release_version) = Version::parse(version_str) {
if config.version.matches(&release_version) {
println!(
"Found compatible version: {release_version} (requested: {})",
config.version
);
for asset in &release.assets {
if asset.name.contains(&config.package) {
println!("Found matching asset: {}", asset.name);
let download_dir =
config.download_dir.as_ref().map(|p| p.to_str().unwrap());
let binary_path = download_binary(
&client,
&asset.browser_download_url,
&asset.name,
download_dir,
RepoProvider::Github,
)
.await?;
println!("Successfully found and downloaded release on page {page}");
return Ok(ReleaseDep {
name: config.package.clone(),
version: release_version,
binary: binary_path,
});
}
}
println!("No matching binary found for release {}", release.tag_name);
}
}
}
page += 1;
}
bail!(
"No compatible release found for package {} with version {}",
config.package,
config.version
);
}