From 560980d9d788f0756732e0587c664b37c6693a08 Mon Sep 17 00:00:00 2001 From: geoffsee <> Date: Mon, 25 Aug 2025 15:24:10 -0400 Subject: [PATCH] refactor(core): Improve code readability and structure - Format code uniformly, applying consistent indentation and spacing - Restructure enum, function, and match-case formatting for clarity - Remove redundant `defaults` from CI workflows --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 11 +- crates/muxox/src/main.rs | 280 ++++++++++++++---- crates/muxox/tests/config_tests.rs | 24 +- crates/muxox/tests/platform_specific_tests.rs | 52 ++-- muxox.toml | 10 - 6 files changed, 268 insertions(+), 111 deletions(-) delete mode 100644 muxox.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe6c483..60117f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: include: - - name: default (native-tls) + - name: default features: "" no-default-features: false steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0574ca1..836573c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,9 +12,6 @@ jobs: docs: name: Build and validate documentation runs-on: ubuntu-latest - defaults: - run: - working-directory: crates/muxox strategy: fail-fast: false matrix: @@ -70,14 +67,11 @@ jobs: name: Test before release runs-on: ubuntu-latest needs: docs - defaults: - run: - working-directory: crates/muxox strategy: fail-fast: false matrix: include: - - name: default (native-tls) + - name: default features: "" no-default-features: false @@ -129,9 +123,6 @@ jobs: permissions: id-token: write # Required for OIDC token exchange https://crates.io/docs/trusted-publishing needs: test - defaults: - run: - working-directory: crates/muxox steps: - name: Checkout uses: actions/checkout@v4 diff --git a/crates/muxox/src/main.rs b/crates/muxox/src/main.rs index 9d71f6b..2db122b 100644 --- a/crates/muxox/src/main.rs +++ b/crates/muxox/src/main.rs @@ -1,7 +1,6 @@ use std::{ collections::VecDeque, - fs, - io, + fs, io, path::{Path, PathBuf}, process::{Child, Command, Stdio}, time::Duration, @@ -12,17 +11,17 @@ use clap::Parser; use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyModifiers}; use crossterm::terminal::{disable_raw_mode, enable_raw_mode}; use ratatui::{ + Terminal, backend::CrosstermBackend, layout::{Constraint, Direction, Layout}, style::{Color, Modifier, Style}, text::{Line, Span}, widgets::{Block, Borders, List, ListItem, Paragraph, Wrap}, - Terminal, }; use serde::Deserialize; -use tokio::{io::AsyncBufReadExt, process::Command as AsyncCommand, sync::mpsc, task, time}; #[cfg(windows)] use std::os::windows::process::CommandExt as _; +use tokio::{io::AsyncBufReadExt, process::Command as AsyncCommand, sync::mpsc, task, time}; #[derive(Debug, Parser)] #[command(author, version, about = "Run multiple dev servers with a simple TUI.")] @@ -46,7 +45,9 @@ struct ServiceCfg { #[serde(default = "default_log_capacity")] log_capacity: usize, } -fn default_log_capacity() -> usize { 2000 } +fn default_log_capacity() -> usize { + 2000 +} #[cfg(test)] mod tests { @@ -102,12 +103,12 @@ mod tests { let cfg: Config = toml::from_str(toml_input).expect("Valid Config"); assert_eq!(cfg.service.len(), 2); - + assert_eq!(cfg.service[0].name, "frontend"); assert_eq!(cfg.service[0].cmd, "pnpm client:dev"); assert_eq!(cfg.service[0].cwd, Some(PathBuf::from("./"))); assert_eq!(cfg.service[0].log_capacity, 5000); - + assert_eq!(cfg.service[1].name, "backend"); assert_eq!(cfg.service[1].cmd, "pnpm server:dev"); assert_eq!(cfg.service[1].cwd, Some(PathBuf::from("./"))); @@ -124,24 +125,24 @@ mod tests { }; let mut state = ServiceState::new(cfg.clone()); - + // Test initial state assert_eq!(state.status, Status::Stopped); assert!(state.child.is_none()); assert_eq!(state.log.len(), 0); // The ServiceState::new function enforces a minimum capacity of 256 assert_eq!(state.log.capacity(), 256); - + // Test log functionality state.push_log("line 1"); state.push_log("line 2"); assert_eq!(state.log.len(), 2); - + // Test log capacity for i in 3..=12 { state.push_log(format!("line {}", i)); } - + // Should have removed oldest entries to stay within capacity assert_eq!(state.log.len(), 10); assert_eq!(state.log.front().unwrap(), "line 3"); @@ -150,7 +151,12 @@ mod tests { } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum Status { Stopped, Starting, Running, Stopping } +enum Status { + Stopped, + Starting, + Running, + Stopping, +} #[derive(Debug)] struct ServiceState { @@ -170,7 +176,9 @@ impl ServiceState { } } fn push_log(&mut self, line: impl Into) { - if self.log.len() == self.cfg.log_capacity { self.log.pop_front(); } + if self.log.len() == self.cfg.log_capacity { + self.log.pop_front(); + } self.log.push_back(line.into()); } } @@ -196,7 +204,11 @@ async fn main() -> Result<()> { let cfg = load_config(cli.config.as_deref())?; let (tx, mut rx) = mpsc::unbounded_channel::(); - let mut app = App { services: cfg.service.into_iter().map(ServiceState::new).collect(), selected: 0, tx: tx.clone() }; + let mut app = App { + services: cfg.service.into_iter().map(ServiceState::new).collect(), + selected: 0, + tx: tx.clone(), + }; // Signal watcher: on any exit signal, nuke children then exit. task::spawn(signal_watcher(tx.clone())); @@ -204,7 +216,11 @@ async fn main() -> Result<()> { // TUI setup enable_raw_mode()?; let mut stdout = io::stdout(); - crossterm::execute!(stdout, crossterm::terminal::EnterAlternateScreen, crossterm::event::EnableMouseCapture)?; + crossterm::execute!( + stdout, + crossterm::terminal::EnterAlternateScreen, + crossterm::event::EnableMouseCapture + )?; let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; @@ -225,10 +241,14 @@ async fn main() -> Result<()> { } } if handled { /* app mutated, redraw next loop */ } - if last_tick.elapsed() >= tick_rate { last_tick = time::Instant::now(); } + if last_tick.elapsed() >= tick_rate { + last_tick = time::Instant::now(); + } // Drain channel - while let Ok(msg) = rx.try_recv() { apply_msg(&mut app, msg); } + while let Ok(msg) = rx.try_recv() { + apply_msg(&mut app, msg); + } } }); @@ -243,15 +263,41 @@ fn draw_ui(f: &mut ratatui::Frame, app: &App) { .constraints([Constraint::Percentage(30), Constraint::Percentage(70)]) .split(f.area()); - let items: Vec = app.services.iter().enumerate().map(|(_i, s)| { - let status = match s.status { Status::Stopped=>"●", Status::Starting=>"◔", Status::Running=>"◉", Status::Stopping=>"◑" }; - let color = match s.status { Status::Running=>Color::Green, Status::Starting=>Color::Yellow, Status::Stopping=>Color::Magenta, Status::Stopped=>Color::DarkGray }; - ListItem::new(Line::from(vec![Span::styled(format!(" {status} "), Style::default().fg(color)), Span::raw(&s.cfg.name)])) - }).collect(); + let items: Vec = app + .services + .iter() + .enumerate() + .map(|(_i, s)| { + let status = match s.status { + Status::Stopped => "●", + Status::Starting => "◔", + Status::Running => "◉", + Status::Stopping => "◑", + }; + let color = match s.status { + Status::Running => Color::Green, + Status::Starting => Color::Yellow, + Status::Stopping => Color::Magenta, + Status::Stopped => Color::DarkGray, + }; + ListItem::new(Line::from(vec![ + Span::styled(format!(" {status} "), Style::default().fg(color)), + Span::raw(&s.cfg.name), + ])) + }) + .collect(); let list = List::new(items) - .block(Block::default().title("Services (↑/↓ select, Enter start/stop, r restart, c clear, q quit)").borders(Borders::ALL)) - .highlight_style(Style::default().add_modifier(Modifier::BOLD).bg(Color::DarkGray)); + .block( + Block::default() + .title("Services (↑/↓ select, Enter start/stop, r restart, c clear, q quit)") + .borders(Borders::ALL), + ) + .highlight_style( + Style::default() + .add_modifier(Modifier::BOLD) + .bg(Color::DarkGray), + ); f.render_stateful_widget(list, chunks[0], &mut list_state(app.selected)); @@ -263,11 +309,31 @@ fn draw_ui(f: &mut ratatui::Frame, app: &App) { let selected = &app.services[app.selected]; let header = Paragraph::new(vec![ - Line::from(vec![Span::styled("Name: ", Style::default().fg(Color::DarkGray)), Span::raw(&selected.cfg.name)]), - Line::from(vec![Span::styled("Cmd: ", Style::default().fg(Color::DarkGray)), Span::raw(&selected.cfg.cmd)]), - Line::from(vec![Span::styled("Cwd: ", Style::default().fg(Color::DarkGray)), Span::raw(selected.cfg.cwd.as_ref().and_then(|p| p.to_str()).unwrap_or("."))]), + Line::from(vec![ + Span::styled("Name: ", Style::default().fg(Color::DarkGray)), + Span::raw(&selected.cfg.name), + ]), + Line::from(vec![ + Span::styled("Cmd: ", Style::default().fg(Color::DarkGray)), + Span::raw(&selected.cfg.cmd), + ]), + Line::from(vec![ + Span::styled("Cwd: ", Style::default().fg(Color::DarkGray)), + Span::raw( + selected + .cfg + .cwd + .as_ref() + .and_then(|p| p.to_str()) + .unwrap_or("."), + ), + ]), ]) - .block(Block::default().title("Selected Service").borders(Borders::ALL)); + .block( + Block::default() + .title("Selected Service") + .borders(Borders::ALL), + ); let log_text: Vec = selected.log.iter().map(|l| Line::from(l.clone())).collect(); let log = Paragraph::new(log_text) @@ -291,11 +357,28 @@ fn handle_key(k: KeyEvent, app: &mut App) -> bool { cleanup_and_exit(app); return true; } - (KeyCode::Down, _) => { app.selected = (app.selected + 1).min(app.services.len()-1); return true; } - (KeyCode::Up, _) => { if app.selected>0 { app.selected -= 1; } return true; } - (KeyCode::Enter, _) | (KeyCode::Char(' '), _) => { toggle_selected(app); return true; } - (KeyCode::Char('r'), _) => { restart_selected(app); return true; } - (KeyCode::Char('c'), _) if k.modifiers == KeyModifiers::NONE => { app.services[app.selected].log.clear(); return true; } + (KeyCode::Down, _) => { + app.selected = (app.selected + 1).min(app.services.len() - 1); + return true; + } + (KeyCode::Up, _) => { + if app.selected > 0 { + app.selected -= 1; + } + return true; + } + (KeyCode::Enter, _) | (KeyCode::Char(' '), _) => { + toggle_selected(app); + return true; + } + (KeyCode::Char('r'), _) => { + restart_selected(app); + return true; + } + (KeyCode::Char('c'), _) if k.modifiers == KeyModifiers::NONE => { + app.services[app.selected].log.clear(); + return true; + } _ => {} } false @@ -303,13 +386,27 @@ fn handle_key(k: KeyEvent, app: &mut App) -> bool { fn toggle_selected(app: &mut App) { let idx = app.selected; - match app.services[idx].status { Status::Stopped => { start_service(idx, app); }, Status::Running | Status::Starting => { stop_service(idx, app); }, Status::Stopping => {} } + match app.services[idx].status { + Status::Stopped => { + start_service(idx, app); + } + Status::Running | Status::Starting => { + stop_service(idx, app); + } + Status::Stopping => {} + } } -fn restart_selected(app: &mut App) { let idx = app.selected; stop_service(idx, app); start_service(idx, app); } +fn restart_selected(app: &mut App) { + let idx = app.selected; + stop_service(idx, app); + start_service(idx, app); +} fn start_service(idx: usize, app: &mut App) { - if matches!(app.services[idx].status, Status::Running | Status::Starting) { return; } + if matches!(app.services[idx].status, Status::Running | Status::Starting) { + return; + } app.services[idx].status = Status::Starting; let tx = app.tx.clone(); let sc = app.services[idx].cfg.clone(); @@ -317,7 +414,9 @@ fn start_service(idx: usize, app: &mut App) { // Build command under a shell let mut cmd = AsyncCommand::new(shell_program()); cmd.arg(shell_flag()).arg(shell_exec(&sc.cmd)); - if let Some(cwd) = sc.cwd.clone() { cmd.current_dir(cwd); } + if let Some(cwd) = sc.cwd.clone() { + cmd.current_dir(cwd); + } cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); set_process_group(&mut cmd); @@ -331,7 +430,9 @@ fn start_service(idx: usize, app: &mut App) { let tx2 = tx.clone(); task::spawn(async move { let mut reader = tokio::io::BufReader::new(out).lines(); - while let Ok(Some(line)) = reader.next_line().await { let _ = tx2.send(AppMsg::Log(idx, line)); } + while let Ok(Some(line)) = reader.next_line().await { + let _ = tx2.send(AppMsg::Log(idx, line)); + } }); } // stderr @@ -339,7 +440,9 @@ fn start_service(idx: usize, app: &mut App) { let tx2 = tx.clone(); task::spawn(async move { let mut reader = tokio::io::BufReader::new(err).lines(); - while let Ok(Some(line)) = reader.next_line().await { let _ = tx2.send(AppMsg::Log(idx, format!("[stderr] {line}"))); } + while let Ok(Some(line)) = reader.next_line().await { + let _ = tx2.send(AppMsg::Log(idx, format!("[stderr] {line}"))); + } }); } @@ -348,17 +451,24 @@ fn start_service(idx: usize, app: &mut App) { let code = status.map(|s| s.code().unwrap_or(-1)).unwrap_or(-1); let _ = tx.send(AppMsg::Stopped(idx, code)); } - Err(e) => { let _ = tx.send(AppMsg::Log(idx, format!("spawn failed: {e}"))); let _ = tx.send(AppMsg::Stopped(idx, -1)); } + Err(e) => { + let _ = tx.send(AppMsg::Log(idx, format!("spawn failed: {e}"))); + let _ = tx.send(AppMsg::Stopped(idx, -1)); + } } }); } fn stop_service(idx: usize, app: &mut App) { let sc = &mut app.services[idx]; - if !matches!(sc.status, Status::Running | Status::Starting) { return; } + if !matches!(sc.status, Status::Running | Status::Starting) { + return; + } sc.status = Status::Stopping; sc.push_log("Stopping..."); - if let Some(child) = sc.child.take() { drop(child); } // actual kill handled by kill_tree below + if let Some(child) = sc.child.take() { + drop(child); + } // actual kill handled by kill_tree below kill_tree(idx, app); } @@ -373,7 +483,9 @@ fn apply_msg(app: &mut App, msg: AppMsg) { s.status = Status::Stopped; s.push_log(format!("[exited: code {code}]").as_str()); } - AppMsg::Log(i, line) => { app.services[i].push_log(line); } + AppMsg::Log(i, line) => { + app.services[i].push_log(line); + } AppMsg::AbortedAll => { /* UI can optionally display something */ } } } @@ -382,7 +494,11 @@ fn cleanup_and_exit(app: &mut App) { // Restore terminal first to avoid leaving it raw if we panic later. let _ = disable_raw_mode(); let mut stdout = io::stdout(); - let _ = crossterm::execute!(stdout, crossterm::event::DisableMouseCapture, crossterm::terminal::LeaveAlternateScreen); + let _ = crossterm::execute!( + stdout, + crossterm::event::DisableMouseCapture, + crossterm::terminal::LeaveAlternateScreen + ); // Kill all children forcefully kill_all(app); @@ -391,7 +507,9 @@ fn cleanup_and_exit(app: &mut App) { } fn kill_all(app: &mut App) { - for i in 0..app.services.len() { kill_tree(i, app); } + for i in 0..app.services.len() { + kill_tree(i, app); + } } fn load_config(provided: Option<&Path>) -> Result { @@ -399,7 +517,9 @@ fn load_config(provided: Option<&Path>) -> Result { Some(p) => vec![p.to_path_buf()], None => { let mut v = vec![PathBuf::from("muxox.toml")]; - if let Some(proj) = directories::ProjectDirs::from("dev", "local", "devmux") { v.push(proj.config_dir().join("muxox.toml")); } + if let Some(proj) = directories::ProjectDirs::from("dev", "local", "devmux") { + v.push(proj.config_dir().join("muxox.toml")); + } v } }; @@ -414,7 +534,12 @@ fn load_config(provided: Option<&Path>) -> Result { #[cfg(unix)] fn set_process_group(cmd: &mut AsyncCommand) { - unsafe { cmd.pre_exec(|| { libc::setsid(); Ok(()) }) }; + unsafe { + cmd.pre_exec(|| { + libc::setsid(); + Ok(()) + }) + }; } #[cfg(windows)] fn set_process_group(cmd: &mut AsyncCommand) { @@ -425,31 +550,45 @@ fn set_process_group(cmd: &mut AsyncCommand) { #[cfg(unix)] fn shell_program() -> &'static str { - if std::env::var("SHELL").ok().filter(|s| !s.is_empty()).is_some() { + if std::env::var("SHELL") + .ok() + .filter(|s| !s.is_empty()) + .is_some() + { // Can't return a dynamically created String as &'static str // For simplicity, return a common shell path "/bin/bash" - } else { - "/bin/sh" + } else { + "/bin/sh" } } #[cfg(unix)] -fn shell_flag() -> &'static str { "-lc" } +fn shell_flag() -> &'static str { + "-lc" +} #[cfg(unix)] -fn shell_exec(cmd: &str) -> String { cmd.to_string() } +fn shell_exec(cmd: &str) -> String { + cmd.to_string() +} #[cfg(windows)] -fn shell_program() -> &'static str { "cmd.exe" } +fn shell_program() -> &'static str { + "cmd.exe" +} #[cfg(windows)] -fn shell_flag() -> &'static str { "/C" } +fn shell_flag() -> &'static str { + "/C" +} #[cfg(windows)] -fn shell_exec(cmd: &str) -> String { cmd.to_string() } +fn shell_exec(cmd: &str) -> String { + cmd.to_string() +} #[cfg(unix)] fn kill_tree(idx: usize, app: &mut App) { // These imports are left as warnings intentionally since they might be needed // if we implement more advanced process group management in the future - use nix::sys::signal::{killpg, Signal}; + use nix::sys::signal::{Signal, killpg}; use nix::unistd::Pid; let name = app.services[idx].cfg.name.clone(); // We don't track exact pgid; setsid() made child leader so killpg(-pid) works if we had it. @@ -457,9 +596,17 @@ fn kill_tree(idx: usize, app: &mut App) { // Simpler: store no pid; rely on pkill -f cmd as fallback. let cmdline = &app.services[idx].cfg.cmd; // best-effort group kill via pkill - let _ = Command::new("pkill").arg("-TERM").arg("-f").arg(cmdline).status(); + let _ = Command::new("pkill") + .arg("-TERM") + .arg("-f") + .arg(cmdline) + .status(); std::thread::sleep(Duration::from_millis(250)); - let _ = Command::new("pkill").arg("-KILL").arg("-f").arg(cmdline).status(); + let _ = Command::new("pkill") + .arg("-KILL") + .arg("-f") + .arg(cmdline) + .status(); app.services[idx].push_log(format!("[killed {name}]")); } @@ -467,21 +614,24 @@ fn kill_tree(idx: usize, app: &mut App) { fn kill_tree(idx: usize, app: &mut App) { let name = app.services[idx].cfg.name.clone(); // Use taskkill to nuke the subtree - let _ = Command::new("taskkill").args(["/F","/T","/FI"]).arg(format!("WINDOWTITLE eq {}", name)).status(); + let _ = Command::new("taskkill") + .args(["/F", "/T", "/FI"]) + .arg(format!("WINDOWTITLE eq {}", name)) + .status(); // fallback: taskkill by image name is too coarse; skip app.services[idx].push_log(format!("[killed {name}]")); } #[cfg(unix)] async fn signal_watcher(tx: mpsc::UnboundedSender) { - use tokio::signal::unix::{signal, SignalKind}; - + use tokio::signal::unix::{SignalKind, signal}; + // Create and pin futures let ctrlc = tokio::signal::ctrl_c(); let sigterm = signal(SignalKind::terminate()).expect("sigterm"); - + tokio::pin!(ctrlc, sigterm); - + // For Unix systems, wait for either Ctrl-C or SIGTERM loop { tokio::select! { @@ -496,11 +646,11 @@ async fn signal_watcher(tx: mpsc::UnboundedSender) { // Create and pin futures let ctrlc = tokio::signal::ctrl_c(); tokio::pin!(ctrlc); - + // For non-Unix systems, just wait for Ctrl-C loop { tokio::select! { _ = &mut ctrlc => { let _=tx.send(AppMsg::AbortedAll); break; } } } -} \ No newline at end of file +} diff --git a/crates/muxox/tests/config_tests.rs b/crates/muxox/tests/config_tests.rs index 4443083..d4ecc30 100644 --- a/crates/muxox/tests/config_tests.rs +++ b/crates/muxox/tests/config_tests.rs @@ -95,7 +95,8 @@ fn empty_service_array_is_valid() { // A config with an empty service array is valid let toml_input = "service = []"; - let cfg: TestConfig = toml::from_str(toml_input).expect("config with empty service array should be valid"); + let cfg: TestConfig = + toml::from_str(toml_input).expect("config with empty service array should be valid"); assert_eq!(cfg.services.len(), 0); } @@ -112,17 +113,26 @@ fn missing_service_field_is_invalid() { fn parses_real_muxox_toml() { // Try to parse the actual muxox.toml file from the repository root let result = std::fs::read_to_string("../../muxox.toml"); - + if let Ok(contents) = result { let cfg: TestConfig = toml::from_str(&contents).expect("real muxox.toml should be valid"); - assert!(!cfg.services.is_empty(), "muxox.toml should have at least one service"); - + assert!( + !cfg.services.is_empty(), + "muxox.toml should have at least one service" + ); + // Verify it has the expected services (these assertions depend on the actual file content) let service_names: Vec<&str> = cfg.services.iter().map(|s| s.name.as_str()).collect(); - assert!(service_names.contains(&"frontend"), "Should have a frontend service"); - assert!(service_names.contains(&"backend"), "Should have a backend service"); + assert!( + service_names.contains(&"frontend"), + "Should have a frontend service" + ); + assert!( + service_names.contains(&"backend"), + "Should have a backend service" + ); } else { // Test is still valuable even if we can't find the real file println!("Note: Could not find ../../muxox.toml, skipping part of test"); } -} \ No newline at end of file +} diff --git a/crates/muxox/tests/platform_specific_tests.rs b/crates/muxox/tests/platform_specific_tests.rs index c598f98..91dc322 100644 --- a/crates/muxox/tests/platform_specific_tests.rs +++ b/crates/muxox/tests/platform_specific_tests.rs @@ -12,17 +12,20 @@ mod unix_tests { // This is a simple test to verify that we're on a Unix platform // The actual shell_program function is private in main.rs assert!(cfg!(unix), "This test should only run on Unix platforms"); - + // We can test that standard Unix directories exist - assert!(std::path::Path::new("/bin/sh").exists() || std::path::Path::new("/usr/bin/sh").exists(), - "Expected to find a shell at /bin/sh or /usr/bin/sh on Unix"); + assert!( + std::path::Path::new("/bin/sh").exists() + || std::path::Path::new("/usr/bin/sh").exists(), + "Expected to find a shell at /bin/sh or /usr/bin/sh on Unix" + ); } - + #[test] fn test_unix_signals() { // We can test that nix/signal functionality works as expected - use nix::sys::signal::{Signal, SigSet}; - + use nix::sys::signal::{SigSet, Signal}; + // Create a signal set and verify basic operations let mut set = SigSet::empty(); set.add(Signal::SIGTERM); @@ -35,7 +38,10 @@ mod unix_tests { mod windows_tests { #[test] fn test_windows_platform() { - assert!(cfg!(windows), "This test should only run on Windows platforms"); + assert!( + cfg!(windows), + "This test should only run on Windows platforms" + ); } } @@ -43,25 +49,31 @@ mod windows_tests { #[test] fn test_process_creation() { use std::process::Command; - + // A simple command that should work on any platform let output = if cfg!(windows) { Command::new("cmd").args(["/C", "echo hello"]).output() } else { Command::new("sh").args(["-c", "echo hello"]).output() }; - + // Verify we can create processes assert!(output.is_ok(), "Should be able to create a basic process"); - + if let Ok(output) = output { let stdout = String::from_utf8_lossy(&output.stdout); // On Windows, echo adds CRLF, on Unix just LF - let expected = if cfg!(windows) { "hello\r\n" } else { "hello\n" }; + let expected = if cfg!(windows) { + "hello\r\n" + } else { + "hello\n" + }; assert!(stdout.contains("hello"), "Expected 'hello' in output"); // Optionally, do a more precise check with the expected output format - assert!(stdout.trim() == "hello" || stdout == expected, - "Output should be exactly 'hello' with optional newline formatting"); + assert!( + stdout.trim() == "hello" || stdout == expected, + "Output should be exactly 'hello' with optional newline formatting" + ); } } @@ -70,11 +82,15 @@ fn test_process_creation() { fn test_environment_detection() { if cfg!(unix) { // Unix environment checks - assert!(std::path::Path::new("/").exists(), "Root directory should exist on Unix"); + assert!( + std::path::Path::new("/").exists(), + "Root directory should exist on Unix" + ); } else if cfg!(windows) { // Windows environment checks - assert!(std::path::Path::new("C:\\").exists() || - std::path::Path::new("D:\\").exists(), - "Expected to find C: or D: drive on Windows"); + assert!( + std::path::Path::new("C:\\").exists() || std::path::Path::new("D:\\").exists(), + "Expected to find C: or D: drive on Windows" + ); } -} \ No newline at end of file +} diff --git a/muxox.toml b/muxox.toml deleted file mode 100644 index f77ff45..0000000 --- a/muxox.toml +++ /dev/null @@ -1,10 +0,0 @@ -[[service]] -name = "frontend" -cmd = "pnpm client:dev" -cwd = "./" -log_capacity = 5000 - -[[service]] -name = "backend" -cmd = "pnpm server:dev" -cwd = "./" \ No newline at end of file