use clap::Args;
use crate::cli::GlobalArgs;
use crate::error::{
OlError, OL_4250_UPDATE_FETCH_FAILED, OL_4253_UPDATE_APPLY_FAILED,
OL_4258_UPDATE_REFUSED_CARGO_INSTALL, OL_4259_UPDATE_BLOCKED_MIN_SUPPORTED,
};
use crate::update::{
apply_local, check, ApplyMode, ApplyOptions, ApplyResult, ApplyStage, CheckResult,
};
#[derive(Args, Debug, Clone)]
pub struct UpdateArgs {
#[arg(long)]
pub check: bool,
#[arg(long)]
pub apply: bool,
#[arg(long, value_name = "URL")]
pub registry: Option<String>,
#[arg(long)]
pub force_cargo: bool,
}
pub async fn run(g: &GlobalArgs, args: UpdateArgs) -> Result<(), OlError> {
let registry = args
.registry
.clone()
.or_else(|| std::env::var("OPENLATCH_PROVIDER_NPM_REGISTRY").ok())
.unwrap_or_else(|| "https://registry.npmjs.org".to_string());
let current = env!("CARGO_PKG_VERSION").to_string();
if args.check && args.apply {
return Err(OlError::new(
OL_4250_UPDATE_FETCH_FAILED,
"--check and --apply are mutually exclusive",
));
}
if !args.check && !args.apply {
return run_check(g, ¤t, ®istry).await;
}
if args.check {
return run_check(g, ¤t, ®istry).await;
}
if !g.yes && !args.force_cargo {
return Err(OlError::new(
OL_4250_UPDATE_FETCH_FAILED,
"refusing to apply without --yes (non-interactive safety)",
)
.with_suggestion("Re-run with: openlatch-provider update --apply --yes"));
}
let opts = ApplyOptions {
current_version: current.clone(),
registry_origin: registry.clone(),
download_timeout: std::time::Duration::from_secs(60),
force_cargo_install: args.force_cargo,
mode: ApplyMode::InProcess,
};
match apply_local(opts).await {
ApplyResult::Applied { from, to, .. } => {
if !g.quiet {
eprintln!("openlatch-provider update applied: {from} -> {to}");
}
Ok(())
}
ApplyResult::UpToDate { current } => {
if !g.quiet {
eprintln!("openlatch-provider already on latest version: {current}");
}
Ok(())
}
ApplyResult::RefusedCargoInstall { suggestion } => Err(OlError::new(
OL_4258_UPDATE_REFUSED_CARGO_INSTALL,
"auto-update refused: cargo-installed binary detected",
)
.with_suggestion(suggestion)),
ApplyResult::Failed { stage, reason } => {
let code = match stage {
ApplyStage::Check => OL_4250_UPDATE_FETCH_FAILED,
_ => OL_4253_UPDATE_APPLY_FAILED,
};
if reason.contains("min_supported_provider") {
return Err(OlError::new(OL_4259_UPDATE_BLOCKED_MIN_SUPPORTED, reason));
}
Err(OlError::new(
code,
format!("apply failed at {}: {reason}", stage.as_str()),
))
}
}
}
async fn run_check(g: &GlobalArgs, current: &str, registry: &str) -> Result<(), OlError> {
let result = check(current, registry).await;
let json = match &result {
CheckResult::UpToDate { current } => serde_json::json!({
"current": current,
"latest": serde_json::Value::Null,
"severity": serde_json::Value::Null,
"min_supported_provider": serde_json::Value::Null,
}),
CheckResult::Available {
current,
latest,
severity,
min_supported,
..
} => serde_json::json!({
"current": current,
"latest": latest,
"severity": severity.as_str(),
"min_supported_provider": min_supported,
}),
CheckResult::Failed { reason } => serde_json::json!({
"current": current,
"latest": serde_json::Value::Null,
"severity": serde_json::Value::Null,
"min_supported_provider": serde_json::Value::Null,
"error": reason,
}),
};
if !g.quiet {
println!(
"{}",
serde_json::to_string_pretty(&json).unwrap_or_default()
);
}
Ok(())
}