#![allow(missing_docs)]
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use serde::{Deserialize, Serialize};
const DEFAULT_REPO: &str = "BurntSushi/ripgrep";
const DEFAULT_PRETEND_VERSION: &str = "0.0.1";
const CACHE_KEY: &str = "latest-version";
#[derive(Debug, Deserialize, Serialize)]
#[serde(default)]
struct Config {
log_level: librebar::config::LogLevel,
repo: String,
pretend_version: String,
}
impl Default for Config {
fn default() -> Self {
Self {
log_level: librebar::config::LogLevel::Info,
repo: DEFAULT_REPO.to_string(),
pretend_version: DEFAULT_PRETEND_VERSION.to_string(),
}
}
}
#[derive(Parser)]
#[command(
name = "updater",
about = "GitHub release check with 24h cache and env suppression"
)]
struct Cli {
#[command(flatten)]
common: librebar::cli::CommonArgs,
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Subcommand)]
enum Command {
Check,
Info,
ClearCache,
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let cli = Cli::parse();
cli.common.apply_color();
cli.common.apply_chdir()?;
let app = librebar::init("updater")
.with_version(env!("CARGO_PKG_VERSION"))
.with_cli(cli.common)
.config::<Config>()
.logging()
.start()?;
match cli.command.unwrap_or(Command::Info) {
Command::Check => run_check(&app).await,
Command::Info => print_info(&app),
Command::ClearCache => clear_cache(&app),
}
}
async fn run_check(app: &librebar::App<Config>) -> Result<()> {
let config = app.config();
let checker =
librebar::update::UpdateChecker::new(app.app_name(), &config.pretend_version, &config.repo);
if checker.is_suppressed() {
println!(
"update check suppressed by {}_NO_UPDATE_CHECK",
app.app_name().to_uppercase()
);
return Ok(());
}
println!(
"checking {} for releases (pretending to run v{})...",
config.repo, config.pretend_version
);
match checker.check().await {
Some(info) => {
tracing::info!(
latest = %info.latest,
current = %info.current,
"update available",
);
println!("{}", info.message());
}
None => {
tracing::info!("no update available or check failed");
println!("no newer release found (or the check failed — run with -v for details)");
}
}
Ok(())
}
fn print_info(app: &librebar::App<Config>) -> Result<()> {
let config = app.config();
let suppress_var = format!("{}_NO_UPDATE_CHECK", app.app_name().to_uppercase());
let suppressed = std::env::var(&suppress_var)
.ok()
.is_some_and(|v| v == "1" || v.eq_ignore_ascii_case("true"));
let cache = librebar::cache::Cache::default_for(app.app_name())
.context("cache directory is not available on this platform")?;
let cache_entry = cache.get(CACHE_KEY)?;
println!("app: {} v{}", app.app_name(), app.version());
println!("sources: {:?}", app.config_sources());
println!("repo: {}", config.repo);
println!("pretending: v{}", config.pretend_version);
println!("cache dir: {}", cache.dir().display());
match cache_entry {
Some(bytes) => {
let latest = String::from_utf8_lossy(&bytes);
println!("cache: fresh — latest-version={latest}");
}
None => println!("cache: empty (next check will hit the network)"),
}
println!(
"{}: {}",
suppress_var,
if suppressed {
"set (check will be skipped)"
} else {
"unset"
}
);
Ok(())
}
fn clear_cache(app: &librebar::App<Config>) -> Result<()> {
let cache = librebar::cache::Cache::default_for(app.app_name())
.context("cache directory is not available on this platform")?;
cache.clear()?;
println!("cache cleared: {}", cache.dir().display());
Ok(())
}