1use crate::errors::{RealmError, Result};
2use clap::{Parser, Subcommand};
3use std::path::PathBuf;
4
5use crate::activation::RealmEnvironment;
6use crate::bundle::Bundler;
7use crate::config::RealmConfig;
8use crate::env::EnvManager;
9use crate::process::ProcessManager;
10use crate::proxy::ProxyServer;
11use crate::runtime::{Runtime, RuntimeManager};
12use crate::templates::TemplateManager;
13
14#[derive(Parser)]
15#[command(name = "realm")]
16#[command(about = "Full-stack development environment CLI with built-in proxy")]
17#[command(version = env!("REALM_VERSION"))]
18pub struct Cli {
19 #[command(subcommand)]
20 pub command: Commands,
21}
22
23#[derive(Subcommand)]
24pub enum Commands {
25 Init {
27 #[arg(default_value = ".venv")]
29 path: PathBuf,
30
31 #[arg(long, default_value = "bun")]
33 runtime: String,
34
35 #[arg(long)]
37 template: Option<String>,
38 },
39
40 Start,
42
43 Stop,
45
46 Proxy,
48
49 Bundle,
51
52 Create {
54 #[arg(long)]
56 template: String,
57 },
58
59 Templates {
61 #[command(subcommand)]
62 command: TemplateCommands,
63 },
64
65 List {
67 #[arg(long)]
69 runtime: String,
70 },
71}
72
73#[derive(Subcommand)]
74pub enum TemplateCommands {
75 List,
77}
78
79pub struct CliHandler {
80 template_manager: TemplateManager,
81 runtime_manager: RuntimeManager,
82}
83
84impl CliHandler {
85 pub fn new() -> Result<Self> {
86 Ok(Self {
87 template_manager: TemplateManager::new()?,
88 runtime_manager: RuntimeManager::new()?,
89 })
90 }
91
92 pub async fn handle_command(&self, command: Commands) -> Result<()> {
93 match command {
94 Commands::Init {
95 path,
96 runtime,
97 template,
98 } => self.handle_init(path, runtime, template).await,
99 Commands::Start => self.handle_start().await,
100 Commands::Stop => self.handle_stop().await,
101 Commands::Proxy => self.handle_proxy().await,
102 Commands::Bundle => self.handle_bundle().await,
103 Commands::Create { template } => self.handle_create_template(template).await,
104 Commands::Templates { command } => self.handle_templates(command).await,
105 Commands::List { runtime } => self.handle_list(runtime).await,
106 }
107 }
108
109 async fn handle_init(
110 &self,
111 path: PathBuf,
112 runtime_spec: String,
113 template: Option<String>,
114 ) -> Result<()> {
115 println!("🏗️ Initializing realm environment...");
116
117 let runtime = Runtime::parse(&runtime_spec)?;
119
120 if !self.runtime_manager.is_version_installed(&runtime) {
122 println!("📦 Getting {} {}...", runtime.name(), runtime.version());
123 self.runtime_manager.install_version(&runtime).await?;
124 }
125
126 if let Some(template_name) = &template {
128 let project_dir = std::env::current_dir()?.join("project");
129 println!("🎯 Creating project from template '{template_name}'...");
130 self
131 .template_manager
132 .init_from_template(template_name, &project_dir)?;
133 std::env::set_current_dir(&project_dir)?;
134 }
135
136 let realm_env = RealmEnvironment::init(&path)?;
138
139 realm_env.setup_python_isolation(&runtime, &self.runtime_manager)?;
141
142 println!("✅ Realm environment initialized!");
143 println!("🎯 Runtime: {} {}", runtime.name(), runtime.version());
144 if let Some(template_name) = template {
145 println!("📄 Template: {template_name}");
146 }
147 println!();
148 println!("Next steps:");
149 println!(" source {}/bin/activate", path.display());
150 println!(" realm start");
151
152 Ok(())
153 }
154
155 async fn handle_start(&self) -> Result<()> {
156 if std::env::var("REALM_ENV").is_err() {
158 return Err(RealmError::ValidationError(
159 "Not in an activated realm environment. Run 'source .venv/bin/activate' first.".to_string()
160 ));
161 }
162
163 println!("🚀 Starting realm environment...");
164
165 let config = RealmConfig::load("realm.yml")?;
167
168 let mut env_manager = EnvManager::new();
170 env_manager.load_from_map(&config.env);
171 if let Some(env_file) = &config.env_file {
172 env_manager.load_from_file(env_file)?;
173 }
174 env_manager.apply();
175
176 let process_manager = ProcessManager::new();
178 process_manager.load_processes(&config)?;
179
180 println!("🔧 Starting processes...");
182 process_manager.start_all()?;
183
184 println!("🌐 Starting proxy server...");
186 let proxy_server = ProxyServer::new(config, process_manager);
187
188 proxy_server.start().await?;
190
191 Ok(())
192 }
193
194 async fn handle_stop(&self) -> Result<()> {
195 println!("🛑 Stopping realm environment...");
196
197 let config = RealmConfig::load("realm.yml")?;
199
200 let process_manager = ProcessManager::new();
202 process_manager.load_processes(&config)?;
203 process_manager.stop_all()?;
204
205 println!("✅ All processes stopped");
206 Ok(())
207 }
208
209 async fn handle_proxy(&self) -> Result<()> {
210 println!("🌐 Starting proxy server...");
211
212 let config = RealmConfig::load("realm.yml")?;
214
215 let process_manager = ProcessManager::new();
217 process_manager.load_processes(&config)?;
218
219 let proxy_server = ProxyServer::new(config, process_manager);
221 proxy_server.start().await?;
222
223 Ok(())
224 }
225
226 async fn handle_bundle(&self) -> Result<()> {
227 println!("📦 Creating deployment bundle...");
228
229 let config = RealmConfig::load("realm.yml")?;
231
232 let bundler = Bundler::new(config)?;
234 bundler.bundle()?;
235
236 Ok(())
237 }
238
239 async fn handle_create_template(&self, template_name: String) -> Result<()> {
240 println!("🎨 Creating template '{template_name}'...");
241
242 self
243 .template_manager
244 .create_template_from_current_dir(&template_name)?;
245
246 Ok(())
247 }
248
249 async fn handle_templates(&self, command: TemplateCommands) -> Result<()> {
250 match command {
251 TemplateCommands::List => {
252 println!("📄 Available templates:");
253
254 let _ = self.template_manager.create_builtin_templates();
256
257 let templates = self.template_manager.list_templates()?;
258 if templates.is_empty() {
259 println!(" No templates found");
260 } else {
261 for template in templates {
262 println!(" • {template}");
263 }
264 }
265
266 Ok(())
267 }
268 }
269 }
270
271 async fn handle_list(&self, runtime_spec: String) -> Result<()> {
272 let runtime = Runtime::parse(&runtime_spec)?;
273
274 println!("📦 Fetching available {} versions...", runtime.name());
275
276 let versions = self.runtime_manager.list_available_versions(&runtime).await?;
277
278 if versions.is_empty() {
279 println!(" No versions found");
280 } else {
281 println!("\n Available versions:");
282 for version in versions {
283 let installed_marker = if self.runtime_manager.is_version_installed(&Runtime::from_name_version(runtime.name(), &version)) {
284 " (installed)"
285 } else {
286 ""
287 };
288 println!(" • {}{}", version, installed_marker);
289 }
290 }
291
292 Ok(())
293 }
294}
295
296impl Default for CliHandler {
297 fn default() -> Self {
298 Self::new().expect("Failed to create CliHandler")
299 }
300}