diff --git a/Cargo.lock b/Cargo.lock index 8ba01fb74c..3cf50b2d7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -563,8 +563,8 @@ checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", "bitflags", - "clap_derive", - "clap_lex", + "clap_derive 3.2.18", + "clap_lex 0.2.4", "indexmap", "once_cell", "strsim", @@ -572,6 +572,21 @@ dependencies = [ "textwrap 0.16.0", ] +[[package]] +name = "clap" +version = "4.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3" +dependencies = [ + "bitflags", + "clap_derive 4.1.0", + "clap_lex 0.3.2", + "is-terminal", + "once_cell", + "strsim", + "termcolor", +] + [[package]] name = "clap_derive" version = "3.2.18" @@ -585,6 +600,19 @@ dependencies = [ "syn", ] +[[package]] +name = "clap_derive" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8" +dependencies = [ + "heck 0.4.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "clap_lex" version = "0.2.4" @@ -594,6 +622,15 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clap_lex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "cloud" version = "0.9.0" @@ -4249,7 +4286,7 @@ version = "0.9.0" dependencies = [ "anyhow", "cap-std", - "clap 3.2.23", + "clap 4.1.6", "rand 0.8.5", "rand_chacha 0.3.1", "rand_core 0.6.4", @@ -4328,7 +4365,7 @@ dependencies = [ "bytes", "cargo-target-dep", "chrono", - "clap 3.2.23", + "clap 4.1.6", "cloud", "cloud-openapi", "comfy-table", @@ -4424,7 +4461,7 @@ version = "0.9.0" dependencies = [ "anyhow", "async-trait", - "clap 3.2.23", + "clap 4.1.6", "criterion", "futures", "futures-util", @@ -4661,7 +4698,7 @@ version = "0.9.0" dependencies = [ "anyhow", "async-trait", - "clap 3.2.23", + "clap 4.1.6", "ctrlc", "dirs 4.0.0", "futures", diff --git a/Cargo.toml b/Cargo.toml index 8368e11811..e6a8892af2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ async-trait = "0.1" bindle = { workspace = true } bytes = "1.1" chrono = "0.4" -clap = { version = "3.1.15", features = ["derive", "env"] } +clap = { version = "4.1.6", features = ["derive", "env"] } cloud = { path = "crates/cloud" } cloud-openapi = { git = "https://github.com/fermyon/cloud-openapi" } comfy-table = "5.0" diff --git a/crates/abi-conformance/Cargo.toml b/crates/abi-conformance/Cargo.toml index cec73bc6e8..07a385a98d 100644 --- a/crates/abi-conformance/Cargo.toml +++ b/crates/abi-conformance/Cargo.toml @@ -7,7 +7,7 @@ edition = { workspace = true } [dependencies] anyhow = "1.0.44" cap-std = "1.0.3" -clap = { version = "3.1.15", features = ["derive", "env"] } +clap = { version = "4.1.6", features = ["derive", "env"] } rand = "0.8.5" rand_chacha = "0.3.1" rand_core = "0.6.3" diff --git a/crates/abi-conformance/src/bin/spin-abi-conformance.rs b/crates/abi-conformance/src/bin/spin-abi-conformance.rs index 66cfda0ad4..430a4d6a05 100644 --- a/crates/abi-conformance/src/bin/spin-abi-conformance.rs +++ b/crates/abi-conformance/src/bin/spin-abi-conformance.rs @@ -8,18 +8,18 @@ use std::{ use wasmtime::{Config, Engine, Module}; #[derive(Parser)] -#[clap(author, version, about)] +#[command(author, version, about)] pub struct Options { /// Name of Wasm file to test (or stdin if not specified) - #[clap(short, long)] + #[arg(short, long)] pub input: Option, /// Name of JSON file to write report to (or stdout if not specified) - #[clap(short, long)] + #[arg(short, long)] pub output: Option, /// Name of TOML configuration file to use - #[clap(short, long)] + #[arg(short, long)] pub config: Option, } diff --git a/crates/http/Cargo.toml b/crates/http/Cargo.toml index e0d196b6ff..0485d22679 100644 --- a/crates/http/Cargo.toml +++ b/crates/http/Cargo.toml @@ -10,7 +10,7 @@ doctest = false [dependencies] anyhow = "1.0" async-trait = "0.1" -clap = "3" +clap = "4.1.6" futures = "0.3" futures-util = "0.3.8" http = "0.2" diff --git a/crates/http/src/lib.rs b/crates/http/src/lib.rs index e1c4794237..6415679b53 100644 --- a/crates/http/src/lib.rs +++ b/crates/http/src/lib.rs @@ -59,15 +59,15 @@ pub struct HttpTrigger { #[derive(Args)] pub struct CliArgs { /// IP address and port to listen on - #[clap(long = "listen", default_value = "127.0.0.1:3000", value_parser = parse_listen_addr)] + #[arg(long = "listen", default_value = "127.0.0.1:3000", value_parser = parse_listen_addr)] pub address: SocketAddr, /// The path to the certificate to use for https, if this is not set, normal http will be used. The cert should be in PEM format - #[clap(long, env = "SPIN_TLS_CERT", requires = "tls-key")] + #[arg(long, env = "SPIN_TLS_CERT", requires = "tls_key")] pub tls_cert: Option, /// The path to the certificate key to use for https, if this is not set, normal http will be used. The key should be in PKCS#8 format - #[clap(long, env = "SPIN_TLS_KEY", requires = "tls-cert")] + #[arg(long, env = "SPIN_TLS_KEY", requires = "tls_cert")] pub tls_key: Option, } diff --git a/crates/trigger/Cargo.toml b/crates/trigger/Cargo.toml index aeef4b5aa2..572b575d5f 100644 --- a/crates/trigger/Cargo.toml +++ b/crates/trigger/Cargo.toml @@ -7,7 +7,7 @@ edition = { workspace = true } [dependencies] anyhow = "1.0" async-trait = "0.1" -clap = { version = "3.1.15", features = ["derive", "env"] } +clap = { version = "4.1", features = ["derive", "env"] } ctrlc = { version = "3.2", features = ["termination"] } dirs = "4" futures = "0.3" diff --git a/crates/trigger/src/cli.rs b/crates/trigger/src/cli.rs index 3583611bb5..a9469e711f 100644 --- a/crates/trigger/src/cli.rs +++ b/crates/trigger/src/cli.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use anyhow::{Context, Result}; -use clap::{Args, IntoApp, Parser}; +use clap::{ArgAction, Args, CommandFactory, Parser}; use serde::de::DeserializeOwned; use tokio::{ task::JoinHandle, @@ -26,13 +26,13 @@ pub const SPIN_WORKING_DIR: &str = "SPIN_WORKING_DIR"; /// A command that runs a TriggerExecutor. #[derive(Parser, Debug)] -#[clap(next_help_heading = "TRIGGER OPTIONS")] +#[command(next_help_heading = "TRIGGER OPTIONS", ignore_errors = true)] pub struct TriggerExecutorCommand where Executor::RunConfig: Args, { /// Log directory for the stdout and stderr of components. - #[clap( + #[arg( name = APP_LOG_DIR, short = 'L', long = "log-dir", @@ -40,17 +40,17 @@ where pub log: Option, /// Disable Wasmtime cache. - #[clap( + #[arg( name = DISABLE_WASMTIME_CACHE, long = "disable-cache", env = DISABLE_WASMTIME_CACHE, conflicts_with = WASMTIME_CACHE_FILE, - takes_value = false, + action = ArgAction::SetTrue, )] pub disable_cache: bool, /// Wasmtime cache configuration file. - #[clap( + #[arg( name = WASMTIME_CACHE_FILE, long = "cache", env = WASMTIME_CACHE_FILE, @@ -59,15 +59,15 @@ where pub cache: Option, /// Print output to stdout/stderr only for given component(s) - #[clap( + #[arg( name = FOLLOW_LOG_OPT, long = "follow", - multiple_occurrences = true, + action = ArgAction::Append, )] pub follow_components: Vec, /// Silence all component output to stdout/stderr - #[clap( + #[arg( long = "quiet", short = 'q', aliases = &["sh", "shush"], @@ -76,25 +76,25 @@ where pub silence_component_logs: bool, /// Set the static assets of the components in the temporary directory as writable. - #[clap(long = "allow-transient-write")] + #[arg(long = "allow-transient-write")] pub allow_transient_write: bool, /// Configuration file for config providers and wasmtime config. - #[clap( + #[arg( name = RUNTIME_CONFIG_FILE, long = "runtime-config-file", env = RUNTIME_CONFIG_FILE, )] pub runtime_config_file: Option, - #[clap(flatten)] + #[command(flatten)] pub run_config: Executor::RunConfig, - #[clap(long = "help-args-only", hide = true)] + #[arg(long = "help-args-only", hide = true)] pub help_args_only: bool, /// Load the application from the registry. - #[clap(long = "from-registry", hide = true)] + #[arg(long = "from-registry", hide = true)] pub from_registry: bool, } diff --git a/examples/spin-timer/Cargo.toml b/examples/spin-timer/Cargo.toml index 55993a58d5..4c10c0904f 100644 --- a/examples/spin-timer/Cargo.toml +++ b/examples/spin-timer/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] anyhow = "1.0.68" async-trait = "0.1" -clap = { version = "3.1.15", features = ["derive", "env"] } +clap = { version = "4.1", features = ["derive", "env"] } futures = "0.3.25" serde = "1.0" spin-core = { version = "0.7.1", path = "../../crates/core" } diff --git a/examples/spin-timer/src/main.rs b/examples/spin-timer/src/main.rs index c9b96f0bc8..e6315007fc 100644 --- a/examples/spin-timer/src/main.rs +++ b/examples/spin-timer/src/main.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; use anyhow::Error; -use clap::{Parser}; use serde::{Deserialize, Serialize}; use spin_trigger::{cli::TriggerExecutorCommand, TriggerExecutor, TriggerAppEngine}; diff --git a/src/bin/spin.rs b/src/bin/spin.rs index a3164adf56..57d3fc324d 100644 --- a/src/bin/spin.rs +++ b/src/bin/spin.rs @@ -41,40 +41,42 @@ fn version() -> &'static str { /// The Spin CLI #[derive(Parser)] -#[clap( +#[command( name = "spin", version = version(), + next_display_order = None )] enum SpinApp { - #[clap(subcommand, alias = "template")] + #[command(subcommand, alias = "template")] Templates(TemplateCommands), New(NewCommand), Add(AddCommand), Up(UpCommand), - #[clap(subcommand)] + #[command(subcommand)] Bindle(BindleCommands), - #[clap(subcommand)] + #[command(subcommand)] Cloud(CloudCommands), // acts as a cross-level subcommand shortcut -> `spin cloud deploy` Deploy(DeployCommand), // acts as a cross-level subcommand shortcut -> `spin cloud login` Login(LoginCommand), - #[clap(subcommand, alias = "oci")] + #[command(subcommand, alias = "oci")] Registry(RegistryCommands), Build(BuildCommand), - #[clap(subcommand, alias = "plugin")] + #[command(subcommand, alias = "plugin")] Plugins(PluginCommands), - #[clap(subcommand, hide = true)] + #[command(subcommand, hide = true)] Trigger(TriggerCommands), - #[clap(external_subcommand)] + #[command(external_subcommand)] External(Vec), } #[derive(Subcommand)] +#[command(next_display_order = None, ignore_errors=true)] enum TriggerCommands { Http(TriggerExecutorCommand), Redis(TriggerExecutorCommand), - #[clap(name = spin_cli::HELP_ARGS_ONLY_TRIGGER_TYPE, hide = true)] + #[command(name = spin_cli::HELP_ARGS_ONLY_TRIGGER_TYPE, hide = true)] HelpArgsOnly(TriggerExecutorCommand), } diff --git a/src/commands/bindle.rs b/src/commands/bindle.rs index 65677080d6..a41cee5a0e 100644 --- a/src/commands/bindle.rs +++ b/src/commands/bindle.rs @@ -9,6 +9,7 @@ use crate::{opts::*, parse_buildinfo, sloth::warn_if_slow_response}; /// Commands for publishing applications as bindles. #[derive(Subcommand, Debug)] +#[command(next_display_order = None)] pub enum BindleCommands { /// Create a standalone bindle for subsequent publication. Prepare(Prepare), @@ -30,7 +31,7 @@ impl BindleCommands { #[derive(Parser, Debug)] pub struct Prepare { /// Path to spin.toml - #[clap( + #[arg( name = APP_CONFIG_FILE_OPT, short = 'f', long = "file", @@ -38,15 +39,15 @@ pub struct Prepare { pub app: Option, /// Build metadata to append to the bindle version - #[clap( + #[arg( name = BUILDINFO_OPT, long = "buildinfo", - parse(try_from_str = parse_buildinfo), + value_parser = parse_buildinfo, )] pub buildinfo: Option, /// Path to create standalone bindle. - #[clap( + #[arg( name = STAGING_DIR_OPT, long = "staging-dir", short = 'd', @@ -58,7 +59,7 @@ pub struct Prepare { #[derive(Parser, Debug)] pub struct Push { /// Path to spin.toml - #[clap( + #[arg( name = APP_CONFIG_FILE_OPT, short = 'f', long = "file", @@ -66,16 +67,16 @@ pub struct Push { pub app: Option, /// Build metadata to append to the bindle version - #[clap( + #[arg( name = BUILDINFO_OPT, long = "buildinfo", - parse(try_from_str = parse_buildinfo), + value_parser = parse_buildinfo, )] pub buildinfo: Option, /// Path to assemble the bindle before pushing (defaults to /// temporary directory). - #[clap( + #[arg( name = STAGING_DIR_OPT, long = "staging-dir", short = 'd', @@ -83,7 +84,7 @@ pub struct Push { pub staging_dir: Option, /// URL of bindle server - #[clap( + #[arg( name = BINDLE_SERVER_URL_OPT, long = "bindle-server", env = BINDLE_URL_ENV, @@ -91,7 +92,7 @@ pub struct Push { pub bindle_server_url: String, /// Basic http auth username for the bindle server - #[clap( + #[arg( name = BINDLE_USERNAME, long = "bindle-username", env = BINDLE_USERNAME, @@ -100,7 +101,7 @@ pub struct Push { pub bindle_username: Option, /// Basic http auth password for the bindle server - #[clap( + #[arg( name = BINDLE_PASSWORD, long = "bindle-password", env = BINDLE_PASSWORD, @@ -109,11 +110,11 @@ pub struct Push { pub bindle_password: Option, /// Ignore server certificate errors - #[clap( + #[arg( name = INSECURE_OPT, short = 'k', long = "insecure", - takes_value = false, + num_args = 0, )] pub insecure: bool, } diff --git a/src/commands/build.rs b/src/commands/build.rs index 549f03334e..36c995b569 100644 --- a/src/commands/build.rs +++ b/src/commands/build.rs @@ -9,10 +9,10 @@ use super::up::UpCommand; /// Run the build command for each component. #[derive(Parser, Debug)] -#[clap(about = "Build the Spin application", allow_hyphen_values = true)] +#[command(about = "Build the Spin application", allow_hyphen_values = true)] pub struct BuildCommand { /// Path to application manifest. The default is "spin.toml". - #[clap( + #[arg( name = APP_CONFIG_FILE_OPT, short = 'f', long = "from", @@ -21,10 +21,10 @@ pub struct BuildCommand { pub app: Option, /// Run the application after building. - #[clap(name = BUILD_UP_OPT, short = 'u', long = "up")] + #[arg(name = BUILD_UP_OPT, short = 'u', long = "up")] pub up: bool, - #[clap(requires = BUILD_UP_OPT)] + #[arg(requires = BUILD_UP_OPT)] pub up_args: Vec, } diff --git a/src/commands/cloud.rs b/src/commands/cloud.rs index 280fe173c2..f6edf75054 100644 --- a/src/commands/cloud.rs +++ b/src/commands/cloud.rs @@ -6,6 +6,7 @@ use super::login::LoginCommand; /// Commands for publishing applications to the Fermyon Platform. #[derive(Subcommand, Debug)] +#[command(next_display_order = None)] pub enum CloudCommands { /// Package and upload an application to the Fermyon Platform. Deploy(DeployCommand), diff --git a/src/commands/deploy.rs b/src/commands/deploy.rs index 927a2c38bb..388bdcd54f 100644 --- a/src/commands/deploy.rs +++ b/src/commands/deploy.rs @@ -40,10 +40,10 @@ const BINDLE_REGISTRY_URL_PATH: &str = "api/registry"; /// Package and upload an application to the Fermyon Platform. #[derive(Parser, Debug)] -#[clap(about = "Package and upload an application to the Fermyon Platform")] +#[command(about = "Package and upload an application to the Fermyon Platform")] pub struct DeployCommand { /// Path to spin.toml - #[clap( + #[arg( name = APP_CONFIG_FILE_OPT, short = 'f', long = "file", @@ -53,7 +53,7 @@ pub struct DeployCommand { /// Path to assemble the bindle before pushing (defaults to /// a temporary directory) - #[clap( + #[arg( name = STAGING_DIR_OPT, long = "staging-dir", short = 'd', @@ -61,7 +61,7 @@ pub struct DeployCommand { pub staging_dir: Option, /// Disable attaching buildinfo - #[clap( + #[arg( long = "no-buildinfo", conflicts_with = BUILDINFO_OPT, env = "SPIN_DEPLOY_NO_BUILDINFO" @@ -69,26 +69,26 @@ pub struct DeployCommand { pub no_buildinfo: bool, /// Build metadata to append to the bindle version - #[clap( + #[arg( name = BUILDINFO_OPT, long = "buildinfo", - parse(try_from_str = parse_buildinfo), + value_parser = parse_buildinfo, )] pub buildinfo: Option, /// Deploy existing bindle if it already exists on bindle server - #[clap(short = 'e', long = "deploy-existing-bindle")] + #[arg(short = 'e', long = "deploy-existing-bindle")] pub redeploy: bool, /// How long in seconds to wait for a deployed HTTP application to become /// ready. The default is 60 seconds. Set it to 0 to skip waiting /// for readiness. - #[clap(long = "readiness-timeout", default_value = "60")] + #[arg(long = "readiness-timeout", default_value = "60")] pub readiness_timeout_secs: u16, /// Deploy to the Fermyon instance saved under the specified name. /// If omitted, Spin deploys to the default unnamed instance. - #[clap( + #[arg( name = "environment-name", long = "environment-name", env = DEPLOYMENT_ENV_NAME_ENV diff --git a/src/commands/external.rs b/src/commands/external.rs index 5f8c43c118..ab57a5b1f6 100644 --- a/src/commands/external.rs +++ b/src/commands/external.rs @@ -34,7 +34,7 @@ fn parse_subcommand(mut cmd: Vec) -> anyhow::Result<(String, Vec /// subprocess. pub async fn execute_external_subcommand( cmd: Vec, - app: clap::App<'_>, + app: clap::Command, ) -> anyhow::Result<()> { let (plugin_name, args, override_compatibility_check) = parse_subcommand(cmd)?; let plugin_store = PluginStore::try_default()?; @@ -73,7 +73,7 @@ pub async fn execute_external_subcommand( Ok(()) } -fn print_similar_commands(app: clap::App, plugin_name: &str) { +fn print_similar_commands(app: clap::Command, plugin_name: &str) { let similar = similar_commands(app, plugin_name); match similar.len() { 0 => (), @@ -88,7 +88,7 @@ fn print_similar_commands(app: clap::App, plugin_name: &str) { } } -fn similar_commands(app: clap::App, target: &str) -> Vec { +fn similar_commands(app: clap::Command, target: &str) -> Vec { app.get_subcommands() .filter_map(|sc| { if levenshtein::levenshtein(sc.get_name(), target) <= 2 { diff --git a/src/commands/login.rs b/src/commands/login.rs index 0797709afa..57fb39cf2f 100644 --- a/src/commands/login.rs +++ b/src/commands/login.rs @@ -30,10 +30,10 @@ const DEFAULT_CLOUD_URL: &str = "https://cloud.fermyon.com/"; /// Log into the Fermyon Platform. #[derive(Parser, Debug)] -#[clap(about = "Log into the Fermyon Platform")] +#[command(about = "Log into the Fermyon Platform")] pub struct LoginCommand { /// URL of bindle server - #[clap( + #[arg( name = BINDLE_SERVER_URL_OPT, long = "bindle-server", env = BINDLE_URL_ENV, @@ -41,7 +41,7 @@ pub struct LoginCommand { pub bindle_server_url: Option, /// Basic http auth username for the bindle server - #[clap( + #[arg( name = BINDLE_USERNAME, long = "bindle-username", env = BINDLE_USERNAME, @@ -50,7 +50,7 @@ pub struct LoginCommand { pub bindle_username: Option, /// Basic http auth password for the bindle server - #[clap( + #[arg( name = BINDLE_PASSWORD, long = "bindle-password", env = BINDLE_PASSWORD, @@ -59,16 +59,16 @@ pub struct LoginCommand { pub bindle_password: Option, /// Ignore server certificate errors from bindle and hippo - #[clap( + #[arg( name = INSECURE_OPT, short = 'k', long = "insecure", - takes_value = false, + num_args = 0, )] pub insecure: bool, /// URL of hippo server - #[clap( + #[arg( name = HIPPO_SERVER_URL_OPT, long = "url", env = HIPPO_URL_ENV, @@ -78,7 +78,7 @@ pub struct LoginCommand { pub hippo_server_url: url::Url, /// Hippo username - #[clap( + #[arg( name = HIPPO_USERNAME, long = "username", env = HIPPO_USERNAME, @@ -87,7 +87,7 @@ pub struct LoginCommand { pub hippo_username: Option, /// Hippo password - #[clap( + #[arg( name = HIPPO_PASSWORD, long = "password", env = HIPPO_PASSWORD, @@ -96,64 +96,56 @@ pub struct LoginCommand { pub hippo_password: Option, /// Display login status - #[clap( + #[arg( name = "status", long = "status", - takes_value = false, + num_args = 0, conflicts_with = "list", - conflicts_with = "get-device-code", - conflicts_with = "check-device-code" + conflicts_with = "get_device_code", + conflicts_with = "check_device_code" )] pub status: bool, // fetch a device code - #[clap( - name = "get-device-code", + #[arg( long = "get-device-code", - takes_value = false, + num_args = 0, hide = true, conflicts_with = "status", - conflicts_with = "check-device-code" + conflicts_with = "check_device_code" )] pub get_device_code: bool, // check a device code - #[clap( - name = "check-device-code", + #[arg( long = "check-device-code", hide = true, conflicts_with = "status", - conflicts_with = "get-device-code" + conflicts_with = "get_device_code" )] pub check_device_code: Option, // authentication method used for logging in (username|github) - #[clap( - name = "auth-method", - long = "auth-method", - env = "AUTH_METHOD", - arg_enum - )] + #[arg(long = "auth-method", env = "AUTH_METHOD", value_enum)] pub method: Option, /// Save the login details under the specified name instead of making them /// the default. Use named environments with `spin deploy --environment-name `. - #[clap( - name = "environment-name", + #[arg( long = "environment-name", env = DEPLOYMENT_ENV_NAME_ENV )] pub deployment_env_id: Option, /// List saved logins. - #[clap( + #[arg( name = "list", long = "list", - takes_value = false, - conflicts_with = "environment-name", + num_args = 0, + conflicts_with = "deployment_env_id", conflicts_with = "status", - conflicts_with = "get-device-code", - conflicts_with = "check-device-code" + conflicts_with = "get_device_code", + conflicts_with = "check_device_code" )] pub list: bool, } @@ -540,11 +532,11 @@ fn ensure(root: &PathBuf) -> Result<()> { } /// The method by which to authenticate the login. -#[derive(clap::ArgEnum, Clone, Debug, Eq, PartialEq)] +#[derive(clap::ValueEnum, Clone, Debug, Eq, PartialEq)] pub enum AuthMethod { - #[clap(name = "github")] + #[value(name = "github")] Github, - #[clap(name = "username")] + #[value(name = "username")] UsernameAndPassword, } diff --git a/src/commands/new.rs b/src/commands/new.rs index 4d84779e7d..3d0973f5a8 100644 --- a/src/commands/new.rs +++ b/src/commands/new.rs @@ -5,7 +5,7 @@ use std::{ }; use anyhow::{anyhow, Context, Result}; -use clap::Parser; +use clap::{ArgAction, Parser}; use path_absolutize::Absolutize; use tokio; @@ -16,58 +16,59 @@ use crate::opts::{APP_CONFIG_FILE_OPT, DEFAULT_MANIFEST_FILE}; /// Scaffold a new application based on a template. #[derive(Parser, Debug)] +#[command(next_display_order = None)] pub struct TemplateNewCommandCore { /// The template from which to create the new application or component. Run `spin templates list` to see available options. pub template_id: Option, /// The name of the new application or component. - #[clap(value_parser = validate_name)] + #[arg(value_parser = validate_name)] pub name: Option, /// Filter templates to select by tags. - #[clap( + #[arg( long = "tag", - multiple_occurrences = true, - conflicts_with = "template-id" + action = ArgAction::Append, + conflicts_with = "template_id" )] pub tags: Vec, /// The directory in which to create the new application or component. /// The default is the name argument. - #[clap(short = 'o', long = "output")] + #[arg(short = 'o', long = "output")] pub output_path: Option, /// Parameter values to be passed to the template (in name=value format). - #[clap(short = 'v', long = "value", multiple_occurrences = true)] + #[arg(short = 'v', long = "value", action = ArgAction::Append)] pub values: Vec, /// A TOML file which contains parameter values in name = "value" format. /// Parameters passed as CLI option overwrite parameters specified in the /// file. - #[clap(long = "values-file")] + #[arg(long = "values-file")] pub values_file: Option, /// An optional argument that allows to skip prompts for the manifest file /// by accepting the defaults if available on the template - #[clap(long = "accept-defaults", takes_value = false)] + #[arg(long = "accept-defaults", num_args = 0)] pub accept_defaults: bool, } /// Scaffold a new application based on a template. #[derive(Parser, Debug)] pub struct NewCommand { - #[clap(flatten)] + #[command(flatten)] options: TemplateNewCommandCore, } /// Scaffold a new component into an existing application. #[derive(Parser, Debug)] pub struct AddCommand { - #[clap(flatten)] + #[command(flatten)] options: TemplateNewCommandCore, /// Path to spin.toml. - #[clap( + #[arg( name = APP_CONFIG_FILE_OPT, short = 'f', long = "file", @@ -165,7 +166,7 @@ impl TemplateNewCommandCore { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ParameterValue { pub name: String, pub value: String, diff --git a/src/commands/plugins.rs b/src/commands/plugins.rs index f75c6b6d07..d2fc9bb604 100644 --- a/src/commands/plugins.rs +++ b/src/commands/plugins.rs @@ -15,6 +15,7 @@ use crate::opts::*; /// Install/uninstall Spin plugins. #[derive(Subcommand, Debug)] +#[command(next_display_order = None)] pub enum PluginCommands { /// Install plugin from a manifest. /// @@ -51,7 +52,7 @@ impl PluginCommands { #[derive(Parser, Debug)] pub struct Install { /// Name of Spin plugin. - #[clap( + #[arg( name = PLUGIN_NAME_OPT, conflicts_with = PLUGIN_REMOTE_PLUGIN_MANIFEST_OPT, conflicts_with = PLUGIN_LOCAL_PLUGIN_MANIFEST_OPT, @@ -60,7 +61,7 @@ pub struct Install { pub name: Option, /// Path to local plugin manifest. - #[clap( + #[arg( name = PLUGIN_LOCAL_PLUGIN_MANIFEST_OPT, short = 'f', long = "file", @@ -70,7 +71,7 @@ pub struct Install { pub local_manifest_src: Option, /// URL of remote plugin manifest to install. - #[clap( + #[arg( name = PLUGIN_REMOTE_PLUGIN_MANIFEST_OPT, short = 'u', long = "url", @@ -80,21 +81,21 @@ pub struct Install { pub remote_manifest_src: Option, /// Skips prompt to accept the installation of the plugin. - #[clap(short = 'y', long = "yes", takes_value = false)] + #[arg(short = 'y', long = "yes", num_args = 0)] pub yes_to_all: bool, /// Overrides a failed compatibility check of the plugin with the current version of Spin. - #[clap(long = PLUGIN_OVERRIDE_COMPATIBILITY_CHECK_FLAG, takes_value = false)] + #[arg(long = PLUGIN_OVERRIDE_COMPATIBILITY_CHECK_FLAG, num_args = 0)] pub override_compatibility_check: bool, /// Specific version of a plugin to be install from the centralized plugins /// repository. - #[clap( + #[arg( long = "version", short = 'v', conflicts_with = PLUGIN_REMOTE_PLUGIN_MANIFEST_OPT, conflicts_with = PLUGIN_LOCAL_PLUGIN_MANIFEST_OPT, - requires(PLUGIN_NAME_OPT) + requires = PLUGIN_NAME_OPT )] pub version: Option, } @@ -149,7 +150,7 @@ impl Uninstall { #[derive(Parser, Debug)] pub struct Upgrade { /// Name of Spin plugin to upgrade. - #[clap( + #[arg( name = PLUGIN_NAME_OPT, conflicts_with = PLUGIN_ALL_OPT, required_unless_present_any = [PLUGIN_ALL_OPT], @@ -157,19 +158,19 @@ pub struct Upgrade { pub name: Option, /// Upgrade all plugins. - #[clap( + #[arg( short = 'a', long = "all", name = PLUGIN_ALL_OPT, conflicts_with = PLUGIN_NAME_OPT, conflicts_with = PLUGIN_REMOTE_PLUGIN_MANIFEST_OPT, conflicts_with = PLUGIN_LOCAL_PLUGIN_MANIFEST_OPT, - takes_value = false, + num_args = 0, )] pub all: bool, /// Path to local plugin manifest. - #[clap( + #[arg( name = PLUGIN_LOCAL_PLUGIN_MANIFEST_OPT, short = 'f', long = "file", @@ -178,7 +179,7 @@ pub struct Upgrade { pub local_manifest_src: Option, /// Path to remote plugin manifest. - #[clap( + #[arg( name = PLUGIN_REMOTE_PLUGIN_MANIFEST_OPT, short = 'u', long = "url", @@ -187,16 +188,16 @@ pub struct Upgrade { pub remote_manifest_src: Option, /// Skips prompt to accept the installation of the plugin[s]. - #[clap(short = 'y', long = "yes", takes_value = false)] + #[arg(short = 'y', long = "yes", num_args = 0)] pub yes_to_all: bool, /// Overrides a failed compatibility check of the plugin with the current version of Spin. - #[clap(long = PLUGIN_OVERRIDE_COMPATIBILITY_CHECK_FLAG, takes_value = false)] + #[arg(long = PLUGIN_OVERRIDE_COMPATIBILITY_CHECK_FLAG, num_args = 0)] pub override_compatibility_check: bool, /// Specific version of a plugin to be install from the centralized plugins /// repository. - #[clap( + #[arg( long = "version", short = 'v', conflicts_with = PLUGIN_REMOTE_PLUGIN_MANIFEST_OPT, @@ -207,7 +208,7 @@ pub struct Upgrade { pub version: Option, /// Allow downgrading a plugin's version. - #[clap(short = 'd', long = "downgrade", takes_value = false)] + #[arg(short = 'd', long = "downgrade", num_args = 0)] pub downgrade: bool, } @@ -293,7 +294,7 @@ impl Upgrade { #[derive(Parser, Debug)] pub struct List { /// List only installed plugins. - #[clap(long = "installed", takes_value = false)] + #[arg(long = "installed", num_args = 0)] pub installed: bool, } diff --git a/src/commands/registry.rs b/src/commands/registry.rs index ddf99afcd3..0d079dde03 100644 --- a/src/commands/registry.rs +++ b/src/commands/registry.rs @@ -10,6 +10,7 @@ use crate::opts::*; /// Currently, the OCI commands are reusing the credentials from ~/.docker/config.json to /// authenticate to registries. #[derive(Subcommand, Debug)] +#[command(next_display_order = None)] pub enum RegistryCommands { /// Push a Spin application to a registry. Push(Push), @@ -32,7 +33,7 @@ impl RegistryCommands { #[derive(Parser, Debug)] pub struct Push { /// Path to spin.toml - #[clap( + #[arg( name = APP_CONFIG_FILE_OPT, short = 'f', long = "file", @@ -40,16 +41,16 @@ pub struct Push { pub app: Option, /// Ignore server certificate errors - #[clap( + #[arg( name = INSECURE_OPT, short = 'k', long = "insecure", - takes_value = false, + num_args = 0, )] pub insecure: bool, /// Reference of the Spin application - #[clap()] + #[arg()] pub reference: String, } @@ -72,16 +73,16 @@ impl Push { #[derive(Parser, Debug)] pub struct Pull { /// Ignore server certificate errors - #[clap( + #[arg( name = INSECURE_OPT, short = 'k', long = "insecure", - takes_value = false, + num_args = 0, )] pub insecure: bool, /// Reference of the Spin application - #[clap()] + #[arg()] pub reference: String, } @@ -98,22 +99,18 @@ impl Pull { #[derive(Parser, Debug)] pub struct Login { /// Username for the registry - #[clap(long = "username", short = 'u')] + #[arg(long = "username", short = 'u')] pub username: Option, /// Password for the registry - #[clap(long = "password", short = 'p')] + #[arg(long = "password", short = 'p')] pub password: Option, /// Take the password from stdin - #[clap( - long = "password-stdin", - takes_value = false, - conflicts_with = "password" - )] + #[arg(long = "password-stdin", num_args = 0, conflicts_with = "password")] pub password_stdin: bool, - #[clap()] + #[arg()] pub server: String, } diff --git a/src/commands/templates.rs b/src/commands/templates.rs index f25b90b31e..7155844385 100644 --- a/src/commands/templates.rs +++ b/src/commands/templates.rs @@ -1,7 +1,7 @@ use std::{collections::HashSet, path::PathBuf}; use anyhow::{Context, Result}; -use clap::{Parser, Subcommand, ValueEnum}; +use clap::{ArgAction, Parser, Subcommand, ValueEnum}; use comfy_table::Table; use path_absolutize::Absolutize; @@ -21,6 +21,7 @@ const DEFAULT_TEMPLATE_REPO: &str = "https://github.com/fermyon/spin"; /// Commands for working with WebAssembly component templates. #[derive(Subcommand, Debug)] +#[command(next_display_order = None)] pub enum TemplateCommands { /// Install templates from a Git repository or local directory. /// @@ -57,7 +58,7 @@ impl TemplateCommands { pub struct Install { /// The URL of the templates git repository. /// The templates must be in a git repository in a "templates" directory. - #[clap( + #[arg( name = INSTALL_FROM_GIT_OPT, long = "git", conflicts_with = INSTALL_FROM_DIR_OPT, @@ -65,11 +66,11 @@ pub struct Install { pub git: Option, /// The optional branch of the git repository. - #[clap(long = "branch", requires = INSTALL_FROM_GIT_OPT)] + #[arg(long = "branch", requires = INSTALL_FROM_GIT_OPT)] pub branch: Option, /// Local directory containing the template(s) to install. - #[clap( + #[arg( name = INSTALL_FROM_DIR_OPT, long = "dir", conflicts_with = INSTALL_FROM_GIT_OPT, @@ -77,7 +78,7 @@ pub struct Install { pub dir: Option, /// If present, updates existing templates instead of skipping. - #[clap(long = "upgrade", alias = "update")] + #[arg(long = "upgrade", alias = "update")] pub update: bool, } @@ -87,7 +88,7 @@ pub struct Upgrade { /// By default, Spin displays the list of installed repositories and /// prompts you to choose which to upgrade. Pass this flag to /// upgrade only the specified repository without prompting. - #[clap( + #[arg( name = UPGRADE_ONLY, long = "repo", )] @@ -95,13 +96,13 @@ pub struct Upgrade { /// The optional branch of the git repository, if a specific /// repository is given. - #[clap(long = "branch", requires = UPGRADE_ONLY)] + #[arg(long = "branch", requires = UPGRADE_ONLY)] pub branch: Option, /// By default, Spin displays the list of installed repositories and /// prompts you to choose which to upgrade. Pass this flag to /// upgrade all repositories without prompting. - #[clap(long = "all", conflicts_with = UPGRADE_ONLY)] + #[arg(long = "all", conflicts_with = UPGRADE_ONLY)] pub all: bool, } @@ -442,15 +443,15 @@ impl Uninstall { #[derive(Parser, Debug)] pub struct List { /// Filter templates matching all provided tags. - #[clap(long = "tag", multiple_occurrences = true)] + #[arg(long = "tag", action = ArgAction::Append)] pub tags: Vec, /// The format in which to list the templates. - #[clap(value_enum, long = "format", default_value = "table", hide = true)] + #[arg(value_enum, long = "format", default_value = "table", hide = true)] pub format: ListFormat, /// Whether to show additional template details in the list. - #[clap(long = "verbose", takes_value = false)] + #[arg(long = "verbose", num_args = 0)] pub verbose: bool, } diff --git a/src/commands/up.rs b/src/commands/up.rs index 31b240916a..83700e815b 100644 --- a/src/commands/up.rs +++ b/src/commands/up.rs @@ -19,19 +19,21 @@ const APPLICATION_OPT: &str = "APPLICATION"; /// Start the Fermyon runtime. #[derive(Parser, Debug, Default)] -#[clap( +#[command( about = "Start the Spin application", allow_hyphen_values = true, - disable_help_flag = true + disable_help_flag = true, + external_subcommand = true, + ignore_errors = true )] pub struct UpCommand { - #[clap(short = 'h', long = "help")] + #[arg(short = 'h', long = "help")] pub help: bool, /// The application to run. This may be a manifest (spin.toml) file, a /// directory containing a spin.toml file, or a remote registry reference. /// If omitted, it defaults to "spin.toml". - #[clap( + #[arg( name = APPLICATION_OPT, short = 'f', long = "from", @@ -40,7 +42,7 @@ pub struct UpCommand { /// The application to run. This is the same as `--from` but forces the /// application to be interpreted as a file or directory path. - #[clap( + #[arg( hide = true, name = APP_CONFIG_FILE_OPT, long = "from-file", @@ -53,7 +55,7 @@ pub struct UpCommand { /// The application to run. This interprets the applicaiton as a bindle ID. /// This option is deprecated; use OCI registries and `--from` where possible. - #[clap( + #[arg( hide = true, name = BINDLE_ID_OPT, short = 'b', @@ -67,7 +69,7 @@ pub struct UpCommand { pub bindle_source: Option, /// URL of bindle server. - #[clap( + #[arg( hide = true, name = BINDLE_SERVER_URL_OPT, long = "bindle-server", @@ -76,7 +78,7 @@ pub struct UpCommand { pub server: Option, /// Basic http auth username for the bindle server - #[clap( + #[arg( hide = true, name = BINDLE_USERNAME, long = "bindle-username", @@ -86,7 +88,7 @@ pub struct UpCommand { pub bindle_username: Option, /// Basic http auth password for the bindle server - #[clap( + #[arg( hide = true, name = BINDLE_PASSWORD, long = "bindle-password", @@ -97,7 +99,7 @@ pub struct UpCommand { /// The application to run. This is the same as `--from` but forces the /// application to be interpreted as an OCI registry reference. - #[clap( + #[arg( hide = true, name = FROM_REGISTRY_OPT, long = "from-registry", @@ -108,20 +110,20 @@ pub struct UpCommand { pub registry_source: Option, /// Ignore server certificate errors from bindle server or registry - #[clap( + #[arg( name = INSECURE_OPT, short = 'k', long = "insecure", - takes_value = false, + num_args = 0, )] pub insecure: bool, /// Pass an environment variable (key=value) to all components of the application. - #[clap(short = 'e', long = "env", parse(try_from_str = parse_env_var))] + #[arg(short = 'e', long = "env", value_parser = parse_env_var)] pub env: Vec<(String, String)>, /// Temporary directory for the static assets of the components. - #[clap(long = "temp")] + #[arg(long = "temp")] pub tmp: Option, /// For local apps with directory mounts and no excluded files, mount them directly instead of using a temporary @@ -129,12 +131,12 @@ pub struct UpCommand { /// /// This allows you to update the assets on the host filesystem such that the updates are visible to the guest /// without a restart. This cannot be used with bindle apps or apps which use file patterns and/or exclusions. - #[clap(long, takes_value = false, conflicts_with = BINDLE_ID_OPT)] + #[arg(long, num_args = 0, conflicts_with = BINDLE_ID_OPT)] pub direct_mounts: bool, /// All other args, to be passed through to the trigger - #[clap(hide = true)] - pub trigger_args: Vec, + #[arg(hide = true, allow_hyphen_values = true)] + pub trigger_args: Option>, } impl UpCommand { @@ -159,6 +161,10 @@ impl UpCommand { }) } + fn trigger_args(&self) -> Vec { + self.trigger_args.clone().unwrap_or_default() + } + fn resolve_app_source(&self) -> AppSource { match ( &self.app_source, @@ -182,7 +188,7 @@ impl UpCommand { } else if self.trigger_args_look_file_like() { let msg = format!( "Default file 'spin.toml' found. Did you mean `spin up -f {}`?`", - self.trigger_args[0].to_string_lossy() + self.trigger_args()[0].to_string_lossy() ); AppSource::Unresolvable(msg) } else { @@ -194,7 +200,8 @@ impl UpCommand { // Heuristic for the user typing `spin up foo` instead of `spin up -f foo` - in the // first case `foo` gets interpreted as a trigger arg which is probably not what the // user intended. - !self.trigger_args.is_empty() && !self.trigger_args[0].to_string_lossy().starts_with('-') + !self.trigger_args().is_empty() + && !self.trigger_args()[0].to_string_lossy().starts_with('-') } fn infer_file_source(path: PathBuf) -> AppSource { @@ -299,7 +306,7 @@ impl UpCommand { let locked_url = self.write_locked_app(&locked_app, &working_dir).await?; cmd.env(SPIN_LOCKED_URL, locked_url) .env(SPIN_WORKING_DIR, &working_dir) - .args(&self.trigger_args); + .args(&self.trigger_args()); } TriggerExecOpts::Remote { locked_url, @@ -308,7 +315,7 @@ impl UpCommand { } => { cmd.env(SPIN_LOCKED_URL, locked_url) .env(SPIN_WORKING_DIR, &working_dir) - .args(&self.trigger_args); + .args(&self.trigger_args()); if from_registry { cmd.arg("--from-registry"); }