use std::collections::HashMap;
use std::path::{Path, PathBuf};
use anyhow::Result;
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub struct CompiledOutput {
pub html: String,
pub assets: HashMap<String, Vec<u8>>,
pub actions: Vec<ActionHandler>,
pub meta: ProjectMeta,
}
#[derive(Debug, Clone)]
pub struct ActionHandler {
pub route: String,
pub method: String,
pub node_id: String,
pub handler_code: String,
}
#[derive(Debug, Clone, Default)]
pub struct ProjectMeta {
pub name: String,
pub domain: Option<String>,
pub env_vars: HashMap<String, String>,
}
#[derive(Debug)]
pub struct Bundle {
pub output_dir: PathBuf,
pub files: HashMap<PathBuf, Vec<u8>>,
pub summary: String,
}
impl Bundle {
pub fn write_to_disk(&self) -> Result<()> {
for (rel_path, content) in &self.files {
let full_path = self.output_dir.join(rel_path);
if let Some(parent) = full_path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(&full_path, content)?;
}
Ok(())
}
pub fn total_size(&self) -> usize {
self.files.values().map(|v| v.len()).sum()
}
}
#[derive(Debug)]
pub struct DeployResult {
pub url: Option<String>,
pub deployment_id: Option<String>,
pub message: String,
}
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
pub struct DeployConfig {
#[serde(default)]
pub adapter: String,
pub domain: Option<String>,
#[serde(default)]
pub env: HashMap<String, String>,
#[serde(default)]
pub settings: HashMap<String, String>,
}
pub fn load_config(project_dir: &Path) -> Result<DeployConfig> {
let config_path = project_dir.join(".voce/config.toml");
if config_path.exists() {
let content = std::fs::read_to_string(&config_path)?;
let config: DeployConfig = toml::from_str(&content)?;
Ok(config)
} else {
Ok(DeployConfig::default())
}
}
pub trait Adapter {
fn name(&self) -> &str;
fn prepare(&self, compiled: &CompiledOutput, config: &DeployConfig) -> Result<Bundle>;
fn deploy(&self, bundle: &Bundle, config: &DeployConfig) -> Result<DeployResult>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bundle_total_size() {
let mut files = HashMap::new();
files.insert(PathBuf::from("index.html"), vec![0u8; 100]);
files.insert(PathBuf::from("style.css"), vec![0u8; 50]);
let bundle = Bundle {
output_dir: PathBuf::from("dist"),
files,
summary: "test".to_string(),
};
assert_eq!(bundle.total_size(), 150);
}
#[test]
fn default_config() {
let config = DeployConfig::default();
assert!(config.adapter.is_empty());
assert!(config.domain.is_none());
}
}