5 files changed
+142
-10
crates/cli/src/deploy/config.rs
+9
-7
@@ -1,15 +1,17 @@
1
-use std::{fs, path::Path};
2
-
3
-use crate::ui::{fail_message, fail_symbol, succeed_message, succeed_symbol};
1
+use crate::{
2
+ deploy::setup::setup,
3
+ ui::{fail_message, fail_symbol, succeed_message, succeed_symbol},
4
+};
5
use git2::{Cred, CredentialType, Error};
5
-use serde::Deserialize;
6
+use serde::{Deserialize, Serialize};
7
use smbcloud_model::project::Project;
8
use smbcloud_networking::environment::Environment;
9
use smbcloud_networking_project::get_project;
10
use spinners::Spinner;
11
+use std::{fs, path::Path};
12
use thiserror::Error;
13
12
-pub(crate) async fn check_config() -> Result<Config, ConfigError> {
14
+pub(crate) async fn check_config(env: Environment) -> Result<Config, ConfigError> {
15
let mut spinner: Spinner = Spinner::new(
16
spinners::Spinners::SimpleDotsScrolling,
17
succeed_message("Checking config"),
@@ -21,7 +23,7 @@ pub(crate) async fn check_config() -> Result<Config, ConfigError> {
23
let config_path = Path::new(".smb/config.toml");
24
if !config_path.exists() {
25
spinner.stop_and_persist(&fail_symbol(), fail_message("Invalid config."));
24
- return Err(ConfigError::MissingConfig);
26
+ setup(env).await?;
27
}
28
29
// Parse toml file
@@ -64,7 +66,7 @@ pub(crate) async fn check_project(env: Environment, id: i32) -> Result<(), Confi
66
}
67
}
68
67
-#[derive(Deserialize)]
69
+#[derive(Deserialize, Serialize)]
70
pub struct Config {
71
pub name: String,
72
pub description: Option<String>,
crates/cli/src/deploy/mod.rs
+2
-1
@@ -1,6 +1,7 @@
1
pub mod config;
2
mod git;
3
mod remote_messages;
4
+mod setup;
5
6
use crate::{
7
account::lib::protected_request,
@@ -24,7 +25,7 @@ pub async fn process_deploy(env: Environment) -> Result<CommandResult> {
25
protected_request(env).await?;
26
27
// Check config.
27
- let config = check_config().await?;
28
+ let config = check_config(env).await?;
29
30
// Validate config with project.
31
check_project(env, config.project.id).await?;
crates/cli/src/deploy/setup.rs
new
+112
@@ -0,0 +1,112 @@
1
+use crate::deploy::config::{Config, ConfigError};
2
+use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select};
3
+use smbcloud_model::project::{Project, ProjectCreate};
4
+use smbcloud_networking::environment::Environment;
5
+use smbcloud_networking_project::{create_project, get_all};
6
+use std::{env, fs, path::Path};
7
+
8
+pub async fn setup(env: Environment) -> Result<Config, ConfigError> {
9
+ let path = env::current_dir().ok();
10
+ let path_str = path
11
+ .as_ref()
12
+ .map(|p| p.display().to_string())
13
+ .unwrap_or_else(|| ".".to_string());
14
+
15
+ let confirm = Confirm::with_theme(&ColorfulTheme::default())
16
+ .with_prompt(format!("Setup project in {}? y/n", path_str))
17
+ .interact()
18
+ .map_err(|_| ConfigError::MissingConfig)?;
19
+
20
+ if !confirm {
21
+ return Err(ConfigError::MissingConfig);
22
+ }
23
+
24
+ let projects = match get_all(env).await {
25
+ Ok(x) => x,
26
+ Err(_) => return Err(ConfigError::MissingConfig),
27
+ };
28
+
29
+ let project = if !projects.is_empty() {
30
+ select_project(env, projects).await?
31
+ } else {
32
+ create_new_project(env).await?
33
+ };
34
+
35
+ let name = project.name.clone();
36
+ let description = project.description.clone();
37
+
38
+ // Create config struct
39
+ let config = Config {
40
+ project,
41
+ name,
42
+ description,
43
+ };
44
+
45
+ // Ensure .smb directory exists
46
+ let smb_dir = Path::new(".smb");
47
+ if !smb_dir.exists() {
48
+ fs::create_dir(smb_dir).map_err(|_| ConfigError::MissingConfig)?;
49
+ }
50
+
51
+ // Write config to .smb/config.toml
52
+ let config_toml = toml::to_string_pretty(&config).map_err(|_| ConfigError::MissingConfig)?;
53
+ fs::write(".smb/config.toml", config_toml).map_err(|_| ConfigError::MissingConfig)?;
54
+
55
+ println!("Config saved to .smb/config.toml");
56
+
57
+ Ok(config)
58
+}
59
+
60
+async fn select_project(env: Environment, projects: Vec<Project>) -> Result<Project, ConfigError> {
61
+ let confirm = Confirm::with_theme(&ColorfulTheme::default())
62
+ .with_prompt("Use existing project? y/n")
63
+ .interact()
64
+ .map_err(|_| ConfigError::MissingConfig)?;
65
+
66
+ if !confirm {
67
+ return create_new_project(env).await;
68
+ }
69
+ let selection = Select::with_theme(&ColorfulTheme::default())
70
+ .items(&projects)
71
+ .default(0)
72
+ .interact()
73
+ .map_err(|_| ConfigError::MissingConfig)?;
74
+
75
+ let project = projects[selection].clone();
76
+
77
+ Ok(project)
78
+}
79
+
80
+async fn create_new_project(env: Environment) -> Result<Project, ConfigError> {
81
+ let project_name = match Input::<String>::with_theme(&ColorfulTheme::default())
82
+ .with_prompt("Project name")
83
+ .interact()
84
+ {
85
+ Ok(project_name) => project_name,
86
+ Err(_) => {
87
+ return Err(ConfigError::MissingConfig);
88
+ }
89
+ };
90
+ let description = match Input::<String>::with_theme(&ColorfulTheme::default())
91
+ .with_prompt("Description")
92
+ .interact()
93
+ {
94
+ Ok(description) => description,
95
+ Err(_) => {
96
+ return Err(ConfigError::MissingConfig);
97
+ }
98
+ };
99
+
100
+ match create_project(
101
+ env,
102
+ ProjectCreate {
103
+ name: project_name,
104
+ description,
105
+ },
106
+ )
107
+ .await
108
+ {
109
+ Ok(project) => Ok(project),
110
+ Err(_) => Err(ConfigError::MissingConfig),
111
+ }
112
+}
crates/cli/src/project/deployment.rs
+1
-1
@@ -19,7 +19,7 @@ pub(crate) async fn process_deployment(
19
let mut spinner: Spinner =
20
Spinner::new(spinners::Spinners::Hamburger, succeed_message("Loading"));
21
// Load project id from .smb/config.toml
22
- let config = check_config().await?;
22
+ let config = check_config(env).await?;
23
24
check_project(env, config.project.id).await?;
25
crates/smbcloud-model/src/project.rs
+18
-1
@@ -1,3 +1,5 @@
1
+use std::fmt::Display;
2
+
3
use crate::{app_auth::AuthApp, ar_date_format};
4
use chrono::{DateTime, Utc};
5
use serde::{Deserialize, Serialize};
@@ -9,7 +11,7 @@ pub struct Config {
11
pub current_auth_app: Option<AuthApp>,
12
}
13
12
-#[derive(Deserialize, Debug, Serialize)]
14
+#[derive(Deserialize, Debug, Serialize, Clone)]
15
pub struct Project {
16
pub id: i32,
17
pub name: String,
@@ -20,6 +22,21 @@ pub struct Project {
22
#[serde(with = "ar_date_format")]
23
pub updated_at: DateTime<Utc>,
24
}
25
+
26
+impl Display for Project {
27
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28
+ write!(
29
+ f,
30
+ "ID: {}\nName: {}\nRepository: {}\nDescription: {}\nCreated At: {}\nUpdated At: {}",
31
+ self.id,
32
+ self.name,
33
+ self.repository,
34
+ self.description.as_deref().unwrap_or(""),
35
+ self.created_at.format("%Y-%m-%d %H:%M:%S"),
36
+ self.updated_at.format("%Y-%m-%d %H:%M:%S"),
37
+ )
38
+ }
39
+}
40
#[derive(Serialize, Debug)]
41
pub struct ProjectCreate {
42
pub name: String,