1 use {
2 crate::{app_auth::AuthApp, ar_date_format, runner::Runner},
3 chrono::{DateTime, Utc},
4 serde::{Deserialize, Serialize},
5 serde_repr::{Deserialize_repr, Serialize_repr},
6 std::fmt::Display,
7 tsync::tsync,
8 };
9
10 fn default_datetime() -> DateTime<Utc> {
11 DateTime::UNIX_EPOCH
12 }
13
14 /// How the project's files are delivered to the server.
15 ///
16 /// `Git` — the classic smbCloud flow: push to a remote git repo, the server
17 /// builds and restarts the process.
18 /// `Rsync` — files are transferred directly with rsync over SSH; no build step
19 /// runs on the server. Ideal for pre-built static sites or assets.
20 #[derive(Deserialize_repr, Serialize_repr, Debug, Clone, Copy, PartialEq, Eq, Default)]
21 #[repr(u8)]
22 #[tsync]
23 pub enum DeploymentMethod {
24 #[default]
25 Git = 0,
26 Rsync = 1,
27 }
28
29 impl Display for DeploymentMethod {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 match self {
32 DeploymentMethod::Git => write!(f, "Git"),
33 DeploymentMethod::Rsync => write!(f, "Rsync"),
34 }
35 }
36 }
37
38 #[derive(Deserialize, Debug, Serialize, Clone)]
39 pub struct Config {
40 /// Legacy project field — kept for backward compatibility during migration.
41 pub current_project: Option<Project>,
42 /// The active FrontendApp for CLI deploy operations.
43 #[serde(default)]
44 pub current_frontend_app: Option<crate::frontend_app::FrontendApp>,
45 pub current_auth_app: Option<AuthApp>,
46 }
47
48 #[derive(Deserialize, Serialize, Debug, Clone)]
49 #[tsync]
50 pub struct Project {
51 /// Umbrella smbCloud workspace ID.
52 pub id: i32,
53 #[serde(default)]
54 pub name: String,
55 #[serde(default)]
56 pub runner: Runner,
57 /// Defaults to `Git` when absent (older API responses won't include the field).
58 #[serde(default)]
59 pub deployment_method: DeploymentMethod,
60 pub path: Option<String>,
61 pub repository: Option<String>,
62 pub description: Option<String>,
63 /// Deployable app ID for precise deployment tracking. Optional during the
64 /// migration away from project-as-app semantics.
65 pub frontend_app_id: Option<String>,
66 /// Repo ID backing this deploy target. Optional until the API exposes it
67 /// consistently to the CLI.
68 pub deploy_repo_id: Option<i64>,
69 /// Repo-relative app path for monorepo targets, e.g. "apps/web/console".
70 pub source_path: Option<String>,
71 #[serde(default = "default_datetime")]
72 pub created_at: DateTime<Utc>,
73 #[serde(default = "default_datetime")]
74 pub updated_at: DateTime<Utc>,
75 /// Deployment kind, e.g. "vite-spa", "nextjs-ssr", or "rust".
76 pub kind: Option<String>,
77 /// Local source directory to build from, e.g. "frontend/connected-devices"
78 /// or a Rust crate root like ".".
79 /// Used by local-build deploys such as vite-spa, nextjs-ssr, and rust.
80 /// Distinct from `path`, which is the remote destination on the server.
81 pub source: Option<String>,
82 /// Build output directory relative to `source`, e.g. "dist".
83 pub output: Option<String>,
84 /// Package manager to use for the build step, e.g. "pnpm".
85 pub package_manager: Option<String>,
86 /// PM2 process name to restart after a nextjs-ssr deploy, e.g. "my-app".
87 /// Matches the name passed to `pm2 start` on the server.
88 pub pm2_app: Option<String>,
89 /// Environment variables to seed into the PM2 ecosystem `env_production` block.
90 /// Populated from the server-side App record; not written to `.smb/config.toml`.
91 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub pm2_env: Option<std::collections::HashMap<String, serde_json::Value>>,
93 /// Port the standalone server binds to (default: 3000). Must match nginx upstream configuration.
94 #[serde(default)]
95 pub port: Option<u16>,
96 /// Path to a shared lib directory to rsync to the server before deploying,
97 /// e.g. "lib". Used by Rails apps that depend on native gems built from
98 /// monorepo-level source. Relative to the repo root.
99 pub shared_lib: Option<String>,
100 /// SSH command to run on the server after rsyncing the shared lib,
101 /// e.g. "cd ~/lib/gems/gem_error_codes && rbenv local 3.4.2 && bundle install && bundle exec rake compile".
102 pub compile_cmd: Option<String>,
103 /// Install command override, e.g. "pnpm install --frozen-lockfile".
104 #[serde(default)]
105 pub install_command: Option<String>,
106 /// Rust binary filename to upload and restart, e.g. "onde-cloud".
107 /// When absent, the CLI falls back to the Cargo package name.
108 pub binary_name: Option<String>,
109 /// Rust target triple used for local cross-compilation before upload,
110 /// e.g. "x86_64-unknown-linux-gnu".
111 pub rust_target: Option<String>,
112 /// Swift SDK identifier used to cross-compile a Swift/Vapor app for Linux,
113 /// e.g. "x86_64-swift-linux-musl" (the Static Linux SDK). Defaults to
114 /// "x86_64-swift-linux-musl" when absent. Built natively on the host with
115 /// `swift build --swift-sdk <id>` — no Docker, no emulation.
116 pub swift_sdk: Option<String>,
117 /// Optional toolchain identifier passed via the `TOOLCHAINS` env var when
118 /// cross-compiling Swift. Needed on macOS where the default `swift` is
119 /// Apple's Xcode toolchain (which lacks `lld`); point it at an installed
120 /// swift.org toolchain, e.g. "swift" (resolves to swift-latest) or a bundle
121 /// id like "org.swift.632202605101a". Unnecessary when `swift` is already a
122 /// swift.org toolchain (e.g. via swiftly).
123 pub swift_toolchain: Option<String>,
124 }
125
126 impl Display for Project {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
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,
137 pub description: String,
138 }
139
140 #[derive(Deserialize, Serialize, Debug)]
141 #[tsync]
142 pub struct Deployment {
143 pub id: i32,
144 pub project_id: i32,
145 pub frontend_app_id: Option<String>,
146 pub frontend_app_name: Option<String>,
147 pub commit_hash: String,
148 pub status: DeploymentStatus,
149 #[serde(with = "ar_date_format")]
150 pub created_at: DateTime<Utc>,
151 #[serde(with = "ar_date_format")]
152 pub updated_at: DateTime<Utc>,
153 }
154
155 #[derive(Deserialize, Serialize, Debug)]
156 pub struct DeploymentPayload {
157 pub commit_hash: String,
158 pub status: DeploymentStatus,
159 pub frontend_app_id: Option<String>,
160 }
161
162 #[derive(Deserialize_repr, Serialize_repr, Debug, Clone, Copy)] // Added Clone, Copy
163 #[repr(u8)]
164 #[tsync]
165 pub enum DeploymentStatus {
166 Started = 0,
167 Failed,
168 Done,
169 }
170
171 impl Display for DeploymentStatus {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 match self {
174 DeploymentStatus::Started => write!(f, "🚀"),
175 DeploymentStatus::Failed => write!(f, "❌"),
176 DeploymentStatus::Done => write!(f, "✅"),
177 }
178 }
179 }
180
181 #[cfg(test)]
182 mod tests {
183 use super::*;
184 use serde_json::json;
185 #[test]
186 fn test_project_create() {
187 let project_create = ProjectCreate {
188 name: "test".to_owned(),
189 description: "test".to_owned(),
190 };
191 let json = json!({
192 "name": "test",
193 "description": "test"
194 });
195 assert_eq!(serde_json::to_value(project_create).unwrap(), json);
196 }
197
198 #[test]
199 fn test_deployment_status_display() {
200 assert_eq!(format!("{}", DeploymentStatus::Started), "🚀");
201 assert_eq!(DeploymentStatus::Started.to_string(), "🚀");
202
203 assert_eq!(format!("{}", DeploymentStatus::Failed), "❌");
204 assert_eq!(DeploymentStatus::Failed.to_string(), "❌");
205
206 assert_eq!(format!("{}", DeploymentStatus::Done), "✅");
207 assert_eq!(DeploymentStatus::Done.to_string(), "✅");
208 }
209 }