Files
Nomarchy/pkgs/nomarchy-what-changed/nomarchy-what-changed.sh
Bernardo Magri 639f553cb7
Some checks failed
Check / eval (push) Has been cancelled
feat(lifecycle): plain-language generation diffs (#82)
nomarchy-what-changed wraps nvd into "N added, M removed, K updated"
summaries. nomarchy-rebuild / nomarchy-home print full nvd and toast
the one-liner; System › What changed? offers toast + floating report.
checks.what-changed fixtures nvd via NOMARCHY_NVD.

Verified: V2 — flake check --no-build; checks.what-changed green;
local smoke on real HM generations.
2026-07-11 10:26:57 +01:00

179 lines
5.4 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.
# 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 <before> <after>" >&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: 02 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