use anyhow::Result;
use clap::{Parser, Subcommand};
mod check;
mod client_gen;
mod commands;
mod config;
mod idl;
mod templates;
use commands::client::ClientCommands;
use commands::config::ConfigCommands;
use commands::keys::KeyCommands;
use idl::Generator;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Init {
project_name: String,
#[arg(long, help = "Don't initialize git")]
no_git: bool,
#[arg(
long,
help = "Include a worked PDA-account example instead of a no-op program"
)]
with_example: bool,
},
Build {
#[arg(long, short, help = "Suppress verbose output")]
quiet: bool,
#[arg(
long,
short = 'F',
value_delimiter = ',',
help = "Space or comma separated list of features to activate (repeatable)"
)]
features: Vec<String>,
#[arg(
long,
value_enum,
help = "SBPF version to build for, passed to cargo build-sbf (default: [build].arch in Pinoc.toml, else the toolchain's)"
)]
arch: Option<config::Arch>,
#[arg(
long,
help = "Program address override for IDL generation, for programs that don't use declare_id!"
)]
program_id: Option<String>,
#[arg(
long,
value_enum,
help = "Force the .codama.json generator instead of auto-detecting Codama macros"
)]
idl_generator: Option<Generator>,
},
Test {
#[arg(long, short, help = "Suppress verbose output")]
quiet: bool,
#[arg(
long,
short = 'F',
value_delimiter = ',',
help = "Space or comma separated list of features to activate (repeatable)"
)]
features: Vec<String>,
#[arg(
long,
value_enum,
help = "SBPF version to build for, passed to cargo build-sbf (default: [build].arch in Pinoc.toml, else the toolchain's)"
)]
arch: Option<config::Arch>,
#[arg(
long,
help = "Skip the cargo build-sbf step and test against the existing target/deploy/*.so"
)]
no_build: bool,
#[arg(
long,
value_delimiter = ',',
help = "Features for the pre-test SBF build, if different from --features (\"\" for none)"
)]
build_features: Option<Vec<String>>,
#[arg(
last = true,
value_name = "CARGO_ARGS",
help = "Arguments for cargo test, e.g. `-- --test client -- --nocapture`"
)]
cargo_args: Vec<String>,
},
Deploy {
#[arg(long, help = "Cluster override")]
cluster: Option<String>,
#[arg(long, help = "Wallet override")]
wallet: Option<String>,
},
Clean {
#[arg(long, help = "Remove all files including keypair files")]
no_preserve: bool,
},
Add {
package_name: String,
},
Search {
query: Option<String>,
},
Keys {
#[command(subcommand)]
command: KeyCommands,
},
Idl {
#[arg(
long,
help = "Output directory for the IDL JSON",
default_value = "target/idl"
)]
out_dir: String,
#[arg(
long,
help = "Program address to use, for programs that don't call declare_id!"
)]
program_id: Option<String>,
#[arg(
long,
value_enum,
help = "Force the .codama.json generator instead of auto-detecting Codama macros"
)]
idl_generator: Option<Generator>,
},
Client {
#[command(subcommand)]
command: ClientCommands,
},
Config {
#[command(subcommand)]
command: ConfigCommands,
},
Check {
#[arg(long, help = "Emit findings as JSON")]
json: bool,
#[arg(
long,
num_args = 1..,
help = "Promote these lint codes to deny; `*` or `all` for every code"
)]
deny: Vec<String>,
#[arg(
long,
num_args = 1..,
help = "Suppress these lint codes; `*` or `all` for every code"
)]
allow: Vec<String>,
},
#[command(name = "--help")]
Help,
}
fn main() -> Result<()> {
let cli = Cli::parse();
match &cli.command {
Commands::Init {
project_name,
no_git,
with_example,
} => {
commands::init::init_project(project_name, *no_git, *with_example)?;
}
Commands::Build {
quiet,
features,
arch,
program_id,
idl_generator,
} => {
commands::build::run_build(
*quiet,
features,
*arch,
program_id.as_deref(),
*idl_generator,
)?;
}
Commands::Test {
quiet,
features,
arch,
no_build,
build_features,
cargo_args,
} => {
commands::test::run_test(
*quiet,
features,
build_features.as_deref(),
*arch,
*no_build,
cargo_args,
)?;
}
Commands::Deploy { cluster, wallet } => {
commands::deploy::run_deploy(cluster.as_deref(), wallet.as_deref())?;
}
Commands::Clean { no_preserve } => {
commands::clean::clean_project(*no_preserve)?;
}
Commands::Add { package_name } => {
commands::packages::add_package(package_name)?;
}
Commands::Search { query } => {
commands::packages::search_packages(query.as_deref())?;
}
Commands::Keys { command } => match command {
KeyCommands::List => {
commands::keys::list_program_keys()?;
}
KeyCommands::Sync => {
commands::keys::sync_program_keys()?;
}
},
Commands::Idl {
out_dir,
program_id,
idl_generator,
} => {
commands::idl::run_idl(out_dir, program_id.as_deref(), *idl_generator)?;
}
Commands::Client { command } => match command {
ClientCommands::Generate {
out_dir,
idl_dir,
generator,
language,
auto_install,
yes,
with_cpi,
no_cpi,
} => {
commands::client::generate_client(commands::client::GenerateArgs {
idl_dir,
out_dir: out_dir.as_deref(),
generator: *generator,
language: *language,
auto_install: *auto_install,
yes: *yes,
with_cpi: *with_cpi,
no_cpi: *no_cpi,
})?;
}
},
Commands::Config { command } => match command {
ConfigCommands::Init { yes } => {
commands::config::init_config(*yes)?;
}
},
Commands::Check { json, deny, allow } => {
let code = commands::check::run_check(*json, deny.clone(), allow.clone())?;
std::process::exit(code);
}
Commands::Help => {
commands::help::display_help_banner()?;
}
}
Ok(())
}