Skip to main content

zoi_hooks/
lib.rs

1//! Hooks functionality for Zoi.
2//!
3//! This crate provides the logic for executing package-specific and global
4//! hooks.
5
6use std::process::Command;
7
8use anyhow::{Result, anyhow};
9use colored::Colorize;
10use zoi_core::types::{self, Hooks, PlatformOrStringVec};
11use zoi_core::utils;
12
13/// Manages system-wide "Global Transaction Hooks".
14pub mod global;
15
16/// The type of hook being executed.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum HookType {
19    /// Runs before a package is installed.
20    PreInstall,
21    /// Runs after a package is installed.
22    PostInstall,
23    /// Runs before a package is upgraded.
24    PreUpgrade,
25    /// Runs after a package is upgraded.
26    PostUpgrade,
27    /// Runs before a package is removed.
28    PreRemove,
29    /// Runs after a package is removed.
30    PostRemove
31}
32
33/// Executes a list of shell commands within a specific scope.
34fn execute_commands(commands: &[String], scope: types::Scope) -> Result<()> {
35    let scope_str = format!("{scope:?}").to_lowercase();
36    for cmd_str in commands {
37        println!("> {}", cmd_str.cyan());
38        let mut command = if cfg!(target_os = "windows") {
39            let mut c = Command::new("pwsh");
40            c.arg("-Command").arg(cmd_str);
41            c
42        } else {
43            let mut c = Command::new("bash");
44            c.arg("-c").arg(cmd_str);
45            c
46        };
47
48        command.env("ZOI_SCOPE", &scope_str);
49
50        let status = command.status()?;
51
52        if !status.success() {
53            return Err(anyhow!("Hook command failed: {cmd_str}"));
54        }
55    }
56    Ok(())
57}
58
59/// Runs the specified type of hooks for a package.
60///
61/// # Arguments
62///
63/// * `hooks` - The hooks configuration from the package.
64/// * `hook_type` - The type of hook to run.
65/// * `scope` - The installation scope (System, User, or Project).
66///
67/// # Errors
68///
69/// Returns an error if getting the current platform or executing a hook command
70/// fails.
71pub fn run_hooks(
72    hooks: &Hooks,
73    hook_type: HookType,
74    scope: types::Scope
75) -> Result<()> {
76    let platform = utils::get_platform()?;
77
78    let commands_to_run = match hook_type {
79        HookType::PreInstall => &hooks.pre_install,
80        HookType::PostInstall => &hooks.post_install,
81        HookType::PreUpgrade => &hooks.pre_upgrade,
82        HookType::PostUpgrade => &hooks.post_upgrade,
83        HookType::PreRemove => &hooks.pre_remove,
84        HookType::PostRemove => &hooks.post_remove
85    };
86
87    if let Some(platform_or_string_vec) = commands_to_run {
88        match platform_or_string_vec {
89            PlatformOrStringVec::StringVec(cmds) => {
90                execute_commands(cmds, scope)?;
91            }
92            PlatformOrStringVec::Platform(platform_map) => {
93                if let Some(cmds) = platform_map.get(&platform) {
94                    execute_commands(cmds, scope)?;
95                } else if let Some(cmds) = platform_map.get("default") {
96                    execute_commands(cmds, scope)?;
97                }
98            }
99        }
100    }
101
102    Ok(())
103}