toasty_cli/migration/
drop.rs

1use super::HistoryFile;
2use crate::{Config, theme::dialoguer_theme};
3use anyhow::Result;
4use clap::Parser;
5use console::style;
6use dialoguer::Select;
7use std::fs;
8use toasty::Db;
9
10/// Removes a migration from the history and deletes its files on disk.
11///
12/// The migration to drop can be specified by `--name`, by `--latest`, or by
13/// interactive selection when neither flag is provided. Dropping a migration
14/// removes its SQL file, its snapshot file, and its entry in the history
15/// file. It does **not** undo any changes already applied to the database.
16#[derive(Parser, Debug)]
17pub struct DropCommand {
18    /// Name of the migration to drop (if not provided, will prompt)
19    #[arg(short, long)]
20    name: Option<String>,
21
22    /// Drop the latest migration
23    #[arg(short, long)]
24    latest: bool,
25}
26
27impl DropCommand {
28    pub(crate) fn run(self, _db: &Db, config: &Config) -> Result<()> {
29        let history_path = config.migration.get_history_file_path();
30        let mut history = HistoryFile::load_or_default(&history_path)?;
31
32        if history.migrations().is_empty() {
33            eprintln!("{}", style("No migrations found in history").red().bold());
34            anyhow::bail!("No migrations found in history");
35        }
36
37        // Determine which migration to drop
38        let migration_index = if self.latest {
39            // Drop the latest migration
40            history.migrations().len() - 1
41        } else if let Some(name) = &self.name {
42            // Find migration by name
43            history
44                .migrations()
45                .iter()
46                .position(|m| m.name == *name)
47                .ok_or_else(|| anyhow::anyhow!("Migration '{}' not found", name))?
48        } else {
49            // Interactive picker with fancy theme
50            println!();
51            println!("  {}", style("Drop Migration").cyan().bold().underlined());
52            println!();
53
54            let migration_display: Vec<String> = history
55                .migrations()
56                .iter()
57                .map(|m| format!("  {}", m.name))
58                .collect();
59
60            Select::with_theme(&dialoguer_theme())
61                .with_prompt("  Select migration to drop")
62                .items(&migration_display)
63                .default(migration_display.len() - 1)
64                .interact()?
65        };
66
67        println!();
68
69        let migration = &history.migrations()[migration_index];
70        let migration_name = migration.name.clone();
71        let snapshot_name = migration.snapshot_name.clone();
72
73        // Delete migration file
74        let migration_path = config.migration.get_migrations_dir().join(&migration_name);
75        if migration_path.exists() {
76            fs::remove_file(&migration_path)?;
77            println!(
78                "  {} {}",
79                style("✓").green().bold(),
80                style(format!("Deleted migration: {}", migration_name)).dim()
81            );
82        } else {
83            println!(
84                "  {} {}",
85                style("⚠").yellow().bold(),
86                style(format!("Migration file not found: {}", migration_name))
87                    .yellow()
88                    .dim()
89            );
90        }
91
92        // Delete snapshot file
93        let snapshot_path = config.migration.get_snapshots_dir().join(&snapshot_name);
94        if snapshot_path.exists() {
95            fs::remove_file(&snapshot_path)?;
96            println!(
97                "  {} {}",
98                style("✓").green().bold(),
99                style(format!("Deleted snapshot: {}", snapshot_name)).dim()
100            );
101        } else {
102            println!(
103                "  {} {}",
104                style("⚠").yellow().bold(),
105                style(format!("Snapshot file not found: {}", snapshot_name))
106                    .yellow()
107                    .dim()
108            );
109        }
110
111        // Remove from history
112        history.remove_migration(migration_index);
113        history.save(&history_path)?;
114
115        println!(
116            "  {} {}",
117            style("✓").green().bold(),
118            style("Updated migration history").dim()
119        );
120        println!();
121        println!(
122            "  {} {}",
123            style("").magenta(),
124            style(format!(
125                "Migration '{}' successfully dropped",
126                migration_name
127            ))
128            .green()
129            .bold()
130        );
131        println!();
132
133        Ok(())
134    }
135}