add engine and rpc client, archive old code
This commit is contained in:
		
							
								
								
									
										1320
									
								
								_archive/devtools/reloadd/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1320
									
								
								_archive/devtools/reloadd/Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										11
									
								
								_archive/devtools/reloadd/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								_archive/devtools/reloadd/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "reloadd"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
clap = { version = "4", features = ["derive"] }
 | 
			
		||||
tokio = { version = "1", features = ["full"] }
 | 
			
		||||
tokio-tungstenite = "0.21"
 | 
			
		||||
notify = "6"
 | 
			
		||||
futures-util = "0.3"
 | 
			
		||||
							
								
								
									
										124
									
								
								_archive/devtools/reloadd/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								_archive/devtools/reloadd/README.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,124 @@
 | 
			
		||||
# Reloadd
 | 
			
		||||
 | 
			
		||||
A powerful development tool for automatically restarting your application and reloading your browser when files change.
 | 
			
		||||
 | 
			
		||||
## Features
 | 
			
		||||
 | 
			
		||||
- 🔄 **File Watching**: Monitor multiple directories for changes
 | 
			
		||||
- 🚀 **Auto-Restart**: Automatically restart your application when files change
 | 
			
		||||
- 🌐 **Browser Reload**: Automatically reload connected browsers
 | 
			
		||||
- 🔌 **WebSocket Integration**: Uses WebSockets for instant browser reloading
 | 
			
		||||
- 📊 **Sequential Commands**: Run multiple commands in sequence
 | 
			
		||||
- 🔧 **Configurable Ports**: Customize web server and WebSocket ports
 | 
			
		||||
- 🛠️ **Robust Error Handling**: Clear error messages and graceful recovery
 | 
			
		||||
 | 
			
		||||
## Installation
 | 
			
		||||
 | 
			
		||||
### From Source
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# Clone the repository
 | 
			
		||||
git clone https://github.com/yourusername/reloadd.git
 | 
			
		||||
cd reloadd
 | 
			
		||||
 | 
			
		||||
# Build and install
 | 
			
		||||
cargo install --path .
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Usage
 | 
			
		||||
 | 
			
		||||
### Basic Usage
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
reloadd --watch src --watch templates -- run --example server
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### With Sequential Commands
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
reloadd --watch src --run "cargo build" --run "cargo test" --run "cargo run --example server"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Commands will run in the order specified. All commands except the last one will run to completion. The last command is treated as the long-running server process.
 | 
			
		||||
 | 
			
		||||
### With Custom Port
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
reloadd --watch src --port 3000 --run "cargo run --example server"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### In a Shell Script
 | 
			
		||||
 | 
			
		||||
Create a `develop.sh` script for your project:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
set -e
 | 
			
		||||
 | 
			
		||||
# Start dev server with file watching and browser reload
 | 
			
		||||
reloadd \
 | 
			
		||||
  --watch src \
 | 
			
		||||
  --watch templates \
 | 
			
		||||
  --port 8080 \
 | 
			
		||||
  --run "cargo run --example server"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Make it executable and run it:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
chmod +x develop.sh
 | 
			
		||||
./develop.sh
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Command Line Options
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
Usage: reloadd [OPTIONS] --watch <PATH>... [-- <COMMAND>...]
 | 
			
		||||
 | 
			
		||||
Arguments:
 | 
			
		||||
  [COMMAND]...  Command to run on change (legacy format)
 | 
			
		||||
 | 
			
		||||
Options:
 | 
			
		||||
  -w, --watch <PATH>...   Paths to watch (like src/, templates/, scripts/)
 | 
			
		||||
  -p, --port <PORT>       Port for the web server [default: 8080]
 | 
			
		||||
  -r, --run <COMMAND>...  Multiple commands to run
 | 
			
		||||
  -h, --help              Print help
 | 
			
		||||
  -V, --version           Print version
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## LiveReload Integration
 | 
			
		||||
 | 
			
		||||
When you start the tool, it will output a script tag that you can add to your HTML files:
 | 
			
		||||
 | 
			
		||||
