Skip to content

Commit e719105

Browse files
committed
...
1 parent 5a25a9f commit e719105

File tree

1 file changed

+159
-0
lines changed

1 file changed

+159
-0
lines changed

src/self_update.rs

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
use anyhow::Result;
2+
use colored::*;
3+
use serde::Deserialize;
4+
use std::env;
5+
use std::fs;
6+
use std::path::PathBuf;
7+
8+
const REPO_OWNER: &str = "michaelessiet"; // Change this to your GitHub username
9+
const REPO_NAME: &str = "bert-cli";
10+
11+
#[derive(Deserialize)]
12+
struct GithubRelease {
13+
tag_name: String,
14+
// name: String,
15+
body: Option<String>,
16+
assets: Vec<GithubAsset>,
17+
html_url: String,
18+
}
19+
20+
#[derive(Deserialize)]
21+
struct GithubAsset {
22+
name: String,
23+
browser_download_url: String,
24+
}
25+
26+
pub async fn self_update() -> Result<()> {
27+
println!("Checking for updates 🐕");
28+
29+
// Get current version
30+
let current_version = env!("CARGO_PKG_VERSION");
31+
println!("Current version: {}", current_version);
32+
33+
// Get latest release from GitHub
34+
let client = reqwest::Client::new();
35+
let url = format!(
36+
"https://api.github.com/repos/{}/{}/releases/latest",
37+
REPO_OWNER, REPO_NAME
38+
);
39+
40+
let response = client
41+
.get(&url)
42+
.header("User-Agent", "bert-updater")
43+
.send()
44+
.await?;
45+
46+
if !response.status().is_success() {
47+
anyhow::bail!("Failed to fetch latest release information");
48+
}
49+
50+
let release: GithubRelease = response.json().await?;
51+
let latest_version = release.tag_name.trim_start_matches('v');
52+
53+
println!("Latest version: {}", latest_version);
54+
55+
if latest_version == current_version {
56+
println!("{}", "bert is already up to date!".green());
57+
return Ok(());
58+
}
59+
60+
println!(
61+
"New version available: {} -> {}",
62+
current_version, latest_version
63+
);
64+
if let Some(body) = release.body {
65+
println!("\nRelease notes:\n{}", body);
66+
}
67+
68+
// Find the appropriate asset for the current platform
69+
let asset_name = get_platform_asset_name();
70+
let asset = release
71+
.assets
72+
.iter()
73+
.find(|a| a.name == asset_name)
74+
.ok_or_else(|| anyhow::anyhow!("No compatible binary found for your platform"))?;
75+
76+
println!("Downloading update...");
77+
78+
// Download the new binary
79+
let response = client
80+
.get(&asset.browser_download_url)
81+
.header("User-Agent", "bert-updater")
82+
.send()
83+
.await?;
84+
85+
if !response.status().is_success() {
86+
anyhow::bail!("Failed to download update");
87+
}
88+
89+
// Get current executable path
90+
let current_exe = env::current_exe()?;
91+
let temp_path = get_temp_path(&current_exe);
92+
93+
// Save the new binary to a temporary location
94+
let bytes = response.bytes().await?;
95+
fs::write(&temp_path, bytes)?;
96+
97+
// Make the new binary executable on Unix systems
98+
#[cfg(unix)]
99+
{
100+
use std::os::unix::fs::PermissionsExt;
101+
fs::set_permissions(&temp_path, fs::Permissions::from_mode(0o755))?;
102+
}
103+
104+
// Replace the old binary
105+
println!("Installing update...");
106+
107+
#[cfg(windows)]
108+
{
109+
// On Windows, we need to move the current executable to a temp path first
110+
let old_exe = current_exe.with_extension("old.exe");
111+
fs::rename(&current_exe, &old_exe)?;
112+
fs::rename(&temp_path, &current_exe)?;
113+
// Try to remove the old executable, but don't fail if we can't
114+
fs::remove_file(old_exe).ok();
115+
}
116+
#[cfg(not(windows))]
117+
{
118+
fs::rename(&temp_path, &current_exe)?;
119+
}
120+
121+
println!("{}", "Update completed successfully!".green());
122+
println!("New version: {}", latest_version);
123+
println!("Release page: {}", release.html_url);
124+
125+
Ok(())
126+
}
127+
128+
fn get_platform_asset_name() -> String {
129+
#[cfg(target_os = "linux")]
130+
{
131+
"bert-linux-amd64".to_string()
132+
}
133+
#[cfg(target_os = "macos")]
134+
{
135+
if cfg!(target_arch = "aarch64") {
136+
"bert-darwin-arm64".to_string()
137+
} else {
138+
"bert-darwin-amd64".to_string()
139+
}
140+
}
141+
#[cfg(target_os = "windows")]
142+
{
143+
"bert-windows-amd64.exe".to_string()
144+
}
145+
}
146+
147+
fn get_temp_path(current_exe: &PathBuf) -> PathBuf {
148+
let file_name = current_exe.file_name().unwrap();
149+
let temp_dir = env::temp_dir();
150+
151+
#[cfg(windows)]
152+
{
153+
temp_dir.join(format!("{}.new", file_name.to_string_lossy()))
154+
}
155+
#[cfg(not(windows))]
156+
{
157+
temp_dir.join(format!("{}.new", file_name.to_string_lossy()))
158+
}
159+
}

0 commit comments

Comments
 (0)