use crate::e_prompts::yesno;
use anyhow::{bail, Context, Result};
use std::error::Error;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use which::which;
#[cfg(windows)]
pub fn is_admin() -> bool {
let shell = "[bool]([System.Security.Principal.WindowsPrincipal][System.Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)";
let output = std::process::Command::new("powershell")
.args(["-c", shell])
.output()
.expect("Failed to execute PowerShell command");
String::from_utf8(output.stdout).unwrap_or_default().trim() == "True"
}
#[cfg(unix)]
pub fn is_admin() -> bool {
use libc::{geteuid, getuid};
unsafe { getuid() == 0 || geteuid() == 0 }
}
pub fn ensure_admin_privileges() -> Result<()> {
if !is_admin() {
return Err(anyhow::anyhow!(
"This program must be run as an administrator. Please restart it with administrative privileges."
));
}
Ok(())
}
pub fn ensure_npm() -> Result<PathBuf> {
ensure_node()?;
which("npm").context("`npm` not found in PATH. Please install Node.js and npm.")
}
pub fn ensure_napi_cli() -> Result<PathBuf, Box<dyn Error>> {
if let Ok(path) = which("napi") {
return Ok(path);
}
println!("`napi` CLI not found. Install it globally now?");
match yesno(
"Do you want to install `@napi-rs/cli` globally via npm?",
Some(true),
) {
Ok(Some(true)) => {
let npm = ensure_npm()?;
println!("Installing `@napi-rs/cli` via `npm install -g @napi-rs/cli`…");
let mut child = Command::new(npm)
.args(&["install", "-g", "@napi-rs/cli"])
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.map_err(|e| format!("Failed to spawn install command: {}", e))?;
child
.wait()
.map_err(|e| format!("Error while waiting for installation: {}", e))?;
}
Ok(Some(false)) => return Err("User skipped installing `@napi-rs/cli`".into()),
Ok(None) => return Err("Installation of `@napi-rs/cli` cancelled (timeout)".into()),
Err(e) => return Err(format!("Error during prompt: {}", e).into()),
}
which("napi").map_err(|_| "`napi` still not found after installation".into())
}
pub fn ensure_cross_env() -> Result<PathBuf, Box<dyn Error>> {
if let Ok(path) = which("cross-env") {
return Ok(path);
}
println!("`cross-env` is not installed. Install it globally now?");
match yesno(
"Do you want to install `cross-env` globally via npm?",
Some(true),
) {
Ok(Some(true)) => {
let npm = ensure_npm()?;
println!("Installing `cross-env` via `npm install -g cross-env`…");
let mut child = Command::new(npm)
.args(&["install", "-g", "cross-env"])
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.map_err(|e| format!("Failed to spawn install command: {}", e))?;
child
.wait()
.map_err(|e| format!("Error while waiting for installation: {}", e))?;
}
Ok(Some(false)) => return Err("User skipped installing `cross-env`".into()),
Ok(None) => return Err("Installation of `cross-env` cancelled (timeout)".into()),
Err(e) => return Err(format!("Error during prompt: {}", e).into()),
}
which("cross-env").map_err(|_| "`cross-env` still not found after installation".into())
}
pub fn ensure_pnpm() -> Result<PathBuf> {
ensure_node()?;
if let Ok(path) = which("pnpm") {
return Ok(path);
}
println!("`pnpm` is not installed. Install it now?");
match yesno(
"Do you want to install `pnpm` globally via npm?",
Some(true),
) {
Ok(Some(true)) => {
let npm_path = ensure_npm()?;
println!("Installing `pnpm` via `npm install -g pnpm`…");
let mut child = Command::new(npm_path)
.args(&["install", "-g", "pnpm"])
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.context("failed to spawn `npm install -g pnpm`")?;
child
.wait()
.context("error while waiting for `npm install -g pnpm` to finish")?;
}
Ok(Some(false)) => bail!("user skipped installing `pnpm`"),
Ok(None) => bail!("installation of `pnpm` cancelled (timeout)"),
Err(e) => bail!("error during prompt: {}", e),
}
which("pnpm").context("`pnpm` still not found in PATH after installation")
}
pub fn ensure_dx() -> Result<PathBuf> {
if let Ok(path) = which("dx") {
return Ok(path);
}
println!("`dx` CLI not found. Install the Dioxus CLI now?");
match yesno(
"Do you want to install the Dioxus CLI via `cargo install dioxus-cli`?",
Some(true),
) {
Ok(Some(true)) => {
println!("Installing `dioxus-cli` via `cargo install dioxus-cli`…");
let mut child = Command::new("cargo")
.args(&["install", "dioxus-cli"])
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.context("failed to spawn `cargo install dioxus-cli`")?;
child
.wait()
.context("error while waiting for `cargo install dioxus-cli` to finish")?;
}
Ok(Some(false)) => bail!("user skipped installing the Dioxus CLI"),
Ok(None) => bail!("installation of the Dioxus CLI cancelled (timeout)"),
Err(e) => bail!("error during prompt: {}", e),
}
which("dx").context("`dx` still not found in PATH after installation")
}
pub fn ensure_trunk() -> Result<PathBuf> {
if let Ok(path) = which("trunk") {
return Ok(path);
}
println!("`trunk` is not installed. Install it now?");
match yesno("Do you want to install `trunk`?", Some(true)) {
Ok(Some(true)) => {
println!("Installing `trunk` via `cargo install trunk`…");
let mut child = Command::new("cargo")
.args(&["install", "trunk"])
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.context("failed to spawn `cargo install trunk`")?;
child
.wait()
.context("failed while waiting for `cargo install trunk` to finish")?;
}
Ok(Some(false)) => {
anyhow::bail!("user skipped installing `trunk`");
}
Ok(None) => {
anyhow::bail!("installation of `trunk` cancelled (timeout)");
}
Err(e) => {
anyhow::bail!("error during prompt: {}", e);
}
}
which("trunk").context("`trunk` still not found in PATH after installation")
}
pub fn ensure_rust_script() -> Result<PathBuf> {
if let Ok(path) = which("rust-script") {
return Ok(path);
}
println!("`rust-script` is not installed. Install it now?");
match yesno("Do you want to install `rust-script`?", Some(true)) {
Ok(Some(true)) => {
println!("Installing `rust-script` via `cargo install rust-script`…");
let mut child = Command::new("cargo")
.args(&["install", "rust-script"])
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.context("failed to spawn `cargo install rust-script`")?;
child
.wait()
.context("failed while waiting for `cargo install rust-script` to finish")?;
}
Ok(Some(false)) => {
anyhow::bail!("user skipped installing `rust-script`");
}
Ok(None) => {
anyhow::bail!("installation of `rust-script` cancelled (timeout)");
}
Err(e) => {
anyhow::bail!("error during prompt: {}", e);
}
}
which("rust-script").context("`rust-script` still not found in PATH after installation")
}
pub fn check_npm_and_install(
workspace_parent: &Path,
be_silent: bool,
) -> Result<(), Box<dyn Error>> {
if workspace_parent.join("pnpm-workspace.yaml").exists() {
println!("Skipping npm checks for pnpm workspace.");
return Ok(());
}
if !be_silent {
println!(
"Checking for package.json in: {}",
workspace_parent.display()
);
}
if workspace_parent.join("package.json").exists() {
if !be_silent {
println!("package.json found in: {}", workspace_parent.display());
}
match which("npm") {
Ok(npm_path) => {
if !be_silent {
println!("Found npm at: {}", npm_path.display());
}
let output = Command::new(npm_path.clone())
.arg("ls")
.arg("--depth=1")
.current_dir(workspace_parent)
.output()
.map_err(|e| eprintln!("Failed to execute npm ls: {}", e))
.ok();
if let Some(output) = output {
if !be_silent {
println!("npm ls output: {}", String::from_utf8_lossy(&output.stdout));
}
if !output.status.success() {
eprintln!(
"npm ls failed for directory: {}",
workspace_parent.display()
);
eprintln!("{}", String::from_utf8_lossy(&output.stderr));
if !be_silent {
println!(
"Running npm install in directory: {}",
workspace_parent.display()
);
}
let install_output = Command::new(npm_path)
.arg("install")
.current_dir(workspace_parent)
.output()
.map_err(|e| eprintln!("Failed to execute npm install: {}", e))
.ok();
if !be_silent {
if let Some(install_output) = install_output {
println!(
"npm install output: {}",
String::from_utf8_lossy(&install_output.stdout)
);
if install_output.status.success() {
println!(
"npm install completed successfully in: {}",
workspace_parent.display()
);
} else {
eprintln!(
"npm install failed in directory: {}",
workspace_parent.display()
);
eprintln!(
"{}",
String::from_utf8_lossy(&install_output.stderr)
);
}
}
}
}
}
}
Err(_) => {
eprintln!("npm is not installed or not in the system PATH.");
return Err("npm not found".into());
}
}
}
Ok(())
}
pub fn check_pnpm_and_install(workspace_parent: &Path, be_silent: bool) -> Result<PathBuf> {
let workspace_yaml = workspace_parent.join("pnpm-workspace.yaml");
if workspace_yaml.exists() {
let pnpm = ensure_pnpm()?;
if !be_silent {
println!(
"Found pnpm-workspace.yaml in: {}",
workspace_parent.display()
);
println!("Running `pnpm install`…");
}
let status = Command::new(&pnpm)
.arg("install")
.current_dir(workspace_parent)
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.status()
.context("failed to execute `pnpm install`")?;
if !status.success() {
bail!("`pnpm install` failed with exit code {}", status);
}
Command::new(&pnpm)
.args(&["run", "build:debug"])
.current_dir(workspace_parent)
.env("CARGO", "cargo")
.status()?;
if !be_silent {
println!("pnpm install succeeded");
}
return Ok(pnpm);
} else {
if !be_silent {
println!(
"No pnpm-workspace.yaml found in {}, skipping `pnpm install`.",
workspace_parent.display()
);
}
}
Ok(PathBuf::new())
}
pub fn ensure_node() -> Result<PathBuf> {
if let Ok(path) = which("node") {
return Ok(path);
}
#[cfg(target_os = "windows")]
{
println!("`node` is not installed.");
match yesno(
"Do you want to install Node.js using NVM (via Chocolatey)?",
Some(true),
) {
Ok(Some(true)) => {
println!("Installing NVM via Chocolatey...");
let choco = ensure_choco()?;
let mut child = Command::new(choco)
.args(&["install", "nvm"]) .stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.context("Failed to spawn `choco install nvm`")?;
child
.wait()
.context("Error while waiting for `choco install nvm` to finish")?;
let nvm = which("nvm").context("`nvm` not found in PATH after installation.")?;
let mut child = Command::new(&nvm)
.args(&["install", "lts"])
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.context("Failed to spawn `nvm install lts`")?;
child
.wait()
.context("Error while waiting for `nvm install lts` to finish")?;
let mut child = Command::new(&nvm)
.args(&["use", "lts"])
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.context("Failed to spawn `nvm use lts`")?;
child
.wait()
.context("Error while waiting for `nvm use lts` to finish")?;
}
Ok(Some(false)) => {
anyhow::bail!("User declined to install Node.js.");
}
Ok(None) => {
anyhow::bail!("Installation of Node.js cancelled (timeout).");
}
Err(e) => {
anyhow::bail!("Error during prompt: {}", e);
}
}
}
#[cfg(not(target_os = "windows"))]
{
println!("`node` is not installed. Please install Node.js manually.");
anyhow::bail!("Node.js installation is not automated for this platform.");
}
which("node").context("`node` still not found after installation")
}
pub fn ensure_github_gh() -> Result<PathBuf> {
if let Ok(path) = which("gh") {
return Ok(path);
}
let default_path = Path::new("C:\\Program Files\\GitHub CLI\\gh.exe");
if default_path.exists() {
return Ok(default_path.to_path_buf());
}
#[cfg(target_os = "windows")]
{
let choco = ensure_choco()?;
println!("Installing GitHub CLI (`gh`) via Chocolatey...");
if let Err(e) = ensure_admin_privileges() {
eprintln!("Error: {}", e);
return Err(e);
}
let mut child = Command::new(choco)
.args(&["install", "gh", "y"])
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.context("Failed to spawn `choco install gh`")?;
child
.wait()
.context("Error while waiting for `choco install gh` to finish")?;
if default_path.exists() {
return Ok(default_path.to_path_buf());
}
}
#[cfg(not(target_os = "windows"))]
{
anyhow::bail!("GitHub CLI installation is only automated on Windows.");
}
which("gh").context("`gh` still not found after installation")
}
pub fn ensure_choco() -> Result<PathBuf> {
if let Ok(path) = which("choco") {
return Ok(path);
}
#[cfg(target_os = "windows")]
{
println!("`choco` (Chocolatey) is not installed.");
println!("It is required to proceed. Do you want to install it manually?");
match yesno(
"Do you want to install Chocolatey manually by following the instructions?",
Some(true),
) {
Ok(Some(true)) => {
println!("Please run the following command in PowerShell to install Chocolatey:");
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'))");
anyhow::bail!(
"Chocolatey installation is not automated. Please install it manually."
);
}
Ok(Some(false)) => {
anyhow::bail!("User declined to install Chocolatey.");
}
Ok(None) => {
anyhow::bail!("Installation of Chocolatey cancelled (timeout).");
}
Err(e) => {
anyhow::bail!("Error during prompt: {}", e);
}
}
}
#[cfg(not(target_os = "windows"))]
{
anyhow::bail!("Chocolatey is only supported on Windows.");
}
}
pub fn ensure_leptos() -> Result<PathBuf> {
if let Ok(path) = which("cargo-leptos") {
return Ok(path);
}
println!("`cargo-leptos` CLI not found. Install it now?");
match yesno(
"Do you want to install the `cargo-leptos` CLI via `cargo install cargo-leptos`?",
Some(true),
) {
Ok(Some(true)) => {
if which("perl").is_err() {
println!("`perl` is not installed or not found in PATH.");
println!("OpenSSL requires `perl` for installation unless OpenSSL is already configured in your environment.");
println!("It is recommended to have a working `perl` distribution installed for openssl.");
ensure_perl();
}
println!("Installing `cargo-leptos` via `cargo install cargo-leptos`…");
let mut child = Command::new("cargo")
.args(&["install", "cargo-leptos"])
.stdin(Stdio::null())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.spawn()
.context("Failed to spawn `cargo install cargo-leptos`")?;
child
.wait()
.context("Error while waiting for `cargo install cargo-leptos` to finish")?;
}
Ok(Some(false)) => bail!("User skipped installing `cargo-leptos`."),
Ok(None) => bail!("Installation of `cargo-leptos` cancelled (timeout)."),
Err(e) => bail!("Error during prompt: {}", e),
}
which("cargo-leptos").context("`cargo-leptos` still not found after installation")
}
#[cfg(target_os = "windows")]
pub fn ensure_perl() {
use std::process::Command;
use which::which;
if which("choco").is_err() {
eprintln!("Chocolatey (choco) is not installed.");
println!("Please install Chocolatey from https://chocolatey.org/install to proceed with Perl installation.");
return;
}
println!("Perl is missing. You can install Strawberry Perl using Chocolatey (choco).");
println!("Suggestion: choco install strawberryperl");
match crate::e_prompts::yesno(
"Do you want to install Strawberry Perl using choco?",
Some(true), ) {
Ok(Some(true)) => {
println!("Installing Strawberry Perl...");
match Command::new("choco")
.args(["install", "strawberryperl", "-y"])
.spawn()
{
Ok(mut child) => {
child.wait().ok(); println!("Strawberry Perl installation completed.");
}
Err(e) => {
eprintln!("Error installing Strawberry Perl via choco: {}", e);
}
}
}
Ok(Some(false)) => {
println!("Strawberry Perl installation skipped.");
}
Ok(None) => {
println!("Installation cancelled (timeout or invalid input).");
}
Err(e) => {
eprintln!("Error during prompt: {}", e);
}
}
}
#[cfg(not(target_os = "windows"))]
pub fn ensure_perl() {
println!("auto_sense_perl is only supported on Windows with Chocolatey.");
}