From db488d8f416ae0280e6dcfe87afa6d2c83ea179e Mon Sep 17 00:00:00 2001 From: technofab Date: Wed, 17 Sep 2025 11:59:53 +0200 Subject: [PATCH] chore: initial commit --- .envrc | 2 + .gitignore | 2 + .gitlab-ci.yml | 5 ++ README.md | 21 +++++ docs/images/logo.svg | 1 + docs/index.md | 19 ++++ docs/options.md | 3 + docs/style.css | 15 ++++ flake.lock | 63 +++++++++++++ flake.nix | 35 ++++++++ lib/default.nix | 3 + lib/flake.nix | 5 ++ lib/modules/cocogitto.nix | 59 +++++++++++++ lib/modules/cocogitto_test.nix | 28 ++++++ lib/modules/default.nix | 7 ++ lib/modules/lefthook.nix | 152 ++++++++++++++++++++++++++++++++ lib/modules/lefthook_helpers.sh | 149 +++++++++++++++++++++++++++++++ lib/modules/lefthook_test.nix | 46 ++++++++++ lib/modules/taskfile.nix | 102 +++++++++++++++++++++ lib/modules/taskfile_test.nix | 46 ++++++++++ nix/repo/ci.nix | 34 +++++++ nix/repo/devShells.nix | 60 +++++++++++++ nix/repo/docs.nix | 68 ++++++++++++++ nix/repo/flake.lock | 118 +++++++++++++++++++++++++ nix/repo/flake.nix | 24 +++++ nix/repo/tests.nix | 10 +++ 26 files changed, 1077 insertions(+) create mode 100644 .envrc create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 README.md create mode 100755 docs/images/logo.svg create mode 100644 docs/index.md create mode 100644 docs/options.md create mode 100644 docs/style.css create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 lib/default.nix create mode 100644 lib/flake.nix create mode 100644 lib/modules/cocogitto.nix create mode 100644 lib/modules/cocogitto_test.nix create mode 100644 lib/modules/default.nix create mode 100644 lib/modules/lefthook.nix create mode 100644 lib/modules/lefthook_helpers.sh create mode 100644 lib/modules/lefthook_test.nix create mode 100644 lib/modules/taskfile.nix create mode 100644 lib/modules/taskfile_test.nix create mode 100644 nix/repo/ci.nix create mode 100644 nix/repo/devShells.nix create mode 100644 nix/repo/docs.nix create mode 100644 nix/repo/flake.lock create mode 100644 nix/repo/flake.nix create mode 100644 nix/repo/tests.nix diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..565a52a --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +source $(fetchurl https://gitlab.com/rensa-nix/direnv/-/raw/v0.3.0/direnvrc "sha256-u7+KEz684NnIZ+Vh5x5qLrt8rKdnUNexewBoeTcEVHQ=") +use ren //repo/devShells/default diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c4f190 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.ren +result diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..a415272 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,5 @@ +# Generated by soonix, DO NOT EDIT +include: +- component: gitlab.com/TECHNOFAB/nix-gitlab-ci/nix-gitlab-ci@3.0.0-alpha.2 + inputs: + version: 3.0.0-alpha.2 diff --git a/README.md b/README.md new file mode 100644 index 0000000..d042ed1 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Rensa Devtools + +Utilities and dev tools with integration into the Rensa ecosystem (mainly devshell). + +## Usage + +```nix +let + devshell = inputs.devshell.lib { inherit pkgs; }; +in +devshell.mkShell { + imports = [inputs.devtools.devshellModule]; + + # use the modules + lefthook.enable = true; +} +``` + +## Docs + +See [docs](https://devtools.rensa.projects.tf). diff --git a/docs/images/logo.svg b/docs/images/logo.svg new file mode 100755 index 0000000..d114fe7 --- /dev/null +++ b/docs/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..aeec6be --- /dev/null +++ b/docs/index.md @@ -0,0 +1,19 @@ +# Rensa Devtools + +Utilities and dev tools with integration into the Rensa ecosystem (mainly devshell). + +## Usage + +```nix +let + devshell = inputs.devshell.lib { inherit pkgs; }; +in +devshell.mkShell { + imports = [inputs.devtools.devshellModule]; + + # use the modules + lefthook.enable = true; +} +``` + +[See all options](./options.md) diff --git a/docs/options.md b/docs/options.md new file mode 100644 index 0000000..4ca74a4 --- /dev/null +++ b/docs/options.md @@ -0,0 +1,3 @@ +# Options + +{% include 'options.md' %} diff --git a/docs/style.css b/docs/style.css new file mode 100644 index 0000000..b2ae4ff --- /dev/null +++ b/docs/style.css @@ -0,0 +1,15 @@ +.md-header__button.md-logo { + margin: 0; + padding-top: .2rem; + padding-bottom: .2rem; +} + +[dir="ltr"] .md-header__title { + margin-left: 0; +} + +.md-header__button.md-logo img, +.md-header__button.md-logo svg { + height: 2rem; +} + diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..7dd88b1 --- /dev/null +++ b/flake.lock @@ -0,0 +1,63 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1757487488, + "narHash": "sha256-zwE/e7CuPJUWKdvvTCB7iunV4E/+G0lKfv4kk/5Izdg=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ab0f3607a6c7486ea22229b92ed2d355f1482ee0", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1754184128, + "narHash": "sha256-AjhoyBL4eSyXf01Bmc6DiuaMrJRNdWopmdnMY0Pa/M0=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "02e72200e6d56494f4a7c0da8118760736e41b60", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "ren": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "dir": "lib", + "lastModified": 1756370106, + "narHash": "sha256-l84ojcHuQWBwn4BRxQsMMfQpcq/Az/sHh/hSqFgVtyg=", + "owner": "rensa-nix", + "repo": "core", + "rev": "9c1a29fa9ba7cbbb78b9e47eb8afbcd29303a3b4", + "type": "gitlab" + }, + "original": { + "dir": "lib", + "owner": "rensa-nix", + "repo": "core", + "type": "gitlab" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "ren": "ren" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..88fdf2e --- /dev/null +++ b/flake.nix @@ -0,0 +1,35 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + ren.url = "gitlab:rensa-nix/core?dir=lib"; + }; + + outputs = { + ren, + self, + ... + } @ inputs: + ren.buildWith + { + inherit inputs; + cellsFrom = ./nix; + transformInputs = system: i: + i + // { + pkgs = import i.nixpkgs {inherit system;}; + }; + cellBlocks = with ren.blocks; [ + (simple "devShells") + (simple "tests") + (simple "docs") + (simple "ci") + ]; + } + { + packages = ren.select self [ + ["repo" "tests"] + ["repo" "docs"] + ["repo" "ci" "packages"] + ]; + }; +} diff --git a/lib/default.nix b/lib/default.nix new file mode 100644 index 0000000..32fc3ee --- /dev/null +++ b/lib/default.nix @@ -0,0 +1,3 @@ +_: { + devshellModule = ./modules; +} diff --git a/lib/flake.nix b/lib/flake.nix new file mode 100644 index 0000000..ccd8411 --- /dev/null +++ b/lib/flake.nix @@ -0,0 +1,5 @@ +{ + outputs = _i: { + devshellModule = ./modules; + }; +} diff --git a/lib/modules/cocogitto.nix b/lib/modules/cocogitto.nix new file mode 100644 index 0000000..4198a9b --- /dev/null +++ b/lib/modules/cocogitto.nix @@ -0,0 +1,59 @@ +{ + lib, + pkgs, + config, + ... +}: let + inherit (lib) mkEnableOption mkOption mkIf types; + cfg = config.cocogitto; + + # we need a newer version than in nixpkgs, since the PR which adds `--config` + # didn't land in a release yet + cocogitto = pkgs.cocogitto.overrideAttrs (_prev: { + version = "2025-09-11"; + src = pkgs.fetchFromGitHub { + owner = "oknozor"; + repo = "cocogitto"; + rev = "031cc238cb3e3e8aa3a525c1df089c3e70020efc"; + hash = "sha256-fyhugacBLJPMqHWxoxBTFhIE3wHDB9xdrqJYzJc36I0="; + }; + }); + + configFile = (pkgs.formats.toml {}).generate "cog.toml" cfg.config; + cogAlias = pkgs.writeTextFile { + name = "cog-alias"; + destination = "/bin/${cfg.alias}"; + executable = true; + text = + # sh + '' + ${cocogitto}/bin/cog --config "${configFile}" ''${@:1} + ''; + }; +in { + options.cocogitto = { + enable = + mkEnableOption "Cocogitto" + // { + default = cfg.config != {}; + }; + alias = mkOption { + type = types.str; + default = "cog"; + description = '' + Alias for `cog`. + ''; + }; + config = mkOption { + type = types.attrs; + default = {}; + description = '' + Configure cocogitto here. + ''; + }; + }; + + config = mkIf cfg.enable { + packages = [cogAlias]; + }; +} diff --git a/lib/modules/cocogitto_test.nix b/lib/modules/cocogitto_test.nix new file mode 100644 index 0000000..71a8c8e --- /dev/null +++ b/lib/modules/cocogitto_test.nix @@ -0,0 +1,28 @@ +{ + ntlib, + devshell, + ... +}: let + module = ./cocogitto.nix; +in { + suites."Cocogitto" = { + pos = __curPos; + tests = [ + { + name = "basic"; + type = "script"; + script = let + shell = devshell.mkShell { + imports = [module]; + cocogitto.enable = true; + }; + in + # sh + '' + ${ntlib.helpers.scriptHelpers} + assert "-f ${shell}/bin/cog" "/bin/cog should exist" + ''; + } + ]; + }; +} diff --git a/lib/modules/default.nix b/lib/modules/default.nix new file mode 100644 index 0000000..7fbd308 --- /dev/null +++ b/lib/modules/default.nix @@ -0,0 +1,7 @@ +{ + imports = [ + ./lefthook.nix + ./taskfile.nix + ./cocogitto.nix + ]; +} diff --git a/lib/modules/lefthook.nix b/lib/modules/lefthook.nix new file mode 100644 index 0000000..b8f48b6 --- /dev/null +++ b/lib/modules/lefthook.nix @@ -0,0 +1,152 @@ +{ + lib, + pkgs, + config, + ... +}: let + inherit (lib) concatStringsSep concatMapStringsSep subtractLists mkEnableOption mkOption types mkIf; + cfg = config.lefthook; + + allHookNames = [ + "applypatch-msg" + "pre-applypatch" + "post-applypatch" + "pre-commit" + "prepare-commit-msg" + "commit-msg" + "post-commit" + "pre-rebase" + "post-rewrite" + "post-checkout" + "post-merge" + "pre-push" + "pre-auto-gc" + "post-update" + "sendemail-validate" + "fsmonitor-watchman" + "p4-changelist" + "p4-prepare-changelist" + "p4-post-changelist" + "p4-pre-submit" + "post-index-change" + "pre-receive" + "update" + "proc-receive" + "reference-transaction" + "push-to-checkout" + "pre-merge-commit" + ]; + currentHookNames = builtins.filter (h: builtins.elem h allHookNames) (builtins.attrNames cfg.config); + unusedNixHookNames = subtractLists allHookNames currentHookNames; + unusedNixHookNamesStr = concatStringsSep " " unusedNixHookNames; + + hookContent = hookName: + pkgs.writeShellScript hookName '' + if [ "$LEFTHOOK" = "0" ]; then + exit 0 + fi + + ${lefthookAlias}/bin/${cfg.alias} run "${hookName}" "$@" + ''; + + lefthookConfig = (pkgs.formats.yaml {}).generate "lefthook.yaml" cfg.config; + lefthookAlias = pkgs.writeShellScriptBin cfg.alias '' + if [ "$1" = "install" ]; then + echo "Warning, using 'lefthook install' should not be used, use the shellHook instead" + fi + if [ "$1" = "install" ]; then + echo "Warning, using 'lefthook install' should not be used, use the shellHook instead" + fi + if [ "$1" = "install" ]; then + echo "Warning, using 'lefthook install' should not be used, use the shellHook instead" + fi + LEFTHOOK_CONFIG="${lefthookConfig}" ${pkgs.lefthook}/bin/lefthook ''${@:1} + ''; +in { + options.lefthook = { + enable = + mkEnableOption "Lefthook" + // { + default = cfg.config != {}; + }; + shellHook = mkOption { + type = types.bool; + default = true; + description = '' + Whether to add a shell hook which automatically installs the git hooks. + ''; + }; + alias = mkOption { + type = types.str; + default = "lefthook"; + example = "hooks"; + description = '' + Alias for the lefthook command. + ''; + }; + config = mkOption { + type = types.attrs; + default = {}; + description = '' + Config for lefthook. See https://lefthook.dev/configuration/. + ''; + example = { + pre-commit = { + parallel = true; + jobs = [ + { + name = "hello"; + run = "echo world"; + } + ]; + }; + }; + }; + + outputs = { + shellHook = mkOption { + type = types.str; + readOnly = true; + description = '' + The script to run on shell activation. + It automatically installs the git hooks and removes unused/previously used ones. + ''; + }; + }; + }; + config = { + lefthook.outputs.shellHook = + # sh + '' + ${builtins.readFile ./lefthook_helpers.sh} + + # ensure git is available and we are in a Git repository + if ! command -v git &> /dev/null; then + __log ERROR "Git command not found. Cannot manage Git hooks." >&2 + return 1 + fi + + local GIT_REAL_DIR + GIT_REAL_DIR=$(git rev-parse --git-dir 2>/dev/null) + if [ $? -ne 0 ]; then + __log INFO "Not inside a Git repository. Skipping Git hook setup." >&2 + return 0 + fi + # Use realpath to handle .git file for worktrees, and resolve relative path + GIT_REAL_DIR=$(realpath "$GIT_REAL_DIR") + + # clean up unused hooks + cleanup_git_hooks "${unusedNixHookNamesStr}" + + ${concatMapStringsSep "\n" (hook: '' + setup_git_hook "${hook}" "${hookContent hook}" + '') + currentHookNames} + ''; + packages = mkIf cfg.enable [lefthookAlias]; + enterShellCommands."lefthook" = mkIf (cfg.enable && cfg.shellHook) { + text = cfg.outputs.shellHook; + deps = ["env"]; + }; + }; +} diff --git a/lib/modules/lefthook_helpers.sh b/lib/modules/lefthook_helpers.sh new file mode 100644 index 0000000..9e96bcd --- /dev/null +++ b/lib/modules/lefthook_helpers.sh @@ -0,0 +1,149 @@ +# Usage: __log "message" +# LEVEL can be: TRACE, INFO, WARN, ERROR +__log() { + local level="$1" msg="$2" + local format=$'\E[mlefthook: \E[38;5;8m%s\E[m\n' + case "$level" in + TRACE) + if [[ -n "${LEFTHOOK_VERBOSE:-}" ]]; then + # shellcheck disable=SC2059 + printf "$format" "[TRACE] ${msg}" >&2 + fi + ;; + INFO) + if [[ -n "${LEFTHOOK_VERBOSE:-}" ]]; then + # shellcheck disable=SC2059 + printf "$format" "$msg" >&2 + fi + ;; + WARN) + # shellcheck disable=SC2059 + printf "$format" "[WARN] ${msg}" >&2 + ;; + ERROR) + # shellcheck disable=SC2059 + printf "$format" "[ERROR] ${msg}" >&2 + ;; + esac +} + +# Helper to get the current Git hooks directory, supporting worktrees. +# Returns the absolute path to the hooks directory. +_get_git_hooks_dir() { + # Check if we are inside a Git repository + if ! command -v git &> /dev/null; then + __log ERROR "Git command not found. Cannot resolve Git hooks directory." + return 1 + fi + if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + __log ERROR "Not inside a Git repository. Cannot resolve Git hooks directory." + return 1 + fi + local GIT_DIR_PATH + GIT_DIR_PATH=$(git rev-parse --git-dir) + GIT_DIR_PATH=$(realpath "$GIT_DIR_PATH") # Resolve to absolute path + echo "${GIT_DIR_PATH}/hooks" + return 0 +} + +# Function to set up a Git hook as a symlink to a Nix store path. +# Usage: setup_git_hook +setup_git_hook() { + local HOOK_NAME="$1" + local NIX_HOOK_PATH="$2" + local NIX_STORE_PATTERN="/nix/store/" # Pattern to identify Nix store paths + + local HOOK_DIR + HOOK_DIR=$(_get_git_hooks_dir) + if [ $? -ne 0 ]; then + return 1 # Error from _get_git_hooks_dir + fi + + local HOOK_FILE="${HOOK_DIR}/${HOOK_NAME}" + local BACKUP_FILE="${HOOK_FILE}.old" + + # Ensure the hooks directory exists + mkdir -p "$HOOK_DIR" + + if [ -e "$HOOK_FILE" ]; then # Check if anything exists at the hook path + if [ -L "$HOOK_FILE" ]; then # Existing hook is a symlink + local CURRENT_TARGET + CURRENT_TARGET=$(readlink "$HOOK_FILE") + + if [[ "$CURRENT_TARGET" == "$NIX_HOOK_PATH" ]]; then + __log TRACE "Hook '$HOOK_NAME' already exists and points to the correct Nix store path. Doing nothing." + return 0 + elif [[ "$CURRENT_TARGET" == $NIX_STORE_PATTERN* ]]; then + __log TRACE "Hook '$HOOK_NAME' is a symlink to a different Nix store path. Replacing it with the new symlink." + rm "$HOOK_FILE" + else + __log INFO "Hook '$HOOK_NAME' is a symlink but not to a Nix store path. Backing up to '$BACKUP_FILE'." + mv "$HOOK_FILE" "$BACKUP_FILE" + fi + elif [ -f "$HOOK_FILE" ]; then # Existing hook is a regular file + # Assumption: any non-symlink hook file is not ours + __log INFO "Hook '$HOOK_NAME' is an existing regular file (not managed by us). Backing up to '$BACKUP_FILE'." + mv "$HOOK_FILE" "$BACKUP_FILE" + else # Existing hook is neither a symlink nor a regular file (e.g., directory) + __log WARN "Hook '$HOOK_NAME' exists but is not a regular file or symlink. Please check your hooks and remove the hook manually just in case." + exit 1 + fi + fi + + __log INFO "Creating symlink for '$HOOK_NAME' to '$NIX_HOOK_PATH'." + ln -s "$NIX_HOOK_PATH" "$HOOK_FILE" +} + +# Function to clean up specific Git hooks that are no longer used by Nix. +# It removes symlinks that point to the Nix store for the given hook names +# and restores any .old backups if they exist. +# Usage: cleanup_git_hooks +cleanup_git_hooks() { + local UNUSED_HOOK_NAMES_STR="$1" + local NIX_STORE_PATTERN="/nix/store/" + + if [ -z "$UNUSED_HOOK_NAMES_STR" ]; then + __log TRACE "No unused hooks specified for cleanup. Doing nothing." + return 0 + fi + + local HOOK_DIR + HOOK_DIR=$(_get_git_hooks_dir) + if [ $? -ne 0 ]; then + return 1 # Error from _get_git_hooks_dir + fi + + if [ ! -d "$HOOK_DIR" ]; then + __log TRACE "No hooks directory found at '$HOOK_DIR'. Nothing to clean up." + return 0 + fi + + __log TRACE "Cleaning up unused Git hooks in '$HOOK_DIR'..." + for HOOK_NAME in $UNUSED_HOOK_NAMES_STR; do + local HOOK_FILE_PATH="${HOOK_DIR}/${HOOK_NAME}" + + if [ -L "$HOOK_FILE_PATH" ]; then # Only consider symlinks + local TARGET + TARGET=$(readlink "$HOOK_FILE_PATH") + + # Check if the symlink points to a Nix store path + if [[ "$TARGET" == $NIX_STORE_PATTERN* ]]; then + __log INFO "Removing unused Nix-managed hook: '$HOOK_NAME' (points to '$TARGET')" + rm "$HOOK_FILE_PATH" + + # Check for a corresponding .old file and restore it if it exists + local OLD_HOOK="${HOOK_FILE_PATH}.old" + if [ -e "$OLD_HOOK" ]; then + __log INFO "Restoring backup: '$OLD_HOOK' to '${HOOK_FILE_PATH}'" + mv "$OLD_HOOK" "$HOOK_FILE_PATH" + fi + else + __log TRACE "Hook '$HOOK_NAME' is a symlink but not to a Nix store path. Skipping cleanup (it's not our managed symlink)." + fi + elif [ -e "$HOOK_FILE_PATH" ]; then + __log TRACE "Hook '$HOOK_NAME' exists but is not a symlink (not managed by us). Skipping cleanup." + else + __log TRACE "Hook '$HOOK_NAME' does not exist. Skipping cleanup." + fi + done +} diff --git a/lib/modules/lefthook_test.nix b/lib/modules/lefthook_test.nix new file mode 100644 index 0000000..18b8d38 --- /dev/null +++ b/lib/modules/lefthook_test.nix @@ -0,0 +1,46 @@ +{ + ntlib, + devshell, + ... +}: let + module = ./lefthook.nix; +in { + suites."Lefthook" = { + pos = __curPos; + tests = [ + { + name = "basic"; + type = "script"; + script = let + shell = devshell.mkShell { + imports = [module]; + lefthook.enable = true; + }; + in + # sh + '' + ${ntlib.helpers.scriptHelpers} + assert "-f ${shell}/bin/lefthook" "/bin/lefthook should exist" + ''; + } + { + name = "alias"; + type = "script"; + script = let + shell = devshell.mkShell { + imports = [module]; + lefthook = { + enable = true; + alias = "hooks"; + }; + }; + in + # sh + '' + ${ntlib.helpers.scriptHelpers} + assert "-f ${shell}/bin/hooks" "/bin/hooks should exist" + ''; + } + ]; + }; +} diff --git a/lib/modules/taskfile.nix b/lib/modules/taskfile.nix new file mode 100644 index 0000000..02d20ae --- /dev/null +++ b/lib/modules/taskfile.nix @@ -0,0 +1,102 @@ +{ + lib, + pkgs, + config, + ... +}: let + inherit (lib) mapAttrs mkEnableOption mkOption mkIf types; + cfg = config.task; + + patchedTasks = mapAttrs (_name: value: let + taskDir = value.dir or ""; + absolutePathOrTemplate = (builtins.substring 0 1 taskDir) == "/" || (builtins.substring 0 1 taskDir) == "{"; + in + value + // { + dir = + if absolutePathOrTemplate + then taskDir + else ''{{env "TASK_ROOT_DIR" | default .USER_WORKING_DIR}}/${taskDir}''; + }) + cfg.tasks; + + generator = name: value: + pkgs.writeTextFile { + inherit name; + text = builtins.toJSON value; + }; + # NOTE: this requires python just to convert json to yaml, since json is valid yaml we just ignore that + # generator = (pkgs.formats.yaml {}).generate; + taskfile = generator "taskfile" { + version = 3; + inherit (cfg) interval; + tasks = patchedTasks; + }; + # when using a , as alias for example the store path looks weird. + # This way it can be identified as being the task alias + taskAlias = pkgs.writeTextFile { + name = "task-alias"; + destination = "/bin/${cfg.alias}"; + executable = true; + text = let + taskfileScript = + if cfg.lazy + then + # sh + "$(nix build '${builtins.unsafeDiscardOutputDependency taskfile.drvPath}^*' --no-link --print-out-paths)" + else taskfile; + in + # sh + '' + TASKFILE="${taskfileScript}" + STATE_DIR="''${REN_STATE:-''${DEVENV_STATE:-''${PRJ_CACHE_HOME}}}" + ROOT_DIR="''${REN_ROOT:-''${DEVENV_ROOT:-''${PRJ_ROOT}}}" + + TASK_TEMP_DIR="''${STATE_DIR}/.task" \ + TASK_ROOT_DIR="$ROOT_DIR" \ + ${pkgs.go-task}/bin/task --taskfile "$TASKFILE" ''${@:1} + ''; + }; +in { + options.task = { + enable = + mkEnableOption "Task" + // { + default = cfg.tasks != {}; + }; + lazy = mkOption { + type = types.bool; + default = true; + description = '' + Whether the taskfile should be built on-demand/lazily. + It will probably not land in the gcroot and thus might get cleaned up with every gc. + On the other hand, this way loading the devshell is faster. Decide for yourself :) + ''; + }; + alias = mkOption { + type = types.str; + default = "task"; + description = '' + Alias for `task`, eg. set to `,` to be able to run `, --list-all`. + ''; + }; + interval = mkOption { + type = types.str; + default = "5000ms"; + description = '' + Interval for `task` to check for filesystem changes/watcher updates. + ''; + }; + tasks = mkOption { + type = types.attrs; + default = {}; + description = '' + Configure all your tasks here. + ''; + }; + }; + + config = mkIf cfg.enable { + packages = [taskAlias]; + }; +} diff --git a/lib/modules/taskfile_test.nix b/lib/modules/taskfile_test.nix new file mode 100644 index 0000000..5b44007 --- /dev/null +++ b/lib/modules/taskfile_test.nix @@ -0,0 +1,46 @@ +{ + ntlib, + devshell, + ... +}: let + module = ./taskfile.nix; +in { + suites."Taskfile" = { + pos = __curPos; + tests = [ + { + name = "basic"; + type = "script"; + script = let + shell = devshell.mkShell { + imports = [module]; + task.enable = true; + }; + in + # sh + '' + ${ntlib.helpers.scriptHelpers} + assert "-f ${shell}/bin/task" "/bin/task should exist" + ''; + } + { + name = "alias"; + type = "script"; + script = let + shell = devshell.mkShell { + imports = [module]; + task = { + enable = true; + alias = ","; + }; + }; + in + # sh + '' + ${ntlib.helpers.scriptHelpers} + assert "-f ${shell}/bin/," "/bin/, should exist" + ''; + } + ]; + }; +} diff --git a/nix/repo/ci.nix b/nix/repo/ci.nix new file mode 100644 index 0000000..b0798f3 --- /dev/null +++ b/nix/repo/ci.nix @@ -0,0 +1,34 @@ +{inputs, ...}: let + inherit (inputs) cilib; +in + cilib.mkCI { + pipelines."default" = { + stages = ["build" "deploy"]; + jobs = { + "docs" = { + stage = "build"; + script = [ + # sh + '' + nix build .#docs:default + mkdir -p public + cp -r result/. public/ + '' + ]; + artifacts.paths = ["public"]; + }; + "pages" = { + nix.enable = false; + image = "alpine:latest"; + stage = "deploy"; + script = ["true"]; + artifacts.paths = ["public"]; + rules = [ + { + "if" = "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"; + } + ]; + }; + }; + }; + } diff --git a/nix/repo/devShells.nix b/nix/repo/devShells.nix new file mode 100644 index 0000000..cfd36c4 --- /dev/null +++ b/nix/repo/devShells.nix @@ -0,0 +1,60 @@ +{ + inputs, + cell, + ... +}: let + inherit (inputs) self pkgs devshell soonix treefmt; + inherit (cell) ci; + + treefmtWrapper = treefmt.mkWrapper pkgs { + programs = { + alejandra.enable = true; + deadnix.enable = true; + statix.enable = true; + mdformat.enable = true; + }; + }; +in { + default = devshell.mkShell { + imports = [ + "${self}/lib/modules" + soonix.devshellModule + ]; + soonix.hooks.ci = ci.soonix; + packages = [ + pkgs.hello + treefmtWrapper + ]; + + task = { + alias = ","; + tasks = { + "hello" = { + cmd = "echo world!"; + }; + }; + }; + + lefthook.config = { + skip_output = ["meta" "execution_out"]; + "pre-commit" = { + parallel = true; + jobs = [ + { + name = "treefmt"; + stage_fixed = true; + run = "${treefmtWrapper}/bin/treefmt"; + } + ]; + }; + }; + + cocogitto.config.changelog = { + path = "CHANGELOG.md"; + template = "remote"; + remote = "gitlab.com"; + repository = "devtools"; + owner = "rensa-nix"; + }; + }; +} diff --git a/nix/repo/docs.nix b/nix/repo/docs.nix new file mode 100644 index 0000000..f39650f --- /dev/null +++ b/nix/repo/docs.nix @@ -0,0 +1,68 @@ +{inputs, ...}: let + inherit (inputs) pkgs devtools doclib; + + optionsDoc = doclib.mkOptionDocs { + module = devtools.devshellModule; + roots = [ + { + url = "https://gitlab.com/rensa-nix/devtools/-/blob/main/lib"; + path = "${inputs.self}/lib"; + } + ]; + }; + optionsDocs = pkgs.runCommand "options-docs" {} '' + mkdir -p $out + ln -s ${optionsDoc} $out/options.md + ''; +in + (doclib.mkDocs { + docs."default" = { + base = "${inputs.self}"; + path = "${inputs.self}/docs"; + material = { + enable = true; + colors = { + primary = "red"; + accent = "red"; + }; + umami = { + enable = true; + src = "https://analytics.tf/umami"; + siteId = "db90861a-0b7f-4654-b7ca-f45ce80c44b3"; + domains = ["devtools.rensa.projects.tf"]; + }; + }; + macros = { + enable = true; + includeDir = toString optionsDocs; + }; + config = { + site_name = "Devtools"; + repo_name = "rensa-nix/devtools"; + repo_url = "https://gitlab.com/rensa-nix/devtools"; + extra_css = ["style.css"]; + theme = { + logo = "images/logo.svg"; + icon.repo = "simple/gitlab"; + favicon = "images/logo.svg"; + }; + nav = [ + {"Introduction" = "index.md";} + {"Options" = "options.md";} + ]; + markdown_extensions = [ + { + "pymdownx.highlight".pygments_lang_class = true; + } + "pymdownx.inlinehilite" + "pymdownx.snippets" + "pymdownx.superfences" + "pymdownx.escapeall" + "fenced_code" + ]; + }; + }; + }).packages + // { + inherit optionsDocs; + } diff --git a/nix/repo/flake.lock b/nix/repo/flake.lock new file mode 100644 index 0000000..7443660 --- /dev/null +++ b/nix/repo/flake.lock @@ -0,0 +1,118 @@ +{ + "nodes": { + "devshell": { + "locked": { + "dir": "lib", + "lastModified": 1755673398, + "narHash": "sha256-51MmR+Eo1+bKDd/Ss77wwTqi4yAR2xgmyCSEbKWSpj0=", + "owner": "rensa-nix", + "repo": "devshell", + "rev": "e76bef387e8a4574f9b6d37b1a424e706491af08", + "type": "gitlab" + }, + "original": { + "dir": "lib", + "owner": "rensa-nix", + "repo": "devshell", + "type": "gitlab" + } + }, + "nix-gitlab-ci-lib": { + "locked": { + "dir": "lib", + "lastModified": 1756974596, + "narHash": "sha256-KxQj76sUqvPNtrqzNWMZeOWqTitc0aFCYj7UZzToiEA=", + "owner": "TECHNOFAB", + "repo": "nix-gitlab-ci", + "rev": "00cf5b83c6c46698fba12a54b9cc15c6d4e5a4dd", + "type": "gitlab" + }, + "original": { + "dir": "lib", + "owner": "TECHNOFAB", + "ref": "3.0.0-alpha.2", + "repo": "nix-gitlab-ci", + "type": "gitlab" + } + }, + "nixmkdocs": { + "locked": { + "dir": "lib", + "lastModified": 1757055638, + "narHash": "sha256-KHYSkEreFe4meXzSdEbknC/HwaQSNClQkc8vzHlAsMM=", + "owner": "TECHNOFAB", + "repo": "nixmkdocs", + "rev": "7840a5febdbeaf2da90babf6c94b3d0929d2bf74", + "type": "gitlab" + }, + "original": { + "dir": "lib", + "owner": "TECHNOFAB", + "repo": "nixmkdocs", + "type": "gitlab" + } + }, + "nixtest-lib": { + "locked": { + "dir": "lib", + "lastModified": 1756812148, + "narHash": "sha256-0g8KNk4zoLApA51PBHOWqPLRYpprjrQuSzNCjfBQgu8=", + "owner": "TECHNOFAB", + "repo": "nixtest", + "rev": "5741109cc9ec2b6d41b56abd3f5bc51ed7a9a228", + "type": "gitlab" + }, + "original": { + "dir": "lib", + "owner": "TECHNOFAB", + "repo": "nixtest", + "type": "gitlab" + } + }, + "root": { + "inputs": { + "devshell": "devshell", + "nix-gitlab-ci-lib": "nix-gitlab-ci-lib", + "nixmkdocs": "nixmkdocs", + "nixtest-lib": "nixtest-lib", + "soonix-lib": "soonix-lib", + "treefmt-nix": "treefmt-nix" + } + }, + "soonix-lib": { + "locked": { + "dir": "lib", + "lastModified": 1757424411, + "narHash": "sha256-x99obZwqDAhUB+VUhAV9sKH00NnsVi481n/8bdvZCUY=", + "owner": "TECHNOFAB", + "repo": "soonix", + "rev": "add807ef8980197bbd06652a36d937b93b2a31c7", + "type": "gitlab" + }, + "original": { + "dir": "lib", + "owner": "TECHNOFAB", + "repo": "soonix", + "type": "gitlab" + } + }, + "treefmt-nix": { + "flake": false, + "locked": { + "lastModified": 1756662192, + "narHash": "sha256-F1oFfV51AE259I85av+MAia221XwMHCOtZCMcZLK2Jk=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "1aabc6c05ccbcbf4a635fb7a90400e44282f61c4", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/nix/repo/flake.nix b/nix/repo/flake.nix new file mode 100644 index 0000000..333ab5f --- /dev/null +++ b/nix/repo/flake.nix @@ -0,0 +1,24 @@ +{ + inputs = { + nixtest-lib.url = "gitlab:TECHNOFAB/nixtest?dir=lib"; + nixmkdocs.url = "gitlab:TECHNOFAB/nixmkdocs?dir=lib"; + devshell.url = "gitlab:rensa-nix/devshell?dir=lib"; + soonix-lib.url = "gitlab:TECHNOFAB/soonix?dir=lib"; + nix-gitlab-ci-lib.url = "gitlab:TECHNOFAB/nix-gitlab-ci/3.0.0-alpha.2?dir=lib"; + treefmt-nix = { + url = "github:numtide/treefmt-nix"; + flake = false; + }; + }; + outputs = i: + i + // { + ntlib = i.nixtest-lib.lib {inherit (i.parent) pkgs;}; + doclib = i.nixmkdocs.lib {inherit (i.parent) pkgs;}; + devshell = i.devshell.lib {inherit (i.parent) pkgs;}; + soonix = i.soonix-lib.lib {inherit (i.parent) pkgs;}; + cilib = i.nix-gitlab-ci-lib.lib {inherit (i.parent) pkgs;}; + devtools = import "${i.parent.self}/lib" {inherit (i.parent) pkgs;}; + treefmt = import i.treefmt-nix; + }; +} diff --git a/nix/repo/tests.nix b/nix/repo/tests.nix new file mode 100644 index 0000000..d1f1329 --- /dev/null +++ b/nix/repo/tests.nix @@ -0,0 +1,10 @@ +{inputs, ...}: let + inherit (inputs) pkgs ntlib devshell; +in { + tests = ntlib.mkNixtest { + modules = ntlib.autodiscover {dir = "${inputs.self}/lib/modules";}; + args = { + inherit ntlib devshell pkgs; + }; + }; +}