91 lines
2.1 KiB
Rust
91 lines
2.1 KiB
Rust
//! Lifecycle hook execution.
|
|
|
|
use doot_lang::HookStage;
|
|
use std::process::Command;
|
|
use thiserror::Error;
|
|
|
|
/// Hook execution errors.
|
|
#[derive(Error, Debug)]
|
|
pub enum HookError {
|
|
#[error("hook failed: {command}: {message}")]
|
|
ExecutionFailed { command: String, message: String },
|
|
|
|
#[error("io error: {0}")]
|
|
IoError(#[from] std::io::Error),
|
|
}
|
|
|
|
/// A lifecycle hook.
|
|
#[derive(Debug, Clone)]
|
|
pub struct Hook {
|
|
pub stage: HookStage,
|
|
pub command: String,
|
|
pub working_dir: Option<std::path::PathBuf>,
|
|
}
|
|
|
|
/// Executes lifecycle hooks.
|
|
pub struct HookRunner {
|
|
hooks: Vec<Hook>,
|
|
dry_run: bool,
|
|
}
|
|
|
|
impl HookRunner {
|
|
/// Creates a new hook runner.
|
|
pub fn new() -> Self {
|
|
Self {
|
|
hooks: Vec::new(),
|
|
dry_run: false,
|
|
}
|
|
}
|
|
|
|
/// Sets dry run mode.
|
|
pub fn dry_run(mut self, dry_run: bool) -> Self {
|
|
self.dry_run = dry_run;
|
|
self
|
|
}
|
|
|
|
/// Registers a hook.
|
|
pub fn add_hook(&mut self, hook: Hook) {
|
|
self.hooks.push(hook);
|
|
}
|
|
|
|
/// Runs all hooks for a given stage.
|
|
#[tracing::instrument(skip(self))]
|
|
pub fn run_stage(&self, stage: HookStage) -> Result<(), HookError> {
|
|
for hook in self.hooks.iter().filter(|h| h.stage == stage) {
|
|
self.run_hook(hook)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[tracing::instrument(skip(self), fields(command = %hook.command))]
|
|
fn run_hook(&self, hook: &Hook) -> Result<(), HookError> {
|
|
if self.dry_run {
|
|
println!("[dry-run] would run: {}", hook.command);
|
|
return Ok(());
|
|
}
|
|
|
|
let mut cmd = Command::new("sh");
|
|
cmd.arg("-c").arg(&hook.command);
|
|
|
|
if let Some(ref dir) = hook.working_dir {
|
|
cmd.current_dir(dir);
|
|
}
|
|
|
|
let output = cmd.output()?;
|
|
|
|
if !output.status.success() {
|
|
return Err(HookError::ExecutionFailed {
|
|
command: hook.command.clone(),
|
|
message: String::from_utf8_lossy(&output.stderr).to_string(),
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Default for HookRunner {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|