//! 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("