oxyde_cloud_deploy/
deploy.rs1use cargo_leptos::config::Opts;
2use oxyde_cloud_client::{Client, ReqwestJsonError, UploadFileError};
3use oxyde_cloud_common::config::CloudConfig;
4use std::env::VarError;
5use std::ffi::OsStr;
6use std::fs::read_dir;
7use std::path::{Path, PathBuf};
8use thiserror::Error;
9use walkdir::WalkDir;
10
11#[derive(Debug, Error)]
12pub enum Error {
13 #[error("Build error: {0}")]
14 Build(#[from] anyhow::Error),
15
16 #[error("IO error: {0}")]
17 Io(#[from] std::io::Error),
18
19 #[error("Check Name error: {0}")]
20 CheckName(#[from] ReqwestJsonError),
21
22 #[error("Upload error: {0}")]
23 Upload(#[from] UploadFileError),
24
25 #[error("Deployment done error: {0}")]
26 Done(#[from] reqwest::Error),
27
28 #[error("Error reading variable `OXYDE_CLOUD_API_KEY`: {0}")]
29 ApiKeyEnv(#[from] VarError),
30
31 #[error("Config loading error: {0}")]
32 Config(#[from] oxyde_cloud_common::config::Error),
33}
34
35pub async fn deploy_with_config_file(
36 config: &PathBuf,
37 cargo_leptos_opts: Opts,
38) -> Result<(), Error> {
39 let config = CloudConfig::load(&config).await?;
40 deploy(&config, cargo_leptos_opts).await?;
41 Ok(())
42}
43
44pub async fn deploy(config: &CloudConfig, cargo_leptos_opts: Opts) -> Result<(), Error> {
45 crate::build::build(cargo_leptos_opts.clone()).await.map_err(Error::Build)?;
46
47 let target_dir = "target";
48 let target_bin_dir = "target/x86_64-unknown-linux-musl";
49
50 let server_bin_dir = if cargo_leptos_opts.release {
51 "release"
52 } else {
53 "debug"
54 };
55 let frontend_dir = "site";
56
57 let api_key = std::env::var("OXYDE_CLOUD_API_KEY")?;
58 let client = Client::new(api_key.clone());
59
60 let frontend_path = Path::new(target_dir).join(frontend_dir);
61 let server_path = Path::new(target_bin_dir).join(server_bin_dir);
62
63 let mut files = recursive_files_from_dir(frontend_path);
64 files.append(&mut server_files(server_path)?);
65
66 log::debug!(target:"cargo_leptos", "Found files: {:#?}", files);
67
68 log::info!(target:"cargo_leptos", "Deploying app {}", config.app.slug);
69
70 if let Err(err) = deploy_inner(config, client, &mut files).await {
71 log::error!(target:"cargo_leptos", "Deploy failed: {:?}", err);
72 return Err(err);
73 }
74
75 log::info!(target:"cargo_leptos", "Deployed app to {}", config.deployed_url());
76
77 Ok(())
78}
79
80async fn deploy_inner(
81 config: &CloudConfig,
82 client: Client,
83 files: &mut Vec<PathBuf>,
84) -> Result<(), Error> {
85 for file in files {
86 log::debug!(target:"cargo_leptos", "Uploading {}...", file.display());
87 client.clone().upload_file(&config.app.slug, file).await?;
88 }
89
90 log::debug!(target:"cargo_leptos", "Deploying app...");
91 client.upload_done(config).await?;
92
93 Ok(())
94}
95
96fn recursive_files_from_dir(dir: impl AsRef<Path>) -> Vec<PathBuf> {
97 WalkDir::new(dir)
98 .into_iter()
99 .filter_map(|e| e.ok().map(|e| e.into_path()))
100 .filter_map(|e| if e.is_file() { Some(e) } else { None })
101 .collect()
102}
103
104fn server_files(dir: impl AsRef<Path>) -> std::io::Result<Vec<PathBuf>> {
105 let starts_with_a_dot = |path: &Path| {
106 path.file_name().expect("cant read filename").to_str().expect("cant convert filename").starts_with(".")
107 };
108
109 Ok(read_dir(dir)?
110 .filter_map(|d| {
111 d.ok().and_then(|e| {
112 let path = e.path();
113 if path.is_file() && path.extension() == None && !starts_with_a_dot(&path) {
114 Some(path)
115 } else {
116 None
117 }
118 })
119 })
120 .collect())
121}