drizzle_cli/commands/
check.rs

1//! Check command - validates configuration
2
3use std::path::Path;
4
5use crate::config::{Config, Credentials, PostgresCreds};
6use crate::error::CliError;
7use crate::output;
8
9pub fn run(
10    config: &Config,
11    db_name: Option<&str>,
12    _dialect_override: Option<&str>,
13    out_override: Option<&Path>,
14) -> Result<(), CliError> {
15    let db = config.database(db_name)?;
16
17    // CLI flags override config
18    let effective_out = out_override
19        .map(Path::to_path_buf)
20        .unwrap_or(db.out.clone());
21
22    println!("{}", output::heading("Checking configuration..."));
23    println!();
24
25    if !config.is_single_database() {
26        let name = db_name.unwrap_or("(default)");
27        println!("  {}: {}", output::label("Database"), name);
28    }
29
30    let mut warnings = Vec::new();
31    let mut has_errors = false;
32
33    // Basic info
34    println!("  {}: {}", output::label("Dialect"), db.dialect);
35    if let Some(driver) = db.driver {
36        println!("  {}: {}", output::label("Driver"), driver);
37    }
38    println!("  {}: {}", output::label("Schema"), db.schema_display());
39    println!("  {}: {}", output::label("Output"), effective_out.display());
40
41    // Schema files
42    println!();
43    print!("  {} Schema files... ", output::label("Checking"));
44    match db.schema_files() {
45        Ok(files) => {
46            println!("{}", output::status_ok());
47            for f in &files {
48                println!("    {}", f.display());
49            }
50        }
51        Err(e) => {
52            println!("{}", output::status_error());
53            println!("    {e}");
54            has_errors = true;
55        }
56    }
57
58    // Migrations dir
59    println!();
60    print!("  {} Migrations... ", output::label("Checking"));
61    let dir = &effective_out;
62    let journal_path = effective_out.join("meta").join("_journal.json");
63    if dir.exists() {
64        println!("{}", output::status_ok());
65        if journal_path.exists() {
66            println!("    Journal: {}", output::success("found"));
67        } else {
68            println!(
69                "    Journal: {} (run generate first)",
70                output::warning("missing")
71            );
72            warnings.push("No migration journal");
73        }
74    } else {
75        println!("{}", output::status_warning("NOT CREATED"));
76        warnings.push("Migrations directory doesn't exist yet");
77    }
78
79    // Credentials
80    println!();
81    print!("  {} Credentials... ", output::label("Checking"));
82    match db.credentials() {
83        Ok(Some(creds)) => {
84            println!("{}", output::status_ok());
85            print_credentials(&creds);
86        }
87        Ok(None) => {
88            println!("{}", output::status_warning("NOT SET"));
89            warnings.push("No credentials (needed for push/pull/migrate)");
90        }
91        Err(e) => {
92            println!("{}", output::status_error());
93            println!("    {}", e);
94            has_errors = true;
95        }
96    }
97
98    // Summary
99    println!();
100    if has_errors {
101        println!("{}", output::error("Configuration has errors."));
102        Err(CliError::Other("config check failed".into()))
103    } else if warnings.is_empty() {
104        println!("{}", output::success("Configuration OK."));
105        Ok(())
106    } else {
107        println!(
108            "{}",
109            output::warning(&format!("{} warning(s):", warnings.len()))
110        );
111        for w in warnings {
112            println!("  - {w}");
113        }
114        Ok(())
115    }
116}
117
118fn print_credentials(creds: &Credentials) {
119    match creds {
120        Credentials::Sqlite { path } => {
121            println!("    {}: {path}", output::label("SQLite"));
122        }
123        Credentials::Turso { url, auth_token } => {
124            println!("    {}: {}", output::label("Turso"), mask_url(url));
125            if auth_token.is_some() {
126                println!("    Token: ****");
127            }
128        }
129        Credentials::Postgres(pg) => match pg {
130            PostgresCreds::Url(url) => {
131                println!("    {}: {}", output::label("PostgreSQL"), mask_url(url))
132            }
133            PostgresCreds::Host {
134                host,
135                port,
136                database,
137                user,
138                ..
139            } => {
140                println!(
141                    "    {}: {host}:{port}/{database}",
142                    output::label("PostgreSQL")
143                );
144                if let Some(u) = user {
145                    println!("    User: {u}");
146                }
147            }
148        },
149    }
150}
151
152fn mask_url(url: &str) -> String {
153    if let Some(at) = url.find('@')
154        && let Some(colon) = url[..at].rfind(':')
155    {
156        let scheme_end = url.find("://").map(|p| p + 3).unwrap_or(0);
157        if colon > scheme_end {
158            return format!("{}****{}", &url[..colon + 1], &url[at..]);
159        }
160    }
161    url.to_string()
162}