infinity_build_js/
config.rs1use infinity_build_core::{BuildError, BuildResult};
2use serde::{Deserialize, Serialize};
3use std::path::{Path, PathBuf};
4
5#[derive(Debug, Clone, Deserialize, Serialize)]
6pub struct JsBuildConfig {
7 #[serde(flatten)]
8 pub package: PackageSpec,
9
10 #[serde(default)]
11 pub instruments: Vec<Instrument>,
12}
13
14#[derive(Debug, Clone, Deserialize, Serialize)]
15pub struct PackageSpec {
16 pub package_name: String,
17
18 #[serde(default = "default_package_dir")]
19 pub package_dir: PathBuf,
20}
21
22fn default_package_dir() -> PathBuf {
23 PathBuf::from("PackageSources")
24}
25
26#[derive(Debug, Clone, Deserialize, Serialize)]
27pub struct Instrument {
28 pub name: String,
29 pub index: PathBuf,
30
31 #[serde(default)]
32 pub simulator_package: Option<SimulatorPackage>,
33
34 #[serde(default)]
35 pub modules: Vec<ModuleAlias>,
36}
37
38#[derive(Debug, Clone, Deserialize, Serialize)]
39pub struct ModuleAlias {
40 pub resolve: String,
41 pub index: PathBuf,
42}
43
44#[derive(Debug, Clone, Deserialize, Serialize)]
45#[serde(tag = "type", rename_all = "camelCase")]
46pub enum SimulatorPackage {
47 React {
48 #[serde(default)]
49 file_name: Option<String>,
50
51 #[serde(default)]
52 template_id: Option<String>,
53
54 #[serde(default = "default_true")]
55 is_interactive: bool,
56
57 #[serde(default)]
58 imports: Vec<String>,
59
60 #[serde(default)]
61 html_template: Option<PathBuf>,
62
63 #[serde(default)]
64 js_template: Option<PathBuf>,
65 },
66
67 RescriptReact {
71 #[serde(default)]
72 file_name: Option<String>,
73
74 #[serde(default)]
75 template_id: Option<String>,
76
77 #[serde(default = "default_true")]
78 is_interactive: bool,
79
80 #[serde(default)]
81 imports: Vec<String>,
82
83 #[serde(default)]
84 html_template: Option<PathBuf>,
85
86 #[serde(default)]
87 js_template: Option<PathBuf>,
88
89 #[serde(default)]
93 build_command: Option<String>,
94
95 #[serde(default)]
98 build_dir: Option<PathBuf>,
99 },
100
101 BaseInstrument {
102 #[serde(default)]
103 file_name: Option<String>,
104
105 template_id: String,
107 mount_element_id: String,
109
110 #[serde(default)]
111 imports: Vec<String>,
112
113 #[serde(default)]
114 html_template: Option<PathBuf>,
115 },
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq)]
119pub enum SimulatorPackageKind {
120 React,
121 RescriptReact,
122 BaseInstrument,
123}
124
125fn default_true() -> bool {
126 true
127}
128
129impl SimulatorPackage {
130 pub fn kind(&self) -> SimulatorPackageKind {
131 match self {
132 SimulatorPackage::React { .. } => SimulatorPackageKind::React,
133 SimulatorPackage::RescriptReact { .. } => SimulatorPackageKind::RescriptReact,
134 SimulatorPackage::BaseInstrument { .. } => SimulatorPackageKind::BaseInstrument,
135 }
136 }
137
138 pub fn file_name(&self) -> &str {
139 match self {
140 SimulatorPackage::React { file_name, .. }
141 | SimulatorPackage::RescriptReact { file_name, .. }
142 | SimulatorPackage::BaseInstrument { file_name, .. } => {
143 file_name.as_deref().unwrap_or("instrument")
144 }
145 }
146 }
147
148 pub fn imports(&self) -> &[String] {
149 match self {
150 SimulatorPackage::React { imports, .. }
151 | SimulatorPackage::RescriptReact { imports, .. }
152 | SimulatorPackage::BaseInstrument { imports, .. } => imports,
153 }
154 }
155
156 pub fn is_interactive(&self) -> bool {
160 match self {
161 SimulatorPackage::React { is_interactive, .. }
162 | SimulatorPackage::RescriptReact { is_interactive, .. } => *is_interactive,
163 SimulatorPackage::BaseInstrument { .. } => true,
164 }
165 }
166}
167
168#[cfg(windows)]
172fn strip_verbatim_prefix(path: PathBuf) -> PathBuf {
173 let s = path.as_os_str().to_string_lossy();
174 if let Some(rest) = s.strip_prefix(r"\\?\") {
175 if let Some(unc) = rest.strip_prefix(r"UNC\") {
177 return PathBuf::from(format!(r"\\{unc}"));
178 }
179 return PathBuf::from(rest.to_string());
180 }
181 path
182}
183
184#[cfg(not(windows))]
185fn strip_verbatim_prefix(path: PathBuf) -> PathBuf {
186 path
187}
188
189impl Instrument {
190 pub fn resolved_index(&self, project_root: &Path) -> BuildResult<PathBuf> {
191 let abs = project_root.join(&self.index);
192 let canonical = std::fs::canonicalize(&abs)
193 .map_err(|e| BuildError::invalid_path(abs, format!("entrypoint not found: {e}")))?;
194 Ok(strip_verbatim_prefix(canonical))
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn deserializes_rescript_react_package() {
204 let raw = r#"
205 name = "PFD"
206 index = "src/Main.res.mjs"
207
208 [simulator_package]
209 type = "rescriptReact"
210 template_id = "PFD"
211 build_command = "bun run build"
212 build_dir = "ui"
213 "#;
214
215 let instrument: Instrument = toml::from_str(raw).unwrap();
216 match instrument.simulator_package.unwrap() {
217 SimulatorPackage::RescriptReact {
218 template_id,
219 build_command,
220 build_dir,
221 ..
222 } => {
223 assert_eq!(template_id.as_deref(), Some("PFD"));
224 assert_eq!(build_command.as_deref(), Some("bun run build"));
225 assert_eq!(build_dir.as_deref(), Some(Path::new("ui")));
226 }
227 other => panic!("expected RescriptReact, got {other:?}"),
228 }
229 }
230}