Skip to main content

realm/cli/
mod.rs

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  /// Initialize a new realm environment
26  Init {
27    /// Path for the realm environment (default: .venv)
28    #[arg(default_value = ".venv")]
29    path: PathBuf,
30
31    /// Runtime to use (bun, node, bun@1.0.0, node@18)
32    #[arg(long, default_value = "bun")]
33    runtime: String,
34
35    /// Template to use for project scaffolding
36    #[arg(long)]
37    template: Option<String>,
38  },
39
40  /// Start all processes and proxy server
41  Start,
42
43  /// Stop all processes and proxy server
44  Stop,
45
46  /// Start proxy server only
47  Proxy,
48
49  /// Create deployment bundle
50  Bundle,
51
52  /// Create a new template from current project
53  Create {
54    /// Name of the template to create
55    #[arg(long)]
56    template: String,
57  },
58
59  /// Template management commands
60  Templates {
61    #[command(subcommand)]
62    command: TemplateCommands,
63  },
64
65  /// List available runtime versions
66  List {
67    /// Runtime to list versions for (bun, node, python)
68    #[arg(long)]
69    runtime: String,
70  },
71}
72
73#[derive(Subcommand)]
74pub enum TemplateCommands {
75  /// List available templates
76  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    // Parse runtime specification
118    let runtime = Runtime::parse(&runtime_spec)?;
119
120    // Install runtime if needed
121    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    // Create project from template if specified
127    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    // Initialize realm environment
137    let realm_env = RealmEnvironment::init(&path)?;
138
139    // Set up Python-specific isolation if using Python runtime
140    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    // Check if we're in an activated realm environment
157    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    // Load configuration
166    let config = RealmConfig::load("realm.yml")?;
167
168    // Set up environment variables
169    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    // Create process manager
177    let process_manager = ProcessManager::new();
178    process_manager.load_processes(&config)?;
179
180    // Start all processes
181    println!("🔧 Starting processes...");
182    process_manager.start_all()?;
183
184    // Start proxy server
185    println!("🌐 Starting proxy server...");
186    let proxy_server = ProxyServer::new(config, process_manager);
187
188    // This will run indefinitely
189    proxy_server.start().await?;
190
191    Ok(())
192  }
193
194  async fn handle_stop(&self) -> Result<()> {
195    println!("🛑 Stopping realm environment...");
196
197    // Load configuration
198    let config = RealmConfig::load("realm.yml")?;
199
200    // Create process manager and stop all processes
201    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    // Load configuration
213    let config = RealmConfig::load("realm.yml")?;
214
215    // Create process manager (for route mapping)
216    let process_manager = ProcessManager::new();
217    process_manager.load_processes(&config)?;
218
219    // Start proxy server
220    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    // Load configuration
230    let config = RealmConfig::load("realm.yml")?;
231
232    // Create bundler and generate deployment artifacts
233    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        // Create built-in templates if they don't exist
255        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}