zoi-rs 1.18.3

Advanced Package Manager & Environment Orchestrator
Documentation
use crate::pkg::{cache, local, resolve, types};
use crate::utils;
use anyhow::{Result, anyhow};
use colored::*;
use std::fs;
use std::io::Cursor;
use std::path::PathBuf;
use std::process::Command;
use tar::Archive;
use zstd::stream::read::Decoder as ZstdDecoder;

fn ensure_binary_is_cached(pkg: &types::Package, upstream: bool) -> Result<PathBuf> {
    let cache_dir = cache::get_cache_root()?;
    let binary_filename = if cfg!(target_os = "windows") {
        format!("{}.exe", pkg.name)
    } else {
        pkg.name.clone()
    };
    let bin_path = cache_dir.join(&binary_filename);

    if upstream && bin_path.exists() {
        fs::remove_file(&bin_path)?;
    }

    if bin_path.exists() {
        println!("Using cached binary for '{}'.", pkg.name.cyan());
        return Ok(bin_path);
    }

    if !pkg.types.contains(&"pre-compiled".to_string()) {
        return Err(anyhow!(
            "zoi exec only works with 'pre-compiled' package types."
        ));
    }

    println!(
        "No cached binary found for '{}'. Downloading pre-built package...",
        pkg.name.cyan()
    );
    fs::create_dir_all(&cache_dir)?;

    let db_path = resolve::get_db_root()?;
    let config = crate::pkg::config::read_config()?;
    let repo_config = if let Some(handle) = config.default_registry.as_ref().map(|r| &r.handle) {
        crate::pkg::config::read_repo_config(&db_path.join(handle)).ok()
    } else {
        None
    };

    if let Some(repo_config) = repo_config {
        let mut pkg_links_to_try = Vec::new();
        if let Some(main_pkg) = repo_config.pkg.iter().find(|p| p.link_type == "main") {
            pkg_links_to_try.push(main_pkg.clone());
        }
        pkg_links_to_try.extend(
            repo_config
                .pkg
                .iter()
                .filter(|p| p.link_type == "mirror")
                .cloned(),
        );

        for pkg_link in pkg_links_to_try {
            let platform = utils::get_platform()?;
            let (os, arch) = (
                platform.split('-').next().unwrap_or_default(),
                platform.split('-').nth(1).unwrap_or_default(),
            );
            let url_dir = pkg_link
                .url
                .replace("{os}", os)
                .replace("{arch}", arch)
                .replace("{version}", pkg.version.as_deref().unwrap_or_default())
                .replace("{repo}", &pkg.repo);

            let archive_filename = format!("{}.pkg.tar.zst", pkg.name);
            let final_url = format!("{}/{}", url_dir.trim_end_matches('/'), archive_filename);

            println!(
                "Attempting to download pre-built package from: {}",
                final_url.cyan()
            );

            let temp_dir = tempfile::Builder::new().prefix("zoi-exec-dl-").tempdir()?;
            let temp_archive_path = temp_dir.path().join(&archive_filename);

            if crate::pkg::install::util::download_file_with_progress(
                &final_url,
                &temp_archive_path,
                None,
                None,
            )
            .is_ok()
            {
                let downloaded_data = fs::read(&temp_archive_path)?;
                let temp_ext_dir = tempfile::Builder::new().prefix("zoi-exec-ext").tempdir()?;
                let mut archive = Archive::new(ZstdDecoder::new(Cursor::new(downloaded_data))?);
                archive.unpack(temp_ext_dir.path())?;

                let bin_dir_in_archive = temp_ext_dir.path().join("data/pkgstore/bin");
                if bin_dir_in_archive.exists()
                    && let Some(bin_name) = &pkg.bins.as_ref().and_then(|b| b.first())
                {
                    let bin_in_archive = bin_dir_in_archive.join(bin_name);
                    if bin_in_archive.exists() {
                        let final_bin_path = cache_dir.join(bin_name);
                        fs::copy(&bin_in_archive, &final_bin_path)?;
                        #[cfg(unix)]
                        {
                            use std::os::unix::fs::PermissionsExt;
                            fs::set_permissions(
                                &final_bin_path,
                                fs::Permissions::from_mode(0o755),
                            )?;
                        }
                        println!("Binary cached successfully.");
                        return Ok(final_bin_path);
                    }
                }
            }
        }
    }

    Err(anyhow!("Could not download pre-built package for exec."))
}

