Template
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>
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
#!/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
|
||||
}
|
||||
Reference in New Issue
Block a user