use crate::Shim;
use std::path::Path;
pub async fn sync(app: &mut crate::App) -> crate::Result<()> {
use yansi::Paint;
println!("{}", "Syncing Zig indices...".cyan());
println!(" {} Refreshing Zig index...", "→".blue());
app.sync_zig_index().await?;
println!(" {} Zig index synced successfully", "✓".green());
println!(" {} Refreshing community mirrors...", "→".blue());
let mirror_count = app.sync_mirrors().await?;
println!(
" {} Community mirrors synced successfully ({} mirrors)",
"✓".green(),
mirror_count
);
println!(" {} Checking zv binary...", "→".blue());
check_and_update_zv_binary(app, false).await?;
println!("{}", "Sync completed successfully!".green().bold());
Ok(())
}
pub async fn check_and_update_zv_binary(app: &crate::App, quiet: bool) -> crate::Result<()> {
check_and_update_zv_binary_impl(app, quiet, true).await
}
async fn check_and_update_zv_binary_impl(
app: &crate::App,
quiet: bool,
prompt_on_downgrade: bool,
) -> crate::Result<()> {
use crate::tools::files_have_same_hash;
use color_eyre::eyre::Context;
use yansi::Paint;
let zv_dir_bin = app.bin_path();
let target_exe = zv_dir_bin.join(Shim::Zv.executable_name());
let current_exe = std::env::current_exe().wrap_err("Failed to get current executable path")?;
if !target_exe.exists() {
if !quiet {
tracing::info!(
" {} zv binary not found in <ZV_DIR>/bin, installing...",
"→".blue()
);
}
copy_binary_and_regenerate_shims(¤t_exe, &target_exe, app).await?;
if !quiet {
tracing::info!(" {} zv binary installed", "✓".green());
}
return Ok(());
}
match files_have_same_hash(¤t_exe, &target_exe) {
Ok(true) => {
if !quiet {
println!(" {} zv binary is up to date", "✓".green());
}
Ok(())
}
Ok(false) => {
let current_version = env!("CARGO_PKG_VERSION");
match get_binary_version(&target_exe) {
Ok(target_version) => {
let current_version = semver::Version::parse(current_version)
.expect("CARGO_PKG_VERSION should always be valid semver");
use std::cmp::Ordering;
match current_version.cmp(&target_version) {
Ordering::Greater => {
if !quiet {
println!(
" {} Updating zv binary ({} -> {})",
"→".blue(),
Paint::yellow(&target_version),
Paint::green(¤t_version)
);
}
copy_binary_and_regenerate_shims(¤t_exe, &target_exe, app)
.await?;
if !quiet {
println!(" {} zv binary updated", "✓".green());
}
Ok(())
}
Ordering::Less => {
if !quiet {
println!(
" {} Warning: ZV_DIR/bin/zv is newer ({}) than current binary ({})",
"⚠".yellow(),
Paint::green(&target_version),
Paint::yellow(¤t_version)
);
}
if prompt_on_downgrade && !prompt_user_to_downgrade()? {
if !quiet {
println!(" {} Skipping zv binary update", "→".blue());
}
return Ok(());
}
if !quiet {
println!(
" {} {} zv binary ({} -> {})",
"→".blue(),
if prompt_on_downgrade {
"Downgrading"
} else {
"Updating"
},
Paint::green(&target_version),
Paint::yellow(¤t_version)
);
}
copy_binary_and_regenerate_shims(¤t_exe, &target_exe, app)
.await?;
if !quiet {
println!(
" {} zv binary {}",
"✓".green(),
if prompt_on_downgrade {
"downgraded"
} else {
"updated"
}
);
}
Ok(())
}
Ordering::Equal => {
if !quiet {
println!(
" {} Updating zv binary (checksum mismatch for version {})",
"→".blue(),
current_version
);
}
copy_binary_and_regenerate_shims(¤t_exe, &target_exe, app)
.await?;
if !quiet {
println!(" {} zv binary updated", "✓".green());
}
Ok(())
}
}
}
Err(e) => {
tracing::error!(
target: "zv::cli::sync",
error = %e,
"Failed to get version from target binary, will update anyway"
);
if !quiet {
println!(
" {} Warning: failed to get target version, updating anyway",
"⚠".yellow()
);
}
copy_binary_and_regenerate_shims(¤t_exe, &target_exe, app).await?;
if !quiet {
println!(" {} zv binary updated", "✓".green());
}
Ok(())
}
}
}
Err(e) => {
if !quiet {
println!(
" {} Warning: checksum comparison failed: {}, updating anyway",
"⚠".yellow(),
e
);
}
copy_binary_and_regenerate_shims(¤t_exe, &target_exe, app).await?;
if !quiet {
println!(" {} zv binary updated", "✓".green());
}
Ok(())
}
}
}
fn get_binary_version(binary_path: &std::path::Path) -> crate::Result<semver::Version> {
use color_eyre::eyre::eyre;
let output = std::process::Command::new(binary_path)
.arg("--version")
.output()
.map_err(|e| {
eyre!(
"Failed to execute binary at {}: {}",
binary_path.display(),
e
)
})?;
if !output.status.success() {
return Err(eyre!(
"Binary at {} failed to run --version",
binary_path.display()
));
}
let version_output = String::from_utf8_lossy(&output.stdout);
let version_str = version_output
.split_whitespace()
.nth(1)
.ok_or_else(|| eyre!("Failed to parse version from: {}", version_output))?
.trim();
semver::Version::parse(version_str)
.map_err(|e| eyre!("Failed to parse version '{}' as semver: {}", version_str, e))
}
fn prompt_user_to_downgrade() -> crate::Result<bool> {
use dialoguer::Confirm;
if !crate::tools::is_tty() || std::env::var("CI").is_ok() {
return Ok(false);
}
let proceed = Confirm::new()
.with_prompt(" Do you want to replace it with the older version?")
.default(false)
.interact()
.unwrap_or(false);
Ok(proceed)
}
async fn copy_binary_and_regenerate_shims(
source: &Path,
target: &Path,
app: &crate::App,
) -> crate::Result<()> {
use color_eyre::eyre::Context;
tokio::fs::create_dir_all(app.bin_path())
.await
.with_context(|| format!("Failed to create directory {}", app.bin_path().display()))?;
tokio::fs::copy(source, target).await.with_context(|| {
format!(
"Failed to copy zv binary from {} to {}",
source.display(),
target.display()
)
})?;
let toolchain_manager = &app.toolchain_manager;
if let Some(install) = toolchain_manager.get_active_install() {
toolchain_manager
.deploy_shims(install, true)
.await
.with_context(|| "Failed to regenerate shims after updating zv binary")?;
}
Ok(())
}