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::Build::ServiceRegister,
13	Binary::Main::CliArgs,
14	Host::{HostConfig, ExtensionHost::ExtensionHostImpl},
15	Transport::Transport,
16	WASM::Runtime::{WASMConfig, WASMRuntime},
17};
18
19/// Grove entry point manager
20pub struct Entry;
21
22impl Entry {
23	/// Main entry point for the Grove binary
24	#[instrument(skip(args))]
25	pub async fn run(args:CliArgs) -> Result<()> {
26		info!("Starting Grove v{}", env!("CARGO_PKG_VERSION"));
27		info!("Mode: {}", args.mode);
28
29		match args.mode.as_str() {
30			"standalone" => Self::run_standalone(args).await,
31			"service" => Self::run_service(args).await,
32			"validate" => Self::run_validation(args).await,
33			_ => Err(anyhow::anyhow!("Unknown mode: {}", args.mode)),
34		}
35	}
36
37	/// Run in standalone mode
38	#[instrument(skip(args))]
39	async fn run_standalone(args:CliArgs) -> Result<()> {
40		info!("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			host.load_extension(&path).await?;
57			host.activate_all().await?;
58		} else {
59			info!("No extension specified, running in daemon mode");
60		}
61
62		// Keep running until interrupted
63		Self::wait_for_shutdown().await;
64
65		// Shutdown host
66		host.shutdown().await?;
67
68		Ok(())
69	}
70
71	/// Run as a service
72	#[instrument(skip(args))]
73	async fn run_service(args:CliArgs) -> Result<()> {
74		info!("Starting Grove as service");
75
76		// Create transport for Mountain communication
77		let transport = Transport::default();
78
79		// Register with Mountain
80		#[cfg(feature = "gRPC")]
81		{
82			match crate::Binary::Build::ServiceRegister::register_with_mountain(
83				"grove-host",
84				&args.mountain_address,
85				true, // auto reconnect
86			).await {
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		info!("Optimization level: {}", opt_level);
178
179		// For now, return a placeholder result
180		// In production, this would invoke rustc/cargo with wasm32-wasi target
181		Ok(BuildResult { success:true, output_path:output, compile_time_ms:0 })
182	}
183
184	/// List loaded extensions
185	pub async fn list_extensions(detailed:bool) -> Result<Vec<ExtensionInfo>> {
186		info!("Listing extensions");
187
188		// For now, return empty list
189		// In production, this would query the extension manager
190		Ok(Vec::new())
191	}
192
193	/// Create transport based on arguments
194	fn create_transport(args:&CliArgs) -> Result<Transport> {
195		match args.transport.as_str() {
196			"grpc" => {
197				use crate::Transport::gRPCTransport::GrpcTransport;
198				Ok(Transport::gRPC(
199					GrpcTransport::new(&args.grpc_address)
200						.context("Failed to create gRPC transport")?,
201				))
202			},
203			"ipc" => {
204				use crate::Transport::IPCTransport::IPCTransportImpl;
205				Ok(Transport::IPC(
206					IPCTransportImpl::new().context("Failed to create IPC transport")?,
207				))
208			},
209			"wasm" => {
210				use crate::Transport::WASMTransport::WASMTransportImpl;
211				Ok(Transport::WASM(
212					WASMTransportImpl::new(args.wasi, args.memory_limit_mb, args.max_execution_time_ms)
213						.context("Failed to create WASM transport")?,
214				))
215			},
216			_ => Ok(Transport::default()),
217		}
218	}
219
220	/// Wait for shutdown signal
221	async fn wait_for_shutdown() {
222		info!("Grove is running. Press Ctrl+C to stop.");
223
224		tokio::signal::ctrl_c().await.expect("Failed to listen for ctrl+c");
225
226		info!("Shutdown signal received");
227	}
228}
229
230impl Default for Entry {
231	fn default() -> Self { Self }
232}
233
234/// Validation result
235#[derive(Debug, Clone)]
236pub struct ValidationResult {
237	/// Whether validation passed
238	pub is_valid:bool,
239	/// Validation errors
240	pub errors:Vec<String>,
241}
242
243/// Build result
244#[derive(Debug, Clone)]
245pub struct BuildResult {
246	/// Whether build succeeded
247	pub success:bool,
248	/// Output path
249	pub output_path:PathBuf,
250	/// Compile time in ms
251	pub compile_time_ms:u64,
252}
253
254impl BuildResult {
255	/// Check if build succeeded
256	pub fn success(&self) -> bool { self.success }
257}
258
259/// Extension info for listing
260#[derive(Debug, Clone)]
261pub struct ExtensionInfo {
262	/// Extension ID
263	pub name:String,
264	/// Extension version
265	pub version:String,
266	/// Extension path
267	pub path:PathBuf,
268	/// Is active
269	pub is_active:bool,
270}
271
272#[cfg(test)]
273mod tests {
274	use super::*;
275
276	#[tokio::test]
277	async fn test_entry_default() {
278		let entry = Entry::default();
279		// Just test that it can be created
280		let _ = entry;
281	}
282
283	#[tokio::test]
284	async fn test_validate_extension_nonexistent() {
285		let result = Entry::validate_extension(&PathBuf::from("/nonexistent/path"), false)
286			.await
287			.unwrap();
288
289		assert!(!result.is_valid);
290		assert!(!result.errors.is_empty());
291	}
292
293	#[test]
294	fn test_cli_args_default() {
295		let args = CliArgs::default();
296		assert_eq!(args.mode, "standalone");
297		assert!(args.wasi);
298	}
299
300	#[test]
301	fn test_build_result() {
302		let result = BuildResult {
303			success:true,
304			output_path:PathBuf::from("/test/output.wasm"),
305			compile_time_ms:1000,
306		};
307
308		assert!(result.success());
309		assert_eq!(result.compile_time_ms, 1000);
310	}
311}