Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71c7fe09cd | ||
|
|
45f3830794 | ||
|
|
b8d0a69d4d | ||
|
|
c5f8ee6005 | ||
|
|
9983f0b8e9 | ||
|
|
0d339e2de0 | ||
|
|
7dcb0d1b3a |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.pre-commit-config.yaml
|
||||
lefthook.yml
|
||||
.direnv
|
||||
result
|
||||
template/flake.lock
|
||||
template/flake.lock
|
||||
|
||||
16
README.md
16
README.md
@@ -15,7 +15,7 @@
|
||||
## Use the template
|
||||
|
||||
```bash
|
||||
nix flake new myapp -t 'git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/v3.1.0#default' --refresh
|
||||
nix flake new myapp -t 'git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/v3.4.0#default' --refresh
|
||||
```
|
||||
|
||||
## Use the library
|
||||
@@ -23,7 +23,7 @@ nix flake new myapp -t 'git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/ta
|
||||
Add this flake input:
|
||||
|
||||
```nix
|
||||
inputs.repo-lib.url = "git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/v3.1.0";
|
||||
inputs.repo-lib.url = "git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/v3.4.0";
|
||||
inputs.repo-lib.inputs.nixpkgs.follows = "nixpkgs";
|
||||
```
|
||||
|
||||
@@ -66,11 +66,21 @@ outputs = { self, nixpkgs, repo-lib, ... }:
|
||||
`mkRepo` generates:
|
||||
|
||||
- `devShells.${system}.default`
|
||||
- `checks.${system}.pre-commit-check`
|
||||
- `checks.${system}.hook-check`
|
||||
- `checks.${system}.lefthook-check`
|
||||
- `formatter.${system}`
|
||||
- `packages.${system}.release` when `config.release != null`
|
||||
- merged `packages` and `apps` from `perSystem`
|
||||
|
||||
Checks are installed through `lefthook`, with `pre-commit` and `pre-push` commands configured to run in parallel.
|
||||
repo-lib also sets Lefthook `output = [ "failure" "summary" ]` by default.
|
||||
|
||||
For advanced Lefthook features, use raw `config.lefthook` or `perSystem.lefthook`. Those attrsets are merged after generated checks, so you can augment a generated command with fields that the simple `checks` abstraction does not carry, such as `stage_fixed`:
|
||||
|
||||
```nix
|
||||
config.lefthook.pre-push.commands.tests.stage_fixed = true;
|
||||
```
|
||||
|
||||
## Tool banners
|
||||
|
||||
Tools are declared once. Package-backed tools are added to the shell automatically, and both package-backed and command-backed tools are rendered in the startup banner.
|
||||
|
||||
77
flake.lock
generated
77
flake.lock
generated
@@ -1,79 +1,26 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1767039857,
|
||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"git-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1772024342,
|
||||
"narHash": "sha256-+eXlIc4/7dE6EcPs9a2DaSY3fTA9AE526hGqkNID3Wg=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "6e34e97ed9788b17796ee43ccdbaf871a5c2b476",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"lefthook-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"git-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1709087332,
|
||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
||||
"lastModified": 1770377107,
|
||||
"narHash": "sha256-/QEXSDeAo5RK81PtM0yDhmt9k3v1/pse/jsrT1yXNhU=",
|
||||
"owner": "sudosubin",
|
||||
"repo": "lefthook.nix",
|
||||
"rev": "9cdaf7ce95ae77cbabc5b556bdd35d3cf0b849f5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"owner": "sudosubin",
|
||||
"repo": "lefthook.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1770073757,
|
||||
"narHash": "sha256-Vy+G+F+3E/Tl+GMNgiHl9Pah2DgShmIUBJXmbiQPHbI=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "47472570b1e607482890801aeaf29bfb749884f6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1772542754,
|
||||
"narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=",
|
||||
@@ -89,7 +36,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1770107345,
|
||||
"narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=",
|
||||
@@ -107,14 +54,14 @@
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"git-hooks": "git-hooks",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"lefthook-nix": "lefthook-nix",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1770228511,
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||
git-hooks.url = "github:cachix/git-hooks.nix";
|
||||
lefthook-nix.url = "github:sudosubin/lefthook.nix";
|
||||
lefthook-nix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
treefmt-nix.url = "github:numtide/treefmt-nix";
|
||||
};
|
||||
|
||||
@@ -13,13 +14,14 @@
|
||||
self,
|
||||
nixpkgs,
|
||||
treefmt-nix,
|
||||
git-hooks,
|
||||
lefthook-nix,
|
||||
...
|
||||
}:
|
||||
let
|
||||
lib = nixpkgs.lib;
|
||||
repoLib = import ./packages/repo-lib/lib.nix {
|
||||
inherit nixpkgs treefmt-nix git-hooks;
|
||||
inherit nixpkgs treefmt-nix;
|
||||
lefthookNix = lefthook-nix;
|
||||
releaseScriptPath = ./packages/release/release.sh;
|
||||
shellHookTemplatePath = ./packages/repo-lib/shell-hook.sh;
|
||||
};
|
||||
@@ -94,6 +96,7 @@
|
||||
nativeBuildInputs = with pkgs; [
|
||||
bash
|
||||
git
|
||||
nix
|
||||
gnused
|
||||
coreutils
|
||||
gnugrep
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
nixpkgs,
|
||||
treefmt-nix,
|
||||
git-hooks,
|
||||
lefthookNix,
|
||||
releaseScriptPath ? ./release.sh,
|
||||
shellHookTemplatePath ? ../repo-lib/shell-hook.sh,
|
||||
}:
|
||||
@@ -9,7 +9,7 @@ import ../repo-lib/lib.nix {
|
||||
inherit
|
||||
nixpkgs
|
||||
treefmt-nix
|
||||
git-hooks
|
||||
lefthookNix
|
||||
releaseScriptPath
|
||||
shellHookTemplatePath
|
||||
;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
nixpkgs,
|
||||
treefmt-nix,
|
||||
git-hooks,
|
||||
lefthookNix,
|
||||
releaseScriptPath,
|
||||
shellHookTemplatePath,
|
||||
}:
|
||||
@@ -123,7 +123,7 @@ let
|
||||
required = tool.required or false;
|
||||
};
|
||||
|
||||
normalizeCheck =
|
||||
checkToLefthookConfig =
|
||||
pkgs: name: rawCheck:
|
||||
let
|
||||
check = {
|
||||
@@ -152,13 +152,85 @@ let
|
||||
then
|
||||
throw "repo-lib: check '${name}' has unsupported stage '${check.stage}'"
|
||||
else
|
||||
{
|
||||
enable = true;
|
||||
entry = "${wrapper}/bin/${wrapperName}";
|
||||
pass_filenames = check.passFilenames;
|
||||
stages = [ check.stage ];
|
||||
lib.setAttrByPath [ check.stage "commands" name ] {
|
||||
run = "${wrapper}/bin/${wrapperName}${hookStageFileArgs check.stage check.passFilenames}";
|
||||
};
|
||||
|
||||
normalizeLefthookConfig =
|
||||
label: raw: if builtins.isAttrs raw then raw else throw "repo-lib: ${label} must be an attrset";
|
||||
|
||||
normalizeHookStage =
|
||||
hookName: stage:
|
||||
if
|
||||
builtins.elem stage [
|
||||
"pre-commit"
|
||||
"pre-push"
|
||||
"commit-msg"
|
||||
]
|
||||
then
|
||||
stage
|
||||
else
|
||||
throw "repo-lib: hook '${hookName}' has unsupported stage '${stage}' for lefthook";
|
||||
|
||||
hookStageFileArgs =
|
||||
stage: passFilenames:
|
||||
if !passFilenames then
|
||||
""
|
||||
else if stage == "pre-commit" then
|
||||
" {staged_files}"
|
||||
else if stage == "pre-push" then
|
||||
" {push_files}"
|
||||
else if stage == "commit-msg" then
|
||||
" {1}"
|
||||
else
|
||||
throw "repo-lib: unsupported lefthook stage '${stage}'";
|
||||
|
||||
hookToLefthookConfig =
|
||||
name: hook:
|
||||
let
|
||||
supportedFields = [
|
||||
"description"
|
||||
"enable"
|
||||
"entry"
|
||||
"name"
|
||||
"package"
|
||||
"pass_filenames"
|
||||
"stages"
|
||||
];
|
||||
unsupportedFields = builtins.filter (field: !(builtins.elem field supportedFields)) (
|
||||
builtins.attrNames hook
|
||||
);
|
||||
stages = builtins.map (stage: normalizeHookStage name stage) (hook.stages or [ "pre-commit" ]);
|
||||
passFilenames = hook.pass_filenames or false;
|
||||
in
|
||||
if unsupportedFields != [ ] then
|
||||
throw ''
|
||||
repo-lib: hook '${name}' uses unsupported fields for lefthook: ${lib.concatStringsSep ", " unsupportedFields}
|
||||
''
|
||||
else if !(hook ? entry) then
|
||||
throw "repo-lib: hook '${name}' is missing 'entry'"
|
||||
else
|
||||
lib.foldl' lib.recursiveUpdate { } (
|
||||
builtins.map (
|
||||
stage:
|
||||
lib.setAttrByPath [ stage "commands" name ] {
|
||||
run = "${hook.entry}${hookStageFileArgs stage passFilenames}";
|
||||
}
|
||||
) stages
|
||||
);
|
||||
|
||||
parallelHookStageConfig =
|
||||
stage:
|
||||
if
|
||||
builtins.elem stage [
|
||||
"pre-commit"
|
||||
"pre-push"
|
||||
]
|
||||
then
|
||||
lib.setAttrByPath [ stage "parallel" ] true
|
||||
else
|
||||
{ };
|
||||
|
||||
normalizeReleaseStep =
|
||||
step:
|
||||
if step ? writeFile then
|
||||
@@ -277,7 +349,7 @@ let
|
||||
|
||||
buildShellHook =
|
||||
{
|
||||
preCommitShellHook,
|
||||
hooksShellHook,
|
||||
shellEnvScript,
|
||||
bootstrap,
|
||||
shellBannerScript,
|
||||
@@ -289,7 +361,7 @@ let
|
||||
in
|
||||
builtins.replaceStrings
|
||||
[
|
||||
"\${pre-commit-check.shellHook}"
|
||||
"@HOOKS_SHELL_HOOK@"
|
||||
"@TOOL_LABEL_WIDTH@"
|
||||
"@SHELL_ENV_SCRIPT@"
|
||||
"@BOOTSTRAP@"
|
||||
@@ -297,7 +369,7 @@ let
|
||||
"@EXTRA_SHELL_TEXT@"
|
||||
]
|
||||
[
|
||||
preCommitShellHook
|
||||
hooksShellHook
|
||||
(toString toolLabelWidth)
|
||||
shellEnvScript
|
||||
bootstrap
|
||||
@@ -322,6 +394,7 @@ let
|
||||
},
|
||||
checkSpecs ? { },
|
||||
rawHookEntries ? { },
|
||||
lefthookConfig ? { },
|
||||
extraPackages ? [ ],
|
||||
}:
|
||||
let
|
||||
@@ -342,26 +415,44 @@ let
|
||||
// formatting.programs;
|
||||
settings.formatter = { } // formatting.settings;
|
||||
};
|
||||
treefmtWrapper = treefmtEval.config.build.wrapper;
|
||||
lefthookBinWrapper = pkgs.writeShellScript "lefthook-dumb-term" ''
|
||||
exec env TERM=dumb ${lib.getExe pkgs.lefthook} "$@"
|
||||
'';
|
||||
|
||||
normalizedChecks = lib.mapAttrs (name: check: normalizeCheck pkgs name check) checkSpecs;
|
||||
hooks = mergeUniqueAttrs "hook" rawHookEntries normalizedChecks;
|
||||
|
||||
pre-commit-check = git-hooks.lib.${system}.run {
|
||||
normalizedLefthookConfig = normalizeLefthookConfig "lefthook config" lefthookConfig;
|
||||
lefthookCheck = lefthookNix.lib.${system}.run {
|
||||
inherit src;
|
||||
hooks = {
|
||||
treefmt = {
|
||||
enable = true;
|
||||
entry = "${treefmtEval.config.build.wrapper}/bin/treefmt --ci";
|
||||
pass_filenames = true;
|
||||
};
|
||||
gitlint.enable = true;
|
||||
gitleaks = {
|
||||
enable = true;
|
||||
entry = "${pkgs.gitleaks}/bin/gitleaks protect --staged";
|
||||
pass_filenames = false;
|
||||
};
|
||||
}
|
||||
// hooks;
|
||||
config = lib.foldl' lib.recursiveUpdate { } (
|
||||
[
|
||||
{
|
||||
output = [
|
||||
"failure"
|
||||
"summary"
|
||||
];
|
||||
}
|
||||
(parallelHookStageConfig "pre-commit")
|
||||
(parallelHookStageConfig "pre-push")
|
||||
(lib.setAttrByPath [ "pre-commit" "commands" "treefmt" ] {
|
||||
run = "${treefmtWrapper}/bin/treefmt --no-cache {staged_files}";
|
||||
stage_fixed = true;
|
||||
})
|
||||
(lib.setAttrByPath [ "pre-commit" "commands" "gitleaks" ] {
|
||||
run = "${pkgs.gitleaks}/bin/gitleaks protect --staged";
|
||||
})
|
||||
(lib.setAttrByPath [ "commit-msg" "commands" "gitlint" ] {
|
||||
run = "${pkgs.gitlint}/bin/gitlint --staged --msg-filename {1}";
|
||||
})
|
||||
]
|
||||
++ lib.mapAttrsToList (name: check: checkToLefthookConfig pkgs name check) checkSpecs
|
||||
++ lib.mapAttrsToList hookToLefthookConfig rawHookEntries
|
||||
++ [ normalizedLefthookConfig ]
|
||||
);
|
||||
};
|
||||
selectedCheckOutputs = {
|
||||
formatting-check = treefmtEval.config.build.check src;
|
||||
hook-check = lefthookCheck;
|
||||
lefthook-check = lefthookCheck;
|
||||
};
|
||||
|
||||
toolNames = builtins.map (tool: tool.name) tools;
|
||||
@@ -469,19 +560,28 @@ let
|
||||
'';
|
||||
in
|
||||
{
|
||||
inherit pre-commit-check;
|
||||
formatter = treefmtEval.config.build.wrapper;
|
||||
checks = selectedCheckOutputs;
|
||||
formatter = treefmtWrapper;
|
||||
shell = pkgs.mkShell {
|
||||
packages = lib.unique (selectedStandardPackages ++ extraPackages ++ toolPackages);
|
||||
buildInputs = pre-commit-check.enabledPackages;
|
||||
LEFTHOOK_BIN = builtins.toString lefthookBinWrapper;
|
||||
packages = lib.unique (
|
||||
selectedStandardPackages
|
||||
++ extraPackages
|
||||
++ toolPackages
|
||||
++ [
|
||||
pkgs.lefthook
|
||||
treefmtWrapper
|
||||
]
|
||||
);
|
||||
shellHook = buildShellHook {
|
||||
preCommitShellHook = pre-commit-check.shellHook;
|
||||
hooksShellHook = lefthookCheck.shellHook;
|
||||
inherit toolLabelWidth shellEnvScript shellBannerScript;
|
||||
bootstrap = shellConfig.bootstrap;
|
||||
extraShellText = shellConfig.extraShellText;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
// selectedCheckOutputs;
|
||||
in
|
||||
rec {
|
||||
systems = {
|
||||
@@ -552,6 +652,7 @@ rec {
|
||||
settings = { };
|
||||
};
|
||||
checks = { };
|
||||
lefthook = { };
|
||||
release = null;
|
||||
} rawConfig;
|
||||
release =
|
||||
@@ -586,6 +687,7 @@ rec {
|
||||
preToolHook ? "",
|
||||
extraShellHook ? "",
|
||||
additionalHooks ? { },
|
||||
lefthook ? { },
|
||||
tools ? [ ],
|
||||
includeStandardPackages ? true,
|
||||
formatters ? { },
|
||||
@@ -625,6 +727,7 @@ rec {
|
||||
;
|
||||
formatting = normalizedFormatting;
|
||||
rawHookEntries = additionalHooks;
|
||||
lefthookConfig = lefthook;
|
||||
shellConfig = shellConfig;
|
||||
tools = legacyTools;
|
||||
extraPackages =
|
||||
@@ -704,6 +807,7 @@ rec {
|
||||
tools = [ ];
|
||||
shell = { };
|
||||
checks = { };
|
||||
lefthook = { };
|
||||
packages = { };
|
||||
apps = { };
|
||||
}
|
||||
@@ -716,6 +820,9 @@ rec {
|
||||
strictTools = builtins.map (tool: normalizeStrictTool pkgs tool) perSystemResult.tools;
|
||||
duplicateToolNames = duplicateStrings (builtins.map (tool: tool.name) strictTools);
|
||||
mergedChecks = mergeUniqueAttrs "check" normalizedConfig.checks perSystemResult.checks;
|
||||
mergedLefthookConfig =
|
||||
lib.recursiveUpdate (normalizeLefthookConfig "config.lefthook" normalizedConfig.lefthook)
|
||||
(normalizeLefthookConfig "perSystem.lefthook" (perSystemResult.lefthook or { }));
|
||||
shellConfig = lib.recursiveUpdate normalizedConfig.shell (perSystemResult.shell or { });
|
||||
env =
|
||||
if duplicateToolNames != [ ] then
|
||||
@@ -731,6 +838,7 @@ rec {
|
||||
formatting = normalizedConfig.formatting;
|
||||
tools = strictTools;
|
||||
checkSpecs = mergedChecks;
|
||||
lefthookConfig = mergedLefthookConfig;
|
||||
shellConfig = shellConfig;
|
||||
extraPackages = perSystemResult.shell.packages or [ ];
|
||||
};
|
||||
@@ -762,9 +870,7 @@ rec {
|
||||
default = systemResults.${system}.env.shell;
|
||||
});
|
||||
|
||||
checks = lib.genAttrs systems (system: {
|
||||
inherit (systemResults.${system}.env) pre-commit-check;
|
||||
});
|
||||
checks = lib.genAttrs systems (system: systemResults.${system}.env.checks);
|
||||
|
||||
formatter = lib.genAttrs systems (system: systemResults.${system}.env.formatter);
|
||||
packages = lib.genAttrs systems (system: systemResults.${system}.packages);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
${pre-commit-check.shellHook}
|
||||
@HOOKS_SHELL_HOOK@
|
||||
|
||||
if [ -t 1 ]; then
|
||||
command -v tput >/dev/null 2>&1 && tput clear || printf '\033c'
|
||||
|
||||
@@ -35,6 +35,7 @@ repo-lib.lib.mkRepo {
|
||||
};
|
||||
|
||||
checks = { };
|
||||
lefthook = { };
|
||||
|
||||
release = null; # or attrset below
|
||||
};
|
||||
@@ -43,6 +44,7 @@ repo-lib.lib.mkRepo {
|
||||
tools = [ ];
|
||||
shell.packages = [ ];
|
||||
checks = { };
|
||||
lefthook = { };
|
||||
packages = { };
|
||||
apps = { };
|
||||
};
|
||||
@@ -52,7 +54,8 @@ repo-lib.lib.mkRepo {
|
||||
Generated outputs:
|
||||
|
||||
- `devShells.${system}.default`
|
||||
- `checks.${system}.pre-commit-check`
|
||||
- `checks.${system}.hook-check`
|
||||
- `checks.${system}.lefthook-check`
|
||||
- `formatter.${system}`
|
||||
- `packages.${system}.release` when `config.release != null`
|
||||
- merged `packages` and `apps` from `perSystem`
|
||||
@@ -112,7 +115,36 @@ Defaults:
|
||||
Rules:
|
||||
|
||||
- Only `pre-commit` and `pre-push` are supported.
|
||||
- The command is wrapped as a script and connected into `git-hooks.nix`.
|
||||
- The command is wrapped as a script and connected into `lefthook.nix`.
|
||||
- `pre-commit` and `pre-push` commands are configured to run in parallel.
|
||||
|
||||
## Raw Lefthook config
|
||||
|
||||
Use `config.lefthook` or `perSystem.lefthook` for advanced Lefthook features that the built-in `checks` abstraction does not carry.
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
{
|
||||
checks.tests = {
|
||||
command = "go test ./...";
|
||||
stage = "pre-push";
|
||||
};
|
||||
|
||||
lefthook.pre-push.commands.tests.stage_fixed = true;
|
||||
|
||||
lefthook.commit-msg.commands.commitlint = {
|
||||
run = "pnpm commitlint --edit {1}";
|
||||
stage_fixed = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- These attrsets are passed through to `lefthook.nix`.
|
||||
- They are merged after generated checks, so they can extend generated commands.
|
||||
- Prefer `checks` for the simple common case and `lefthook` for advanced fields such as `stage_fixed`, `files`, `glob`, `exclude`, `jobs`, or `scripts`.
|
||||
|
||||
## Tools
|
||||
|
||||
@@ -268,6 +300,7 @@ If the user asks for a webhook after the tag exists remotely:
|
||||
- `extraPackages`
|
||||
- `preToolHook`
|
||||
- `extraShellHook`
|
||||
- `lefthook`
|
||||
- `additionalHooks`
|
||||
- old `tools = [ { name; bin; versionCmd; color; } ]`
|
||||
- `features.oxfmt`
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
watch_file flake.nix
|
||||
use flake
|
||||
|
||||
3
template/.gitignore
vendored
3
template/.gitignore
vendored
@@ -1,8 +1,9 @@
|
||||
.direnv/
|
||||
.pre-commit-config.yaml
|
||||
lefthook.yml
|
||||
|
||||
bazel-*
|
||||
build/
|
||||
dist/
|
||||
|
||||
node_modules/
|
||||
node_modules/
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||
repo-lib.url = "git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/v3.1.0";
|
||||
repo-lib.url = "git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/v3.4.0";
|
||||
repo-lib.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
};
|
||||
|
||||
formatting = {
|
||||
# nixfmt is enabled by default and wired into lefthook.
|
||||
programs = {
|
||||
# shfmt.enable = true;
|
||||
# gofmt.enable = true;
|
||||
@@ -50,14 +51,40 @@
|
||||
};
|
||||
};
|
||||
|
||||
checks.tests = {
|
||||
command = "echo 'No tests defined yet.'";
|
||||
stage = "pre-push";
|
||||
passFilenames = false;
|
||||
# These checks become lefthook commands in the generated `lefthook.yml`.
|
||||
# repo-lib runs `pre-commit` and `pre-push` hook commands in parallel.
|
||||
# It also sets `output = [ "failure" "summary" ]` by default.
|
||||
checks = {
|
||||
tests = {
|
||||
command = "echo 'No tests defined yet.'";
|
||||
stage = "pre-push";
|
||||
passFilenames = false;
|
||||
};
|
||||
|
||||
# fmt = {
|
||||
# command = "nix fmt";
|
||||
# stage = "pre-commit";
|
||||
# passFilenames = false;
|
||||
# };
|
||||
};
|
||||
|
||||
# For advanced Lefthook fields like `stage_fixed`, use raw passthrough.
|
||||
# repo-lib merges this after generated checks.
|
||||
# lefthook.pre-push.commands.tests.stage_fixed = true;
|
||||
# lefthook.commit-msg.commands.commitlint = {
|
||||
# run = "pnpm commitlint --edit {1}";
|
||||
# stage_fixed = true;
|
||||
# };
|
||||
|
||||
# repo-lib also installs built-in hooks for:
|
||||
# - treefmt / nixfmt on `pre-commit`
|
||||
# - gitleaks on `pre-commit`
|
||||
# - gitlint on `commit-msg`
|
||||
|
||||
# release = null;
|
||||
release = {
|
||||
steps = [
|
||||
# Write a generated version file during release.
|
||||
# {
|
||||
# writeFile = {
|
||||
# path = "src/version.ts";
|
||||
@@ -66,6 +93,8 @@
|
||||
# '';
|
||||
# };
|
||||
# }
|
||||
|
||||
# Replace a version string while preserving surrounding captures.
|
||||
# {
|
||||
# replace = {
|
||||
# path = "README.md";
|
||||
@@ -73,6 +102,16 @@
|
||||
# replacement = ''\1$FULL_VERSION\2'';
|
||||
# };
|
||||
# }
|
||||
|
||||
# Run any extra release step with declared runtime inputs.
|
||||
# {
|
||||
# run = {
|
||||
# runtimeInputs = [ pkgs.git ];
|
||||
# script = ''
|
||||
# git status --short
|
||||
# '';
|
||||
# };
|
||||
# }
|
||||
];
|
||||
};
|
||||
};
|
||||
@@ -113,9 +152,16 @@
|
||||
];
|
||||
|
||||
# checks.lint = {
|
||||
# command = "go test ./...";
|
||||
# command = "bun test";
|
||||
# stage = "pre-push";
|
||||
# runtimeInputs = [ pkgs.go ];
|
||||
# passFilenames = false;
|
||||
# runtimeInputs = [ pkgs.bun ];
|
||||
# };
|
||||
|
||||
# checks.generated = {
|
||||
# command = "git diff --exit-code";
|
||||
# stage = "pre-commit";
|
||||
# passFilenames = false;
|
||||
# };
|
||||
|
||||
# packages.my-tool = pkgs.writeShellApplication {
|
||||
|
||||
164
tests/release.sh
164
tests/release.sh
@@ -35,7 +35,7 @@ assert_contains() {
|
||||
local needle="$1"
|
||||
local haystack_file="$2"
|
||||
local message="$3"
|
||||
if ! grep -Fq "$needle" "$haystack_file"; then
|
||||
if ! grep -Fq -- "$needle" "$haystack_file"; then
|
||||
fail "$message (missing '$needle')"
|
||||
fi
|
||||
}
|
||||
@@ -263,6 +263,49 @@ write_mk_repo_command_tool_flake() {
|
||||
EOF
|
||||
}
|
||||
|
||||
write_mk_repo_lefthook_flake() {
|
||||
local repo_dir="$1"
|
||||
cat >"$repo_dir/flake.nix" <<EOF
|
||||
{
|
||||
description = "mkRepo raw lefthook config";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "path:${NIXPKGS_FLAKE_PATH}";
|
||||
repo-lib.url = "path:${ROOT_DIR}";
|
||||
repo-lib.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, repo-lib, ... }:
|
||||
repo-lib.lib.mkRepo {
|
||||
inherit self nixpkgs;
|
||||
src = ./.;
|
||||
|
||||
config = {
|
||||
checks.tests = {
|
||||
command = "echo test";
|
||||
stage = "pre-push";
|
||||
passFilenames = false;
|
||||
};
|
||||
|
||||
lefthook.pre-push.commands.tests.stage_fixed = true;
|
||||
|
||||
release.steps = [ ];
|
||||
};
|
||||
};
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
init_git_repo() {
|
||||
local repo_dir="$1"
|
||||
|
||||
run_capture_ok "init_git_repo: git init failed" git -C "$repo_dir" init
|
||||
run_capture_ok "init_git_repo: git config user.name failed" git -C "$repo_dir" config user.name "Repo Lib Test"
|
||||
run_capture_ok "init_git_repo: git config user.email failed" git -C "$repo_dir" config user.email "repo-lib-test@example.com"
|
||||
run_capture_ok "init_git_repo: git add failed" git -C "$repo_dir" add flake.nix
|
||||
run_capture_ok "init_git_repo: git commit failed" git -C "$repo_dir" commit -m "init"
|
||||
}
|
||||
|
||||
write_tool_failure_flake() {
|
||||
local repo_dir="$1"
|
||||
cat >"$repo_dir/flake.nix" <<EOF
|
||||
@@ -415,7 +458,7 @@ write_legacy_flake() {
|
||||
};
|
||||
in
|
||||
{
|
||||
inherit (env) pre-commit-check;
|
||||
inherit (env) lefthook-check;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -868,6 +911,38 @@ run_set_prerelease_then_full_case() {
|
||||
echo "[test] PASS: $case_name" >&2
|
||||
}
|
||||
|
||||
run_stable_then_beta_cannot_reuse_same_base_case() {
|
||||
local case_name="stable release cannot go back to same-base beta"
|
||||
|
||||
local workdir
|
||||
workdir="$(mktemp -d)"
|
||||
local repo_dir="$workdir/repo"
|
||||
local remote_dir="$workdir/remote.git"
|
||||
CURRENT_LOG="$workdir/case.log"
|
||||
|
||||
prepare_case_repo "$repo_dir" "$remote_dir"
|
||||
|
||||
run_capture_ok "$case_name: initial beta release failed" run_release "$repo_dir" beta
|
||||
run_capture_ok "$case_name: stable promotion failed" run_release "$repo_dir" full
|
||||
run_capture_ok "$case_name: second beta release failed" run_release "$repo_dir" beta
|
||||
|
||||
local got_version
|
||||
got_version="$(version_from_file "$repo_dir")"
|
||||
assert_eq "1.0.2-beta.1" "$got_version" "$case_name: VERSION mismatch"
|
||||
|
||||
if ! git -C "$repo_dir" tag --list | grep -qx "v1.0.1"; then
|
||||
fail "$case_name: expected stable tag v1.0.1 was not created"
|
||||
fi
|
||||
|
||||
if ! git -C "$repo_dir" tag --list | grep -qx "v1.0.2-beta.1"; then
|
||||
fail "$case_name: expected tag v1.0.2-beta.1 was not created"
|
||||
fi
|
||||
|
||||
rm -rf "$workdir"
|
||||
CURRENT_LOG=""
|
||||
echo "[test] PASS: $case_name" >&2
|
||||
}
|
||||
|
||||
run_set_stable_then_full_noop_case() {
|
||||
local case_name="set stable then full fails with no-op"
|
||||
|
||||
@@ -1124,7 +1199,7 @@ run_mk_repo_case() {
|
||||
CURRENT_LOG="$workdir/mk-repo.log"
|
||||
|
||||
run_capture_ok "$case_name: flake show failed" nix flake show --json --no-write-lock-file "$repo_dir"
|
||||
assert_contains '"pre-commit-check"' "$CURRENT_LOG" "$case_name: missing pre-commit-check"
|
||||
assert_contains '"lefthook-check"' "$CURRENT_LOG" "$case_name: missing lefthook-check"
|
||||
assert_contains '"release"' "$CURRENT_LOG" "$case_name: missing release package"
|
||||
assert_contains '"example"' "$CURRENT_LOG" "$case_name: missing merged package"
|
||||
|
||||
@@ -1146,7 +1221,7 @@ run_mk_repo_command_tool_case() {
|
||||
CURRENT_LOG="$workdir/mk-repo-command-tool.log"
|
||||
|
||||
run_capture_ok "$case_name: flake show failed" nix flake show --json --no-write-lock-file "$repo_dir"
|
||||
assert_contains '"pre-commit-check"' "$CURRENT_LOG" "$case_name: missing pre-commit-check"
|
||||
assert_contains '"lefthook-check"' "$CURRENT_LOG" "$case_name: missing lefthook-check"
|
||||
assert_contains '"release"' "$CURRENT_LOG" "$case_name: missing release package"
|
||||
|
||||
run_capture_ok "$case_name: system nix should be available in shell" bash -c 'cd "$1" && nix develop --no-write-lock-file . -c nix --version' _ "$repo_dir"
|
||||
@@ -1158,6 +1233,76 @@ run_mk_repo_command_tool_case() {
|
||||
echo "[test] PASS: $case_name" >&2
|
||||
}
|
||||
|
||||
run_mk_repo_lefthook_case() {
|
||||
local case_name="mkRepo exposes raw lefthook config for advanced hook fields"
|
||||
local workdir
|
||||
workdir="$(mktemp -d)"
|
||||
local repo_dir="$workdir/mk-repo-lefthook"
|
||||
local system
|
||||
local derivation_json="$workdir/lefthook-run.drv.json"
|
||||
local lefthook_yml_drv
|
||||
local lefthook_yml_json="$workdir/lefthook-yml.drv.json"
|
||||
mkdir -p "$repo_dir"
|
||||
write_mk_repo_lefthook_flake "$repo_dir"
|
||||
CURRENT_LOG="$workdir/mk-repo-lefthook.log"
|
||||
|
||||
system="$(nix eval --raw --impure --expr 'builtins.currentSystem')"
|
||||
run_capture_ok "$case_name: flake show failed" nix flake show --json --no-write-lock-file "$repo_dir"
|
||||
run_capture_ok "$case_name: lefthook derivation show failed" bash -c 'nix derivation show "$1" >"$2"' _ "$repo_dir#checks.${system}.lefthook-check" "$derivation_json"
|
||||
|
||||
lefthook_yml_drv="$(perl -0ne 'print "/nix/store/$1\n" if /"([a-z0-9]{32}-lefthook\.yml\.drv)"/' "$derivation_json")"
|
||||
if [[ -z "$lefthook_yml_drv" ]]; then
|
||||
fail "$case_name: could not locate lefthook.yml derivation"
|
||||
fi
|
||||
|
||||
run_capture_ok "$case_name: lefthook.yml derivation show failed" bash -c 'nix derivation show "$1" >"$2"' _ "$lefthook_yml_drv" "$lefthook_yml_json"
|
||||
assert_contains '\"pre-push\":{\"commands\":{\"tests\":{' "$lefthook_yml_json" "$case_name: generated check missing from pre-push"
|
||||
assert_contains 'repo-lib-check-tests' "$lefthook_yml_json" "$case_name: generated check command missing from lefthook config"
|
||||
assert_contains '\"output\":[\"failure\",\"summary\"]' "$lefthook_yml_json" "$case_name: lefthook output config missing"
|
||||
assert_contains '\"stage_fixed\":true' "$lefthook_yml_json" "$case_name: stage_fixed missing from lefthook config"
|
||||
|
||||
rm -rf "$workdir"
|
||||
CURRENT_LOG=""
|
||||
echo "[test] PASS: $case_name" >&2
|
||||
}
|
||||
|
||||
run_mk_repo_treefmt_hook_case() {
|
||||
local case_name="mkRepo configures treefmt and lefthook for dev shell hooks"
|
||||
local workdir
|
||||
workdir="$(mktemp -d)"
|
||||
local repo_dir="$workdir/mk-repo-treefmt"
|
||||
local system
|
||||
local derivation_json="$workdir/treefmt-hook.drv.json"
|
||||
local lefthook_yml_drv
|
||||
local lefthook_yml_json="$workdir/treefmt-hook-yml.drv.json"
|
||||
mkdir -p "$repo_dir"
|
||||
write_mk_repo_flake "$repo_dir"
|
||||
CURRENT_LOG="$workdir/mk-repo-treefmt.log"
|
||||
|
||||
init_git_repo "$repo_dir"
|
||||
|
||||
run_capture_ok "$case_name: treefmt should be available in shell" bash -c 'cd "$1" && nix develop --no-write-lock-file . -c sh -c '"'"'printf "%s\n" "$LEFTHOOK_BIN" && command -v treefmt'"'"'' _ "$repo_dir"
|
||||
assert_contains 'lefthook-dumb-term' "$CURRENT_LOG" "$case_name: LEFTHOOK_BIN wrapper missing"
|
||||
assert_contains '/bin/treefmt' "$CURRENT_LOG" "$case_name: treefmt missing from shell"
|
||||
|
||||
system="$(nix eval --raw --impure --expr 'builtins.currentSystem')"
|
||||
run_capture_ok "$case_name: formatting check derivation show failed" bash -c 'nix derivation show "$1" >"$2"' _ "$repo_dir#checks.${system}.formatting-check" "$workdir/formatting-check.drv.json"
|
||||
run_capture_ok "$case_name: lefthook derivation show failed" bash -c 'nix derivation show "$1" >"$2"' _ "$repo_dir#checks.${system}.lefthook-check" "$derivation_json"
|
||||
|
||||
lefthook_yml_drv="$(perl -0ne 'print "/nix/store/$1\n" if /"([a-z0-9]{32}-lefthook\.yml\.drv)"/' "$derivation_json")"
|
||||
if [[ -z "$lefthook_yml_drv" ]]; then
|
||||
fail "$case_name: could not locate lefthook.yml derivation"
|
||||
fi
|
||||
|
||||
run_capture_ok "$case_name: lefthook.yml derivation show failed" bash -c 'nix derivation show "$1" >"$2"' _ "$lefthook_yml_drv" "$lefthook_yml_json"
|
||||
assert_contains '--no-cache {staged_files}' "$lefthook_yml_json" "$case_name: treefmt hook missing staged-file format command"
|
||||
assert_contains '\"stage_fixed\":true' "$lefthook_yml_json" "$case_name: treefmt hook should re-stage formatted files"
|
||||
|
||||
rm -rf "$workdir"
|
||||
CURRENT_LOG=""
|
||||
echo "[test] PASS: $case_name" >&2
|
||||
}
|
||||
|
||||
run_mk_repo_tool_failure_case() {
|
||||
local case_name="mkRepo required tools fail shell startup"
|
||||
local workdir
|
||||
@@ -1202,7 +1347,7 @@ run_legacy_api_eval_case() {
|
||||
CURRENT_LOG="$workdir/legacy.log"
|
||||
|
||||
run_capture_ok "$case_name: flake show failed" nix flake show --json "$repo_dir"
|
||||
assert_contains '"pre-commit-check"' "$CURRENT_LOG" "$case_name: missing pre-commit-check"
|
||||
assert_contains '"lefthook-check"' "$CURRENT_LOG" "$case_name: missing lefthook-check"
|
||||
assert_contains '"release"' "$CURRENT_LOG" "$case_name: missing release package"
|
||||
|
||||
rm -rf "$workdir"
|
||||
@@ -1220,7 +1365,7 @@ run_template_eval_case() {
|
||||
CURRENT_LOG="$workdir/template.log"
|
||||
|
||||
run_capture_ok "$case_name: flake show failed" nix flake show --json "$repo_dir"
|
||||
assert_contains '"pre-commit-check"' "$CURRENT_LOG" "$case_name: missing pre-commit-check"
|
||||
assert_contains '"lefthook-check"' "$CURRENT_LOG" "$case_name: missing lefthook-check"
|
||||
assert_contains '"release"' "$CURRENT_LOG" "$case_name: missing release package"
|
||||
|
||||
rm -rf "$workdir"
|
||||
@@ -1264,6 +1409,7 @@ EOF
|
||||
run_case "channel-only from stable bumps patch" "beta" "1.0.1-beta.1"
|
||||
run_case "explicit minor bump keeps requested bump" "minor beta" "1.1.0-beta.1"
|
||||
run_set_prerelease_then_full_case
|
||||
run_stable_then_beta_cannot_reuse_same_base_case
|
||||
run_set_stable_then_full_noop_case
|
||||
run_set_stable_from_prerelease_requires_full_case
|
||||
run_patch_stable_from_prerelease_requires_full_case
|
||||
@@ -1271,11 +1417,15 @@ run_structured_release_steps_case
|
||||
run_version_metadata_case
|
||||
run_mk_repo_case
|
||||
run_mk_repo_command_tool_case
|
||||
run_mk_repo_lefthook_case
|
||||
run_mk_repo_treefmt_hook_case
|
||||
run_mk_repo_tool_failure_case
|
||||
run_impure_bootstrap_validation_case
|
||||
run_legacy_api_eval_case
|
||||
run_template_eval_case
|
||||
run_release_replace_backref_case
|
||||
run_randomized_quickcheck_cases
|
||||
if [[ "${QUICKCHECK:-0}" == "1" ]]; then
|
||||
run_randomized_quickcheck_cases
|
||||
fi
|
||||
|
||||
echo "[test] All release tests passed" >&2
|
||||
|
||||
Reference in New Issue
Block a user