leo_lang/cli/helpers/
updater.rs1use leo_errors::{CliError, Result};
18
19use aleo_std;
20
21use colored::Colorize;
22use self_update::{Status, backends::github, version::bump_is_greater};
23use std::{
24 fmt::Write as _,
25 fs,
26 path::{Path, PathBuf},
27 time::{Duration, SystemTime, UNIX_EPOCH},
28};
29
30pub struct Updater;
31
32impl Updater {
34 const LEO_BIN_NAME: &'static str = "leo";
35 const LEO_CACHE_LAST_CHECK_FILE: &'static str = "leo_cache_last_update_check";
36 const LEO_CACHE_VERSION_FILE: &'static str = "leo_cache_latest_version";
37 const LEO_REPO_NAME: &'static str = "leo";
38 const LEO_REPO_OWNER: &'static str = "ProvableHQ";
39 const LEO_UPDATE_CHECK_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60);
41
42 pub fn show_available_releases() -> Result<String> {
44 let releases = github::ReleaseList::configure()
45 .repo_owner(Self::LEO_REPO_OWNER)
46 .repo_name(Self::LEO_REPO_NAME)
47 .build()
48 .map_err(CliError::self_update_error)?
49 .fetch()
50 .map_err(CliError::could_not_fetch_versions)?;
51
52 let mut output = "\nList of available versions\n".to_string();
53 for release in releases {
54 let _ = writeln!(output, " * {}", release.version);
55 }
56
57 Ok(output)
58 }
59
60 pub fn update_to_latest_release(show_output: bool) -> Result<Status> {
62 let status = github::Update::configure()
63 .repo_owner(Self::LEO_REPO_OWNER)
64 .repo_name(Self::LEO_REPO_NAME)
65 .bin_name(Self::LEO_BIN_NAME)
66 .current_version(env!("CARGO_PKG_VERSION"))
67 .show_download_progress(show_output)
68 .no_confirm(true)
69 .show_output(show_output)
70 .build()
71 .map_err(CliError::self_update_build_error)?
72 .update()
73 .map_err(CliError::self_update_error)?;
74
75 Ok(status)
76 }
77
78 pub fn update_available() -> Result<String> {
80 let updater = github::Update::configure()
81 .repo_owner(Self::LEO_REPO_OWNER)
82 .repo_name(Self::LEO_REPO_NAME)
83 .bin_name(Self::LEO_BIN_NAME)
84 .current_version(env!("CARGO_PKG_VERSION"))
85 .build()
86 .map_err(CliError::self_update_error)?;
87
88 let current_version = updater.current_version();
89 let latest_release = updater.get_latest_release().map_err(CliError::self_update_error)?;
90
91 if bump_is_greater(¤t_version, &latest_release.version).map_err(CliError::self_update_error)? {
92 Ok(latest_release.version)
93 } else {
94 Err(CliError::old_release_version(current_version, latest_release.version).into())
95 }
96 }
97
98 pub fn read_latest_version() -> Result<Option<String>, CliError> {
100 let version_file_path = Self::get_version_file_path();
101 match fs::read_to_string(version_file_path) {
102 Ok(version) => Ok(Some(version.trim().to_string())),
103 Err(_) => Ok(None),
104 }
105 }
106
107 pub fn get_cli_string() -> Result<Option<String>, CliError> {
109 if let Some(latest_version) = Self::read_latest_version()? {
110 let colorized_message = format!(
111 "\n🟢 {} {} {}",
112 "A new version is available! Run".bold().green(),
113 "`leo update`".bold().white(),
114 format!("to update to v{}.", latest_version).bold().green()
115 );
116 Ok(Some(colorized_message))
117 } else {
118 Ok(None)
119 }
120 }
121
122 pub fn print_cli() -> Result<(), CliError> {
124 if let Some(message) = Self::get_cli_string()? {
125 println!("{}", message);
126 }
127 Ok(())
128 }
129
130 pub fn check_for_updates(force: bool) -> Result<bool, CliError> {
133 let cache_dir = Self::get_cache_dir();
135 let last_check_file = cache_dir.join(Self::LEO_CACHE_LAST_CHECK_FILE);
136 let version_file = Self::get_version_file_path();
137
138 let should_check = force || Self::should_check_for_updates(&last_check_file)?;
140
141 if should_check {
142 match Self::update_available() {
143 Ok(latest_version) => {
144 Self::update_check_files(&cache_dir, &last_check_file, &version_file, &latest_version)?;
146 Ok(true)
147 }
148 Err(_) => {
149 Self::update_check_files(&cache_dir, &last_check_file, &version_file, env!("CARGO_PKG_VERSION"))?;
152 Ok(false)
153 }
154 }
155 } else if version_file.exists() {
156 if let Ok(stored_version) = fs::read_to_string(&version_file) {
157 let current_version = env!("CARGO_PKG_VERSION");
158 Ok(bump_is_greater(current_version, stored_version.trim()).map_err(CliError::self_update_error)?)
159 } else {
160 Ok(false)
162 }
163 } else {
164 Ok(false)
165 }
166 }
167
168 fn update_check_files(
173 cache_dir: &Path,
174 last_check_file: &Path,
175 version_file: &Path,
176 latest_version: &str,
177 ) -> Result<(), CliError> {
178 fs::create_dir_all(cache_dir).map_err(CliError::cli_io_error)?;
180
181 let current_time = Self::get_current_time()?;
183
184 fs::write(last_check_file, current_time.to_string()).map_err(CliError::cli_io_error)?;
186
187 fs::write(version_file, latest_version).map_err(CliError::cli_io_error)?;
189
190 Ok(())
191 }
192
193 fn should_check_for_updates(last_check_file: &Path) -> Result<bool, CliError> {
198 match fs::read_to_string(last_check_file) {
199 Ok(contents) => {
200 let last_check = contents
202 .parse::<u64>()
203 .map_err(|e| CliError::cli_runtime_error(format!("Failed to parse last check time: {}", e)))?;
204
205 let current_time = Self::get_current_time()?;
207
208 Ok(current_time.saturating_sub(last_check) > Self::LEO_UPDATE_CHECK_INTERVAL.as_secs())
210 }
211 Err(_) => Ok(true),
213 }
214 }
215
216 fn get_current_time() -> Result<u64, CliError> {
218 SystemTime::now()
219 .duration_since(UNIX_EPOCH)
220 .map_err(|e| CliError::cli_runtime_error(format!("System time error: {}", e)))
221 .map(|duration| duration.as_secs())
222 }
223
224 fn get_version_file_path() -> PathBuf {
226 Self::get_cache_dir().join(Self::LEO_CACHE_VERSION_FILE)
227 }
228
229 fn get_cache_dir() -> PathBuf {
231 aleo_std::aleo_dir().join("leo")
232 }
233}