use crate::{
build::BuildSystem,
config::{
self,
rt::{self, RtcBuild, RtcBuilder},
types::{BaseUrl, Minify},
Configuration, Tools,
},
};
use anyhow::Result;
use clap::Args;
use std::{path::PathBuf, sync::Arc};
#[derive(Clone, Debug, Args)]
#[command(name = "build")]
#[command(next_help_heading = "Build")]
pub struct Build {
pub target: Option<PathBuf>,
#[arg(long, env = "TRUNK_BUILD_HTML_OUTPUT")]
pub html_output: Option<String>,
#[arg(long, env = "TRUNK_BUILD_RELEASE")]
#[arg(default_missing_value="true", num_args=0..=1)]
pub release: Option<bool>,
#[arg(long, env = "TRUNK_BUILD_CARGO_PROFILE")]
pub cargo_profile: Option<String>,
#[arg(short, long, env = "TRUNK_BUILD_DIST")]
pub dist: Option<PathBuf>,
#[arg(from_global)]
pub offline: Option<bool>,
#[arg(long, env = "TRUNK_BUILD_FROZEN")]
#[arg(default_missing_value="true", num_args=0..=1)]
pub frozen: Option<bool>,
#[arg(long, env = "TRUNK_BUILD_LOCKED")]
#[arg(default_missing_value="true", num_args=0..=1)]
pub locked: Option<bool>,
#[arg(long, env = "TRUNK_BUILD_PUBLIC_URL")]
pub public_url: Option<BaseUrl>,
#[arg(long, env = "TRUNK_BUILD_PUBLIC_URL_NO_TRAILING_SLASH")]
#[arg(default_missing_value="true", num_args=0..=1)]
pub public_url_no_trailing_slash_fix: Option<bool>,
#[arg(long, env = "TRUNK_BUILD_NO_DEFAULT_FEATURES")]
#[arg(default_missing_value="true", num_args=0..=1)]
pub no_default_features: Option<bool>,
#[arg(long, env = "TRUNK_BUILD_ALL_FEATURES")]
#[arg(default_missing_value="true", num_args=0..=1)]
pub all_features: Option<bool>,
#[arg(
long,
conflicts_with = "all_features",
value_delimiter = ',',
env = "TRUNK_BUILD_FEATURES"
)]
pub features: Option<Vec<String>>,
#[arg(long, env = "TRUNK_BUILD_FILEHASH")]
#[arg(default_missing_value="true", num_args=0..=1)]
pub filehash: Option<bool>,
#[arg(long, env = "TRUNK_BUILD_EXAMPLE")]
pub example: Option<String>,
#[arg(long, env = "TRUNK_BUILD_ROOT_CERTIFICATE")]
pub root_certificate: Option<String>,
#[arg(long, env = "TRUNK_BUILD_ACCEPT_INVALID_CERTS")]
#[arg(default_missing_value="true", num_args=0..=1)]
pub accept_invalid_certs: Option<bool>,
#[arg(short = 'M', long, env = "TRUNK_BUILD_MINIFY")]
#[arg(default_missing_value="true", num_args=0..=1)]
pub minify: Option<bool>,
#[arg(long, env = "TRUNK_BUILD_NO_SRI")]
#[arg(default_missing_value="true", num_args=0..=1)]
pub no_sri: Option<bool>,
#[arg(long, env = "TRUNK_BUILD_ALLOW_SELF_CLOSING_SCRIPT")]
#[arg(default_missing_value="true", num_args=0..=1)]
pub allow_self_closing_script: Option<bool>,
#[command(flatten)]
pub core: super::core::Core,
#[command(flatten)]
pub tools: Tools,
}
impl Build {
pub fn apply_to(self, mut config: Configuration) -> Result<Configuration> {
let Self {
core,
target,
html_output,
release,
cargo_profile,
dist,
offline,
frozen,
locked,
public_url,
public_url_no_trailing_slash_fix,
no_default_features,
all_features,
features,
filehash,
example,
root_certificate,
accept_invalid_certs,
minify,
no_sri,
allow_self_closing_script,
tools,
} = self;
config.build.target = target.unwrap_or(config.build.target);
config.build.html_output = html_output.unwrap_or(config.build.html_output);
config.build.release = release.unwrap_or(config.build.release);
config.build.cargo_profile = cargo_profile.or(config.build.cargo_profile);
config.build.dist = dist.unwrap_or(config.build.dist);
config.build.offline = offline.unwrap_or(config.build.offline);
config.build.frozen = frozen.unwrap_or(config.build.frozen);
config.build.locked = locked.unwrap_or(config.build.locked);
config.build.public_url = public_url.unwrap_or(config.build.public_url);
config.build.public_url_no_trailing_slash_fix = public_url_no_trailing_slash_fix
.unwrap_or(config.build.public_url_no_trailing_slash_fix);
config.build.no_default_features =
no_default_features.unwrap_or(config.build.no_default_features);
config.build.all_features = all_features.unwrap_or(config.build.all_features);
config.build.features = features.unwrap_or(config.build.features);
config.build.filehash = filehash.unwrap_or(config.build.filehash);
config.build.example = example.or(config.build.example);
config.build.root_certificate = root_certificate.or(config.build.root_certificate);
config.build.accept_invalid_certs =
accept_invalid_certs.unwrap_or(config.build.accept_invalid_certs);
config.build.minify = minify
.map(|minify| match minify {
true => Minify::Always,
false => Minify::Never,
})
.unwrap_or(config.build.minify);
config.build.no_sri = no_sri.unwrap_or(config.build.no_sri);
config.build.allow_self_closing_script =
allow_self_closing_script.unwrap_or(config.build.allow_self_closing_script);
let config = core.apply_to(config)?;
let config = tools.apply_to(config)?;
Ok(config)
}
#[tracing::instrument(level = "trace", skip(self, config))]
pub async fn run(self, config: Option<PathBuf>) -> Result<()> {
let (cfg, working_directory) = config::load(config).await?;
let cfg = self.apply_to(cfg)?;
let cfg = RtcBuild::from_config(cfg, working_directory, |_, core| rt::BuildOptions {
core,
inject_autoloader: false,
})
.await?;
cfg.core.enforce_version()?;
let mut system = BuildSystem::new(Arc::new(cfg), None, None).await?;
system.build().await?;
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::{Trunk, TrunkSubcommands};
use clap::Parser;
use rstest::rstest;
#[rstest]
#[case(&["trunk", "build"], None)]
#[case(&["trunk", "build", "--no-default-features"], Some(true))]
#[case(&["trunk", "build", "--no-default-features", "true"], Some(true))]
#[case(&["trunk", "build", "--no-default-features", "false"], Some(false))]
fn test_bool_no_arg(#[case] input: &[&str], #[case] expected: Option<bool>) {
let cli = Trunk::parse_from(input);
let TrunkSubcommands::Build(build) = cli.action else {
panic!("must be a build command");
};
assert_eq!(build.no_default_features, expected);
}
}