Skip to main content

opencode_ralph_loop_cli/commands/
uninstall.rs

1use std::path::{Path, PathBuf};
2use std::time::Instant;
3
4use crate::cli::{OutputFormat, UninstallArgs};
5use crate::error::CliError;
6use crate::output::{Action, FileEntry, Report};
7
8pub fn run(args: &UninstallArgs, output: &OutputFormat) -> Result<(), CliError> {
9    let start = Instant::now();
10
11    let target = resolve_target(&args.path)?;
12    let opencode_dir = target.join(".opencode");
13
14    let manifest = if args.force {
15        crate::manifest::load(&opencode_dir)
16            .unwrap_or_else(|_| crate::manifest::Manifest::new("unknown"))
17    } else {
18        crate::manifest::load(&opencode_dir)?
19    };
20
21    let plugin_version = manifest.plugin_version.clone();
22    let mut report = Report::new("uninstall", &plugin_version);
23
24    for file in &manifest.files {
25        if args.keep_state && file.path == "ralph-loop.local.md" {
26            continue;
27        }
28        if args.keep_node_modules && file.path.starts_with("node_modules") {
29            continue;
30        }
31
32        let dest = opencode_dir.join(&file.path);
33
34        let action = if dest.exists() {
35            if !args.dry_run {
36                std::fs::remove_file(&dest)
37                    .map_err(|e| CliError::io(dest.to_string_lossy().into_owned(), e))?;
38            }
39            Action::Removed
40        } else {
41            Action::Missing
42        };
43
44        report.files.push(FileEntry {
45            path: file.path.clone(),
46            action,
47            sha256: file.sha256.clone(),
48            expected_sha256: None,
49            size_bytes: file.size_bytes,
50        });
51    }
52
53    if !args.dry_run {
54        crate::manifest::remove(&opencode_dir)?;
55    }
56
57    report.duration_ms = start.elapsed().as_millis() as u64;
58    report.finalize();
59    emit_report(&report, output);
60
61    Ok(())
62}
63
64fn resolve_target(path: &Path) -> Result<PathBuf, CliError> {
65    let resolved = if path.is_absolute() {
66        path.to_path_buf()
67    } else {
68        std::env::current_dir()
69            .map_err(|e| CliError::io("current directory", e))?
70            .join(path)
71    };
72
73    if !resolved.exists() {
74        return Err(CliError::Io {
75            path: resolved.to_string_lossy().to_string(),
76            source: std::io::Error::new(std::io::ErrorKind::NotFound, "directory does not exist"),
77        });
78    }
79
80    Ok(resolved)
81}
82
83fn emit_report(report: &Report, format: &OutputFormat) {
84    match format {
85        OutputFormat::Text => crate::output::text::print_report(report),
86        OutputFormat::Json => crate::output::json::print_report(report),
87        OutputFormat::Ndjson => crate::output::ndjson::print_report(report),
88        OutputFormat::Quiet => {}
89    }
90}