Skip to main content

rustbasic_core/cli/
mod.rs

1use std::env;
2use dotenvy::dotenv;
3use colored::*;
4use std::future::Future;
5use std::pin::Pin;
6
7pub mod scaffolding;
8pub mod database;
9pub mod monitoring;
10pub mod builder;
11pub mod utils;
12pub mod auth;
13
14pub type AsyncHook = Box<dyn Fn() -> Pin<Box<dyn Future<Output = ()>>>>;
15
16pub async fn run_cli<F, G>(migrate_fn: F, seed_fn: G) 
17where 
18    F: Fn(String) -> Pin<Box<dyn Future<Output = Result<(), String>>>>,
19    G: Fn() -> Pin<Box<dyn Future<Output = ()>>>
20{
21    let args: Vec<String> = env::args().collect();
22
23    if args.len() < 2 {
24        print_help();
25        return;
26    }
27
28    let command = &args[1];
29
30    // .env hanya diwajibkan untuk perintah selain 'new'
31    if command != "new" {
32        let _ = dotenv(); // Coba muat .env jika ada
33    }
34
35    match command.as_str() {
36         "serve" => {
37            println!("\n   {} {}", "šŸš€".bold(), "Menjalankan server RustBasic dengan Auto-Reload...".magenta().bold());
38            let status = std::process::Command::new("cargo")
39                .args(["watch", "-c", "-q", "--no-ignore", "-i", "target", "-w", "src", "-w", ".env", "-w", "src/resources", "-x", "run"])
40                .status()
41                .expect("āŒ Gagal menjalankan cargo watch. Pastikan cargo-watch sudah terinstall: cargo install cargo-watch");
42            
43            if !status.success() {
44                std::process::exit(status.code().unwrap_or(1));
45            }
46        },
47        "make:model" => {
48            if args.len() < 3 {
49                println!("{}", "āŒ Error: Nama model tidak ditentukan.".red().bold());
50                return;
51            }
52            let model_name = &args[2];
53            let with_migration = args.contains(&"-m".to_string());
54            
55            scaffolding::make_model(model_name);
56            if with_migration {
57                scaffolding::make_rust_migration(model_name);
58            }
59        }
60        "make:migration" => {
61            if args.len() < 3 {
62                println!("{}", "āŒ Error: Nama migration tidak ditentukan.".red().bold());
63                return;
64            }
65            scaffolding::make_rust_migration(&args[2]);
66        }
67        "make:controller" => {
68            if args.len() < 3 {
69                println!("{}", "āŒ Error: Nama controller tidak ditentukan.".red().bold());
70                return;
71            }
72            scaffolding::make_controller(&args[2]);
73        }
74        "make:middleware" => {
75            if args.len() < 3 {
76                println!("{}", "āŒ Error: Nama middleware tidak ditentukan.".red().bold());
77                return;
78            }
79            scaffolding::make_middleware(&args[2]);
80        }
81        "migrate" | "migrate:refresh" | "migrate:back" | "migrate:rollback" => {
82            if command == "migrate:refresh" {
83                println!("{}", "šŸ”„ Menyegarkan database (Refresh Migration)...".yellow());
84            } else if command == "migrate:back" || command == "migrate:rollback" {
85                println!("{}", "āŖ Membatalkan migrasi terakhir (Rollback 1 step)...".yellow());
86            }
87            
88            if let Err(e) = migrate_fn(command.clone()).await {
89                eprintln!("\n{} {}", "āŒ Error:".red().bold(), "Gagal menjalankan operasi database.".bold());
90                eprintln!("{} {}", "šŸ“ Detail:".yellow(), e);
91                eprintln!("\nšŸ’” {}", "Tips:".cyan().bold());
92                eprintln!("   Jika muncul error 'Migration file ... is missing', itu berarti database mencatat");
93                eprintln!("   migrasi yang sudah dijalankan, tapi file migrasinya sudah dihapus atau diubah.");
94                eprintln!("\nšŸ› ļø  {}", "Solusi:".cyan().bold());
95                eprintln!("   Hapus file database: 'rm database/rustbasic.sqlite' lalu jalankan migrasi lagi.");
96                std::process::exit(1);
97            }
98        }
99        "route:list" => {
100            monitoring::list_routes();
101        }
102        "build" => {
103            builder::build_project();
104        }
105        "cache:clear" => {
106            database::clear_cache().await;
107        }
108        "check:update" => {
109            monitoring::check_updates();
110        }
111        "check:security" => {
112            monitoring::check_security();
113        }
114        "key:generate" => {
115            database::generate_app_key();
116        }
117        "make:auth" | "auth" => {
118            if args.len() >= 3 && args[2] == "back" {
119                auth::remove_auth().await;
120            } else {
121                auth::make_auth().await;
122            }
123        }
124        "db:seed" => {
125            seed_fn().await;
126        }
127        "make:seeder" => {
128            if args.len() < 3 {
129                println!("{}", "āŒ Error: Nama seeder tidak ditentukan.".red().bold());
130                return;
131            }
132            scaffolding::make_seeder(&args[2]);
133        }
134        "new" => {
135            if args.len() < 3 {
136                println!("{}", "āŒ Error: Nama project tidak ditentukan.".red().bold());
137                println!("Contoh: rustbasic new myapp");
138                return;
139            }
140            let project_name = &args[2];
141
142            // Cek apakah folder sudah ada
143            if std::path::Path::new(project_name).exists() {
144                println!("{} '{}' {}", "āŒ Error: Folder".red().bold(), project_name.yellow(), "sudah ada! Silakan gunakan nama lain.".red().bold());
145                return;
146            }
147
148            println!("\n✨ {} {}", "Membuat project baru:".bold(), project_name.cyan().bold());
149            
150            let status = std::process::Command::new("git")
151                .args(["clone", "https://github.com/herisvan321/rustbasic", project_name])
152                .status();
153
154            match status {
155                Ok(s) if s.success() => {
156                    // Hapus folder .git agar menjadi project baru
157                    let _ = std::process::Command::new("rm")
158                        .args(["-rf", &format!("{}/.git", project_name)])
159                        .status();
160                    
161                    println!("\nāœ… {} {}", "Project berhasil dibuat!".green().bold(), "Silakan masuk ke folder:".dimmed());
162                    println!("   cd {}", project_name.cyan());
163                    println!("   rustbasic serve\n");
164                }
165                _ => {
166                    println!("{}", "āŒ Gagal mengkloning starter template. Pastikan Anda memiliki koneksi internet dan git terinstall.".red());
167                }
168            }
169        }
170        "auth:back" => {
171            auth::remove_auth().await;
172        }
173       
174        _ => {
175            println!("{} {}", "āŒ Error: Perintah tidak dikenal:".red().bold(), command.yellow());
176            print_help();
177        }
178    }
179}
180
181fn print_help() {
182    println!("\n{}", "šŸ› ļø  RustBasic CLI".magenta().bold());
183    println!("{}", "=================".magenta());
184    println!("{}", "Penggunaan:".bold());
185    println!("  {} {} <Nama>         {}", "rustbasic".blue(), "new".green(), "Membuat project RustBasic baru dari template".dimmed());
186    println!("  {} {} <Nama> [-m]   {}", "rustbasic".blue(), "make:model".green(), "Membuat model Sea-ORM (dan migration Rust)".dimmed());
187    println!("  {} {} <Nama>    {}", "rustbasic".blue(), "make:migration".green(), "Membuat file migration Rust".dimmed());
188    println!("  {} {} <Nama>  {}", "rustbasic".blue(), "make:controller".green(), "Membuat controller Axum".dimmed());
189    println!("  {} {} <Nama>  {}", "rustbasic".blue(), "make:middleware".green(), "Membuat middleware Axum".dimmed());
190    println!("  {} {}                  {}", "rustbasic".blue(), "migrate".green(), "Menjalankan migrasi database (Sea-ORM)".dimmed());
191    println!("  {} {}          {}", "rustbasic".blue(), "migrate:refresh".green(), "Rollback semua dan jalankan kembali migrasi".dimmed());
192    println!("  {} {}             {}", "rustbasic".blue(), "migrate:back".green(), "Membatalkan migrasi terakhir (Rollback)".dimmed());
193    println!("  {} {}               {}", "rustbasic".blue(), "route:list".green(), "Menampilkan daftar route".dimmed());
194    println!("  {} {}                    {}", "rustbasic".blue(), "build".green(), "Membangun project dengan pilihan".dimmed());
195    println!("  {} {}             {}", "rustbasic".blue(), "check:update".green(), "Cek versi terbaru paket (dependencies)".dimmed());
196    println!("  {} {}           {}", "rustbasic".blue(), "check:security".green(), "Audit keamanan aplikasi".dimmed());
197    println!("  {} {}               {}", "rustbasic".blue(), "cache:clear".green(), "Membersihkan logs dan database sessions".dimmed());
198    println!("  {} {}             {}", "rustbasic".blue(), "key:generate".green(), "Membuat APP_KEY baru di file .env".dimmed());
199    println!("  {} {}                   {}", "rustbasic".blue(), "make:auth".green(), "Scaffold autentikasi (Login/Register)".dimmed());
200    println!("  {} {}                   {}", "rustbasic".blue(), "auth:back".red(), "Menghapus semua scaffolding autentikasi".dimmed());
201    println!("  {} {}                  {}", "rustbasic".blue(), "db:seed".green(), "Menjalankan seeder database".dimmed());
202    println!("  {} {} <Nama>    {}", "rustbasic".blue(), "make:seeder".green(), "Membuat file seeder baru".dimmed());
203    println!("  {} {}                    {}", "rustbasic".blue(), "serve".green(), "Menjalankan server dengan Auto-Reload".dimmed());
204    println!("  {}                       {}", "cargo serve".blue(), "(Shortcut) Lebih cepat untuk menjalankan server".dimmed());
205
206    println!();
207}