2026-03-31 17:26:26 -04:00
|
|
|
use crate::HookResult;
|
|
|
|
|
use crate::state::HookState;
|
2026-04-01 20:58:09 -04:00
|
|
|
use notcore::{HookPhase, HookSpec};
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
use std::process::Command;
|
2026-03-31 17:26:26 -04:00
|
|
|
|
|
|
|
|
pub struct HookRunner {
|
|
|
|
|
state_dir: PathBuf,
|
|
|
|
|
force: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl HookRunner {
|
|
|
|
|
pub fn new(state_dir: PathBuf) -> Self {
|
2026-04-01 20:58:09 -04:00
|
|
|
Self {
|
|
|
|
|
state_dir,
|
|
|
|
|
force: false,
|
|
|
|
|
}
|
2026-03-31 17:26:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn with_force(state_dir: PathBuf) -> Self {
|
2026-04-01 20:58:09 -04:00
|
|
|
Self {
|
|
|
|
|
state_dir,
|
|
|
|
|
force: true,
|
|
|
|
|
}
|
2026-03-31 17:26:26 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn run_hook(&self, spec: &HookSpec) -> HookResult {
|
|
|
|
|
let mut state = HookState::load(&self.state_dir).unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
if spec.phase == HookPhase::Setup && !self.force && state.is_done(&spec.name) {
|
|
|
|
|
return HookResult::Skipped;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-01 20:58:09 -04:00
|
|
|
// TODO: support other shells
|
|
|
|
|
let result = Command::new("nu").arg(&spec.script).status();
|
2026-03-31 17:26:26 -04:00
|
|
|
|
|
|
|
|
match result {
|
|
|
|
|
Ok(status) if status.success() => {
|
|
|
|
|
if spec.phase == HookPhase::Setup {
|
|
|
|
|
state.mark_done(&spec.name);
|
|
|
|
|
let _ = state.save(&self.state_dir);
|
|
|
|
|
}
|
|
|
|
|
HookResult::Ok
|
|
|
|
|
}
|
|
|
|
|
Ok(status) => HookResult::Failed(format!("exit code {}", status.code().unwrap_or(-1))),
|
|
|
|
|
Err(e) => HookResult::Failed(e.to_string()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|