1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
use std::{
    ffi::{OsStr, OsString},
    fs::{canonicalize, create_dir_all},
    path::PathBuf,
};

use shuttle_common::project::ProjectName;
use structopt::StructOpt;

#[derive(StructOpt)]
#[structopt(
    // Cargo passes in the subcommand name to the invoked executable. Use a
    // hidden, optional positional argument to deal with it.
    arg(structopt::clap::Arg::with_name("dummy")
        .possible_value("shuttle")
        .required(false)
        .hidden(true))
)]
pub struct Args {
    #[structopt(
        long,
        about = "allows targeting a custom deloyed instance for this command only",
        env = "SHUTTLE_API"
    )]
    /// Run this command against the api at the supplied url
    pub api_url: Option<String>,
    #[structopt(flatten)]
    pub project_args: ProjectArgs,
    #[structopt(subcommand)]
    pub cmd: Command,
}

// Common args for subcommands that deal with projects.
#[derive(StructOpt, Debug)]
pub struct ProjectArgs {
    #[structopt(
        global = true,
        long,
        parse(try_from_os_str = parse_path),
        default_value = ".",
    )]
    /// Specify the working directory
    pub working_directory: PathBuf,
    #[structopt(global = true, long)]
    /// Specify the name of the project (overrides crate name)
    pub name: Option<ProjectName>,
}

#[derive(StructOpt)]
pub enum Command {
    #[structopt(about = "deploy a shuttle project")]
    Deploy(DeployArgs),
    #[structopt(about = "create a new shuttle project")]
    Init(InitArgs),
    #[structopt(about = "view the status of a shuttle project")]
    Status,
    #[structopt(about = "view the logs of a shuttle project")]
    Logs,
    #[structopt(about = "delete the latest deployment for a shuttle project")]
    Delete,
    #[structopt(about = "create user credentials for the shuttle platform")]
    Auth(AuthArgs),
    #[structopt(about = "login to the shuttle platform")]
    Login(LoginArgs),
    #[structopt(about = "run a shuttle project locally")]
    Run(RunArgs),
}

#[derive(StructOpt)]
pub struct LoginArgs {
    #[structopt(long, about = "api key for the shuttle platform")]
    pub api_key: Option<String>,
}

#[derive(StructOpt)]
pub struct AuthArgs {
    #[structopt(about = "the desired username for the shuttle platform")]
    pub username: String,
}

#[derive(StructOpt)]
pub struct DeployArgs {
    #[structopt(long, about = "allow dirty working directories to be packaged")]
    pub allow_dirty: bool,
    #[structopt(long, about = "allows pre-deploy tests to be skipped")]
    pub no_test: bool,
}

#[derive(StructOpt, Debug)]
pub struct RunArgs {
    #[structopt(long, about = "port to start service on", default_value = "8000")]
    pub port: u16,
}

#[derive(StructOpt)]
pub struct InitArgs {
    #[structopt(
        about = "the path to initialize a new shuttle project",
        parse(try_from_os_str = parse_init_path),
        default_value = ".",
    )]
    pub path: PathBuf,
}

// Helper function to parse and return the absolute path
fn parse_path(path: &OsStr) -> Result<PathBuf, OsString> {
    canonicalize(path).map_err(|e| format!("could not turn {path:?} into a real path: {e}").into())
}

// Helper function to parse, create if not exists, and return the absolute path
fn parse_init_path(path: &OsStr) -> Result<PathBuf, OsString> {
    // Create the directory if does not exist
    create_dir_all(path).expect("could not find or create a directory with the given path");

    parse_path(path)
}