Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 121 additions & 22 deletions leo/cli/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

use crate::cli::{commands::*, context::*, helpers::*};
use clap::Parser;
use leo_errors::Result;
use leo_errors::{CliError, Result};
use serde::Serialize;
use std::{path::PathBuf, process::exit};

/// CLI Arguments entry point - includes global parameters and subcommands
Expand All @@ -29,6 +30,9 @@ pub struct CLI {
#[clap(short, global = true, help = "Suppress CLI output")]
quiet: bool,

#[clap(long, global = true, help = "Write results as JSON to a file. Defaults to build/json-outputs/<command>.json if no path specified.", num_args = 0..=1, require_equals = true, default_missing_value = "")]
json_output: Option<String>,

#[clap(long, global = true, help = "Disable Leo's daily check for version updates")]
disable_update_check: bool,

Expand Down Expand Up @@ -127,6 +131,29 @@ enum Commands {
},
}

impl Commands {
fn name(&self) -> &'static str {
match self {
Commands::Account { .. } => "account",
Commands::New { .. } => "new",
Commands::Run { .. } => "run",
Commands::Test { .. } => "test",
Commands::Execute { .. } => "execute",
Commands::Deploy { .. } => "deploy",
Commands::Devnet { .. } => "devnet",
Commands::Query { .. } => "query",
Commands::Build { .. } => "build",
Commands::Debug { .. } => "debug",
Commands::Add { .. } => "add",
Commands::Remove { .. } => "remove",
Commands::Clean { .. } => "clean",
Commands::Synthesize { .. } => "synthesize",
Commands::Update { .. } => "update",
Commands::Upgrade { .. } => "upgrade",
}
}
}

