Files
mrcharles 374288a105 feat: bootstrap meta — cross-project best-practices template
Extract patterns from 18 projects across two machines (12 local infra +
6 remote personal/business via ssh survey). Ship a reusable Gitea template
with AGENTS.md, 5 Crush PreToolUse hooks, git pre-commit/pre-push, a
generalized rules engine, shared bash library, Makefile, lifecycle scripts,
and gardening loop. Includes the canonical BASELINE-PROMPT.md (13 sections)
and PATTERNS.md (standardization scorecard).

The repo self-applies: it passes its own shellcheck (zero info-level),
make fast, and all check-rules.sh checks.

💘 Generated with Crush

Assisted-by: Crush via Crush <crush@charm.land>
2026-08-07 11:17:53 -05:00

138 lines
5.3 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# lib/common.sh — shared helpers for shell scripts and hooks in this repo.
#
# Source it from any script:
# #!/usr/bin/env bash
# set -euo pipefail
# HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# # shellcheck source=lib/common.sh
# source "$HERE/lib/common.sh" # or the appropriate relative path
#
# This library exists to drive a known cross-project inconsistency to zero:
# every repo used to re-paste the ANSI color block, redefine log_* helpers,
# pick one of three incompatible shebangs, and roll its own docker wrapper.
# Import this once instead.
# Do NOT set -euo pipefail here unconditionally — some callers (git hooks)
# source this file and rely on controlling their own shell options. We only
# guarantee the functions below are defined.
###############################################################################
# Config — override via environment before sourcing if needed
###############################################################################
: "${TEMPLATE_ROOT:=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}"
export TEMPLATE_ROOT
###############################################################################
# ANSI colors (defined once, used everywhere)
###############################################################################
if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; BOLD='\033[1m'; NC='\033[0m'
else
RED=''; GREEN=''; YELLOW=''; BLUE=''; BOLD=''; NC=''
fi
export RED GREEN YELLOW BLUE BOLD NC
###############################################################################
# Logging
###############################################################################
log_info() { printf "${BLUE}${NC} %s\n" "$*"; }
log_ok() { printf "${GREEN}${NC} %s\n" "$*"; }
log_warn() { printf "${YELLOW}${NC} %s\n" "$*" >&2; }
log_error() { printf "${RED}${NC} %s\n" "$*" >&2; }
log_step() { printf "\n${BOLD}== %s ==${NC}\n" "$*"; }
die() { log_error "$*"; exit 1; }
###############################################################################
# Predicates
###############################################################################
# have <cmd> — return 0 if <cmd> is on PATH
have() { command -v "$1" >/dev/null 2>&1; }
###############################################################################
# Path helpers
###############################################################################
repo_root() {
# Prefer git's notion of the repo root, fall back to $TEMPLATE_ROOT, then pwd.
if git rev-parse --show-toplevel >/dev/null 2>&1; then
git rev-parse --show-toplevel
else
printf '%s\n' "${TEMPLATE_ROOT:-$(pwd)}"
fi
}
###############################################################################
# Privilege helpers
###############################################################################
# as_root — run the remaining args as root via sudo, or directly if already root.
as_root() {
if [ "$(id -u)" -eq 0 ]; then "$@"; else sudo "$@"; fi
}
###############################################################################
# Docker wrapper
###############################################################################
# docker_run <image> <args...>
# Ephemeral container, host-uid ownership, repo mounted at /data, cwd /data.
# Drives the "host stays clean; everything runs in containers" policy and
# ensures output files are owned by the invoking user, not root.
docker_run() {
[ "$#" -ge 1 ] || die "docker_run: image required"
local image="$1"; shift
have docker || die "docker not found on PATH"
local root
root="$(repo_root)"
docker run --rm \
--user "$(id -u):$(id -g)" \
-e HOME=/tmp \
-v "$root:/data" \
-w /data \
"$image" "$@"
}
###############################################################################
# Rule-audit accumulator (used by scripts/check-rules.sh)
# Globals read/written: RULE_PASS RULE_WARN RULE_FAIL
###############################################################################
init_counters() { RULE_PASS=0; RULE_WARN=0; RULE_FAIL=0; }
# check <description> <pass|warn|fail>
check() {
local desc="$1" result="$2"
case "$result" in
pass)
RULE_PASS=$((RULE_PASS + 1))
if [ "${RULE_VERBOSE:-true}" = true ]; then printf " ${GREEN}PASS${NC} %s\n" "$desc"; fi
;;
warn)
RULE_WARN=$((RULE_WARN + 1))
if [ "${RULE_VERBOSE:-true}" = true ]; then printf " ${YELLOW}WARN${NC} %s\n" "$desc"; fi
;;
fail)
RULE_FAIL=$((RULE_FAIL + 1))
printf " ${RED}FAIL${NC} %s\n" "$desc"
;;
*)
die "check(): invalid result '$result' (use pass|warn|fail)"
;;
esac
}
# print_summary_and_exit
print_summary_and_exit() {
if [ "${RULE_VERBOSE:-true}" = true ]; then
printf "\n=== Summary ===\n PASS: %s\n WARN: %s\n FAIL: %s\n\n" \
"$RULE_PASS" "$RULE_WARN" "$RULE_FAIL"
fi
if [ "$RULE_FAIL" -gt 0 ]; then
if [ "${RULE_VERBOSE:-true}" = true ]; then
printf "AUDIT FAILED — %s rule(s) violated.\n" "$RULE_FAIL"
fi
exit 1
fi
if [ "${RULE_VERBOSE:-true}" = true ]; then printf "AUDIT PASSED.\n"; fi
exit 0
}