drizzle_cli/commands/
check.rs1use std::path::{Path, PathBuf};
4
5use crate::config::{Config, Credentials, Dialect, PostgresCreds};
6use crate::error::CliError;
7use crate::output;
8
9#[derive(clap::Args, Debug, Clone, Default)]
10pub struct CheckOptions {
11 #[arg(long)]
13 pub dialect: Option<Dialect>,
14
15 #[arg(long)]
17 pub out: Option<PathBuf>,
18}
19
20pub fn run(config: &Config, db_name: Option<&str>, opts: &CheckOptions) -> Result<(), CliError> {
30 let db = config.database(db_name)?;
31
32 let effective_dialect = opts.dialect.unwrap_or(db.dialect);
34 let effective_out = opts
35 .out
36 .as_deref()
37 .map_or_else(|| db.out.clone(), Path::to_path_buf);
38
39 println!("{}", output::heading("Checking configuration..."));
40 println!();
41
42 crate::commands::harness::print_db_header(config, db_name);
43
44 let mut warnings = Vec::new();
45 let mut has_errors = false;
46
47 println!(" {}: {}", output::label("Dialect"), effective_dialect);
49 if let Some(driver) = db.driver {
50 println!(" {}: {}", output::label("Driver"), driver);
51 }
52 println!(" {}: {}", output::label("Schema"), db.schema_display());
53 println!(" {}: {}", output::label("Output"), effective_out.display());
54
55 println!();
57 print!(" {} Schema files... ", output::label("Checking"));
58 match db.schema_files() {
59 Ok(files) => {
60 println!("{}", output::status_ok());
61 for f in &files {
62 println!(" {}", f.display());
63 }
64 }
65 Err(e) => {
66 println!("{}", output::status_error());
67 println!(" {e}");
68 has_errors = true;
69 }
70 }
71
72 println!();
74 print!(" {} Migrations... ", output::label("Checking"));
75 let dir = &effective_out;
76 let journal_path = effective_out.join("meta").join("_journal.json");
77 if dir.exists() {
78 println!("{}", output::status_ok());
79 let migration_count = count_migration_dirs(dir)?;
80 if migration_count > 0 {
81 println!(
82 " Folders: {}",
83 output::success(&migration_count.to_string())
84 );
85 } else {
86 println!(
87 " Folders: {} (run generate first)",
88 output::warning("missing")
89 );
90 warnings.push("No migration folders");
91 }
92
93 if journal_path.exists() {
94 println!(
95 " Legacy journal: {} (run upgrade)",
96 output::warning("found")
97 );
98 warnings.push("Legacy migration journal detected");
99 }
100 } else {
101 println!("{}", output::status_warning("NOT CREATED"));
102 warnings.push("Migrations directory doesn't exist yet");
103 }
104
105 println!();
107 print!(" {} Credentials... ", output::label("Checking"));
108 match db.credentials() {
109 Ok(Some(creds)) => {
110 println!("{}", output::status_ok());
111 print_credentials(&creds);
112 }
113 Ok(None) => {
114 println!("{}", output::status_warning("NOT SET"));
115 warnings.push("No credentials (needed for push/pull/migrate)");
116 }
117 Err(e) => {
118 println!("{}", output::status_error());
119 println!(" {e}");
120 has_errors = true;
121 }
122 }
123
124 println!();
126 if has_errors {
127 println!("{}", output::error("Configuration has errors."));
128 Err(CliError::Other("config check failed".into()))
129 } else if warnings.is_empty() {
130 println!("{}", output::success("Configuration OK."));
131 Ok(())
132 } else {
133 println!(
134 "{}",
135 output::warning(&format!("{} warning(s):", warnings.len()))
136 );
137 for w in warnings {
138 println!(" - {w}");
139 }
140 Ok(())
141 }
142}
143
144fn count_migration_dirs(dir: &Path) -> Result<usize, CliError> {
145 let mut count = 0usize;
146 for entry in std::fs::read_dir(dir).map_err(|e| CliError::IoError(e.to_string()))? {
147 let entry = entry.map_err(|e| CliError::IoError(e.to_string()))?;
148 if !entry
149 .file_type()
150 .map_err(|e| CliError::IoError(e.to_string()))?
151 .is_dir()
152 {
153 continue;
154 }
155
156 let tag = entry.file_name().to_string_lossy().to_string();
157 if tag == "meta" {
158 continue;
159 }
160
161 if entry.path().join("migration.sql").exists() {
162 count += 1;
163 }
164 }
165
166 Ok(count)
167}
168
169fn print_credentials(creds: &Credentials) {
170 match creds {
171 Credentials::Sqlite { path } => {
172 println!(" {}: {path}", output::label("SQLite"));
173 }
174 Credentials::Turso { url, auth_token } => {
175 println!(" {}: {}", output::label("Turso"), mask_url(url));
176 if auth_token.is_some() {
177 println!(" Token: ****");
178 }
179 }
180 Credentials::Postgres(pg) => match pg {
181 PostgresCreds::Url(url) => {
182 println!(" {}: {}", output::label("PostgreSQL"), mask_url(url));
183 }
184 PostgresCreds::Host {
185 host,
186 port,
187 database,
188 user,
189 ..
190 } => {
191 println!(
192 " {}: {host}:{port}/{database}",
193 output::label("PostgreSQL")
194 );
195 if let Some(u) = user {
196 println!(" User: {u}");
197 }
198 }
199 },
200 Credentials::D1 {
201 account_id,
202 database_id,
203 ..
204 } => {
205 println!(" {}: {account_id}/{database_id}", output::label("D1"));
207 println!(" Token: ****");
208 }
209 Credentials::AwsDataApi { database, .. } => {
210 println!(" {}: {database}", output::label("AWS Data API"));
213 println!(" SecretArn: ****");
214 println!(" ResourceArn: ****");
215 }
216 }
217}
218
219fn mask_url(url: &str) -> String {
220 if let Some(at) = url.find('@')
221 && let Some(colon) = url[..at].rfind(':')
222 {
223 let scheme_end = url.find("://").map_or(0, |p| p + 3);
224 if colon > scheme_end {
225 return format!("{}****{}", &url[..=colon], &url[at..]);
226 }
227 }
228 url.to_string()
229}