@smbcloud / cli / commits / 2ffad72

parse smb config file

Seto Elkahfi committed May 23, 2025 at 11:54 UTC 2ffad725e0386acc30dad3572968535b2cb35fd9
8 files changed +199 -316
.smb/config.toml new
+4
@@ -0,0 +1,4 @@
1 +name = 'smbCloud CLI'
2 +
3 +[repository]
4 +name = 'smbcloudcli'
Cargo.lock
+11 -105
@@ -317,16 +317,7 @@ version = "5.0.1"
317 source = "registry+https://github.com/rust-lang/crates.io-index"
318 checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
319 dependencies = [
320 - "dirs-sys 0.4.1",
321 -]
322 -
323 -[[package]]
324 -name = "dirs"
325 -version = "6.0.0"
326 -source = "registry+https://github.com/rust-lang/crates.io-index"
327 -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
328 -dependencies = [
329 - "dirs-sys 0.5.0",
320 + "dirs-sys",
321 ]
322
323 [[package]]
@@ -337,22 +328,10 @@ checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
328 dependencies = [
329 "libc",
330 "option-ext",
340 - "redox_users 0.4.6",
331 + "redox_users",
332 "windows-sys 0.48.0",
333 ]
334
344 -[[package]]
345 -name = "dirs-sys"
346 -version = "0.5.0"
347 -source = "registry+https://github.com/rust-lang/crates.io-index"
348 -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
349 -dependencies = [
350 - "libc",
351 - "option-ext",
352 - "redox_users 0.5.0",
353 - "windows-sys 0.59.0",
354 -]
355 -
335 [[package]]
336 name = "displaydoc"
337 version = "0.2.5"
@@ -509,17 +488,6 @@ version = "0.31.1"
488 source = "registry+https://github.com/rust-lang/crates.io-index"
489 checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
490
512 -[[package]]
513 -name = "git-url-parse"
514 -version = "0.4.5"
515 -source = "registry+https://github.com/rust-lang/crates.io-index"
516 -checksum = "68d7ff03a34ea818a59cf30c0d7aa55354925484fa30bcc4cb96d784ff07578f"
517 -dependencies = [
518 - "strum 0.26.3",
519 - "thiserror 1.0.69",
520 - "url",
521 -]
522 -
491 [[package]]
492 name = "git2"
493 version = "0.20.2"
@@ -535,12 +503,6 @@ dependencies = [
503 "url",
504 ]
505
538 -[[package]]
539 -name = "glob"
540 -version = "0.3.2"
541 -source = "registry+https://github.com/rust-lang/crates.io-index"
542 -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
543 -
506 [[package]]
507 name = "h2"
508 version = "0.4.10"
@@ -1330,17 +1292,6 @@ dependencies = [
1292 "thiserror 1.0.69",
1293 ]
1294
1333 -[[package]]
1334 -name = "redox_users"
1335 -version = "0.5.0"
1336 -source = "registry+https://github.com/rust-lang/crates.io-index"
1337 -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
1338 -dependencies = [
1339 - "getrandom 0.2.16",
1340 - "libredox",
1341 - "thiserror 2.0.12",
1342 -]
1343 -
1295 [[package]]
1296 name = "regex"
1297 version = "1.11.1"
@@ -1675,8 +1626,7 @@ dependencies = [
1626 "clap_mangen",
1627 "console",
1628 "dialoguer",
1678 - "dirs 5.0.1",
1679 - "git-url-parse",
1629 + "dirs",
1630 "git2",
1631 "home",
1632 "indicatif",
@@ -1692,7 +1642,7 @@ dependencies = [
1642 "smbcloud-networking-project",
1643 "smbcloud-utils",
1644 "spinners",
1695 - "ssh2-config",
1645 + "thiserror 2.0.12",
1646 "tokio",
1647 "toml",
1648 "tracing",
@@ -1720,7 +1670,7 @@ dependencies = [
1670 "anyhow",
1671 "async-trait",
1672 "clap",
1723 - "dirs 5.0.1",
1673 + "dirs",
1674 "home",
1675 "log",
1676 "maybe-async",
@@ -1728,7 +1678,7 @@ dependencies = [
1678 "serde",
1679 "serde_json",
1680 "smbcloud-model",
1731 - "thiserror 1.0.69",
1681 + "thiserror 2.0.12",
1682 "url-builder",
1683 ]
1684
@@ -1738,7 +1688,7 @@ version = "0.3.9"
1688 dependencies = [
1689 "anyhow",
1690 "async-trait",
1741 - "dirs 5.0.1",
1691 + "dirs",
1692 "home",
1693 "log",
1694 "maybe-async",
@@ -1747,7 +1697,7 @@ dependencies = [
1697 "serde_json",
1698 "smbcloud-model",
1699 "smbcloud-networking",
1750 - "thiserror 1.0.69",
1700 + "thiserror 2.0.12",
1701 ]
1702
1703 [[package]]
@@ -1755,7 +1705,7 @@ name = "smbcloud-utils"
1705 version = "0.3.9"
1706 dependencies = [
1707 "anyhow",
1758 - "dirs 5.0.1",
1708 + "dirs",
1709 "home",
1710 "log",
1711 "regex",
@@ -1782,23 +1732,7 @@ checksum = "a0ef947f358b9c238923f764c72a4a9d42f2d637c46e059dbd319d6e7cfb4f82"
1732 dependencies = [
1733 "lazy_static",
1734 "maplit",
1785 - "strum 0.24.1",
1786 -]
1787 -
1788 -[[package]]
1789 -name = "ssh2-config"
1790 -version = "0.5.4"
1791 -source = "registry+https://github.com/rust-lang/crates.io-index"
1792 -checksum = "27c8904276fd411f602a6d7e447cccb6f669068badf71359bc4564a5144a866b"
1793 -dependencies = [
1794 - "anyhow",
1795 - "bitflags",
1796 - "dirs 6.0.0",
1797 - "git2",
1798 - "glob",
1799 - "log",
1800 - "thiserror 2.0.12",
1801 - "wildmatch",
1735 + "strum",
1736 ]
1737
1738 [[package]]
@@ -1819,16 +1753,7 @@ version = "0.24.1"
1753 source = "registry+https://github.com/rust-lang/crates.io-index"
1754 checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
1755 dependencies = [
1822 - "strum_macros 0.24.3",
1823 -]
1824 -
1825 -[[package]]
1826 -name = "strum"
1827 -version = "0.26.3"
1828 -source = "registry+https://github.com/rust-lang/crates.io-index"
1829 -checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
1830 -dependencies = [
1831 - "strum_macros 0.26.4",
1756 + "strum_macros",
1757 ]
1758
1759 [[package]]
@@ -1844,19 +1769,6 @@ dependencies = [
1769 "syn 1.0.109",
1770 ]
1771
1847 -[[package]]
1848 -name = "strum_macros"
1849 -version = "0.26.4"
1850 -source = "registry+https://github.com/rust-lang/crates.io-index"
1851 -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
1852 -dependencies = [
1853 - "heck 0.5.0",
1854 - "proc-macro2",
1855 - "quote",
1856 - "rustversion",
1857 - "syn 2.0.101",
1858 -]
1859 -
1772 [[package]]
1773 name = "subtle"
1774 version = "2.6.1"
@@ -2444,12 +2356,6 @@ dependencies = [
2356 "wasm-bindgen",
2357 ]
2358
2447 -[[package]]
2448 -name = "wildmatch"
2449 -version = "2.4.0"
2450 -source = "registry+https://github.com/rust-lang/crates.io-index"
2451 -checksum = "68ce1ab1f8c62655ebe1350f589c61e505cf94d385bc6a12899442d9081e71fd"
2452 -
2359 [[package]]
2360 name = "winapi"
2361 version = "0.3.9"
Cargo.toml
+2 -4
@@ -5,7 +5,7 @@ members = [
5 "crates/smbcloud-model",
6 "crates/smbcloud-networking",
7 "crates/smbcloud-networking-project",
8 - "crates/smbcloud-utils"
8 + "crates/smbcloud-utils",
9 ]
10
11 [workspace.dependencies]
@@ -16,7 +16,6 @@ clap = "4.1.6"
16 clap_mangen = "0.2.10"
17 console = "0.15.0"
18 dialoguer = "0.11.0"
19 -git-url-parse = "0.4.5"
19 git2 = "0.20.1"
20 home = "0.5.4"
21 log = "0.4.14"
@@ -31,8 +30,7 @@ smbcloud-utils = { version = "0.3.0", path = "crates/smbcloud-utils" }
30 smbcloud-model = { version = "0.3", path = "crates/smbcloud-model" }
31 smbcloud-networking = { version = "0.3", path = "crates/smbcloud-networking" }
32 spinners = "4.1.0"
34 -ssh2-config = "0.5"
35 -thiserror = "1.0.29"
33 +thiserror = "2.0"
34 toml = "0.8"
35 tokio = "1.44"
36 tracing = "0.1.35"
crates/cli/Cargo.toml
+10 -5
@@ -8,7 +8,13 @@ license = "Apache-2.0"
8 repository = "https://github.com/smbcloudXYZ/smbcloud-cli"
9 documentation = "https://docs.smbcloud.xyz/cli"
10 keywords = ["cli", "cloud", "web2", "web3", "smbcloud"]
11 -categories = ["command-line-utilities", "command-line-interface", "cryptography::cryptocurrencies", "development-tools", "wasm"]
11 +categories = [
12 + "command-line-utilities",
13 + "command-line-interface",
14 + "cryptography::cryptocurrencies",
15 + "development-tools",
16 + "wasm",
17 +]
18 readme = "README.md"
19
20 [[bin]]
@@ -16,7 +22,7 @@ name = "smb"
22 path = "src/main.rs"
23
24 [dependencies]
19 -anyhow = { workspace = true }
25 +anyhow = { workspace = true }
26 clap = { workspace = true, features = ["derive", "env"] }
27 clap_mangen = { workspace = true }
28 console = { workspace = true }
@@ -26,7 +32,6 @@ home = { workspace = true }
32 indicatif = { workspace = true }
33 log = { workspace = true }
34 git2 = { workspace = true }
29 -ssh2-config = { workspace = true }
35 open = { workspace = true }
36 regex = { workspace = true }
37 reqwest = { workspace = true, features = ["json"] }
@@ -38,11 +43,11 @@ smbcloud-networking = { workspace = true }
43 smbcloud-networking-project = { workspace = true }
44 smbcloud-utils = { workspace = true }
45 spinners = { workspace = true }
46 +thiserror = { workspace = true }
47 tokio = { workspace = true, features = ["full"] }
48 toml = { workspace = true }
49 tracing = { workspace = true, features = ["log"] }
50 tracing-bunyan-formatter = { workspace = true }
51 tracing-subscriber = { workspace = true, features = ["registry", "env-filter"] }
52 url-builder = { workspace = true }
47 -url = { workspace = true, features = ["serde"] }
48 -git-url-parse = { workspace = true }
53 +url = { workspace = true, features = ["serde"] }
\ No newline at end of file
crates/cli/src/deploy/config.rs renamed
+38 -4
@@ -1,12 +1,16 @@
1 +use std::{fs, path::Path};
2 +
3 use crate::ui::{fail_message, fail_symbol, succeed_message, succeed_symbol};
4 use anyhow::Result;
5 use console::style;
6 +use git2::{Cred, CredentialType, Error};
7 use spinners::Spinner;
5 -use std::{fs, path::Path};
8 use toml::Value;
9
8 -pub(crate) async fn check_config() -> Result<String> {
9 - let mut spinner = Spinner::new(
10 +use super::remote_url::RemoteUrl;
11 +
12 +pub(crate) async fn check_config() -> Result<Config> {
13 + let mut spinner: Spinner = Spinner::new(
14 spinners::Spinners::SimpleDotsScrolling,
15 style("Checking config...").green().bold().to_string(),
16 );
@@ -33,6 +37,36 @@ pub(crate) async fn check_config() -> Result<String> {
37 .ok_or_else(|| anyhow::anyhow!(fail_message("Repo name not found in config file.")))?;
38
39 spinner.stop_and_persist(&succeed_symbol(), succeed_message("Valid config."));
40 + let remote_url = RemoteUrl::from(repo_name);
41 +
42 + Ok(Config {
43 + remote_url,
44 + name: repo_name.to_owned(),
45 + })
46 +}
47
37 - Ok(repo_name.to_string())
48 +pub struct Config {
49 + pub remote_url: RemoteUrl,
50 + pub name: String,
51 +}
52 +
53 +impl Config {
54 + pub fn credentials(
55 + &self,
56 + ) -> impl FnMut(&str, Option<&str>, CredentialType) -> Result<Cred, Error> + '_ {
57 + move |_url, _username_from_url, _allowed_types| {
58 + Cred::ssh_key("git", None, Path::new(&self.ssh_key_path()), None)
59 + }
60 + }
61 +
62 + fn ssh_key_path(&self) -> String {
63 + // Use the dirs crate to get the home directory
64 + let home = dirs::home_dir().expect("Could not determine home directory");
65 + let key_path = home
66 + .join(".ssh")
67 + .join(format!("id_{}@smbcloud.xyz", self.name));
68 + let key_path_str = key_path.to_string_lossy().to_string();
69 + println!("Key path: {}", key_path_str);
70 + key_path_str
71 + }
72 }
crates/cli/src/deploy/git.rs
+6 -2
@@ -4,7 +4,10 @@ use console::style;
4 use git2::{Remote, Repository};
5 use spinners::Spinner;
6
7 -pub async fn remote_deployment_setup(repo: &Repository, repo_name: String) -> Result<Remote> {
7 +pub async fn remote_deployment_setup<'a>(
8 + repo: &'a Repository,
9 + repo_name: &'a str,
10 +) -> Result<Remote<'a>> {
11 let mut spinner = Spinner::new(
12 spinners::Spinners::SimpleDotsScrolling,
13 style("Setting up remote deployment...")
@@ -21,9 +24,10 @@ pub async fn remote_deployment_setup(repo: &Repository, repo_name: String) -> Re
24 fail_message("Remote deployment is not setup. Will setup remote deployment."),
25 );
26 // Present the user with a message to setup remote deployment
27 + let remote_format = format!("{}.git", repo_name);
28 repo.remote(
29 "smbcloud",
26 - &format!("deploy@api.smbcloud.xyz:git/{}", repo_name),
30 + &format!("git@{}:{}", remote_format, remote_format),
31 )
32 .map_err(|e: git2::Error| {
33 spinner.stop_and_persist(&fail_symbol(), e.to_string());
crates/cli/src/deploy/mod.rs
+48 -196
@@ -1,236 +1,88 @@
1 +mod config;
2 mod git;
2 -mod smb_config;
3 +mod remote_url;
4
5 use crate::{
6 account::lib::protected_request,
7 cli::CommandResult,
7 - ui::{fail_message, fail_symbol, succeed_message, succeed_symbol},
8 + ui::{fail_message, succeed_message, succeed_symbol},
9 };
9 -use anyhow::Result;
10 -use console::style;
10 +use anyhow::{anyhow, Result};
11 +use config::check_config;
12 use git::remote_deployment_setup;
12 -use git2::{Cred, PushOptions, RemoteCallbacks, Repository};
13 -use git_url_parse::{GitUrl, Scheme};
14 -use indicatif::{ProgressBar, ProgressState, ProgressStyle};
15 -use smb_config::check_config;
13 +use git2::{PushOptions, RemoteCallbacks, Repository};
14 use smbcloud_networking::environment::Environment;
15 use spinners::Spinner;
18 -use ssh2_config::{ParseRule, SshConfig};
19 -use std::{cmp::min, fmt::Write, fs::File, io::BufReader};
16
17 pub async fn process_deploy(env: Environment) -> Result<CommandResult> {
18 protected_request(env).await?;
23 - let repo_name = check_config().await?;
24 -
25 - let mut spinner = Spinner::new(
26 - spinners::Spinners::SimpleDotsScrolling,
27 - style("Deploying...").green().bold().to_string(),
28 - );
19 + let config = check_config().await?;
20
21 let repo = match Repository::open(".") {
22 Ok(repo) => repo,
23 Err(_) => {
33 - spinner.stop_and_persist("😩", "No git repository found.".to_owned());
34 - return Ok(CommandResult {
35 - spinner,
36 - symbol: fail_symbol(),
37 - msg: fail_message("No git repository found. Init with `git init` command."),
38 - });
24 + return Err(anyhow!(fail_message(
25 + "No git repository found. Init with `git init` command."
26 + )))
27 }
28 };
29
30 let _main_branch = match repo.head() {
31 Ok(branch) => branch,
32 Err(_) => {
45 - spinner.stop_and_persist("😩", "No main branch found.".to_owned());
46 - return Ok(CommandResult {
47 - spinner,
48 - symbol: fail_symbol(),
49 - msg: fail_message(
50 - "No main branch found. Create with `git checkout -b <branch>` command.",
51 - ),
52 - });
53 - }
54 - };
55 - let mut origin = remote_deployment_setup(&repo, repo_name).await?;
56 - let remote_url = match origin.url() {
57 - Some(url) => url,
58 - None => {
59 - spinner.stop_and_persist("😩", "No remote URL found.".to_owned());
60 - return Ok(CommandResult {
61 - spinner,
62 - symbol: fail_symbol(),
63 - msg: fail_message(
64 - "No remote URL found. Add with `git remote add origin <url>` command.",
65 - ),
66 - });
67 - }
68 - };
69 - //println!("Remote URL: {:#?}", remote_url);
70 - let parsed_url = match GitUrl::parse(remote_url) {
71 - Ok(url) => url,
72 - Err(e) => {
73 - spinner.stop_and_persist("😩", e.to_string());
74 - return Ok(CommandResult {
75 - spinner,
76 - symbol: fail_symbol(),
77 - msg: "Invalid remote URL.".to_owned(),
78 - });
79 - }
80 - };
81 - //println!("Parsed URL: {:#?}", parsed_url);
82 - match parsed_url.scheme {
83 - Scheme::Ssh => {
84 - // println!("SSH URL: {:#?}", parsed_url);
85 - }
86 - _ => {
87 - // Only support ssh for now
88 - return Ok(CommandResult {
89 - spinner,
90 - symbol: fail_symbol(),
91 - msg: fail_message("Only ssh is supported."),
92 - });
93 - }
94 - };
95 -
96 - // Get ssh config from host
97 - let host = match parsed_url.host {
98 - Some(host) => host,
99 - None => {
100 - spinner.stop_and_persist("😩", "No host found.".to_owned());
101 - return Ok(CommandResult {
102 - spinner,
103 - symbol: fail_symbol(),
104 - msg: fail_message("No host found."),
105 - });
33 + return Err(anyhow!(fail_message(
34 + "No main branch found. Create with `git checkout -b <branch>` command."
35 + )))
36 }
37 };
38
109 - // get ssh_config
110 - let ssh_config_file = match home::home_dir() {
111 - Some(home) => {
112 - let ssh_config_path = home.join(".ssh/config");
113 - if ssh_config_path.exists() {
114 - // println!("SSH config path: {:#?}", ssh_config_path);
115 - // Open the file and read it
116 - File::open(ssh_config_path.clone()).expect("Unable to open ssh config file")
117 - } else {
118 - spinner.stop_and_persist("😩", "No ssh config found.".to_owned());
119 - return Ok(CommandResult {
120 - spinner,
121 - symbol: fail_symbol(),
122 - msg: fail_message("No ssh config found."),
123 - });
124 - }
125 - }
126 - None => {
127 - spinner.stop_and_persist("😩", "No home".to_owned());
128 - return Ok(CommandResult {
129 - spinner,
130 - symbol: "😩".to_owned(),
131 - msg: "No home directory found.".to_owned(),
132 - });
133 - }
134 - };
135 - //println!("SSH config path: {:#?}", ssh_config_file);
136 - let mut reader = BufReader::new(ssh_config_file);
137 - let config = SshConfig::default()
138 - .parse(&mut reader, ParseRule::STRICT)
139 - .expect("Failed to parse ssh config file");
140 - //println!("SSH config: {:#?}", config);
39 + let mut origin = remote_deployment_setup(&repo, &config.name).await?;
40
142 - // Get the host config
143 - let host_config = config.query(host);
144 - //println!("Host config: {:#?}", host_config);
145 - // get identity_file
146 - let identity_files = match host_config.identity_file {
147 - Some(identity_files) => {
148 - //println!("Identity file: {:#?}", identity_files);
149 - identity_files
150 - }
151 - None => {
152 - spinner.stop_and_persist("😩", "No identity file found.".to_owned());
153 - return Ok(CommandResult {
154 - spinner,
155 - symbol: "😩".to_owned(),
156 - msg: "No identity files found.".to_owned(),
157 - });
158 - }
159 - };
160 - // println!("Identity files: {:#?}", identity_files);
161 - // get identity_file
162 - let identity_file = match identity_files.first() {
163 - Some(identity_file) => {
164 - //println!("Identity file: {:#?}", identity_file);
165 - identity_file
166 - }
167 - None => {
168 - spinner.stop_and_persist("😩", "No identity file found.".to_owned());
169 - return Ok(CommandResult {
170 - spinner,
171 - symbol: "😩".to_owned(),
172 - msg: "No identity file found.".to_owned(),
173 - });
174 - }
175 - };
41 let mut push_opts = PushOptions::new();
42 let mut callbacks = RemoteCallbacks::new();
43 // Set the credentials
179 - callbacks.credentials(|_url, _username_from_url, _allowed_types| {
180 - Cred::ssh_key("deploy", None, identity_file, None)
181 - });
182 - callbacks.push_transfer_progress(|current, total, bytes| {
183 - // println!("push_transfer_progress: current -> {}, total -> {}, bytes -> {}", current, total, bytes);
184 - let pb = ProgressBar::new(total.try_into().unwrap());
185 - pb.set_style(ProgressStyle::with_template("{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {bytes}/{total_bytes} ({eta})")
186 - .unwrap()
187 - .with_key("eta", |state: &ProgressState, w: &mut dyn Write| write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap())
188 - .progress_chars("#>-"));
189 - let mut current = current;
190 - while current < total {
191 - let new = min(current + 223211, total);
192 - current = new;
193 - pb.set_position(new.try_into().unwrap());
44 + callbacks.credentials(config.credentials());
45 + callbacks.sideband_progress(|data| {
46 + // Convert bytes to string, print line by line
47 + if let Ok(text) = std::str::from_utf8(data) {
48 + for line in text.lines() {
49 + if line.contains("> next build") {
50 + println!(
51 + "{} {}",
52 + succeed_message("Building the app. "),
53 + succeed_symbol(),
54 + );
55 + }
56 + if line.contains("Start hagerstenstreetcut") {
57 + println!(
58 + "{} {}",
59 + succeed_message("Restarting the server."),
60 + succeed_symbol(),
61 + );
62 + }
63 + }
64 }
195 -
196 - pb.finish_with_message("downloaded");
197 - });
198 - callbacks.pack_progress(|stage, current, total| {
199 - println!("pack_progress: current -> {}, total -> {}", current, total);
200 - });
201 - callbacks.sideband_progress(|x| {
202 - //println!("{x}");
203 - return true;
65 + true // continue receiving.
66 });
67 callbacks.push_update_reference(|x, y| match y {
68 Some(e) => {
69 println!("{}", e);
70 Ok(())
71 }
210 - None => {
211 - println!("Done");
212 - Ok(())
213 - }
72 + None => Ok(()),
73 });
74 push_opts.remote_callbacks(callbacks);
75
217 - return match origin.push(&["refs/heads/main:refs/heads/main"], Some(&mut push_opts)) {
218 - Ok(_) => {
219 - println!("Succeed!!!");
220 - Ok(CommandResult {
221 - spinner,
222 - symbol: succeed_symbol(),
223 - msg: succeed_message("Your app has been deployed successfully."),
224 - })
225 - }
226 - Err(e) => {
227 - println!("Failed to push to remote: {:#?}", e);
228 - spinner.stop_and_persist("😩", e.to_string());
229 - Ok(CommandResult {
230 - spinner,
231 - symbol: fail_symbol(),
232 - msg: fail_message(&e.to_string()),
233 - })
234 - }
235 - };
76 + let spinner = Spinner::new(
77 + spinners::Spinners::Hamburger,
78 + succeed_message("Deploying: "),
79 + );
80 + match origin.push(&["refs/heads/main:refs/heads/main"], Some(&mut push_opts)) {
81 + Ok(_) => Ok(CommandResult {
82 + spinner,
83 + symbol: succeed_symbol(),
84 + msg: succeed_message("Your app has been successfully deployed."),
85 + }),
86 + Err(e) => Err(anyhow!(fail_message(&e.to_string()))),
87 + }
88 }
crates/cli/src/deploy/remote_url.rs new
+80
@@ -0,0 +1,80 @@
1 +use thiserror::Error;
2 +
3 +pub struct RemoteUrl {
4 + /// The git user. Should be git.
5 + pub user: String,
6 + /// The custom hostname ends with .git.
7 + pub host: String,
8 + /// The path name of the repo, also ends with .git.
9 + pub path: String,
10 +}
11 +
12 +impl RemoteUrl {
13 + pub fn from(repo_name: &str) -> Self {
14 + // Present the user with a message to setup remote deployment
15 + let remote_format = format!("{}.git", repo_name);
16 + RemoteUrl {
17 + host: "api.smbcloud.xyz".to_owned(),
18 + path: remote_format,
19 + user: "git".to_owned(),
20 + }
21 + }
22 +
23 + /// Returns a `Result<GitUrl>` after normalizing and parsing `url` for metadata
24 + pub fn parse(url: &str) -> Result<RemoteUrl, RemoteUrlParseError> {
25 + let segments = url.split(':').collect::<Vec<&str>>();
26 + // Should have two elements
27 + if segments.len() != 2 {
28 + return Err(RemoteUrlParseError::ParseError);
29 + }
30 + let path = match segments.last() {
31 + Some(path) => path.to_owned(),
32 + None => return Err(RemoteUrlParseError::MissingPath),
33 + };
34 + let (user, host) = match segments.first() {
35 + Some(user_host) => parse_user_host(user_host)?,
36 + None => return Err(RemoteUrlParseError::MissingUserHost),
37 + };
38 +
39 + if host != path {
40 + return Err(RemoteUrlParseError::ParseError);
41 + }
42 +
43 + Ok(RemoteUrl {
44 + host: host.to_owned(),
45 + user: user.to_owned(),
46 + path: path.to_owned(),
47 + })
48 + }
49 +}
50 +
51 +fn parse_user_host(s: &str) -> Result<(&str, &str), RemoteUrlParseError> {
52 + let segments = s.split("@").collect::<Vec<&str>>();
53 + // Should have two elements
54 + if segments.len() != 2 {
55 + return Err(RemoteUrlParseError::MissingUserHost);
56 + }
57 + let user = match segments.first() {
58 + Some(user) => user.to_owned(),
59 + None => return Err(RemoteUrlParseError::MissingUser),
60 + };
61 + let host = match segments.last() {
62 + Some(host) => host.to_owned(),
63 + None => return Err(RemoteUrlParseError::MissingHost),
64 + };
65 + Ok((user, host))
66 +}
67 +
68 +#[derive(Error, Debug, PartialEq, Eq)]
69 +pub enum RemoteUrlParseError {
70 + #[error("Git remote url format is invalid. It should be git@projectname.git:projectname.git")]
71 + ParseError,
72 + #[error("Path should be present.")]
73 + MissingPath,
74 + #[error("User and host should be present.")]
75 + MissingUserHost,
76 + #[error("Hostname should be present.")]
77 + MissingHost,
78 + #[error("User should be present.")]
79 + MissingUser,
80 +}