mirror of
https://github.com/ProvableHQ/leo.git
synced 2024-11-22 05:00:59 +03:00
Merge pull request #28440 from ProvableHQ/feat/cli-new-version-notification
[Feature] CLI Update Notification.
This commit is contained in:
commit
166a3c32e6
@ -124,6 +124,11 @@ pub fn run_with_args(cli: CLI) -> Result<()> {
|
||||
})?;
|
||||
}
|
||||
|
||||
// Check for updates. If not forced, it checks once per day.
|
||||
if let Ok(true) = updater::Updater::check_for_updates(false) {
|
||||
let _ = updater::Updater::print_cli();
|
||||
}
|
||||
|
||||
// Get custom root folder and create context for it.
|
||||
// If not specified, default context will be created in cwd.
|
||||
let context = handle_error(Context::new(cli.path, cli.home, false));
|
||||
@ -143,6 +148,7 @@ pub fn run_with_args(cli: CLI) -> Result<()> {
|
||||
Commands::Update { command } => command.try_execute(context),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cli::{
|
||||
|
@ -16,18 +16,28 @@
|
||||
|
||||
use leo_errors::{CliError, Result};
|
||||
|
||||
use std::fmt::Write as _;
|
||||
use aleo_std;
|
||||
|
||||
use colored::Colorize;
|
||||
use self_update::{Status, backends::github, version::bump_is_greater};
|
||||
use std::{
|
||||
fmt::Write as _,
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
pub struct Updater;
|
||||
|
||||
// TODO Add logic for users to easily select release versions.
|
||||
impl Updater {
|
||||
const LEO_BIN_NAME: &'static str = "leo";
|
||||
const LEO_CACHE_LAST_CHECK_FILE: &'static str = "leo_cache_last_update_check";
|
||||
const LEO_CACHE_VERSION_FILE: &'static str = "leo_cache_latest_version";
|
||||
const LEO_REPO_NAME: &'static str = "leo";
|
||||
const LEO_REPO_OWNER: &'static str = "AleoHQ";
|
||||
// 24 hours
|
||||
const LEO_UPDATE_CHECK_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60);
|
||||
|
||||
/// Show all available releases for `leo`.
|
||||
pub fn show_available_releases() -> Result<String> {
|
||||
@ -85,15 +95,139 @@ impl Updater {
|
||||
}
|
||||
}
|
||||
|
||||
/// Display the CLI message, if the Leo configuration allows.
|
||||
pub fn print_cli() {
|
||||
// If the auto update configuration is off, notify the user to update leo.
|
||||
if let Ok(latest_version) = Self::update_available() {
|
||||
let mut message = "🟢 A new version is available! Run".bold().green().to_string();
|
||||
message += &" `leo update` ".bold().white();
|
||||
message += &format!("to update to v{latest_version}.").bold().green();
|
||||
|
||||
tracing::info!("\n{}\n", message);
|
||||
/// Read the latest version from the version file.
|
||||
pub fn read_latest_version() -> Result<Option<String>, CliError> {
|
||||
let version_file_path = Self::get_version_file_path();
|
||||
match fs::read_to_string(version_file_path) {
|
||||
Ok(version) => Ok(Some(version.trim().to_string())),
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the CLI message if a new version is available.
|
||||
pub fn get_cli_string() -> Result<Option<String>, CliError> {
|
||||
if let Some(latest_version) = Self::read_latest_version()? {
|
||||
let colorized_message = format!(
|
||||
"\n🟢 {} {} {}",
|
||||
"A new version is available! Run".bold().green(),
|
||||
"`leo update`".bold().white(),
|
||||
format!("to update to v{}.", latest_version).bold().green()
|
||||
);
|
||||
Ok(Some(colorized_message))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Display the CLI message if a new version is available.
|
||||
pub fn print_cli() -> Result<(), CliError> {
|
||||
if let Some(message) = Self::get_cli_string()? {
|
||||
println!("{}", message);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check for updates, respecting the update interval. (Currently once per day.)
|
||||
/// If a new version is found, write it to a cache file and alert in every call.
|
||||
pub fn check_for_updates(force: bool) -> Result<bool, CliError> {
|
||||
// Get the cache directory and relevant file paths.
|
||||
let cache_dir = Self::get_cache_dir();
|
||||
let last_check_file = cache_dir.join(Self::LEO_CACHE_LAST_CHECK_FILE);
|
||||
let version_file = Self::get_version_file_path();
|
||||
|
||||
// Determine if we should check for updates.
|
||||
let should_check = force || Self::should_check_for_updates(&last_check_file)?;
|
||||
|
||||
if should_check {
|
||||
match Self::update_available() {
|
||||
Ok(latest_version) => {
|
||||
// A new version is available
|
||||
Self::update_check_files(&cache_dir, &last_check_file, &version_file, &latest_version)?;
|
||||
Ok(true)
|
||||
}
|
||||
Err(_) => {
|
||||
// No new version available or error occurred
|
||||
// We'll treat both cases as "no update" for simplicity
|
||||
Self::update_check_files(&cache_dir, &last_check_file, &version_file, env!("CARGO_PKG_VERSION"))?;
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
} else if version_file.exists() {
|
||||
if let Ok(stored_version) = fs::read_to_string(&version_file) {
|
||||
let current_version = env!("CARGO_PKG_VERSION");
|
||||
Ok(bump_is_greater(current_version, stored_version.trim()).map_err(CliError::self_update_error)?)
|
||||
} else {
|
||||
// If we can't read the file, assume no update is available
|
||||
Ok(false)
|
||||
}
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the check files with the latest version information and timestamp.
|
||||
///
|
||||
/// This function creates the cache directory if it doesn't exist, writes the current time
|
||||
/// to the last check file, and writes the latest version to the version file.
|
||||
fn update_check_files(
|
||||
cache_dir: &Path,
|
||||
last_check_file: &Path,
|
||||
version_file: &Path,
|
||||
latest_version: &str,
|
||||
) -> Result<(), CliError> {
|
||||
// Recursively create the cache directory and all of its parent components if they are missing.
|
||||
fs::create_dir_all(cache_dir).map_err(CliError::cli_io_error)?;
|
||||
|
||||
// Get the current time.
|
||||
let current_time = Self::get_current_time()?;
|
||||
|
||||
// Write the current time to the last check file.
|
||||
fs::write(last_check_file, current_time.to_string()).map_err(CliError::cli_io_error)?;
|
||||
|
||||
// Write the latest version to the version file.
|
||||
fs::write(version_file, latest_version).map_err(CliError::cli_io_error)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Determines if an update check should be performed based on the last check time.
|
||||
///
|
||||
/// This function reads the last check timestamp from a file and compares it with
|
||||
/// the current time to decide if enough time has passed for a new check.
|
||||
fn should_check_for_updates(last_check_file: &Path) -> Result<bool, CliError> {
|
||||
match fs::read_to_string(last_check_file) {
|
||||
Ok(contents) => {
|
||||
// Parse the last check timestamp from the file.
|
||||
let last_check = contents
|
||||
.parse::<u64>()
|
||||
.map_err(|e| CliError::cli_runtime_error(format!("Failed to parse last check time: {}", e)))?;
|
||||
|
||||
// Get the current time.
|
||||
let current_time = Self::get_current_time()?;
|
||||
|
||||
// Check if enough time has passed since the last check.
|
||||
Ok(current_time.saturating_sub(last_check) > Self::LEO_UPDATE_CHECK_INTERVAL.as_secs())
|
||||
}
|
||||
// If we can't read the file, assume we should check
|
||||
Err(_) => Ok(true),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the current system time as seconds since the Unix epoch.
|
||||
fn get_current_time() -> Result<u64, CliError> {
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map_err(|e| CliError::cli_runtime_error(format!("System time error: {}", e)))
|
||||
.map(|duration| duration.as_secs())
|
||||
}
|
||||
|
||||
/// Get the path to the file storing the latest version information.
|
||||
fn get_version_file_path() -> PathBuf {
|
||||
Self::get_cache_dir().join(Self::LEO_CACHE_VERSION_FILE)
|
||||
}
|
||||
|
||||
/// Get the cache directory for Leo.
|
||||
fn get_cache_dir() -> PathBuf {
|
||||
aleo_std::aleo_dir().join("leo")
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user