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 crate::dev_log;
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	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	/// Run in standalone mode
35	async fn run_standalone(args:CliArgs) -> Result<()> {
36		dev_log!("grove", "Starting Grove in standalone mode");
37
38		// Create transport
39		let transport = Self::create_transport(&args)?;
40
41		// Create host configuration
42		let host_config = HostConfig::default().with_activation_timeout(args.max_execution_time_ms);
43
44		// Create extension host
45		let host = ExtensionHostImpl::with_config(transport, host_config)
46			.await
47			.context("Failed to create extension host")?;
48
49		// Load and activate extension if specified
50		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		// Keep running until interrupted
59		Self::wait_for_shutdown().await;
60
61		// Shutdown host
62		host.shutdown().await?;
63
64		Ok(())
65	}
66
67	/// Run as a service
68	async fn run_service(_args:CliArgs) -> Result<()> {
69		dev_log!("grove", "Starting Grove as service");
70
71		// Create transport for Mountain communication
72		let _transport = Transport::default();
73
74		// Register with Mountain
75		#[cfg(feature = "gRPC")]
76		{
77			match crate::Binary::Build::ServiceRegister::register_with_mountain(
78				"grove-host",
79				&args.mountain_address,
80				true, // auto reconnect
81			)
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		// Keep running
95		Self::wait_for_shutdown().await;
96
97		Ok(())
98	}
99
100	/// Validate an extension
101	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	/// Validate an extension manifest
121	pub async fn validate_extension(path:&PathBuf, detailed:bool) -> Result<ValidationResult> {
122		dev_log!("extensions", "Validating extension at: {:?}", path);
123
124		// Check if path exists
125		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		// Parse package.json
132		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	/// Build a WASM module
165	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		// For now, return a placeholder result
175		// In production, this would invoke rustc/cargo with wasm32-wasi target
176		Ok(BuildResult { success:true, output_path:output, compile_time_ms:0 })
177	}
178
179	/// List loaded extensions
180	pub async fn list_extensions(_detailed:bool) -> Result<Vec<ExtensionInfo>> {
181		dev_log!("extensions", "Listing extensions");
182
183		// For now, return empty list
184		// In production, this would query the extension manager
185		Ok(Vec::new())
186	}
187
188	/// Create transport based on arguments
189	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	/// Wait for shutdown signal
213	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/// Validation result
227#[derive(Debug, Clone)]
228pub struct ValidationResult {
229	/// Whether validation passed
230	pub is_valid:bool,
231	/// Validation errors
232	pub errors:Vec<String>,
233}
234
235/// Build result
236#[derive(Debug, Clone)]
237pub struct BuildResult {
238	/// Whether build succeeded
239	pub success:bool,
240	/// Output path
241	pub output_path:PathBuf,
242	/// Compile time in ms
243	pub compile_time_ms:u64,
244}
245
246impl BuildResult {
247	/// Check if build succeeded
248	pub fn success(&self) -> bool { self.success }
249}
250
251/// Extension info for listing
252#[derive(Debug, Clone)]
253pub struct ExtensionInfo {
254	/// Extension ID
255	pub name:String,
256	/// Extension version
257	pub version:String,
258	/// Extension path
259	pub path:PathBuf,
260	/// Is active
261	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		// Just test that it can be created
272		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}