hni 0.0.1-alpha-2

ni-compatible package manager command router with node shim
Documentation
use std::process::ExitCode;

use crate::{
    app::{
        cli::{ParsedCommand, parse_from_env},
        completion::print_completion,
        doctor::print_doctor,
        help::print_help,
        init::print_init,
        version::print_versions,
    },
    core::{
        config::HniConfig,
        detect::detect,
        error::{HniError, HniResult},
        resolve::ResolveContext,
        runner,
        types::{InvocationKind, ResolvedExecution},
    },
    features,
    platform::node::resolve_real_node_path,
};

pub fn run_from_env() -> HniResult<ExitCode> {
    let parsed = parse_from_env()?;
    if parsed.deprecated_debug_alias_used {
        eprintln!(
            "[hni] warning: '?' debug alias is deprecated; use --debug-resolved, --dry-run, or --print-command"
        );
    }

    if !parsed.cwd.exists() {
        return Err(HniError::execution(format!(
            "working directory does not exist: {}",
            parsed.cwd.display()
        )));
    }

    let config = HniConfig::load()?;
    let resolve_ctx = ResolveContext::new(parsed.cwd.clone(), config.clone());

    match parsed.command {
        ParsedCommand::PrintVersions => {
            print_versions(&resolve_ctx);
            Ok(ExitCode::SUCCESS)
        }
        ParsedCommand::PrintHelp(invocation) => {
            print_help(invocation);
            Ok(ExitCode::SUCCESS)
        }
        ParsedCommand::Doctor => {
            print_doctor(&resolve_ctx.cwd, &resolve_ctx.config);
            Ok(ExitCode::SUCCESS)
        }
        ParsedCommand::Completion { shell, program } => {
            print_completion(shell.as_deref(), &program)?;
            Ok(ExitCode::SUCCESS)
        }
        ParsedCommand::Init { shell } => {
            print_init(&shell)?;
            Ok(ExitCode::SUCCESS)
        }
        ParsedCommand::InternalRealNodePath => {
            if let Ok(path) = resolve_real_node_path() {
                println!("{}", path.display());
            }
            Ok(ExitCode::SUCCESS)
        }
        ParsedCommand::Execute { invocation, args } => {
            let resolved = dispatch_invocation(invocation, args, &resolve_ctx)?;
            let Some(resolved) = resolved else {
                return Ok(ExitCode::SUCCESS);
            };

            if parsed.explain {
                print_explain(invocation, &resolved, &resolve_ctx, &config)?;
                return Ok(ExitCode::SUCCESS);
            }

            if parsed.debug {
                let debug_rendered = runner::format_debug(&resolved, config.use_sfw)
                    .map_err(|error| HniError::execution(error.to_string()))?;
                println!("{debug_rendered}");
                return Ok(ExitCode::SUCCESS);
            }

            runner::run(&resolved, config.use_sfw)
                .map_err(|error| HniError::execution(error.to_string()))
        }
    }
}

fn print_explain(
    invocation: InvocationKind,
    resolved: &ResolvedExecution,
    ctx: &ResolveContext,
    config: &HniConfig,
) -> HniResult<()> {
    println!("hni explain");
    println!("invocation: {}", invocation_name(invocation));
    println!("cwd: {}", ctx.cwd.display());
    println!(
        "resolved: {}",
        runner::format_debug(resolved, config.use_sfw)
            .map_err(|error| HniError::execution(error.to_string()))?
    );

    if let Ok(detection) = detect(&ctx.cwd, &ctx.config) {
        println!(
            "detected_agent: {}",
            detection
                .agent
                .map_or_else(|| "none".to_string(), |pm| pm.display_name().to_string())
        );
        println!("detection_source: {:?}", detection.source);
        println!("has_lockfile: {}", detection.has_lock);
    }

    Ok(())
}

fn invocation_name(invocation: InvocationKind) -> &'static str {
    match invocation {
        InvocationKind::Hni => "hni",
        InvocationKind::Ni => "ni",
        InvocationKind::Nr => "nr",
        InvocationKind::Nlx => "nlx",
        InvocationKind::Nu => "nu",
        InvocationKind::Nun => "nun",
        InvocationKind::Nci => "nci",
        InvocationKind::Na => "na",
        InvocationKind::Np => "np",
        InvocationKind::Ns => "ns",
        InvocationKind::NodeShim => "node",
    }
}

fn dispatch_invocation(
    invocation: InvocationKind,
    args: Vec<String>,
    ctx: &ResolveContext,
) -> HniResult<Option<ResolvedExecution>> {
    match invocation {
        InvocationKind::Ni => features::ni::handle(args, ctx),
        InvocationKind::Nr => features::nr::handle(args, ctx),
        InvocationKind::Nlx => features::nlx::handle(args, ctx),
        InvocationKind::Nu => features::nu::handle(args, ctx),
        InvocationKind::Nun => features::nun::handle(args, ctx),
        InvocationKind::Nci => features::nci::handle(args, ctx),
        InvocationKind::Na => features::na::handle(args, ctx),
        InvocationKind::Np => features::np::handle(args, ctx),
        InvocationKind::Ns => features::ns::handle(args, ctx),
        InvocationKind::NodeShim => features::node_shim::handle(args, ctx),
        InvocationKind::Hni => Err(HniError::execution("missing command")),
    }
}