Skip to main content

smbcloud_model/
runner.rs

1use {
2    crate::error_codes::{ErrorCode::UnsupportedRunner, ErrorResponse},
3    serde::{Deserialize, Serialize},
4    serde_repr::{Deserialize_repr, Serialize_repr},
5    std::{
6        fmt::{self, Display, Formatter},
7        path::Path,
8    },
9};
10
11#[derive(Debug, Deserialize_repr, Serialize_repr, Clone, Copy, PartialEq, Default)]
12#[repr(u8)]
13#[tsync::tsync]
14pub enum Runner {
15    #[default]
16    NodeJs = 0,
17    /// A pure static site: no app process on the server, nginx serves files
18    /// directly. Always deployed via rsync — git push has no build step to run.
19    Static = 1,
20    Ruby = 2,
21    Swift = 3,
22    Rust = 4,
23    Monorepo = 255,
24}
25
26impl Display for Runner {
27    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
28        match self {
29            Runner::NodeJs => write!(f, "NodeJs"),
30            Runner::Static => write!(f, "Static"),
31            Runner::Ruby => write!(f, "Ruby"),
32            Runner::Swift => write!(f, "Swift"),
33            Runner::Rust => write!(f, "Rust"),
34            Runner::Monorepo => write!(f, "Monorepo"),
35        }
36    }
37}
38
39#[derive(Debug, Serialize, Deserialize)]
40#[tsync::tsync]
41pub enum NodeJsFramework {
42    NextJs,
43    Astro,
44}
45
46#[derive(Debug, Serialize, Deserialize)]
47#[tsync::tsync]
48pub enum RubyFramework {
49    Rails,
50}
51
52#[derive(Debug, Serialize, Deserialize)]
53#[tsync::tsync]
54pub enum SwiftFramework {
55    Vapor,
56}
57
58impl Runner {
59    pub fn from(repo_path: &Path) -> Result<Runner, ErrorResponse> {
60        // Any package.json-driven app belongs on the NodeJs runner.
61        // Framework-specific checks are not reliable because modern Next.js apps
62        // do not need a next.config.* file at all.
63        if repo_path.join("package.json").exists() {
64            return Ok(Runner::NodeJs);
65        }
66
67        if repo_path.join("Gemfile").exists() {
68            return Ok(Runner::Ruby);
69        }
70        if repo_path.join("Package.swift").exists() {
71            return Ok(Runner::Swift);
72        }
73        if repo_path.join("Cargo.toml").exists() {
74            return Ok(Runner::Rust);
75        }
76        // See if we have a monorepo setup.
77        non_framework_runner()
78    }
79
80    pub fn git_host(&self) -> String {
81        format!("git@{}.smbcloud.xyz", self.api())
82    }
83
84    /// Returns the explicit hostname used for rsync SSH connections.
85    /// e.g. `api.smbcloud.xyz` or `api-1.smbcloud.xyz`
86    pub fn rsync_host(&self) -> String {
87        format!("{}.smbcloud.xyz", self.api())
88    }
89
90    fn api(&self) -> &str {
91        match self {
92            Runner::Monorepo => "monorepo",
93            // Static sites and NodeJs projects share the same lightweight tier
94            Runner::NodeJs | Runner::Static => "api",
95            Runner::Ruby | Runner::Swift | Runner::Rust => "api-1",
96        }
97    }
98}
99
100fn non_framework_runner() -> Result<Runner, ErrorResponse> {
101    Err(ErrorResponse::Error {
102        error_code: UnsupportedRunner,
103        message: UnsupportedRunner.message(None).to_string(),
104    })
105}