chore: initial commit

This commit is contained in:
technofab 2025-09-17 11:59:53 +02:00
commit db488d8f41
No known key found for this signature in database
26 changed files with 1077 additions and 0 deletions

2
.envrc Normal file
View file

@ -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

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
.ren
result

5
.gitlab-ci.yml Normal file
View file

@ -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

21
README.md Normal file
View file

@ -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).

1
docs/images/logo.svg Executable file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" fill="none"><rect width="100" height="100" fill="url(#a)" rx="12"/><path fill="url(#b)" d="m77.3 87.8-2.7 4.7h-5.5l-5.6-10h4.7c2 0 3.7-.6 5.2-1.6l3.9 6.9ZM68.7 79h-.2.2ZM89 67l-5.3 9.6h-7c.4-1 .6-2 .6-3.2V67H89Zm-15.2 6.4v.3a5.9 5.9 0 0 1 0-.3Zm-59.3-13V72L11 66l3.1-5.5h.4Zm6-15.1-4.2 9.5H4.7L2 50l2.7-4.8h15.7Zm52-2.2-1.2 2.2-1.3-3a8.6 8.6 0 0 0-2.5-3.4l.7-.1 4.4 4.3ZM38 37.7h-.5.5ZM29.2 34c-.4.9-.7 1.9-.7 3l-1.2.2 1.9-3.2Zm38.4 1.3a8 8 0 0 1 .3 0h-.3Zm-5-17.2a5 5 0 0 0-3 3.2 5 5 0 0 0-3.1 3l-.5 1.3L45.7 7.6l10.9-.1 6 10.7Zm12-10.6 2.7 4.8-4.2 7.3a10.5 10.5 0 0 0-6.6-2.2c-1.1 0-2.2.1-3.3.5l6-10.4h5.4Z"/><path fill="url(#c)" d="m54.9 92.4-10.7.1-5.6-10h10.7l5.6 9.9Zm-23.2 0h-5.3l-2.8-4.8 3-5.1h10.7l-5.6 10Zm4.5-28Zm.6-1.1a5.8 5.8 0 0 0-.1.2l.1-.2ZM74.5 45a5.6 5.6 0 0 0 4.1 1.7c1.6 0 3-.6 4-1.7l.7-.7-.4.7h12.4L98 50l-2.7 4.8H77.5l-1.2 2.1-.5-1.2-4.2-9.5 1.4-2.4 1.5 1.4Zm3.4-1.9a2.2 2.2 0 0 0 .2 0h-.2Zm11.3-9.1-4.3 7.6.1-1.5V40c0-1.3-.6-2.6-1.7-3.7l-4-4 4.4-7.6 5.5 9.5Zm-8.3 4.6.1.2a2.2 2.2 0 0 1-.1-.2l-3.3-3.2 3.3 3.2ZM40.5 23.3h12.7l2.6 4.6a13.8 13.8 0 0 0 0 .9l-1.5-.1H37.5a8.9 8.9 0 0 0-7.6 4.1H12l5.3-9.5h12.5l-6.2-11 2.6-4.8h5.5l8.8 15.8Z"/><path fill="#fff" d="M63 22.6c0-.5.3-.9 1-1.2a6 6 0 0 1 2.5-.4c2 0 3.7.7 5.1 2.1a7.4 7.4 0 0 1 1.5 8l7.8 7.6c.4.4.6 1 .6 1.6 0 .6-.2 1.1-.6 1.6l-.8.7c-.4.5-1 .7-1.5.7-.7 0-1.2-.2-1.7-.6L69.3 35a7.9 7.9 0 0 1-2.7.5 7 7 0 0 1-5.2-2.1 7 7 0 0 1-2.1-5.1c0-1 .1-2 .4-2.6.3-.6.7-1 1.2-1l.5.2.4.3 3.1 3 1.6-1.5-3.1-3.1-.3-.5v-.5ZM23.6 79c-1.6 0-2.9-.5-4-1.6-1-1.1-1.6-2.4-1.6-4v-11h14c0 .8.2 1.5.7 2 .6.6 1.2.8 2 .8s1.3-.3 1.6-1c.3-.6.7-1.2 1.2-1.7h16.8a2.7 2.7 0 0 0 2.8 2.8c.7 0 1.2-.4 1.5-1 .3-.7.7-1.3 1.2-1.8h14v11c0 1.5-.6 2.8-1.7 3.9-1 1-2.4 1.6-4 1.6H23.7Zm-4.4-22L25 43.7c.4-1 1-1.8 2-2.4 1-.6 2-.9 3-.9h2v-2.7c0-1.6.5-2.8 1.6-4 1-1 2.4-1.5 4-1.5h16.6c1.6 0 2.9.5 4 1.6 1 1 1.6 2.3 1.6 3.9v2.7h2a5.4 5.4 0 0 1 5 3.3L72.6 57H59.8c0-.8-.2-1.5-.8-2-.5-.5-1.2-.8-2-.8-.7 0-1.2.3-1.5 1-.3.7-.7 1.2-1.3 1.8H37.5c0-.8-.2-1.5-.8-2-.5-.5-1.2-.8-2-.8-.7 0-1.2.3-1.5 1-.3.7-.7 1.2-1.3 1.8H19.2Zm18.3-16.6h16.8v-2.7H37.5v2.7Z"/><defs><linearGradient id="b" x1="76.7" x2="59.1" y1="47.7" y2="17.6" gradientUnits="userSpaceOnUse"><stop offset=".2" stop-color="#7EB1DD"/></linearGradient><linearGradient id="c" x1="63.4" x2="81.3" y1="70.8" y2="41.1" gradientUnits="userSpaceOnUse"><stop offset="1" stop-color="#5277C3"/></linearGradient><radialGradient id="a" cx="0" cy="0" r="1" gradientTransform="matrix(0 50 -50 0 50 50)" gradientUnits="userSpaceOnUse"><stop offset=".5" stop-color="#091A3D"/><stop offset="1" stop-color="#000819"/></radialGradient></defs></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

19
docs/index.md Normal file
View file

@ -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)

