drizzle_cli/commands/
init.rs1use crate::config::CONFIG_FILE;
4use crate::error::CliError;
5use crate::output;
6use std::path::Path;
7
8const SCHEMA_URL: &str =
10 "https://raw.githubusercontent.com/themixednuts/drizzle-rs/main/cli/schema.json";
11
12pub const DEFAULT_SCHEMA: &str = "src/schema.rs";
14
15pub const DEFAULT_OUT: &str = "./drizzle";
17
18pub fn run(dialect: &str, driver: Option<&str>) -> Result<(), CliError> {
25 let config_path = Path::new(CONFIG_FILE);
26
27 if config_path.exists() {
28 return Err(CliError::Other(format!(
29 "{CONFIG_FILE} already exists. Delete it first to reinitialize."
30 )));
31 }
32
33 let config_content = config_contents(dialect, driver, DEFAULT_SCHEMA, DEFAULT_OUT)?;
34
35 std::fs::write(config_path, config_content).map_err(|e| CliError::IoError(e.to_string()))?;
36
37 println!("{}", output::success(&format!("Created {CONFIG_FILE}")));
38 println!();
39 println!("Next steps:");
40 println!(" 1. Edit {CONFIG_FILE} with your database credentials");
41 println!(
42 " 2. Create your schema file at {}",
43 output::heading(DEFAULT_SCHEMA)
44 );
45 println!(
46 " 3. Run {} to generate your first migration",
47 output::heading("drizzle generate")
48 );
49
50 Ok(())
51}
52
53pub fn config_contents(
64 dialect: &str,
65 driver: Option<&str>,
66 schema: &str,
67 out: &str,
68) -> Result<String, CliError> {
69 let dialect = dialect.to_lowercase();
70 let driver = driver.map(str::to_lowercase);
71 let schema = toml_string(schema);
72 let out = toml_string(out);
73
74 match dialect.as_str() {
76 "sqlite" => {
77 if let Some(ref d) = driver
78 && d != "rusqlite"
79 {
80 return Err(CliError::Other(format!(
81 "Invalid driver for sqlite: {d}. Supported: rusqlite"
82 )));
83 }
84 Ok(format!(
85 r#"#:schema {SCHEMA_URL}
86
87# Drizzle Configuration (drizzle-rs)
88#
89# This file is parsed by `drizzle-cli` and should stay aligned with its config schema:
90# - dialect: sqlite | turso | postgresql | mysql
91# - drivers: Rust drivers only (optional)
92
93dialect = "sqlite"
94# driver = "rusqlite"
95schema = {schema}
96out = {out}
97# breakpoints = true
98
99[dbCredentials]
100url = "./dev.db"
101"#
102 ))
103 }
104 "turso" => {
105 if let Some(ref d) = driver
106 && d != "libsql"
107 && d != "turso"
108 {
109 return Err(CliError::Other(format!(
110 "Invalid driver for turso: {d}. Supported: libsql, turso"
111 )));
112 }
113 Ok(format!(
114 r#"#:schema {SCHEMA_URL}
115
116# Drizzle Configuration (drizzle-rs)
117
118dialect = "turso"
119# driver = "libsql" # local libsql (embedded)
120# driver = "turso" # remote Turso
121schema = {schema}
122out = {out}
123# breakpoints = true
124
125[dbCredentials]
126url = "libsql://your-db.turso.io"
127authToken = "your-auth-token"
128"#
129 ))
130 }
131 "postgresql" | "postgres" => {
132 if let Some(ref d) = driver
133 && d != "postgres-sync"
134 && d != "tokio-postgres"
135 {
136 return Err(CliError::Other(format!(
137 "Invalid driver for postgresql: {d}. Supported: postgres-sync, tokio-postgres"
138 )));
139 }
140 Ok(format!(
141 r#"#:schema {SCHEMA_URL}
142
143# Drizzle Configuration (drizzle-rs)
144
145dialect = "postgresql"
146# driver = "postgres-sync"
147# driver = "tokio-postgres"
148schema = {schema}
149out = {out}
150# breakpoints = true
151
152[dbCredentials]
153url = "postgres://user:password@localhost:5432/mydb"
154
155# Or use individual connection fields:
156# [dbCredentials]
157# host = "localhost"
158# port = 5432
159# user = "postgres"
160# password = "password"
161# database = "mydb"
162# ssl = true
163"#
164 ))
165 }
166 "mysql" => {
167 if let Some(ref driver) = driver
168 && driver != "mysql-sync"
169 && driver != "mysql-async"
170 {
171 return Err(CliError::Other(format!(
172 "Invalid driver for mysql: {driver}. Supported: mysql-sync, mysql-async"
173 )));
174 }
175 let driver_config = driver.as_deref().map_or_else(
176 || "# driver = \"mysql-sync\"\n# driver = \"mysql-async\"".to_string(),
177 |driver| format!("driver = \"{driver}\""),
178 );
179 Ok(format!(
180 r#"#:schema {SCHEMA_URL}
181
182# Drizzle Configuration (drizzle-rs)
183
184dialect = "mysql"
185{driver_config}
186schema = {schema}
187out = {out}
188# breakpoints = true
189
190[dbCredentials]
191url = "mysql://user:password@localhost:3306/mydb"
192
193# Or use individual connection fields:
194# [dbCredentials]
195# host = "localhost"
196# port = 3306
197# user = "root"
198# password = "password"
199# database = "mydb"
200# ssl = "required"
201"#
202 ))
203 }
204 _ => Err(CliError::Other(format!(
205 "Unknown dialect: {dialect}. Supported: sqlite, turso, postgresql, mysql"
206 ))),
207 }
208}
209
210fn toml_string(value: &str) -> String {
212 toml::Value::String(value.to_string()).to_string()
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 #[test]
220 fn mysql_init_template_uses_mysql_port_and_drivers() {
221 let config = config_contents("mysql", Some("mysql-sync"), DEFAULT_SCHEMA, DEFAULT_OUT)
222 .expect("mysql config");
223 assert!(config.contains("dialect = \"mysql\""), "{config}");
224 assert!(config.contains("mysql://user:password@localhost:3306/mydb"));
225 assert!(config.contains("driver = \"mysql-sync\""), "{config}");
226 assert!(!config.contains("ssl = \"preferred\""), "{config}");
227
228 let async_config =
229 config_contents("mysql", Some("mysql-async"), DEFAULT_SCHEMA, DEFAULT_OUT)
230 .expect("async mysql config");
231 assert!(
232 async_config.contains("driver = \"mysql-async\""),
233 "{async_config}"
234 );
235 assert!(
236 config_contents("mysql", Some("postgres-sync"), DEFAULT_SCHEMA, DEFAULT_OUT).is_err()
237 );
238 }
239
240 #[test]
241 fn template_paths_are_quoted_and_parse_back() {
242 for dialect in ["sqlite", "turso", "postgresql", "mysql"] {
243 let text = config_contents(dialect, None, "src/db/schema.rs", "C:\\app\\drizzle")
244 .expect("config");
245 let value: toml::Table = toml::from_str(&text).expect("valid TOML");
246 assert_eq!(value["schema"].as_str(), Some("src/db/schema.rs"));
247 assert_eq!(value["out"].as_str(), Some("C:\\app\\drizzle"));
248 }
249 }
250}