cargo_e/
e_installer.rs

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// https://github.com/ahaoboy/is-admin
9#[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// https://github.com/ahaoboy/is-admin
20#[cfg(unix)]
21pub fn is_admin() -> bool {
22    use libc::{geteuid, getuid};
23    unsafe { getuid() == 0 || geteuid() == 0 }
24}
25
26/// Check if the program is running as an administrator.
27/// Returns an error if the program is not running with administrative privileges.
28pub 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}
36/// Ensure `npm` is on PATH.  
37/// Ensures Node.js is installed first.  
38/// Returns the full path to the `npm` executable, or an error.
39pub fn ensure_npm() -> Result<PathBuf> {
40    // Ensure Node.js is installed
41    ensure_node()?;
42    which("npm").context("`npm` not found in PATH. Please install Node.js and npm.")
43}
44
45/// Ensure the `napi` CLI is on PATH (provided by `@napi-rs/cli`).  
46/// If missing, prompts the user and installs it globally via `npm install -g @napi-rs/cli`.  
47/// Returns the full path to the `napi` executable.
48pub fn ensure_napi_cli() -> Result<PathBuf, Box<dyn Error>> {
49    // 1) Already installed?
50    if let Ok(path) = which("napi") {
51        return Ok(path);
52    }
53
54    // 2) Prompt the user to install it via npm
55    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    // 3) Retry locating `napi`
81    which("napi").map_err(|_| "`napi` still not found after installation".into())
82}
83
84/// Ensure `cross-env` is on PATH.  
85/// If it’s missing, prompts the user and installs it globally via `npm install -g cross-env`.  
86/// Returns the full path to the `cross-env` executable.
87pub fn ensure_cross_env() -> Result<PathBuf, Box<dyn Error>> {
88    // 1) Already installed?
89    if let Ok(path) = which("cross-env") {
90        return Ok(path);
91    }
92
93    // 2) Prompt the user to install it via npm
94    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            // Make sure npm is available
101            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            // Wait for the installation to finish
112            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    // 3) Retry locating `cross-env`
122    which("cross-env").map_err(|_| "`cross-env` still not found after installation".into())
123}
124/// Ensure `pnpm` is on PATH.  
125/// Ensures Node.js is installed first.  
126/// If it’s missing, will use `npm` (via `ensure_npm`) to install `pnpm` globally.  
127/// Returns the full path to the `pnpm` executable.
128pub fn ensure_pnpm() -> Result<PathBuf> {
129    // Ensure Node.js is installed
130    ensure_node()?;
131
132    // Check if `pnpm` is already installed
133    if let Ok(path) = which("pnpm") {
134        return Ok(path);
135    }
136
137    // Otherwise, prompt the user to install it via npm
138    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            // Make sure we have npm
145            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    // Retry locating `pnpm`
166    which("pnpm").context("`pnpm` still not found in PATH after installation")
167}
168
169/// Ensure the `dx` CLI (the Dioxus helper) is on PATH.
170/// If missing, prompts the user to install the Dioxus CLI via `cargo install dioxus-cli`.
171/// Returns the full path to the `dx` executable.
172pub fn ensure_dx() -> Result<PathBuf> {
173    // 1) Check if `dx` is already on PATH
174    if let Ok(path) = which("dx") {
175        return Ok(path);
176    }
177
178    // 2) Prompt the user to install it
179    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    // 3) Retry locating `dx`
204    which("dx").context("`dx` still not found in PATH after installation")
205}
206
207/// Ensure `trunk` is on PATH.  
208/// Returns the full path to the `trunk` executable, or an error.
209pub fn ensure_trunk() -> Result<PathBuf> {
210    // 1) First try to locate `trunk`
211    if let Ok(path) = which("trunk") {
212        return Ok(path);
213    }
214
215    // 2) Prompt the user to install it
216    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    // 3) Re‐try locating `trunk`
244    which("trunk").context("`trunk` still not found in PATH after installation")
245}
246
247/// Ensure `rust-script` is on PATH.  
248/// Returns the full path to the `rust-script` executable, or an error.
249pub fn ensure_rust_script() -> Result<PathBuf> {
250    // 1) First try to locate `trunk`
251    if let Ok(path) = which("rust-script") {
252        return Ok(path);
253    }
254
255    // 2) Prompt the user to install it
256    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}
284// Helper function to check for package.json and run npm install if needed
285pub 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        // If this is a pnpm workspace, skip npm checks
291        println!("Skipping npm checks for pnpm workspace.");
292        return Ok(());
293    }
294    // Check if package.json exists at the workspace parent level
295    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        // Get the path to npm using `which`.
306        match which("npm") {
307            Ok(npm_path) => {
308                if !be_silent {
309                    println!("Found npm at: {}", npm_path.display());
310                }
311
312                // Run `npm ls --depth=1` in the specified directory
313                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                        // Print the npm error output for debugging.
327                        eprintln!(
328                            "npm ls failed for directory: {}",
329                            workspace_parent.display()
330                        );
331                        eprintln!("{}", String::from_utf8_lossy(&output.stderr));
332
333                        // Run `npm install` to fix the missing dependencies
334                        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
382/// Check for a pnpm workspace and, if found, run `pnpm install`.  
383/// Returns the full path to the `pnpm` executable.
384pub fn check_pnpm_and_install(workspace_parent: &Path, be_silent: bool) -> Result<PathBuf> {
385    // if this is a pnpm workspace, install deps
386    let workspace_yaml = workspace_parent.join("pnpm-workspace.yaml");
387    if workspace_yaml.exists() {
388        // ensure pnpm is available (and install it if necessary)
389        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        //         if cfg!( target_os = "windows" ) {
411        //             #[cfg(windows)]
412        // use std::os::windows::process::CommandExt;
413        //             // WinAPI flag for “create a new console window”
414        // #[cfg(windows)]
415        // const CREATE_NEW_CONSOLE: u32 = 0x0000_0010;
416        //             println!("Running `pnpm run build:debug windows");
417        //                 // Build the command
418        //     let mut cmd = Command::new("cmd");
419        //     cmd.args(&["/C", "pnpm run build:debug"])
420        //        .current_dir(workspace_parent);
421        //     //    .stdin(Stdio::null())
422        //     //    .stdout(Stdio::inherit())
423        //     //    .stderr(Stdio::inherit());
424
425        //     // On Windows, ask for a new console window
426        //     #[cfg(windows)]
427        //     {
428        //         cmd.creation_flags(CREATE_NEW_CONSOLE);
429        //     }
430        //                 let status =
431        //     cmd.status()?;
432        // if !status.success() {
433        //     anyhow::bail!("`pnpm run build:debug` failed with {}", status);
434        // }
435        //         } else {
436        // ensure_napi_cli().ok();
437        // ensure_cross_env().ok();
438        Command::new(&pnpm)
439            .args(&["run", "build:debug"])
440            .current_dir(workspace_parent)
441            .env("CARGO", "cargo")
442            .status()?;
443        // };
444
445        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
460/// Ensure `node` is on PATH.  
461/// If missing, attempts to install Node.js using `nvm` (automated for Windows, manual prompt otherwise).  
462/// Returns the full path to the `node` executable.
463pub fn ensure_node() -> Result<PathBuf> {
464    // Check if `node` is already installed
465    if let Ok(path) = which("node") {
466        return Ok(path);
467    }
468
469    #[cfg(target_os = "windows")]
470    {
471        // On Windows, use Chocolatey to install NVM and set Node.js to LTS
472        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"]) //, "-y"])
482                    .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                // Use NVM to install and use the latest LTS version of Node.js
493                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        // On non-Windows systems, prompt the user to install Node.js manually
533        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    // Retry locating `node`
538    which("node").context("`node` still not found after installation")
539}
540
541/// Ensure the GitHub CLI (`gh`) is on PATH.  
542/// If missing, installs it using Chocolatey on Windows.  
543/// Returns the full path to the `gh` executable.
544pub fn ensure_github_gh() -> Result<PathBuf> {
545    // Check if `gh` is already installed
546    if let Ok(path) = which("gh") {
547        return Ok(path);
548    }
549    // Check if `gh.exe` exists in the default installation path
550    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        // Ensure Chocolatey is installed
557        let choco = ensure_choco()?;
558
559        // Install GitHub CLI using Chocolatey
560        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    // Retry locating `gh`
587    which("gh").context("`gh` still not found after installation")
588}
589
590/// Ensure `choco` (Chocolatey) is on PATH.  
591/// If missing, prompts the user to install Chocolatey manually.  
592/// Returns the full path to the `choco` executable.
593pub fn ensure_choco() -> Result<PathBuf> {
594    // Check if `choco` is already installed
595    if let Ok(path) = which("choco") {
596        return Ok(path);
597    }
598
599    #[cfg(target_os = "windows")]
600    {
601        // On Windows, prompt the user to install Chocolatey manually
602        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
633/// Ensure the `cargo-leptos` CLI is on PATH.  
634/// If missing, prompts the user to install it via `cargo install cargo-leptos`.  
635/// Returns the full path to the `cargo-leptos` executable.
636pub fn ensure_leptos() -> Result<PathBuf> {
637    // 1) Check if `cargo-leptos` is already on PATH
638    if let Ok(path) = which("cargo-leptos") {
639        return Ok(path);
640    }
641
642    // 2) Prompt the user to install it
643    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            // Check if `perl` is available
650            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    // 3) Retry locating `cargo-leptos`
676    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    // Check if choco is installed
685    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), // Default to yes
697    ) {
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(); // Wait for installation to complete
706                    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}