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