1use crate::pkg;
2use anyhow::Result;
3use colored::*;
4
5pub fn run() -> Result<()> {
6 println!("{} Running Zoi doctor...", "::".bold().blue());
7 println!("Checking your system for potential issues...");
8
9 let mut issues_found = 0;
10
11 println!("\n{} Checking for broken symlinks...", "->".bold().cyan());
12 match pkg::doctor::check_broken_symlinks() {
13 Ok(broken_links) => {
14 if broken_links.is_empty() {
15 println!("{}", "No broken symlinks found.".green());
16 } else {
17 issues_found += broken_links.len();
18 println!(
19 "{}: Found {} broken symlinks:",
20 "Warning".yellow(),
21 broken_links.len()
22 );
23 for link in broken_links {
24 println!(" - {}", link.display());
25 }
26 println!(
27 "\nConsider running 'zoi uninstall <package>' and reinstalling it for the affected packages."
28 );
29 }
30 }
31 Err(e) => {
32 eprintln!(
33 "{}: Failed to check for broken symlinks: {}",
34 "Error".red(),
35 e
36 );
37 issues_found += 1;
38 }
39 }
40
41 println!("\n{} Checking PATH configuration...", "->".bold().cyan());
42 match pkg::doctor::check_path_configuration() {
43 Ok(Some(warning)) => {
44 issues_found += 1;
45 println!("{}: {}", "Warning".yellow(), warning);
46 println!("Please run 'zoi shell <shell>' to add Zoi's binary directory to your PATH.");
47 }
48 Ok(None) => {
49 println!("{}", "PATH configuration looks good.".green());
50 }
51 Err(e) => {
52 eprintln!(
53 "{}: Failed to check PATH configuration: {}",
54 "Error".red(),
55 e
56 );
57 issues_found += 1;
58 }
59 }
60
61 println!(
62 "\n{} Checking for outdated repositories...",
63 "->".bold().cyan()
64 );
65 match pkg::doctor::check_outdated_repos() {
66 Ok(Some(warning)) => {
67 issues_found += 1;
68 println!("{}: {}", "Warning".yellow(), warning);
69 println!("Consider running 'zoi sync' to update your local package database.");
70 }
71 Ok(None) => {
72 println!("{}", "Repositories look up to date.".green());
73 }
74 Err(e) => {
75 eprintln!("{}: Failed to check repositories: {}", "Error".red(), e);
76 issues_found += 1;
77 }
78 }
79
80 println!(
81 "\n{} Checking for duplicate package IDs...",
82 "->".bold().cyan()
83 );
84 match pkg::doctor::check_duplicate_packages() {
85 Ok(duplicates) => {
86 if duplicates.is_empty() {
87 println!("{}", "No duplicate package IDs found.".green());
88 } else {
89 issues_found += duplicates.len();
90 println!(
91 "{}: Found {} duplicate package IDs across registries:",
92 "Warning".yellow(),
93 duplicates.len()
94 );
95 for (pkg_id, registries) in duplicates {
96 println!(
97 " - {} (found in: {})",
98 pkg_id.cyan(),
99 registries.join(", ")
100 );
101 }
102 println!(
103 "\nThis may cause ambiguity during installation. Consider specifying the registry handle (e.g. #registry@repo/name)."
104 );
105 }
106 }
107 Err(e) => {
108 eprintln!("{}: Failed to check for duplicates: {}", "Error".red(), e);
109 issues_found += 1;
110 }
111 }
112
113 println!("\n{} Checking PGP configurations...", "->".bold().cyan());
114 match pkg::doctor::check_pgp_configuration() {
115 Ok(missing_keys) => {
116 if missing_keys.is_empty() {
117 println!("{}", "PGP configuration looks valid.".green());
118 } else {
119 issues_found += missing_keys.len();
120 println!(
121 "{}: The following trusted PGP keys are missing from your keyring:",
122 "Warning".yellow()
123 );
124 for key in missing_keys {
125 println!(" - {}", key.red());
126 }
127 println!("\nRun 'zoi pgp add --name <name> --url <url>' to add missing keys.");
128 }
129 }
130 Err(e) => {
131 eprintln!(
132 "{}: Failed to check PGP configuration: {}",
133 "Error".red(),
134 e
135 );
136 issues_found += 1;
137 }
138 }
139
140 println!("\n{} Validating zoi.lock integrity...", "->".bold().cyan());
141 match pkg::doctor::validate_lockfile_integrity() {
142 Ok(missing_packages) => {
143 if missing_packages.is_empty() {
144 println!("{}", "zoi.lock integrity is good.".green());
145 } else {
146 issues_found += missing_packages.len();
147 println!(
148 "{}: The following packages are recorded but missing from the store:",
149 "Warning".yellow()
150 );
151 for pkg in missing_packages {
152 println!(" - {}", pkg.red());
153 }
154 println!(
155 "\nYour package record file is out of sync with the actual installation store."
156 );
157 }
158 }
159 Err(e) => {
160 eprintln!("{}: Failed to validate zoi.lock: {}", "Error".red(), e);
161 issues_found += 1;
162 }
163 }
164
165 println!("\n{} Checking for orphaned packages...", "->".bold().cyan());
166 match pkg::doctor::check_orphaned_packages() {
167 Ok(orphaned) => {
168 if orphaned.is_empty() {
169 println!("{}", "No orphaned packages found.".green());
170 } else {
171 issues_found += orphaned.len();
172 println!(
173 "{}: Found {} orphaned packages (unused dependencies):",
174 "Warning".yellow(),
175 orphaned.len()
176 );
177 for pkg in orphaned {
178 println!(" - {}", pkg.cyan());
179 }
180 println!("\nConsider running 'zoi autoremove' to clean up these packages.");
181 }
182 }
183 Err(e) => {
184 eprintln!(
185 "{}: Failed to check for orphaned packages: {}",
186 "Error".red(),
187 e
188 );
189 issues_found += 1;
190 }
191 }
192
193 println!("\n{} Checking for ghost dependents...", "->".bold().cyan());
194 match pkg::doctor::check_ghost_dependents() {
195 Ok(ghost_links) => {
196 if ghost_links.is_empty() {
197 println!("{}", "No ghost dependents found.".green());
198 } else {
199 issues_found += ghost_links.len();
200 println!(
201 "{}: Found {} broken dependent links (ghost parents):",
202 "Warning".yellow(),
203 ghost_links.len()
204 );
205 for (_, parent_id) in &ghost_links {
206 println!(" - parent missing: {}", parent_id.cyan());
207 }
208
209 if crate::utils::ask_for_confirmation(
210 "\nDo you want to prune these broken links?",
211 false,
212 ) {
213 pkg::doctor::prune_ghost_dependents(&ghost_links)?;
214 println!("{}", "Successfully pruned broken links.".green());
215 issues_found -= ghost_links.len();
216 } else {
217 println!("Broken links were NOT pruned.");
218 }
219 }
220 }
221 Err(e) => {
222 eprintln!(
223 "{}: Failed to check for ghost dependents: {}",
224 "Error".red(),
225 e
226 );
227 issues_found += 1;
228 }
229 }
230
231 println!("\n{} Checking for external tools...", "->".bold().cyan());
232 let tool_results = pkg::doctor::check_external_tools();
233 if tool_results.essential_missing.is_empty() && tool_results.recommended_missing.is_empty() {
234 println!(
235 "{}",
236 "All essential and recommended tools are installed.".green()
237 );
238 } else {
239 if !tool_results.essential_missing.is_empty() {
240 issues_found += tool_results.essential_missing.len();
241 println!("{}: Essential tools are missing:", "Error".red().bold());
242 for tool in tool_results.essential_missing {
243 println!(" - {}", tool.red());
244 }
245 println!(
246 "Please install these tools as they are required for Zoi to function correctly."
247 );
248 }
249 if !tool_results.recommended_missing.is_empty() {
250 println!("{}: Recommended tools are missing:", "Note".yellow().bold());
251 for tool in tool_results.recommended_missing {
252 println!(" - {}", tool.yellow());
253 }
254 println!("Zoi will work without these, but some features may be limited.");
255 }
256 }
257
258 if issues_found == 0 {
259 println!(
260 "\n{}",
261 "Zoi is looking healthy! No issues found.".green().bold()
262 );
263 } else {
264 println!("\nFound {} potential issues.", issues_found);
265 }
266
267 Ok(())
268}