prest_build/
lib.rs

1use anyhow::Result;
2use serde_json;
3
4#[cfg(feature = "pwa")]
5mod pwa;
6#[cfg(feature = "pwa")]
7pub use pwa::*;
8
9#[cfg(feature = "typescript")]
10mod swc_bundler;
11#[cfg(feature = "typescript")]
12mod typescript {
13    pub fn bundle_ts() {
14        // Check if we're in a publishing context
15        let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_default();
16        println!("cargo:warning=DEBUG: CARGO_MANIFEST_DIR = {}", manifest_dir);
17        
18        let is_publishing = std::env::var("CARGO_PUBLISH").is_ok() 
19            || std::env::var("DOCS_RS").is_ok()
20            || std::env::var("PREST_OFFLINE").is_ok()
21            || manifest_dir.contains("/target/package/"); // Detect if we're in a cargo package directory
22        
23        println!("cargo:warning=DEBUG: is_publishing = {}", is_publishing);
24        println!("cargo:warning=DEBUG: CARGO_PUBLISH = {:?}", std::env::var("CARGO_PUBLISH"));
25        println!("cargo:warning=DEBUG: DOCS_RS = {:?}", std::env::var("DOCS_RS"));
26        println!("cargo:warning=DEBUG: PREST_OFFLINE = {:?}", std::env::var("PREST_OFFLINE"));
27        println!("cargo:warning=DEBUG: manifest_dir.contains('/target/package/') = {}", manifest_dir.contains("/target/package/"));
28            
29        let (exports, custom_node_modules_path) = if is_publishing {
30            // In publishing mode, use OUT_DIR for node_modules
31            let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR should be set in build context");
32            println!("cargo:warning=Publishing mode detected. Using OUT_DIR for NPM packages: {}", out_dir);
33            
34            // Try to use pre-built exports or provide minimal fallback
35            let exports = match std::env::var("PREST_PREBUILT_EXPORTS") {
36                Ok(exports_json) => {
37                    serde_json::from_str(&exports_json).unwrap_or_else(|_| {
38                        println!("cargo:warning=Failed to parse PREST_PREBUILT_EXPORTS, using minimal fallback");
39                        create_minimal_exports()
40                    })
41                },
42                Err(_) => {
43                    // Try to download to OUT_DIR
44                    match prest_npm::run_with_path(&out_dir) {
45                        Ok(exports) => exports,
46                        Err(e) => {
47                            println!("cargo:warning=NPM download failed in publishing mode: {}", e);
48                            println!("cargo:warning=Using minimal fallback exports");
49                            create_minimal_exports()
50                        }
51                    }
52                }
53            };
54            (exports, Some(out_dir))
55        } else {
56            // In development mode, use standard path
57            println!("cargo:warning=Development mode detected. Using standard NPM path");
58            (prest_npm::run().unwrap(), None)
59        };
60        
61        for (name, path) in exports {
62            let result = if let Some(custom_path) = &custom_node_modules_path {
63                crate::swc_bundler::bundle_js_with_node_modules(&path, &name, Some(custom_path))
64            } else {
65                crate::swc_bundler::bundle_js(&path, &name)
66            };
67            
68            if let Err(e) = result {
69                println!("cargo:warning=Failed to bundle {}: {}", name, e);
70            }
71        }
72    }
73    
74    fn create_minimal_exports() -> std::collections::HashMap<String, String> {
75        let mut exports = std::collections::HashMap::new();
76        
77        // Provide minimal fallback paths for essential packages
78        // These would be pre-built and included in the repository
79        exports.insert("htmx".to_string(), "ui/preset.ts".to_string());
80        
81        exports
82    }
83}
84#[cfg(feature = "typescript")]
85pub use typescript::bundle_ts;
86
87#[cfg(feature = "sass")]
88mod sass {
89    use std::{fs::write, path::Path};
90    pub fn bundle_sass(path: &str) -> anyhow::Result<()> {
91        let opts = grass::Options::default().style(grass::OutputStyle::Compressed);
92        let css = grass::from_path(path, &opts)?;
93        let scss_filename = Path::new(path).file_name().unwrap().to_str().unwrap();
94        let css_filename = scss_filename
95            .replace(".scss", ".css")
96            .replace(".sass", ".css");
97        let out_file = super::out_path(&css_filename);
98        write(out_file, css)?;
99        Ok(())
100    }
101}
102#[cfg(feature = "sass")]
103pub use sass::bundle_sass;
104
105pub use cfg_aliases::cfg_aliases;
106
107pub fn default_cfg_aliases() {
108    cfg_aliases! {
109        wasm: { target_arch = "wasm32" },
110        sw: { wasm },
111        not_wasm: { not(wasm) },
112        host: { not_wasm },
113        debug: { debug_assertions },
114        release: { not(debug_assertions) },
115    }
116}
117
118pub fn read_lib_name() -> Result<String> {
119    use toml::{Table, Value};
120    let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")?;
121    let manifest_path = &format!("{manifest_dir}/Cargo.toml");
122    let manifest = std::fs::read_to_string(manifest_path)?;
123    let parsed = manifest.parse::<Table>()?;
124    let lib_name = if parsed.contains_key("lib") {
125        let Value::Table(lib_table) = &parsed["lib"] else {
126            panic!("should be unreachable");
127        };
128        if lib_table.contains_key("name") {
129            lib_table["name"].as_str().unwrap().to_owned()
130        } else {
131            parsed["package"]["name"].as_str().unwrap().to_owned()
132        }
133    } else {
134        parsed["package"]["name"].as_str().unwrap().to_owned()
135    };
136    Ok(lib_name.replace("-", "_"))
137}
138
139/// Utility that attempts to find the path of the current build's target path
140pub fn find_target_dir() -> Option<String> {
141    use std::{ffi::OsStr, path::PathBuf};
142    if let Some(target_dir) = std::env::var_os("CARGO_TARGET_DIR") {
143        let target_dir = PathBuf::from(target_dir);
144        if target_dir.is_absolute() {
145            if let Some(str) = target_dir.to_str() {
146                return Some(str.to_owned());
147            } else {
148                return None;
149            }
150        } else {
151            return None;
152        };
153    }
154
155    let mut dir = PathBuf::from(out_path(""));
156    loop {
157        if dir.join(".rustc_info.json").exists()
158            || dir.join("CACHEDIR.TAG").exists()
159            || dir.file_name() == Some(OsStr::new("target"))
160                && dir
161                    .parent()
162                    .map_or(false, |parent| parent.join("Cargo.toml").exists())
163        {
164            if let Some(str) = dir.to_str() {
165                return Some(str.to_owned());
166            } else {
167                return None;
168            }
169        }
170        if dir.pop() {
171            continue;
172        }
173        return None;
174    }
175}
176
177/// Utility for composition of paths to build artifacts
178pub fn out_path(filename: &str) -> String {
179    let dir = std::env::var("OUT_DIR").unwrap();
180    format!("{dir}/{filename}")
181}