zoi/pkg/
rollback.rs

1use crate::pkg::{local, resolve, types};
2use crate::utils;
3use anyhow::{Result, anyhow};
4use colored::*;
5use semver::Version;
6use std::fs;
7use std::path::{Path, PathBuf};
8
9#[cfg(windows)]
10use junction;
11
12pub fn run(package_name: &str, yes: bool) -> Result<()> {
13    println!("Attempting to roll back '{}'...", package_name.cyan());
14
15    let resolved_source = resolve::resolve_source(package_name)?;
16    let mut pkg =
17        crate::pkg::lua::parser::parse_lua_package(resolved_source.path.to_str().unwrap(), None)?;
18    if let Some(repo_name) = resolved_source.repo_name {
19        pkg.repo = repo_name;
20    }
21    let registry_handle = resolved_source.registry_handle;
22
23    let (_manifest, scope) =
24        if let Some(m) = local::is_package_installed(&pkg.name, types::Scope::User)? {
25            (m, types::Scope::User)
26        } else if let Some(m) = local::is_package_installed(&pkg.name, types::Scope::System)? {
27            (m, types::Scope::System)
28        } else if let Some(m) = local::is_package_installed(&pkg.name, types::Scope::Project)? {
29            (m, types::Scope::Project)
30        } else {
31            return Err(anyhow!("Package '{}' is not installed.", package_name));
32        };
33
34    let handle = registry_handle.as_deref().unwrap_or("local");
35    let package_dir = local::get_package_dir(scope, handle, &pkg.repo, &pkg.name)?;
36
37    let mut versions = Vec::new();
38    if let Ok(entries) = fs::read_dir(&package_dir) {
39        for entry in entries.flatten() {
40            let path = entry.path();
41            if path.is_dir()
42                && let Some(version_str) = path.file_name().and_then(|s| s.to_str())
43                && version_str != "latest"
44                && let Ok(version) = Version::parse(version_str)
45            {
46                versions.push(version);
47            }
48        }
49    }
50    versions.sort();
51
52    if versions.len() < 2 {
53        return Err(anyhow!("No previous version to roll back to."));
54    }
55
56    let current_version = versions.pop().unwrap();
57    let previous_version = versions.pop().unwrap();
58
59    println!(
60        "Rolling back from version {} to {}",
61        current_version.to_string().yellow(),
62        previous_version.to_string().green()
63    );
64
65    if !utils::ask_for_confirmation("This will remove the current version. Continue?", yes) {
66        println!("Operation aborted.");
67        return Ok(());
68    }
69
70    let latest_symlink_path = package_dir.join("latest");
71    let previous_version_dir = package_dir.join(previous_version.to_string());
72    if latest_symlink_path.exists() || latest_symlink_path.is_symlink() {
73        if latest_symlink_path.is_dir() {
74            fs::remove_dir_all(&latest_symlink_path)?;
75        } else {
76            fs::remove_file(&latest_symlink_path)?;
77        }
78    }
79    #[cfg(unix)]
80    std::os::unix::fs::symlink(&previous_version_dir, &latest_symlink_path)?;
81    #[cfg(windows)]
82    {
83        junction::create(&previous_version_dir, &latest_symlink_path)?;
84    }
85
86    let prev_manifest_path = previous_version_dir.join("manifest.yaml");
87    let content = fs::read_to_string(&prev_manifest_path)?;
88    let prev_manifest: types::InstallManifest = serde_yaml::from_str(&content)?;
89
90    if let Some(bins) = &prev_manifest.bins {
91        let bin_root = get_bin_root()?;
92        for bin in bins {
93            let symlink_path = bin_root.join(bin);
94            if symlink_path.exists() {
95                fs::remove_file(&symlink_path)?;
96            }
97            let bin_path_in_store = previous_version_dir.join("bin").join(bin);
98            if bin_path_in_store.exists() {
99                create_symlink(&bin_path_in_store, &symlink_path)?;
100            }
101        }
102    }
103
104    let current_version_dir = package_dir.join(current_version.to_string());
105    fs::remove_dir_all(current_version_dir)?;
106
107    println!(
108        "Successfully rolled back '{}' to version {}.",
109        package_name.cyan(),
110        previous_version.to_string().green()
111    );
112
113    Ok(())
114}
115
116fn get_bin_root() -> Result<PathBuf> {
117    let home_dir = home::home_dir().ok_or_else(|| anyhow!("Could not find home directory."))?;
118    Ok(home_dir.join(".zoi").join("pkgs").join("bin"))
119}
120
121fn create_symlink(target: &Path, link: &Path) -> Result<()> {
122    if link.exists() {
123        fs::remove_file(link)?;
124    }
125    #[cfg(unix)]
126    {
127        std::os::unix::fs::symlink(target, link)?;
128    }
129    #[cfg(windows)]
130    {
131        fs::copy(target, link)?;
132    }
133    Ok(())
134}