@smbcloud / cli / commits / 19eca5a

Project new doesn't need repo or app

Seto Elkahfi committed Jun 12, 2026 at 00:14 UTC 19eca5a72bef80f3d8c8b6a1a8444da4572f3448
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 }