Skip to main content

euv_cli/server/
fn.rs

1use super::*;
2
3/// Sets the global application state.
4///
5/// # Arguments
6///
7/// - `Arc<AppState>` - The shared application state to store globally.
8///
9/// # Returns
10///
11/// - `Result<(), EuvError>` - Indicates success or failure of the initialization.
12pub(crate) fn set_global_state(state: Arc<AppState>) -> Result<(), EuvError> {
13    APP_STATE.set(state).map_err(|_: Arc<AppState>| {
14        EuvError::Message(String::from("Global state already initialized"))
15    })
16}
17
18/// Retrieves the global application state.
19///
20/// # Returns
21///
22/// - `Option<Arc<AppState>>` - The global state if initialized.
23pub(crate) fn get_global_state() -> Option<Arc<AppState>> {
24    APP_STATE.get().cloned()
25}
26
27/// Generates `index.html` based on the build profile.
28///
29/// Uses `INDEX_HTML_RELEASE` when `is_release` is `true` (no live-reload script),
30/// otherwise uses `INDEX_HTML_DEV` (includes live-reload instrumentation).
31///
32/// Then writes the template with the import path placeholder replaced to disk.
33///
34/// # Arguments
35///
36/// - `&HtmlConfig` - The HTML generation configuration.
37///
38/// # Returns
39///
40/// - `Result<String, EuvError>` - The generated HTML content written to disk.
41pub(crate) async fn generate_html(config: &HtmlConfig) -> Result<String, EuvError> {
42    let template_content: String = if let Some(custom_path) = config.try_get_custom_index_html() {
43        let bytes: Vec<u8> =
44            read(custom_path)
45                .await
46                .map_err(|error: io::Error| EuvError::IoPath {
47                    message: String::from("Failed to read custom index.html"),
48                    path: custom_path.to_path_buf(),
49                    error,
50                })?;
51        String::from_utf8(bytes).map_err(|error: FromUtf8Error| EuvError::Utf8 {
52            message: String::from("Custom index.html is not valid UTF-8"),
53            error,
54        })?
55    } else if config.get_is_release() {
56        INDEX_HTML_RELEASE.to_string()
57    } else {
58        INDEX_HTML_DEV.to_string()
59    };
60    let html: String = minify_html_template(
61        &template_content
62            .replace(IMPORT_PATH_PLACEHOLDER, config.get_import_path())
63            .replace(RELOAD_ROUTE_PLACEHOLDER, RELOAD_ROUTE),
64    );
65    let index_path: PathBuf = config.get_serving_root().join(INDEX_HTML_FILE_NAME);
66    create_dir_all(config.get_serving_root())
67        .await
68        .map_err(|error: io::Error| EuvError::Io {
69            message: String::from("Failed to create static directory"),
70            error,
71        })?;
72    write(&index_path, &html)
73        .await
74        .map_err(|error: io::Error| EuvError::Io {
75            message: String::from("Failed to write index.html"),
76            error,
77        })?;
78    Ok(html)
79}
80
81/// Resolves the effective www directory, handling wasm-pack nested output.
82///
83/// # Arguments
84///
85/// - `&Path` - The candidate www directory path.
86///
87/// # Returns
88///
89/// - `PathBuf` - The resolved www directory containing `index.html`.
90pub async fn resolve_www_dir(www_dir: &Path) -> PathBuf {
91    if metadata(www_dir.join(INDEX_HTML_FILE_NAME)).await.is_ok() {
92        return www_dir.to_path_buf();
93    }
94    let parent_name: Option<&str> = www_dir
95        .file_name()
96        .and_then(|file_name_os_str: &ffi::OsStr| file_name_os_str.to_str());
97    if let Some(name) = parent_name {
98        let nested: PathBuf = www_dir.join(name);
99        if metadata(nested.join(INDEX_HTML_FILE_NAME)).await.is_ok() {
100            return nested;
101        }
102    }
103    www_dir.to_path_buf()
104}
105
106/// Resolves the pkg directory for serving WASM artifacts.
107///
108/// Delegates to `resolve_out_dir` which respects `--out-dir`
109/// from wasm-pack args or defaults to `{www_dir}/pkg`.
110///
111/// # Arguments
112///
113/// - `&ModeArgs` - The CLI arguments for resolving out_dir.
114///
115/// # Returns
116///
117/// - `PathBuf` - The resolved pkg directory containing WASM build artifacts.
118pub fn resolve_pkg_dir(args: &ModeArgs) -> PathBuf {
119    resolve_out_dir(args)
120}
121
122/// Resolves a file path within a base directory with path-traversal protection.
123///
124/// Canonicalizes both the file path and the base directory, then verifies
125/// that the canonical file path starts with the canonical base directory.
126///
127/// # Arguments
128///
129/// - `&Path` - The base directory to resolve within.
130/// - `&str` - The relative path to the file.
131///
132/// # Returns
133///
134/// - `Option<PathBuf>` - The resolved file path if valid, `None` otherwise.
135pub async fn resolve_file_in_base(base: &Path, path: &str) -> Option<PathBuf> {
136    let file_path: PathBuf = base.join(path);
137    let canonical_path: PathBuf = canonicalize(&file_path).await.ok()?;
138    let base_canonical: PathBuf = canonicalize(base).await.ok()?;
139    canonical_path
140        .starts_with(&base_canonical)
141        .then_some(file_path)
142}