Skip to main content

arc_web/commands/
develop.rs

1use std::path::PathBuf;
2use std::process::ExitStatus;
3use std::{fs, io};
4use tokio::io::{AsyncBufReadExt, BufReader, Lines};
5use tokio::process::{ChildStderr, ChildStdout, Command};
6use tokio::task::JoinHandle;
7use tokio::try_join;
8use tracing::{debug, error, info};
9
10/// Starts the development environment with `cargo-watch` for Rust hot-reloading
11/// and Vite for frontend asset bundling, running both concurrently.
12pub async fn run_development() -> io::Result<()> {
13    info!("Starting development mode");
14
15    let cargo_watch_task: JoinHandle<io::Result<()>> = tokio::spawn(run_cargo_watch());
16    let bundle_task: JoinHandle<io::Result<()>> = tokio::spawn(run_vite_bundle());
17
18    match try_join!(cargo_watch_task, bundle_task) {
19        Ok(_) => {
20            info!("Development environment running successfully");
21            Ok(())
22        }
23        Err(e) => {
24            error!("Development environment error: {:?}", e);
25            Err(io::Error::other("Failed to run development tasks"))
26        }
27    }
28}
29
30async fn run_cargo_watch() -> io::Result<()> {
31    let mut cargo_watch_process = Command::new("cargo")
32        .arg("watch")
33        .arg("-x")
34        .arg("run serve")
35        .arg("-i")
36        .arg("database/*")
37        .arg("-i")
38        .arg("dist/*")
39        .arg("-i")
40        .arg("node_modules/*")
41        .stdout(std::process::Stdio::piped())
42        .stderr(std::process::Stdio::piped())
43        .spawn()
44        .expect("Failed to start Cargo Watch");
45
46    let stdout = cargo_watch_process
47        .stdout
48        .take()
49        .expect("Failed to capture stdout");
50    let stderr = cargo_watch_process
51        .stderr
52        .take()
53        .expect("Failed to capture stderr");
54
55    let stdout_task = tokio::spawn(async move {
56        let mut reader: Lines<BufReader<ChildStdout>> = BufReader::new(stdout).lines();
57        while let Ok(Some(line)) = reader.next_line().await {
58            debug!("[cargo-watch] {}", line);
59        }
60    });
61
62    let stderr_task = tokio::spawn(async move {
63        let mut reader: Lines<BufReader<ChildStderr>> = BufReader::new(stderr).lines();
64        while let Ok(Some(line)) = reader.next_line().await {
65            info!("[cargo-watch] {}", line);
66        }
67    });
68
69    let status = cargo_watch_process
70        .wait()
71        .await
72        .expect("Cargo Watch process wasn't running");
73
74    stdout_task.await.expect("Failed to handle stdout");
75    stderr_task.await.expect("Failed to handle stderr");
76
77    if !status.success() {
78        return Err(io::Error::other(format!(
79            "Cargo Watch process exited with status: {:?}",
80            status
81        )));
82    }
83
84    Ok(())
85}
86
87async fn run_vite_bundle() -> io::Result<()> {
88    if !fs::exists(PathBuf::from("node_modules")).unwrap() {
89        let mut npm_install_process = Command::new("npm")
90            .arg("install")
91            .stdout(std::process::Stdio::piped())
92            .stderr(std::process::Stdio::piped())
93            .spawn()
94            .expect("Failed to install nodejs dependencies!");
95
96        let status: ExitStatus = npm_install_process
97            .wait()
98            .await
99            .expect("Npm Install wasn't running");
100
101        if !status.success() {
102            return Err(io::Error::other(format!(
103                "Npm Install process exited with status: {:?}",
104                status
105            )));
106        }
107    }
108
109    let mut vite_process = Command::new("npx")
110        .arg("vite")
111        .arg("build")
112        .arg("--watch")
113        .stdout(std::process::Stdio::piped())
114        .stderr(std::process::Stdio::piped())
115        .spawn()
116        .expect("Failed to start Vite dev server");
117
118    let stdout = vite_process
119        .stdout
120        .take()
121        .expect("Failed to capture stdout");
122    let stderr = vite_process
123        .stderr
124        .take()
125        .expect("Failed to capture stderr");
126
127    let stdout_task = tokio::spawn(async move {
128        let mut reader: Lines<BufReader<ChildStdout>> = BufReader::new(stdout).lines();
129        while let Ok(Some(line)) = reader.next_line().await {
130            debug!("[vite] {}", line);
131        }
132    });
133
134    let stderr_task: JoinHandle<()> = tokio::spawn(async move {
135        let mut reader: Lines<BufReader<ChildStderr>> = BufReader::new(stderr).lines();
136        while let Ok(Some(line)) = reader.next_line().await {
137            info!("[vite] {}", line);
138        }
139    });
140
141    let status: ExitStatus = vite_process
142        .wait()
143        .await
144        .expect("Vite process wasn't running");
145
146    stdout_task.await.expect("Failed to handle stdout");
147    stderr_task.await.expect("Failed to handle stderr");
148
149    if !status.success() {
150        return Err(io::Error::other(format!(
151            "Vite process exited with status: {:?}",
152            status
153        )));
154    }
155
156    Ok(())
157}