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};
9use tracing::{error, info, instrument};
10
11use crate::{
12	Binary::Main::CliArgs,
13	Host::{ExtensionHost::ExtensionHostImpl, HostConfig},
14	Transport::Strategy::Transport,
15};
16
17/// Grove entry point manager
18pub struct Entry;
19
20impl Entry {
21	/// Main entry point for the Grove binary
22	#[instrument(skip(args))]
23	pub async fn run(args:CliArgs) -> Result<()> {
24		info!("Starting Grove v{}", env!("CARGO_PKG_VERSION"));
25		info!("Mode: {}", args.mode);
26
27		match args.mode.as_str() {
28			"standalone" => Self::run_standalone(args).await,
29			"service" => Self::run_service(args).await,
30			"validate" => Self::run_validation(args).await,
31			_ => Err(anyhow::anyhow!("Unknown mode: {}", args.mode)),
32		}
33	}
34
35	/// Run in standalone mode
36	#[instrument(skip(args))]
37	async fn run_standalone(args:CliArgs) -> Result<()> {
38		info!("Starting Grove in standalone mode");
39
40		// Create transport
41		let transport = Self::create_transport(&args)?;
42
43		// Create host configuration
44		let host_config = HostConfig::default().with_activation_timeout(args.max_execution_time_ms);
45
46		// Create extension host
47		let host = ExtensionHostImpl::with_config(transport, host_config)
48			.await
49			.context("Failed to create extension host")?;
50
51		// Load and activate extension if specified
52		if let Some(extension_path) = args.extension {
53			let path = PathBuf::from(extension_path);
54			host.load_extension(&path).await?;
55			host.activate_all().await?;
56		} else {
57			info!("No extension specified, running in daemon mode");
58		}
59
60		// Keep running until interrupted
61		Self::wait_for_shutdown().await;
62
63		// Shutdown host
64		host.shutdown().await?;
65
66		Ok(())
67	}
68
69	/// Run as a service
70	#[instrument(skip(_args))]
71	async fn run_service(_args:CliArgs) -> Result<()> {
72		info!("Starting Grove as service");
73
74		// Create transport for Mountain communication
75		let _transport = Transport::default();
76
77		// Register with Mountain
78		#[cfg(feature = "gRPC")]
79		{
80			match crate::Binary::Build::ServiceRegister::register_with_mountain(
81				"grove-host",
82				&args.mountain_address,
83				true, // auto reconnect
84			)
85			.await
86			{
87				Ok(_) => info!("Registered with Mountain"),
88				Err(e) => warn!("Failed to register with Mountain: {}", e),
89			}
90		}
91
92		#[cfg(not(feature = "gRPC"))]
93		{
94			info!("gRPC feature not enabled, skipping Mountain registration");
95		}
96
97		// Keep running
98		Self::wait_for_shutdown().await;
99
100		Ok(())
101	}
102
103	/// Validate an extension
104	#[instrument(skip(args))]
105	async fn run_validation(args:CliArgs) -> Result<()> {
106		info!("Validating extension");
107
108		let extension_path = args
109			.extension
110			.ok_or_else(|| anyhow::anyhow!("Extension path required for validation"))?;
111
112		let path = PathBuf::from(extension_path);
113		let result = Self::validate_extension(&path, false).await?;
114
115		if result.is_valid {
116			info!("Extension validation passed");
117			Ok(())
118		} else {
119			error!("Extension validation failed");
120			Err(anyhow::anyhow!("Validation failed"))
121		}
122	}
123
124	/// Validate an extension manifest
125	pub async fn validate_extension(path:&PathBuf, detailed:bool) -> Result<ValidationResult> {
126		info!("Validating extension at: {:?}", path);
127
128		// Check if path exists
129		if !path.exists() {
130			return Ok(ValidationResult { is_valid:false, errors:vec![format!("Path does not exist: {:?}", path)] });
131		}
132
133		let mut errors = Vec::new();
134
135		// Parse package.json
136		let package_json_path = path.join("package.json");
137		if package_json_path.exists() {
138			match tokio::fs::read_to_string(&package_json_path).await {
139				Ok(content) => {
140					match serde_json::from_str::<serde_json::Value>(&content) {
141						Ok(_) => {
142							info!("Valid package.json found");
143						},
144						Err(e) => {
145							errors.push(format!("Invalid package.json: {}", e));
146						},
147					}
148				},
149				Err(e) => {
150					errors.push(format!("Failed to read package.json: {}", e));
151				},
152			}
153		} else {
154			errors.push("package.json not found".to_string());
155		}
156
157		let is_valid = errors.is_empty();
158
159		if detailed && !errors.is_empty() {
160			for error in &errors {
161				info!("Validation error: {}", error);
162			}
163		}
164
165		Ok(ValidationResult { is_valid, errors })
166	}
167
168	/// Build a WASM module
169	pub async fn build_wasm_module(
170		source:PathBuf,
171		output:PathBuf,
172		_opt_level:String,
173		_target:Option<String>,
174	) -> Result<BuildResult> {
175		info!("Building WASM module from: {:?}", source);
176		info!("Output: {:?}", output);
177
178		// For now, return a placeholder result
179		// In production, this would invoke rustc/cargo with wasm32-wasi target
180		Ok(BuildResult { success:true, output_path:output, compile_time_ms:0 })
181	}
182
183	/// List loaded extensions
184	pub async fn list_extensions(_detailed:bool) -> Result<Vec<ExtensionInfo>> {
185		info!("Listing extensions");
186
187		// For now, return empty list
188		// In production, this would query the extension manager
189		Ok(Vec::new())
190	}
191
192	/// Create transport based on arguments
193	fn create_transport(args:&CliArgs) -> Result<Transport> {
194		match args.transport.as_str() {
195			"grpc" => {
196				use crate::Transport::gRPCTransport::gRPCTransport;
197				Ok(Transport::gRPC(
198					gRPCTransport::New(&args.grpc_address)
199						.context("Failed to create gRPC transport")?,
200				))
201			},
202			"ipc" => {
203				use crate::Transport::IPCTransport::IPCTransport;
204				Ok(Transport::IPC(
205					IPCTransport::New().context("Failed to create IPC transport")?,
206				))
207			},
208			"wasm" => {
209				use crate::Transport::WASMTransport::WASMTransportImpl;
210				Ok(Transport::WASM(
211					WASMTransportImpl::new(args.wasi, args.memory_limit_mb, args.max_execution_time_ms)
212						.context("Failed to create WASM transport")?,
213				))
214			},
215			_ => Ok(Transport::default()),
216		}
217	}
218
219	/// Wait for shutdown signal
220	async fn wait_for_shutdown() {
221		info!("Grove is running. Press Ctrl+C to stop.");
222
223		tokio::signal::ctrl_c().await.expect("Failed to listen for ctrl+c");
224
225		info!("Shutdown signal received");
226	}
227}
228
229impl Default for Entry {
230	fn default() -> Self { Self }
231}
232
233/// Validation result
234#[derive(Debug, Clone)]
235pub struct ValidationResult {
236	/// Whether validation passed
237	pub is_valid:bool,
238	/// Validation errors
239	pub errors:Vec<String>,
240}
241
242/// Build result
243#[derive(Debug, Clone)]
244pub struct BuildResult {
245	/// Whether build succeeded
246	pub success:bool,
247	/// Output path
248	pub output_path:PathBuf,
249	/// Compile time in ms
250	pub compile_time_ms:u64,
251}
252
253impl BuildResult {
254	/// Check if build succeeded
255	pub fn success(&self) -> bool { self.success }
256}
257
258/// Extension info for listing
259#[derive(Debug, Clone)]
260pub struct ExtensionInfo {
261	/// Extension ID
262	pub name:String,
263	/// Extension version
264	pub version:String,
265	/// Extension path
266	pub path:PathBuf,
267	/// Is active
268	pub is_active:bool,
269}
270
271#[cfg(test)]
272mod tests {
273	use super::*;
274
275	#[tokio::test]
276	async fn test_entry_default() {
277		let entry = Entry::default();
278		// Just test that it can be created
279		let _ = entry;
280	}
281
282	#[tokio::test]
283	async fn test_validate_extension_nonexistent() {
284		let result = Entry::validate_extension(&PathBuf::from("/nonexistent/path"), false)
285			.await
286			.unwrap();
287
288		assert!(!result.is_valid);
289		assert!(!result.errors.is_empty());
290	}
291
292	#[test]
293	fn test_cli_args_default() {
294		let args = CliArgs::default();
295		assert_eq!(args.mode, "standalone");
296		assert!(args.wasi);
297	}
298
299	#[test]
300	fn test_build_result() {
301		let result = BuildResult {
302			success:true,
303			output_path:PathBuf::from("/test/output.wasm"),
304			compile_time_ms:1000,
305		};
306
307		assert!(result.success());
308		assert_eq!(result.compile_time_ms, 1000);
309	}
310}