rok-cli 0.3.2

Developer CLI for rok-based Axum applications
//! `--template minimal` — Bare axum + sqlx skeleton.

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";