diff --git a/Cargo.lock b/Cargo.lock index 2b2fc4d4..14ed619a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,6 +119,7 @@ dependencies = [ "ignore", "itertools", "miette", + "owo-colors", "pallas", "pallas-traverse", "petgraph", diff --git a/crates/aiken-project/Cargo.toml b/crates/aiken-project/Cargo.toml index fcdb0d8f..e10ead1b 100644 --- a/crates/aiken-project/Cargo.toml +++ b/crates/aiken-project/Cargo.toml @@ -18,6 +18,7 @@ hex = "0.4.3" ignore = "0.4.18" itertools = "0.10.1" miette = { version = "5.3.0", features = ["fancy"] } +owo-colors = "3.5.0" pallas = "0.16.0" pallas-traverse = "0.16.0" petgraph = "0.6.2" diff --git a/crates/aiken-project/src/config.rs b/crates/aiken-project/src/config.rs index 415fe43b..956dca25 100644 --- a/crates/aiken-project/src/config.rs +++ b/crates/aiken-project/src/config.rs @@ -1,13 +1,14 @@ -use crate::error::Error; +use crate::{package_name::PackageName, Error}; use aiken_lang::ast::Span; use miette::NamedSource; -use serde::{de::Visitor, Deserialize, Serialize}; -use std::{fmt::Display, fs, path::PathBuf}; +use serde::{Deserialize, Serialize}; +use std::{fmt::Display, fs, io, path::Path}; -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] pub struct Config { pub name: PackageName, pub version: String, + pub license: Option, #[serde(default)] pub description: String, pub repository: Option, @@ -15,7 +16,7 @@ pub struct Config { pub dependencies: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] pub struct Repository { pub user: String, pub project: String, @@ -37,67 +38,6 @@ pub struct Dependency { pub source: Platform, } -#[derive(PartialEq, Eq, Hash, Clone)] -pub struct PackageName { - pub owner: String, - pub repo: String, -} - -impl Display for PackageName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}/{}", self.owner, self.repo) - } -} - -impl Serialize for PackageName { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl<'de> Deserialize<'de> for PackageName { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct PackageNameVisitor; - - impl<'de> Visitor<'de> for PackageNameVisitor { - type Value = PackageName; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter - .write_str("a string representing an owner and repo, ex: aiken-lang/stdlib") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - let mut name = v.split('/'); - - let owner = name.next().ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &self) - })?; - - let repo = name.next().ok_or_else(|| { - serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &self) - })?; - - Ok(PackageName { - owner: owner.to_string(), - repo: repo.to_string(), - }) - } - } - - deserializer.deserialize_str(PackageNameVisitor) - } -} - impl Display for Platform { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> { match *self { @@ -109,10 +49,39 @@ impl Display for Platform { } impl Config { - pub fn load(dir: PathBuf) -> Result { + pub fn default(name: &PackageName) -> Self { + Config { + name: name.clone(), + version: "0.0.0".to_string(), + license: Some("Apache-2.0".to_string()), + description: format!("Aiken contracts for project '{name}'"), + repository: Some(Repository { + user: name.owner.clone(), + project: name.repo.clone(), + platform: Platform::Github, + }), + dependencies: vec![Dependency { + name: PackageName { + owner: "aiken-lang".to_string(), + repo: "stdlib".to_string(), + }, + version: "main".to_string(), + source: Platform::Github, + }], + } + } + + pub fn save(&self, dir: &Path) -> Result<(), io::Error> { + let aiken_toml_path = dir.join("aiken.toml"); + let aiken_toml = toml::to_string_pretty(self).unwrap(); + fs::write(aiken_toml_path, aiken_toml) + } + + pub fn load(dir: &Path) -> Result { let config_path = dir.join("aiken.toml"); - let raw_config = fs::read_to_string(&config_path) - .map_err(|_| Error::MissingManifest { path: dir.clone() })?; + let raw_config = fs::read_to_string(&config_path).map_err(|_| Error::MissingManifest { + path: dir.to_path_buf(), + })?; let result: Self = toml::from_str(&raw_config).map_err(|e| Error::TomlLoading { path: config_path.clone(), @@ -128,4 +97,19 @@ impl Config { Ok(result) } + + pub fn insert(mut self, dependency: &Dependency, and_replace: bool) -> Option { + for mut existing in self.dependencies.iter_mut() { + if existing.name == dependency.name { + return if and_replace { + existing.version = dependency.version.clone(); + Some(self) + } else { + None + }; + } + } + self.dependencies.push(dependency.clone()); + Some(self) + } } diff --git a/crates/aiken-project/src/deps.rs b/crates/aiken-project/src/deps.rs index dfc82a45..3d28d20f 100644 --- a/crates/aiken-project/src/deps.rs +++ b/crates/aiken-project/src/deps.rs @@ -6,8 +6,9 @@ use serde::{Deserialize, Serialize}; use tokio::time::Instant; use crate::{ - config::{Config, Dependency, PackageName}, + config::{Config, Dependency}, error::Error, + package_name::PackageName, paths, telemetry::{Event, EventListener}, }; diff --git a/crates/aiken-project/src/deps/downloader.rs b/crates/aiken-project/src/deps/downloader.rs index 5dd82d17..ad3868c9 100644 --- a/crates/aiken-project/src/deps/downloader.rs +++ b/crates/aiken-project/src/deps/downloader.rs @@ -4,8 +4,8 @@ use futures::future; use reqwest::Client; use crate::{ - config::PackageName, error::Error, + package_name::PackageName, paths::{self, CacheKey}, }; diff --git a/crates/aiken-project/src/deps/manifest.rs b/crates/aiken-project/src/deps/manifest.rs index 0b22b83f..7ed9140c 100644 --- a/crates/aiken-project/src/deps/manifest.rs +++ b/crates/aiken-project/src/deps/manifest.rs @@ -5,8 +5,9 @@ use miette::NamedSource; use serde::{Deserialize, Serialize}; use crate::{ - config::{Config, Dependency, PackageName, Platform}, + config::{Config, Dependency, Platform}, error::Error, + package_name::PackageName, paths, telemetry::{Event, EventListener}, }; diff --git a/crates/aiken-project/src/error.rs b/crates/aiken-project/src/error.rs index b0b5c7ab..32855568 100644 --- a/crates/aiken-project/src/error.rs +++ b/crates/aiken-project/src/error.rs @@ -1,4 +1,4 @@ -use crate::{deps::manifest::Package, pretty, script::EvalHint}; +use crate::{deps::manifest::Package, package_name::PackageName, pretty, script::EvalHint}; use aiken_lang::{ ast::{BinOp, Span}, parser::error::ParseError, @@ -264,10 +264,10 @@ impl Diagnostic for Error { Error::ValidatorMustReturnBool { .. } => Some(Box::new("aiken::scripts")), Error::WrongValidatorArity { .. } => Some(Box::new("aiken::validators")), Error::TestFailure { path, .. } => Some(Box::new(path.to_str().unwrap_or(""))), - Error::Http(_) => Some(Box::new("aiken::deps")), + Error::Http(_) => Some(Box::new("aiken::packages::download")), Error::ZipExtract(_) => None, Error::JoinError(_) => None, - Error::UnknownPackageVersion { .. } => Some(Box::new("aiken::deps")), + Error::UnknownPackageVersion { .. } => Some(Box::new("aiken::packages::resolve")), } } @@ -418,6 +418,8 @@ pub enum Warning { #[source] warning: tipo::error::Warning, }, + #[error("{name} is already a dependency.")] + DependencyAlreadyExists { name: PackageName }, } impl Diagnostic for Warning { @@ -429,6 +431,7 @@ impl Diagnostic for Warning { match self { Warning::Type { named, .. } => Some(named), Warning::NoValidators => None, + Warning::DependencyAlreadyExists { .. } => None, } } @@ -436,6 +439,7 @@ impl Diagnostic for Warning { match self { Warning::Type { warning, .. } => warning.labels(), Warning::NoValidators => None, + Warning::DependencyAlreadyExists { .. } => None, } } @@ -443,6 +447,19 @@ impl Diagnostic for Warning { match self { Warning::Type { .. } => Some(Box::new("aiken::check")), Warning::NoValidators => Some(Box::new("aiken::check")), + Warning::DependencyAlreadyExists { .. } => { + Some(Box::new("aiken::packages::already_exists")) + } + } + } + + fn help<'a>(&'a self) -> Option> { + match self { + Warning::Type { .. } => None, + Warning::NoValidators => None, + Warning::DependencyAlreadyExists { .. } => Some(Box::new( + "If you need to change the version, try 'aiken packages upgrade' instead.", + )), } } } diff --git a/crates/aiken-project/src/lib.rs b/crates/aiken-project/src/lib.rs index bb595375..0e161666 100644 --- a/crates/aiken-project/src/lib.rs +++ b/crates/aiken-project/src/lib.rs @@ -5,6 +5,7 @@ pub mod error; pub mod format; pub mod module; pub mod options; +pub mod package_name; pub mod paths; pub mod pretty; pub mod script; @@ -18,10 +19,10 @@ use aiken_lang::{ uplc::CodeGenerator, IdGenerator, }; -use config::PackageName; use deps::UseManifest; use miette::NamedSource; use options::{CodeGenMode, Options}; +use package_name::PackageName; use pallas::{ codec::minicbor, ledger::{addresses::Address, primitives::babbage}, @@ -85,7 +86,7 @@ where module_types.insert("aiken".to_string(), builtins::prelude(&id_gen)); module_types.insert("aiken/builtin".to_string(), builtins::plutus(&id_gen)); - let config = Config::load(root.clone())?; + let config = Config::load(&root)?; Ok(Project { config, diff --git a/crates/aiken-project/src/package_name.rs b/crates/aiken-project/src/package_name.rs new file mode 100644 index 00000000..fd6cb73d --- /dev/null +++ b/crates/aiken-project/src/package_name.rs @@ -0,0 +1,143 @@ +use owo_colors::OwoColorize; +use serde::{de::Visitor, Deserialize, Serialize}; +use std::{ + fmt::{self, Display}, + str::FromStr, +}; +use thiserror::Error; + +#[derive(PartialEq, Eq, Hash, Clone)] +pub struct PackageName { + pub owner: String, + pub repo: String, +} + +impl PackageName { + fn validate(&self) -> Result<(), Error> { + let r = regex::Regex::new("^[a-z0-9_-]+$").expect("regex could not be compiled"); + + if !(r.is_match(&self.owner) && r.is_match(&self.repo)) { + return Err(Error::InvalidProjectName { + reason: InvalidProjectNameReason::Format, + name: self.to_string(), + }); + } + + Ok(()) + } +} + +impl FromStr for PackageName { + type Err = Error; + + fn from_str(name: &str) -> Result { + let mut name_split = name.split('/'); + let owner = name_split + .next() + .ok_or_else(|| Error::InvalidProjectName { + name: name.to_string(), + reason: InvalidProjectNameReason::Format, + })? + .to_string(); + let repo = name_split + .next() + .ok_or_else(|| Error::InvalidProjectName { + name: name.to_string(), + reason: InvalidProjectNameReason::Format, + })? + .to_string(); + let package_name = PackageName { owner, repo }; + package_name.validate()?; + Ok(package_name) + } +} + +impl Display for PackageName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}/{}", self.owner, self.repo) + } +} + +impl Serialize for PackageName { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for PackageName { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct PackageNameVisitor; + + impl<'de> Visitor<'de> for PackageNameVisitor { + type Value = PackageName; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter + .write_str("a string representing an owner and repo, ex: aiken-lang/stdlib") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + let mut name = v.split('/'); + + let owner = name.next().ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &self) + })?; + + let repo = name.next().ok_or_else(|| { + serde::de::Error::invalid_value(serde::de::Unexpected::Str(v), &self) + })?; + + Ok(PackageName { + owner: owner.to_string(), + repo: repo.to_string(), + }) + } + } + + deserializer.deserialize_str(PackageNameVisitor) + } +} + +#[derive(Debug, Error, miette::Diagnostic)] +pub enum Error { + #[error("{} is not a valid project name: {}", name.red(), reason.to_string())] + InvalidProjectName { + name: String, + reason: InvalidProjectNameReason, + }, + #[error("A project named {} already exists.", name.red())] + ProjectExists { name: String }, +} + +#[derive(Debug, Clone, Copy)] +pub enum InvalidProjectNameReason { + Reserved, + Format, +} + +impl fmt::Display for InvalidProjectNameReason { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + InvalidProjectNameReason::Reserved => write!(f, "It's a reserved word in Aiken."), + InvalidProjectNameReason::Format => write!( + f, + "It is malformed.\n\nProjects must be named as:\n\n\t\ + {}/{}\n\nEach part must start with a lowercase letter \ + and may only contain lowercase letters, numbers, hyphens or underscores.\ + \nFor example,\n\n\t{}", + "{owner}".bright_blue(), + "{project}".bright_blue(), + "aiken-lang/stdlib".bright_blue(), + ), + } + } +} diff --git a/crates/aiken-project/src/paths.rs b/crates/aiken-project/src/paths.rs index 4f514e90..def9946e 100644 --- a/crates/aiken-project/src/paths.rs +++ b/crates/aiken-project/src/paths.rs @@ -1,5 +1,5 @@ use crate::deps::manifest::Package; -use crate::{config::PackageName, error::Error}; +use crate::{error::Error, package_name::PackageName}; use reqwest::Client; use std::path::PathBuf; diff --git a/crates/aiken/src/cmd/error.rs b/crates/aiken/src/cmd/error.rs deleted file mode 100644 index 5f72cc9f..00000000 --- a/crates/aiken/src/cmd/error.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::fmt; - -use owo_colors::OwoColorize; -use thiserror::Error; - -#[derive(Debug, Error, miette::Diagnostic)] -pub enum Error { - #[error("{} is not a valid project name: {}", name.red(), reason.to_string())] - InvalidProjectName { - name: String, - reason: InvalidProjectNameReason, - }, - #[error("A project named {} already exists.", name.red())] - ProjectExists { name: String }, -} - -#[derive(Debug, Clone, Copy)] -pub enum InvalidProjectNameReason { - AikenPrefix, - AikenReservedModule, - Format, -} - -impl fmt::Display for InvalidProjectNameReason { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - InvalidProjectNameReason::AikenPrefix => write!(f, "it's a reserved word in Aiken."), - InvalidProjectNameReason::AikenReservedModule => { - write!(f, "it's a reserved module name in Aiken.") - } - InvalidProjectNameReason::Format => write!( - f, - "it is malformed.\n\nProjects must be named as:\n\n\t\ - {}/{}\n\nEach part must start with a lowercase letter \ - and may only contain lowercase letters, numbers, hyphens or underscores.\ - \nFor example,\n\n\t{}", - "{owner}".bright_blue(), - "{project}".bright_blue(), - "aiken-lang/stdlib".bright_blue(), - ), - } - } -} diff --git a/crates/aiken/src/cmd/mod.rs b/crates/aiken/src/cmd/mod.rs index 9bfd480b..49240999 100644 --- a/crates/aiken/src/cmd/mod.rs +++ b/crates/aiken/src/cmd/mod.rs @@ -1,10 +1,9 @@ pub mod build; pub mod check; -pub mod deps; pub mod docs; -pub mod error; pub mod fmt; pub mod lsp; pub mod new; +pub mod packages; pub mod tx; pub mod uplc; diff --git a/crates/aiken/src/cmd/new.rs b/crates/aiken/src/cmd/new.rs index d84e4312..b9c24e7d 100644 --- a/crates/aiken/src/cmd/new.rs +++ b/crates/aiken/src/cmd/new.rs @@ -1,11 +1,15 @@ -use aiken_project::config::PackageName; +use aiken_project::{ + config::Config, + package_name::{self, PackageName}, +}; use indoc::{formatdoc, indoc}; use miette::IntoDiagnostic; use owo_colors::OwoColorize; -use std::path::PathBuf; -use std::{fs, path::Path}; - -use super::error::{Error, InvalidProjectNameReason}; +use std::{ + fs, + path::{Path, PathBuf}, + str::FromStr, +}; #[derive(clap::Args)] /// Create a new Aiken project @@ -18,14 +22,9 @@ pub struct Args { } pub fn exec(args: Args) -> miette::Result<()> { - validate_name(&args.name)?; - - let package_name = package_name_from_str(&args.name)?; - + let package_name = PackageName::from_str(&args.name).into_diagnostic()?; create_project(args, &package_name)?; - print_success_message(&package_name); - Ok(()) } @@ -33,7 +32,7 @@ fn create_project(args: Args, package_name: &PackageName) -> miette::Result<()> let root = PathBuf::from(&package_name.repo); if root.exists() { - Err(Error::ProjectExists { + Err(package_name::Error::ProjectExists { name: package_name.repo.clone(), })?; } @@ -44,10 +43,12 @@ fn create_project(args: Args, package_name: &PackageName) -> miette::Result<()> create_validators_folder(&root)?; } - aiken_toml(&root, package_name)?; - readme(&root, &package_name.repo)?; + Config::default(package_name) + .save(&root) + .into_diagnostic()?; + gitignore(&root)?; Ok(()) @@ -85,28 +86,6 @@ fn create_validators_folder(root: &Path) -> miette::Result<()> { Ok(()) } -fn aiken_toml(root: &Path, package_name: &PackageName) -> miette::Result<()> { - let aiken_toml_path = root.join("aiken.toml"); - - fs::write( - aiken_toml_path, - formatdoc! { - r#" - name = "{name}" - version = "0.0.0" - licences = ["Apache-2.0"] - description = "Aiken contracts for project '{name}'" - - dependencies = [ - {{ name = "aiken-lang/stdlib", version = "main", source = "github" }}, - ] - "#, - name = package_name.to_string(), - }, - ) - .into_diagnostic() -} - fn readme(root: &Path, project_name: &str) -> miette::Result<()> { fs::write( root.join("README.md"), @@ -198,49 +177,3 @@ fn gitignore(root: &Path) -> miette::Result<()> { Ok(()) } - -fn validate_name(name: &str) -> Result<(), Error> { - if name.starts_with("aiken_") { - Err(Error::InvalidProjectName { - name: name.to_string(), - reason: InvalidProjectNameReason::AikenPrefix, - }) - } else if name == "aiken" { - Err(Error::InvalidProjectName { - name: name.to_string(), - reason: InvalidProjectNameReason::AikenReservedModule, - }) - } else if !regex::Regex::new("^[a-z0-9_-]+/[a-z0-9_-]+$") - .expect("regex could not be compiled") - .is_match(name) - { - Err(Error::InvalidProjectName { - name: name.to_string(), - reason: InvalidProjectNameReason::Format, - }) - } else { - Ok(()) - } -} - -fn package_name_from_str(name: &str) -> Result { - let mut name_split = name.split('/'); - - let owner = name_split - .next() - .ok_or_else(|| Error::InvalidProjectName { - name: name.to_string(), - reason: InvalidProjectNameReason::Format, - })? - .to_string(); - - let repo = name_split - .next() - .ok_or_else(|| Error::InvalidProjectName { - name: name.to_string(), - reason: InvalidProjectNameReason::Format, - })? - .to_string(); - - Ok(PackageName { owner, repo }) -} diff --git a/crates/aiken/src/cmd/packages/add.rs b/crates/aiken/src/cmd/packages/add.rs new file mode 100644 index 00000000..522f8cbe --- /dev/null +++ b/crates/aiken/src/cmd/packages/add.rs @@ -0,0 +1,78 @@ +use aiken_project::{ + config::{Config, Dependency, Platform}, + error::Warning, + package_name::PackageName, + pretty, +}; +use miette::IntoDiagnostic; +use owo_colors::OwoColorize; +use std::{path::PathBuf, process, str::FromStr}; + +#[derive(clap::Args)] +/// Add a new project package as dependency +pub struct Args { + /// Package name, in the form of {owner}/{repository}. + /// + /// For example → 'add aiken-lang/stdlib' + /// + /// Note that by default, this assumes the package is located + /// on Github. + pub package: String, + /// The package version, as a git commit hash, a tag or a branch name. + #[clap(long)] + pub version: String, + + #[clap(hide = true)] + pub overwrite: bool, +} + +pub fn exec(args: Args) -> miette::Result<()> { + let root = PathBuf::from("."); + + let dependency = Dependency { + name: PackageName::from_str(&args.package)?, + version: args.version, + source: Platform::Github, + }; + + let config = match Config::load(&root) { + Ok(config) => config, + Err(e) => { + e.report(); + process::exit(1); + } + }; + + println!( + "{} {}", + pretty::pad_left("Package".to_string(), 13, " ") + .bold() + .purple(), + dependency.name.bright_blue(), + ); + + match config.insert(&dependency, args.overwrite) { + Some(config) => { + config.save(&root).into_diagnostic()?; + println!( + "{} version → {}", + pretty::pad_left( + if args.overwrite { "Changed" } else { "Added" }.to_string(), + 13, + " " + ) + .bold() + .purple(), + dependency.version.yellow() + ); + Ok(()) + } + None => { + let warning = Warning::DependencyAlreadyExists { + name: dependency.name, + }; + warning.report(); + process::exit(1) + } + } +} diff --git a/crates/aiken/src/cmd/deps/clear_cache.rs b/crates/aiken/src/cmd/packages/clear_cache.rs similarity index 100% rename from crates/aiken/src/cmd/deps/clear_cache.rs rename to crates/aiken/src/cmd/packages/clear_cache.rs diff --git a/crates/aiken/src/cmd/deps/mod.rs b/crates/aiken/src/cmd/packages/mod.rs similarity index 57% rename from crates/aiken/src/cmd/deps/mod.rs rename to crates/aiken/src/cmd/packages/mod.rs index cc35df18..7c72f793 100644 --- a/crates/aiken/src/cmd/deps/mod.rs +++ b/crates/aiken/src/cmd/packages/mod.rs @@ -1,4 +1,6 @@ +pub mod add; pub mod clear_cache; +pub mod upgrade; use clap::Subcommand; @@ -6,12 +8,20 @@ use clap::Subcommand; #[derive(Subcommand)] #[clap(setting(clap::AppSettings::DeriveDisplayOrder))] pub enum Cmd { + /// Add a new package dependency + Add(add::Args), + + /// Change the version of an installed dependency + Upgrade(upgrade::Args), + /// Clear the system-wide dependencies cache ClearCache, } pub fn exec(cmd: Cmd) -> miette::Result<()> { match cmd { + Cmd::Add(args) => add::exec(args), Cmd::ClearCache => clear_cache::exec(), + Cmd::Upgrade(args) => upgrade::exec(args), } } diff --git a/crates/aiken/src/cmd/packages/upgrade.rs b/crates/aiken/src/cmd/packages/upgrade.rs new file mode 100644 index 00000000..4c681201 --- /dev/null +++ b/crates/aiken/src/cmd/packages/upgrade.rs @@ -0,0 +1,24 @@ +use super::add; + +#[derive(clap::Args)] +/// Change the version of an installed dependency +pub struct Args { + /// Package name, in the form of {owner}/{repository}. + /// + /// For example → 'add aiken-lang/stdlib' + /// + /// Note that by default, this assumes the package is located + /// on Github. + package: String, + /// The package version, as a git commit hash, a tag or a branch name. + #[clap(long)] + version: String, +} + +pub fn exec(args: Args) -> miette::Result<()> { + add::exec(add::Args { + package: args.package, + version: args.version, + overwrite: true, + }) +} diff --git a/crates/aiken/src/main.rs b/crates/aiken/src/main.rs index ebb977eb..f66b8825 100644 --- a/crates/aiken/src/main.rs +++ b/crates/aiken/src/main.rs @@ -1,4 +1,8 @@ -use aiken::cmd::{build, check, deps, docs, fmt, lsp, new, tx, uplc}; +use aiken::cmd::{ + build, check, docs, fmt, lsp, new, + packages::{self, add}, + tx, uplc, +}; use clap::Parser; /// Aiken: a smart-contract language and toolchain for Cardano @@ -12,9 +16,10 @@ pub enum Cmd { Build(build::Args), Check(check::Args), Docs(docs::Args), + Add(add::Args), #[clap(subcommand)] - Deps(deps::Cmd), + Packages(packages::Cmd), #[clap(subcommand)] Tx(tx::Cmd), @@ -38,9 +43,10 @@ fn main() -> miette::Result<()> { Cmd::New(args) => new::exec(args), Cmd::Fmt(args) => fmt::exec(args), Cmd::Build(args) => build::exec(args), - Cmd::Docs(args) => docs::exec(args), - Cmd::Deps(args) => deps::exec(args), Cmd::Check(args) => check::exec(args), + Cmd::Docs(args) => docs::exec(args), + Cmd::Add(args) => add::exec(args), + Cmd::Packages(args) => packages::exec(args), Cmd::Lsp(args) => lsp::exec(args), Cmd::Tx(sub_cmd) => tx::exec(sub_cmd), Cmd::Uplc(sub_cmd) => uplc::exec(sub_cmd),