@smbcloud / cli / commits / b6577d9

config deploy

Seto Elkahfi committed May 29, 2025 at 16:15 UTC b6577d9fe362e30334f4cf2ba8e5e31e513a37fa
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,