1use crate::e_prompts::yesno;
2use anyhow::{bail, Context, Result};
3use std::error::Error;
4use std::path::{Path, PathBuf};
5use std::process::{Command, Stdio};
6use which::which;
7
8pub fn ensure_npm() -> Result<PathBuf> {
12 ensure_node()?;
14 which("npm").context("`npm` not found in PATH. Please install Node.js and npm.")
15}
16
17pub fn ensure_napi_cli() -> Result<PathBuf, Box<dyn Error>> {
21 if let Ok(path) = which("napi") {
23 return Ok(path);
24 }
25
26 println!("`napi` CLI not found. Install it globally now?");
28 match yesno(
29 "Do you want to install `@napi-rs/cli` globally via npm?",
30 Some(true),
31 ) {
32 Ok(Some(true)) => {
33 let npm = ensure_npm()?;
34 println!("Installing `@napi-rs/cli` via `npm install -g @napi-rs/cli`…");
35 let mut child = Command::new(npm)
36 .args(&["install", "-g", "@napi-rs/cli"])
37 .stdin(Stdio::null())
38 .stdout(Stdio::inherit())
39 .stderr(Stdio::inherit())
40 .spawn()
41 .map_err(|e| format!("Failed to spawn install command: {}", e))?;
42
43 child
44 .wait()
45 .map_err(|e| format!("Error while waiting for installation: {}", e))?;
46 }
47 Ok(Some(false)) => return Err("User skipped installing `@napi-rs/cli`".into()),
48 Ok(None) => return Err("Installation of `@napi-rs/cli` cancelled (timeout)".into()),
49 Err(e) => return Err(format!("Error during prompt: {}", e).into()),
50 }
51
52 which("napi").map_err(|_| "`napi` still not found after installation".into())
54}
55
56pub fn ensure_cross_env() -> Result<PathBuf, Box<dyn Error>> {
60 if let Ok(path) = which("cross-env") {
62 return Ok(path);
63 }
64
65 println!("`cross-env` is not installed. Install it globally now?");
67 match yesno(
68 "Do you want to install `cross-env` globally via npm?",
69 Some(true),
70 ) {
71 Ok(Some(true)) => {
72 let npm = ensure_npm()?;
74 println!("Installing `cross-env` via `npm install -g cross-env`…");
75 let mut child = Command::new(npm)
76 .args(&["install", "-g", "cross-env"])
77 .stdin(Stdio::null())
78 .stdout(Stdio::inherit())
79 .stderr(Stdio::inherit())
80 .spawn()
81 .map_err(|e| format!("Failed to spawn install command: {}", e))?;
82
83 child
85 .wait()
86 .map_err(|e| format!("Error while waiting for installation: {}", e))?;
87 }
88 Ok(Some(false)) => return Err("User skipped installing `cross-env`".into()),
89 Ok(None) => return Err("Installation of `cross-env` cancelled (timeout)".into()),
90 Err(e) => return Err(format!("Error during prompt: {}", e).into()),
91 }
92
93 which("cross-env").map_err(|_| "`cross-env` still not found after installation".into())
95}
96pub fn ensure_pnpm() -> Result<PathBuf> {
101 ensure_node()?;
103
104 if let Ok(path) = which("pnpm") {
106 return Ok(path);
107 }
108
109 println!("`pnpm` is not installed. Install it now?");
111 match yesno(
112 "Do you want to install `pnpm` globally via npm?",
113 Some(true),
114 ) {
115 Ok(Some(true)) => {
116 let npm_path = ensure_npm()?;
118 println!("Installing `pnpm` via `npm install -g pnpm`…");
119
120 let mut child = Command::new(npm_path)
121 .args(&["install", "-g", "pnpm"])
122 .stdin(Stdio::null())
123 .stdout(Stdio::inherit())
124 .stderr(Stdio::inherit())
125 .spawn()
126 .context("failed to spawn `npm install -g pnpm`")?;
127
128 child
129 .wait()
130 .context("error while waiting for `npm install -g pnpm` to finish")?;
131 }
132 Ok(Some(false)) => bail!("user skipped installing `pnpm`"),
133 Ok(None) => bail!("installation of `pnpm` cancelled (timeout)"),
134 Err(e) => bail!("error during prompt: {}", e),
135 }
136
137 which("pnpm").context("`pnpm` still not found in PATH after installation")
139}
140
141pub fn ensure_dx() -> Result<PathBuf> {
145 if let Ok(path) = which("dx") {
147 return Ok(path);
148 }
149
150 println!("`dx` CLI not found. Install the Dioxus CLI now?");
152 match yesno(
153 "Do you want to install the Dioxus CLI via `cargo install dioxus-cli`?",
154 Some(true),
155 ) {
156 Ok(Some(true)) => {
157 println!("Installing `dioxus-cli` via `cargo install dioxus-cli`…");
158 let mut child = Command::new("cargo")
159 .args(&["install", "dioxus-cli"])
160 .stdin(Stdio::null())
161 .stdout(Stdio::inherit())
162 .stderr(Stdio::inherit())
163 .spawn()
164 .context("failed to spawn `cargo install dioxus-cli`")?;
165
166 child
167 .wait()
168 .context("error while waiting for `cargo install dioxus-cli` to finish")?;
169 }
170 Ok(Some(false)) => bail!("user skipped installing the Dioxus CLI"),
171 Ok(None) => bail!("installation of the Dioxus CLI cancelled (timeout)"),
172 Err(e) => bail!("error during prompt: {}", e),
173 }
174
175 which("dx").context("`dx` still not found in PATH after installation")
177}
178
179pub fn ensure_trunk() -> Result<PathBuf> {
182 if let Ok(path) = which("trunk") {
184 return Ok(path);
185 }
186
187 println!("`trunk` is not installed. Install it now?");
189 match yesno("Do you want to install `trunk`?", Some(true)) {
190 Ok(Some(true)) => {
191 println!("Installing `trunk` via `cargo install trunk`…");
192 let mut child = Command::new("cargo")
193 .args(&["install", "trunk"])
194 .stdin(Stdio::null())
195 .stdout(Stdio::inherit())
196 .stderr(Stdio::inherit())
197 .spawn()
198 .context("failed to spawn `cargo install trunk`")?;
199
200 child
201 .wait()
202 .context("failed while waiting for `cargo install trunk` to finish")?;
203 }
204 Ok(Some(false)) => {
205 anyhow::bail!("user skipped installing `trunk`");
206 }
207 Ok(None) => {
208 anyhow::bail!("installation of `trunk` cancelled (timeout)");
209 }
210 Err(e) => {
211 anyhow::bail!("error during prompt: {}", e);
212 }
213 }
214
215 which("trunk").context("`trunk` still not found in PATH after installation")
217}
218
219pub fn ensure_rust_script() -> Result<PathBuf> {
222 if let Ok(path) = which("rust-script") {
224 return Ok(path);
225 }
226
227 println!("`rust-script` is not installed. Install it now?");
229 match yesno("Do you want to install `rust-script`?", Some(true)) {
230 Ok(Some(true)) => {
231 println!("Installing `rust-script` via `cargo install rust-script`…");
232 let mut child = Command::new("cargo")
233 .args(&["install", "rust-script"])
234 .stdin(Stdio::null())
235 .stdout(Stdio::inherit())
236 .stderr(Stdio::inherit())
237 .spawn()
238 .context("failed to spawn `cargo install rust-script`")?;
239
240 child
241 .wait()
242 .context("failed while waiting for `cargo install rust-script` to finish")?;
243 }
244 Ok(Some(false)) => {
245 anyhow::bail!("user skipped installing `rust-script`");
246 }
247 Ok(None) => {
248 anyhow::bail!("installation of `rust-script` cancelled (timeout)");
249 }
250 Err(e) => {
251 anyhow::bail!("error during prompt: {}", e);
252 }
253 }
254 which("rust-script").context("`rust-script` still not found in PATH after installation")
255}
256pub fn check_npm_and_install(workspace_parent: &Path) -> Result<(), Box<dyn Error>> {
258 if workspace_parent.join("pnpm-workspace.yaml").exists() {
259 println!("Skipping npm checks for pnpm workspace.");
261 return Ok(());
262 }
263 println!(
265 "Checking for package.json in: {}",
266 workspace_parent.display()
267 );
268 if workspace_parent.join("package.json").exists() {
269 println!("package.json found in: {}", workspace_parent.display());
270 match which("npm") {
272 Ok(npm_path) => {
273 println!("Found npm at: {}", npm_path.display());
274
275 let output = Command::new(npm_path.clone())
277 .arg("ls")
278 .arg("--depth=1")
279 .current_dir(workspace_parent)
280 .output()
281 .map_err(|e| eprintln!("Failed to execute npm ls: {}", e))
282 .ok();
283
284 if let Some(output) = output {
285 println!("npm ls output: {}", String::from_utf8_lossy(&output.stdout));
286 if !output.status.success() {
287 eprintln!(
289 "npm ls failed for directory: {}",
290 workspace_parent.display()
291 );
292 eprintln!("{}", String::from_utf8_lossy(&output.stderr));
293
294 println!(
296 "Running npm install in directory: {}",
297 workspace_parent.display()
298 );
299 let install_output = Command::new(npm_path)
300 .arg("install")
301 .current_dir(workspace_parent)
302 .output()
303 .map_err(|e| eprintln!("Failed to execute npm install: {}", e))
304 .ok();
305
306 if let Some(install_output) = install_output {
307 println!(
308 "npm install output: {}",
309 String::from_utf8_lossy(&install_output.stdout)
310 );
311 if install_output.status.success() {
312 println!(
313 "npm install completed successfully in: {}",
314 workspace_parent.display()
315 );
316 } else {
317 eprintln!(
318 "npm install failed in directory: {}",
319 workspace_parent.display()
320 );
321 eprintln!("{}", String::from_utf8_lossy(&install_output.stderr));
322 }
323 }
324 }
325 }
326 }
327 Err(_) => {
328 eprintln!("npm is not installed or not in the system PATH.");
329 return Err("npm not found".into());
330 }
331 }
332 }
333 Ok(())
334}
335
336pub fn check_pnpm_and_install(workspace_parent: &Path) -> Result<PathBuf> {
339 let workspace_yaml = workspace_parent.join("pnpm-workspace.yaml");
341 if workspace_yaml.exists() {
342 let pnpm = ensure_pnpm()?;
344 println!(
345 "Found pnpm-workspace.yaml in: {}",
346 workspace_parent.display()
347 );
348 println!("Running `pnpm install`…");
349
350 let status = Command::new(&pnpm)
351 .arg("install")
352 .current_dir(workspace_parent)
353 .stdin(Stdio::null())
354 .stdout(Stdio::inherit())
355 .stderr(Stdio::inherit())
356 .status()
357 .context("failed to execute `pnpm install`")?;
358
359 if !status.success() {
360 bail!("`pnpm install` failed with exit code {}", status);
361 }
362 Command::new(&pnpm)
391 .args(&["run", "build:debug"])
392 .current_dir(workspace_parent)
393 .env("CARGO", "cargo")
394 .status()?;
395 println!("✅ pnpm install succeeded");
398 return Ok(pnpm);
399 } else {
400 println!(
401 "No pnpm-workspace.yaml found in {}, skipping `pnpm install`.",
402 workspace_parent.display()
403 );
404 }
405 Ok(PathBuf::new())
406}
407
408pub fn ensure_node() -> Result<PathBuf> {
412 if let Ok(path) = which("node") {
414 return Ok(path);
415 }
416
417 #[cfg(target_os = "windows")]
418 {
419 println!("`node` is not installed.");
421 match yesno(
422 "Do you want to install Node.js using NVM (via Chocolatey)?",
423 Some(true),
424 ) {
425 Ok(Some(true)) => {
426 println!("Installing NVM via Chocolatey...");
427 let choco = ensure_choco()?;
428 let mut child = Command::new(choco)
429 .args(&["install", "nvm"]) .stdin(Stdio::null())
431 .stdout(Stdio::inherit())
432 .stderr(Stdio::inherit())
433 .spawn()
434 .context("Failed to spawn `choco install nvm`")?;
435
436 child
437 .wait()
438 .context("Error while waiting for `choco install nvm` to finish")?;
439
440 let nvm = which("nvm").context("`nvm` not found in PATH after installation.")?;
442 let mut child = Command::new(&nvm)
443 .args(&["install", "lts"])
444 .stdin(Stdio::null())
445 .stdout(Stdio::inherit())
446 .stderr(Stdio::inherit())
447 .spawn()
448 .context("Failed to spawn `nvm install lts`")?;
449
450 child
451 .wait()
452 .context("Error while waiting for `nvm install lts` to finish")?;
453
454 let mut child = Command::new(&nvm)
455 .args(&["use", "lts"])
456 .stdin(Stdio::null())
457 .stdout(Stdio::inherit())
458 .stderr(Stdio::inherit())
459 .spawn()
460 .context("Failed to spawn `nvm use lts`")?;
461
462 child
463 .wait()
464 .context("Error while waiting for `nvm use lts` to finish")?;
465 }
466 Ok(Some(false)) => {
467 anyhow::bail!("User declined to install Node.js.");
468 }
469 Ok(None) => {
470 anyhow::bail!("Installation of Node.js cancelled (timeout).");
471 }
472 Err(e) => {
473 anyhow::bail!("Error during prompt: {}", e);
474 }
475 }
476 }
477
478 #[cfg(not(target_os = "windows"))]
479 {
480 println!("`node` is not installed. Please install Node.js manually.");
482 anyhow::bail!("Node.js installation is not automated for this platform.");
483 }
484
485 which("node").context("`node` still not found after installation")
487}
488
489pub fn ensure_choco() -> Result<PathBuf> {
493 if let Ok(path) = which("choco") {
495 return Ok(path);
496 }
497
498 #[cfg(target_os = "windows")]
499 {
500 println!("`choco` (Chocolatey) is not installed.");
502 println!("It is required to proceed. Do you want to install it manually?");
503 match yesno(
504 "Do you want to install Chocolatey manually by following the instructions?",
505 Some(true),
506 ) {
507 Ok(Some(true)) => {
508 println!("Please run the following command in PowerShell to install Chocolatey:");
509 println!("Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))");
510 anyhow::bail!(
511 "Chocolatey installation is not automated. Please install it manually."
512 );
513 }
514 Ok(Some(false)) => {
515 anyhow::bail!("User declined to install Chocolatey.");
516 }
517 Ok(None) => {
518 anyhow::bail!("Installation of Chocolatey cancelled (timeout).");
519 }
520 Err(e) => {
521 anyhow::bail!("Error during prompt: {}", e);
522 }
523 }
524 }
525
526 #[cfg(not(target_os = "windows"))]
527 {
528 anyhow::bail!("Chocolatey is only supported on Windows.");
529 }
530}