oxyde_cloud_deploy/
deploy.rs

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