From 5caa66ec8d82fbf24e4d5b7373da18bc1e2017df Mon Sep 17 00:00:00 2001 From: technofab Date: Tue, 20 Jan 2026 21:52:00 +0100 Subject: [PATCH] feat(cli): add check mode which exits 2 if hooks/files are outdated --- lib/module.nix | 145 +++++++++++++++++++++++------------------- tests/soonix_test.nix | 1 + 2 files changed, 82 insertions(+), 64 deletions(-) diff --git a/lib/module.nix b/lib/module.nix index f1c9942..ad57a16 100644 --- a/lib/module.nix +++ b/lib/module.nix @@ -154,57 +154,65 @@ 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 - hook = hooks.${hookName}; - modes = { - link = - # sh - '' - if [[ ! -L "${hook.output}" ]] || [[ "$(readlink "${hook.output}")" != "${hook.generatedDerivation}" ]]; then - _soonix_log "info" "${hookName}" "Creating symlink: ${hook.output} -> ${hook.generatedDerivation}" + runHooks = concatMapStringsSep "\n" (hookName: let + hook = hooks.${hookName}; + modes = { + link = + # sh + '' + 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}" - _changed=true else - _soonix_log "info" "${hookName}" "Symlink up to date: ${hook.output}" + _soonix_log "info" "${hookName}" "Would create symlink: ${hook.output} -> ${hook.generatedDerivation}" fi - ''; - copy = - # sh - '' - if [[ ! -f "${hook.output}" ]] || ! cmp -s "${hook.generatedDerivation}" "${hook.output}"; then - _soonix_log "info" "${hookName}" "Copying file: ${hook.generatedDerivation} -> ${hook.output}" + _changed=true + else + _soonix_log "info" "${hookName}" "Symlink up to date: ${hook.output}" + fi + ''; + copy = + # sh + '' + 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}" - _changed=true else - _soonix_log "info" "${hookName}" "File up to date: ${hook.output}" + _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}" + fi + ''; + }; - optionalGitignore = - if hook.hook.gitignore - then '' - _soonix_add_to_gitignore "${hook.output}" - '' - else ""; - in - builtins.addErrorContext "[soonix] while generating script for ${hookName}" - # sh + optionalGitignore = + if hook.hook.gitignore + then '' + _soonix_add_to_gitignore "${hook.output}" '' - # Process hook: ${hookName} + 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,13 +225,15 @@ in { ${modes.${hook.hook.mode} or (throw "Mode ${hook.hook.mode} doesnt exist")} - # Add to gitignore if requested - ${optionalGitignore} + if [[ "$CHECK_MODE" != "true" ]]; then + # Add to gitignore if requested + ${optionalGitignore} - # Run extra commands if file changed - if [[ "$_changed" == "true" && -n "${hook.hook.extra}" ]]; then - _soonix_log "info" "${hookName}" "Running extra command: ${hook.hook.extra}" - eval "${hook.hook.extra}" + # Run extra commands if file changed + if [[ "$_changed" == "true" && -n "${hook.hook.extra}" ]]; then + _soonix_log "info" "${hookName}" "Running extra command: ${hook.hook.extra}" + eval "${hook.hook.extra}" + fi fi if [[ "$_changed" == "true" ]]; then @@ -235,10 +245,11 @@ in { _soonix_log "error" "${hookName}" "Failed to process hook" _soonix_failed+=("${hookName}") } - '') - (getHookNames args); + fi + '') + (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;}} - } - _soonix - else - function _soonix() { - ${generateShellHook {}} - } - _soonix - fi + function _soonix() { + ${generateShellHook} + } + _soonix + ;; + check) + CHECK_MODE=true + echo "Checking files..." + function _soonix() { + ${generateShellHook} + } + _soonix ;; esac ''; diff --git a/tests/soonix_test.nix b/tests/soonix_test.nix index e5d266a..39b6bbf 100644 --- a/tests/soonix_test.nix +++ b/tests/soonix_test.nix @@ -87,6 +87,7 @@ in { assert_file_contains "${soonixBin}" "test.json" assert_file_contains "${soonixBin}" "SKIP_GITIGNORE" + assert_file_contains "${soonixBin}" "CHECK_MODE" ''; } ];