use crate::Shim;
use std::path::Path;
pub async fn sync(app: &mut crate::App) -> crate::Result<()> {
use yansi::Paint;
println!("{}", "Syncing zv...".cyan());
ensure_directories(app).await?;
println!(" {} Checking zv binary...", "→".blue());
let binary_updated = check_and_update_zv_binary(app, false).await?;
#[cfg(unix)]
if let Some(pub_bin) = app.public_bin_path() {
create_public_bin_symlinks(app.bin_path(), pub_bin).await?;
}
if binary_updated
&& let Err(e) = crate::app::migrations::migrate(app.path(), &app.paths.config_file).await
{
eprintln!(" {} Warning: Migration failed: {}", "⚠".yellow(), e);
}
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!("{}", "Sync completed successfully!".green().bold());
if !app.source_set {
#[cfg(target_os = "linux")]
{
let target = app
.public_bin_path()
.map(|p| p.display().to_string())
.unwrap_or_else(|| app.bin_path().display().to_string());
println!(
"{} {} is not in your PATH. This is unusual on Linux.",
"⚠".yellow(),
Paint::cyan(&target)
);
}
#[cfg(target_os = "macos")]
{
if app.paths.tier == 2 {
println!(
"{} PATH not configured. Run {} to add zv to your PATH.",
"⚠".yellow(),
Paint::blue("zv setup")
);
}
}
#[cfg(windows)]
{
println!(
"{} PATH not configured. Run {} to add zv to your PATH.",
"⚠".yellow(),
Paint::blue("zv setup")
);
}
}
Ok(())
}
async fn ensure_directories(app: &crate::App) -> crate::Result<()> {
use std::path::Path;
async fn ensure(dir: &Path) -> crate::Result<()> {
if !dir.try_exists().unwrap_or(false)
&& let Some(parent) = dir.parent()
&& parent.exists()
{
tokio::fs::create_dir_all(dir).await?;
}
Ok(())
}
ensure(&app.paths.data_dir).await?;
ensure(&app.paths.config_dir).await?;
ensure(&app.paths.cache_dir).await?;
ensure(app.bin_path()).await?;
if let Some(ref pub_dir) = app.paths.public_bin_dir {
ensure(pub_dir).await?;
}
Ok(())
}
pub async fn check_and_update_zv_binary(app: &crate::App, quiet: bool) -> crate::Result<bool> {
tracing::debug!(target: "zv::cli::sync", "Checking for zv binary updates");
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<bool> {
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 {}, installing...",
zv_dir_bin.display()
);
}
copy_binary_and_regenerate_shims(¤t_exe, &target_exe, app, quiet).await?;
if !quiet {
tracing::info!("zv binary installed");
}
return Ok(true);
}
match files_have_same_hash(¤t_exe, &target_exe) {
Ok(true) => {
if !quiet {
println!(" {} zv binary is up to date", "✓".green());
}
Ok(false)
}
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, quiet)
.await?;
if !quiet {
println!(" {} zv binary updated", "✓".green());
}
Ok(true)
}
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(false);
}
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, quiet)
.await?;
if !quiet {
println!(
" {} zv binary {}",
"✓".green(),
if prompt_on_downgrade {
"downgraded"
} else {
"updated"
}
);
}
Ok(true)
}
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, quiet)
.await?;
if !quiet {
println!(" {} zv binary updated", "✓".green());
}
Ok(true)
}
}
}
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, quiet).await?;
if !quiet {
println!(" {} zv binary updated", "✓".green());
}
Ok(true)
}
}
}
Err(e) => {
if !quiet {
println!(
" {} Warning: checksum comparison failed: {}, updating anyway",
"⚠".yellow(),
e
);
}
copy_binary_and_regenerate_shims(¤t_exe, &target_exe, app, quiet).await?;
if !quiet {
println!(" {} zv binary updated", "✓".green());
}
Ok(true)
}
}
}
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 an older version?")
.default(false)
.interact()
.unwrap_or(false);
Ok(proceed)
}
async fn copy_binary_and_regenerate_shims(
source: &Path,
target: &Path,
app: &crate::App,
quiet: bool,
) -> 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()))?;
if target.exists() {
tokio::fs::remove_file(target).await.with_context(|| {
format!("Failed to remove existing binary at {}", target.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, quiet)
.await
.with_context(|| "Failed to regenerate shims after updating zv binary")?;
}
#[cfg(unix)]
if let Some(pub_bin) = app.public_bin_path() {
create_public_bin_symlinks(app.bin_path(), pub_bin)
.await
.with_context(|| {
format!(
"Failed to create public bin symlinks in {}",
pub_bin.display()
)
})?;
}
Ok(())
}
#[cfg(unix)]
async fn create_public_bin_symlinks(internal_bin: &Path, public_bin: &Path) -> crate::Result<()> {
use color_eyre::eyre::Context;
use crate::Shim;
tokio::fs::create_dir_all(public_bin)
.await
.with_context(|| format!("Failed to create public bin dir {}", public_bin.display()))?;
async fn place_symlink(target: &Path, link: &Path) -> crate::Result<()> {
if link.exists() || link.is_symlink() {
tokio::fs::remove_file(link).await?;
}
tokio::fs::symlink(target, link).await?;
Ok(())
}
let zv_name = Shim::Zv.executable_name();
let zv_src = internal_bin.join(zv_name);
let zv_dst = public_bin.join(zv_name);
if zv_src.exists() {
place_symlink(&zv_src, &zv_dst)
.await
.with_context(|| format!("Failed to symlink zv in {}", public_bin.display()))?;
tracing::debug!("Linked {} → {}", zv_dst.display(), zv_src.display());
}
let zig_name = Shim::Zig.executable_name();
let zig_src = internal_bin.join(zig_name);
let zig_dst = public_bin.join(zig_name);
if zig_src.exists() {
place_symlink(&zig_src, &zig_dst)
.await
.with_context(|| format!("Failed to symlink zig in {}", public_bin.display()))?;
tracing::debug!("Linked {} → {}", zig_dst.display(), zig_src.display());
}
Ok(())
}