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#[cfg(windows)]
8pub fn is_admin() -> bool {
9 let shell = "[bool]([System.Security.Principal.WindowsPrincipal][System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)";
10 let output = std::process::Command::new("powershell")
11 .args(["-c", shell])
12 .output()
13 .expect("Failed to execute PowerShell command");
14 String::from_utf8(output.stdout).unwrap_or_default().trim() == "True"
15}
16
17#[cfg(unix)]
18pub fn is_admin() -> bool {
19 unsafe {
25 println!("`is_admin` is not implemented for Unix systems yet.");
26 false
27 }
28}
29pub fn ensure_admin_privileges() -> Result<()> {
32 if !is_admin() {
33 return Err(anyhow::anyhow!(
34 "This program must be run as an administrator. Please restart it with administrative privileges."
35 ));
36 }
37 Ok(())
38}
39pub fn ensure_npm() -> Result<PathBuf> {
43 ensure_node()?;
45 which("npm").context("`npm` not found in PATH. Please install Node.js and npm.")
46}
47
48pub fn ensure_napi_cli() -> Result<PathBuf, Box<dyn Error>> {
52 if let Ok(path) = which("napi") {
54 return Ok(path);
55 }
56
57 println!("`napi` CLI not found. Install it globally now?");
59 match yesno(
60 "Do you want to install `@napi-rs/cli` globally via npm?",
61 Some(true),
62 ) {
63 Ok(Some(true)) => {
64 let npm = ensure_npm()?;
65 println!("Installing `@napi-rs/cli` via `npm install -g @napi-rs/cli`…");
66 let mut child = Command::new(npm)
67 .args(&["install", "-g", "@napi-rs/cli"])
68 .stdin(Stdio::null())
69 .stdout(Stdio::inherit())
70 .stderr(Stdio::inherit())
71 .spawn()
72 .map_err(|e| format!("Failed to spawn install command: {}", e))?;
73
74 child
75 .wait()
76 .map_err(|e| format!("Error while waiting for installation: {}", e))?;
77 }
78 Ok(Some(false)) => return Err("User skipped installing `@napi-rs/cli`".into()),
79 Ok(None) => return Err("Installation of `@napi-rs/cli` cancelled (timeout)".into()),
80 Err(e) => return Err(format!("Error during prompt: {}", e).into()),
81 }
82
83 which("napi").map_err(|_| "`napi` still not found after installation".into())
85}
86
87pub fn ensure_cross_env() -> Result<PathBuf, Box<dyn Error>> {
91 if let Ok(path) = which("cross-env") {
93 return Ok(path);
94 }
95
96 println!("`cross-env` is not installed. Install it globally now?");
98 match yesno(
99 "Do you want to install `cross-env` globally via npm?",
100 Some(true),
101 ) {
102 Ok(Some(true)) => {
103 let npm = ensure_npm()?;
105 println!("Installing `cross-env` via `npm install -g cross-env`…");
106 let mut child = Command::new(npm)
107 .args(&["install", "-g", "cross-env"])
108 .stdin(Stdio::null())
109 .stdout(Stdio::inherit())
110 .stderr(Stdio::inherit())
111 .spawn()
112 .map_err(|e| format!("Failed to spawn install command: {}", e))?;
113
114 child
116 .wait()
117 .map_err(|e| format!("Error while waiting for installation: {}", e))?;
118 }
119 Ok(Some(false)) => return Err("User skipped installing `cross-env`".into()),
120 Ok(None) => return Err("Installation of `cross-env` cancelled (timeout)".into()),
121 Err(e) => return Err(format!("Error during prompt: {}", e).into()),
122 }
123
124 which("cross-env").map_err(|_| "`cross-env` still not found after installation".into())
126}
127pub fn ensure_pnpm() -> Result<PathBuf> {
132 ensure_node()?;
134
135 if let Ok(path) = which("pnpm") {
137 return Ok(path);
138 }
139
140 println!("`pnpm` is not installed. Install it now?");
142 match yesno(
143 "Do you want to install `pnpm` globally via npm?",
144 Some(true),
145 ) {
146 Ok(Some(true)) => {
147 let npm_path = ensure_npm()?;
149 println!("Installing `pnpm` via `npm install -g pnpm`…");
150
151 let mut child = Command::new(npm_path)
152 .args(&["install", "-g", "pnpm"])
153 .stdin(Stdio::null())
154 .stdout(Stdio::inherit())
155 .stderr(Stdio::inherit())
156 .spawn()
157 .context("failed to spawn `npm install -g pnpm`")?;
158
159 child
160 .wait()
161 .context("error while waiting for `npm install -g pnpm` to finish")?;
162 }
163 Ok(Some(false)) => bail!("user skipped installing `pnpm`"),
164 Ok(None) => bail!("installation of `pnpm` cancelled (timeout)"),
165 Err(e) => bail!("error during prompt: {}", e),
166 }
167
168 which("pnpm").context("`pnpm` still not found in PATH after installation")
170}
171
172pub fn ensure_dx() -> Result<PathBuf> {
176 if let Ok(path) = which("dx") {
178 return Ok(path);
179 }
180
181 println!("`dx` CLI not found. Install the Dioxus CLI now?");
183 match yesno(
184 "Do you want to install the Dioxus CLI via `cargo install dioxus-cli`?",
185 Some(true),
186 ) {
187 Ok(Some(true)) => {
188 println!("Installing `dioxus-cli` via `cargo install dioxus-cli`…");
189 let mut child = Command::new("cargo")
190 .args(&["install", "dioxus-cli"])
191 .stdin(Stdio::null())
192 .stdout(Stdio::inherit())
193 .stderr(Stdio::inherit())
194 .spawn()
195 .context("failed to spawn `cargo install dioxus-cli`")?;
196
197 child
198 .wait()
199 .context("error while waiting for `cargo install dioxus-cli` to finish")?;
200 }
201 Ok(Some(false)) => bail!("user skipped installing the Dioxus CLI"),
202 Ok(None) => bail!("installation of the Dioxus CLI cancelled (timeout)"),
203 Err(e) => bail!("error during prompt: {}", e),
204 }
205
206 which("dx").context("`dx` still not found in PATH after installation")
208}
209
210pub fn ensure_trunk() -> Result<PathBuf> {
213 if let Ok(path) = which("trunk") {
215 return Ok(path);
216 }
217
218 println!("`trunk` is not installed. Install it now?");
220 match yesno("Do you want to install `trunk`?", Some(true)) {
221 Ok(Some(true)) => {
222 println!("Installing `trunk` via `cargo install trunk`…");
223 let mut child = Command::new("cargo")
224 .args(&["install", "trunk"])
225 .stdin(Stdio::null())
226 .stdout(Stdio::inherit())
227 .stderr(Stdio::inherit())
228 .spawn()
229 .context("failed to spawn `cargo install trunk`")?;
230
231 child
232 .wait()
233 .context("failed while waiting for `cargo install trunk` to finish")?;
234 }
235 Ok(Some(false)) => {
236 anyhow::bail!("user skipped installing `trunk`");
237 }
238 Ok(None) => {
239 anyhow::bail!("installation of `trunk` cancelled (timeout)");
240 }
241 Err(e) => {
242 anyhow::bail!("error during prompt: {}", e);
243 }
244 }
245
246 which("trunk").context("`trunk` still not found in PATH after installation")
248}
249
250pub fn ensure_rust_script() -> Result<PathBuf> {
253 if let Ok(path) = which("rust-script") {
255 return Ok(path);
256 }
257
258 println!("`rust-script` is not installed. Install it now?");
260 match yesno("Do you want to install `rust-script`?", Some(true)) {
261 Ok(Some(true)) => {
262 println!("Installing `rust-script` via `cargo install rust-script`…");
263 let mut child = Command::new("cargo")
264 .args(&["install", "rust-script"])
265 .stdin(Stdio::null())
266 .stdout(Stdio::inherit())
267 .stderr(Stdio::inherit())
268 .spawn()
269 .context("failed to spawn `cargo install rust-script`")?;
270
271 child
272 .wait()
273 .context("failed while waiting for `cargo install rust-script` to finish")?;
274 }
275 Ok(Some(false)) => {
276 anyhow::bail!("user skipped installing `rust-script`");
277 }
278 Ok(None) => {
279 anyhow::bail!("installation of `rust-script` cancelled (timeout)");
280 }
281 Err(e) => {
282 anyhow::bail!("error during prompt: {}", e);
283 }
284 }
285 which("rust-script").context("`rust-script` still not found in PATH after installation")
286}
287pub fn check_npm_and_install(workspace_parent: &Path) -> Result<(), Box<dyn Error>> {
289 if workspace_parent.join("pnpm-workspace.yaml").exists() {
290 println!("Skipping npm checks for pnpm workspace.");
292 return Ok(());
293 }
294 println!(
296 "Checking for package.json in: {}",
297 workspace_parent.display()
298 );
299 if workspace_parent.join("package.json").exists() {
300 println!("package.json found in: {}", workspace_parent.display());
301 match which("npm") {
303 Ok(npm_path) => {
304 println!("Found npm at: {}", npm_path.display());
305
306 let output = Command::new(npm_path.clone())
308 .arg("ls")
309 .arg("--depth=1")
310 .current_dir(workspace_parent)
311 .output()
312 .map_err(|e| eprintln!("Failed to execute npm ls: {}", e))
313 .ok();
314
315 if let Some(output) = output {
316 println!("npm ls output: {}", String::from_utf8_lossy(&output.stdout));
317 if !output.status.success() {
318 eprintln!(
320 "npm ls failed for directory: {}",
321 workspace_parent.display()
322 );
323 eprintln!("{}", String::from_utf8_lossy(&output.stderr));
324
325 println!(
327 "Running npm install in directory: {}",
328 workspace_parent.display()
329 );
330 let install_output = Command::new(npm_path)
331 .arg("install")
332 .current_dir(workspace_parent)
333 .output()
334 .map_err(|e| eprintln!("Failed to execute npm install: {}", e))
335 .ok();
336
337 if let Some(install_output) = install_output {
338 println!(
339 "npm install output: {}",
340 String::from_utf8_lossy(&install_output.stdout)
341 );
342 if install_output.status.success() {
343 println!(
344 "npm install completed successfully in: {}",
345 workspace_parent.display()
346 );
347 } else {
348 eprintln!(
349 "npm install failed in directory: {}",
350 workspace_parent.display()
351 );
352 eprintln!("{}", String::from_utf8_lossy(&install_output.stderr));
353 }
354 }
355 }
356 }
357 }
358 Err(_) => {
359 eprintln!("npm is not installed or not in the system PATH.");
360 return Err("npm not found".into());
361 }
362 }
363 }
364 Ok(())
365}
366
367pub fn check_pnpm_and_install(workspace_parent: &Path) -> Result<PathBuf> {
370 let workspace_yaml = workspace_parent.join("pnpm-workspace.yaml");
372 if workspace_yaml.exists() {
373 let pnpm = ensure_pnpm()?;
375 println!(
376 "Found pnpm-workspace.yaml in: {}",
377 workspace_parent.display()
378 );
379 println!("Running `pnpm install`…");
380
381 let status = Command::new(&pnpm)
382 .arg("install")
383 .current_dir(workspace_parent)
384 .stdin(Stdio::null())
385 .stdout(Stdio::inherit())
386 .stderr(Stdio::inherit())
387 .status()
388 .context("failed to execute `pnpm install`")?;
389
390 if !status.success() {
391 bail!("`pnpm install` failed with exit code {}", status);
392 }
393 Command::new(&pnpm)
422 .args(&["run", "build:debug"])
423 .current_dir(workspace_parent)
424 .env("CARGO", "cargo")
425 .status()?;
426 println!("✅ pnpm install succeeded");
429 return Ok(pnpm);
430 } else {
431 println!(
432 "No pnpm-workspace.yaml found in {}, skipping `pnpm install`.",
433 workspace_parent.display()
434 );
435 }
436 Ok(PathBuf::new())
437}
438
439pub fn ensure_node() -> Result<PathBuf> {
443 if let Ok(path) = which("node") {
445 return Ok(path);
446 }
447
448 #[cfg(target_os = "windows")]
449 {
450 println!("`node` is not installed.");
452 match yesno(
453 "Do you want to install Node.js using NVM (via Chocolatey)?",
454 Some(true),
455 ) {
456 Ok(Some(true)) => {
457 println!("Installing NVM via Chocolatey...");
458 let choco = ensure_choco()?;
459 let mut child = Command::new(choco)
460 .args(&["install", "nvm"]) .stdin(Stdio::null())
462 .stdout(Stdio::inherit())
463 .stderr(Stdio::inherit())
464 .spawn()
465 .context("Failed to spawn `choco install nvm`")?;
466
467 child
468 .wait()
469 .context("Error while waiting for `choco install nvm` to finish")?;
470
471 let nvm = which("nvm").context("`nvm` not found in PATH after installation.")?;
473 let mut child = Command::new(&nvm)
474 .args(&["install", "lts"])
475 .stdin(Stdio::null())
476 .stdout(Stdio::inherit())
477 .stderr(Stdio::inherit())
478 .spawn()
479 .context("Failed to spawn `nvm install lts`")?;
480
481 child
482 .wait()
483 .context("Error while waiting for `nvm install lts` to finish")?;
484
485 let mut child = Command::new(&nvm)
486 .args(&["use", "lts"])
487 .stdin(Stdio::null())
488 .stdout(Stdio::inherit())
489 .stderr(Stdio::inherit())
490 .spawn()
491 .context("Failed to spawn `nvm use lts`")?;
492
493 child
494 .wait()
495 .context("Error while waiting for `nvm use lts` to finish")?;
496 }
497 Ok(Some(false)) => {
498 anyhow::bail!("User declined to install Node.js.");
499 }
500 Ok(None) => {
501 anyhow::bail!("Installation of Node.js cancelled (timeout).");
502 }
503 Err(e) => {
504 anyhow::bail!("Error during prompt: {}", e);
505 }
506 }
507 }
508
509 #[cfg(not(target_os = "windows"))]
510 {
511 println!("`node` is not installed. Please install Node.js manually.");
513 anyhow::bail!("Node.js installation is not automated for this platform.");
514 }
515
516 which("node").context("`node` still not found after installation")
518}
519
520pub fn ensure_github_gh() -> Result<PathBuf> {
524 if let Ok(path) = which("gh") {
526 return Ok(path);
527 }
528 let default_path = Path::new("C:\\Program Files\\GitHub CLI\\gh.exe");
530 if default_path.exists() {
531 return Ok(default_path.to_path_buf());
532 }
533 #[cfg(target_os = "windows")]
534 {
535 let choco = ensure_choco()?;
537
538 println!("Installing GitHub CLI (`gh`) via Chocolatey...");
540 if let Err(e) = ensure_admin_privileges() {
541 eprintln!("Error: {}", e);
542 return Err(e);
543 }
544 let mut child = Command::new(choco)
545 .args(&["install", "gh","y"])
546 .stdin(Stdio::null())
547 .stdout(Stdio::inherit())
548 .stderr(Stdio::inherit())
549 .spawn()
550 .context("Failed to spawn `choco install gh`")?;
551
552 child.wait().context("Error while waiting for `choco install gh` to finish")?;
553 if default_path.exists() {
554 return Ok(default_path.to_path_buf());
555 }
556 }
557
558 #[cfg(not(target_os = "windows"))]
559 {
560 anyhow::bail!("GitHub CLI installation is only automated on Windows.");
561 }
562
563 which("gh").context("`gh` still not found after installation")
565}
566
567pub fn ensure_choco() -> Result<PathBuf> {
571 if let Ok(path) = which("choco") {
573 return Ok(path);
574 }
575
576 #[cfg(target_os = "windows")]
577 {
578 println!("`choco` (Chocolatey) is not installed.");
580 println!("It is required to proceed. Do you want to install it manually?");
581 match yesno(
582 "Do you want to install Chocolatey manually by following the instructions?",
583 Some(true),
584 ) {
585 Ok(Some(true)) => {
586 println!("Please run the following command in PowerShell to install Chocolatey:");
587 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'))");
588 anyhow::bail!(
589 "Chocolatey installation is not automated. Please install it manually."
590 );
591 }
592 Ok(Some(false)) => {
593 anyhow::bail!("User declined to install Chocolatey.");
594 }
595 Ok(None) => {
596 anyhow::bail!("Installation of Chocolatey cancelled (timeout).");
597 }
598 Err(e) => {
599 anyhow::bail!("Error during prompt: {}", e);
600 }
601 }
602 }
603
604 #[cfg(not(target_os = "windows"))]
605 {
606 anyhow::bail!("Chocolatey is only supported on Windows.");
607 }
608}