Skip to main content

zoi_cli/cmd/install/
mod.rs

1/// Command-line arguments for the `install` command.
2pub mod args;
3/// High-level installation orchestration.
4pub mod orchestrator;
5
6use anyhow::Result;
7use colored::Colorize;
8use zoi_project as project;
9
10use crate::pkg::types;
11
12/// The primary high-level orchestration for the `zoi install` command.
13///
14/// # Errors
15///
16/// Returns an error if the installation process fails.
17pub fn run(
18    sources: &[String],
19    repo: Option<String>,
20    force: bool,
21    all_optional: bool,
22    yes: bool,
23    scope: Option<crate::cli::InstallScope>,
24    local: bool,
25    global: bool,
26    save: bool,
27    deps_only: bool,
28    build_deps_only: bool,
29    build_type: Option<&str>,
30    dry_run: bool,
31    plugin_manager: Option<&crate::pkg::plugin::PluginManager>,
32    build: bool,
33    frozen: bool,
34    explain: bool,
35    plan_json: bool,
36    retry: u32,
37    verbose: bool,
38    purl: bool,
39    project_config: Option<project::config::ProjectConfig>
40) -> Result<()> {
41    if plan_json && !dry_run {
42        return Err(anyhow::anyhow!("--plan-json requires --dry-run"));
43    }
44    let mut resolved_scope = scope.map(|s| match s {
45        crate::cli::InstallScope::User => types::Scope::User,
46        crate::cli::InstallScope::System => types::Scope::System,
47        crate::cli::InstallScope::Project => types::Scope::Project
48    });
49
50    if local {
51        resolved_scope = Some(types::Scope::Project);
52    } else if global {
53        resolved_scope = Some(types::Scope::User);
54    }
55
56    let scope = resolved_scope
57        .unwrap_or_else(crate::pkg::utils::resolve_fallback_scope);
58
59    // --build-deps-only resolves the package definitions up front and turns
60    // their declared build dependencies into the install sources. This works
61    // for any source type (local .pkg.lua, registry package, URL, git).
62    let effective_sources: Vec<String> = if build_deps_only {
63        if sources.is_empty() && repo.is_none() {
64            return Err(anyhow::anyhow!(
65                "--build-deps-only requires at least one package source."
66            ));
67        }
68        let platform = zoi_core::utils::get_platform()?;
69        let mut all_build_deps: Vec<String> = Vec::new();
70        for source in sources {
71            let resolved = zoi_resolver::resolve::resolve_source(
72                source,
73                Some(scope),
74                true,
75                yes
76            )?;
77            let deps = crate::pkg::package::build::get_build_dependencies(
78                resolved.path.as_path(),
79                build_type,
80                &platform,
81                None,
82                false
83            )?
84            .unwrap_or_default();
85            println!(
86                "{} Build dependencies for {source}: {}",
87                "::".bold().blue(),
88                if deps.is_empty() {
89                    "none".dimmed().to_string()
90                } else {
91                    deps.join(", ")
92                }
93            );
94            all_build_deps.extend(deps);
95        }
96        all_build_deps.sort();
97        all_build_deps.dedup();
98        if all_build_deps.is_empty() {
99            println!(
100                "{}",
101                "Nothing to install - no build dependencies found.".green()
102            );
103            return Ok(());
104        }
105        all_build_deps
106    } else {
107        sources.to_vec()
108    };
109
110    let options = orchestrator::InstallOptions {
111        scope,
112        force,
113        all_optional,
114        yes,
115        save,
116        deps_only,
117        build_type,
118        dry_run,
119        plugin_manager,
120        build,
121        frozen,
122        explain,
123        plan_json,
124        retry,
125        verbose,
126        purl,
127        project_config
128    };
129
130    let orchestrator = orchestrator::Orchestrator::new(options);
131    orchestrator.run(&effective_sources, repo)
132}