Files
vulp/src/release_info.rs
T
dualfroz cd4e8e92ea feat: respect $VULP_REPO env override for update-check; add CI workflow
The compiled binary's update-check tile hardcoded dualfroz/fxg with no
way to override it, unlike the install scripts which already honor
$VULP_REPO — a fork or a future repo rename needed a rebuild just to
fix the tile. Also add .github/workflows/ci.yml (build + clippy -D
warnings + test on push/PR): the only existing workflow was the
tag-triggered release build, so nothing gated PRs before merge.
2026-07-23 07:24:58 +02:00

83 lines
3.6 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. Overridable via
/// `$VULP_REPO` (same env var the installers already honor), so a fork or a
/// future rename doesn't require a rebuild just to fix the update-check tile.
fn vulp_repo() -> String {
std::env::var("VULP_REPO").unwrap_or_else(|_| "dualfroz/fxg".to_string())
}
/// 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/{}/releases/latest", vulp_repo())).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/{}/releases/tags/v{version}", vulp_repo())).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
}