14 files changed
+200
-131
Cargo.lock
+60
@@ -1575,6 +1575,15 @@ dependencies = [
1575
"syn 2.0.101",
1576
]
1577
1578
+[[package]]
1579
+name = "serde_spanned"
1580
+version = "0.6.8"
1581
+source = "registry+https://github.com/rust-lang/crates.io-index"
1582
+checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
1583
+dependencies = [
1584
+ "serde",
1585
+]
1586
+
1587
[[package]]
1588
name = "serde_urlencoded"
1589
version = "0.7.1"
@@ -1659,6 +1668,7 @@ dependencies = [
1668
"spinners",
1669
"ssh2-config",
1670
"tokio",
1671
+ "toml",
1672
"tracing",
1673
"tracing-bunyan-formatter",
1674
"tracing-subscriber",
@@ -2056,6 +2066,47 @@ dependencies = [
2066
"tokio",
2067
]
2068
2069
+[[package]]
2070
+name = "toml"
2071
+version = "0.8.22"
2072
+source = "registry+https://github.com/rust-lang/crates.io-index"
2073
+checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
2074
+dependencies = [
2075
+ "serde",
2076
+ "serde_spanned",
2077
+ "toml_datetime",
2078
+ "toml_edit",
2079
+]
2080
+
2081
+[[package]]
2082
+name = "toml_datetime"
2083
+version = "0.6.9"
2084
+source = "registry+https://github.com/rust-lang/crates.io-index"
2085
+checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
2086
+dependencies = [
2087
+ "serde",
2088
+]
2089
+
2090
+[[package]]
2091
+name = "toml_edit"
2092
+version = "0.22.26"
2093
+source = "registry+https://github.com/rust-lang/crates.io-index"
2094
+checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
2095
+dependencies = [
2096
+ "indexmap",
2097
+ "serde",
2098
+ "serde_spanned",
2099
+ "toml_datetime",
2100
+ "toml_write",
2101
+ "winnow",
2102
+]
2103
+
2104
+[[package]]
2105
+name = "toml_write"
2106
+version = "0.1.1"
2107
+source = "registry+https://github.com/rust-lang/crates.io-index"
2108
+checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076"
2109
+
2110
[[package]]
2111
name = "tower"
2112
version = "0.5.2"
@@ -2676,6 +2727,15 @@ version = "0.53.0"
2727
source = "registry+https://github.com/rust-lang/crates.io-index"
2728
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
2729
2730
+[[package]]
2731
+name = "winnow"
2732
+version = "0.7.9"
2733
+source = "registry+https://github.com/rust-lang/crates.io-index"
2734
+checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3"
2735
+dependencies = [
2736
+ "memchr",
2737
+]
2738
+
2739
[[package]]
2740
name = "wit-bindgen-rt"
2741
version = "0.39.0"
smbcloud-cli/Cargo.toml
+1
@@ -38,6 +38,7 @@ smbcloud-networking-project = { workspace = true }
38
smbcloud-utils = { workspace = true }
39
spinners = { workspace = true }
40
tokio = { workspace = true, features = ["full"] }
41
+toml = { workspace = true }
42
tracing = { workspace = true, features = ["log"] }
43
tracing-bunyan-formatter = { workspace = true }
44
tracing-subscriber = { workspace = true, features = ["registry", "env-filter"] }
smbcloud-cli/src/account/lib.rs
+11
-1
@@ -9,7 +9,7 @@ use smbcloud_networking::{
9
GH_OAUTH_CLIENT_ID, GH_OAUTH_REDIRECT_HOST, GH_OAUTH_REDIRECT_PORT, PATH_AUTHORIZE,
10
},
11
environment::Environment,
12
- smb_base_url_builder,
12
+ smb_base_url_builder, smb_token_file_path,
13
};
14
use spinners::Spinner;
15
use std::{
@@ -239,3 +239,13 @@ pub async fn save_token(env: Environment, response: &Response) -> Result<()> {
239
None => Err(anyhow!("Failed to get token. Probably a backend issue.")),
240
}
241
}
242
+
243
+pub async fn protected_request(env: Environment) -> Result<()> {
244
+ // Check if token file exists
245
+ if smb_token_file_path(env).is_none() {
246
+ return Err(anyhow!(
247
+ "Please authorize your account first with `smb account login` command."
248
+ ));
249
+ }
250
+ Ok(())
251
+}
\ No newline at end of file
smbcloud-cli/src/account/login/process.rs
+12
-16
@@ -3,7 +3,7 @@ use crate::{
3
lib::{authorize_github, save_token},
4
signup::{do_signup, SignupMethod},
5
},
6
- cli::CommandResult,
6
+ cli::CommandResult, ui::{fail_symbol, succeed_message, succeed_symbol},
7
};
8
use anyhow::{anyhow, Result};
9
use console::{style, Term};
@@ -36,7 +36,7 @@ pub async fn process_login(env: Environment) -> Result<CommandResult> {
36
spinners::Spinners::SimpleDotsScrolling,
37
style("Loading...").green().bold().to_string(),
38
),
39
- symbol: "✅".to_owned(),
39
+ symbol: succeed_symbol(),
40
msg: "You are already logged in. Please logout first.".to_owned(),
41
});
42
}
@@ -80,12 +80,12 @@ pub async fn process_logout(env: Environment) -> Result<CommandResult> {
80
spinners::Spinners::SimpleDotsScrolling,
81
style("Cancel operation.").green().bold().to_string(),
82
),
83
- symbol: "✅".to_owned(),
83
+ symbol: succeed_symbol(),
84
msg: "Doing nothing.".to_owned(),
85
});
86
}
87
88
- let mut spinner = Spinner::new(
88
+ let spinner = Spinner::new(
89
spinners::Spinners::SimpleDotsScrolling,
90
style("Logging you out...").green().bold().to_string(),
91
);
@@ -93,15 +93,11 @@ pub async fn process_logout(env: Environment) -> Result<CommandResult> {
93
// Call backend
94
match do_process_logout(env).await {
95
Ok(_) => {
96
- spinner.stop_and_persist("✅", "Done.".to_owned());
96
fs::remove_file(token_path)?;
97
Ok(CommandResult {
99
- spinner: Spinner::new(
100
- spinners::Spinners::SimpleDotsScrolling,
101
- style("Loading...").green().bold().to_string(),
102
- ),
103
- symbol: "✅".to_owned(),
104
- msg: "You are now logged out!".to_owned(),
98
+ spinner,
99
+ symbol: succeed_symbol(),
100
+ msg: succeed_message("You are logged out!"),
101
})
102
}
103
Err(e) => Err(anyhow!("{e}")),
@@ -112,7 +108,7 @@ pub async fn process_logout(env: Environment) -> Result<CommandResult> {
108
spinners::Spinners::SimpleDotsScrolling,
109
style("Loading...").green().bold().to_string(),
110
),
115
- symbol: "😏".to_owned(),
111
+ symbol: fail_symbol(),
112
msg: "You are not logged in.".to_owned(),
113
})
114
}
@@ -380,8 +376,8 @@ async fn do_process_login(env: Environment, args: LoginArgs) -> Result<CommandRe
376
spinners::Spinners::SimpleDotsScrolling,
377
style("Loading...").green().bold().to_string(),
378
),
383
- symbol: "✅".to_owned(),
384
- msg: "You are now logged in!".to_owned(),
379
+ symbol: succeed_symbol(),
380
+ msg: succeed_message("You are logged in!"),
381
})
382
}
383
StatusCode::NOT_FOUND => {
@@ -391,7 +387,7 @@ async fn do_process_login(env: Environment, args: LoginArgs) -> Result<CommandRe
387
spinners::Spinners::SimpleDotsScrolling,
388
style("Account not found.").green().bold().to_string(),
389
),
394
- symbol: "✅".to_owned(),
390
+ symbol: style("✔").green().to_string(),
391
msg: "Please signup!".to_owned(),
392
})
393
}
@@ -444,7 +440,7 @@ async fn send_reset_password(env: Environment, user: Option<User>) -> Result<Com
440
);
441
return Ok(CommandResult {
442
spinner,
447
- symbol: "✅".to_owned(),
443
+ symbol: style("✔").green().to_string(),
444
msg: "Doing nothing.".to_owned(),
445
});
446
}
smbcloud-cli/src/cli/mod.rs
+12
-8
@@ -9,6 +9,12 @@ pub struct CommandResult {
9
pub msg: String,
10
}
11
12
+impl CommandResult {
13
+ pub fn stop_and_persist(mut self) {
14
+ self.spinner.stop_and_persist(&self.symbol, self.msg);
15
+ }
16
+}
17
+
18
#[derive(Parser)]
19
#[clap(author, version, about)]
20
pub struct Cli {
@@ -32,20 +38,18 @@ pub enum Commands {
38
command: account::cli::Commands,
39
},
40
41
+ #[clap(about = "Login to your account.")]
42
+ Login {},
43
+
44
#[clap(about = "Manage your projects. Add, delete, edit. Need authentication.")]
45
Project {
46
#[clap(subcommand)]
47
command: project::cli::Commands,
48
},
49
+
50
#[clap(about = "Initialize project. Requires an smbCloud account.")]
41
- Init {
42
- /// Project name
43
- #[clap(short, long, required = false)]
44
- name: Option<String>,
45
- /// Project description
46
- #[clap(short, long, required = false)]
47
- description: Option<String>,
48
- },
51
+ Init { },
52
+
53
#[clap(about = "Deploy project. It will use deploy.sh script in the .smb folder.")]
54
Deploy {},
55
}
smbcloud-cli/src/deploy/git.rs
+2
-4
@@ -3,7 +3,7 @@ use console::style;
3
use git2::{Remote, Repository};
4
use spinners::Spinner;
5
6
-pub async fn remote_deployment_setup(repo: &Repository) -> Result<Remote> {
6
+pub async fn remote_deployment_setup(repo: &Repository, repo_name: String) -> Result<Remote> {
7
let mut spinner = Spinner::new(
8
spinners::Spinners::SimpleDotsScrolling,
9
style("Setting up remote deployment...")
@@ -17,7 +17,7 @@ pub async fn remote_deployment_setup(repo: &Repository) -> Result<Remote> {
17
Err(_) => {
18
spinner.stop_and_persist("😩", "Remote deployment is not setup. Will setup remote deployment.".to_owned());
19
// Present the user with a message to setup remote deployment
20
- repo.remote("smbcloud", "deploy@api.smbcloud.xyz:git/foodandtravel")
20
+ repo.remote("smbcloud", &format!("deploy@api.smbcloud.xyz:git/{}", repo_name))
21
.map_err(|e: git2::Error| {
22
spinner.stop_and_persist("😩", e.to_string());
23
anyhow!("Failed to setup remote deployment: {e}")
@@ -27,8 +27,6 @@ pub async fn remote_deployment_setup(repo: &Repository) -> Result<Remote> {
27
));
28
}
29
};
30
- // Simulate some work
31
- tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
30
31
spinner.stop_and_persist("🚀", "Remote deployment setup complete.".to_owned());
32
smbcloud-cli/src/deploy/mod.rs
+7
-4
@@ -2,7 +2,7 @@ mod git;
2
mod smb_config;
3
4
use std::{fs::File, io::BufReader};
5
-use crate::cli::CommandResult;
5
+use crate::{account::lib::protected_request, cli::CommandResult};
6
use anyhow::Result;
7
use console::style;
8
use git::remote_deployment_setup;
@@ -14,7 +14,8 @@ use spinners::Spinner;
14
use ssh2_config::{ParseRule, SshConfig};
15
16
pub async fn process_deploy(env: Environment) -> Result<CommandResult> {
17
- check_config(env).await?;
17
+ protected_request(env).await?;
18
+ let repo_name = check_config().await?;
19
println!("Deploying your app...");
20
let mut spinner = Spinner::new(
21
spinners::Spinners::SimpleDotsScrolling,
@@ -45,7 +46,7 @@ pub async fn process_deploy(env: Environment) -> Result<CommandResult> {
46
});
47
}
48
};
48
- let mut origin = remote_deployment_setup(&repo).await?;
49
+ let origin = remote_deployment_setup(&repo, repo_name).await?;
50
let remote_url = match origin.url() {
51
Some(url) => url,
52
None => {
@@ -173,6 +174,7 @@ pub async fn process_deploy(env: Environment) -> Result<CommandResult> {
174
});
175
push_opts.remote_callbacks(callbacks);
176
177
+ /*
178
match origin.push(&["refs/heads/main:refs/heads/main"], Some(&mut push_opts)) {
179
Ok(_) => {}
180
Err(e) => {
@@ -185,7 +187,8 @@ pub async fn process_deploy(env: Environment) -> Result<CommandResult> {
187
});
188
}
189
};
188
-
190
+ */
191
+
192
Ok(CommandResult {
193
spinner,
194
symbol: "🚀".to_owned(),
smbcloud-cli/src/deploy/smb_config.rs
+24
-21
@@ -1,30 +1,33 @@
1
+use anyhow::Result;
2
use console::style;
2
-use smbcloud_networking::{environment::Environment, get_smb_token};
3
use spinners::Spinner;
4
-use anyhow::Result;
5
-use crate::cli::CommandResult;
4
+use std::{fs, path::Path};
5
+use toml::Value;
6
7
-pub(crate) async fn check_config(env: Environment) -> Result<CommandResult> {
7
+pub(crate) async fn check_config() -> Result<String> {
8
let mut spinner = Spinner::new(
9
spinners::Spinners::SimpleDotsScrolling,
10
style("Checking config...").green().bold().to_string(),
11
);
12
13
- let token = match get_smb_token(env).await {
14
- Ok(token) => token,
15
- Err(e) => {
16
- spinner.stop_and_persist("😩", e.to_string());
17
- return Ok(CommandResult {
18
- spinner,
19
- symbol: "😩".to_owned(),
20
- msg: "Failed to get SMB token.".to_owned(),
21
- });
22
- }
23
- };
13
+ // Get .smb/config.toml file path in the current directory
14
+ let config_path = Path::new(".smb/config.toml");
15
+ if !config_path.exists() {
16
+ spinner.stop_and_persist("😩", "No config file found.".to_owned());
17
+ return Err(anyhow::anyhow!(
18
+ "No config file found. Please run `smbcloud init` command."
19
+ ));
20
+ }
21
+
22
+ // Parse toml file
23
+ let config_content = fs::read_to_string(config_path)?;
24
+ let config: Value = toml::from_str(&config_content)?;
25
+
26
+ let repo_name = config
27
+ .get("repo")
28
+ .and_then(|repo| repo.get("name"))
29
+ .and_then(|name| name.as_str())
30
+ .ok_or_else(|| anyhow::anyhow!("Repo name not found in config file"))?;
31
25
- Ok(CommandResult {
26
- spinner,
27
- symbol: "✅".to_owned(),
28
- msg: format!("SMB Token: {}", token),
29
- })
30
-}
\ No newline at end of file
32
+ Ok(repo_name.to_string())
33
+}
smbcloud-cli/src/lib.rs
+1
@@ -2,3 +2,4 @@ pub mod account;
2
pub mod cli;
3
pub mod deploy;
4
pub mod project;
5
+mod ui;
\ No newline at end of file
smbcloud-cli/src/main.rs
+4
-5
@@ -1,6 +1,7 @@
1
use anyhow::{anyhow, Result};
2
use clap::Parser;
3
use console::style;
4
+use smbcloud_cli::account::login::process_login;
5
use smbcloud_cli::cli::CommandResult;
6
use smbcloud_cli::project::init::process_project_init;
7
use smbcloud_cli::{
@@ -74,8 +75,7 @@ fn setup_logging(env: Environment, level: Option<EnvFilter>) -> Result<()> {
75
async fn main() {
76
match run().await {
77
Ok(result) => {
77
- let mut spinner = result.spinner;
78
- spinner.stop_and_persist(&result.symbol, result.msg);
78
+ result.stop_and_persist();
79
std::process::exit(1);
80
}
81
Err(e) => {
@@ -111,10 +111,9 @@ async fn run() -> Result<CommandResult> {
111
112
match cli.command {
113
Commands::Account { command } => process_account(cli.environment, command).await,
114
+ Commands::Login {} => process_login(cli.environment).await,
115
Commands::Project { command } => process_project(cli.environment, command).await,
115
- Commands::Init { name, description } => {
116
- process_project_init(cli.environment, name, description).await
117
- }
116
+ Commands::Init { } => { process_project_init(cli.environment).await }
117
Commands::Deploy {} => process_deploy(cli.environment).await,
118
}
119
}
smbcloud-cli/src/project/init.rs
+46
-23
@@ -1,38 +1,61 @@
1
-use crate::cli::CommandResult;
2
-use anyhow::Result;
1
+use crate::{account::lib::protected_request, cli::CommandResult, ui::{succeed_message, succeed_symbol}};
2
+use anyhow::{anyhow, Result};
3
use console::style;
4
+use dialoguer::{theme::ColorfulTheme, Input};
5
use smbcloud_model::project::ProjectCreate;
6
use smbcloud_networking::environment::Environment;
7
use smbcloud_networking_project::create_project;
8
use spinners::Spinner;
9
9
-pub async fn process_project_init(
10
- env: Environment,
11
- name: Option<String>,
12
- description: Option<String>,
13
-) -> Result<CommandResult> {
14
- let mut spinner = Spinner::new(
10
+pub async fn process_project_init(env: Environment) -> Result<CommandResult> {
11
+ protected_request(env).await?;
12
+
13
+ let project_name = match Input::<String>::with_theme(&ColorfulTheme::default())
14
+ .with_prompt("Project name")
15
+ .interact()
16
+ {
17
+ Ok(project_name) => project_name,
18
+ Err(_) => {
19
+ let error = anyhow!("Invalid project name.");
20
+ return Err(error);
21
+ }
22
+ };
23
+ let description = match Input::<String>::with_theme(&ColorfulTheme::default())
24
+ .with_prompt("Description")
25
+ .interact()
26
+ {
27
+ Ok(description) => description,
28
+ Err(_) => {
29
+ let error = anyhow!("Invalid description.");
30
+ return Err(error);
31
+ }
32
+ };
33
+
34
+ let spinner = Spinner::new(
35
spinners::Spinners::SimpleDotsScrolling,
16
- style("Loading...").green().bold().to_string(),
36
+ style("Creating a project...").green().bold().to_string(),
37
);
18
- let name = name.unwrap_or_else(|| "New Project".to_string());
19
- let description = description.unwrap_or_else(|| "No description".to_string());
20
- let project = ProjectCreate { name, description };
21
- match create_project(env, project).await {
22
- Ok(p) => {
23
- spinner.stop_and_persist("✅", "Done.".to_owned());
24
- Ok(CommandResult {
25
- spinner,
26
- symbol: "✅".to_owned(),
27
- msg: format!("Project {} has been created.", p.name),
28
- })
29
- }
38
+
39
+ match create_project(
40
+ env,
41
+ ProjectCreate {
42
+ name: project_name.clone(),
43
+ description: description.clone(),
44
+ },
45
+ )
46
+ .await
47
+ {
48
+ Ok(_) => Ok(CommandResult {
49
+ spinner,
50
+ symbol: succeed_symbol(),
51
+ msg: succeed_message(&format!("{project_name} has been created.")),
52
+ }),
53
Err(e) => {
54
println!("Error: {e:#?}");
55
Ok(CommandResult {
56
spinner,
34
- symbol: "😩".to_owned(),
35
- msg: "Failed to initiate a project.".to_string(),
57
+ symbol: succeed_symbol(),
58
+ msg: format!("Failed to create a project {project_name}."),
59
})
60
}
61
}
smbcloud-cli/src/project/mod.rs
+6
-48
@@ -6,62 +6,20 @@ use crate::cli::CommandResult;
6
use anyhow::{anyhow, Result};
7
use console::style;
8
use dialoguer::{theme::ColorfulTheme, Input};
9
+use init::process_project_init;
10
use log::debug;
10
-use smbcloud_model::{
11
- self,
12
- project::{Config, Project, ProjectCreate},
13
-};
11
+use smbcloud_model::
12
+ project::{Config, Project}
13
+;
14
use smbcloud_networking::environment::Environment;
15
-use smbcloud_networking_project::{create_project, delete_project, get_all, get_project};
15
+use smbcloud_networking_project::{delete_project, get_all, get_project};
16
use spinners::Spinner;
17
use std::{fs::OpenOptions, io::Write};
18
19
pub async fn process_project(env: Environment, commands: Commands) -> Result<CommandResult> {
20
match commands {
21
Commands::New {} => {
22
- let project_name = Input::<String>::with_theme(&ColorfulTheme::default())
23
- .with_prompt("Project name")
24
- .interact()
25
- .unwrap();
26
- let description = Input::<String>::with_theme(&ColorfulTheme::default())
27
- .with_prompt("Description")
28
- .interact()
29
- .unwrap();
30
-
31
- let mut spinner = Spinner::new(
32
- spinners::Spinners::SimpleDotsScrolling,
33
- style("Creating a project...").green().bold().to_string(),
34
- );
35
-
36
- match create_project(
37
- env,
38
- ProjectCreate {
39
- name: project_name.clone(),
40
- description: description.clone(),
41
- },
42
- )
43
- .await
44
- {
45
- Ok(_) => {
46
- spinner.stop_and_persist("✅", "Done.".to_owned());
47
- Ok(CommandResult {
48
- spinner: Spinner::new(
49
- spinners::Spinners::SimpleDotsScrolling,
50
- style("Loading...").green().bold().to_string(),
51
- ),
52
- symbol: "✅".to_owned(),
53
- msg: format!("{project_name} has been created."),
54
- })
55
- }
56
- Err(e) => {
57
- println!("Error: {e:#?}");
58
- Ok(CommandResult {
59
- spinner,
60
- symbol: "😩".to_owned(),
61
- msg: format!("Failed to create a project {project_name}."),
62
- })
63
- }
64
- }
22
+ process_project_init(env).await
23
}
24
Commands::List {} => {
25
let mut spinner = Spinner::new(
smbcloud-cli/src/ui/mod.rs
new
+13
@@ -0,0 +1,13 @@
1
+use console::style;
2
+
3
+pub fn succeed_symbol() -> String {
4
+ style("✔").green().to_string()
5
+}
6
+
7
+pub fn fail_symbol() -> String {
8
+ style("✘").for_stderr().red().to_string()
9
+}
10
+
11
+pub fn succeed_message(message: &str) -> String {
12
+ style(message).bold().white().to_string()
13
+}
\ No newline at end of file
smbcloud-networking/src/constants.rs
+1
-1
@@ -17,4 +17,4 @@ pub const PATH_USERS_SIGN_IN: &str = "v1/users/sign_in";
17
pub const PATH_LINK_GITHUB_ACCOUNT: &str = "v1/link_github_account";
18
pub const PATH_RESET_PASSWORD_INSTRUCTIONS: &str = "v1/resend_reset_password_instructions";
19
pub const PATH_RESEND_CONFIRMATION: &str = "v1/resend_confirmation";
20
-pub const PATH_AUTHORIZE: &str = "v1/authorize";
20
+pub const PATH_AUTHORIZE: &str = "v1/authorize";
\ No newline at end of file