Compare commits

...

9 commits

Author SHA1 Message Date
5676a7e6b8 Merge branch 'feat/cli' into 'main'
feat!: add cli with update and list subcommands + gitignore param

Closes #3

See merge request TECHNOFAB/soonix!3
2026-04-07 16:01:03 +09:00
14077d4916 Merge branch 'renovate/lock-file-maintenance' into 'main'
chore(deps): lock file maintenance

See merge request TECHNOFAB/soonix!6
2026-04-07 16:00:52 +09:00
Renovate Bot
e508bfb474 chore(deps): lock file maintenance 2026-02-01 00:48:36 +00:00
aef4bc1a26
chore(module): wrap soonix script already in shell hook
makes it more flexible and should thus work everywhere (also in
pkgs.mkShell shellHook)
2026-01-21 19:24:23 +01:00
837ee45bed
fix(module): set CHECK_MODE and SKIP_GITIGNORE in shell hook to default
add mkCLI shorthand
2026-01-21 17:11:07 +01:00
5caa66ec8d
feat(cli): add check mode which exits 2 if hooks/files are outdated 2026-01-20 21:52:00 +01:00
374e31d852
chore(devShells): use new soonix syntax 2026-01-05 22:12:36 +01:00
039919d5a2
Merge branch 'main' into feat/cli 2026-01-05 22:12:15 +01:00
b39e16ec3c
feat!: add cli with update and list subcommands + gitignore param 2026-01-05 21:39:41 +01:00
8 changed files with 264 additions and 123 deletions

View file

@ -16,7 +16,7 @@
}, },
"postUpgradeTasks": { "postUpgradeTasks": {
"commands": [ "commands": [
"nix-portable nix run .#soonix:update" "nix-portable nix run .#soonix -- update --skip-gitignore"
] ]
} }
} }

18
flake.lock generated
View file

@ -2,11 +2,11 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1766902085, "lastModified": 1769461804,
"narHash": "sha256-coBu0ONtFzlwwVBzmjacUQwj3G+lybcZ1oeNSQkgC0M=", "narHash": "sha256-msG8SU5WsBUfVVa/9RPLaymvi5bI8edTavbIq3vRlhI=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c0b0e0fddf73fd517c3471e546c0df87a42d53f4", "rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -18,11 +18,11 @@
}, },
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"lastModified": 1754184128, "lastModified": 1766884708,
"narHash": "sha256-AjhoyBL4eSyXf01Bmc6DiuaMrJRNdWopmdnMY0Pa/M0=", "narHash": "sha256-x8nyRwtD0HMeYtX60xuIuZJbwwoI7/UKAdCiATnQNz0=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixpkgs.lib", "repo": "nixpkgs.lib",
"rev": "02e72200e6d56494f4a7c0da8118760736e41b60", "rev": "15177f81ad356040b4460a676838154cbf7f6213",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -37,11 +37,11 @@
}, },
"locked": { "locked": {
"dir": "lib", "dir": "lib",
"lastModified": 1766497301, "lastModified": 1768913456,
"narHash": "sha256-W7WeOXMUZROMtbU1qQNWy/yai+k8gG09YACFQ7ImpsQ=", "narHash": "sha256-P+uWjzg09q57Ur2jWCkGwNvk1bMyU20kUIKHYj+kxK0=",
"owner": "rensa-nix", "owner": "rensa-nix",
"repo": "core", "repo": "core",
"rev": "e08c48b5db1052bfb8b8dad764e05decc1af893e", "rev": "e5f47b57ae06f2fc1f888bcb56413baccb5d1062",
"type": "gitlab" "type": "gitlab"
}, },
"original": { "original": {

View file

@ -22,4 +22,5 @@ in rec {
}; };
mkShellHook = userConfig: (make userConfig).config.shellHook; mkShellHook = userConfig: (make userConfig).config.shellHook;
mkCLI = userConfig: (make userConfig).config.packages.soonix;
} }

View file

