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#[cfg(unix)]
21pub fn is_admin() -> bool {
22 use libc::{geteuid, getuid};
23 unsafe { getuid() == 0 || geteuid() == 0 }
24}
25
26pub fn ensure_admin_privileges() -> Result<()> {
29 if !is_admin() {
30 return Err(anyhow::anyhow!(
31 "This program must be run as an administrator. Please restart it with administrative privileges."
32 ));
33 }
34 Ok(())
35}
36pub fn ensure_npm() -> Result<PathBuf> {
40 ensure_node()?;
42 which("npm").context("`npm` not found in PATH. Please install Node.js and npm.")
43}
44
45pub fn ensure_napi_cli() -> Result<PathBuf, Box<dyn Error>> {
49 if let Ok(path) = which("napi") {
51 return Ok(path);
52 }
53
54 println!("`napi` CLI not found. Install it globally now?");
56 match yesno(
57 "Do you want to install `@napi-rs/cli` globally via npm?",
58 Some(true),
59 ) {
60 Ok(Some(true)) => {
61 let npm = ensure_npm()?;
62 println!("Installing `@napi-rs/cli` via `npm install -g @napi-rs/cli`…");
63 let mut child = Command::new(npm)
64 .args(&["install", "-g", "@napi-rs/cli"])
65 .stdin(Stdio::null())
66 .stdout(Stdio::inherit())
67 .stderr(Stdio::inherit())
68 .spawn()
69 .map_err(|e| format!("Failed to spawn install command: {}", e))?;
70
71 child
72 .wait()
73 .map_err(|e| format!("Error while waiting for installation: {}", e))?;
74 }
75 Ok(Some(false)) => return Err("User skipped installing `@napi-rs/cli`".into()),
76 Ok(None) => return Err("Installation of `@napi-rs/cli` cancelled (timeout)".into()),
77 Err(e) => return Err(format!("Error during prompt: {}", e).into()),
78 }
79
80 which("napi").map_err(|_| "`napi` still not found after installation".into())
82}
83
84pub fn ensure_cross_env() -> Result<PathBuf, Box<dyn Error>> {
88 if let Ok(path) = which("cross-env") {
90 return Ok(path);
91 }
92
93 println!("`cross-env` is not installed. Install it globally now?");
95 match yesno(
96 "Do you want to install `cross-env` globally via npm?",
97 Some(true),
98 ) {
99 Ok(Some(true)) => {
100 let npm = ensure_npm()?;
102 println!("Installing `cross-env` via `npm install -g cross-env`…");
103 let mut child = Command::new(npm)
104 .args(&["install", "-g", "cross-env"])
105 .stdin(Stdio::null())
106 .stdout(Stdio::inherit())
107 .stderr(Stdio::inherit())
108 .spawn()
109 .map_err(|e| format!("Failed to spawn install command: {}", e))?;
110
111 child
113 .wait()
114 .map_err(|e| format!("Error while waiting for installation: {}", e))?;
115 }
116 Ok(Some(false)) => return Err("User skipped installing `cross-env`".into()),
117 Ok(None) => return Err("Installation of `cross-env` cancelled (timeout)".into()),
118 Err(e) => return Err(format!("Error during prompt: {}", e).into()),
119 }
120
121 which("cross-env").map_err(|_| "`cross-env` still not found after installation".into())
123}
124pub fn ensure_pnpm() -> Result<PathBuf> {
129 ensure_node()?;
131
132 if let Ok(path) = which("pnpm") {
134 return Ok(path);
135 }
136
137 println!("`pnpm` is not installed. Install it now?");
139 match yesno(
140 "Do you want to install `pnpm` globally via npm?",
141 Some(true),
142 ) {
143 Ok(Some(true)) => {
144 let npm_path = ensure_npm()?;
146 println!("Installing `pnpm` via `npm install -g pnpm`…");
147
148 let mut child = Command::new(npm_path)
149 .args(&["install", "-g", "pnpm"])
150 .stdin(Stdio::null())
151 .stdout(Stdio::inherit())
152 .stderr(Stdio::inherit())
153 .spawn()
154 .context("failed to spawn `npm install -g pnpm`")?;
155
156 child
157 .wait()
158 .context("error while waiting for `npm install -g pnpm` to finish")?;
159 }
160 Ok(Some(false)) => bail!("user skipped installing `pnpm`"),
161 Ok(None) => bail!("installation of `pnpm` cancelled (timeout)"),
162 Err(e) => bail!("error during prompt: {}", e),
163 }
164
165 which("pnpm").context("`pnpm` still not found in PATH after installation")
167}
168
169pub fn ensure_dx() -> Result<PathBuf> {
173 if let Ok(path) = which("dx") {
175 return Ok(path);
176 }
177
178 println!("`dx` CLI not found. Install the Dioxus CLI now?");
180 match yesno(
181 "Do you want to install the Dioxus CLI via `cargo install dioxus-cli`?",
182 Some(true),
183 ) {
184 Ok(Some(true)) => {
185 println!("Installing `dioxus-cli` via `cargo install dioxus-cli`…");
186 let mut child = Command::new("cargo")
187 .args(&["install", "dioxus-cli"])
188 .stdin(Stdio::null())
189 .stdout(Stdio::inherit())
190 .stderr(Stdio::inherit())
191 .spawn()
192 .context("failed to spawn `cargo install dioxus-cli`")?;
193
194 child
195 .wait()
196 .context("error while waiting for `cargo install dioxus-cli` to finish")?;
197 }
198 Ok(Some(false)) => bail!("user skipped installing the Dioxus CLI"),
199 Ok(None) => bail!("installation of the Dioxus CLI cancelled (timeout)"),
200 Err(e) => bail!("error during prompt: {}", e),
201 }
202
203 which("dx").context("`dx` still not found in PATH after installation")
205}
206
207pub fn ensure_trunk() -> Result<PathBuf> {
210 if let Ok(path) = which("trunk") {
212 return Ok(path);
213 }
214
215 println!("`trunk` is not installed. Install it now?");
217 match yesno("Do you want to install `trunk`?", Some(true)) {
218 Ok(Some(true)) => {
219 println!("Installing `trunk` via `cargo install trunk`…");
220 let mut child = Command::new("cargo")
221 .args(&["install", "trunk"])
222 .stdin(Stdio::null())
223 .stdout(Stdio::inherit())
224 .stderr(Stdio::inherit())
225 .spawn()
226 .context("failed to spawn `cargo install trunk`")?;
227
228 child
229 .wait()
230 .context("failed while waiting for `cargo install trunk` to finish")?;
231 }
232 Ok(Some(false)) => {
233 anyhow::bail!("user skipped installing `trunk`");
234 }
235 Ok(None) => {
236 anyhow::bail!("installation of `trunk` cancelled (timeout)");
237 }
238 Err(e) => {
239 anyhow::bail!("error during prompt: {}", e);
240 }
241 }
242
243 which("trunk").context("`trunk` still not found in PATH after installation")
245}
246
247pub fn ensure_rust_script() -> Result<PathBuf> {
250 if let Ok(path) = which("rust-script") {
252 return Ok(path);
253 }
254
255 println!("`rust-script` is not installed. Install it now?");
257 match yesno("Do you want to install `rust-script`?", Some(true)) {
258 Ok(Some(true)) => {
259 println!("Installing `rust-script` via `cargo install rust-script`…");
260 let mut child = Command::new("cargo")
261 .args(&["install", "rust-script"])
262 .stdin(Stdio::null())
263 .stdout(Stdio::inherit())
264 .stderr(Stdio::inherit())
265 .spawn()
266 .context("failed to spawn `cargo install rust-script`")?;
267
268 child
269 .wait()
270 .context("failed while waiting for `cargo install rust-script` to finish")?;
271 }
272 Ok(Some(false)) => {
273 anyhow::bail!("user skipped installing `rust-script`");
274 }
275 Ok(None) => {
276 anyhow::bail!("installation of `rust-script` cancelled (timeout)");
277 }
278 Err(e) => {
279 anyhow::bail!("error during prompt: {}", e);
280 }
281 }
282 which("rust-script").context("`rust-script` still not found in PATH after installation")
283}
284pub fn check_npm_and_install(workspace_parent: &Path) -> Result<(), Box<dyn Error>> {
286 if workspace_parent.join("pnpm-workspace.yaml").exists() {
287 println!("Skipping npm checks for pnpm workspace.");
289 return Ok(());
290 }
291 println!(
293 "Checking for package.json in: {}",
294 workspace_parent.display()
295 );
296 if workspace_parent.join("package.json").exists() {
297 println!("package.json found in: {}", workspace_parent.display());
298 match which("npm") {
300 Ok(npm_path) => {
301 println!("Found npm at: {}", npm_path.display());
302
303 let output = Command::new(npm_path.clone())
305 .arg("ls")
306 .arg("--depth=1")
307 .current_dir(workspace_parent)
308 .output()
309 .map_err(|e| eprintln!("Failed to execute npm ls: {}", e))
310 .ok();
311
312 if let Some(output) = output {
313 println!("npm ls output: {}", String::from_utf8_lossy(&output.stdout));
314 if !output.status.success() {
315 eprintln!(
317 "npm ls failed for directory: {}",
318 workspace_parent.display()
319 );
320 eprintln!("{}", String::from_utf8_lossy(&output.stderr));
321
322 println!(
324 "Running npm install in directory: {}",
325 workspace_parent.display()
326 );
327 let install_output = Command::new(npm_path)
328 .arg("install")
329 .current_dir(workspace_parent)
330 .output()
331 .map_err(|e| eprintln!("Failed to execute npm install: {}", e))
332 .ok();
333
334 if let Some(install_output) = install_output {
335 println!(
336 "npm install output: {}",
337 String::from_utf8_lossy(&install_output.stdout)
338 );
339 if install_output.status.success() {
340 println!(
341 "npm install completed successfully in: {}",
342 workspace_parent.display()
343 );
344 } else {
345 eprintln!(
346 "npm install failed in directory: {}",
347 workspace_parent.display()
348 );
349 eprintln!("{}", String::from_utf8_lossy(&install_output.stderr));
350 }
351 }
352 }
353 }
354 }
355 Err(_) => {
356 eprintln!("npm is not installed or not in the system PATH.");
357 return Err("npm not found".into());
358 }
359 }
360 }
361 Ok(())
362}
363
364pub fn check_pnpm_and_install(workspace_parent: &Path) -> Result<PathBuf> {
367 let workspace_yaml = workspace_parent.join("pnpm-workspace.yaml");
369 if workspace_yaml.exists() {
370 let pnpm = ensure_pnpm()?;
372 println!(
373 "Found pnpm-workspace.yaml in: {}",
374 workspace_parent.display()
375 );
376 println!("Running `pnpm install`…");
377
378 let status = Command::new(&pnpm)
379 .arg("install")
380 .current_dir(workspace_parent)
381 .stdin(Stdio::null())
382 .stdout(Stdio::inherit())
383 .stderr(Stdio::inherit())
384 .status()
385 .context("failed to execute `pnpm install`")?;
386
387 if !status.success() {
388 bail!("`pnpm install` failed with exit code {}", status);
389 }
390 Command::new(&pnpm)
419 .args(&["run", "build:debug"])
420 .current_dir(workspace_parent)
421 .env("CARGO", "cargo")
422 .status()?;
423 println!("✅ pnpm install succeeded");
426 return Ok(pnpm);
427 } else {
428 println!(
429 "No pnpm-workspace.yaml found in {}, skipping `pnpm install`.",
430 workspace_parent.display()
431 );
432 }
433 Ok(PathBuf::new())
434}
435
436pub fn ensure_node() -> Result<PathBuf> {
440 if let Ok(path) = which("node") {
442 return Ok(path);
443 }
444
445 #[cfg(target_os = "windows")]
446 {
447 println!("`node` is not installed.");
449 match yesno(
450 "Do you want to install Node.js using NVM (via Chocolatey)?",
451 Some(true),
452 ) {
453 Ok(Some(true)) => {
454 println!("Installing NVM via Chocolatey...");
455 let choco = ensure_choco()?;
456 let mut child = Command::new(choco)
457 .args(&["install", "nvm"]) .stdin(Stdio::null())
459 .stdout(Stdio::inherit())
460 .stderr(Stdio::inherit())
461 .spawn()
462 .context("Failed to spawn `choco install nvm`")?;
463
464 child
465 .wait()
466 .context("Error while waiting for `choco install nvm` to finish")?;
467
468 let nvm = which("nvm").context("`nvm` not found in PATH after installation.")?;
470 let mut child = Command::new(&nvm)
471 .args(&["install", "lts"])
472 .stdin(Stdio::null())
473 .stdout(Stdio::inherit())
474 .stderr(Stdio::inherit())
475 .spawn()
476 .context("Failed to spawn `nvm install lts`")?;
477
478 child
479 .wait()
480 .context("Error while waiting for `nvm install lts` to finish")?;
481
482 let mut child = Command::new(&nvm)
483 .args(&["use", "lts"])
484 .stdin(Stdio::null())
485 .stdout(Stdio::inherit())
486 .stderr(Stdio::inherit())
487 .spawn()
488 .context("Failed to spawn `nvm use lts`")?;
489
490 child
491 .wait()
492 .context("Error while waiting for `nvm use lts` to finish")?;
493 }
494 Ok(Some(false)) => {
495 anyhow::bail!("User declined to install Node.js.");
496 }
497 Ok(None) => {
498 anyhow::bail!("Installation of Node.js cancelled (timeout).");
499 }
500 Err(e) => {
501 anyhow::bail!("Error during prompt: {}", e);
502 }
503 }
504 }
505
506 #[cfg(not(target_os = "windows"))]
507 {
508 println!("`node` is not installed. Please install Node.js manually.");
510 anyhow::bail!("Node.js installation is not automated for this platform.");
511 }
512
513 which("node").context("`node` still not found after installation")
515}
516
517pub fn ensure_github_gh() -> Result<PathBuf> {
521 if let Ok(path) = which("gh") {
523 return Ok(path);
524 }
525 let default_path = Path::new("C:\\Program Files\\GitHub CLI\\gh.exe");
527 if default_path.exists() {
528 return Ok(default_path.to_path_buf());
529 }
530 #[cfg(target_os = "windows")]
531 {
532 let choco = ensure_choco()?;
534
535 println!("Installing GitHub CLI (`gh`) via Chocolatey...");
537 if let Err(e) = ensure_admin_privileges() {
538 eprintln!("Error: {}", e);
539 return Err(e);
540 }
541 let mut child = Command::new(choco)
542 .args(&["install", "gh", "y"])
543 .stdin(Stdio::null())
544 .stdout(Stdio::inherit())
545 .stderr(Stdio::inherit())
546 .spawn()
547 .context("Failed to spawn `choco install gh`")?;
548
549 child
550 .wait()
551 .context("Error while waiting for `choco install gh` to finish")?;
552 if default_path.exists() {
553 return Ok(default_path.to_path_buf());
554 }
555 }
556
557 #[cfg(not(target_os = "windows"))]
558 {
559 anyhow::bail!("GitHub CLI installation is only automated on Windows.");
560 }
561
562 which("gh").context("`gh` still not found after installation")
564}
565
566pub fn ensure_choco() -> Result<PathBuf> {
570 if let Ok(path) = which("choco") {
572 return Ok(path);
573 }
574
575 #[cfg(target_os = "windows")]
576 {
577 println!("`choco` (Chocolatey) is not installed.");
579 println!("It is required to proceed. Do you want to install it manually?");
580 match yesno(
581 "Do you want to install Chocolatey manually by following the instructions?",
582 Some(true),
583 ) {
584 Ok(Some(true)) => {
585 println!("Please run the following command in PowerShell to install Chocolatey:");
586 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'))");
587 anyhow::bail!(
588 "Chocolatey installation is not automated. Please install it manually."
589 );
590 }
591 Ok(Some(false)) => {
592 anyhow::bail!("User declined to install Chocolatey.");
593 }
594 Ok(None) => {
595 anyhow::bail!("Installation of Chocolatey cancelled (timeout).");
596 }
597 Err(e) => {
598 anyhow::bail!("Error during prompt: {}", e);
599 }
600 }
601 }
602
603 #[cfg(not(target_os = "windows"))]
604 {
605 anyhow::bail!("Chocolatey is only supported on Windows.");
606 }
607}
608
609pub fn ensure_leptos() -> Result<PathBuf> {
613 if let Ok(path) = which("cargo-leptos") {
615 return Ok(path);
616 }
617
618 println!("`cargo-leptos` CLI not found. Install it now?");
620 match yesno(
621 "Do you want to install the `cargo-leptos` CLI via `cargo install cargo-leptos`?",
622 Some(true),
623 ) {
624 Ok(Some(true)) => {
625 if which("perl").is_err() {
627 println!("`perl` is not installed or not found in PATH.");
628 println!("OpenSSL requires `perl` for installation unless OpenSSL is already configured in your environment.");
629 println!("It is recommended to have a working `perl` distribution installed for openssl.");
630 ensure_perl();
631 }
632
633 println!("Installing `cargo-leptos` via `cargo install cargo-leptos`…");
634 let mut child = Command::new("cargo")
635 .args(&["install", "cargo-leptos"])
636 .stdin(Stdio::null())
637 .stdout(Stdio::inherit())
638 .stderr(Stdio::inherit())
639 .spawn()
640 .context("Failed to spawn `cargo install cargo-leptos`")?;
641
642 child
643 .wait()
644 .context("Error while waiting for `cargo install cargo-leptos` to finish")?;
645 }
646 Ok(Some(false)) => bail!("User skipped installing `cargo-leptos`."),
647 Ok(None) => bail!("Installation of `cargo-leptos` cancelled (timeout)."),
648 Err(e) => bail!("Error during prompt: {}", e),
649 }
650
651 which("cargo-leptos").context("`cargo-leptos` still not found after installation")
653}
654
655#[cfg(target_os = "windows")]
656pub fn ensure_perl() {
657 use std::process::Command;
658 use which::which;
659
660 if which("choco").is_err() {
662 eprintln!("Chocolatey (choco) is not installed.");
663 println!("Please install Chocolatey from https://chocolatey.org/install to proceed with Perl installation.");
664 return;
665 }
666
667 println!("Perl is missing. You can install Strawberry Perl using Chocolatey (choco).");
668 println!("Suggestion: choco install strawberryperl");
669
670 match crate::e_prompts::yesno(
671 "Do you want to install Strawberry Perl using choco?",
672 Some(true), ) {
674 Ok(Some(true)) => {
675 println!("Installing Strawberry Perl...");
676 match Command::new("choco")
677 .args(["install", "strawberryperl", "-y"])
678 .spawn()
679 {
680 Ok(mut child) => {
681 child.wait().ok(); println!("Strawberry Perl installation completed.");
683 }
684 Err(e) => {
685 eprintln!("Error installing Strawberry Perl via choco: {}", e);
686 }
687 }
688 }
689 Ok(Some(false)) => {
690 println!("Strawberry Perl installation skipped.");
691 }
692 Ok(None) => {
693 println!("Installation cancelled (timeout or invalid input).");
694 }
695 Err(e) => {
696 eprintln!("Error during prompt: {}", e);
697 }
698 }
699}
700
701#[cfg(not(target_os = "windows"))]
702pub fn ensure_perl() {
703 println!("auto_sense_perl is only supported on Windows with Chocolatey.");
704}