use std::{fs, path::Path};
pub fn generate(name: &str, root: &Path) -> anyhow::Result<()> {
for dir in &["src", "database/migrations"] {
fs::create_dir_all(root.join(dir))?;
}
let files: Vec<(&str, String)> = vec![
("Cargo.toml", cargo_toml(name)),
("src/main.rs", main_rs(name)),
(".env.example", ENV_EXAMPLE.into()),
(".gitignore", GITIGNORE.into()),
("database/migrations/.gitkeep", String::new()),
];
for (rel, content) in &files {
fs::write(root.join(rel), content)?;
println!(" create {rel}");
}
Ok(())
}
fn cargo_toml(name: &str) -> String {
format!(
r#"[package]
name = "{name}"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "{name}"
path = "src/main.rs"
[dependencies]
axum = "0.8"
tokio = {{ version = "1", features = ["full"] }}
serde = {{ version = "1", features = ["derive"] }}
serde_json = "1"
sqlx = {{ version = "0.8", default-features = false, features = [
"macros", "runtime-tokio-native-tls", "postgres",
] }}
dotenvy = "0.15"
"#
)
}
fn main_rs(name: &str) -> String {
format!(
r#"use axum::{{routing::get, Router}};
#[tokio::main]
async fn main() {{
dotenvy::dotenv().ok();
let addr = std::env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0:3000".into());
let app = Router::new().route("/", get(|| async {{ "{name} is running" }}));
let listener = tokio::net::TcpListener::bind(&addr).await.expect("bind");
println!("{name} listening on {{addr}}");
axum::serve(listener, app).await.expect("server");
}}
"#
)
}
const ENV_EXAMPLE: &str = r#"LISTEN_ADDR=0.0.0.0:3000
DATABASE_URL=postgres://postgres:postgres@localhost/app
"#;
const GITIGNORE: &str = "/target\n.env\n";