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(
286 workspace_parent: &Path,
287 be_silent: bool,
288) -> 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 if !be_silent {
296 println!(
297 "Checking for package.json in: {}",
298 workspace_parent.display()
299 );
300 }
301 if workspace_parent.join("package.json").exists() {
302 if !be_silent {
303 println!("package.json found in: {}", workspace_parent.display());
304 }
305 match which("npm") {
307 Ok(npm_path) => {
308 if !be_silent {
309 println!("Found npm at: {}", npm_path.display());
310 }
311
312 let output = Command::new(npm_path.clone())
314 .arg("ls")
315 .arg("--depth=1")
316 .current_dir(workspace_parent)
317 .output()
318 .map_err(|e| eprintln!("Failed to execute npm ls: {}", e))
319 .ok();
320
321 if let Some(output) = output {
322 if !be_silent {
323 println!("npm ls output: {}", String::from_utf8_lossy(&output.stdout));
324 }
325 if !output.status.success() {
326 eprintln!(
328 "npm ls failed for directory: {}",
329 workspace_parent.display()
330 );
331 eprintln!("{}", String::from_utf8_lossy(&output.stderr));
332
333 if !be_silent {
335 println!(
336 "Running npm install in directory: {}",
337 workspace_parent.display()
338 );
339 }
340 let install_output = Command::new(npm_path)
341 .arg("install")
342 .current_dir(workspace_parent)
343 .output()
344 .map_err(|e| eprintln!("Failed to execute npm install: {}", e))
345 .ok();
346
347 if !be_silent {
348 if let Some(install_output) = install_output {
349 println!(
350 "npm install output: {}",
351 String::from_utf8_lossy(&install_output.stdout)
352 );
353 if install_output.status.success() {
354 println!(
355 "npm install completed successfully in: {}",
356 workspace_parent.display()
357 );
358 } else {
359 eprintln!(
360 "npm install failed in directory: {}",
361 workspace_parent.display()
362 );
363 eprintln!(
364 "{}",
365 String::from_utf8_lossy(&install_output.stderr)
366 );
367 }
368 }
369 }
370 }
371 }
372 }
373 Err(_) => {
374 eprintln!("npm is not installed or not in the system PATH.");
375 return Err("npm not found".into());
376 }
377 }
378 }
379 Ok(())
380}
381
382pub fn check_pnpm_and_install(workspace_parent: &Path, be_silent: bool) -> Result<PathBuf> {
385 let workspace_yaml = workspace_parent.join("pnpm-workspace.yaml");
387 if workspace_yaml.exists() {
388 let pnpm = ensure_pnpm()?;
390 if !be_silent {
391 println!(
392 "Found pnpm-workspace.yaml in: {}",
393 workspace_parent.display()
394 );
395 println!("Running `pnpm install`…");
396 }
397
398 let status = Command::new(&pnpm)
399 .arg("install")
400 .current_dir(workspace_parent)
401 .stdin(Stdio::null())
402 .stdout(Stdio::inherit())
403 .stderr(Stdio::inherit())
404 .status()
405 .context("failed to execute `pnpm install`")?;
406
407 if !status.success() {
408 bail!("`pnpm install` failed with exit code {}", status);
409 }
410 Command::new(&pnpm)
439 .args(&["run", "build:debug"])
440 .current_dir(workspace_parent)
441 .env("CARGO", "cargo")
442 .status()?;
443 if !be_silent {
446 println!("pnpm install succeeded");
447 }
448 return Ok(pnpm);
449 } else {
450 if !be_silent {
451 println!(
452 "No pnpm-workspace.yaml found in {}, skipping `pnpm install`.",
453 workspace_parent.display()
454 );
455 }
456 }
457 Ok(PathBuf::new())
458}
459
460pub fn ensure_node() -> Result<PathBuf> {
464 if let Ok(path) = which("node") {
466 return Ok(path);
467 }
468
469 #[cfg(target_os = "windows")]
470 {
471 println!("`node` is not installed.");
473 match yesno(
474 "Do you want to install Node.js using NVM (via Chocolatey)?",
475 Some(true),
476 ) {
477 Ok(Some(true)) => {
478 println!("Installing NVM via Chocolatey...");
479 let choco = ensure_choco()?;
480 let mut child = Command::new(choco)
481 .args(&["install", "nvm"]) .stdin(Stdio::null())
483 .stdout(Stdio::inherit())
484 .stderr(Stdio::inherit())
485 .spawn()
486 .context("Failed to spawn `choco install nvm`")?;
487
488 child
489 .wait()
490 .context("Error while waiting for `choco install nvm` to finish")?;
491
492 let nvm = which("nvm").context("`nvm` not found in PATH after installation.")?;
494 let mut child = Command::new(&nvm)
495 .args(&["install", "lts"])
496 .stdin(Stdio::null())
497 .stdout(Stdio::inherit())
498 .stderr(Stdio::inherit())
499 .spawn()
500 .context("Failed to spawn `nvm install lts`")?;
501
502 child
503 .wait()
504 .context("Error while waiting for `nvm install lts` to finish")?;
505
506 let mut child = Command::new(&nvm)
507 .args(&["use", "lts"])
508 .stdin(Stdio::null())
509 .stdout(Stdio::inherit())
510 .stderr(Stdio::inherit())
511 .spawn()
512 .context("Failed to spawn `nvm use lts`")?;
513
514 child
515 .wait()
516 .context("Error while waiting for `nvm use lts` to finish")?;
517 }
518 Ok(Some(false)) => {
519 anyhow::bail!("User declined to install Node.js.");
520 }
521 Ok(None) => {
522 anyhow::bail!("Installation of Node.js cancelled (timeout).");
523 }
524 Err(e) => {
525 anyhow::bail!("Error during prompt: {}", e);
526 }
527 }
528 }
529
530 #[cfg(not(target_os = "windows"))]
531 {
532 println!("`node` is not installed. Please install Node.js manually.");
534 anyhow::bail!("Node.js installation is not automated for this platform.");
535 }
536
537 which("node").context("`node` still not found after installation")
539}
540
541pub fn ensure_github_gh() -> Result<PathBuf> {
545 if let Ok(path) = which("gh") {
547 return Ok(path);
548 }
549 let default_path = Path::new("C:\\Program Files\\GitHub CLI\\gh.exe");
551 if default_path.exists() {
552 return Ok(default_path.to_path_buf());
553 }
554 #[cfg(target_os = "windows")]
555 {
556 let choco = ensure_choco()?;
558
559 println!("Installing GitHub CLI (`gh`) via Chocolatey...");
561 if let Err(e) = ensure_admin_privileges() {
562 eprintln!("Error: {}", e);
563 return Err(e);
564 }
565 let mut child = Command::new(choco)
566 .args(&["install", "gh", "y"])
567 .stdin(Stdio::null())
568 .stdout(Stdio::inherit())
569 .stderr(Stdio::inherit())
570 .spawn()
571 .context("Failed to spawn `choco install gh`")?;
572
573 child
574 .wait()
575 .context("Error while waiting for `choco install gh` to finish")?;
576 if default_path.exists() {
577 return Ok(default_path.to_path_buf());
578 }
579 }
580
581 #[cfg(not(target_os = "windows"))]
582 {
583 anyhow::bail!("GitHub CLI installation is only automated on Windows.");
584 }
585
586 which("gh").context("`gh` still not found after installation")
588}
589
590pub fn ensure_choco() -> Result<PathBuf> {
594 if let Ok(path) = which("choco") {
596 return Ok(path);
597 }
598
599 #[cfg(target_os = "windows")]
600 {
601 println!("`choco` (Chocolatey) is not installed.");
603 println!("It is required to proceed. Do you want to install it manually?");
604 match yesno(
605 "Do you want to install Chocolatey manually by following the instructions?",
606 Some(true),
607 ) {
608 Ok(Some(true)) => {
609 println!("Please run the following command in PowerShell to install Chocolatey:");
610 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'))");
611 anyhow::bail!(
612 "Chocolatey installation is not automated. Please install it manually."
613 );
614 }
615 Ok(Some(false)) => {
616 anyhow::bail!("User declined to install Chocolatey.");
617 }
618 Ok(None) => {
619 anyhow::bail!("Installation of Chocolatey cancelled (timeout).");
620 }
621 Err(e) => {
622 anyhow::bail!("Error during prompt: {}", e);
623 }
624 }
625 }
626
627 #[cfg(not(target_os = "windows"))]
628 {
629 anyhow::bail!("Chocolatey is only supported on Windows.");
630 }
631}
632
633pub fn ensure_leptos() -> Result<PathBuf> {
637 if let Ok(path) = which("cargo-leptos") {
639 return Ok(path);
640 }
641
642 println!("`cargo-leptos` CLI not found. Install it now?");
644 match yesno(
645 "Do you want to install the `cargo-leptos` CLI via `cargo install cargo-leptos`?",
646 Some(true),
647 ) {
648 Ok(Some(true)) => {
649 if which("perl").is_err() {
651 println!("`perl` is not installed or not found in PATH.");
652 println!("OpenSSL requires `perl` for installation unless OpenSSL is already configured in your environment.");
653 println!("It is recommended to have a working `perl` distribution installed for openssl.");
654 ensure_perl();
655 }
656
657 println!("Installing `cargo-leptos` via `cargo install cargo-leptos`…");
658 let mut child = Command::new("cargo")
659 .args(&["install", "cargo-leptos"])
660 .stdin(Stdio::null())
661 .stdout(Stdio::inherit())
662 .stderr(Stdio::inherit())
663 .spawn()
664 .context("Failed to spawn `cargo install cargo-leptos`")?;
665
666 child
667 .wait()
668 .context("Error while waiting for `cargo install cargo-leptos` to finish")?;
669 }
670 Ok(Some(false)) => bail!("User skipped installing `cargo-leptos`."),
671 Ok(None) => bail!("Installation of `cargo-leptos` cancelled (timeout)."),
672 Err(e) => bail!("Error during prompt: {}", e),
673 }
674
675 which("cargo-leptos").context("`cargo-leptos` still not found after installation")
677}
678
679#[cfg(target_os = "windows")]
680pub fn ensure_perl() {
681 use std::process::Command;
682 use which::which;
683
684 if which("choco").is_err() {
686 eprintln!("Chocolatey (choco) is not installed.");
687 println!("Please install Chocolatey from https://chocolatey.org/install to proceed with Perl installation.");
688 return;
689 }
690
691 println!("Perl is missing. You can install Strawberry Perl using Chocolatey (choco).");
692 println!("Suggestion: choco install strawberryperl");
693
694 match crate::e_prompts::yesno(
695 "Do you want to install Strawberry Perl using choco?",
696 Some(true), ) {
698 Ok(Some(true)) => {
699 println!("Installing Strawberry Perl...");
700 match Command::new("choco")
701 .args(["install", "strawberryperl", "-y"])
702 .spawn()
703 {
704 Ok(mut child) => {
705 child.wait().ok(); println!("Strawberry Perl installation completed.");
707 }
708 Err(e) => {
709 eprintln!("Error installing Strawberry Perl via choco: {}", e);
710 }
711 }
712 }
713 Ok(Some(false)) => {
714 println!("Strawberry Perl installation skipped.");
715 }
716 Ok(None) => {
717 println!("Installation cancelled (timeout or invalid input).");
718 }
719 Err(e) => {
720 eprintln!("Error during prompt: {}", e);
721 }
722 }
723}
724
725#[cfg(not(target_os = "windows"))]
726pub fn ensure_perl() {
727 println!("auto_sense_perl is only supported on Windows with Chocolatey.");
728}