5 files changed
+61
-65
crates/cli/src/deploy/setup_create_new_project.rs
+7
-16
@@ -101,24 +101,15 @@ pub(crate) async fn create_new_project(
101
}
102
};
103
104
- match create_project(
104
+ let project = create_project(
105
env,
106
client(),
107
access_token.clone(),
108
- ProjectCreate {
109
- name,
110
- runner,
111
- repository,
112
- description,
113
- deployment_method: Default::default(),
114
- },
108
+ ProjectCreate { name, description },
109
)
116
- .await
117
- {
118
- Ok(project) => {
119
- let _ = ensure_default_frontend_app_for_project(env, &access_token, &project).await;
120
- Ok(project)
121
- }
122
- Err(e) => Err(e),
123
- }
110
+ .await?;
111
+
112
+ ensure_default_frontend_app_for_project(env, &access_token, &project, runner, Some(repository))
113
+ .await?;
114
+ Ok(project)
115
}
crates/cli/src/deploy/setup_project.rs
+4
@@ -74,10 +74,14 @@ pub(crate) async fn setup_project(
74
Some(frontend_app) => {
75
merge_project_with_frontend_app(&workspace_project, &frontend_app)
76
}
77
+ // Older workspace projects still carry legacy runner/repository
78
+ // fields; reuse them when seeding the project's first app.
79
None => match ensure_default_frontend_app_for_project(
80
env,
81
&access_token,
82
&workspace_project,
83
+ workspace_project.runner,
84
+ workspace_project.repository.clone(),
85
)
86
.await
87
{
crates/cli/src/project/crud_create.rs
+40
-36
@@ -5,7 +5,6 @@ use crate::{
5
cli::CommandResult,
6
project::deploy_target::{
7
ensure_default_frontend_app_for_project, merge_project_with_frontend_app,
8
- resolve_frontend_app_for_project,
8
},
9
ui::{fail_message, succeed_message, succeed_symbol},
10
};
@@ -14,6 +13,7 @@ use console::style;
13
use dialoguer::console::Term;
14
use dialoguer::Select;
15
use dialoguer::{theme::ColorfulTheme, Input};
16
+use smbcloud_model::frontend_app::FrontendApp;
17
use smbcloud_model::project::{Project, ProjectCreate};
18
use smbcloud_model::runner::Runner;
19
use smbcloud_network::environment::Environment;
@@ -40,6 +40,8 @@ pub async fn process_project_init(
40
}
41
};
42
43
+ // Runner and repository describe the project's first app — the deployable
44
+ // unit inside the workspace — not the workspace itself.
45
let runners = vec![
46
Runner::NodeJs,
47
Runner::Static,
@@ -47,18 +49,22 @@ pub async fn process_project_init(
49
Runner::Swift,
50
Runner::Rust,
51
];
50
- let runner = Select::with_theme(&ColorfulTheme::default())
52
+ let runner = match Select::with_theme(&ColorfulTheme::default())
53
.items(&runners)
54
.default(0)
55
.interact_on_opt(&Term::stderr())
54
- .map(|i| runners[i.unwrap()])
55
- .unwrap();
56
+ {
57
+ Ok(Some(index)) => runners[index],
58
+ _ => {
59
+ return Err(anyhow!(fail_message("Invalid runner.")));
60
+ }
61
+ };
62
63
let repository = match Input::<String>::with_theme(&ColorfulTheme::default())
64
.with_prompt("Repository name")
65
.interact()
66
{
61
- Ok(project_name) => project_name,
67
+ Ok(repository) => repository,
68
Err(_) => {
69
return Err(anyhow!(fail_message("Invalid repository name.")));
70
}
@@ -79,58 +85,56 @@ pub async fn process_project_init(
85
);
86
87
let access_token = get_smb_token(env)?;
82
- match create_project(
88
+ let project = match create_project(
89
env,
90
client(),
91
access_token.clone(),
92
ProjectCreate {
93
name: project_name.clone(),
88
- runner,
89
- repository,
94
description: description.clone(),
91
- deployment_method: Default::default(),
95
},
96
)
97
.await
98
{
96
- Ok(project) => {
97
- if should_init_project {
98
- let _ = ensure_default_frontend_app_for_project(env, &access_token, &project).await;
99
- write_smb_config(env, &access_token, project.clone()).await?;
100
- }
101
-
102
- Ok(CommandResult {
103
- spinner,
104
- symbol: succeed_symbol(),
105
- msg: succeed_message(&format!("{project_name} has been created.")),
106
- })
107
- }
99
+ Ok(project) => project,
100
Err(e) => {
101
println!("Error: {e:#?}");
110
- Err(anyhow!(fail_message("Failed to create project.")))
102
+ return Err(anyhow!(fail_message("Failed to create project.")));
103
}
112
- }
113
-}
104
+ };
105
115
-async fn write_smb_config(
116
- env: Environment,
117
- access_token: &str,
118
- workspace_project: Project,
119
-) -> Result<()> {
120
- let deploy_target = match resolve_frontend_app_for_project(
106
+ let frontend_app = match ensure_default_frontend_app_for_project(
107
env,
122
- access_token,
123
- &workspace_project,
124
- false,
108
+ &access_token,
109
+ &project,
110
+ runner,
111
+ Some(repository),
112
)
113
.await
114
{
128
- Ok(Some(frontend_app)) => {
129
- merge_project_with_frontend_app(&workspace_project, &frontend_app)
115
+ Ok(frontend_app) => frontend_app,
116
+ Err(e) => {
117
+ println!("Error: {e:#?}");
118
+ return Err(anyhow!(fail_message(
119
+ "Project created, but failed to create its app."
120
+ )));
121
}
131
- _ => workspace_project.clone(),
122
};
123
124
+ if should_init_project {
125
+ write_smb_config(&project, &frontend_app)?;
126
+ }
127
+
128
+ Ok(CommandResult {
129
+ spinner,
130
+ symbol: succeed_symbol(),
131
+ msg: succeed_message(&format!("{project_name} has been created.")),
132
+ })
133
+}
134
+
135
+fn write_smb_config(workspace_project: &Project, frontend_app: &FrontendApp) -> Result<()> {
136
+ let deploy_target = merge_project_with_frontend_app(workspace_project, frontend_app);
137
+
138
let config = DeployConfig {
139
name: workspace_project.name.clone(),
140
description: workspace_project.description.clone(),
crates/cli/src/project/deploy_target.rs
+7
-2
@@ -4,6 +4,7 @@ use {
4
smbcloud_model::{
5
frontend_app::{AppType, FrontendApp, FrontendAppCreate},
6
project::Project,
7
+ runner::Runner,
8
},
9
smbcloud_network::environment::Environment,
10
smbcloud_networking_project::{
@@ -55,10 +56,14 @@ pub async fn resolve_frontend_app_for_project(
56
Ok(selection.map(|index| frontend_apps[index].clone()))
57
}
58
59
+// Runner and repository are passed explicitly because the workspace project no
60
+// longer carries deploy concerns — they belong to the app, the deployable unit.
61
pub async fn ensure_default_frontend_app_for_project(
62
env: Environment,
63
access_token: &str,
64
project: &Project,
65
+ runner: Runner,
66
+ repository: Option<String>,
67
) -> Result<FrontendApp, smbcloud_model::error_codes::ErrorResponse> {
68
create_frontend_app(
69
env,
@@ -68,9 +73,9 @@ pub async fn ensure_default_frontend_app_for_project(
73
name: project.name.clone(),
74
project_id: project.id,
75
app_type: AppType::Web,
71
- runner: project.runner,
76
+ runner,
77
deployment_method: project.deployment_method,
73
- repository: project.repository.clone(),
78
+ repository,
79
description: project.description.clone(),
80
},
81
)
crates/smbcloud-model/src/project.rs
+3
-11
@@ -128,15 +128,13 @@ impl Display for Project {
128
write!(f, "ID: {}, Name: {}", self.id, self.name,)
129
}
130
}
131
+/// Payload for creating the umbrella workspace. Deploy concerns (runner,
132
+/// repository, deployment method) live on the App, not the Project.
133
#[derive(Serialize, Debug, Deserialize, Clone)]
134
#[tsync]
135
pub struct ProjectCreate {
136
pub name: String,
135
- pub repository: String,
137
pub description: String,
137
- pub runner: Runner,
138
- #[serde(default)]
139
- pub deployment_method: DeploymentMethod,
138
}
139
140
#[derive(Deserialize, Serialize, Debug)]
@@ -188,17 +186,11 @@ mod tests {
186
fn test_project_create() {
187
let project_create = ProjectCreate {
188
name: "test".to_owned(),
191
- repository: "test".to_owned(),
189
description: "test".to_owned(),
193
- runner: Runner::NodeJs,
194
- deployment_method: DeploymentMethod::Git,
190
};
191
let json = json!({
192
"name": "test",
198
- "repository": "test",
199
- "description": "test",
200
- "runner": 0,
201
- "deployment_method": 0
193
+ "description": "test"
194
});
195
assert_eq!(serde_json::to_value(project_create).unwrap(), json);
196
}