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(workspace_parent: &Path) -> Result<(), Box<dyn Error>> {
286    if workspace_parent.join("pnpm-workspace.yaml").exists() {
287        // If this is a pnpm workspace, skip npm checks
288        println!("Skipping npm checks for pnpm workspace.");
289        return Ok(());
290    }
291    // Check if package.json exists at the workspace parent level
292    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        // Get the path to npm using `which`.
299        match which("npm") {
300            Ok(npm_path) => {
301                println!("Found npm at: {}", npm_path.display());
302
303                // Run `npm ls --depth=1` in the specified directory
304                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                        // Print the npm error output for debugging.
316                        eprintln!(
317                            "npm ls failed for directory: {}",
318                            workspace_parent.display()
319                        );
320                        eprintln!("{}", String::from_utf8_lossy(&output.stderr));
321
322                        // Run `npm install` to fix the missing dependencies
323                        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
364/// Check for a pnpm workspace and, if found, run `pnpm install`.  
365/// Returns the full path to the `pnpm` executable.
366pub fn check_pnpm_and_install(workspace_parent: &Path) -> Result<PathBuf> {
367    // if this is a pnpm workspace, install deps
368    let workspace_yaml = workspace_parent.join("pnpm-workspace.yaml");
369    if workspace_yaml.exists() {
370        // ensure pnpm is available (and install it if necessary)
371        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        //         if cfg!( target_os = "windows" ) {
391        //             #[cfg(windows)]
392        // use std::os::windows::process::CommandExt;
393        //             // WinAPI flag for “create a new console window”
394        // #[cfg(windows)]
395        // const CREATE_NEW_CONSOLE: u32 = 0x0000_0010;
396        //             println!("Running `pnpm run build:debug windows");
397        //                 // Build the command
398        //     let mut cmd = Command::new("cmd");
399        //     cmd.args(&["/C", "pnpm run build:debug"])
400        //        .current_dir(workspace_parent);
401        //     //    .stdin(Stdio::null())
402        //     //    .stdout(Stdio::inherit())
403        //     //    .stderr(Stdio::inherit());
404
405        //     // On Windows, ask for a new console window
406        //     #[cfg(windows)]
407        //     {
408        //         cmd.creation_flags(CREATE_NEW_CONSOLE);
409        //     }
410        //                 let status =
411        //     cmd.status()?;
412        // if !status.success() {
413        //     anyhow::bail!("`pnpm run build:debug` failed with {}", status);
414        // }
415        //         } else {
416        // ensure_napi_cli().ok();
417        // ensure_cross_env().ok();
418        Command::new(&pnpm)
419            .args(&["run", "build:debug"])
420            .current_dir(workspace_parent)
421            .env("CARGO", "cargo")
422            .status()?;
423        // };
424
425        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
436/// Ensure `node` is on PATH.  
437/// If missing, attempts to install Node.js using `nvm` (automated for Windows, manual prompt otherwise).  
438/// Returns the full path to the `node` executable.
439pub fn ensure_node() -> Result<PathBuf> {
440    // Check if `node` is already installed
441    if let Ok(path) = which("node") {
442        return Ok(path);
443    }
444
445    #[cfg(target_os = "windows")]
446    {
447        // On Windows, use Chocolatey to install NVM and set Node.js to LTS
448        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"]) //, "-y"])
458                    .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                // Use NVM to install and use the latest LTS version of Node.js
469                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        // On non-Windows systems, prompt the user to install Node.js manually
509        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    // Retry locating `node`
514    which("node").context("`node` still not found after installation")
515}
516
517/// Ensure the GitHub CLI (`gh`) is on PATH.  
518/// If missing, installs it using Chocolatey on Windows.  
519/// Returns the full path to the `gh` executable.
520pub fn ensure_github_gh() -> Result<PathBuf> {
521    // Check if `gh` is already installed
522    if let Ok(path) = which("gh") {
523        return Ok(path);
524    }
525    // Check if `gh.exe` exists in the default installation path
526    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        // Ensure Chocolatey is installed
533        let choco = ensure_choco()?;
534
535        // Install GitHub CLI using Chocolatey
536        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    // Retry locating `gh`
563    which("gh").context("`gh` still not found after installation")
564}
565
566/// Ensure `choco` (Chocolatey) is on PATH.  
567/// If missing, prompts the user to install Chocolatey manually.  
568/// Returns the full path to the `choco` executable.
569pub fn ensure_choco() -> Result<PathBuf> {
570    // Check if `choco` is already installed
571    if let Ok(path) = which("choco") {
572        return Ok(path);
573    }
574
575    #[cfg(target_os = "windows")]
576    {
577        // On Windows, prompt the user to install Chocolatey manually
578        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
609/// Ensure the `cargo-leptos` CLI is on PATH.  
610/// If missing, prompts the user to install it via `cargo install cargo-leptos`.  
611/// Returns the full path to the `cargo-leptos` executable.
612pub fn ensure_leptos() -> Result<PathBuf> {
613    // 1) Check if `cargo-leptos` is already on PATH
614    if let Ok(path) = which("cargo-leptos") {
615        return Ok(path);
616    }
617
618    // 2) Prompt the user to install it
619    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            // Check if `perl` is available
626            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    // 3) Retry locating `cargo-leptos`
652    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    // Check if choco is installed
661    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), // Default to yes
673    ) {
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(); // Wait for installation to complete
682                    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}