3
docs/options.md Normal file
View file

@ -0,0 +1,3 @@
# Options
{% include 'options.md' %}

15
docs/style.css Normal file
View file

@ -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;
}

63
flake.lock generated Normal file
View file

@ -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
}

35
flake.nix Normal file
View file

@ -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"]
];
};
}

3
lib/default.nix Normal file
View file

@ -0,0 +1,3 @@
_: {
devshellModule = ./modules;
}

5
lib/flake.nix Normal file
View file

@ -0,0 +1,5 @@
{
outputs = _i: {
devshellModule = ./modules;
};
}

59
lib/modules/cocogitto.nix Normal file
View file

@ -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];
};
}

View file

@ -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"
'';
}
];
};
}

7
lib/modules/default.nix Normal file
View file

@ -0,0 +1,7 @@
{
imports = [
./lefthook.nix
./taskfile.nix
./cocogitto.nix
];
}

152
lib/modules/lefthook.nix Normal file
View file

@ -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"];
};
};
}

View file

@ -0,0 +1,149 @@
# Usage: __log <LEVEL> "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 <hook_name> <nix_store_hook_path>
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 <space-separated-list-of-unused-hook-names>
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
}

View file

@ -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"
'';
}
];
};
}

102
lib/modules/taskfile.nix Normal file
View file

@ -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];
};
}

View file

@ -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"
'';
}
];
};
}

34
nix/repo/ci.nix Normal file
View file

@ -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";
}
];
};
};
};
}

60
nix/repo/devShells.nix Normal file
View file

@ -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";
};
};
}

68
nix/repo/docs.nix Normal file
View file

@ -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;
}

118
nix/repo/flake.lock generated Normal file
View file

@ -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
}

24
nix/repo/flake.nix Normal file
View file

@ -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;
};
}

10
nix/repo/tests.nix Normal file
View file

@ -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;
};
};
}