mirror of
https://github.com/logos-co/scaffold.git
synced 2026-08-27 12:41:13 +00:00
324 lines
9.0 KiB
Rust
324 lines
9.0 KiB
Rust
use std::path::PathBuf;
|
|
|
|
use anyhow::anyhow;
|
|
use clap::{CommandFactory, Parser, Subcommand};
|
|
|
|
use crate::commands::build::cmd_build_shortcut;
|
|
use crate::commands::client::cmd_client;
|
|
use crate::commands::deploy::cmd_deploy;
|
|
use crate::commands::doctor::cmd_doctor;
|
|
use crate::commands::idl::cmd_idl;
|
|
use crate::commands::localnet::{cmd_localnet, LocalnetAction};
|
|
use crate::commands::new::{cmd_new, NewCommand};
|
|
use crate::commands::report::cmd_report;
|
|
use crate::commands::setup::{cmd_setup, SetupCommand, WalletInstallMode};
|
|
use crate::commands::wallet::{cmd_wallet, WalletAction};
|
|
use crate::constants::VERSION;
|
|
use crate::DynResult;
|
|
|
|
#[derive(Debug, Parser)]
|
|
#[command(
|
|
name = "logos-scaffold",
|
|
version = VERSION,
|
|
disable_help_subcommand = true
|
|
)]
|
|
struct Cli {
|
|
#[command(subcommand)]
|
|
command: Option<Commands>,
|
|
}
|
|
|
|
#[derive(Debug, Subcommand)]
|
|
enum Commands {
|
|
#[command(about = "Create a new logos-scaffold project")]
|
|
Create(NewArgs),
|
|
#[command(about = "Alias for `create`")]
|
|
New(NewArgs),
|
|
Setup(SetupArgs),
|
|
Build(BuildArgs),
|
|
Deploy(DeployArgs),
|
|
Localnet(LocalnetArgs),
|
|
Wallet(WalletArgs),
|
|
Doctor(DoctorArgs),
|
|
#[command(about = "Collect a sanitized diagnostics archive for issue reporting")]
|
|
Report(ReportArgs),
|
|
#[command(hide = true)]
|
|
Help,
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct NewArgs {
|
|
name: String,
|
|
#[arg(long)]
|
|
vendor_deps: bool,
|
|
#[arg(long)]
|
|
lssa_path: Option<PathBuf>,
|
|
#[arg(long)]
|
|
cache_root: Option<PathBuf>,
|
|
#[arg(long, default_value = "default")]
|
|
template: String,
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct SetupArgs {
|
|
#[arg(long, value_enum, default_value_t = WalletInstallMode::Auto)]
|
|
wallet_install: WalletInstallMode,
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct BuildArgs {
|
|
#[command(subcommand)]
|
|
subcommand: Option<BuildSubcommand>,
|
|
project_path: Option<PathBuf>,
|
|
}
|
|
|
|
#[derive(Debug, Subcommand)]
|
|
enum BuildSubcommand {
|
|
#[command(about = "Build IDL files from the current project")]
|
|
Idl(BuildSubArgs),
|
|
#[command(about = "Build client code from IDL files")]
|
|
Client(BuildSubArgs),
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct BuildSubArgs {
|
|
project_path: Option<PathBuf>,
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct DeployArgs {
|
|
program_name: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct DoctorArgs {
|
|
#[arg(long)]
|
|
json: bool,
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct ReportArgs {
|
|
#[arg(long)]
|
|
out: Option<PathBuf>,
|
|
#[arg(long, default_value_t = 500)]
|
|
tail: usize,
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct LocalnetArgs {
|
|
#[command(subcommand)]
|
|
command: LocalnetSubcommand,
|
|
}
|
|
|
|
#[derive(Debug, Subcommand)]
|
|
enum LocalnetSubcommand {
|
|
Start(LocalnetStartArgs),
|
|
Stop,
|
|
Status(LocalnetStatusArgs),
|
|
Logs(LocalnetLogsArgs),
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct LocalnetStartArgs {
|
|
#[arg(long, default_value_t = 20)]
|
|
timeout_sec: u64,
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct LocalnetStatusArgs {
|
|
#[arg(long)]
|
|
json: bool,
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct LocalnetLogsArgs {
|
|
#[arg(long, default_value_t = 200)]
|
|
tail: usize,
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct WalletArgs {
|
|
#[command(subcommand)]
|
|
command: WalletSubcommand,
|
|
}
|
|
|
|
#[derive(Debug, Subcommand)]
|
|
enum WalletSubcommand {
|
|
#[command(about = "List wallet accounts (same as `wallet account list`)")]
|
|
List(WalletListArgs),
|
|
#[command(about = "Top up wallet using pinata faucet claim")]
|
|
Topup(WalletTopupArgs),
|
|
#[command(about = "Manage project default wallet")]
|
|
Default(WalletDefaultArgs),
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct WalletListArgs {
|
|
#[arg(long)]
|
|
long: bool,
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct WalletTopupArgs {
|
|
#[arg(value_name = "ADDRESS")]
|
|
address: Option<String>,
|
|
#[arg(long = "address", value_name = "ADDRESS")]
|
|
address_flag: Option<String>,
|
|
#[arg(long)]
|
|
dry_run: bool,
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct WalletDefaultArgs {
|
|
#[command(subcommand)]
|
|
command: WalletDefaultSubcommand,
|
|
}
|
|
|
|
#[derive(Debug, Subcommand)]
|
|
enum WalletDefaultSubcommand {
|
|
Set(WalletDefaultSetArgs),
|
|
}
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
struct WalletDefaultSetArgs {
|
|
#[arg(value_name = "ADDRESS")]
|
|
address: Option<String>,
|
|
#[arg(long = "address", value_name = "ADDRESS")]
|
|
address_flag: Option<String>,
|
|
}
|
|
|
|
pub(crate) fn run(args: Vec<String>) -> DynResult<()> {
|
|
if let Some(action) = wallet_passthrough_action(&args)? {
|
|
return cmd_wallet(action);
|
|
}
|
|
|
|
let cli = match Cli::try_parse_from(args) {
|
|
Ok(cli) => cli,
|
|
Err(err) => match err.kind() {
|
|
clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion => {
|
|
print!("{err}");
|
|
return Ok(());
|
|
}
|
|
_ => return Err(anyhow!(err.to_string())),
|
|
},
|
|
};
|
|
|
|
match cli.command {
|
|
Some(Commands::Create(args)) | Some(Commands::New(args)) => cmd_new(NewCommand {
|
|
name: args.name,
|
|
vendor_deps: args.vendor_deps,
|
|
lssa_path: args.lssa_path,
|
|
cache_root: args.cache_root,
|
|
template: args.template,
|
|
}),
|
|
Some(Commands::Setup(args)) => cmd_setup(SetupCommand {
|
|
wallet_install: args.wallet_install,
|
|
}),
|
|
Some(Commands::Build(args)) => match args.subcommand {
|
|
Some(BuildSubcommand::Idl(sub)) => cmd_idl(
|
|
&sub.project_path
|
|
.map(|p| vec!["build".to_string(), p.to_string_lossy().to_string()])
|
|
.unwrap_or_else(|| vec!["build".to_string()]),
|
|
),
|
|
Some(BuildSubcommand::Client(sub)) => cmd_client(
|
|
&sub.project_path
|
|
.map(|p| vec!["build".to_string(), p.to_string_lossy().to_string()])
|
|
.unwrap_or_else(|| vec!["build".to_string()]),
|
|
),
|
|
None => cmd_build_shortcut(args.project_path),
|
|
},
|
|
Some(Commands::Deploy(args)) => cmd_deploy(args.program_name),
|
|
Some(Commands::Localnet(localnet)) => {
|
|
let action = match localnet.command {
|
|
LocalnetSubcommand::Start(args) => LocalnetAction::Start {
|
|
timeout_sec: args.timeout_sec,
|
|
},
|
|
LocalnetSubcommand::Stop => LocalnetAction::Stop,
|
|
LocalnetSubcommand::Status(args) => LocalnetAction::Status { json: args.json },
|
|
LocalnetSubcommand::Logs(args) => LocalnetAction::Logs { tail: args.tail },
|
|
};
|
|
cmd_localnet(action)
|
|
}
|
|
Some(Commands::Wallet(args)) => {
|
|
let action = match args.command {
|
|
WalletSubcommand::List(args) => WalletAction::List { long: args.long },
|
|
WalletSubcommand::Topup(args) => WalletAction::Topup {
|
|
address: merge_optional_address(
|
|
args.address,
|
|
args.address_flag,
|
|
"wallet topup",
|
|
)?,
|
|
dry_run: args.dry_run,
|
|
},
|
|
WalletSubcommand::Default(args) => match args.command {
|
|
WalletDefaultSubcommand::Set(set) => WalletAction::DefaultSet {
|
|
address: require_address(
|
|
set.address,
|
|
set.address_flag,
|
|
"wallet default set",
|
|
)?,
|
|
},
|
|
},
|
|
};
|
|
cmd_wallet(action)
|
|
}
|
|
Some(Commands::Doctor(args)) => cmd_doctor(args.json),
|
|
Some(Commands::Report(args)) => cmd_report(args.out, args.tail),
|
|
Some(Commands::Help) => print_help(),
|
|
None => print_help(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn print_help() -> DynResult<()> {
|
|
let mut cmd = Cli::command();
|
|
cmd.print_help()?;
|
|
println!();
|
|
Ok(())
|
|
}
|
|
|
|
fn wallet_passthrough_action(args: &[String]) -> DynResult<Option<WalletAction>> {
|
|
if args.len() < 3 {
|
|
return Ok(None);
|
|
}
|
|
|
|
if args[1] == "wallet" && args[2] == "--" {
|
|
if args.len() == 3 {
|
|
return Err(anyhow!(
|
|
"wallet passthrough requires at least one argument after `--`. Example: `logos-scaffold wallet -- account list`"
|
|
));
|
|
}
|
|
|
|
return Ok(Some(WalletAction::Proxy {
|
|
args: args[3..].to_vec(),
|
|
}));
|
|
}
|
|
|
|
Ok(None)
|
|
}
|
|
|
|
fn merge_optional_address(
|
|
positional: Option<String>,
|
|
flagged: Option<String>,
|
|
context: &str,
|
|
) -> DynResult<Option<String>> {
|
|
if positional.is_some() && flagged.is_some() {
|
|
return Err(anyhow!(
|
|
"{context}: provide address either as positional argument or `--address`, not both."
|
|
));
|
|
}
|
|
|
|
Ok(positional.or(flagged))
|
|
}
|
|
|
|
fn require_address(
|
|
positional: Option<String>,
|
|
flagged: Option<String>,
|
|
context: &str,
|
|
) -> DynResult<String> {
|
|
let merged = merge_optional_address(positional, flagged, context)?;
|
|
merged.ok_or_else(|| {
|
|
anyhow!(
|
|
"{context} requires an address. Examples: `logos-scaffold wallet default set <address>` or `logos-scaffold wallet default set --address <address>`."
|
|
)
|
|
})
|
|
}
|