CLI

When the cli feature is enabled, Roadster provides some CLI commands that are built into the app. Custom CLI commands can also be added using clap.

CLI commands run after the app is prepared (e.g., health checks, lifecycle handlers, and services registered, etc), and before the health checks, lifecycle handlers, and services are run. This means that the app needs to have a valid configuration in order to run a CLI command, but otherwise the app's resources (e.g. DB, Redis) don't need to be healthy (unless, of course, the specific CLI command requires the resource).

Commands provided by Roadster

Roadster defines some top-level CLI options that can apply to all commands, but otherwise everything provided by Roadster is scoped under the roadster sub-command and its alias r.

Adding custom CLI commands

Custom CLI commands and options can be defined using clap. Custom commands can be provided either at the top level, or scoped a sub-command, similar to how Roadster's commands are scoped.

Once the custom commands are defined using clap, the RunCommand trait needs to be implemented for the top-level command struct. This is what allows Roadster to invoke your custom CLI commands.

/// CLI example: Commands specific to managing the `cli-example` app are provided in the CLI
/// as well. Subcommands not listed under the `roadster` subcommand are specific to `cli-example`.
#[derive(Debug, Parser)]
#[command(version, about)]
#[non_exhaustive]
pub struct AppCli {
    #[command(subcommand)]
    pub command: Option<AppCommand>,
}

#[async_trait]
impl RunCommand<App, AppContext> for AppCli {
    #[allow(clippy::disallowed_types)]
    async fn run(&self, prepared_app: &CliState<App, AppContext>) -> RoadsterResult<bool> {
        if let Some(command) = self.command.as_ref() {
            command.run(prepared_app).await
        } else {
            Ok(false)
        }
    }
}

/// App specific subcommands
///
/// Note: This doc comment doesn't appear in the CLI `--help` message.
#[derive(Debug, Subcommand)]
pub enum AppCommand {
    /// Print a "hello world" message.
    HelloWorld,
}

#[async_trait]
impl RunCommand<App, AppContext> for AppCommand {
    async fn run(&self, _prepared_app: &CliState<App, AppContext>) -> RoadsterResult<bool> {
        match self {
            AppCommand::HelloWorld => {
                info!("Hello, world!");
            }
        }
        Ok(true)
    }
}

Sample CLI help text for the above example

$> ROADSTER__ENVIRONMENT=dev cargo run -- -h
A "Batteries Included" web framework for rust designed to get you moving fast.

CLI example: Commands specific to managing the `cli-example` app are provided in the CLI as well. Subcommands not listed under the `roadster` subcommand are specific to `cli-example`

Usage: cli-example [OPTIONS] [COMMAND]

Commands:
  roadster     Roadster subcommands. Subcommands provided by Roadster are listed under this subcommand in order to avoid naming conflicts with the consumer's subcommands [aliases: r]
  hello-world  Print a "hello world" message  
  help         Print this message or the help of the given subcommand(s)

Options:
  -e, --environment <ENVIRONMENT>      Specify the environment to use to run the application. This overrides the corresponding environment variable if it's set [possible values: development, test, production, <custom>]
      --skip-validate-config           Skip validation of the app config. This can be useful for debugging the app config when used in conjunction with the `print-config` command
      --allow-dangerous                Allow dangerous/destructive operations when running in the `production` environment. If this argument is not provided, dangerous/destructive operations will not be performed when running in `production`
      --config-dir <CONFIG_DIRECTORY>  The location of the config directory (where the app's config files are located). If not provided, will default to `./config/`
  -h, --help                           Print help
  -V, --version                        Print version