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 if command != "new" {
32 let _ = dotenv(); }
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 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 let _ = std::process::Command::new("rm")
158 .args(["-rf", &format!("{}/.git", project_name)])
159 .status();
160
161 let env_example = format!("{}/.env.example", project_name);
163 let env_file = format!("{}/.env", project_name);
164 if std::path::Path::new(&env_example).exists() {
165 match std::fs::copy(&env_example, &env_file) {
166 Ok(_) => println!(" {} .env.example ā .env", "š".blue()),
167 Err(e) => println!(" {} Gagal menyalin .env: {}", "ā ļø".yellow(), e),
168 }
169 }
170
171 if std::path::Path::new(&env_file).exists() {
173 let current_dir = std::env::current_dir().ok();
174 if std::env::set_current_dir(project_name).is_ok() {
175 database::generate_app_key();
176 if let Some(prev_dir) = current_dir {
177 let _ = std::env::set_current_dir(prev_dir);
178 }
179 }
180 }
181
182 println!("\nā
{} {}", "Project berhasil dibuat!".green().bold(), "Silakan masuk ke folder:".dimmed());
183 println!(" cd {}", project_name.cyan());
184 println!(" rustbasic serve\n");
185 }
186 _ => {
187 println!("{}", "ā Gagal mengkloning starter template. Pastikan Anda memiliki koneksi internet dan git terinstall.".red());
188 }
189 }
190 }
191 "auth:back" => {
192 auth::remove_auth().await;
193 }
194
195 _ => {
196 println!("{} {}", "ā Error: Perintah tidak dikenal:".red().bold(), command.yellow());
197 print_help();
198 }
199 }
200}
201
202fn print_help() {
203 println!("\n{}", "š ļø RustBasic CLI".magenta().bold());
204 println!("{}", "=================".magenta());
205 println!("{}", "Penggunaan:".bold());
206 println!(" {} {} <Nama> {}", "rustbasic".blue(), "new".green(), "Membuat project RustBasic baru dari template".dimmed());
207 println!(" {} {} <Nama> [-m] {}", "rustbasic".blue(), "make:model".green(), "Membuat model Sea-ORM (dan migration Rust)".dimmed());
208 println!(" {} {} <Nama> {}", "rustbasic".blue(), "make:migration".green(), "Membuat file migration Rust".dimmed());
209 println!(" {} {} <Nama> {}", "rustbasic".blue(), "make:controller".green(), "Membuat controller Axum".dimmed());
210 println!(" {} {} <Nama> {}", "rustbasic".blue(), "make:middleware".green(), "Membuat middleware Axum".dimmed());
211 println!(" {} {} {}", "rustbasic".blue(), "migrate".green(), "Menjalankan migrasi database (Sea-ORM)".dimmed());
212 println!(" {} {} {}", "rustbasic".blue(), "migrate:refresh".green(), "Rollback semua dan jalankan kembali migrasi".dimmed());
213 println!(" {} {} {}", "rustbasic".blue(), "migrate:back".green(), "Membatalkan migrasi terakhir (Rollback)".dimmed());
214 println!(" {} {} {}", "rustbasic".blue(), "route:list".green(), "Menampilkan daftar route".dimmed());
215 println!(" {} {} {}", "rustbasic".blue(), "build".green(), "Membangun project dengan pilihan".dimmed());
216 println!(" {} {} {}", "rustbasic".blue(), "check:update".green(), "Cek versi terbaru paket (dependencies)".dimmed());
217 println!(" {} {} {}", "rustbasic".blue(), "check:security".green(), "Audit keamanan aplikasi".dimmed());
218 println!(" {} {} {}", "rustbasic".blue(), "cache:clear".green(), "Membersihkan logs dan database sessions".dimmed());
219 println!(" {} {} {}", "rustbasic".blue(), "key:generate".green(), "Membuat APP_KEY baru di file .env".dimmed());
220 println!(" {} {} {}", "rustbasic".blue(), "make:auth".green(), "Scaffold autentikasi (Login/Register)".dimmed());
221 println!(" {} {} {}", "rustbasic".blue(), "auth:back".red(), "Menghapus semua scaffolding autentikasi".dimmed());
222 println!(" {} {} {}", "rustbasic".blue(), "db:seed".green(), "Menjalankan seeder database".dimmed());
223 println!(" {} {} <Nama> {}", "rustbasic".blue(), "make:seeder".green(), "Membuat file seeder baru".dimmed());
224 println!(" {} {} {}", "rustbasic".blue(), "serve".green(), "Menjalankan server dengan Auto-Reload".dimmed());
225 println!(" {} {}", "cargo serve".blue(), "(Shortcut) Lebih cepat untuk menjalankan server".dimmed());
226
227 println!();
228}