1use std::path::Path;
21use std::process::{Command, Stdio};
22
23use anyhow::{Context, Result, bail};
24
25use crate::cli::DevArgs;
26
27pub fn execute(args: &DevArgs) -> Result<()> {
29 let path = &args.path;
30
31 let is_cargo_project = path.is_dir() && path.join("Cargo.toml").exists();
33 let is_binary = path.is_file() && is_executable(path);
34
35 if !is_cargo_project && !is_binary {
36 bail!(
37 "Path '{}' is neither a Cargo project nor an executable binary.\n\
38 For a Cargo project, provide the directory containing Cargo.toml.\n\
39 For a binary, provide the path to the executable.",
40 path.display()
41 );
42 }
43
44 if args.inspector {
45 eprintln!(
46 "Warning: --inspector / --inspector-port are not yet implemented and will be ignored."
47 );
48 }
49
50 if args.watch {
51 run_with_watch(args, is_cargo_project)
52 } else if is_cargo_project {
53 run_cargo_project(args)
54 } else {
55 run_binary(args)
56 }
57}
58
59fn run_with_watch(args: &DevArgs, is_cargo_project: bool) -> Result<()> {
61 if !is_command_available("cargo-watch") {
63 eprintln!("cargo-watch is not installed. Install it with:");
64 eprintln!(" cargo install cargo-watch");
65 eprintln!();
66 eprintln!("Then run this command again.");
67 bail!("cargo-watch not found");
68 }
69
70 if !is_cargo_project {
71 bail!("--watch requires a Cargo project directory, not a binary");
72 }
73
74 println!("Starting development server with hot reload...");
75 println!(" Project: {}", args.path.display());
76 println!(" Mode: {}", if args.release { "release" } else { "debug" });
77 println!();
78 println!("Press Ctrl+C to stop.");
79 println!();
80
81 let mut cmd = Command::new("cargo");
82 cmd.arg("watch")
83 .arg("-x")
84 .arg(build_cargo_run_args(args))
85 .current_dir(&args.path)
86 .stdin(Stdio::inherit())
87 .stdout(Stdio::inherit())
88 .stderr(Stdio::inherit());
89
90 let status = cmd.status().context("Failed to run cargo-watch")?;
91
92 if !status.success() {
93 bail!("cargo-watch exited with non-zero status");
94 }
95
96 Ok(())
97}
98
99fn run_cargo_project(args: &DevArgs) -> Result<()> {
101 println!("Starting development server...");
102 println!(" Project: {}", args.path.display());
103 println!(" Mode: {}", if args.release { "release" } else { "debug" });
104 println!();
105
106 let mut cmd = Command::new("cargo");
107 cmd.arg("run");
108
109 if args.release {
110 cmd.arg("--release");
111 }
112
113 if !args.server_args.is_empty() {
114 cmd.arg("--");
115 cmd.args(&args.server_args);
116 }
117
118 cmd.current_dir(&args.path)
119 .stdin(Stdio::inherit())
120 .stdout(Stdio::inherit())
121 .stderr(Stdio::inherit());
122
123 let status = cmd.status().context("Failed to run cargo")?;
124
125 if !status.success() {
126 bail!("Server exited with non-zero status");
127 }
128
129 Ok(())
130}
131
132fn run_binary(args: &DevArgs) -> Result<()> {
134 println!("Starting MCP server...");
135 println!(" Binary: {}", args.path.display());
136 println!();
137
138 let mut cmd = Command::new(&args.path);
139 cmd.args(&args.server_args)
140 .stdin(Stdio::inherit())
141 .stdout(Stdio::inherit())
142 .stderr(Stdio::inherit());
143
144 let status = cmd.status().context("Failed to run server binary")?;
145
146 if !status.success() {
147 bail!("Server exited with non-zero status");
148 }
149
150 Ok(())
151}
152
153fn build_cargo_run_args(args: &DevArgs) -> String {
155 let mut cmd_args = vec!["run".to_string()];
156
157 if args.release {
158 cmd_args.push("--release".to_string());
159 }
160
161 if !args.server_args.is_empty() {
162 cmd_args.push("--".to_string());
163 cmd_args.extend(args.server_args.clone());
164 }
165
166 cmd_args.join(" ")
167}
168
169fn is_command_available(cmd: &str) -> bool {
172 which::which(cmd).is_ok()
173}
174
175fn is_executable(path: &Path) -> bool {
177 #[cfg(unix)]
178 {
179 use std::os::unix::fs::PermissionsExt;
180 path.metadata()
181 .map(|m| m.is_file() && (m.permissions().mode() & 0o111) != 0)
182 .unwrap_or(false)
183 }
184
185 #[cfg(not(unix))]
186 {
187 path.is_file()
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use std::path::PathBuf;
195
196 #[test]
197 fn test_build_cargo_run_args_basic() {
198 let args = DevArgs {
199 path: PathBuf::from("."),
200 watch: false,
201 server_args: vec![],
202 release: false,
203 inspector: false,
204 inspector_port: 5173,
205 };
206
207 assert_eq!(build_cargo_run_args(&args), "run");
208 }
209
210 #[test]
211 fn test_build_cargo_run_args_release() {
212 let args = DevArgs {
213 path: PathBuf::from("."),
214 watch: false,
215 server_args: vec![],
216 release: true,
217 inspector: false,
218 inspector_port: 5173,
219 };
220
221 assert_eq!(build_cargo_run_args(&args), "run --release");
222 }
223
224 #[test]
225 fn test_build_cargo_run_args_with_server_args() {
226 let args = DevArgs {
227 path: PathBuf::from("."),
228 watch: false,
229 server_args: vec!["--port".to_string(), "8080".to_string()],
230 release: false,
231 inspector: false,
232 inspector_port: 5173,
233 };
234
235 assert_eq!(build_cargo_run_args(&args), "run -- --port 8080");
236 }
237}