forgecode / crates /forge_main /src /update.rs
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
d90101d verified
Raw
History Blame Contribute Delete
4 kB
use std::sync::Arc;
use colored::Colorize;
use forge_api::API;
use forge_config::{Update, UpdateFrequency};
use forge_select::ForgeWidget;
use forge_tracker::VERSION;
use update_informer::{Check, Version, registry};
/// Runs the official installation script to update Forge, failing silently.
/// When `auto_update` is true, exits immediately after a successful update
/// without prompting the user.
async fn execute_update_command(api: Arc<impl API>, auto_update: bool) {
// Spawn a new task that won't block the main application
let output = api
.execute_shell_command_raw("curl -fsSL https://forgecode.dev/cli | sh")
.await;
match output {
Err(err) => {
// Send an event to the tracker on failure
// We don't need to handle this result since we're failing silently
let _ = send_update_failure_event(&format!("Auto update failed {err}")).await;
}
Ok(output) => {
if output.success() {
let should_exit = if auto_update {
true
} else {
let answer = forge_select::ForgeWidget::confirm(
"You need to close forge to complete update. Do you want to close it now?",
)
.with_default(true)
.prompt();
answer.unwrap_or_default().unwrap_or_default()
};
if should_exit {
std::process::exit(0);
}
} else {
let exit_output = match output.code() {
Some(code) => format!("Process exited with code: {code}"),
None => "Process exited without code".to_string(),
};
let _ =
send_update_failure_event(&format!("Auto update failed, {exit_output}",)).await;
}
}
}
}
async fn confirm_update(version: Version) -> bool {
let answer = ForgeWidget::confirm(format!(
"Confirm upgrade from {} -> {} (latest)?",
VERSION.to_string().bold().white(),
version.to_string().bold().white()
))
.with_default(true)
.prompt();
match answer {
Ok(Some(result)) => result,
Ok(None) => false, // User canceled
Err(_) => false, // Error occurred
}
}
fn should_check_for_updates(frequency: &UpdateFrequency) -> bool {
!matches!(frequency, UpdateFrequency::Never)
}
/// Checks if there is an update available
pub async fn on_update(api: Arc<impl API>, update: Option<&Update>) {
let update = update.cloned().unwrap_or_default();
let frequency = update.frequency.unwrap_or_default();
if !should_check_for_updates(&frequency) {
return;
}
let auto_update = update.auto_update.unwrap_or_default();
// Check if version is development version, in which case we skip the update
// check
if VERSION.contains("dev") || VERSION == "0.1.0" {
// Skip update for development version 0.1.0
return;
}
let informer = update_informer::new(registry::GitHub, "tailcallhq/forgecode", VERSION)
.interval(frequency.into());
if let Some(version) = informer.check_version().ok().flatten()
&& (auto_update || confirm_update(version).await)
{
execute_update_command(api, auto_update).await;
}
}
/// Sends an event to the tracker when an update fails
async fn send_update_failure_event(error_msg: &str) -> anyhow::Result<()> {
tracing::error!(error = error_msg, "Update failed");
// Always return Ok since we want to fail silently
Ok(())
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
#[test]
fn test_should_skip_update_check_when_frequency_is_never() {
let fixture = UpdateFrequency::Never;
let actual = should_check_for_updates(&fixture);
let expected = false;
assert_eq!(actual, expected);
}
}