feat(lifecycle): plain-language generation diffs (#82)
Some checks failed
Check / eval (push) Has been cancelled
Some checks failed
Check / eval (push) Has been cancelled
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.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
# Installed on both NixOS (systemPackages) and Home Manager so a home
|
||||
# switch can refresh them without waiting for a full system rebuild —
|
||||
# otherwise a broken nomarchy-pull can never update itself.
|
||||
{ lib, writeShellScriptBin, nvd, jq, symlinkJoin }:
|
||||
{ lib, writeShellScriptBin, nvd, jq, symlinkJoin, nomarchy-what-changed }:
|
||||
|
||||
let
|
||||
# Shared preamble: refuse root, resolve flake path.
|
||||
@@ -78,6 +78,13 @@ let
|
||||
else
|
||||
echo "nomarchy-rebuild: what changed:"
|
||||
${nvd}/bin/nvd diff "$before" "$after" || true
|
||||
# One-line toast for the session (full report already on the terminal).
|
||||
if command -v notify-send >/dev/null 2>&1 \
|
||||
&& command -v nomarchy-what-changed >/dev/null 2>&1; then
|
||||
body=$(nomarchy-what-changed --summary --diff "$before" "$after" 2>/dev/null \
|
||||
| sed 's/^Diff: //' || true)
|
||||
[ -n "''${body:-}" ] && notify-send -a Nomarchy "System rebuild" "$body" || true
|
||||
fi
|
||||
fi
|
||||
'';
|
||||
|
||||
@@ -87,6 +94,16 @@ let
|
||||
echo "nomarchy-home: no flake.nix at $flake" >&2
|
||||
exit 1
|
||||
fi
|
||||
# Snapshot the active HM generation before the switch so we can nvd it.
|
||||
hm_before=""
|
||||
for d in \
|
||||
"''${XDG_STATE_HOME:-$HOME/.local/state}/nix/profiles" \
|
||||
"/nix/var/nix/profiles/per-user/$(id -un)"; do
|
||||
if [ -L "$d/home-manager" ]; then
|
||||
hm_before=$(readlink -f "$d/home-manager" 2>/dev/null || true)
|
||||
break
|
||||
fi
|
||||
done
|
||||
log=$(mktemp)
|
||||
trap 'rm -f "$log"' EXIT
|
||||
set +e
|
||||
@@ -102,7 +119,27 @@ let
|
||||
echo "Recovery: home-manager generations (or docs/RECOVERY.md)"
|
||||
exit "$rc"
|
||||
fi
|
||||
echo "nomarchy-home: desktop applied."
|
||||
hm_after=""
|
||||
for d in \
|
||||
"''${XDG_STATE_HOME:-$HOME/.local/state}/nix/profiles" \
|
||||
"/nix/var/nix/profiles/per-user/$(id -un)"; do
|
||||
if [ -L "$d/home-manager" ]; then
|
||||
hm_after=$(readlink -f "$d/home-manager" 2>/dev/null || true)
|
||||
break
|
||||
fi
|
||||
done
|
||||
if [ -n "$hm_before" ] && [ -n "$hm_after" ] && [ "$hm_before" != "$hm_after" ]; then
|
||||
echo "nomarchy-home: what changed:"
|
||||
${nvd}/bin/nvd diff "$hm_before" "$hm_after" || true
|
||||
if command -v notify-send >/dev/null 2>&1 \
|
||||
&& command -v nomarchy-what-changed >/dev/null 2>&1; then
|
||||
body=$(nomarchy-what-changed --summary --diff "$hm_before" "$hm_after" 2>/dev/null \
|
||||
| sed 's/^Diff: //' || true)
|
||||
[ -n "''${body:-}" ] && notify-send -a Nomarchy "Desktop updated" "$body" || true
|
||||
fi
|
||||
else
|
||||
echo "nomarchy-home: desktop applied."
|
||||
fi
|
||||
'';
|
||||
|
||||
# Legacy aliases
|
||||
@@ -125,6 +162,7 @@ symlinkJoin {
|
||||
sys-update
|
||||
sys-rebuild
|
||||
home-update
|
||||
nomarchy-what-changed
|
||||
];
|
||||
meta = {
|
||||
description = "Nomarchy machine-flake lifecycle: pull, rebuild, home";
|
||||
|
||||
9
pkgs/nomarchy-what-changed/default.nix
Normal file
9
pkgs/nomarchy-what-changed/default.nix
Normal file
@@ -0,0 +1,9 @@
|
||||
# Plain-language generation diff (BACKLOG #82). Wraps nvd; PATH-resolved
|
||||
# notify is left to callers. NOMARCHY_NVD lets checks inject a fixture.
|
||||
{ writeShellApplication, coreutils, nvd, gnugrep, gnused }:
|
||||
|
||||
writeShellApplication {
|
||||
name = "nomarchy-what-changed";
|
||||
runtimeInputs = [ coreutils nvd gnugrep gnused ];
|
||||
text = builtins.readFile ./nomarchy-what-changed.sh;
|
||||
}
|
||||
178
pkgs/nomarchy-what-changed/nomarchy-what-changed.sh
Normal file
178
pkgs/nomarchy-what-changed/nomarchy-what-changed.sh
Normal file
@@ -0,0 +1,178 @@
|
||||
# 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: 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
|
||||
Reference in New Issue
Block a user