rok-cli 0.3.2

Developer CLI for rok-based Axum applications
//! `rok make:scaffold` — common feature template registry.

use anyhow::Result;
use std::collections::HashMap;

pub mod activity_log;
pub mod admin;
pub mod api_versioned;
pub mod auth;
pub mod billing;
pub mod comments;
pub mod crud;
pub mod crud_api;
pub mod export;
pub mod flags;
pub mod import;
pub mod multi_tenant;
pub mod newsletter;
pub mod notification;
pub mod ratings;
pub mod search;
pub mod social_auth;
pub mod upload;
pub mod webhook;
pub mod websocket;

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct FieldSpec {
    pub name: String,
    pub r#type: String,
    pub validations: Vec<String>,
}

#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ScaffoldArgs {
    pub name: Option<String>,
    pub model: Option<String>,
    pub fields: Vec<FieldSpec>,
    pub flags: HashMap<String, bool>,
    pub dry_run: bool,
    pub json: bool,
}

#[allow(dead_code)]
impl ScaffoldArgs {
    pub fn name_or(&self, fallback: &str) -> String {
        self.name.clone().unwrap_or_else(|| fallback.to_string())
    }

    pub fn model_or(&self, fallback: &str) -> String {
        self.model.clone().unwrap_or_else(|| fallback.to_string())
    }
}

#[derive(Debug, Default)]
pub struct ScaffoldResult {
    pub files_created: Vec<String>,
    pub dirs_created: Vec<String>,
    pub warnings: Vec<String>,
}

impl ScaffoldResult {
    pub fn print_human(&self) {
        for d in &self.dirs_created {
            eprintln!("mkdir  {d}");
        }
        for f in &self.files_created {
            println!("created {f}");
        }
        for w in &self.warnings {
            eprintln!("warning: {w}");
        }
    }

    pub fn print_json(&self) {
        let obj = serde_json::json!({
            "status": "ok",
            "files_created": self.files_created,
            "dirs_created": self.dirs_created,
            "warnings": self.warnings,
        });
        println!("{}", serde_json::to_string_pretty(&obj).unwrap());
    }
}

pub trait Scaffold: Send + Sync {
    fn name(&self) -> &'static str;
    fn description(&self) -> &'static str;
    fn generate(&self, args: &ScaffoldArgs) -> Result<ScaffoldResult>;
}

pub struct ScaffoldRegistry {
    entries: HashMap<&'static str, Box<dyn Scaffold>>,
}

impl ScaffoldRegistry {
    pub fn new() -> Self {
        let mut r = Self {
            entries: HashMap::new(),
        };
        r.register(Box::new(auth::AuthScaffold));
        r.register(Box::new(crud::CrudScaffold));
        r.register(Box::new(crud_api::CrudApiScaffold));
        r.register(Box::new(upload::UploadScaffold));
        r.register(Box::new(webhook::WebhookScaffold));
        r.register(Box::new(search::SearchScaffold));
        r.register(Box::new(notification::NotificationScaffold));
        r.register(Box::new(billing::BillingScaffold));
        r.register(Box::new(social_auth::SocialAuthScaffold));
        r.register(Box::new(admin::AdminScaffold));
        r.register(Box::new(websocket::WebsocketScaffold));
        r.register(Box::new(export::ExportScaffold));
        r.register(Box::new(import::ImportScaffold));
        r.register(Box::new(multi_tenant::MultiTenantScaffold));
        r.register(Box::new(api_versioned::ApiVersionedScaffold));
        r.register(Box::new(comments::CommentsScaffold));
        r.register(Box::new(ratings::RatingsScaffold));
        r.register(Box::new(activity_log::ActivityLogScaffold));
        r.register(Box::new(newsletter::NewsletterScaffold));
        r.register(Box::new(flags::FlagsScaffold));
        r
    }

    fn register(&mut self, s: Box<dyn Scaffold>) {
        self.entries.insert(s.name(), s);
    }

    pub fn get(&self, name: &str) -> Option<&dyn Scaffold> {
        self.entries.get(name).map(|b| b.as_ref())
    }

    pub fn list(&self) -> Vec<(&'static str, &'static str)> {
        let mut list: Vec<_> = self
            .entries
            .values()
            .map(|s| (s.name(), s.description()))
            .collect();
        list.sort_by_key(|(n, _)| *n);
        list
    }
}

// ── Shared file writing helper ────────────────────────────────────────────────

pub fn write_file(
    result: &mut ScaffoldResult,
    path: &str,
    content: &str,
    dry_run: bool,
) -> Result<()> {
    if !dry_run {
        if let Some(parent) = std::path::Path::new(path).parent() {
            if !parent.as_os_str().is_empty() {
                std::fs::create_dir_all(parent)?;
                let dir = parent.to_string_lossy().to_string();
                if !result.dirs_created.contains(&dir) {
                    result.dirs_created.push(dir);
                }
            }
        }
        std::fs::write(path, content)?;
    }
    result.files_created.push(path.to_string());
    Ok(())
}