fn find_executable(
    pkg: &types::Package,
    upstream: bool,
    cache_only: bool,
    local_only: bool,
    registry_handle: Option<&str>,
) -> Result<PathBuf> {
    let handle = registry_handle.unwrap_or("local");

    if upstream {
        return ensure_binary_is_cached(pkg, true);
    }

    let scopes_to_check = if local_only {
        vec![types::Scope::Project]
    } else {
        vec![
            types::Scope::Project,
            types::Scope::User,
            types::Scope::System,
        ]
    };

    for scope in scopes_to_check {
        if let Ok(package_dir) = local::get_package_dir(scope, handle, &pkg.repo, &pkg.name) {
            let binary_filename = if cfg!(target_os = "windows") {
                format!("{}.exe", pkg.name)
            } else {
                pkg.name.clone()
            };

            if let Some(v) = &pkg.version {
                let v_dir = package_dir.join(v);
                if v_dir.exists() {
                    let bin_path = v_dir.join("bin").join(&binary_filename);
                    if bin_path.exists() {
                        return Ok(bin_path);
                    }
                }
            }

            let latest_path = package_dir.join("latest");
            if latest_path.exists() {
                let bin_path = latest_path.join("bin").join(&binary_filename);
                if bin_path.exists() {
                    let scope_str = match scope {
                        types::Scope::Project => "project-local",
                        types::Scope::User => "user",
                        types::Scope::System => "system",
                    };
                    println!("Using {} binary for '{}'.", scope_str, pkg.name.cyan());
                    return Ok(bin_path);
                }
            }
        }
    }

    if local_only {
        return Err(anyhow!("No local project binary found."));
    }

    if cache_only {
        let cache_dir = cache::get_cache_root()?;
        let binary_filename = if cfg!(target_os = "windows") {
            format!("{}.exe", pkg.name)
        } else {
            pkg.name.clone()
        };
        let bin_path = cache_dir.join(&binary_filename);
        if bin_path.exists() {
            println!("Using cached binary for '{}'.", pkg.name.cyan());
            return Ok(bin_path);
        }
        return Err(anyhow!("No cached binary found."));
    }

    ensure_binary_is_cached(pkg, false)
}

pub fn run(
    source: &str,
    args: Vec<String>,
    upstream: bool,
    cache_only: bool,
    local_only: bool,
) -> Result<i32> {
    let resolved_source = resolve::resolve_source(source, false, false)?;

    if let Some(repo_name) = &resolved_source.repo_name {
        utils::print_repo_warning(repo_name);
    }

    let mut pkg: types::Package = crate::pkg::lua::parser::parse_lua_package(
        resolved_source.path.to_str().ok_or_else(|| {
            anyhow!(
                "Path contains invalid UTF-8 characters: {:?}",
                resolved_source.path
            )
        })?,
        None,
        false,
    )?;

    if let Some(repo_name) = resolved_source.repo_name.clone() {
        pkg.repo = repo_name;
    }

    if pkg.package_type == types::PackageType::App {
        return Err(anyhow!(
            "This package is an 'app' template. Use 'zoi create <pkg> <appName>' to create an app from it."
        ));
    }

    let bin_path = find_executable(
        &pkg,
        upstream,
        cache_only,
        local_only,
        resolved_source.registry_handle.as_deref(),
    )?;

    match crate::pkg::telemetry::posthog_capture_event(
        "exec",
        &pkg,
        env!("CARGO_PKG_VERSION"),
        resolved_source
            .registry_handle
            .as_deref()
            .unwrap_or("local"),
        None,
    ) {
        Ok(true) => println!("{} telemetry sent", "Info:".green()),
        Ok(false) => (),
        Err(e) => eprintln!("{} telemetry failed: {}", "Warning:".yellow(), e),
    }

    println!(
        "\n{} Executing '{}'...\n",
        "::".bold().blue(),
        pkg.name.bold()
    );

    let mut cmd = Command::new(&bin_path);
    if !args.is_empty() {
        cmd.args(&args);
    }

    println!("> \"{}\" {}", bin_path.display(), args.join(" "));

    let status = cmd.status()?;

    Ok(status.code().unwrap_or(1))
}