use crate::builder::BuildOutput;
use std::fs;
use std::path::Path;
pub fn write_build_output(
output: &BuildOutput,
out_dir: &Path,
media_dir: &Path,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if out_dir.exists() {
fs::remove_dir_all(out_dir)?;
}
fs::create_dir_all(out_dir)?;
let asset_tags = extract_asset_tags();
for page in &output.pages {
let content = if page.path.ends_with(".html") {
inject_assets(&page.content, asset_tags.as_deref())
} else {
page.content.clone()
};
let file_path = out_dir.join(&page.path);
if let Some(parent) = file_path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&file_path, &content)?;
}
let data_dir = out_dir.join("data");
fs::create_dir_all(&data_dir)?;
for (ext_id, data) in &output.extensions_data {
let path = data_dir.join(format!("{ext_id}.json"));
let json = serde_json::to_string_pretty(data)?;
fs::write(&path, &json)?;
}
let search_json = serde_json::to_string_pretty(&output.search_docs)?;
fs::write(data_dir.join("search-index.json"), &search_json)?;
write_embedded_assets(out_dir)?;
let index_html = out_dir.join("index.html");
if index_html.exists() {
let _ = fs::copy(&index_html, out_dir.join("404.html"));
}
if media_dir.exists() {
copy_dir_recursive(media_dir, &out_dir.join("media"))?;
}
tracing::info!(
pages = output.pages.len(),
extensions = output.extensions_data.len(),
dir = %out_dir.display(),
"build output written"
);
Ok(())
}
fn extract_asset_tags() -> Option<String> {
let html = crate::http::spa_index_html()?;
let mut tags = Vec::new();
for line in html.lines() {
let t = line.trim();
if t.starts_with("<script ") || t.starts_with("<link rel=\"stylesheet") {
tags.push(t.to_string());
}
}
if tags.is_empty() {
None
} else {
Some(tags.join("\n "))
}
}
fn inject_assets(shell: &str, asset_tags: Option<&str>) -> String {
match asset_tags {
Some(tags) => shell.replace(r#"<script src="/assets/index.js"></script>"#, tags),
None => shell.to_string(),
}
}
fn write_embedded_assets(out_dir: &Path) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
for (path, bytes) in crate::http::embedded_spa_files() {
let dest = out_dir.join(&path);
if let Some(parent) = dest.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&dest, &bytes)?;
}
Ok(())
}
fn copy_dir_recursive(
src: &Path,
dst: &Path,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if !src.is_dir() {
return Err(format!("not a directory: {}", src.display()).into());
}
fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let file_type = entry.file_type()?;
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
if file_type.is_dir() {
copy_dir_recursive(&src_path, &dst_path)?;
} else {
fs::copy(&src_path, &dst_path)?;
}
}
Ok(())
}