pub fn handle_error<T>(res: Result<T>) -> T {
match res {
Ok(t) => t,
Expand All @@ -137,10 +164,26 @@ pub fn handle_error<T>(res: Result<T>) -> T {
}
}

/// JSON output types for commands that support `--json`.
#[derive(Serialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
enum JsonOutput {
Deploy(DeployOutput),
Run(RunOutput),
Execute(ExecuteOutput),
Test(TestOutput),
Query(serde_json::Value),
Synthesize(SynthesizeOutput),
}

/// Run command with custom build arguments.
pub fn run_with_args(cli: CLI) -> Result<()> {
// JSON output mode implies quiet mode.
let quiet = cli.quiet || cli.json_output.is_some();

// Print the variables found in the `.env` files.
if let Ok(vars) = dotenvy::dotenv_iter().map(|v| v.flatten().collect::<Vec<_>>()) {
if !quiet && let Ok(vars) = dotenvy::dotenv_iter().map(|v| v.flatten().collect::<Vec<_>>()) {
if !vars.is_empty() {
println!("📢 Loading environment variables from a `.env` file in the directory tree.");
}
Expand All @@ -151,43 +194,77 @@ pub fn run_with_args(cli: CLI) -> Result<()> {
// Initialize the `.env` file.
dotenvy::dotenv().ok();

if !cli.quiet {
if !quiet {
// Init logger with optional debug flag.
logger::init_logger("leo", match cli.debug {
false => 1,
true => 2,
})?;
}

// Check for updates. If not forced, it checks once per day.
if !cli.disable_update_check
// Check for updates. If not forced, it checks once per day.
if !quiet
&& !cli.disable_update_check
&& let Ok(true) = updater::Updater::check_for_updates(false)
{
let _ = updater::Updater::print_cli();
}

// Get custom root folder and create context for it.
// If not specified, default context will be created in cwd.
let context = handle_error(Context::new(cli.path, cli.home, false));
let context = handle_error(Context::new(cli.path.clone(), cli.home, false));

let command_name = cli.command.name();
let mut command_output: Option<JsonOutput> = None;

match cli.command {
Commands::Add { command } => command.try_execute(context),
Commands::Account { command } => command.try_execute(context),
Commands::New { command } => command.try_execute(context),
Commands::Build { command } => command.try_execute(context),
Commands::Debug { command } => command.try_execute(context),
Commands::Query { command } => command.try_execute(context),
Commands::Clean { command } => command.try_execute(context),
Commands::Deploy { command } => command.try_execute(context),
Commands::Devnet { command } => command.try_execute(context),
Commands::Run { command } => command.try_execute(context),
Commands::Test { command } => command.try_execute(context),
Commands::Execute { command } => command.try_execute(context),
Commands::Remove { command } => command.try_execute(context),
Commands::Synthesize { command } => command.try_execute(context),
Commands::Update { command } => command.try_execute(context),
Commands::Upgrade { command } => command.try_execute(context),
Commands::Add { command } => command.try_execute(context)?,
Commands::Account { command } => command.try_execute(context)?,
Commands::New { command } => command.try_execute(context)?,
Commands::Build { command } => command.try_execute(context)?,
Commands::Debug { command } => command.try_execute(context)?,
Commands::Query { command } => {
let result = command.execute(context)?;
let data = serde_json::from_str(&result).unwrap_or_else(|_| serde_json::Value::String(result));
command_output = Some(JsonOutput::Query(data));
}
Commands::Clean { command } => command.try_execute(context)?,
Commands::Deploy { command } => command_output = Some(JsonOutput::Deploy(command.execute(context)?)),
Commands::Devnet { command } => command.try_execute(context)?,
Commands::Run { command } => command_output = Some(JsonOutput::Run(command.execute(context)?)),
Commands::Test { command } => command_output = Some(JsonOutput::Test(command.execute(context)?)),
Commands::Execute { command } => command_output = Some(JsonOutput::Execute(command.execute(context)?)),
Commands::Remove { command } => command.try_execute(context)?,
Commands::Synthesize { command } => command_output = Some(JsonOutput::Synthesize(command.execute(context)?)),
Commands::Update { command } => command.try_execute(context)?,
Commands::Upgrade { command } => command_output = Some(JsonOutput::Deploy(command.execute(context)?)),
}

if let Some(json_output_arg) = cli.json_output
&& let Some(output) = command_output
{
let json = serde_json::to_string_pretty(&output).expect("JSON serialization failed");

// Use provided path or default to build/json-outputs/<command>.json
let path = if json_output_arg.is_empty() {
cli.path
.unwrap_or_else(|| PathBuf::from("."))
.join("build")
.join("json-outputs")
.join(format!("{command_name}.json"))
} else {
PathBuf::from(json_output_arg)
};

if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)
.map_err(|e| CliError::custom(format!("Failed to create directory: {e}")))?;
}
std::fs::write(&path, json)
.map_err(|e| CliError::custom(format!("Failed to write JSON output to {}: {e}", path.display())))?;
}

Ok(())
}

#[cfg(test)]
Expand Down Expand Up @@ -223,6 +300,7 @@ mod tests {
let run = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::Run {
command: crate::cli::commands::LeoRun {
Expand Down Expand Up @@ -266,6 +344,7 @@ mod tests {
let run = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::Run {
command: crate::cli::commands::LeoRun {
Expand Down Expand Up @@ -310,6 +389,7 @@ mod tests {
let run = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::Run {
command: crate::cli::commands::LeoRun {
Expand Down Expand Up @@ -348,6 +428,7 @@ mod tests {
let run = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::Run {
command: crate::cli::commands::LeoRun {
Expand Down Expand Up @@ -389,6 +470,7 @@ mod test_helpers {
let new = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::New {
command: LeoNew {
Expand Down Expand Up @@ -463,6 +545,7 @@ function external_nested_function:
let add = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::Add {
command: LeoAdd {
Expand Down Expand Up @@ -510,6 +593,7 @@ function external_nested_function:
let create_grandparent_project = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::New {
command: LeoNew {
Expand All @@ -525,6 +609,7 @@ function external_nested_function:
let create_parent_project = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::New {
command: LeoNew {
Expand All @@ -540,6 +625,7 @@ function external_nested_function:
let create_child_project = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::New {
command: LeoNew {
Expand Down Expand Up @@ -597,6 +683,7 @@ program child.aleo {
let add_grandparent_dependency_1 = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::Add {
command: LeoAdd {
Expand All @@ -613,6 +700,7 @@ program child.aleo {
let add_grandparent_dependency_2 = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::Add {
command: LeoAdd {
Expand All @@ -629,6 +717,7 @@ program child.aleo {
let add_parent_dependency = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::Add {
command: LeoAdd {
Expand Down Expand Up @@ -674,6 +763,7 @@ program child.aleo {
let create_outer_project = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::New {
command: LeoNew {
Expand All @@ -689,6 +779,7 @@ program child.aleo {
let create_inner_1_project = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::New {
command: LeoNew {
Expand All @@ -704,6 +795,7 @@ program child.aleo {
let create_inner_2_project = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::New {
command: LeoNew {
Expand Down Expand Up @@ -784,6 +876,7 @@ program outer.aleo {
let add_outer_dependency_1 = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::Add {
command: LeoAdd {
Expand All @@ -800,6 +893,7 @@ program outer.aleo {
let add_outer_dependency_2 = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::Add {
command: LeoAdd {
Expand Down Expand Up @@ -844,6 +938,7 @@ program outer.aleo {
let create_outer_project = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::New {
command: LeoNew {
Expand All @@ -859,6 +954,7 @@ program outer.aleo {
let create_inner_1_project = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::New {
command: LeoNew {
Expand All @@ -874,6 +970,7 @@ program outer.aleo {
let create_inner_2_project = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::New {
command: LeoNew {
Expand Down Expand Up @@ -985,6 +1082,7 @@ program outer_2.aleo {
let add_outer_dependency_1 = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::Add {
command: LeoAdd {
Expand All @@ -1001,6 +1099,7 @@ program outer_2.aleo {
let add_outer_dependency_2 = CLI {
debug: false,
quiet: false,
json_output: None,
disable_update_check: false,
command: Commands::Add {
command: LeoAdd {
Expand Down
3 changes: 3 additions & 0 deletions leo/cli/commands/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ pub use interactive::*;
mod options;
pub use options::*;

mod output;
pub use output::*;

mod query;
pub use query::*;

Expand Down
Loading