fix(notfiles,nothooks): validate package on unlink; batch state I/O in HookRunner

Closes #10, closes #13
This commit is contained in:
Joseph O'Brien
2026-04-11 23:40:40 -04:00
parent 11d329c5a2
commit 0d60583c65
6 changed files with 118 additions and 39 deletions

View File

@@ -1,6 +1,6 @@
use crate::HookResult;
use crate::state::HookState;
use notcore::{HookPhase, HookSpec};
use notcore::{HookPhase, HookSpec, Report, StepStatus};
use std::path::PathBuf;
use std::process::Command;
@@ -46,9 +46,41 @@ impl HookRunner {
}
}
pub fn run_hook(&self, spec: &HookSpec) -> HookResult {
/// Run all hooks in `hooks` that match `phase`.
///
/// State is loaded once before iterating and saved once at the end (only if
/// at least one Setup hook completed successfully), avoiding N file reads
/// and writes per phase.
pub fn run_phase(&self, hooks: &[HookSpec], phase: &HookPhase) -> Report {
let mut state = HookState::load(&self.state_dir).unwrap_or_default();
let mut state_dirty = false;
let mut report = Report::default();
for spec in hooks.iter().filter(|h| &h.phase == phase) {
let result = self.run_hook_with_state(spec, &mut state, &mut state_dirty);
let status = match &result {
HookResult::Ok => StepStatus::Ok,
HookResult::Skipped => StepStatus::Skipped,
HookResult::Failed(msg) => StepStatus::Failed(msg.clone()),
};
report.add(&spec.name, status);
}
if state_dirty {
let _ = state.save(&self.state_dir);
}
report
}
/// Execute a single hook, using the caller-owned `state` and `dirty` flag
/// instead of loading/saving per invocation.
fn run_hook_with_state(
&self,
spec: &HookSpec,
state: &mut HookState,
state_dirty: &mut bool,
) -> HookResult {
if spec.phase == HookPhase::Setup && !self.force && state.is_done(&spec.name) {
return HookResult::Skipped;
}
@@ -63,7 +95,7 @@ impl HookRunner {
Ok(status) if status.success() => {
if spec.phase == HookPhase::Setup {
state.mark_done(&spec.name);
let _ = state.save(&self.state_dir);
*state_dirty = true;
}
HookResult::Ok
}
@@ -71,4 +103,18 @@ impl HookRunner {
Err(e) => HookResult::Failed(e.to_string()),
}
}
/// Run a single hook with its own load/save cycle.
///
/// Prefer [`HookRunner::run_phase`] when running multiple hooks to avoid
/// repeated state I/O.
pub fn run_hook(&self, spec: &HookSpec) -> HookResult {
let mut state = HookState::load(&self.state_dir).unwrap_or_default();
let mut state_dirty = false;
let result = self.run_hook_with_state(spec, &mut state, &mut state_dirty);
if state_dirty {
let _ = state.save(&self.state_dir);
}
result
}
}