oxyde_cloud_deploy/
deploy.rs

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