105 lines
2.5 KiB
Bash
Executable File
105 lines
2.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
RULES_FILE="${HOMELAB_IMPACT_RULES:-${REPO_ROOT}/infra/jeannie-impact/rules.tsv}"
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: ./jeannie impact [--since REF] [PATH...]
|
|
|
|
Explain which homelab areas are touched by changed files or explicit paths.
|
|
Default mode analyzes the current working tree diff.
|
|
EOF
|
|
}
|
|
|
|
since=""
|
|
paths=()
|
|
while [ "$#" -gt 0 ]; do
|
|
case "$1" in
|
|
--since)
|
|
since="${2:-}"
|
|
shift 2
|
|
;;
|
|
-h|--help|help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
paths+=("$1")
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ ! -s "$RULES_FILE" ]; then
|
|
echo "Missing impact rules: $RULES_FILE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [ "${#paths[@]}" -eq 0 ]; then
|
|
if [ -n "$since" ]; then
|
|
mapfile -t paths < <(git -C "$REPO_ROOT" diff --name-only "$since"...HEAD)
|
|
else
|
|
mapfile -t paths < <(git -C "$REPO_ROOT" status --short | awk '{print $NF}')
|
|
fi
|
|
fi
|
|
|
|
if [ "${#paths[@]}" -eq 0 ]; then
|
|
echo "No changed files or paths to analyze."
|
|
exit 0
|
|
fi
|
|
|
|
python3 - "$RULES_FILE" "${paths[@]}" <<'PY'
|
|
import csv
|
|
import sys
|
|
|
|
rules_file = sys.argv[1]
|
|
paths = [path.strip() for path in sys.argv[2:] if path.strip()]
|
|
|
|
with open(rules_file, encoding="utf-8", newline="") as handle:
|
|
rules = list(csv.DictReader(handle, delimiter="\t"))
|
|
|
|
matches = []
|
|
for path in paths:
|
|
normalized = path.removeprefix("./")
|
|
for rule in rules:
|
|
pattern = rule["pattern"]
|
|
if normalized == pattern.rstrip("/") or normalized.startswith(pattern):
|
|
matches.append((normalized, rule))
|
|
|
|
print("Jeannie Impact")
|
|
print("==============")
|
|
print("Files:")
|
|
for path in paths:
|
|
print(f" {path}")
|
|
|
|
if not matches:
|
|
print("\nNo specific impact rule matched.")
|
|
print("Validate:")
|
|
print(" ./jeannie validate")
|
|
raise SystemExit(0)
|
|
|
|
seen = set()
|
|
print("\nImpacted Areas")
|
|
for path, rule in matches:
|
|
key = (rule["area"], rule["risk"])
|
|
if key in seen:
|
|
continue
|
|
seen.add(key)
|
|
print(f"\n- {rule['area']}")
|
|
print(f" risk: {rule['risk']}")
|
|
print(" touches:")
|
|
for item in rule["touches"].split(";"):
|
|
print(f" - {item}")
|
|
print(" validate:")
|
|
for command in rule["validate"].split(";"):
|
|
print(f" {command}")
|
|
if rule["apply"] != "none":
|
|
print(" apply path:")
|
|
for command in rule["apply"].split(";"):
|
|
print(f" {command}")
|
|
else:
|
|
print(" apply path: none")
|
|
PY
|