Skip to main content

chub_cli/commands/
snapshot.rs

1use clap::{Args, Subcommand};
2use owo_colors::OwoColorize;
3
4use chub_core::team::snapshots;
5
6use crate::output;
7
8#[derive(Args)]
9pub struct SnapshotArgs {
10    #[command(subcommand)]
11    command: SnapshotCommand,
12}
13
14#[derive(Subcommand)]
15pub enum SnapshotCommand {
16    /// Capture all pinned doc versions as a snapshot
17    Create(SnapshotCreateArgs),
18    /// Restore exact doc versions from a snapshot
19    Restore(SnapshotRestoreArgs),
20    /// Show what changed between two snapshots
21    Diff(SnapshotDiffArgs),
22    /// List all snapshots
23    List,
24}
25
26#[derive(Args)]
27pub struct SnapshotCreateArgs {
28    /// Snapshot name (e.g. "v2.1.0")
29    name: String,
30}
31
32#[derive(Args)]
33pub struct SnapshotRestoreArgs {
34    /// Snapshot name to restore
35    name: String,
36}
37
38#[derive(Args)]
39pub struct SnapshotDiffArgs {
40    /// First snapshot name
41    from: String,
42    /// Second snapshot name
43    to: String,
44}
45
46pub fn run(args: SnapshotArgs, json: bool) {
47    match args.command {
48        SnapshotCommand::Create(a) => run_create(a, json),
49        SnapshotCommand::Restore(a) => run_restore(a, json),
50        SnapshotCommand::Diff(a) => run_diff(a, json),
51        SnapshotCommand::List => run_list(json),
52    }
53}
54
55fn run_create(args: SnapshotCreateArgs, json: bool) {
56    match snapshots::create_snapshot(&args.name) {
57        Ok(snap) => {
58            if json {
59                println!(
60                    "{}",
61                    serde_json::json!({
62                        "status": "created",
63                        "name": snap.name,
64                        "pins": snap.pins.len(),
65                        "created_at": snap.created_at,
66                    })
67                );
68            } else {
69                output::success(&format!(
70                    "Snapshot \"{}\" created with {} pins.",
71                    snap.name.bold(),
72                    snap.pins.len()
73                ));
74            }
75        }
76        Err(e) => {
77            output::error(&e.to_string(), json);
78            std::process::exit(1);
79        }
80    }
81}
82
83fn run_restore(args: SnapshotRestoreArgs, json: bool) {
84    match snapshots::restore_snapshot(&args.name) {
85        Ok(snap) => {
86            if json {
87                println!(
88                    "{}",
89                    serde_json::json!({
90                        "status": "restored",
91                        "name": snap.name,
92                        "pins": snap.pins.len(),
93                    })
94                );
95            } else {
96                output::success(&format!(
97                    "Restored snapshot \"{}\": {} pins.",
98                    snap.name.bold(),
99                    snap.pins.len()
100                ));
101            }
102        }
103        Err(e) => {
104            output::error(&e.to_string(), json);
105            std::process::exit(1);
106        }
107    }
108}
109
110fn run_diff(args: SnapshotDiffArgs, json: bool) {
111    match snapshots::diff_snapshots(&args.from, &args.to) {
112        Ok(diffs) => {
113            if json {
114                println!(
115                    "{}",
116                    serde_json::to_string_pretty(&serde_json::json!({
117                        "from": args.from,
118                        "to": args.to,
119                        "changes": diffs,
120                    }))
121                    .unwrap_or_default()
122                );
123            } else {
124                if diffs.is_empty() {
125                    eprintln!("{}", "No changes between snapshots.".dimmed());
126                    return;
127                }
128                eprintln!(
129                    "{}\n",
130                    format!("{} → {} ({} changes):", args.from, args.to, diffs.len()).bold()
131                );
132                for diff in &diffs {
133                    match &diff.change {
134                        snapshots::DiffChange::Added { version } => {
135                            let v = version.as_deref().unwrap_or("latest");
136                            eprintln!("  {} {} ({})", "+".green(), diff.id.bold(), v);
137                        }
138                        snapshots::DiffChange::Removed { version } => {
139                            let v = version.as_deref().unwrap_or("latest");
140                            eprintln!("  {} {} ({})", "-".red(), diff.id.bold(), v);
141                        }
142                        snapshots::DiffChange::Changed {
143                            from_version,
144                            to_version,
145                        } => {
146                            let from = from_version.as_deref().unwrap_or("?");
147                            let to = to_version.as_deref().unwrap_or("?");
148                            eprintln!("  {} {} ({} → {})", "~".yellow(), diff.id.bold(), from, to);
149                        }
150                    }
151                }
152            }
153        }
154        Err(e) => {
155            output::error(&e.to_string(), json);
156            std::process::exit(1);
157        }
158    }
159}
160
161fn run_list(json: bool) {
162    let snapshots_list = snapshots::list_snapshots();
163
164    if json {
165        let items: Vec<serde_json::Value> = snapshots_list
166            .iter()
167            .map(|(name, created_at)| {
168                serde_json::json!({
169                    "name": name,
170                    "created_at": created_at,
171                })
172            })
173            .collect();
174        println!(
175            "{}",
176            serde_json::to_string_pretty(&serde_json::json!({
177                "snapshots": items,
178                "total": snapshots_list.len(),
179            }))
180            .unwrap_or_default()
181        );
182    } else {
183        if snapshots_list.is_empty() {
184            eprintln!(
185                "{}",
186                "No snapshots. Use `chub snapshot create <name>` to create one.".dimmed()
187            );
188            return;
189        }
190        eprintln!(
191            "{}",
192            format!("{} snapshots:\n", snapshots_list.len()).bold()
193        );
194        for (name, created_at) in &snapshots_list {
195            eprintln!("  {}  {}", name.bold(), created_at.dimmed());
196        }
197    }
198}