feat(cli): add check mode which exits 2 if hooks/files are outdated

This commit is contained in:
technofab 2026-01-20 21:52:00 +01:00
parent 374e31d852
commit 5caa66ec8d
Signed by: technofab
SSH key fingerprint: SHA256:bV4h88OqS/AxjbPn66uUdvK9JsgIW4tv3vwJQ8tpMqQ
2 changed files with 82 additions and 64 deletions

View file

@ -154,16 +154,7 @@ in {
config = let
hooks = config.hooks;
# allow excluding gitignore since stuff like renovate can't use/commit it anyways
getHookNames = {includeGitignored ? true}:
if includeGitignored
then (builtins.attrNames hooks)
else
map
(hook: hook.name)
(builtins.filter (hook: !hook.hook.gitignore) (builtins.attrValues hooks));
runHooks = args:
concatMapStringsSep "\n" (hookName: let
runHooks = concatMapStringsSep "\n" (hookName: let
hook = hooks.${hookName};
modes = {
link =
@ -171,8 +162,12 @@ in {
''
if [[ ! -L "${hook.output}" ]] || [[ "$(readlink "${hook.output}")" != "${hook.generatedDerivation}" ]]; then
_soonix_log "info" "${hookName}" "Creating symlink: ${hook.output} -> ${hook.generatedDerivation}"
if [[ "$CHECK_MODE" != "true" ]]; then
mkdir -p "$(dirname "${hook.output}")"
ln -sf "${hook.generatedDerivation}" "${hook.output}"
else
_soonix_log "info" "${hookName}" "Would create symlink: ${hook.output} -> ${hook.generatedDerivation}"
fi
_changed=true
else
_soonix_log "info" "${hookName}" "Symlink up to date: ${hook.output}"
@ -183,10 +178,14 @@ in {
''
if [[ ! -f "${hook.output}" ]] || ! cmp -s "${hook.generatedDerivation}" "${hook.output}"; then
_soonix_log "info" "${hookName}" "Copying file: ${hook.generatedDerivation} -> ${hook.output}"
if [[ "$CHECK_MODE" != "true" ]]; then
mkdir -p "$(dirname "${hook.output}")"
# required since they're read only
rm -f "${hook.output}"
cp "${hook.generatedDerivation}" "${hook.output}"
else
_soonix_log "info" "${hookName}" "Would copy file: ${hook.generatedDerivation} -> ${hook.output}"
fi
_changed=true
else
_soonix_log "info" "${hookName}" "File up to date: ${hook.output}"
@ -200,11 +199,20 @@ in {
_soonix_add_to_gitignore "${hook.output}"
''
else "";
isGitignored =
if hook.hook.gitignore
then "true"
else "false";
in
builtins.addErrorContext "[soonix] while generating script for ${hookName}"
# sh
''
# Process hook: ${hookName}
# Skip if SKIP_GITIGNORE is set and this hook is gitignored
if [[ "$SKIP_GITIGNORE" == "true" && "${isGitignored}" == "true" ]]; then
: # skip
else
while IFS= read -r line; do
case "$line" in
UPDATED) _soonix_updated+=("${hookName}") ;;
@ -217,6 +225,7 @@ in {
${modes.${hook.hook.mode} or (throw "Mode ${hook.hook.mode} doesnt exist")}
if [[ "$CHECK_MODE" != "true" ]]; then
# Add to gitignore if requested
${optionalGitignore}
@ -225,6 +234,7 @@ in {
_soonix_log "info" "${hookName}" "Running extra command: ${hook.hook.extra}"
eval "${hook.hook.extra}"
fi
fi
if [[ "$_changed" == "true" ]]; then
echo "UPDATED"
@ -235,10 +245,11 @@ in {
_soonix_log "error" "${hookName}" "Failed to process hook"
_soonix_failed+=("${hookName}")
}
fi
'')
(getHookNames args);
(builtins.attrNames hooks);
generateShellHook = args:
generateShellHook =
builtins.addErrorContext "[soonix] while generating shell hook"
# sh
''
@ -275,13 +286,16 @@ in {
_soonix_failed=()
_soonix_uptodate=()
${runHooks args}
${runHooks}
local output=$'\E[msoonix:\E[38;5;8m'
local status=0
if [[ ''${#_soonix_updated[@]} -gt 0 ]]; then
output="$output [updated: ''${_soonix_updated[*]}]" >&2
if [[ "$CHECK_MODE" == "true" ]]; then
status=2
fi
fi
if [[ ''${#_soonix_uptodate[@]} -gt 0 ]]; then
output="$output [unchanged: ''${_soonix_uptodate[*]}]" >&2
@ -293,8 +307,8 @@ in {
printf "%s\E[m\n" "$output" >&2
if [[ $status -eq 1 ]]; then
exit 1
if [[ $status -ne 0 ]]; then
exit $status
fi
'';
@ -308,7 +322,7 @@ in {
# nothing to do if no hooks exist
shellHook =
if (builtins.length (builtins.attrNames config.hooks) > 0)
then generateShellHook {}
then generateShellHook
else "";
shellHookFile = pkgs.writeShellScript "shellHook" shellHook;
devshellModule = {
@ -325,6 +339,7 @@ in {
set -euo pipefail
SKIP_GITIGNORE=false
CHECK_MODE=false
COMMAND=""
show_help() {
@ -336,6 +351,7 @@ in {
COMMANDS:
update Update all managed files
check Check if all files are up to date (dry-run)
list List all managed file targets
help Show this help message
@ -352,7 +368,7 @@ in {
while [[ $# -gt 0 ]]; do
case "$1" in
update|list|help)
update|check|list|help)
COMMAND="$1"
shift
;;
@ -396,17 +412,18 @@ in {
'') (builtins.attrNames hooks)}
;;
update)
if [[ "$SKIP_GITIGNORE" == "true" ]]; then
function _soonix() {
${generateShellHook {includeGitignored = false;}}
${generateShellHook}
}
_soonix
else
;;
check)
CHECK_MODE=true
echo "Checking files..."
function _soonix() {
${generateShellHook {}}
${generateShellHook}
}
_soonix
fi
;;
esac
'';

View file

@ -87,6 +87,7 @@ in {
assert_file_contains "${soonixBin}" "test.json"
assert_file_contains "${soonixBin}" "SKIP_GITIGNORE"
assert_file_contains "${soonixBin}" "CHECK_MODE"
'';
}
];