# nomarchy-what-changed — plain-language "what changed last rebuild". # Wraps nvd into a short summary (for toasts / menu) and optional full # report. In-checkout state is not involved; this only reads Nix profiles. # # usage: # nomarchy-what-changed # system + home (last two gens each) # nomarchy-what-changed system|home # one layer # nomarchy-what-changed --summary … # one line per layer (toast body) # nomarchy-what-changed --diff PATH1 PATH2 # nomarchy-what-changed --summary --diff PATH1 PATH2 # # NOMARCHY_NVD overrides the nvd binary (tests inject a fixture). set -euo pipefail nvd_bin="${NOMARCHY_NVD:-nvd}" summary_only=0 mode="" # empty | system | home | diff diff_a="" diff_b="" usage() { echo "usage: nomarchy-what-changed [--summary] [system|home]" >&2 echo " nomarchy-what-changed [--summary] --diff " >&2 exit 64 } while [ $# -gt 0 ]; do case "$1" in --summary) summary_only=1; shift ;; --diff) mode="diff" shift [ $# -ge 2 ] || usage diff_a=$1; diff_b=$2; shift 2 ;; system|home) mode="$1"; shift ;; -h|--help) usage ;; *) usage ;; esac done # One-line plain-language summary of an nvd --color never report. # Prefer package row counts ([A.]/[R.]/[C.]); fall back to closure path delta. summarize_report() { local label=$1 report=$2 local added removed changed if printf '%s\n' "$report" | grep -q 'No version or selection state changes'; then printf '%s: no package changes\n' "$label" return fi # Count package rows (nvd tags: A added, R removed, C version/selection change). added=$(printf '%s\n' "$report" | grep -c '\[A\.' || true) removed=$(printf '%s\n' "$report" | grep -c '\[R\.' || true) changed=$(printf '%s\n' "$report" | grep -c '\[C\.' || true) # Strip trailing newline noise from grep -c on empty (already || true → 0). added=${added:-0}; removed=${removed:-0}; changed=${changed:-0} if [ "$added" -eq 0 ] && [ "$removed" -eq 0 ] && [ "$changed" -eq 0 ]; then # Closure-only deltas (sources, etc. without package names). local paths_a paths_r paths_a=$(printf '%s\n' "$report" \ | sed -n 's/.*(\([0-9]*\) paths added,.*/\1/p' | head -1) paths_r=$(printf '%s\n' "$report" \ | sed -n 's/.*paths added, \([0-9]*\) paths removed.*/\1/p' | head -1) paths_a=${paths_a:-0}; paths_r=${paths_r:-0} if [ "$paths_a" -eq 0 ] && [ "$paths_r" -eq 0 ]; then printf '%s: no package changes\n' "$label" else printf '%s: %s paths added, %s removed\n' "$label" "$paths_a" "$paths_r" fi return fi local parts=() [ "$added" -gt 0 ] && parts+=("$added added") [ "$removed" -gt 0 ] && parts+=("$removed removed") [ "$changed" -gt 0 ] && parts+=("$changed updated") local joined joined=$(printf '%s, ' "${parts[@]}") joined=${joined%, } printf '%s: %s\n' "$label" "$joined" } run_diff() { local before=$1 after=$2 label=$3 if [ ! -e "$before" ] || [ ! -e "$after" ]; then printf '%s: no previous generation to compare\n' "$label" return 0 fi if [ "$(readlink -f "$before" 2>/dev/null || echo "$before")" \ = "$(readlink -f "$after" 2>/dev/null || echo "$after")" ]; then printf '%s: no package changes\n' "$label" return 0 fi local report report=$("$nvd_bin" --color never diff "$before" "$after" 2>/dev/null) || { printf '%s: could not diff (is nvd installed?)\n' "$label" return 0 } if [ "$summary_only" -eq 1 ]; then summarize_report "$label" "$report" else printf '── %s ──\n' "$label" printf '%s\n' "$report" echo summarize_report "$label" "$report" fi } # Resolve last two links for a profile stem (…/system or …/home-manager). last_two() { local stem=$1 # shellcheck disable=SC2012 ls -d "${stem}"-[0-9]*-link 2>/dev/null | sort -V | tail -n 2 } system_pair() { last_two /nix/var/nix/profiles/system } home_pair() { local d d="${XDG_STATE_HOME:-$HOME/.local/state}/nix/profiles" if ls -d "$d"/home-manager-[0-9]*-link >/dev/null 2>&1; then last_two "$d/home-manager" return fi d="/nix/var/nix/profiles/per-user/${USER:-$(id -un)}" if ls -d "$d"/home-manager-[0-9]*-link >/dev/null 2>&1; then last_two "$d/home-manager" return fi return 0 } pair_to_before_after() { # stdin: 0–2 paths; sets before/after (before may be empty). local -a links=() while IFS= read -r line; do [ -n "$line" ] && links+=("$line") done before=""; after="" if [ "${#links[@]}" -ge 2 ]; then before=$(readlink -f "${links[0]}") after=$(readlink -f "${links[1]}") elif [ "${#links[@]}" -eq 1 ]; then after=$(readlink -f "${links[0]}") fi } if [ "$mode" = diff ]; then run_diff "$diff_a" "$diff_b" "Diff" exit 0 fi if [ -z "$mode" ] || [ "$mode" = system ]; then before=""; after="" pair_to_before_after < <(system_pair) if [ -n "$after" ] && [ -n "$before" ]; then run_diff "$before" "$after" "System" elif [ -n "$after" ]; then printf 'System: only one generation on disk — rebuild once more to compare\n' else printf 'System: no NixOS profile found\n' fi fi if [ -z "$mode" ] || [ "$mode" = home ]; then before=""; after="" pair_to_before_after < <(home_pair) if [ -n "$after" ] && [ -n "$before" ]; then run_diff "$before" "$after" "Desktop" elif [ -n "$after" ]; then printf 'Desktop: only one generation on disk — switch once more to compare\n' else printf 'Desktop: no Home Manager profile found\n' fi fi