drizzle_cli/commands/
status.rs1use crate::config::Config;
6use crate::error::CliError;
7use crate::output;
8
9pub fn run(config: &Config, db_name: Option<&str>) -> Result<(), CliError> {
17 let db = config.database(db_name)?;
18
19 println!("{}", output::heading("Migration Status"));
20 println!();
21
22 if !config.is_single_database() {
23 crate::commands::harness::print_db_header(config, db_name);
24 println!();
25 }
26
27 let out_dir = db.migrations_dir();
28 let journal_path = db.journal_path();
29
30 if !out_dir.exists() {
32 println!(" {}", output::warning("No migrations directory found."));
33 println!(" Run 'drizzle generate' to create your first migration.");
34 return Ok(());
35 }
36
37 if journal_path.exists() {
38 println!(
39 " {}",
40 output::warning("Legacy migration journal detected. Run 'drizzle upgrade' first.")
41 );
42 println!();
43 }
44
45 let entries = discover_migration_dirs(out_dir)?;
46 if entries.is_empty() {
47 println!(" {}", output::warning("No migrations found."));
48 return Ok(());
49 }
50
51 println!(" {} migration folder(s):\n", entries.len());
53
54 for (i, (tag, migration_path, snapshot_path)) in entries.iter().enumerate() {
55 let sql_exists = migration_path.exists();
57 let snapshot_exists = snapshot_path.exists();
58
59 let status_icon = if sql_exists && snapshot_exists {
60 output::success("✓")
61 } else if sql_exists {
62 output::warning("○")
63 } else {
64 output::error("✗")
65 };
66 let idx_display = output::muted(&format!("{:3}.", i + 1));
67
68 println!(" {idx_display} {status_icon} {tag}");
69
70 if !sql_exists {
71 println!(" {}", output::error("Migration file missing!"));
72 }
73 if !snapshot_exists && sql_exists {
74 println!(" {}", output::warning("Snapshot file missing"));
75 }
76 }
77
78 println!();
79 println!(
80 " {}: {}",
81 output::muted("Migrations directory"),
82 out_dir.display()
83 );
84 println!(
85 " {}: {}",
86 output::muted("Schema files"),
87 db.schema_display()
88 );
89
90 Ok(())
91}
92
93fn discover_migration_dirs(
94 out_dir: &std::path::Path,
95) -> Result<Vec<(String, std::path::PathBuf, std::path::PathBuf)>, CliError> {
96 let mut entries = Vec::new();
97
98 for entry in std::fs::read_dir(out_dir).map_err(|e| CliError::IoError(e.to_string()))? {
99 let entry = entry.map_err(|e| CliError::IoError(e.to_string()))?;
100 if !entry
101 .file_type()
102 .map_err(|e| CliError::IoError(e.to_string()))?
103 .is_dir()
104 {
105 continue;
106 }
107
108 let tag = entry.file_name().to_string_lossy().to_string();
109 if tag == "meta" {
110 continue;
111 }
112
113 let path = entry.path();
114 let migration_path = path.join("migration.sql");
115 if !migration_path.exists() {
116 continue;
117 }
118
119 let snapshot_path = path.join("snapshot.json");
120 entries.push((tag, migration_path, snapshot_path));
121 }
122
123 entries.sort_by(|a, b| a.0.cmp(&b.0));
124 Ok(entries)
125}