//! build.rs — Static-asset pipeline for OxiCloud //! //! **Release mode** (`cargo build --release`): //! 1. Copies `static/` → `static-dist/` (processed mirror). //! 2. Resolves CSS `@import` chains → flat `main.css`. //! 3. Bundles all index.html CSS/JS → `app.{hash}.css` / `app.{hash}.js`. //! 4. Minifies every `.css` (lightningcss) and `.js` (oxc). //! 5. Rewrites `index.html` with bundled asset paths. //! 6. Minifies locale JSON files. //! 7. Updates `sw.js` cache manifest. //! 8. Writes HTML files to `$OUT_DIR` for `include_str!()`. //! //! **Debug mode** (`cargo build`): //! • Copies HTML files to `$OUT_DIR` for `include_str!()` only. use std::fs; use std::io; use std::path::{Path, PathBuf}; // ─── HTML files embedded via include_str!() in Rust source ─────────────────── const HTML_INCLUDE: &[&str] = &[ "login.html", "profile.html", "admin.html", "device-verify.html", "nextcloud-login.html", "share.html", ]; // ─── View CSS files linked directly in index.html (not via @import) ────────── const INDEX_VIEW_CSS: &[&str] = &[ "views/inlineViewer.css", "views/favorites.css", "views/recent.css", "views/shared.css", "views/trash.css", "views/photos.css", "views/photosLightbox.css", "views/music.css", ]; // ═══════════════════════════════════════════════════════════════════════════════ // Entry point // ═══════════════════════════════════════════════════════════════════════════════ fn main() { let manifest_dir = penv("CARGO_MANIFEST_DIR"); let out_dir = penv("OUT_DIR"); let static_dir = manifest_dir.join("static"); println!("cargo:rerun-if-changed=static"); println!("cargo:rerun-if-changed=build.rs"); // ── Guard: Docker cacher stage has no static/ ──────────────────────────── if !static_dir.exists() { for name in HTML_INCLUDE { let _ = fs::write(out_dir.join(name), ""); } return; } let is_release = env_or("PROFILE", "debug") == "release"; if is_release { process_release(&manifest_dir, &static_dir, &out_dir); } else { // Debug: copy original HTML for include_str!() for name in HTML_INCLUDE { let src = static_dir.join(name); if src.exists() { let _ = fs::copy(&src, out_dir.join(name)); } } } } // ═══════════════════════════════════════════════════════════════════════════════ // Release pipeline // ═══════════════════════════════════════════════════════════════════════════════ fn process_release(manifest_dir: &Path, static_dir: &Path, out_dir: &Path) { let dist_dir = manifest_dir.join("static-dist"); // Start fresh if dist_dir.exists() { fs::remove_dir_all(&dist_dir).expect("clean static-dist"); } // 1. Mirror static/ → static-dist/ copy_dir_recursive(static_dir, &dist_dir).expect("copy static → static-dist"); let css_dir = static_dir.join("css"); // ── 2. Resolve main.css @imports ───────────────────────────────────────── let resolved_main = resolve_css_imports(&css_dir.join("main.css"), &css_dir); let minified_main = css_minify_safe(&resolved_main); fs::write(dist_dir.join("css/main.css"), &minified_main).expect("write main.css"); // ── 3. Build CSS bundle for index.html ─────────────────────────────────── let mut css_all = resolved_main; for view in INDEX_VIEW_CSS { let p = css_dir.join(view); if p.exists() { css_all.push_str(&fs::read_to_string(&p).unwrap_or_default()); css_all.push('\n'); } } let css_bundle = css_minify_safe(&css_all); let css_hash = fnv_hash(css_bundle.as_bytes()); let css_name = format!("app.{css_hash}.css"); fs::write(dist_dir.join("css").join(&css_name), &css_bundle).expect("write css bundle"); // ── 4. Minify ALL individual CSS in static-dist/ ───────────────────────── minify_tree_css(&dist_dir.join("css")); // ── 5. Bundle all ES modules into one IIFE ─────────────────────────────── // Walk the import graph starting from every ")); continue; } // ── Replace all type="module" scripts with single bundle ───────────── if t.starts_with("" )); js_done = true; } continue; } // ── Drop "Styles" / "Scripts" section comments ─────────────────────── if t.starts_with("