Skip to main content

sr_core/
hooks.rs

1use std::collections::HashMap;
2use std::process::Command;
3
4use crate::error::ReleaseError;
5
6/// A shell command to run as a lifecycle hook.
7#[derive(Debug, Clone)]
8pub struct HookCommand {
9    pub command: String,
10}
11
12/// Release context passed to hooks as environment variables.
13#[derive(Debug, Clone, Default)]
14pub struct HookContext {
15    pub env: HashMap<String, String>,
16}
17
18impl HookContext {
19    /// Set an environment variable in the context.
20    pub fn set(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
21        self.env.insert(key.into(), value.into());
22        self
23    }
24}
25
26/// Runs lifecycle hooks at various points in the release process.
27pub trait HookRunner: Send + Sync {
28    fn run(&self, hooks: &[HookCommand], ctx: &HookContext) -> Result<(), ReleaseError>;
29}
30
31/// Default hook runner that executes commands via the system shell.
32pub struct ShellHookRunner;
33
34impl HookRunner for ShellHookRunner {
35    fn run(&self, hooks: &[HookCommand], ctx: &HookContext) -> Result<(), ReleaseError> {
36        for hook in hooks {
37            let status = Command::new("sh")
38                .arg("-c")
39                .arg(&hook.command)
40                .envs(&ctx.env)
41                .status()
42                .map_err(|e| ReleaseError::Hook {
43                    command: format!("{}: {e}", hook.command),
44                })?;
45
46            if !status.success() {
47                return Err(ReleaseError::Hook {
48                    command: hook.command.clone(),
49                });
50            }
51        }
52        Ok(())
53    }
54}