use std::path::PathBuf;
use super::{build, BuildOptions, Output, Processors, DEFAULT_HTML};
use crate::builder_shared::source_builder_methods;
use crate::vendor::PackageSpec;
use crate::{Error, Result};
#[derive(Default)]
pub struct Build {
roots: Vec<PathBuf>,
out: Option<PathBuf>,
mount: Option<String>,
html: Option<String>,
template: Option<PathBuf>,
specs: Vec<PackageSpec>,
manifests: Vec<PathBuf>,
processors: Processors,
output: Output,
}
source_builder_methods!(Build);
impl Build {
pub fn new() -> Self {
Self::default()
}
pub fn out(mut self, out: impl Into<PathBuf>) -> Self {
self.out = Some(out.into());
self
}
pub fn mount(mut self, mount: impl Into<String>) -> Self {
self.mount = Some(mount.into());
self
}
pub fn html(mut self, html: impl Into<String>) -> Self {
self.html = Some(html.into());
self
}
pub fn template(mut self, template: impl Into<PathBuf>) -> Self {
self.template = Some(template.into());
self
}
pub fn vendor(mut self, spec: impl AsRef<str>) -> Self {
self.specs.push(PackageSpec::parse(spec.as_ref()));
self
}
pub fn vendor_specs(mut self, specs: impl IntoIterator<Item = PackageSpec>) -> Self {
self.specs.extend(specs);
self
}
pub fn manifest(mut self, manifest: impl Into<PathBuf>) -> Self {
self.manifests.push(manifest.into());
self
}
pub fn minify(mut self, on: bool) -> Self {
self.output.minify = on;
self
}
pub fn gzip(mut self, on: bool) -> Self {
self.output.gzip = on;
self
}
pub fn optimized(mut self) -> Self {
self.output = Output::optimized();
self
}
pub fn run(self) -> Result<()> {
let out = self.out.ok_or_else(|| {
Error::Build("Build::out is required — set the output directory with .out(…)".into())
})?;
let mut specs = self.specs;
for manifest in &self.manifests {
specs.extend(crate::vendor::specs_from_package_json(manifest)?);
}
let mut seen = std::collections::HashSet::new();
specs.retain(|s| seen.insert(s.name().to_string()));
let mount = self.mount.unwrap_or_else(|| "/web_modules".to_string());
let html = self.html.unwrap_or_else(|| DEFAULT_HTML.to_string());
build(&BuildOptions {
specs: &specs,
roots: &self.roots,
out: &out,
mount: &mount,
html: &html,
template: self.template.as_deref(),
processors: self.processors,
output: self.output,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn toggles_and_output_map_to_processors() {
let b = Build::new()
.root("web")
.scss(false)
.tera(false)
.minify(true)
.gzip(true)
.scss_load_path("styles");
assert_eq!(b.roots, [PathBuf::from("web")]);
assert!(b.processors.typescript, "typescript stays default-on");
assert!(!b.processors.scss && !b.processors.tera, "explicit off");
assert!(b.output.minify && b.output.gzip);
assert_eq!(
b.processors.extra_scss_load_paths,
[PathBuf::from("styles")]
);
}
#[test]
fn optimized_sets_both_outputs() {
let b = Build::new().optimized();
assert!(b.output.minify && b.output.gzip);
}
#[test]
fn reject_preset_and_pattern_compose_on_processors() {
use crate::reject::Presets;
let b = Build::new()
.root("web")
.reject_preset(Presets::ALL & !Presets::CONFIG)
.reject(".htpasswd");
let r = &b.processors.reject;
assert!(r.rejects("app.ts"), "source preset still on");
assert!(r.rejects(".env"), "hidden preset still on");
assert!(!r.rejects("package.json"), "config preset dropped");
assert!(r.rejects(".htpasswd"), "extra pattern added on top");
}
#[test]
fn vendor_parses_specs() {
let b = Build::new().vendor("lit@^3").vendor("@lit/context@^1");
let names: Vec<&str> = b.specs.iter().map(PackageSpec::name).collect();
assert_eq!(names, ["lit", "@lit/context"]);
}
#[test]
fn run_requires_out() {
let err = Build::new().root("web").run().unwrap_err();
assert!(
matches!(err, Error::Build(_)),
"missing out is a build error"
);
}
#[test]
fn run_builds_a_non_vendored_tree() {
let dir = tempfile::tempdir().unwrap();
let src = dir.path().join("src");
let out = dir.path().join("out");
std::fs::create_dir_all(&src).unwrap();
std::fs::write(src.join("page.html"), "<p>hi</p>").unwrap();
Build::new().root(&src).out(&out).run().unwrap();
assert!(out.join("page.html").exists(), "static file copied through");
assert!(out.join("index.html").exists(), "fallback index written");
assert!(!out.join("web_modules").exists(), "no specs ⇒ no vendoring");
}
}