use std::path::PathBuf;
use crate::config::ProjectConfig;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("no build command configured; set build.command in project.cfg or pass --command")]
NoBuildCommand,
#[error("reading {path}: {source}")]
Read {
path: String,
#[source]
source: std::io::Error,
},
#[error("{path}: {source}")]
Config {
path: String,
#[source]
source: crate::config::ConfigError,
},
#[error("build command failed: `{command}` exited with {status}")]
CommandFailed {
command: String,
status: std::process::ExitStatus,
},
#[error(transparent)]
Io(#[from] std::io::Error),
}
type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, clap::Args)]
pub struct BuildArgs {
#[arg(long)]
command: Option<String>,
}
pub async fn run(args: BuildArgs, config: &ProjectConfig) -> Result<()> {
let command = resolve_command(args.command, config)?;
run_command(&command).await
}
pub fn resolve_command(override_command: Option<String>, config: &ProjectConfig) -> Result<String> {
override_command
.or_else(|| config.build.as_ref().map(|build| build.command.clone()))
.ok_or(Error::NoBuildCommand)
}
#[derive(Debug, clap::Args)]
pub struct ValidateArgs {
#[arg(default_value = "project.cfg")]
path: PathBuf,
}
pub fn validate(args: ValidateArgs) -> Result<()> {
let text = std::fs::read_to_string(&args.path).map_err(|source| Error::Read {
path: args.path.display().to_string(),
source,
})?;
let project = ProjectConfig::parse(&text).map_err(|source| Error::Config {
path: args.path.display().to_string(),
source,
})?;
let config = &project.routing;
println!(
"{} ok — {} redirect(s), {} rewrite(s), {} header rule(s)",
args.path.display(),
config.redirects.len(),
config.rewrites.len(),
config.headers.len(),
);
if !config.handlers.is_empty()
|| !config.consumers.is_empty()
|| !config.crons.is_empty()
|| !config.streams.is_empty()
{
println!(
" {} handler(s), {} consumer(s), {} cron(s), {} stream(s)",
config.handlers.len(),
config.consumers.len(),
config.crons.len(),
config.streams.len(),
);
}
Ok(())
}
pub async fn run_command(command: &str) -> Result<()> {
tracing::info!(%command, "running build command");
let status = tokio::process::Command::new("sh")
.arg("-c")
.arg(command)
.status()
.await?;
if !status.success() {
return Err(Error::CommandFailed {
command: command.to_string(),
status,
});
}
Ok(())
}