@ -4,7 +4,7 @@
lib, lib,
... ...
}: let }: let
inherit (lib) types mkOption concatMapStringsSep; inherit (lib) types mkOption concatMapStringsSep mapAttrsToList;
soonix_lib = import ./. {inherit pkgs;}; soonix_lib = import ./. {inherit pkgs;};
inherit (soonix_lib) engines buildAllFiles; inherit (soonix_lib) engines buildAllFiles;
in { in {
@ -146,15 +146,14 @@ in {
Packages for updating the hooks without a devshell. (readonly) Packages for updating the hooks without a devshell. (readonly)
''; '';
example = { example = {
"soonix:update" = "<derivation>"; "soonix" = "<derivation>";
}; };
}; };
}; };
config = let config = let
hooks = config.hooks; hooks = config.hooks;
hookNames = builtins.attrNames hooks; # allow excluding gitignore since stuff like renovate can't use/commit it anyways
runHooks = concatMapStringsSep "\n" (hookName: let runHooks = concatMapStringsSep "\n" (hookName: let
hook = hooks.${hookName}; hook = hooks.${hookName};
modes = { modes = {
@ -163,8 +162,12 @@ in {
'' ''
if [[ ! -L "${hook.output}" ]] || [[ "$(readlink "${hook.output}")" != "${hook.generatedDerivation}" ]]; then if [[ ! -L "${hook.output}" ]] || [[ "$(readlink "${hook.output}")" != "${hook.generatedDerivation}" ]]; then
_soonix_log "info" "${hookName}" "Creating symlink: ${hook.output} -> ${hook.generatedDerivation}" _soonix_log "info" "${hookName}" "Creating symlink: ${hook.output} -> ${hook.generatedDerivation}"
if [[ "$CHECK_MODE" != "true" ]]; then
mkdir -p "$(dirname "${hook.output}")" mkdir -p "$(dirname "${hook.output}")"
ln -sf "${hook.generatedDerivation}" "${hook.output}" ln -sf "${hook.generatedDerivation}" "${hook.output}"
else
_soonix_log "info" "${hookName}" "Would create symlink: ${hook.output} -> ${hook.generatedDerivation}"
fi
_changed=true _changed=true
else else
_soonix_log "info" "${hookName}" "Symlink up to date: ${hook.output}" _soonix_log "info" "${hookName}" "Symlink up to date: ${hook.output}"
@ -175,10 +178,14 @@ in {
'' ''
if [[ ! -f "${hook.output}" ]] || ! cmp -s "${hook.generatedDerivation}" "${hook.output}"; then if [[ ! -f "${hook.output}" ]] || ! cmp -s "${hook.generatedDerivation}" "${hook.output}"; then
_soonix_log "info" "${hookName}" "Copying file: ${hook.generatedDerivation} -> ${hook.output}" _soonix_log "info" "${hookName}" "Copying file: ${hook.generatedDerivation} -> ${hook.output}"
if [[ "$CHECK_MODE" != "true" ]]; then
mkdir -p "$(dirname "${hook.output}")" mkdir -p "$(dirname "${hook.output}")"
# required since they're read only # required since they're read only
rm -f "${hook.output}" rm -f "${hook.output}"
cp "${hook.generatedDerivation}" "${hook.output}" cp "${hook.generatedDerivation}" "${hook.output}"
else
_soonix_log "info" "${hookName}" "Would copy file: ${hook.generatedDerivation} -> ${hook.output}"
fi
_changed=true _changed=true
else else
_soonix_log "info" "${hookName}" "File up to date: ${hook.output}" _soonix_log "info" "${hookName}" "File up to date: ${hook.output}"
@ -192,10 +199,20 @@ in {
_soonix_add_to_gitignore "${hook.output}" _soonix_add_to_gitignore "${hook.output}"
'' ''
else ""; else "";
isGitignored =
if hook.hook.gitignore
then "true"
else "false";
in in
builtins.addErrorContext "[soonix] while generating script for ${hookName}"
# sh # sh
'' ''
# Process hook: ${hookName} # 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 while IFS= read -r line; do
case "$line" in case "$line" in
UPDATED) _soonix_updated+=("${hookName}") ;; UPDATED) _soonix_updated+=("${hookName}") ;;
@ -208,6 +225,7 @@ in {
${modes.${hook.hook.mode} or (throw "Mode ${hook.hook.mode} doesnt exist")} ${modes.${hook.hook.mode} or (throw "Mode ${hook.hook.mode} doesnt exist")}
if [[ "$CHECK_MODE" != "true" ]]; then
# Add to gitignore if requested # Add to gitignore if requested
${optionalGitignore} ${optionalGitignore}
@ -216,6 +234,7 @@ in {
_soonix_log "info" "${hookName}" "Running extra command: ${hook.hook.extra}" _soonix_log "info" "${hookName}" "Running extra command: ${hook.hook.extra}"
eval "${hook.hook.extra}" eval "${hook.hook.extra}"
fi fi
fi
if [[ "$_changed" == "true" ]]; then if [[ "$_changed" == "true" ]]; then
echo "UPDATED" echo "UPDATED"
@ -226,12 +245,18 @@ in {
_soonix_log "error" "${hookName}" "Failed to process hook" _soonix_log "error" "${hookName}" "Failed to process hook"
_soonix_failed+=("${hookName}") _soonix_failed+=("${hookName}")
} }
fi
'') '')
hookNames; (builtins.attrNames hooks);
generatedShellHook = generateShellHook =
builtins.addErrorContext "[soonix] while generating shell hook"
# sh # sh
'' ''
function _soonix() {
CHECK_MODE=''${CHECK_MODE:-false}
SKIP_GITIGNORE=''${SKIP_GITIGNORE:-false}
_soonix_log() { _soonix_log() {
local level="$1" local level="$1"
local hook="$2" local hook="$2"
@ -272,6 +297,9 @@ in {
if [[ ''${#_soonix_updated[@]} -gt 0 ]]; then if [[ ''${#_soonix_updated[@]} -gt 0 ]]; then
output="$output [updated: ''${_soonix_updated[*]}]" >&2 output="$output [updated: ''${_soonix_updated[*]}]" >&2
if [[ "$CHECK_MODE" == "true" ]]; then
status=2
fi
fi fi
if [[ ''${#_soonix_uptodate[@]} -gt 0 ]]; then if [[ ''${#_soonix_uptodate[@]} -gt 0 ]]; then
output="$output [unchanged: ''${_soonix_uptodate[*]}]" >&2 output="$output [unchanged: ''${_soonix_uptodate[*]}]" >&2
@ -283,13 +311,16 @@ in {
printf "%s\E[m\n" "$output" >&2 printf "%s\E[m\n" "$output" >&2
if [[ $status -eq 1 ]]; then if [[ $status -ne 0 ]]; then
exit 1 exit $status
fi fi
}
_soonix
unset _soonix
''; '';
allFiles = allFiles =
lib.mapAttrsToList (name: hook: { mapAttrsToList (name: hook: {
src = hook.generatedDerivation; src = hook.generatedDerivation;
path = hook.output; path = hook.output;
}) })
@ -297,8 +328,8 @@ in {
in rec { in rec {
# nothing to do if no hooks exist # nothing to do if no hooks exist
shellHook = shellHook =
if (builtins.length hookNames > 0) if (builtins.length (builtins.attrNames config.hooks) > 0)
then generatedShellHook then generateShellHook
else ""; else "";
shellHookFile = pkgs.writeShellScript "shellHook" shellHook; shellHookFile = pkgs.writeShellScript "shellHook" shellHook;
devshellModule = { devshellModule = {
@ -307,11 +338,96 @@ in {
}; };
finalFiles = buildAllFiles allFiles; finalFiles = buildAllFiles allFiles;
# make it simpler to update the hooks without any devshell # make it simpler to update the hooks without any devshell
packages."soonix:update" = pkgs.writeShellScriptBin "soonix:update" '' packages = {
function _soonix() { soonix =
${shellHook} pkgs.writeShellScriptBin "soonix"
# sh
''
set -euo pipefail
SKIP_GITIGNORE=false
CHECK_MODE=false
COMMAND=""
show_help() {
cat << EOF
soonix - Declarative file management tool
USAGE:
soonix <COMMAND> [OPTIONS]
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
OPTIONS:
--skip-gitignore Skip files that would be added to .gitignore
EXAMPLES:
soonix update # Update all files
soonix update --skip-gitignore # Update only non-gitignored files
soonix list # List all targets
soonix list --skip-gitignore # List only non-gitignored targets
EOF
} }
_soonix
while [[ $# -gt 0 ]]; do
case "$1" in
update|check|list|help)
COMMAND="$1"
shift
;;
--skip-gitignore)
SKIP_GITIGNORE=true
shift
;;
*)
echo "Error: Unknown argument '$1'" >&2
echo ""
show_help
exit 1
;;
esac
done
if [[ -z "$COMMAND" ]]; then
echo "Error: No command specified" >&2
echo ""
show_help
exit 1
fi
case "$COMMAND" in
help)
show_help
;;
list)
${concatMapStringsSep "\n" (hookName: let
hook = hooks.${hookName};
in ''
if [[ "$SKIP_GITIGNORE" == "true" && "${
if hook.hook.gitignore
then "true"
else "false"
}" == "true" ]]; then
: # skip
else
echo "${hook.output}"
fi
'') (builtins.attrNames hooks)}
;;
update)
${generateShellHook}
;;
check)
CHECK_MODE=true
echo "Checking files..."
${generateShellHook}
;;
esac
''; '';
}; };
};
} }

View file

@ -41,8 +41,10 @@ in {
} }
{ {
name = "soonix"; name = "soonix";
files = "nix run .#soonix -- list --skip-gitignore";
stage_fixed = true; stage_fixed = true;
run = "nix run .#soonix:update"; # {files} is needed so lefthook git add's them
run = "nix run .#soonix -- update --skip-gitignore; #{files}";
} }
]; ];
}; };

19
nix/repo/flake.lock generated
View file

@ -57,11 +57,11 @@
"nixmkdocs-lib": { "nixmkdocs-lib": {
"locked": { "locked": {
"dir": "lib", "dir": "lib",
"lastModified": 1766404754, "lastModified": 1767549915,
"narHash": "sha256-EjBe6x6BT8ckPirMWhSf1GfaFxORYxR/Uu71FvSAm60=", "narHash": "sha256-by3r2qddlyzylup5fzSaDwtoy3eFHNKb65IuIq6bsAs=",
"owner": "TECHNOFAB", "owner": "TECHNOFAB",
"repo": "nixmkdocs", "repo": "nixmkdocs",
"rev": "cfa9606eeeb9288e2799896d7d42b3d3860f9ccb", "rev": "f3b2f4b19178e97c5580367be0f97e61a085db6d",
"type": "gitlab" "type": "gitlab"
}, },
"original": { "original": {
@ -74,16 +74,17 @@
"nixtest-lib": { "nixtest-lib": {
"locked": { "locked": {
"dir": "lib", "dir": "lib",
"lastModified": 1766514204, "lastModified": 1765728058,
"narHash": "sha256-jK6gHvJJjUpEWrPvraBgstsF7oifatuWsj9suy7GFus=", "narHash": "sha256-V3FXECl1oTxEtGteNz3o3GJs/X8asSn1TxRpZ2F+htU=",
"owner": "TECHNOFAB", "owner": "TECHNOFAB",
"repo": "nixtest", "repo": "nixtest",
"rev": "b87e38847d84973e84b6727b24fea48b7bc108c1", "rev": "2477ad31ae3aa4134e1bb5eeddbebe0cb64ccb57",
"type": "gitlab" "type": "gitlab"
}, },
"original": { "original": {
"dir": "lib", "dir": "lib",
"owner": "TECHNOFAB", "owner": "TECHNOFAB",
"ref": "v1.2.1",
"repo": "nixtest", "repo": "nixtest",
"type": "gitlab" "type": "gitlab"
} }
@ -101,11 +102,11 @@
"treefmt-nix": { "treefmt-nix": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1767122417, "lastModified": 1769691507,
"narHash": "sha256-yOt/FTB7oSEKQH9EZMFMeuldK1HGpQs2eAzdS9hNS/o=", "narHash": "sha256-8aAYwyVzSSwIhP2glDhw/G0i5+wOrren3v6WmxkVonM=",
"owner": "numtide", "owner": "numtide",
"repo": "treefmt-nix", "repo": "treefmt-nix",
"rev": "dec15f37015ac2e774c84d0952d57fcdf169b54d", "rev": "28b19c5844cc6e2257801d43f2772a4b4c050a1b",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -14,7 +14,7 @@ in
data = { data = {
extends = ["config:recommended"]; extends = ["config:recommended"];
postUpgradeTasks.commands = [ postUpgradeTasks.commands = [
"nix-portable nix run .#soonix:update" "nix-portable nix run .#soonix -- update --skip-gitignore"
]; ];
lockFileMaintenance = { lockFileMaintenance = {
enabled = true; enabled = true;

View file

@ -78,6 +78,27 @@ in {
assert_file_contains ${shellHook} "gomplate" assert_file_contains ${shellHook} "gomplate"
''; '';
} }
{
name = "packages";
type = "script";
script = let
conf = (soonix.make {inherit hooks;}).config;
soonixBin = conf.packages.soonix + "/bin/soonix";
in
# sh
''
${ntlib.helpers.path [pkgs.gnugrep]}
${ntlib.helpers.scriptHelpers}
assert -f "${soonixBin}" "should exist"
assert_file_contains "${soonixBin}" "gotmpl"
assert_file_contains "${soonixBin}" "test.json"
assert_file_contains "${soonixBin}" "SKIP_GITIGNORE"
assert_file_contains "${soonixBin}" "CHECK_MODE"
'';
}
]; ];
}; };
} }