1use std::path::PathBuf;
7
8use anyhow::{Context, Result};
9use crate::dev_log;
10
11use crate::{
12 Binary::Main::CliArgs,
13 Host::{ExtensionHost::ExtensionHostImpl, HostConfig},
14 Transport::Strategy::Transport,
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 dev_log!("lifecycle", "Mode: {}", args.mode);
25
26 match args.mode.as_str() {
27 "standalone" => Self::run_standalone(args).await,
28 "service" => Self::run_service(args).await,
29 "validate" => Self::run_validation(args).await,
30 _ => Err(anyhow::anyhow!("Unknown mode: {}", args.mode)),
31 }
32 }
33
34 async fn run_standalone(args:CliArgs) -> Result<()> {
36 dev_log!("grove", "Starting Grove in standalone mode");
37
38 let transport = Self::create_transport(&args)?;
40
41 let host_config = HostConfig::default().with_activation_timeout(args.max_execution_time_ms);
43
44 let host = ExtensionHostImpl::with_config(transport, host_config)
46 .await
47 .context("Failed to create extension host")?;
48
49 if let Some(extension_path) = args.extension {
51 let path = PathBuf::from(extension_path);
52 host.load_extension(&path).await?;
53 host.activate_all().await?;
54 } else {
55 dev_log!("grove", "No extension specified, running in daemon mode");
56 }
57
58 Self::wait_for_shutdown().await;
60
61 host.shutdown().await?;
63
64 Ok(())
65 }
66
67 async fn run_service(_args:CliArgs) -> Result<()> {
69 dev_log!("grove", "Starting Grove as service");
70
71 let _transport = Transport::default();
73
74 #[cfg(feature = "gRPC")]
76 {
77 match crate::Binary::Build::ServiceRegister::register_with_mountain(
78 "grove-host",
79 &args.mountain_address,
80 true, )
82 .await
83 {
84 Ok(_) => dev_log!("grove", "Registered with Mountain"),
85 Err(e) => dev_log!("grove", "warn: failed to register with Mountain: {}", e),
86 }
87 }
88
89 #[cfg(not(feature = "gRPC"))]
90 {
91 dev_log!("grpc", "gRPC feature not enabled, skipping Mountain registration");
92 }
93
94 Self::wait_for_shutdown().await;
96
97 Ok(())
98 }
99
100 async fn run_validation(args:CliArgs) -> Result<()> {
102 dev_log!("extensions", "Validating extension");
103
104 let extension_path = args
105 .extension
106 .ok_or_else(|| anyhow::anyhow!("Extension path required for validation"))?;
107
108 let path = PathBuf::from(extension_path);
109 let result = Self::validate_extension(&path, false).await?;
110
111 if result.is_valid {
112 dev_log!("extensions", "Extension validation passed");
113 Ok(())
114 } else {
115 dev_log!("extensions", "error: extension validation failed");
116 Err(anyhow::anyhow!("Validation failed"))
117 }
118 }
119
120 pub async fn validate_extension(path:&PathBuf, detailed:bool) -> Result<ValidationResult> {
122 dev_log!("extensions", "Validating extension at: {:?}", path);
123
124 if !path.exists() {
126 return Ok(ValidationResult { is_valid:false, errors:vec![format!("Path does not exist: {:?}", path)] });
127 }
128
129 let mut errors = Vec::new();
130
131 let package_json_path = path.join("package.json");
133 if package_json_path.exists() {
134 match tokio::fs::read_to_string(&package_json_path).await {
135 Ok(content) => {
136 match serde_json::from_str::<serde_json::Value>(&content) {
137 Ok(_) => {
138 dev_log!("extensions", "Valid package.json found");
139 },
140 Err(e) => {
141 errors.push(format!("Invalid package.json: {}", e));
142 },
143 }
144 },
145 Err(e) => {
146 errors.push(format!("Failed to read package.json: {}", e));
147 },
148 }
149 } else {
150 errors.push("package.json not found".to_string());
151 }
152
153 let is_valid = errors.is_empty();
154
155 if detailed && !errors.is_empty() {
156 for error in &errors {
157 dev_log!("extensions", "Validation error: {}", error);
158 }
159 }
160
161 Ok(ValidationResult { is_valid, errors })
162 }
163
164 pub async fn build_wasm_module(
166 source:PathBuf,
167 output:PathBuf,
168 _opt_level:String,
169 _target:Option<String>,
170 ) -> Result<BuildResult> {
171 dev_log!("wasm", "Building WASM module from: {:?}", source);
172 dev_log!("wasm", "Output: {:?}", output);
173
174 Ok(BuildResult { success:true, output_path:output, compile_time_ms:0 })
177 }
178
179 pub async fn list_extensions(_detailed:bool) -> Result<Vec<ExtensionInfo>> {
181 dev_log!("extensions", "Listing extensions");
182
183 Ok(Vec::new())
186 }
187
188 fn create_transport(args:&CliArgs) -> Result<Transport> {
190 match args.transport.as_str() {
191 "grpc" => {
192 use crate::Transport::gRPCTransport::gRPCTransport;
193 Ok(Transport::gRPC(
194 gRPCTransport::New(&args.grpc_address).context("Failed to create gRPC transport")?,
195 ))
196 },
197 "ipc" => {
198 use crate::Transport::IPCTransport::IPCTransport;
199 Ok(Transport::IPC(IPCTransport::New().context("Failed to create IPC transport")?))
200 },
201 "wasm" => {
202 use crate::Transport::WASMTransport::WASMTransportImpl;
203 Ok(Transport::WASM(
204 WASMTransportImpl::new(args.wasi, args.memory_limit_mb, args.max_execution_time_ms)
205 .context("Failed to create WASM transport")?,
206 ))
207 },
208 _ => Ok(Transport::default()),
209 }
210 }
211
212 async fn wait_for_shutdown() {
214 dev_log!("lifecycle", "Grove is running. Press Ctrl+C to stop.");
215
216 tokio::signal::ctrl_c().await.expect("Failed to listen for ctrl+c");
217
218 dev_log!("lifecycle", "Shutdown signal received");
219 }
220}
221
222impl Default for Entry {
223 fn default() -> Self { Self }
224}
225
226#[derive(Debug, Clone)]
228pub struct ValidationResult {
229 pub is_valid:bool,
231 pub errors:Vec<String>,
233}
234
235#[derive(Debug, Clone)]
237pub struct BuildResult {
238 pub success:bool,
240 pub output_path:PathBuf,
242 pub compile_time_ms:u64,
244}
245
246impl BuildResult {
247 pub fn success(&self) -> bool { self.success }
249}
250
251#[derive(Debug, Clone)]
253pub struct ExtensionInfo {
254 pub name:String,
256 pub version:String,
258 pub path:PathBuf,
260 pub is_active:bool,
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[tokio::test]
269 async fn test_entry_default() {
270 let entry = Entry::default();
271 let _ = entry;
273 }
274
275 #[tokio::test]
276 async fn test_validate_extension_nonexistent() {
277 let result = Entry::validate_extension(&PathBuf::from("/nonexistent/path"), false)
278 .await
279 .unwrap();
280
281 assert!(!result.is_valid);
282 assert!(!result.errors.is_empty());
283 }
284
285 #[test]
286 fn test_cli_args_default() {
287 let args = CliArgs::default();
288 assert_eq!(args.mode, "standalone");
289 assert!(args.wasi);
290 }
291
292 #[test]
293 fn test_build_result() {
294 let result = BuildResult {
295 success:true,
296 output_path:PathBuf::from("/test/output.wasm"),
297 compile_time_ms:1000,
298 };
299
300 assert!(result.success());
301 assert_eq!(result.compile_time_ms, 1000);
302 }
303}