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}