drizzle_cli/commands/
check.rs1use std::path::{Path, PathBuf};
4
5use crate::config::{Config, Credentials, Dialect, MySQLCreds, 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"), output::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!(
183 " {}: {}",
184 output::label("PostgreSQL"),
185 output::mask_url(url)
186 );
187 }
188 PostgresCreds::Host {
189 host,
190 port,
191 database,
192 user,
193 ..
194 } => {
195 println!(
196 " {}: {host}:{port}/{database}",
197 output::label("PostgreSQL")
198 );
199 if let Some(u) = user {
200 println!(" User: {u}");
201 }
202 }
203 },
204 Credentials::MySQL(mysql) => match mysql {
205 MySQLCreds::Url(url) => {
206 println!(" {}: {}", output::label("MySQL"), output::mask_url(url));
207 }
208 MySQLCreds::Host {
209 host,
210 port,
211 database,
212 user,
213 ..
214 } => {
215 println!(" {}: {host}:{port}/{database}", output::label("MySQL"));
216 if let Some(user) = user {
217 println!(" User: {user}");
218 }
219 }
220 },
221 Credentials::D1 {
222 account_id,
223 database_id,
224 ..
225 } => {
226 println!(" {}: {account_id}/{database_id}", output::label("D1"));
228 println!(" Token: ****");
229 }
230 Credentials::AwsDataApi { database, .. } => {
231 println!(" {}: {database}", output::label("AWS Data API"));
234 println!(" SecretArn: ****");
235 println!(" ResourceArn: ****");
236 }
237 }
238}