1use std::collections::HashMap;
7use std::path::{Path, PathBuf};
8
9use anyhow::Result;
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug)]
14pub struct CompiledOutput {
15 pub html: String,
17 pub assets: HashMap<String, Vec<u8>>,
19 pub actions: Vec<ActionHandler>,
21 pub meta: ProjectMeta,
23}
24
25#[derive(Debug, Clone)]
27pub struct ActionHandler {
28 pub route: String,
30 pub method: String,
32 pub node_id: String,
34 pub handler_code: String,
36}
37
38#[derive(Debug, Clone, Default)]
40pub struct ProjectMeta {
41 pub name: String,
43 pub domain: Option<String>,
45 pub env_vars: HashMap<String, String>,
47}
48
49#[derive(Debug)]
51pub struct Bundle {
52 pub output_dir: PathBuf,
54 pub files: HashMap<PathBuf, Vec<u8>>,
56 pub summary: String,
58}
59
60impl Bundle {
61 pub fn write_to_disk(&self) -> Result<()> {
63 for (rel_path, content) in &self.files {
64 let full_path = self.output_dir.join(rel_path);
65 if let Some(parent) = full_path.parent() {
66 std::fs::create_dir_all(parent)?;
67 }
68 std::fs::write(&full_path, content)?;
69 }
70 Ok(())
71 }
72
73 pub fn total_size(&self) -> usize {
75 self.files.values().map(|v| v.len()).sum()
76 }
77}
78
79#[derive(Debug)]
81pub struct DeployResult {
82 pub url: Option<String>,
84 pub deployment_id: Option<String>,
86 pub message: String,
88}
89
90#[derive(Debug, Clone, Deserialize, Serialize, Default)]
92pub struct DeployConfig {
93 #[serde(default)]
95 pub adapter: String,
96 pub domain: Option<String>,
98 #[serde(default)]
100 pub env: HashMap<String, String>,
101 #[serde(default)]
103 pub settings: HashMap<String, String>,
104}
105
106pub fn load_config(project_dir: &Path) -> Result<DeployConfig> {
108 let config_path = project_dir.join(".voce/config.toml");
109 if config_path.exists() {
110 let content = std::fs::read_to_string(&config_path)?;
111 let config: DeployConfig = toml::from_str(&content)?;
112 Ok(config)
113 } else {
114 Ok(DeployConfig::default())
115 }
116}
117
118pub trait Adapter {
120 fn name(&self) -> &str;
122
123 fn prepare(&self, compiled: &CompiledOutput, config: &DeployConfig) -> Result<Bundle>;
125
126 fn deploy(&self, bundle: &Bundle, config: &DeployConfig) -> Result<DeployResult>;
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[test]
136 fn bundle_total_size() {
137 let mut files = HashMap::new();
138 files.insert(PathBuf::from("index.html"), vec![0u8; 100]);
139 files.insert(PathBuf::from("style.css"), vec![0u8; 50]);
140 let bundle = Bundle {
141 output_dir: PathBuf::from("dist"),
142 files,
143 summary: "test".to_string(),
144 };
145 assert_eq!(bundle.total_size(), 150);
146 }
147
148 #[test]
149 fn default_config() {
150 let config = DeployConfig::default();
151 assert!(config.adapter.is_empty());
152 assert!(config.domain.is_none());
153 }
154}