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