leo_lang/cli/commands/
add.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use super::*;
18use leo_package::{Dependency, Location, Manifest};
19use std::path::PathBuf;
20
21/// Add a new on-chain or local dependency to the current package.
22#[derive(Parser, Debug)]
23#[clap(name = "leo", author = "The Leo Team <leo@provable.com>", version)]
24pub struct LeoAdd {
25    #[clap(name = "NAME", help = "The dependency name. Ex: `credits.aleo` or `credits`.")]
26    pub(crate) name: String,
27
28    #[clap(flatten)]
29    pub(crate) source: DependencySource,
30
31    #[clap(
32        short = 'c',
33        long,
34        hide = true,
35        help = "[UNUSED] Clear all previous dependencies.",
36        default_value = "false"
37    )]
38    pub(crate) clear: bool,
39
40    #[clap(long, help = "This is a development dependency.", default_value = "false")]
41    pub(crate) dev: bool,
42}
43
44#[derive(Parser, Debug)]
45#[group(required = true, multiple = false)]
46pub struct DependencySource {
47    #[clap(short = 'l', long, help = "Whether the dependency is local to the machine.", group = "source")]
48    pub(crate) local: Option<PathBuf>,
49
50    #[clap(short = 'n', long, help = "Whether the dependency is on a live network.", group = "source")]
51    pub(crate) network: bool,
52
53    #[clap(
54        short = 'e',
55        long,
56        help = "The expected edition of the program. DO NOT USE THIS UNLESS YOU KNOW WHAT YOU ARE DOING.",
57        group = "source"
58    )]
59    pub(crate) edition: Option<u16>,
60}
61
62impl Command for LeoAdd {
63    type Input = ();
64    type Output = ();
65
66    fn log_span(&self) -> Span {
67        tracing::span!(tracing::Level::INFO, "Leo")
68    }
69
70    fn prelude(&self, _: Context) -> Result<Self::Input> {
71        Ok(())
72    }
73
74    fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
75        let path = context.dir()?;
76
77        let manifest_path = path.join(leo_package::MANIFEST_FILENAME);
78        let mut manifest = Manifest::read_from_file(&manifest_path)?;
79
80        // Make sure the program name is valid.
81        // Allow both `credits.aleo` and `credits` syntax.
82        let name = if self.name.ends_with(".aleo") { self.name.clone() } else { format!("{}.aleo", self.name) };
83
84        if !leo_package::is_valid_aleo_name(&name) {
85            return Err(CliError::invalid_program_name(name).into());
86        }
87
88        let new_dependency = Dependency {
89            name: name.clone(),
90            location: if self.source.local.is_some() { Location::Local } else { Location::Network },
91            path: self.source.local.clone(),
92            edition: self.source.edition,
93        };
94
95        let deps = if self.dev { &mut manifest.dev_dependencies } else { &mut manifest.dependencies };
96
97        if let Some(matched_dep) = deps.get_or_insert_default().iter_mut().find(|dep| dep.name == new_dependency.name) {
98            if let Some(path) = &matched_dep.path {
99                tracing::warn!(
100                    "⚠️ Program `{name}` already exists as a local dependency at `{}`. Overwriting.",
101                    path.display()
102                );
103            } else {
104                tracing::warn!("⚠️ Program `{name}` already exists as a network dependency. Overwriting.");
105            }
106            *matched_dep = new_dependency;
107        } else {
108            deps.as_mut().unwrap().push(new_dependency);
109            if let Some(path) = self.source.local.as_ref() {
110                tracing::info!("✅ Added local dependency to program `{name}` at path `{}`.", path.display());
111            } else {
112                tracing::info!("✅ Added network dependency `{name}` from network `{}`.", self.source.network);
113            }
114        }
115
116        manifest.write_to_file(manifest_path)?;
117
118        Ok(())
119    }
120}