Skip to main content

Grove/Binary/Main/
Entry.rs

1//! Entry Module (Binary/Main)
2//!
3//! Main entry point for the Grove binary.
4//! Handles CLI argument parsing and initialization of the Grove host.
5
6use 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
17/// Grove entry point manager
18pub struct Entry;
19
20impl Entry {
21	/// Main entry point for the Grove binary
22	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	/// Run in standalone mode
39	async fn run_standalone(args:CliArgs) -> Result<()> {
40		dev_log!("grove", "Starting Grove in standalone mode");
41
42		// Create transport
43		let transport = Self::create_transport(&args)?;
44
45		// Create host configuration
46		let host_config = HostConfig::default().with_activation_timeout(args.max_execution_time_ms);
47
48		// Create extension host
49		let host = ExtensionHostImpl::with_config(transport, host_config)
50			.await
51			.context("Failed to create extension host")?;
52
53		// Load and activate extension if specified
54		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		// Keep running until interrupted
65		Self::wait_for_shutdown().await;
66
67		// Shutdown host
68		host.shutdown().await?;
69
70		Ok(())
71	}
72
73	/// Run as a service
74	async fn run_service(_args:CliArgs) -> Result<()> {
75		dev_log!("grove", "Starting Grove as service");
76
77		// Create transport for Mountain communication
78		let _transport = Transport::default();
79
80		// Register with Mountain
81		#[cfg(feature = "gRPC")]
82		{
83			match crate::Binary::Build::ServiceRegister::register_with_mountain(
84				"grove-host",
85				&args.mountain_address,
86				true, // auto reconnect
87			)
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		// Keep running
102		Self::wait_for_shutdown().await;
103
104		Ok(())
105	}
106
107	/// Validate an extension
108	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	/// Validate an extension manifest
131	pub async fn validate_extension(path:&PathBuf, detailed:bool) -> Result<ValidationResult> {
132		dev_log!("extensions", "Validating extension at: {:?}", path);
133
134		// Check if path exists
135		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		// Parse package.json
142		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	/// Build a WASM module
178	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		// For now, return a placeholder result
192		// In production, this would invoke rustc/cargo with wasm32-wasi target
193		Ok(BuildResult { success:true, output_path:output, compile_time_ms:0 })
194	}
195
196	/// List loaded extensions
197	pub async fn list_extensions(_detailed:bool) -> Result<Vec<ExtensionInfo>> {
198		dev_log!("extensions", "Listing extensions");
199
200		// For now, return empty list
201		// In production, this would query the extension manager
202		Ok(Vec::new())
203	}
204
205	/// Create transport based on arguments
206	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	/// Wait for shutdown signal
236	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/// Validation result
250#[derive(Debug, Clone)]
251pub struct ValidationResult {
252	/// Whether validation passed
253	pub is_valid:bool,
254
255	/// Validation errors
256	pub errors:Vec<String>,
257}
258
259/// Build result
260#[derive(Debug, Clone)]
261pub struct BuildResult {
262	/// Whether build succeeded
263	pub success:bool,
264
265	/// Output path
266	pub output_path:PathBuf,
267
268	/// Compile time in ms
269	pub compile_time_ms:u64,
270}
271
272impl BuildResult {
273	/// Check if build succeeded
274	pub fn success(&self) -> bool { self.success }
275}
276
277/// Extension info for listing
278#[derive(Debug, Clone)]
279pub struct ExtensionInfo {
280	/// Extension ID
281	pub name:String,
282
283	/// Extension version
284	pub version:String,
285
286	/// Extension path
287	pub path:PathBuf,
288
289	/// Is active
290	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		// Just test that it can be created
303		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}