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