dx/dxkit/core/hook.sh

135 lines
No EOL
2.9 KiB
Bash

#!/usr/bin/env bash
# core/hook.sh
#
# Lifecycle hook runner.
#
# Hooks are shell scripts sourced in lexicographic order from:
# dxkit/hooks/<command>/<timing>/
#
# Timing:
# pre — runs before the command's main logic
# post — runs after the command's main logic
#
# Naming convention:
# NN-description.sh (e.g. 10-permissions.sh, 20-update-cron.sh)
# Numeric prefix controls execution order.
#
# Hooks are sourced (not executed) so they have full access to all
# loaded modules, functions, and environment variables.
#
# Usage:
# hook::run init pre
# hook::run init post
# hook::run build post
#
# Example structure:
# dxkit/hooks/
# init/
# pre/
# 10-something.sh
# post/
# 10-permissions.sh
# 20-update-cron.sh
# build/
# post/
# 10-something.sh
# ============================================
# Runner
# ============================================
function hook::run() {
local command="$1"
local timing="$2"
local hook_dir
hook_dir="$(ctx::dxkit)/hooks/${command}/${timing}"
[[ -d "$hook_dir" ]] || return 0
local hooks
hooks="$(hook::_list "$hook_dir")"
[[ -z "$hooks" ]] && return 0
log::info "Running ${timing}-${command} hooks..."
local hook
while IFS= read -r hook; do
[[ -f "$hook" ]] || continue
hook::_run_one "$hook"
done <<< "$hooks"
}
# ============================================
# Run a single hook
# ============================================
function hook::_run_one() {
local hook="$1"
local name
name="$(basename "$hook")"
log::info "${name}"
# shellcheck disable=SC1090
source "$hook" || {
log::error "Hook failed: ${name}"
return 1
}
}
# ============================================
# List hooks in a directory sorted by name
# ============================================
function hook::_list() {
local dir="$1"
find "$dir" -maxdepth 1 -name "*.sh" -type f | sort
}
# ============================================
# Predicates
# ============================================
function hook::has() {
local command="$1"
local timing="$2"
local hook_dir
hook_dir="$(ctx::dxkit)/hooks/${command}/${timing}"
[[ -d "$hook_dir" ]] && [[ -n "$(hook::_list "$hook_dir")" ]]
}
# ============================================
# Scaffold hook directory and example file
# ============================================
function hook::scaffold() {
local command="$1"
local timing="${2:-post}"
local hook_dir
hook_dir="$(ctx::dxkit)/hooks/${command}/${timing}"
mkdir -p "$hook_dir"
local example="${hook_dir}/10-example.sh"
[[ -f "$example" ]] && return 0
cat > "$example" <<EOF
#!/usr/bin/env bash
# ${timing}-${command} hook: 10-example.sh
#
# This hook runs ${timing} dx ${command}.
# All modules and environment variables are available.
#
# Remove or rename this file when adding real hooks.
# log::info "Running example ${timing}-${command} hook..."
EOF
log::info "Scaffolded hook: ${example}"
}