Skip to main content

infinity_build_js/
config.rs

1use 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    BaseInstrument {
68        #[serde(default)]
69        file_name: Option<String>,
70
71        /// Required. Must match `BaseInstrument.templateID()`.
72        template_id: String,
73        /// Required. Must match the ID passed to `FSComponent.render()`.
74        mount_element_id: String,
75
76        #[serde(default)]
77        imports: Vec<String>,
78
79        #[serde(default)]
80        html_template: Option<PathBuf>,
81    },
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum SimulatorPackageKind {
86    React,
87    BaseInstrument,
88}
89
90fn default_true() -> bool {
91    true
92}
93
94impl SimulatorPackage {
95    pub fn kind(&self) -> SimulatorPackageKind {
96        match self {
97            SimulatorPackage::React { .. } => SimulatorPackageKind::React,
98            SimulatorPackage::BaseInstrument { .. } => SimulatorPackageKind::BaseInstrument,
99        }
100    }
101
102    pub fn file_name(&self) -> &str {
103        match self {
104            SimulatorPackage::React { file_name, .. }
105            | SimulatorPackage::BaseInstrument { file_name, .. } => {
106                file_name.as_deref().unwrap_or("instrument")
107            }
108        }
109    }
110
111    pub fn imports(&self) -> &[String] {
112        match self {
113            SimulatorPackage::React { imports, .. }
114            | SimulatorPackage::BaseInstrument { imports, .. } => imports,
115        }
116    }
117}
118
119/// Strip the Windows `\\?\` verbatim prefix from a canonicalized path.
120/// Rolldown/oxc_resolver treats verbatim paths as unresolvable URL-like
121/// strings (`//?/C:/...`), so we hand them plain drive-letter paths.
122#[cfg(windows)]
123fn strip_verbatim_prefix(path: PathBuf) -> PathBuf {
124    let s = path.as_os_str().to_string_lossy();
125    if let Some(rest) = s.strip_prefix(r"\\?\") {
126        // Keep UNC shares as `\\server\share\...`
127        if let Some(unc) = rest.strip_prefix(r"UNC\") {
128            return PathBuf::from(format!(r"\\{unc}"));
129        }
130        return PathBuf::from(rest.to_string());
131    }
132    path
133}
134
135#[cfg(not(windows))]
136fn strip_verbatim_prefix(path: PathBuf) -> PathBuf {
137    path
138}
139
140impl Instrument {
141    pub fn resolved_index(&self, project_root: &Path) -> BuildResult<PathBuf> {
142        let abs = project_root.join(&self.index);
143        let canonical = std::fs::canonicalize(&abs)
144            .map_err(|e| BuildError::invalid_path(abs, format!("entrypoint not found: {e}")))?;
145        Ok(strip_verbatim_prefix(canonical))
146    }
147}