1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3
4use crate::engine::{collection::Collection, compile::CompileConfig};
5
6#[derive(Deserialize, Serialize, Clone)]
10#[serde(default)]
11pub struct EngineConfig {
12 pub root: PathBuf,
13 pub output_dir: PathBuf,
14 pub output_name: Option<String>,
15 pub output_format: Option<String>,
16 pub clean: bool,
17 pub strict: bool,
18 pub collections: Vec<Collection>,
19 pub include_html: bool,
20 pub cache_enabled: bool,
24
25 #[serde(flatten)]
26 pub compile: CompileConfig,
27}
28
29impl Default for EngineConfig {
30 fn default() -> Self {
31 Self {
32 root: PathBuf::new(),
33 output_dir: PathBuf::new(),
34 output_name: None,
35 output_format: None,
36 clean: false,
37 strict: false,
38 collections: Vec::new(),
39 include_html: false,
40 cache_enabled: true,
41 compile: CompileConfig::default(),
42 }
43 }
44}
45
46impl EngineConfig {
47 pub(crate) fn load(config_path: &PathBuf) -> std::io::Result<EngineConfig> {
50 let ext = config_path.extension().and_then(|s| s.to_str()).unwrap_or("");
51 if matches!(ext, "ts" | "js" | "mjs") {
52 return Self::load_ts(config_path);
53 }
54 let raw = std::fs::read_to_string(config_path)?;
55 let cfg: EngineConfig =
56 toml::from_str(&raw).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
57
58 Ok(cfg)
59 }
60
61 fn load_ts(config: &PathBuf) -> std::io::Result<EngineConfig> {
65 use std::io::Write;
66 let abs = std::fs::canonicalize(config)?;
67 let script = include_str!("../../scripts/load-config.mjs");
68 let mut tmp = tempfile::Builder::new().suffix(".mjs").tempfile()?;
69 tmp.write_all(script.as_bytes())?;
70 tmp.flush()?;
71 let tmp_path = tmp.path().to_path_buf();
72
73 let attempts: &[(&str, &[&str])] = &[("bun", &[]), ("node", &["--import", "tsx"])];
74 let mut last_err: Option<String> = None;
75 for (cmd, prefix_args) in attempts {
76 let mut c = std::process::Command::new(cmd);
77 c.args(*prefix_args).arg(&tmp_path).arg(&abs);
78 match c.output() {
79 Ok(out) if out.status.success() => {
80 let json = String::from_utf8(out.stdout)
81 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
82 let cfg: EngineConfig = serde_json::from_str(&json).map_err(|e| {
83 std::io::Error::new(std::io::ErrorKind::InvalidData, format!("ts config: {e}\n--- output ---\n{json}"))
84 })?;
85 return Ok(cfg);
86 },
87 Ok(out) => {
88 last_err = Some(format!("{cmd} exit {}: {}", out.status, String::from_utf8_lossy(&out.stderr)));
89 },
90 Err(e) => last_err = Some(format!("{cmd}: {e}")),
91 }
92 }
93 Err(std::io::Error::new(
94 std::io::ErrorKind::NotFound,
95 format!("ts config requires `bun` or `node` w/ tsx on PATH ({})", last_err.unwrap_or_default(),),
96 ))
97 }
98}