use color_eyre::eyre::Context;
use yansi::Paint;
pub mod actions;
pub mod context;
pub mod instructions;
pub mod interactive;
pub mod requirements;
pub mod unix;
pub mod windows;
pub use actions::*;
pub use context::*;
pub use instructions::*;
pub use interactive::*;
pub use requirements::*;
pub async fn pre_setup_checks(context: &SetupContext) -> crate::Result<SetupRequirements> {
let bin_path_in_path = check_bin_path_in_path(context);
let zv_dir_action = determine_zv_dir_action(context).await?;
let path_action = determine_path_action(context, bin_path_in_path);
let needs_post_setup = !bin_path_in_path
|| matches!(zv_dir_action, ZvDirAction::MakePermanent { .. })
|| !matches!(path_action, PathAction::AlreadyConfigured);
Ok(SetupRequirements::new(
bin_path_in_path,
zv_dir_action,
path_action,
needs_post_setup,
))
}
pub fn check_bin_path_in_path(context: &SetupContext) -> bool {
use crate::shell::path_utils::check_dir_in_path_for_shell;
check_dir_in_path_for_shell(&context.shell, context.app.bin_path())
}
pub async fn determine_zv_dir_action(context: &SetupContext) -> crate::Result<ZvDirAction> {
if !context.using_env_var {
return Ok(ZvDirAction::NotSet);
}
let zv_dir = context.app.path();
let is_permanent = if cfg!(windows) {
#[cfg(windows)]
{
windows::check_zv_dir_permanent_windows(zv_dir).await?
}
#[cfg(not(windows))]
{
false
} } else {
unix::check_zv_dir_permanent_unix(&context.shell, zv_dir).await?
};
if is_permanent {
Ok(ZvDirAction::AlreadyPermanent)
} else {
if context.dry_run {
Ok(ZvDirAction::MakePermanent {
current_path: zv_dir.clone(),
})
} else if will_use_interactive_mode(context) {
Ok(ZvDirAction::MakePermanent {
current_path: zv_dir.clone(),
})
} else {
let should_make_permanent = ask_user_zv_dir_confirmation(zv_dir)?;
if should_make_permanent {
Ok(ZvDirAction::MakePermanent {
current_path: zv_dir.clone(),
})
} else {
Ok(ZvDirAction::NotSet)
}
}
}
}
fn will_use_interactive_mode(context: &SetupContext) -> bool {
if context.no_interactive {
return false;
}
if std::env::var("CI").is_ok() {
return false;
}
if let Ok(term) = std::env::var("TERM")
&& term == "dumb"
{
return false;
}
crate::tools::supports_interactive_prompts()
}
pub fn determine_path_action(context: &SetupContext, bin_path_in_path: bool) -> PathAction {
if bin_path_in_path {
return PathAction::AlreadyConfigured;
}
let bin_path = context.app.bin_path().clone();
if context.shell.is_windows_shell() && !context.shell.is_powershell_in_unix() {
PathAction::AddToRegistry { bin_path }
} else {
let env_file_path = context.app.env_path().clone();
let rc_file = unix::select_rc_file(&context.shell);
PathAction::GenerateEnvFile {
env_file_path,
rc_file,
bin_path,
}
}
}
fn ask_user_zv_dir_confirmation(zv_dir: &std::path::Path) -> crate::Result<bool> {
use std::io::{self, Write};
use yansi::Paint;
let home_dir = dirs::home_dir().ok_or_else(|| {
crate::ZvError::shell_context_creation_failed("Could not determine home directory")
})?;
let default_zv_dir = home_dir.join(".zv");
println!("{}\n", Paint::yellow("⚠ Custom ZV_DIR detected").bold());
println!(
"Your environment has ZV_DIR set to path: {}",
Paint::cyan(&zv_dir.display().to_string())
);
println!(
"Default path would be: {}",
Paint::dim(&default_zv_dir.display().to_string())
);
println!();
let target_description = if cfg!(windows) {
"system environment variables"
} else {
".profile"
};
print!(
"Do you want zv to make ZV_DIR={} permanent by adding it to {}? [y/N]: ",
Paint::cyan(&zv_dir.display().to_string()),
Paint::green(&target_description)
);
io::stdout().flush().map_err(|e| {
crate::ZvError::shell_setup_failed(
"user-confirmation",
&format!("Failed to flush stdout: {}", e),
)
})?;
let mut input = String::new();
io::stdin().read_line(&mut input).map_err(|e| {
crate::ZvError::shell_setup_failed(
"user-confirmation",
&format!("Failed to read user input: {}", e),
)
})?;
let response = input.trim().to_lowercase();
let should_set_permanent = matches!(response.as_str(), "y" | "yes");
if !should_set_permanent {
println!();
println!("{}", Paint::yellow("⚠ Important considerations:"));
println!(
"• Temporary ZV_DIR settings will break zv in new sessions unless the next session also has it set"
);
println!("• Ensure ZV_DIR is permanently set in your shell profile or system environment");
println!();
} else {
println!();
println!(
"{}",
Paint::green("zv will set ZV_DIR permanently during setup...")
);
println!();
}
Ok(should_set_permanent)
}
pub async fn execute_zv_dir_setup(
context: &SetupContext,
action: &ZvDirAction,
) -> crate::Result<()> {
match action {
ZvDirAction::NotSet => {
if !context.dry_run {
println!("ZV_DIR: Using default path (no permanent setting needed)");
}
Ok(())
}
ZvDirAction::AlreadyPermanent => {
if !context.dry_run {
println!("ZV_DIR: Already set permanently");
}
Ok(())
}
ZvDirAction::MakePermanent { current_path } => {
if context.dry_run {
println!("Would set ZV_DIR={} permanently", current_path.display());
return Ok(());
}
println!("Setting ZV_DIR={} permanently...", current_path.display());
if context.shell.is_windows_shell() && !context.shell.is_powershell_in_unix() {
#[cfg(windows)]
{
windows::execute_zv_dir_setup_windows(current_path).await
}
#[cfg(not(windows))]
{
Ok(())
} } else {
unix::execute_zv_dir_setup_unix(context, current_path).await
}
}
}
}
pub async fn execute_path_setup(context: &SetupContext, action: &PathAction) -> crate::Result<()> {
match action {
PathAction::AlreadyConfigured => {
if !context.dry_run {
println!("PATH: Already configured with zv bin directory");
}
Ok(())
}
PathAction::AddToRegistry { bin_path } => {
if context.dry_run {
println!(
"Would add {} to PATH via Windows registry",
bin_path.display()
);
return Ok(());
}
println!(
"Adding {} to PATH via Windows registry...",
bin_path.display()
);
#[cfg(windows)]
{
windows::execute_path_setup_windows(context, bin_path).await
}
#[cfg(not(windows))]
{
Ok(())
} }
PathAction::GenerateEnvFile {
env_file_path,
rc_file,
bin_path,
} => {
if context.dry_run {
println!(
"Would generate env file at {} and modify {}",
Paint::blue(&env_file_path.display()),
Paint::blue(&rc_file.display())
);
return Ok(());
}
println!("Generating environment file and updating shell configuration...");
unix::execute_path_setup_unix(context, env_file_path, rc_file, bin_path).await
}
}
}
pub async fn execute_setup(
context: &SetupContext,
requirements: &SetupRequirements,
) -> crate::Result<()> {
use yansi::Paint;
if context.dry_run {
println!("{}", Paint::cyan("🟦 Executing Setup (Dry Run)"));
} else {
println!("{}", Paint::green("🟩 Executing Setup"));
}
execute_zv_dir_setup(context, &requirements.zv_dir_action)
.await
.with_context(|| "ZV_DIR setup failed")?;
execute_path_setup(context, &requirements.path_action)
.await
.with_context(|| "PATH setup failed")?;
if requirements.needs_post_setup {
post_setup_actions(context)
.await
.with_context(|| "Post-setup actions failed")?;
}
Ok(())
}
pub async fn post_setup_actions(context: &SetupContext) -> crate::Result<()> {
use yansi::Paint;
if context.dry_run {
println!("{}", Paint::cyan("→ Post-Setup Actions (Dry Run)"));
println!(" Would check and update zv binary if needed");
println!(" Would regenerate shims if binary was updated");
} else {
println!("{}", Paint::green("→ Post-Setup Actions"));
crate::cli::sync::check_and_update_zv_binary(&context.app, true)
.await
.with_context(|| "Failed to update zv binary")?;
}
if context.dry_run {
println!("{}", Paint::cyan("← Post-Setup Actions Complete"));
} else {
println!("{}", Paint::green("← Post-Setup Actions Complete"));
}
Ok(())
}