1use std::path::PathBuf;
7
8use anyhow::{Context, Result};
9
10use crate::{
11 Binary::Main::CliArgs,
12 Host::{ExtensionHost::ExtensionHostImpl, HostConfig},
13 Transport::Strategy::Transport,
14 dev_log,
15};
16
17pub struct Entry;
19
20impl Entry {
21 pub async fn run(args:CliArgs) -> Result<()> {
23 dev_log!("lifecycle", "Starting Grove v{}", env!("CARGO_PKG_VERSION"));
24
25 dev_log!("lifecycle", "Mode: {}", args.mode);
26
27 match args.mode.as_str() {
28 "standalone" => Self::run_standalone(args).await,
29
30 "service" => Self::run_service(args).await,
31
32 "validate" => Self::run_validation(args).await,
33
34 _ => Err(anyhow::anyhow!("Unknown mode: {}", args.mode)),
35 }
36 }
37
38 async fn run_standalone(args:CliArgs) -> Result<()> {
40 dev_log!("grove", "Starting Grove in standalone mode");
41
42 let transport = Self::create_transport(&args)?;
44
45 let host_config = HostConfig::default().with_activation_timeout(args.max_execution_time_ms);
47
48 let host = ExtensionHostImpl::with_config(transport, host_config)
50 .await
51 .context("Failed to create extension host")?;
52
53 if let Some(extension_path) = args.extension {
55 let path = PathBuf::from(extension_path);
56
57 host.load_extension(&path).await?;
58
59 host.activate_all().await?;
60 } else {
61 dev_log!("grove", "No extension specified, running in daemon mode");
62 }
63
64 Self::wait_for_shutdown().await;
66
67 host.shutdown().await?;
69
70 Ok(())
71 }
72
73 async fn run_service(_args:CliArgs) -> Result<()> {
75 dev_log!("grove", "Starting Grove as service");
76
77 let _transport = Transport::default();
79
80 #[cfg(feature = "gRPC")]
82 {
83 match crate::Binary::Build::ServiceRegister::register_with_mountain(
84 "grove-host",
85 &args.mountain_address,
86 true, )
88 .await
89 {
90 Ok(_) => dev_log!("grove", "Registered with Mountain"),
91
92 Err(e) => dev_log!("grove", "warn: failed to register with Mountain: {}", e),
93 }
94 }
95
96 #[cfg(not(feature = "gRPC"))]
97 {
98 dev_log!("grpc", "gRPC feature not enabled, skipping Mountain registration");
99 }
100
101 Self::wait_for_shutdown().await;
103
104 Ok(())
105 }
106
107 async fn run_validation(args:CliArgs) -> Result<()> {
109 dev_log!("extensions", "Validating extension");
110
111 let extension_path = args
112 .extension
113 .ok_or_else(|| anyhow::anyhow!("Extension path required for validation"))?;
114
115 let path = PathBuf::from(extension_path);
116
117 let result = Self::validate_extension(&path, false).await?;
118
119 if result.is_valid {
120 dev_log!("extensions", "Extension validation passed");
121
122 Ok(())
123 } else {
124 dev_log!("extensions", "error: extension validation failed");
125
126 Err(anyhow::anyhow!("Validation failed"))
127 }
128 }
129
130 pub async fn validate_extension(path:&PathBuf, detailed:bool) -> Result<ValidationResult> {
132 dev_log!("extensions", "Validating extension at: {:?}", path);
133
134 if !path.exists() {
136 return Ok(ValidationResult { is_valid:false, errors:vec![format!("Path does not exist: {:?}", path)] });
137 }
138
139 let mut errors = Vec::new();
140
141 let package_json_path = path.join("package.json");
143
144 if package_json_path.exists() {
145 match tokio::fs::read_to_string(&package_json_path).await {
146 Ok(content) => {
147 match serde_json::from_str::<serde_json::Value>(&content) {
148 Ok(_) => {
149 dev_log!("extensions", "Valid package.json found");
150 },
151
152 Err(e) => {
153 errors.push(format!("Invalid package.json: {}", e));
154 },
155 }
156 },
157
158 Err(e) => {
159 errors.push(format!("Failed to read package.json: {}", e));
160 },
161 }
162 } else {
163 errors.push("package.json not found".to_string());
164 }
165
166 let is_valid = errors.is_empty();
167
168 if detailed && !errors.is_empty() {
169 for error in &errors {
170 dev_log!("extensions", "Validation error: {}", error);
171 }
172 }
173
174 Ok(ValidationResult { is_valid, errors })
175 }
176
177 pub async fn build_wasm_module(
179 source:PathBuf,
180
181 output:PathBuf,
182
183 _opt_level:String,
184
185 _target:Option<String>,
186 ) -> Result<BuildResult> {
187 dev_log!("wasm", "Building WASM module from: {:?}", source);
188
189 dev_log!("wasm", "Output: {:?}", output);
190
191 Ok(BuildResult { success:true, output_path:output, compile_time_ms:0 })
194 }
195
196 pub async fn list_extensions(_detailed:bool) -> Result<Vec<ExtensionInfo>> {
198 dev_log!("extensions", "Listing extensions");
199
200 Ok(Vec::new())
203 }
204
205 fn create_transport(args:&CliArgs) -> Result<Transport> {
207 match args.transport.as_str() {
208 "grpc" => {
209 use crate::Transport::gRPCTransport::gRPCTransport;
210
211 Ok(Transport::gRPC(
212 gRPCTransport::New(&args.grpc_address).context("Failed to create gRPC transport")?,
213 ))
214 },
215
216 "ipc" => {
217 use crate::Transport::IPCTransport::IPCTransport;
218
219 Ok(Transport::IPC(IPCTransport::New().context("Failed to create IPC transport")?))
220 },
221
222 "wasm" => {
223 use crate::Transport::WASMTransport::WASMTransportImpl;
224
225 Ok(Transport::WASM(
226 WASMTransportImpl::new(args.wasi, args.memory_limit_mb, args.max_execution_time_ms)
227 .context("Failed to create WASM transport")?,
228 ))
229 },
230
231 _ => Ok(Transport::default()),
232 }
233 }
234
235 async fn wait_for_shutdown() {
237 dev_log!("lifecycle", "Grove is running. Press Ctrl+C to stop.");
238
239 tokio::signal::ctrl_c().await.expect("Failed to listen for ctrl+c");
240
241 dev_log!("lifecycle", "Shutdown signal received");
242 }
243}
244
245impl Default for Entry {
246 fn default() -> Self { Self }
247}
248
249#[derive(Debug, Clone)]
251pub struct ValidationResult {
252 pub is_valid:bool,
254
255 pub errors:Vec<String>,
257}
258
259#[derive(Debug, Clone)]
261pub struct BuildResult {
262 pub success:bool,
264
265 pub output_path:PathBuf,
267
268 pub compile_time_ms:u64,
270}
271
272impl BuildResult {
273 pub fn success(&self) -> bool { self.success }
275}
276
277#[derive(Debug, Clone)]
279pub struct ExtensionInfo {
280 pub name:String,
282
283 pub version:String,
285
286 pub path:PathBuf,
288
289 pub is_active:bool,
291}
292
293#[cfg(test)]
294mod tests {
295
296 use super::*;
297
298 #[tokio::test]
299 async fn test_entry_default() {
300 let entry = Entry::default();
301
302 let _ = entry;
304 }
305
306 #[tokio::test]
307 async fn test_validate_extension_nonexistent() {
308 let result = Entry::validate_extension(&PathBuf::from("/nonexistent/path"), false)
309 .await
310 .unwrap();
311
312 assert!(!result.is_valid);
313
314 assert!(!result.errors.is_empty());
315 }
316
317 #[test]
318 fn test_cli_args_default() {
319 let args = CliArgs::default();
320
321 assert_eq!(args.mode, "standalone");
322
323 assert!(args.wasi);
324 }
325
326 #[test]
327 fn test_build_result() {
328 let result = BuildResult {
329 success:true,
330
331 output_path:PathBuf::from("/test/output.wasm"),
332
333 compile_time_ms:1000,
334 };
335
336 assert!(result.success());
337
338 assert_eq!(result.compile_time_ms, 1000);
339 }
340}