orbiton/
maintenance.rs

1// Maintenance utilities for the development server
2// This module provides cleanup and maintenance functionality for HMR and project state
3
4use console::style;
5use log::{info, warn};
6use std::path::Path;
7use std::time::Duration;
8
9use crate::config::OrbitonConfig;
10use crate::dev_server::DevServer;
11use crate::hmr::HmrContext;
12
13/// Maintenance operations for the development environment
14pub struct MaintenanceManager {
15    hmr_context: HmrContext,
16    config: OrbitonConfig,
17}
18
19impl MaintenanceManager {
20    /// Create a new maintenance manager
21    pub fn new(project_dir: &Path) -> anyhow::Result<Self> {
22        let config = OrbitonConfig::load_from_project(project_dir)?;
23        let hmr_context = HmrContext::new(project_dir.to_path_buf());
24
25        Ok(Self {
26            hmr_context,
27            config,
28        })
29    }
30
31    /// Perform cleanup of stale HMR updates
32    pub fn cleanup_stale_updates(&self, max_age: Duration) {
33        info!("Cleaning up stale HMR updates older than {max_age:?}");
34
35        let stale_modules = self.hmr_context.get_stale_updates(max_age);
36        if !stale_modules.is_empty() {
37            println!(
38                "{} Removing {} stale modules: {}",
39                style("Cleanup:").bold().yellow(),
40                stale_modules.len(),
41                stale_modules.join(", ")
42            );
43
44            self.hmr_context.clear_stale_updates(max_age);
45        } else {
46            println!("{} No stale modules found", style("Info:").bold().blue());
47        }
48    }
49
50    /// Clear all pending HMR updates
51    pub fn clear_all_updates(&self) {
52        info!("Clearing all pending HMR updates");
53        self.hmr_context.clear();
54        println!(
55            "{} All HMR updates cleared",
56            style("Cleanup:").bold().green()
57        );
58    }
59    /// Get information about the oldest pending update
60    #[allow(dead_code)] // Used in tests and maintenance operations
61    pub fn get_update_info(&self) -> Option<Duration> {
62        let oldest_age = self.hmr_context.get_oldest_update_age();
63        if let Some(age) = oldest_age {
64            println!(
65                "{} Oldest pending update is {:?} old",
66                style("Info:").bold().blue(),
67                age
68            );
69        } else {
70            println!("{} No pending updates", style("Info:").bold().blue());
71        }
72        oldest_age
73    }
74    /// Merge configuration with override settings
75    #[allow(dead_code)] // Used in tests and maintenance operations
76    pub fn apply_config_overrides(&mut self, overrides: OrbitonConfig) {
77        info!("Applying configuration overrides");
78        self.config.merge_with(&overrides);
79
80        println!(
81            "{} Configuration updated with overrides",
82            style("Config:").bold().green()
83        );
84    }
85    /// Create a simple dev server for testing (uses DevServer::new)
86    #[allow(dead_code)] // Used in tests and maintenance operations
87    pub fn create_simple_dev_server(
88        &self,
89        port: u16,
90        project_dir: &Path,
91    ) -> anyhow::Result<DevServer> {
92        info!("Creating simple development server on port {port}");
93        DevServer::new(port, project_dir)
94    }
95    /// Perform automated maintenance based on configuration
96    #[allow(dead_code)] // Used in tests and maintenance operations
97    pub fn perform_automated_maintenance(&self) -> anyhow::Result<()> {
98        info!("Performing automated maintenance");
99
100        // Get cleanup threshold from config (default to 5 minutes)
101        let cleanup_threshold = Duration::from_millis(self.config.hmr.debounce_ms * 300); // 300x debounce time
102
103        // Cleanup stale updates
104        self.cleanup_stale_updates(cleanup_threshold);
105
106        // Show update info
107        self.get_update_info();
108
109        // If we have too many pending updates, warn about it
110        let pending_count = self.hmr_context.get_pending_updates().len();
111        if pending_count > 10 {
112            warn!(
113                "High number of pending updates ({pending_count}), consider restarting the dev server"
114            );
115
116            println!(
117                "{} {} pending updates - consider restarting for optimal performance",
118                style("Warning:").bold().yellow(),
119                pending_count
120            );
121        }
122
123        Ok(())
124    }
125
126    /// Show maintenance status information
127    pub fn show_status(&self) {
128        info!("Displaying maintenance status");
129
130        println!("{}", style("=== Maintenance Status ===").bold().cyan());
131
132        // Show HMR status
133        let pending_updates = self.hmr_context.get_pending_updates();
134        println!(
135            "{} {} pending HMR updates",
136            style("HMR:").bold().blue(),
137            pending_updates.len()
138        );
139
140        if !pending_updates.is_empty() {
141            println!("  Modules: {}", pending_updates.join(", "));
142
143            if let Some(oldest_age) = self.hmr_context.get_oldest_update_age() {
144                println!("  Oldest update: {oldest_age:?} ago");
145            }
146        }
147
148        // Show configuration status
149        println!(
150            "{} Port: {}, HMR: {}",
151            style("Config:").bold().blue(),
152            self.config.dev_server.port,
153            if self.config.hmr.enabled {
154                "enabled"
155            } else {
156                "disabled"
157            }
158        );
159        println!(
160            "  Debounce: {}ms, Source dir: {}",
161            self.config.hmr.debounce_ms, self.config.project.src_dir
162        );
163
164        // Show stale update information
165        let stale_threshold = Duration::from_secs(300); // 5 minutes
166        let stale_updates = self.hmr_context.get_stale_updates(stale_threshold);
167        if !stale_updates.is_empty() {
168            println!(
169                "{} {} stale updates (older than 5 minutes)",
170                style("Warning:").bold().yellow(),
171                stale_updates.len()
172            );
173        }
174
175        println!("{}", style("==========================").bold().cyan());
176    }
177    /// Get the configuration for external use
178    #[allow(dead_code)] // Used in tests and maintenance operations
179    pub fn config(&self) -> &OrbitonConfig {
180        &self.config
181    }
182
183    /// Get the HMR context for external use
184    #[allow(dead_code)] // Used in tests and maintenance operations
185    pub fn hmr_context(&self) -> &HmrContext {
186        &self.hmr_context
187    }
188}
189
190/// Utility function to create maintenance manager and perform cleanup
191#[allow(dead_code)] // Used in tests and maintenance operations
192pub fn perform_project_maintenance(project_dir: &Path) -> anyhow::Result<()> {
193    let manager = MaintenanceManager::new(project_dir)?;
194    manager.perform_automated_maintenance()
195}
196
197/// Utility function to demonstrate config merging
198#[allow(dead_code)] // Used in tests and maintenance operations
199pub fn demo_config_merging(project_dir: &Path) -> anyhow::Result<()> {
200    let mut manager = MaintenanceManager::new(project_dir)?;
201
202    // Create override config with different settings
203    let mut override_config = OrbitonConfig::default();
204    override_config.dev_server.port = 9000;
205    override_config.hmr.enabled = false;
206    override_config.hmr.debounce_ms = 1000;
207
208    println!(
209        "{} Original port: {}",
210        style("Before:").bold().blue(),
211        manager.config().dev_server.port
212    );
213
214    manager.apply_config_overrides(override_config);
215
216    println!(
217        "{} New port: {}",
218        style("After:").bold().green(),
219        manager.config().dev_server.port
220    );
221
222    Ok(())
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use std::fs;
229    use tempfile::tempdir;
230
231    #[test]
232    fn test_maintenance_manager_creation() {
233        let temp_dir = tempdir().unwrap();
234        let result = MaintenanceManager::new(temp_dir.path());
235        assert!(result.is_ok());
236    }
237
238    #[test]
239    fn test_hmr_cleanup() {
240        let temp_dir = tempdir().unwrap();
241        let project_dir = temp_dir.path();
242
243        // Create a source file
244        let src_dir = project_dir.join("src");
245        fs::create_dir_all(&src_dir).unwrap();
246        let test_file = src_dir.join("test.rs");
247        fs::write(&test_file, "// test").unwrap();
248
249        let manager = MaintenanceManager::new(project_dir).unwrap();
250
251        // Record a file change
252        manager.hmr_context().record_file_change(&test_file);
253
254        // Test cleanup
255        manager.cleanup_stale_updates(Duration::from_secs(1));
256        manager.clear_all_updates();
257        manager.get_update_info();
258    }
259
260    #[test]
261    fn test_config_merging() {
262        let temp_dir = tempdir().unwrap();
263        let project_dir = temp_dir.path();
264
265        let mut manager = MaintenanceManager::new(project_dir).unwrap();
266        let original_port = manager.config().dev_server.port;
267
268        let mut override_config = OrbitonConfig::default();
269        override_config.dev_server.port = 9999;
270
271        manager.apply_config_overrides(override_config);
272
273        assert_ne!(manager.config().dev_server.port, original_port);
274        assert_eq!(manager.config().dev_server.port, 9999);
275    }
276
277    #[test]
278    fn test_simple_dev_server_creation() {
279        let temp_dir = tempdir().unwrap();
280        let project_dir = temp_dir.path();
281
282        let manager = MaintenanceManager::new(project_dir).unwrap();
283        let result = manager.create_simple_dev_server(0, project_dir); // Use port 0 for testing
284        assert!(result.is_ok());
285    }
286}