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