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}