leo_lang/cli/helpers/
updater.rs1use leo_errors::{CliError, Result};
18
19use aleo_std;
20
21use colored::Colorize;
22use self_update::{Status, backends::github, get_target, 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 .with_target(get_target())
48 .build()
49 .map_err(CliError::self_update_error)?
50 .fetch()
51 .map_err(CliError::could_not_fetch_versions)?;
52
53 let mut output = format!(
54 "\nList of available versions for: {}.\nUse the quoted name to select specific releases.\n\n",
55 get_target()
56 );
57 for release in releases {
58 let _ = writeln!(output, " * {} | '{}'", release.version, release.name);
59 }
60
61 Ok(output)
62 }
63
64 pub fn update(show_output: bool, version: Option<String>) -> Result<Status> {
67 let mut update = github::Update::configure();
68 update
70 .repo_owner(Self::LEO_REPO_OWNER)
71 .repo_name(Self::LEO_REPO_NAME)
72 .bin_name(Self::LEO_BIN_NAME)
73 .current_version(env!("CARGO_PKG_VERSION"))
74 .show_download_progress(show_output)
75 .no_confirm(true)
76 .show_output(show_output);
77 if let Some(version) = version {
79 update.target_version_tag(&version);
80 }
81 let status =
82 update.build().map_err(CliError::self_update_build_error)?.update().map_err(CliError::self_update_error)?;
83
84 Ok(status)
85 }
86
87 pub fn update_available() -> Result<String> {
89 let updater = github::Update::configure()
90 .repo_owner(Self::LEO_REPO_OWNER)
91 .repo_name(Self::LEO_REPO_NAME)
92 .bin_name(Self::LEO_BIN_NAME)
93 .current_version(env!("CARGO_PKG_VERSION"))
94 .build()
95 .map_err(CliError::self_update_error)?;
96
97 let current_version = updater.current_version();
98 let latest_release = updater.get_latest_release().map_err(CliError::self_update_error)?;
99
100 if bump_is_greater(¤t_version, &latest_release.version).map_err(CliError::self_update_error)? {
101 Ok(latest_release.version)
102 } else {
103 Err(CliError::old_release_version(current_version, latest_release.version).into())
104 }
105 }
106
107 pub fn read_latest_version() -> Result<Option<String>, CliError> {
109 let version_file_path = Self::get_version_file_path();
110 match fs::read_to_string(version_file_path) {
111 Ok(version) => Ok(Some(version.trim().to_string())),
112 Err(_) => Ok(None),
113 }
114 }
115
116 pub fn get_cli_string() -> Result<Option<String>, CliError> {
118 if let Some(latest_version) = Self::read_latest_version()? {
119 let colorized_message = format!(
120 "\n🟢 {} {} {}",
121 "A new version is available! Run".bold().green(),
122 "`leo update`".bold().white(),
123 format!("to update to v{latest_version}.").bold().green()
124 );
125 Ok(Some(colorized_message))
126 } else {
127 Ok(None)
128 }
129 }
130
131 pub fn print_cli() -> Result<(), CliError> {
133 if let Some(message) = Self::get_cli_string()? {
134 println!("{message}");
135 }
136 Ok(())
137 }
138
139 pub fn check_for_updates(force: bool) -> Result<bool, CliError> {
142 let cache_dir = Self::get_cache_dir();
144 let last_check_file = cache_dir.join(Self::LEO_CACHE_LAST_CHECK_FILE);
145 let version_file = Self::get_version_file_path();
146
147 let should_check = force || Self::should_check_for_updates(&last_check_file)?;
149
150 if should_check {
151 match Self::update_available() {
152 Ok(latest_version) => {
153 Self::update_check_files(&cache_dir, &last_check_file, &version_file, &latest_version)?;
155 Ok(true)
156 }
157 Err(_) => {
158 Self::update_check_files(&cache_dir, &last_check_file, &version_file, env!("CARGO_PKG_VERSION"))?;
161 Ok(false)
162 }
163 }
164 } else if version_file.exists() {
165 if let Ok(stored_version) = fs::read_to_string(&version_file) {
166 let current_version = env!("CARGO_PKG_VERSION");
167 Ok(bump_is_greater(current_version, stored_version.trim()).map_err(CliError::self_update_error)?)
168 } else {
169 Ok(false)
171 }
172 } else {
173 Ok(false)
174 }
175 }
176
177 fn update_check_files(
182 cache_dir: &Path,
183 last_check_file: &Path,
184 version_file: &Path,
185 latest_version: &str,
186 ) -> Result<(), CliError> {
187 fs::create_dir_all(cache_dir).map_err(CliError::cli_io_error)?;
189
190 let current_time = Self::get_current_time()?;
192
193 fs::write(last_check_file, current_time.to_string()).map_err(CliError::cli_io_error)?;
195
196 fs::write(version_file, latest_version).map_err(CliError::cli_io_error)?;
198
199 Ok(())
200 }
201
202 fn should_check_for_updates(last_check_file: &Path) -> Result<bool, CliError> {
207 match fs::read_to_string(last_check_file) {
208 Ok(contents) => {
209 let last_check = contents
211 .parse::<u64>()
212 .map_err(|e| CliError::cli_runtime_error(format!("Failed to parse last check time: {e}")))?;
213
214 let current_time = Self::get_current_time()?;
216
217 Ok(current_time.saturating_sub(last_check) > Self::LEO_UPDATE_CHECK_INTERVAL.as_secs())
219 }
220 Err(_) => Ok(true),
222 }
223 }
224
225 fn get_current_time() -> Result<u64, CliError> {
227 SystemTime::now()
228 .duration_since(UNIX_EPOCH)
229 .map_err(|e| CliError::cli_runtime_error(format!("System time error: {e}")))
230 .map(|duration| duration.as_secs())
231 }
232
233 fn get_version_file_path() -> PathBuf {
235 Self::get_cache_dir().join(Self::LEO_CACHE_VERSION_FILE)
236 }
237
238 fn get_cache_dir() -> PathBuf {
240 aleo_std::aleo_dir().join("leo")
241 }
242}