@smbcloud / cli / commits / 8e5437b

migrate Project to FrontendApp

Seto Elkahfi committed Apr 3, 2026 at 19:14 UTC 8e5437bc0a873e14677e06d828aa7821d4412cd0
6 files changed +155 -3
crates/cli/src/project/crud_read.rs
+1
@@ -143,6 +143,7 @@ pub(crate) async fn process_project_use(env: Environment, id: String) -> Result<
143
144 let config = Config {
145 current_project: Some(project),
146 + current_frontend_app: None,
147 current_auth_app: None,
148 };
149
crates/smbcloud-model/src/app_auth.rs
+2 -2
@@ -2,7 +2,7 @@ use crate::ar_date_format;
2 use chrono::{DateTime, Utc};
3 use serde::{Deserialize, Serialize};
4
5 -#[derive(Serialize, Deserialize, Debug)]
5 +#[derive(Serialize, Deserialize, Debug, Clone)]
6 pub struct AuthApp {
7 pub id: String,
8 pub secret: Option<String>,
@@ -13,7 +13,7 @@ pub struct AuthApp {
13 pub updated_at: DateTime<Utc>,
14 }
15
16 -#[derive(Serialize, Deserialize, Debug)]
16 +#[derive(Serialize, Deserialize, Debug, Clone)]
17 pub struct AuthAppCreate {
18 pub name: String,
19 pub description: String,
crates/smbcloud-model/src/frontend_app.rs new
+144
@@ -0,0 +1,144 @@
1 +use {
2 + crate::{
3 + ar_date_format,
4 + project::{DeploymentMethod, DeploymentStatus},
5 + runner::Runner,
6 + },
7 + chrono::{DateTime, Utc},
8 + serde::{Deserialize, Serialize},
9 + serde_repr::{Deserialize_repr, Serialize_repr},
10 + std::fmt::Display,
11 + tsync::tsync,
12 +};
13 +
14 +/// Whether this app is a web application or a Tauri cross-platform app.
15 +#[derive(Deserialize_repr, Serialize_repr, Debug, Clone, Copy, PartialEq, Eq, Default)]
16 +#[repr(u8)]
17 +#[tsync]
18 +pub enum AppType {
19 + /// Web application (SPA, SSR, static site). All legacy Projects map here.
20 + #[default]
21 + Web = 0,
22 + /// Cross-platform desktop/mobile application built with Tauri.
23 + Tauri = 1,
24 +}
25 +
26 +impl Display for AppType {
27 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 + match self {
29 + AppType::Web => write!(f, "Web"),
30 + AppType::Tauri => write!(f, "Tauri"),
31 + }
32 + }
33 +}
34 +
35 +/// A deployable frontend application on the smbCloud platform.
36 +///
37 +/// Replaces the legacy `Project` as the primary unit of deployment.
38 +/// A `FrontendApp` belongs to a `Tenant` directly and is associated with an
39 +/// owner workspace (Project). It can be shared across multiple workspaces.
40 +#[derive(Deserialize, Serialize, Debug, Clone)]
41 +#[tsync]
42 +pub struct FrontendApp {
43 + pub id: String,
44 + pub name: String,
45 + pub app_type: AppType,
46 + pub runner: Runner,
47 + #[serde(default)]
48 + pub deployment_method: DeploymentMethod,
49 + pub project_id: i32,
50 + pub tenant_id: i32,
51 + pub repository: Option<String>,
52 + pub description: Option<String>,
53 + pub project_ids: Vec<i32>,
54 +
55 + // ── CLI-local deployment config fields ───────────────────────────────────
56 + // These are not persisted in the database; they are stored in the local
57 + // .smbcloud config file alongside the FrontendApp record.
58 + /// Deployment kind, e.g. "vite-spa". Absent for server-side runners.
59 + pub kind: Option<String>,
60 + /// Local source directory to build from, e.g. "frontend/my-app".
61 + pub source: Option<String>,
62 + /// Build output directory relative to `source`, e.g. "dist".
63 + pub output: Option<String>,
64 + /// Package manager to use for the build step, e.g. "pnpm".
65 + pub package_manager: Option<String>,
66 + /// PM2 process name to restart after a nextjs-ssr deploy.
67 + pub pm2_app: Option<String>,
68 + /// Path to a shared lib directory to rsync before deploying.
69 + pub shared_lib: Option<String>,
70 + /// SSH command to run on the server after rsyncing the shared lib.
71 + pub compile_cmd: Option<String>,
72 + /// Remote destination path on the server.
73 + pub path: Option<String>,
74 +
75 + pub created_at: DateTime<Utc>,
76 + pub updated_at: DateTime<Utc>,
77 +}
78 +
79 +impl Display for FrontendApp {
80 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 + write!(
82 + f,
83 + "ID: {}, Name: {}, Type: {}",
84 + self.id, self.name, self.app_type
85 + )
86 + }
87 +}
88 +
89 +/// Payload for creating a new FrontendApp via the API.
90 +#[derive(Serialize, Debug, Deserialize, Clone)]
91 +#[tsync]
92 +pub struct FrontendAppCreate {
93 + pub name: String,
94 + pub project_id: i32,
95 + pub app_type: AppType,
96 + pub runner: Runner,
97 + #[serde(default)]
98 + pub deployment_method: DeploymentMethod,
99 + pub repository: Option<String>,
100 + pub description: Option<String>,
101 +}
102 +
103 +/// A deployment record tied to a FrontendApp.
104 +#[derive(Deserialize, Serialize, Debug)]
105 +#[tsync]
106 +pub struct FrontendAppDeployment {
107 + pub id: i32,
108 + pub frontend_app_id: String,
109 + pub commit_hash: String,
110 + pub status: DeploymentStatus,
111 + #[serde(with = "ar_date_format")]
112 + pub created_at: DateTime<Utc>,
113 + #[serde(with = "ar_date_format")]
114 + pub updated_at: DateTime<Utc>,
115 +}
116 +
117 +#[cfg(test)]
118 +mod tests {
119 + use super::*;
120 + use serde_json::json;
121 +
122 + #[test]
123 + fn test_frontend_app_create_serialization() {
124 + let create = FrontendAppCreate {
125 + name: "my-app".to_owned(),
126 + project_id: 1,
127 + app_type: AppType::Web,
128 + runner: Runner::NodeJs,
129 + deployment_method: DeploymentMethod::Git,
130 + repository: Some("my-repo".to_owned()),
131 + description: None,
132 + };
133 + let value = serde_json::to_value(&create).unwrap();
134 + assert_eq!(value["app_type"], json!(0));
135 + assert_eq!(value["runner"], json!(0));
136 + assert_eq!(value["deployment_method"], json!(0));
137 + }
138 +
139 + #[test]
140 + fn test_app_type_display() {
141 + assert_eq!(AppType::Web.to_string(), "Web");
142 + assert_eq!(AppType::Tauri.to_string(), "Tauri");
143 + }
144 +}
crates/smbcloud-model/src/lib.rs
+1
@@ -2,6 +2,7 @@ pub mod account;
2 pub mod app_auth;
3 pub mod error_codes;
4 pub mod forgot;
5 +pub mod frontend_app;
6 pub mod login;
7 pub mod oauth;
8 pub mod project;
crates/smbcloud-model/src/project.rs
+5 -1
@@ -31,9 +31,13 @@ impl Display for DeploymentMethod {
31 }
32 }
33
34 -#[derive(Deserialize, Debug, Serialize)]
34 +#[derive(Deserialize, Debug, Serialize, Clone)]
35 pub struct Config {
36 + /// Legacy project field — kept for backward compatibility during migration.
37 pub current_project: Option<Project>,
38 + /// The active FrontendApp for CLI deploy operations.
39 + #[serde(default)]
40 + pub current_frontend_app: Option<crate::frontend_app::FrontendApp>,
41 pub current_auth_app: Option<AuthApp>,
42 }
43
crates/smbcloud-utils/src/lib.rs
+2
@@ -29,6 +29,7 @@ pub async fn get_config() -> Result<Config> {
29 if !path.exists() {
30 let config = Config {
31 current_project: None,
32 + current_frontend_app: None,
33 current_auth_app: None,
34 };
35 return Ok(config);
@@ -47,6 +48,7 @@ pub async fn get_config() -> Result<Config> {
48 None => {
49 let config = Config {
50 current_project: None,
51 + current_frontend_app: None,
52 current_auth_app: None,
53 };
54 Ok(config)