use std::collections::HashMap;
use anyhow::Result;
use serde_json::json;
use smithy::{GenerateCli, LintCli, ValidateCli};
use wash_lib::{
cli::{
capture::{CaptureCommand, CaptureSubcommand},
claims::ClaimsCliCommand,
get::GetCommand,
inspect::InspectCliCommand,
link::LinkCommand,
registry::{RegistryCommand, RegistryPullCommand, RegistryPushCommand},
spy::SpyCommand,
start::StartCommand,
stop::StopCommand,
CommandOutput, OutputKind,
},
drain::Drain as DrainSelection,
};
use app::AppCliCommand;
use build::BuildCommand;
use call::CallCli;
use clap::{Parser, Subcommand};
use completions::CompletionOpts;
use ctl::CtlCliCommand;
use ctx::CtxCommand;
use down::DownCommand;
use generate::NewCliCommand;
use keys::KeysCliCommand;
use par::ParCliCommand;
use up::UpCommand;
mod app;
mod appearance;
mod build;
mod call;
mod cfg;
mod common;
mod completions;
mod ctl;
mod ctx;
mod down;
mod drain;
mod generate;
mod keys;
mod par;
mod smithy;
mod up;
mod util;
const HELP: &str = r#"
_________________________________________________________________________________
_____ _ _ _____ _ _ _
/ ____| | | | / ____| | | | |
__ ____ _ ___ _ __ ___ | | | | ___ _ _ __| | | (___ | |__ ___| | |
\ \ /\ / / _` / __| '_ ` _ \| | | |/ _ \| | | |/ _` | \___ \| '_ \ / _ \ | |
\ V V / (_| \__ \ | | | | | |____| | (_) | |_| | (_| | ____) | | | | __/ | |
\_/\_/ \__,_|___/_| |_| |_|\_____|_|\___/ \__,_|\__,_| |_____/|_| |_|\___|_|_|
_________________________________________________________________________________
Interact and manage wasmCloud applications, projects, and runtime environments
Usage: wash [OPTIONS] <COMMAND>
Applications:
app Manage declarative applications and deployments (wadm) (experimental)
call Invoke a wasmCloud actor
ctl Interact with a wasmCloud control interface
Projects:
build Build (and sign) a wasmCloud actor, provider, or interface
claims Generate and manage JWTs for wasmCloud actors
gen Generate code from smithy IDL files
inspect Inspect capability provider or actor module
lint Perform lint checks on smithy models
new Create a new project from template
par Create, inspect, and modify capability provider archive files
reg Push an actor or provider component to an OCI or Bindle registry
spy Spy on all invocations between an actor and its linked providers
validate Perform validation checks on smithy models
Configuration:
completions Generate shell completions
ctx Manage wasmCloud host configuration contexts
drain Manage contents of local wasmCloud caches
keys Utilities for generating and managing keys
Runtime environments:
up Bootstrap a wasmCloud environment
down Tear down a wasmCloud environment launched with wash up
Options:
-o, --output <OUTPUT> Specify output format (text or json) [default: text]
--experimental Whether or not to enable experimental features [default: false]
-h, --help Print help
-V, --version Print version
"#;
#[derive(Debug, Clone, Parser)]
#[clap(name = "wash", version, override_help = HELP)]
struct Cli {
#[clap(
short = 'o',
long = "output",
default_value = "text",
help = "Specify output format (text or json)",
global = true
)]
pub(crate) output: OutputKind,
#[clap(
long = "experimental",
id = "experimental",
env = "WASH_EXPERIMENTAL",
default_value = "false",
help = "Whether or not to enable experimental features",
global = true
)]
pub(crate) experimental: bool,
#[clap(subcommand)]
command: CliCommand,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, Subcommand)]
enum CliCommand {
#[clap(name = "app", subcommand)]
App(AppCliCommand),
#[clap(name = "build")]
Build(BuildCommand),
#[clap(name = "call")]
Call(CallCli),
#[clap(name = "capture")]
Capture(CaptureCommand),
#[clap(name = "completions")]
Completions(CompletionOpts),
#[clap(name = "claims", subcommand)]
Claims(ClaimsCliCommand),
#[clap(name = "ctl", subcommand)]
Ctl(CtlCliCommand),
#[clap(name = "ctx", subcommand)]
Ctx(CtxCommand),
#[clap(name = "down")]
Down(DownCommand),
#[clap(name = "drain", subcommand)]
Drain(DrainSelection),
#[clap(name = "gen")]
Gen(GenerateCli),
#[clap(name = "get", subcommand)]
Get(GetCommand),
#[clap(name = "inspect")]
Inspect(InspectCliCommand),
#[clap(name = "keys", subcommand)]
Keys(KeysCliCommand),
#[clap(name = "lint")]
Lint(LintCli),
#[clap(name = "link", subcommand)]
Link(LinkCommand),
#[clap(name = "new", subcommand)]
New(NewCliCommand),
#[clap(name = "par", subcommand)]
Par(ParCliCommand),
#[clap(name = "reg", subcommand)]
Reg(RegistryCommand),
#[clap(name = "push")]
RegPush(RegistryPushCommand),
#[clap(name = "pull")]
RegPull(RegistryPullCommand),
#[clap(name = "spy")]
Spy(SpyCommand),
#[clap(name = "start", subcommand)]
Start(StartCommand),
#[clap(name = "stop", subcommand)]
Stop(StopCommand),
#[clap(name = "up")]
Up(UpCommand),
#[clap(name = "validate")]
Validate(ValidateCli),
}
#[tokio::main]
async fn main() {
use clap::CommandFactory;
if env_logger::try_init().is_err() {}
let cli: Cli = Parser::parse();
let output_kind = cli.output;
let res: Result<CommandOutput> = match cli.command {
CliCommand::App(app_cli) => app::handle_command(app_cli, output_kind).await,
CliCommand::Build(build_cli) => build::handle_command(build_cli),
CliCommand::Call(call_cli) => call::handle_command(call_cli.command()).await,
CliCommand::Capture(capture_cli) => {
if !cli.experimental {
experimental_error_message("capture")
} else if let Some(CaptureSubcommand::Replay(cmd)) = capture_cli.replay {
wash_lib::cli::capture::handle_replay_command(cmd).await
} else {
wash_lib::cli::capture::handle_command(capture_cli).await
}
}
CliCommand::Claims(claims_cli) => {
wash_lib::cli::claims::handle_command(claims_cli, output_kind).await
}
CliCommand::Completions(completions_cli) => {
completions::handle_command(completions_cli, Cli::command())
}
CliCommand::Ctl(ctl_cli) => ctl::handle_command(ctl_cli, output_kind).await,
CliCommand::Ctx(ctx_cli) => ctx::handle_command(ctx_cli).await,
CliCommand::Down(down_cli) => down::handle_command(down_cli, output_kind).await,
CliCommand::Drain(drain_cli) => drain::handle_command(drain_cli),
CliCommand::Get(get_cli) => common::get_cmd::handle_command(get_cli, output_kind).await,
CliCommand::Gen(generate_cli) => smithy::handle_gen_command(generate_cli),
CliCommand::Inspect(inspect_cli) => {
wash_lib::cli::inspect::handle_command(inspect_cli, output_kind).await
}
CliCommand::Keys(keys_cli) => keys::handle_command(keys_cli),
CliCommand::Lint(lint_cli) => smithy::handle_lint_command(lint_cli).await,
CliCommand::Link(link_cli) => common::link_cmd::handle_command(link_cli, output_kind).await,
CliCommand::New(new_cli) => generate::handle_command(new_cli).await,
CliCommand::Par(par_cli) => par::handle_command(par_cli, output_kind).await,
CliCommand::Reg(reg_cli) => {
common::registry_cmd::handle_command(reg_cli, output_kind).await
}
CliCommand::RegPush(reg_push_cli) => {
common::registry_cmd::registry_push(reg_push_cli, output_kind).await
}
CliCommand::RegPull(reg_pull_cli) => {
common::registry_cmd::registry_pull(reg_pull_cli, output_kind).await
}
CliCommand::Spy(spy_cli) => {
if !cli.experimental {
experimental_error_message("spy")
} else {
wash_lib::cli::spy::handle_command(spy_cli).await
}
}
CliCommand::Start(start_cli) => {
common::start_cmd::handle_command(start_cli, output_kind).await
}
CliCommand::Stop(stop_cli) => common::stop_cmd::handle_command(stop_cli, output_kind).await,
CliCommand::Up(up_cli) => up::handle_command(up_cli, output_kind).await,
CliCommand::Validate(validate_cli) => smithy::handle_validate_command(validate_cli).await,
};
std::process::exit(match res {
Ok(out) => {
match output_kind {
OutputKind::Json => {
let mut map = out.map;
map.insert("success".to_string(), json!(true));
println!("\n{}", serde_json::to_string_pretty(&map).unwrap());
0
}
OutputKind::Text => {
println!("\n{}", out.text);
match completions::first_run_suggestion() {
Ok(Some(suggestion)) => {
println!("\n{}", suggestion);
0
}
Ok(None) => {
0
}
Err(e) => {
eprintln!("\nError: {}", e);
1
}
}
}
}
}
Err(e) => {
match output_kind {
OutputKind::Json => {
let mut map = HashMap::new();
map.insert("success".to_string(), json!(false));
map.insert("error".to_string(), json!(e.to_string()));
let error_chain = e
.chain()
.skip(1)
.map(|e| format!("{e}"))
.collect::<Vec<String>>();
if !error_chain.is_empty() {
map.insert("error_chain".to_string(), json!(error_chain));
}
let backtrace = e.backtrace().to_string();
if !backtrace.is_empty() && backtrace != "disabled backtrace" {
map.insert("backtrace".to_string(), json!(backtrace));
}
eprintln!("\n{}", serde_json::to_string_pretty(&map).unwrap());
}
OutputKind::Text => {
eprintln!("\n{e:?}");
}
}
1
}
})
}
fn experimental_error_message(command: &str) -> Result<CommandOutput> {
Err(anyhow::anyhow!("The `wash {command}` command is experimental and may change in future releases. Set the `WASH_EXPERIMENTAL` environment variable or `--experimental` flag to `true` to use this command."))
}