feature/rsync-deploy
claude/smbcloud-github-app-k9641p
development
feature/apps
feature/build-release
feature/ci-docs
feature/ci-ruby-gems
feature/cicd
feature/completion
feature/dist-nuget
feature/fix-ci-sdk-pypi-rubygem
feature/fix-clippy-on-stable-channel
feature/fix-deployment-script
feature/github-email
feature/gresiq-v1
feature/project-new-structure
feature/rsync-deploy
feature/ruby-runner
feature/sdk-npm
feature/sdk-workflows
feature/smbcloud-cli-release-npm
feature/smbcloud-deploy
feature/wasm
main
release/v0.7.4
| 1 | use { |
| 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 | fs, |
| 8 | path::PathBuf, |
| 9 | }, |
| 10 | }; |
| 11 | |
| 12 | #[derive(Debug, Deserialize_repr, Serialize_repr, Clone, Copy, PartialEq, Default)] |
| 13 | #[repr(u8)] |
| 14 | #[tsync::tsync] |
| 15 | pub enum Runner { |
| 16 | #[default] |
| 17 | NodeJs = 0, |
| 18 | /// A pure static site: no app process on the server, nginx serves files |
| 19 | /// directly. Always deployed via rsync — git push has no build step to run. |
| 20 | Static = 1, |
| 21 | Ruby = 2, |
| 22 | Swift = 3, |
| 23 | Monorepo = 255, |
| 24 | } |
| 25 | |
| 26 | impl 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::Monorepo => write!(f, "Monorepo"), |
| 34 | } |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | #[derive(Debug, Serialize, Deserialize)] |
| 39 | #[tsync::tsync] |
| 40 | pub enum NodeJsFramework { |
| 41 | NextJs, |
| 42 | Astro, |
| 43 | } |
| 44 | |
| 45 | #[derive(Debug, Serialize, Deserialize)] |
| 46 | #[tsync::tsync] |
| 47 | pub enum RubyFramework { |
| 48 | Rails, |
| 49 | } |
| 50 | |
| 51 | #[derive(Debug, Serialize, Deserialize)] |
| 52 | #[tsync::tsync] |
| 53 | pub enum SwiftFramework { |
| 54 | Vapor, |
| 55 | } |
| 56 | |
| 57 | impl Runner { |
| 58 | pub fn from(repo_path: &PathBuf) -> Result<Runner, ErrorResponse> { |
| 59 | // See if we have a framework-based config. |
| 60 | if repo_path.join("package.json").exists() |
| 61 | && (next_config_exists(repo_path) || astro_config_exists(repo_path)) |
| 62 | { |
| 63 | return Ok(Runner::NodeJs); |
| 64 | } |
| 65 | |
| 66 | if repo_path.join("Gemfile").exists() { |
| 67 | return Ok(Runner::Ruby); |
| 68 | } |
| 69 | if repo_path.join("Package.swift").exists() { |
| 70 | return Ok(Runner::Swift); |
| 71 | } |
| 72 | // See if we have a monorepo setup. |
| 73 | non_framework_runner() |
| 74 | } |
| 75 | |
| 76 | pub fn git_host(&self) -> String { |
| 77 | format!("git@{}.smbcloud.xyz", self.api()) |
| 78 | } |
| 79 | |
| 80 | /// Returns the explicit hostname used for rsync SSH connections. |
| 81 | /// e.g. `api.smbcloud.xyz` or `api-1.smbcloud.xyz` |
| 82 | pub fn rsync_host(&self) -> String { |
| 83 | format!("{}.smbcloud.xyz", self.api()) |
| 84 | } |
| 85 | |
| 86 | fn api(&self) -> &str { |
| 87 | match self { |
| 88 | Runner::Monorepo => "monorepo", |
| 89 | // Static sites and NodeJs projects share the same lightweight tier |
| 90 | Runner::NodeJs | Runner::Static => "api", |
| 91 | Runner::Ruby | Runner::Swift => "api-1", |
| 92 | } |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | fn non_framework_runner() -> Result<Runner, ErrorResponse> { |
| 97 | Err(ErrorResponse::Error { |
| 98 | error_code: UnsupportedRunner, |
| 99 | message: UnsupportedRunner.message(None).to_string(), |
| 100 | }) |
| 101 | } |
| 102 | |
| 103 | // Helper function to detect any next.config.* file |
| 104 | fn next_config_exists(repo_path: &PathBuf) -> bool { |
| 105 | if let Ok(entries) = fs::read_dir(repo_path) { |
| 106 | for entry in entries.flatten() { |
| 107 | let filename = entry.file_name(); |
| 108 | let filename_str = filename.to_string_lossy(); |
| 109 | if filename_str.starts_with("next.config.") { |
| 110 | return true; |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | false |
| 115 | } |
| 116 | |
| 117 | // Helper function to detect any astro.config.* file |
| 118 | fn astro_config_exists(repo_path: &PathBuf) -> bool { |
| 119 | if let Ok(entries) = fs::read_dir(repo_path) { |
| 120 | for entry in entries.flatten() { |
| 121 | let filename = entry.file_name(); |
| 122 | let filename_str = filename.to_string_lossy(); |
| 123 | if filename_str.starts_with("astro.config.") { |
| 124 | return true; |
| 125 | } |
| 126 | } |
| 127 | } |
| 128 | false |
| 129 | } |