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
8#[cfg(windows)]
10pub fn is_admin() -> bool {
11 let shell = "[bool]([System.Security.Principal.WindowsPrincipal][System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)";
12 let output = std::process::Command::new("powershell")
13 .args(["-c", shell])
14 .output()
15 .expect("Failed to execute PowerShell command");
16 String::from_utf8(output.stdout).unwrap_or_default().trim() == "True"
17}
18
19
20#[cfg(unix)]
22pub fn is_admin() -> bool {
23 use libc::{geteuid, getuid};
24 unsafe { getuid() == 0 || geteuid() == 0 }
25}
26
27pub fn ensure_admin_privileges() -> Result<()> {
30 if !is_admin() {
31 return Err(anyhow::anyhow!(
32 "This program must be run as an administrator. Please restart it with administrative privileges."
33 ));
34 }
35 Ok(())
36}
37pub fn ensure_npm() -> Result<PathBuf> {
41 ensure_node()?;
43 which("npm").context("`npm` not found in PATH. Please install Node.js and npm.")
44}
45
46pub fn ensure_napi_cli() -> Result<PathBuf, Box<dyn Error>> {
50 if let Ok(path) = which("napi") {
52 return Ok(path);
53 }
54
55 println!("`napi` CLI not found. Install it globally now?");
57 match yesno(
58 "Do you want to install `@napi-rs/cli` globally via npm?",
59 Some(true),
60 ) {
61 Ok(Some(true)) => {
62 let npm = ensure_npm()?;
63 println!("Installing `@napi-rs/cli` via `npm install -g @napi-rs/cli`…");
64 let mut child = Command::new(npm)
65 .args(&["install", "-g", "@napi-rs/cli"])
66 .stdin(Stdio::null())
67 .stdout(Stdio::inherit())
68 .stderr(Stdio::inherit())
69 .spawn()
70 .map_err(|e| format!("Failed to spawn install command: {}", e))?;
71
72 child
73 .wait()
74 .map_err(|e| format!("Error while waiting for installation: {}", e))?;
75 }
76 Ok(Some(false)) => return Err("User skipped installing `@napi-rs/cli`".into()),
77 Ok(None) => return Err("Installation of `@napi-rs/cli` cancelled (timeout)".into()),
78 Err(e) => return Err(format!("Error during prompt: {}", e).into()),
79 }
80
81 which("napi").map_err(|_| "`napi` still not found after installation".into())
83}
84
85pub fn ensure_cross_env() -> Result<PathBuf, Box<dyn Error>> {
89 if let Ok(path) = which("cross-env") {
91 return Ok(path);
92 }
93
94 println!("`cross-env` is not installed. Install it globally now?");
96 match yesno(
97 "Do you want to install `cross-env` globally via npm?",
98 Some(true),
99 ) {
100 Ok(Some(true)) => {
101 let npm = ensure_npm()?;
103 println!("Installing `cross-env` via `npm install -g cross-env`…");
104 let mut child = Command::new(npm)
105 .args(&["install", "-g", "cross-env"])
106 .stdin(Stdio::null())
107 .stdout(Stdio::inherit())
108 .stderr(Stdio::inherit())
109 .spawn()
110 .map_err(|e| format!("Failed to spawn install command: {}", e))?;
111
112 child
114 .wait()
115 .map_err(|e| format!("Error while waiting for installation: {}", e))?;
116 }
117 Ok(Some(false)) => return Err("User skipped installing `cross-env`".into()),
118 Ok(None) => return Err("Installation of `cross-env` cancelled (timeout)".into()),
119 Err(e) => return Err(format!("Error during prompt: {}", e).into()),
120 }
121
122 which("cross-env").map_err(|_| "`cross-env` still not found after installation".into())
124}
125pub fn ensure_pnpm() -> Result<PathBuf> {
130 ensure_node()?;
132
133 if let Ok(path) = which("pnpm") {
135 return Ok(path);
136 }
137
138 println!("`pnpm` is not installed. Install it now?");
140 match yesno(
141 "Do you want to install `pnpm` globally via npm?",
142 Some(true),
143 ) {
144 Ok(Some(true)) => {
145 let npm_path = ensure_npm()?;
147 println!("Installing `pnpm` via `npm install -g pnpm`…");
148
149 let mut child = Command::new(npm_path)
150 .args(&["install", "-g", "pnpm"])
151 .stdin(Stdio::null())
152 .stdout(Stdio::inherit())
153 .stderr(Stdio::inherit())
154 .spawn()
155 .context("failed to spawn `npm install -g pnpm`")?;
156
157 child
158 .wait()
159 .context("error while waiting for `npm install -g pnpm` to finish")?;
160 }
161 Ok(Some(false)) => bail!("user skipped installing `pnpm`"),
162 Ok(None) => bail!("installation of `pnpm` cancelled (timeout)"),
163 Err(e) => bail!("error during prompt: {}", e),
164 }
165
166 which("pnpm").context("`pnpm` still not found in PATH after installation")
168}
169
170pub fn ensure_dx() -> Result<PathBuf> {
174 if let Ok(path) = which("dx") {
176 return Ok(path);
177 }
178
179 println!("`dx` CLI not found. Install the Dioxus CLI now?");
181 match yesno(
182 "Do you want to install the Dioxus CLI via `cargo install dioxus-cli`?",
183 Some(true),
184 ) {
185 Ok(Some(true)) => {
186 println!("Installing `dioxus-cli` via `cargo install dioxus-cli`…");
187 let mut child = Command::new("cargo")
188 .args(&["install", "dioxus-cli"])
189 .stdin(Stdio::null())
190 .stdout(Stdio::inherit())
191 .stderr(Stdio::inherit())
192 .spawn()
193 .context("failed to spawn `cargo install dioxus-cli`")?;
194
195 child
196 .wait()
197 .context("error while waiting for `cargo install dioxus-cli` to finish")?;
198 }
199 Ok(Some(false)) => bail!("user skipped installing the Dioxus CLI"),
200 Ok(None) => bail!("installation of the Dioxus CLI cancelled (timeout)"),
201 Err(e) => bail!("error during prompt: {}", e),
202 }
203
204 which("dx").context("`dx` still not found in PATH after installation")
206}
207
208pub fn ensure_trunk() -> Result<PathBuf> {
211 if let Ok(path) = which("trunk") {
213 return Ok(path);
214 }
215
216 println!("`trunk` is not installed. Install it now?");
218 match yesno("Do you want to install `trunk`?", Some(true)) {
219 Ok(Some(true)) => {
220 println!("Installing `trunk` via `cargo install trunk`…");
221 let mut child = Command::new("cargo")
222 .args(&["install", "trunk"])
223 .stdin(Stdio::null())
224 .stdout(Stdio::inherit())
225 .stderr(Stdio::inherit())
226 .spawn()
227 .context("failed to spawn `cargo install trunk`")?;
228
229 child
230 .wait()
231 .context("failed while waiting for `cargo install trunk` to finish")?;
232 }
233 Ok(Some(false)) => {
234 anyhow::bail!("user skipped installing `trunk`");
235 }
236 Ok(None) => {
237 anyhow::bail!("installation of `trunk` cancelled (timeout)");
238 }
239 Err(e) => {
240 anyhow::bail!("error during prompt: {}", e);
241 }
242 }
243
244 which("trunk").context("`trunk` still not found in PATH after installation")
246}
247
248pub fn ensure_rust_script() -> Result<PathBuf> {
251 if let Ok(path) = which("rust-script") {
253 return Ok(path);
254 }
255
256 println!("`rust-script` is not installed. Install it now?");
258 match yesno("Do you want to install `rust-script`?", Some(true)) {
259 Ok(Some(true)) => {
260 println!("Installing `rust-script` via `cargo install rust-script`…");
261 let mut child = Command::new("cargo")
262 .args(&["install", "rust-script"])
263 .stdin(Stdio::null())
264 .stdout(Stdio::inherit())
265 .stderr(Stdio::inherit())
266 .spawn()
267 .context("failed to spawn `cargo install rust-script`")?;
268
269 child
270 .wait()
271 .context("failed while waiting for `cargo install rust-script` to finish")?;
272 }
273 Ok(Some(false)) => {
274 anyhow::bail!("user skipped installing `rust-script`");
275 }
276 Ok(None) => {
277 anyhow::bail!("installation of `rust-script` cancelled (timeout)");
278 }
279 Err(e) => {
280 anyhow::bail!("error during prompt: {}", e);
281 }
282 }
283 which("rust-script").context("`rust-script` still not found in PATH after installation")
284}
285pub fn check_npm_and_install(workspace_parent: &Path) -> Result<(), Box<dyn Error>> {
287 if workspace_parent.join("pnpm-workspace.yaml").exists() {
288 println!("Skipping npm checks for pnpm workspace.");
290 return Ok(());
291 }
292 println!(
294 "Checking for package.json in: {}",
295 workspace_parent.display()
296 );
297 if workspace_parent.join("package.json").exists() {
298 println!("package.json found in: {}", workspace_parent.display());
299 match which("npm") {
301 Ok(npm_path) => {
302 println!("Found npm at: {}", npm_path.display());
303
304 let output = Command::new(npm_path.clone())
306 .arg("ls")
307 .arg("--depth=1")
308 .current_dir(workspace_parent)
309 .output()
310 .map_err(|e| eprintln!("Failed to execute npm ls: {}", e))
311 .ok();
312
313 if let Some(output) = output {
314 println!("npm ls output: {}", String::from_utf8_lossy(&output.stdout));
315 if !output.status.success() {
316 eprintln!(
318 "npm ls failed for directory: {}",
319 workspace_parent.display()
320 );
321 eprintln!("{}", String::from_utf8_lossy(&output.stderr));
322
323 println!(
325 "Running npm install in directory: {}",
326 workspace_parent.display()
327 );
328 let install_output = Command::new(npm_path)
329 .arg("install")
330 .current_dir(workspace_parent)
331 .output()
332 .map_err(|e| eprintln!("Failed to execute npm install: {}", e))
333 .ok();
334
335 if let Some(install_output) = install_output {
336 println!(
337 "npm install output: {}",
338 String::from_utf8_lossy(&install_output.stdout)
339 );
340 if install_output.status.success() {
341 println!(
342 "npm install completed successfully in: {}",
343 workspace_parent.display()
344 );
345 } else {
346 eprintln!(
347 "npm install failed in directory: {}",
348 workspace_parent.display()
349 );
350 eprintln!("{}", String::from_utf8_lossy(&install_output.stderr));
351 }
352 }
353 }
354 }
355 }
356 Err(_) => {
357 eprintln!("npm is not installed or not in the system PATH.");
358 return Err("npm not found".into());
359 }
360 }
361 }
362 Ok(())
363}
364
365pub fn check_pnpm_and_install(workspace_parent: &Path) -> Result<PathBuf> {
368 let workspace_yaml = workspace_parent.join("pnpm-workspace.yaml");
370 if workspace_yaml.exists() {
371 let pnpm = ensure_pnpm()?;
373 println!(
374 "Found pnpm-workspace.yaml in: {}",
375 workspace_parent.display()
376 );
377 println!("Running `pnpm install`…");
378
379 let status = Command::new(&pnpm)
380 .arg("install")
381 .current_dir(workspace_parent)
382 .stdin(Stdio::null())
383 .stdout(Stdio::inherit())
384 .stderr(Stdio::inherit())
385 .status()
386 .context("failed to execute `pnpm install`")?;
387
388 if !status.success() {
389 bail!("`pnpm install` failed with exit code {}", status);
390 }
391 Command::new(&pnpm)
420 .args(&["run", "build:debug"])
421 .current_dir(workspace_parent)
422 .env("CARGO", "cargo")
423 .status()?;
424 println!("✅ pnpm install succeeded");
427 return Ok(pnpm);
428 } else {
429 println!(
430 "No pnpm-workspace.yaml found in {}, skipping `pnpm install`.",
431 workspace_parent.display()
432 );
433 }
434 Ok(PathBuf::new())
435}
436
437pub fn ensure_node() -> Result<PathBuf> {
441 if let Ok(path) = which("node") {
443 return Ok(path);
444 }
445
446 #[cfg(target_os = "windows")]
447 {
448 println!("`node` is not installed.");
450 match yesno(
451 "Do you want to install Node.js using NVM (via Chocolatey)?",
452 Some(true),
453 ) {
454 Ok(Some(true)) => {
455 println!("Installing NVM via Chocolatey...");
456 let choco = ensure_choco()?;
457 let mut child = Command::new(choco)
458 .args(&["install", "nvm"]) .stdin(Stdio::null())
460 .stdout(Stdio::inherit())
461 .stderr(Stdio::inherit())
462 .spawn()
463 .context("Failed to spawn `choco install nvm`")?;
464
465 child
466 .wait()
467 .context("Error while waiting for `choco install nvm` to finish")?;
468
469 let nvm = which("nvm").context("`nvm` not found in PATH after installation.")?;
471 let mut child = Command::new(&nvm)
472 .args(&["install", "lts"])
473 .stdin(Stdio::null())
474 .stdout(Stdio::inherit())
475 .stderr(Stdio::inherit())
476 .spawn()
477 .context("Failed to spawn `nvm install lts`")?;
478
479 child
480 .wait()
481 .context("Error while waiting for `nvm install lts` to finish")?;
482
483 let mut child = Command::new(&nvm)
484 .args(&["use", "lts"])
485 .stdin(Stdio::null())
486 .stdout(Stdio::inherit())
487 .stderr(Stdio::inherit())
488 .spawn()
489 .context("Failed to spawn `nvm use lts`")?;
490
491 child
492 .wait()
493 .context("Error while waiting for `nvm use lts` to finish")?;
494 }
495 Ok(Some(false)) => {
496 anyhow::bail!("User declined to install Node.js.");
497 }
498 Ok(None) => {
499 anyhow::bail!("Installation of Node.js cancelled (timeout).");
500 }
501 Err(e) => {
502 anyhow::bail!("Error during prompt: {}", e);
503 }
504 }
505 }
506
507 #[cfg(not(target_os = "windows"))]
508 {
509 println!("`node` is not installed. Please install Node.js manually.");
511 anyhow::bail!("Node.js installation is not automated for this platform.");
512 }
513
514 which("node").context("`node` still not found after installation")
516}
517
518pub fn ensure_github_gh() -> Result<PathBuf> {
522 if let Ok(path) = which("gh") {
524 return Ok(path);
525 }
526 let default_path = Path::new("C:\\Program Files\\GitHub CLI\\gh.exe");
528 if default_path.exists() {
529 return Ok(default_path.to_path_buf());
530 }
531 #[cfg(target_os = "windows")]
532 {
533 let choco = ensure_choco()?;
535
536 println!("Installing GitHub CLI (`gh`) via Chocolatey...");
538 if let Err(e) = ensure_admin_privileges() {
539 eprintln!("Error: {}", e);
540 return Err(e);
541 }
542 let mut child = Command::new(choco)
543 .args(&["install", "gh", "y"])
544 .stdin(Stdio::null())
545 .stdout(Stdio::inherit())
546 .stderr(Stdio::inherit())
547 .spawn()
548 .context("Failed to spawn `choco install gh`")?;
549
550 child
551 .wait()
552 .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}
609
610pub fn ensure_leptos() -> Result<PathBuf> {
614 if let Ok(path) = which("cargo-leptos") {
616 return Ok(path);
617 }
618
619 println!("`cargo-leptos` CLI not found. Install it now?");
621 match yesno(
622 "Do you want to install the `cargo-leptos` CLI via `cargo install cargo-leptos`?",
623 Some(true),
624 ) {
625 Ok(Some(true)) => {
626 if which("perl").is_err() {
628 println!("`perl` is not installed or not found in PATH.");
629 println!("OpenSSL requires `perl` for installation unless OpenSSL is already configured in your environment.");
630 println!("It is recommended to have a working `perl` distribution installed for openssl.");
631 ensure_perl();
632 }
633
634 println!("Installing `cargo-leptos` via `cargo install cargo-leptos`…");
635 let mut child = Command::new("cargo")
636 .args(&["install", "cargo-leptos"])
637 .stdin(Stdio::null())
638 .stdout(Stdio::inherit())
639 .stderr(Stdio::inherit())
640 .spawn()
641 .context("Failed to spawn `cargo install cargo-leptos`")?;
642
643 child
644 .wait()
645 .context("Error while waiting for `cargo install cargo-leptos` to finish")?;
646 }
647 Ok(Some(false)) => bail!("User skipped installing `cargo-leptos`."),
648 Ok(None) => bail!("Installation of `cargo-leptos` cancelled (timeout)."),
649 Err(e) => bail!("Error during prompt: {}", e),
650 }
651
652 which("cargo-leptos").context("`cargo-leptos` still not found after installation")
654}
655
656#[cfg(target_os = "windows")]
657pub fn ensure_perl() {
658 use std::process::Command;
659 use which::which;
660
661 if which("choco").is_err() {
663 eprintln!("Chocolatey (choco) is not installed.");
664 println!("Please install Chocolatey from https://chocolatey.org/install to proceed with Perl installation.");
665 return;
666 }
667
668 println!("Perl is missing. You can install Strawberry Perl using Chocolatey (choco).");
669 println!("Suggestion: choco install strawberryperl");
670
671 match crate::e_prompts::yesno(
672 "Do you want to install Strawberry Perl using choco?",
673 Some(true), ) {
675 Ok(Some(true)) => {
676 println!("Installing Strawberry Perl...");
677 match Command::new("choco")
678 .args(["install", "strawberryperl", "-y"])
679 .spawn()
680 {
681 Ok(mut child) => {
682 child.wait().ok(); println!("Strawberry Perl installation completed.");
684 }
685 Err(e) => {
686 eprintln!("Error installing Strawberry Perl via choco: {}", e);
687 }
688 }
689 }
690 Ok(Some(false)) => {
691 println!("Strawberry Perl installation skipped.");
692 }
693 Ok(None) => {
694 println!("Installation cancelled (timeout or invalid input).");
695 }
696 Err(e) => {
697 eprintln!("Error during prompt: {}", e);
698 }
699 }
700}
701
702#[cfg(not(target_os = "windows"))]
703pub fn ensure_perl() {
704 println!("auto_sense_perl is only supported on Windows with Chocolatey.");
705}