| 
 | 1 | +// SPDX-License-Identifier: GPL-3.0  | 
 | 2 | + | 
 | 3 | +use crate::{  | 
 | 4 | +	cli::{self, traits::Confirm},  | 
 | 5 | +	common::binary::SemanticVersion,  | 
 | 6 | +};  | 
 | 7 | +use anyhow::{Result, anyhow};  | 
 | 8 | +use duct::cmd;  | 
 | 9 | +use std::path::PathBuf;  | 
 | 10 | + | 
 | 11 | +/// Ensure Bun exists and return the absolute path to the `bun` binary.  | 
 | 12 | +///  | 
 | 13 | +/// # Arguments  | 
 | 14 | +/// * `cli`: Command line interface.  | 
 | 15 | +pub fn ensure_bun(cli: &mut impl cli::traits::Cli) -> Result<PathBuf> {  | 
 | 16 | +	if let Some(path) = which("bun") {  | 
 | 17 | +		return Ok(PathBuf::from(path));  | 
 | 18 | +	}  | 
 | 19 | +	if !cli  | 
 | 20 | +		.confirm("📦 Do you want to proceed with the installation of the following package: bun ?")  | 
 | 21 | +		.initial_value(true)  | 
 | 22 | +		.interact()?  | 
 | 23 | +	{  | 
 | 24 | +		return Err(anyhow::anyhow!("🚫 You have cancelled the installation process."));  | 
 | 25 | +	}  | 
 | 26 | +	// Install Bun (macOS/Linux official script)  | 
 | 27 | +	cmd("bash", vec!["-lc", r#"curl -fsSL https://bun.sh/install | bash"#]).run()?;  | 
 | 28 | +	// Use the default install location from the official installer  | 
 | 29 | +	let home = std::env::var("HOME").map_err(|_| anyhow!("HOME not set"))?;  | 
 | 30 | +	let bun_abs = PathBuf::from(format!("{home}/.bun/bin/bun"));  | 
 | 31 | + | 
 | 32 | +	if !bun_abs.exists() {  | 
 | 33 | +		return Err(anyhow!(format!(  | 
 | 34 | +			"Bun installed but not found at {}. Open a new shell or add it to PATH.",  | 
 | 35 | +			bun_abs.display()  | 
 | 36 | +		)));  | 
 | 37 | +	}  | 
 | 38 | +	Ok(bun_abs)  | 
 | 39 | +}  | 
 | 40 | + | 
 | 41 | +/// Require Node v20+ to be installed.  | 
 | 42 | +pub fn ensure_node_v20() -> Result<()> {  | 
 | 43 | +	let v = SemanticVersion::try_from("node".to_string()).map_err(|_| {  | 
 | 44 | +		anyhow!("NodeJS v20+ required but not found. Install from https://nodejs.org and re-run.")  | 
 | 45 | +	})?;  | 
 | 46 | +	if v.0 < 20 {  | 
 | 47 | +		return Err(anyhow!(format!(  | 
 | 48 | +			"NodeJS v20+ required. Detected {}.{}.{}. Please upgrade Node and re-run.",  | 
 | 49 | +			v.0, v.1, v.2  | 
 | 50 | +		)));  | 
 | 51 | +	}  | 
 | 52 | +	Ok(())  | 
 | 53 | +}  | 
 | 54 | + | 
 | 55 | +/// Require `npx` to be available.  | 
 | 56 | +pub fn ensure_npx() -> Result<()> {  | 
 | 57 | +	if !has("npx") && !has("npm") {  | 
 | 58 | +		return Err(anyhow!(  | 
 | 59 | +			"`npx` (or npm with npx) not found on PATH. Install NodeJS from https://nodejs.org and re-run."  | 
 | 60 | +		));  | 
 | 61 | +	}  | 
 | 62 | +	Ok(())  | 
 | 63 | +}  | 
 | 64 | + | 
 | 65 | +fn has(bin: &str) -> bool {  | 
 | 66 | +	cmd("which", vec![bin]).read().is_ok()  | 
 | 67 | +}  | 
 | 68 | + | 
 | 69 | +fn which(bin: &str) -> Option<String> {  | 
 | 70 | +	cmd("which", vec![bin]).read().ok().map(|s| s.trim().to_string())  | 
 | 71 | +}  | 
0 commit comments