```html
 | 
			
		||||
<script>
 | 
			
		||||
const ws = new WebSocket("ws://localhost:35729");
 | 
			
		||||
ws.onmessage = (msg) => {
 | 
			
		||||
  if (msg.data === "reload") location.reload();
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Add this script to your HTML templates to enable automatic browser reloading.
 | 
			
		||||
 | 
			
		||||
## How It Works
 | 
			
		||||
 | 
			
		||||
1. Reloadd watches specified directories for file changes
 | 
			
		||||
2. When a change is detected, it runs your commands in sequence
 | 
			
		||||
3. Build commands (all except the last) run to completion before proceeding
 | 
			
		||||
4. The last command (typically a server) runs and stays active
 | 
			
		||||
5. After a brief delay, it sends a reload signal to connected browsers
 | 
			
		||||
6. Browsers with the LiveReload script will automatically refresh
 | 
			
		||||
 | 
			
		||||
## Error Handling
 | 
			
		||||
 | 
			
		||||
Reloadd includes robust error handling:
 | 
			
		||||
 | 
			
		||||
- Validates watch paths before starting
 | 
			
		||||
- Checks for port availability
 | 
			
		||||
- Provides clear error messages
 | 
			
		||||
- Gracefully exits with error codes when critical errors occur
 | 
			
		||||
 | 
			
		||||
## License
 | 
			
		||||
 | 
			
		||||
MIT
 | 
			
		||||
							
								
								
									
										97
									
								
								_archive/devtools/reloadd/bin/reloadd.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								_archive/devtools/reloadd/bin/reloadd.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
use clap::{Parser};
 | 
			
		||||
use notify::{RecursiveMode, Watcher, EventKind, recommended_watcher};
 | 
			
		||||
use std::sync::{Arc, Mutex};
 | 
			
		||||
use std::process::{Command, Child, Stdio};
 | 
			
		||||
use tokio::net::TcpListener;
 | 
			
		||||
use tokio::sync::broadcast;
 | 
			
		||||
use tokio_tungstenite::accept_async;
 | 
			
		||||
use futures_util::{SinkExt, StreamExt};
 | 
			
		||||
 | 
			
		||||
#[derive(Parser, Debug)]
 | 
			
		||||
#[command(author, version, about)]
 | 
			
		||||
struct Args {
 | 
			
		||||
    /// Paths to watch (like src/, templates/, scripts/)
 | 
			
		||||
    #[arg(short, long, value_name = "PATH", num_args = 1.., required = true)]
 | 
			
		||||
    watch: Vec<String>,
 | 
			
		||||
 | 
			
		||||
    /// Command to run on change (like: -- run --example server)
 | 
			
		||||
    #[arg(last = true)]
 | 
			
		||||
    command: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() {
 | 
			
		||||
    let args = Args::parse();
 | 
			
		||||
 | 
			
		||||
    let (tx, _) = broadcast::channel::<()>(10);
 | 
			
		||||
    let tx_ws = tx.clone();
 | 
			
		||||
    let server_process = Arc::new(Mutex::new(None::<Child>));
 | 
			
		||||
 | 
			
		||||
    // Start WebSocket reload server
 | 
			
		||||
    tokio::spawn(start_websocket_server(tx_ws.clone()));
 | 
			
		||||
 | 
			
		||||
    // Start watching files and restarting command
 | 
			
		||||
    let server_process_clone = Arc::clone(&server_process);
 | 
			
		||||
    let watch_paths = args.watch.clone();
 | 
			
		||||
    let cmd_args = args.command.clone();
 | 
			
		||||
 | 
			
		||||
    std::thread::spawn(move || {
 | 
			
		||||
        let mut watcher = recommended_watcher(move |res| {
 | 
			
		||||
            if let Ok(event) = res {
 | 
			
		||||
                if matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_) | EventKind::Remove(_)) {
 | 
			
		||||
                    println!("📦 Change detected, restarting...");
 | 
			
		||||
 | 
			
		||||
                    // Kill previous process
 | 
			
		||||
                    if let Some(mut child) = server_process_clone.lock().unwrap().take() {
 | 
			
		||||
                        let _ = child.kill();
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Run new process
 | 
			
		||||
                    let mut cmd = Command::new("cargo");
 | 
			
		||||
                    cmd.args(&cmd_args);
 | 
			
		||||
                    cmd.stdout(Stdio::inherit())
 | 
			
		||||
                        .stderr(Stdio::inherit());
 | 
			
		||||
                    let child = cmd.spawn().expect("Failed to spawn");
 | 
			
		||||
 | 
			
		||||
                    *server_process_clone.lock().unwrap() = Some(child);
 | 
			
		||||
 | 
			
		||||
                    // Notify browser
 | 
			
		||||
                    let _ = tx.send(());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }).expect("watcher failed");
 | 
			
		||||
 | 
			
		||||
        // Add watches
 | 
			
		||||
        for path in &watch_paths {
 | 
			
		||||
            watcher.watch(path, RecursiveMode::Recursive).unwrap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        loop {
 | 
			
		||||
            std::thread::sleep(std::time::Duration::from_secs(3600));
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    println!("🔁 Watching paths: {:?}", args.watch);
 | 
			
		||||
    println!("🌐 Connect browser to ws://localhost:35729");
 | 
			
		||||
    loop {
 | 
			
		||||
        tokio::time::sleep(tokio::time::Duration::from_secs(3600)).await;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn start_websocket_server(tx: broadcast::Sender<()>) {
 | 
			
		||||
    let listener = TcpListener::bind("127.0.0.1:35729").await.unwrap();
 | 
			
		||||
 | 
			
		||||
    while let Ok((stream, _)) = listener.accept().await {
 | 
			
		||||
        let tx = tx.clone();
 | 
			
		||||
        let mut rx = tx.subscribe();
 | 
			
		||||
 | 
			
		||||
        tokio::spawn(async move {
 | 
			
		||||
            let ws_stream = accept_async(stream).await.unwrap();
 | 
			
		||||
            let (mut write, _) = ws_stream.split();
 | 
			
		||||
 | 
			
		||||
            while rx.recv().await.is_ok() {
 | 
			
		||||
                let _ = write.send(tokio_tungstenite::tungstenite::Message::Text("reload".into())).await;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										333
									
								
								_archive/devtools/reloadd/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								_archive/devtools/reloadd/src/main.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,333 @@
 | 
			
		||||
use clap::{Parser};
 | 
			
		||||
use notify::{RecursiveMode, Watcher, EventKind, recommended_watcher};
 | 
			
		||||
use std::sync::{Arc, Mutex};
 | 
			
		||||
use std::process::{Command, Child, Stdio};
 | 
			
		||||
use tokio::net::TcpStream;
 | 
			
		||||
use tokio::sync::broadcast;
 | 
			
		||||
use tokio_tungstenite::accept_async;
 | 
			
		||||
use futures_util::{SinkExt, StreamExt};
 | 
			
		||||
use tokio::time::{sleep, Duration};
 | 
			
		||||
use tokio::io::AsyncWriteExt;
 | 
			
		||||
use std::net::TcpListener as StdTcpListener;
 | 
			
		||||
use std::net::SocketAddr;
 | 
			
		||||
 | 
			
		||||
#[derive(Parser, Debug)]
 | 
			
		||||
#[command(author, version, about)]
 | 
			
		||||
struct Args {
 | 
			
		||||
    /// Paths to watch (like src/, templates/, scripts/)
 | 
			
		||||
    #[arg(short, long, value_name = "PATH", num_args = 1.., required = true)]
 | 
			
		||||
    watch: Vec<String>,
 | 
			
		||||
 | 
			
		||||
    /// Port for the web server (default: 8080)
 | 
			
		||||
    #[arg(short, long, default_value = "8080")]
 | 
			
		||||
    port: u16,
 | 
			
		||||
 | 
			
		||||
    /// Multiple commands to run (format: --run "cargo build" --run "cargo test")
 | 
			
		||||
    #[arg(short, long, value_name = "COMMAND", num_args = 1..)]
 | 
			
		||||
    run: Vec<String>,
 | 
			
		||||
 | 
			
		||||
    /// Command to run on change (like: -- run --example server)
 | 
			
		||||
    /// This is kept for backward compatibility
 | 
			
		||||
    #[arg(last = true)]
 | 
			
		||||
    command: Vec<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Run commands in sequence, with the last one potentially being a long-running server
 | 
			
		||||
async fn run_commands_in_sequence(commands: &[Vec<String>], server_process: Arc<Mutex<Option<Child>>>) -> bool {
 | 
			
		||||
    // Run all commands in sequence
 | 
			
		||||
    for (i, command) in commands.iter().enumerate() {
 | 
			
		||||
        let is_last = i == commands.len() - 1;
 | 
			
		||||
        let program = if command[0] == "cargo" || command[0].ends_with("/cargo") {
 | 
			
		||||
            "cargo".to_string()
 | 
			
		||||
        } else {
 | 
			
		||||
            command[0].clone()
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        let args = if command[0] == "cargo" || command[0].ends_with("/cargo") {
 | 
			
		||||
            command.iter().skip(1).cloned().collect::<Vec<String>>()
 | 
			
		||||
        } else {
 | 
			
		||||
            command.iter().skip(1).cloned().collect::<Vec<String>>()
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        let mut cmd = Command::new(&program);
 | 
			
		||||
        cmd.args(&args);
 | 
			
		||||
        cmd.stdout(Stdio::inherit())
 | 
			
		||||
            .stderr(Stdio::inherit());
 | 
			
		||||
        
 | 
			
		||||
        if is_last {
 | 
			
		||||
            // Last command might be a long-running server
 | 
			
		||||
            match cmd.spawn() {
 | 
			
		||||
                Ok(child) => {
 | 
			
		||||
                    *server_process.lock().unwrap() = Some(child);
 | 
			
		||||
                    println!("🚀 Started server process: {} {}", program, args.join(" "));
 | 
			
		||||
                },
 | 
			
		||||
                Err(e) => {
 | 
			
		||||
                    eprintln!("❌ Failed to start server process: {}", e);
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // For non-last commands, wait for them to complete
 | 
			
		||||
            println!("🔄 Running build step: {} {}", program, args.join(" "));
 | 
			
		||||
            match cmd.output() {
 | 
			
		||||
                Ok(output) => {
 | 
			
		||||
                    if !output.status.success() {
 | 
			
		||||
                        eprintln!("❌ Command failed: {} {}", program, args.join(" "));
 | 
			
		||||
                        return false;
 | 
			
		||||
                    }
 | 
			
		||||
                    println!("✅ Command completed successfully");
 | 
			
		||||
                },
 | 
			
		||||
                Err(e) => {
 | 
			
		||||
                    eprintln!("❌ Failed to execute command: {}", e);
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check if a port is already in use
 | 
			
		||||
fn is_port_in_use(port: u16) -> bool {
 | 
			
		||||
    StdTcpListener::bind(format!("127.0.0.1:{}", port)).is_err()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Find an available port starting from the given port
 | 
			
		||||
fn find_available_port(start_port: u16) -> Option<u16> {
 | 
			
		||||
    let mut port = start_port;
 | 
			
		||||
    // Try up to 10 ports (start_port through start_port+9)
 | 
			
		||||
    for _ in 0..10 {
 | 
			
		||||
        if !is_port_in_use(port) {
 | 
			
		||||
            return Some(port);
 | 
			
		||||
        }
 | 
			
		||||
        port += 1;
 | 
			
		||||
    }
 | 
			
		||||
    None
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Generate LiveReload script with the given WebSocket port
 | 
			
		||||
fn generate_livereload_script(ws_port: u16) -> String {
 | 
			
		||||
    format!(
 | 
			
		||||
        r#"<script>
 | 
			
		||||
const ws = new WebSocket("ws://localhost:{}");
 | 
			
		||||
ws.onmessage = (msg) => {{
 | 
			
		||||
  if (msg.data === "reload") location.reload();
 | 
			
		||||
}};
 | 
			
		||||
</script>"#,
 | 
			
		||||
        ws_port
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() {
 | 
			
		||||
    let args = Args::parse();
 | 
			
		||||
    
 | 
			
		||||
    // Handle both the new --run and legacy command format
 | 
			
		||||
    let commands = if !args.run.is_empty() {
 | 
			
		||||
        // New format: each --run is a separate command
 | 
			
		||||
        args.run.iter().map(|cmd| {
 | 
			
		||||
            // Split the command string into arguments
 | 
			
		||||
            cmd.split_whitespace().map(String::from).collect::<Vec<String>>()
 | 
			
		||||
        }).collect::<Vec<Vec<String>>>()
 | 
			
		||||
    } else if !args.command.is_empty() {
 | 
			
		||||
        // Legacy format: single command from the trailing arguments
 | 
			
		||||
        println!("Command: {:?}", args.command);
 | 
			
		||||
        vec![args.command.clone()]
 | 
			
		||||
    } else {
 | 
			
		||||
        // No commands provided
 | 
			
		||||
        eprintln!("❌ Error: No commands provided. Use --run or trailing arguments.");
 | 
			
		||||
        std::process::exit(1);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Check if server port is already in use
 | 
			
		||||
    let server_port = args.port;
 | 
			
		||||
    if is_port_in_use(server_port) {
 | 
			
		||||
        eprintln!("❌ Error: Port {} is already in use. Stop any running instances before starting a new one.", server_port);
 | 
			
		||||
        std::process::exit(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Find an available WebSocket port
 | 
			
		||||
    let ws_port = match find_available_port(35729) {
 | 
			
		||||
        Some(port) => port,
 | 
			
		||||
        None => {
 | 
			
		||||
            eprintln!("❌ Error: Could not find an available WebSocket port. Please free up some ports and try again.");
 | 
			
		||||
            std::process::exit(1);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let (tx, _) = broadcast::channel::<()>(10);
 | 
			
		||||
    let tx_ws = tx.clone();
 | 
			
		||||
    let server_process = Arc::new(Mutex::new(None::<Child>));
 | 
			
		||||
 | 
			
		||||
    // Validate paths before starting the watcher
 | 
			
		||||
    let mut invalid_paths = Vec::new();
 | 
			
		||||
    for path in &args.watch {
 | 
			
		||||
        if !std::path::Path::new(path).exists() {
 | 
			
		||||
            invalid_paths.push(path.clone());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if !invalid_paths.is_empty() {
 | 
			
		||||
        eprintln!("❌ Error: The following watch paths do not exist:");
 | 
			
		||||
        for path in invalid_paths {
 | 
			
		||||
            eprintln!("   - {}", path);
 | 
			
		||||
        }
 | 
			
		||||
        eprintln!("Please provide valid paths to watch.");
 | 
			
		||||
        std::process::exit(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Start WebSocket reload server
 | 
			
		||||
    match start_websocket_server(tx_ws.clone(), ws_port).await {
 | 
			
		||||
        Ok(_) => {},
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            eprintln!("❌ Failed to start WebSocket server: {}", e);
 | 
			
		||||
            std::process::exit(1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Output the LiveReload script for users to add to their projects
 | 
			
		||||
    println!("📋 Add this script to your HTML for live reloading:");
 | 
			
		||||
    println!("{}", generate_livereload_script(ws_port));
 | 
			
		||||
 | 
			
		||||
    // 🚀 Run all commands in sequence
 | 
			
		||||
    if !run_commands_in_sequence(&commands, Arc::clone(&server_process)).await {
 | 
			
		||||
        eprintln!("❌ Command execution failed.");
 | 
			
		||||
        std::process::exit(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    println!("🔁 Watching paths: {:?}", args.watch);
 | 
			
		||||
    println!("🌐 Connect browser to ws://localhost:{}", ws_port);
 | 
			
		||||
    println!("🖥️  Web server running on http://localhost:{}", server_port);
 | 
			
		||||
 | 
			
		||||
    // Create a runtime handle for the watcher to use
 | 
			
		||||
    let rt_handle = tokio::runtime::Handle::current();
 | 
			
		||||
    
 | 
			
		||||
    // Clone necessary values before moving into the watcher thread
 | 
			
		||||
    let watch_paths = args.watch.clone();
 | 
			
		||||
    let commands_clone = commands.clone();
 | 
			
		||||
    let server_process_clone = Arc::clone(&server_process);
 | 
			
		||||
    let tx_clone = tx.clone();
 | 
			
		||||
 | 
			
		||||
    // Create a dedicated thread for the file watcher
 | 
			
		||||
    let watcher_thread = std::thread::spawn(move || {
 | 
			
		||||
        // Create a new watcher
 | 
			
		||||
        let mut watcher = match recommended_watcher(move |res: notify::Result<notify::Event>| {
 | 
			
		||||
            if let Ok(event) = res {
 | 
			
		||||
                if matches!(event.kind, EventKind::Modify(_) | EventKind::Create(_) | EventKind::Remove(_)) {
 | 
			
		||||
                    println!("📦 Change detected, restarting...");
 | 
			
		||||
                    
 | 
			
		||||
                    // Kill previous process
 | 
			
		||||
                    if let Some(mut child) = server_process_clone.lock().unwrap().take() {
 | 
			
		||||
                        let _ = child.kill();
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    // Run new process (only the primary command gets restarted)
 | 
			
		||||
                    if let Some(first_command) = commands.first() {
 | 
			
		||||
                        let program = if first_command[0] == "cargo" || first_command[0].ends_with("/cargo") {
 | 
			
		||||
                            "cargo".to_string()
 | 
			
		||||
                        } else {
 | 
			
		||||
                            first_command[0].clone()
 | 
			
		||||
                        };
 | 
			
		||||
                        
 | 
			
		||||
                        let args = if first_command[0] == "cargo" || first_command[0].ends_with("/cargo") {
 | 
			
		||||
                            first_command.iter().skip(1).cloned().collect::<Vec<String>>()
 | 
			
		||||
                        } else {
 | 
			
		||||
                            first_command.iter().skip(1).cloned().collect::<Vec<String>>()
 | 
			
		||||
                        };
 | 
			
		||||
                        
 | 
			
		||||
                        let mut cmd = Command::new(&program);
 | 
			
		||||
                        cmd.args(&args);
 | 
			
		||||
                        cmd.stdout(Stdio::inherit())
 | 
			
		||||
                            .stderr(Stdio::inherit());
 | 
			
		||||
                        
 | 
			
		||||
                        match cmd.spawn() {
 | 
			
		||||
                            Ok(child) => {
 | 
			
		||||
                                *server_process_clone.lock().unwrap() = Some(child);
 | 
			
		||||
                                
 | 
			
		||||
                                // Immediately send reload signal without waiting for server
 | 
			
		||||
                                let tx = tx_clone.clone();
 | 
			
		||||
                                rt_handle.spawn(async move {
 | 
			
		||||
                                    // Small delay to give the server a moment to start
 | 
			
		||||
                                    sleep(Duration::from_millis(500)).await;
 | 
			
		||||
                                    // Notify browser to reload
 | 
			
		||||
                                    let _ = tx.send(());
 | 
			
		||||
                                });
 | 
			
		||||
                            },
 | 
			
		||||
                            Err(e) => {
 | 
			
		||||
                                eprintln!("❌ Failed to spawn process: {}", e);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else if let Err(e) = res {
 | 
			
		||||
                eprintln!("❌ Watch error: {}", e);
 | 
			
		||||
            }
 | 
			
		||||
        }) {
 | 
			
		||||
            Ok(w) => w,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                eprintln!("❌ Failed to create watcher: {}", e);
 | 
			
		||||
                std::process::exit(1);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        // Add watches
 | 
			
		||||
        for path in &watch_paths {
 | 
			
		||||
            match watcher.watch(path.as_ref(), RecursiveMode::Recursive) {
 | 
			
		||||
                Ok(_) => println!("👁️  Watching path: {}", path),
 | 
			
		||||
                Err(e) => {
 | 
			
		||||
                    eprintln!("❌ Failed to watch path '{}': {}", path, e);
 | 
			
		||||
                    std::process::exit(1);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Keep the thread alive
 | 
			
		||||
        loop {
 | 
			
		||||
            std::thread::sleep(std::time::Duration::from_secs(3600));
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    // Wait for the watcher thread to finish (it shouldn't unless there's an error)
 | 
			
		||||
    match watcher_thread.join() {
 | 
			
		||||
        Ok(_) => {},
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            eprintln!("❌ Watcher thread panicked: {:?}", e);
 | 
			
		||||
            std::process::exit(1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn start_websocket_server(tx: broadcast::Sender<()>, ws_port: u16) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
 | 
			
		||||
    let addr = format!("127.0.0.1:{}", ws_port);
 | 
			
		||||
    let listener = tokio::net::TcpListener::bind(&addr).await?;
 | 
			
		||||
    println!("WebSocket server started on ws://localhost:{}", ws_port);
 | 
			
		||||
 | 
			
		||||
    // Spawn a task to handle WebSocket connections
 | 
			
		||||
    tokio::spawn(async move {
 | 
			
		||||
        while let Ok((stream, addr)) = listener.accept().await {
 | 
			
		||||
            println!("New WebSocket connection from: {}", addr);
 | 
			
		||||
            let tx = tx.clone();
 | 
			
		||||
            let mut rx = tx.subscribe();
 | 
			
		||||
 | 
			
		||||
            tokio::spawn(async move {
 | 
			
		||||
                match accept_async(stream).await {
 | 
			
		||||
                    Ok(ws_stream) => {
 | 
			
		||||
                        let (mut write, _) = ws_stream.split();
 | 
			
		||||
                        
 | 
			
		||||
                        while rx.recv().await.is_ok() {
 | 
			
		||||
                            if let Err(e) = write.send(tokio_tungstenite::tungstenite::Message::Text("reload".into())).await {
 | 
			
		||||
                                eprintln!("❌ Error sending reload message to client {}: {}", addr, e);
 | 
			
		||||
                                break;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    Err(e) => {
 | 
			
		||||
                        eprintln!("❌ Failed to accept WebSocket connection from {}: {}", addr, e);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user