Files
vulp/src/release_info.rs
T
dualfroz 0468c032c6 Phases 0-1 + Sprint A: module split, CLI layer, org-refresh fix, hygiene
- LICENSE (MIT) + README truth-pass; docs/cli.md CLI contract
- CLI subcommands (src/cli_cmds/) per "TUI for users, CLI for users+AI"
- Hub/detail refactor: keymap dispatch, modal split, action enum, BgMsg bus
- Fix: org repos were never refreshed (owner==username match); refresh now
  parallel — 49 repos in ~11s, surfaced 70 previously invisible PRs
- Fix: 3 date bugs in detail/git.rs (format-local, Local::now, 1y month math)
- Stash indicator + [m] actions, session persistence, gh CLI token fallback
- Legacy UI modules removed; plan/ docs (roadmap, audit, debate, handoff)
2026-07-06 14:42:52 +02:00

79 lines
3.4 KiB
Rust

//! Fetches GitHub release metadata for the hub's General Settings update
//! tile: the latest published release, and (separately) the release that
//! matches the version currently running.
use anyhow::{Context, Result};
use serde_json::Value;
/// The GitHub repo this binary is published from. Shared with the installer
/// scripts (`install/vulp-installer.sh`/`.ps1`), which hit the same
/// `releases/latest` endpoint to fetch the newest build.
pub const VULP_REPO: &str = "dualfroz/fxg";
/// One GitHub release's metadata, trimmed to what the update tile shows.
#[derive(Debug, Clone)]
pub struct ReleaseInfo {
pub tag: String,
pub author: String,
pub published_at: String,
#[allow(dead_code)] pub body: String, // kept: release-notes preview (roadmap)
pub html_url: String,
}
async fn fetch_release(url: &str) -> Result<ReleaseInfo> {
let client = reqwest::Client::builder()
.user_agent("vulp/0.1")
.timeout(std::time::Duration::from_secs(15))
.build()?;
let resp = client.get(url).send().await?;
let status = resp.status();
let text = resp.text().await?;
if !status.is_success() {
anyhow::bail!("GitHub API {} — {}", status, text);
}
let v: Value = serde_json::from_str(&text).context("parsing release JSON")?;
Ok(ReleaseInfo {
tag: v["tag_name"].as_str().unwrap_or_default().to_string(),
author: v["author"]["login"].as_str().unwrap_or("unknown").to_string(),
published_at: v["published_at"].as_str().unwrap_or_default().to_string(),
body: v["body"].as_str().unwrap_or_default().chars().take(200).collect(),
html_url: v["html_url"].as_str().unwrap_or_default().to_string(),
})
}
/// Info about the very latest release on GitHub, regardless of what's running.
pub async fn fetch_latest() -> Result<ReleaseInfo> {
fetch_release(&format!("https://api.github.com/repos/{VULP_REPO}/releases/latest")).await
}
/// Info about the release matching the version actually running, if one was
/// tagged (`v{CARGO_PKG_VERSION}`) — lets the tile show who shipped *this*
/// build and when, not just what's newest.
pub async fn fetch_current(version: &str) -> Result<ReleaseInfo> {
fetch_release(&format!("https://api.github.com/repos/{VULP_REPO}/releases/tags/v{version}")).await
}
/// The `created_at` date (YYYY-MM-DD) of a GitHub user/org — for the General
/// Settings tile's account "since" line. Unauthenticated public endpoint.
pub async fn fetch_user_since(username: &str) -> Result<String> {
let client = reqwest::Client::builder()
.user_agent("vulp/0.1")
.timeout(std::time::Duration::from_secs(15))
.build()?;
let resp = client.get(format!("https://api.github.com/users/{username}")).send().await?;
let status = resp.status();
let text = resp.text().await?;
if !status.is_success() {
anyhow::bail!("GitHub API {} — {}", status, text);
}
let v: Value = serde_json::from_str(&text).context("parsing user JSON")?;
let created = v["created_at"].as_str().unwrap_or_default();
Ok(created.split('T').next().unwrap_or_default().to_string())
}
/// `true` when the latest published tag differs from the version running.
pub fn is_outdated(current_version: &str, latest_tag: &str) -> bool {
let latest = latest_tag.trim_start_matches('v');
!latest.is_empty() && latest != current_version
}