feat: expose more options for lefthook
This commit is contained in:
@@ -74,6 +74,12 @@ outputs = { self, nixpkgs, repo-lib, ... }:
|
|||||||
|
|
||||||
Checks are installed through `lefthook`, with `pre-commit` and `pre-push` commands configured to run in parallel.
|
Checks are installed through `lefthook`, with `pre-commit` and `pre-push` commands configured to run in parallel.
|
||||||
|
|
||||||
|
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
|
## 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.
|
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.
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ let
|
|||||||
required = tool.required or false;
|
required = tool.required or false;
|
||||||
};
|
};
|
||||||
|
|
||||||
normalizeCheck =
|
checkToLefthookConfig =
|
||||||
pkgs: name: rawCheck:
|
pkgs: name: rawCheck:
|
||||||
let
|
let
|
||||||
check = {
|
check = {
|
||||||
@@ -152,13 +152,13 @@ let
|
|||||||
then
|
then
|
||||||
throw "repo-lib: check '${name}' has unsupported stage '${check.stage}'"
|
throw "repo-lib: check '${name}' has unsupported stage '${check.stage}'"
|
||||||
else
|
else
|
||||||
{
|
lib.setAttrByPath [ check.stage "commands" name ] {
|
||||||
enable = true;
|
run = "${wrapper}/bin/${wrapperName}${hookStageFileArgs check.stage check.passFilenames}";
|
||||||
entry = "${wrapper}/bin/${wrapperName}";
|
|
||||||
pass_filenames = check.passFilenames;
|
|
||||||
stages = [ check.stage ];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
normalizeLefthookConfig =
|
||||||
|
label: raw: if builtins.isAttrs raw then raw else throw "repo-lib: ${label} must be an attrset";
|
||||||
|
|
||||||
normalizeHookStage =
|
normalizeHookStage =
|
||||||
hookName: stage:
|
hookName: stage:
|
||||||
if
|
if
|
||||||
@@ -219,16 +219,6 @@ let
|
|||||||
) stages
|
) stages
|
||||||
);
|
);
|
||||||
|
|
||||||
hookStages =
|
|
||||||
hooks:
|
|
||||||
lib.unique (
|
|
||||||
[
|
|
||||||
"pre-commit"
|
|
||||||
"commit-msg"
|
|
||||||
]
|
|
||||||
++ lib.concatMap (hook: hook.stages or [ "pre-commit" ]) (builtins.attrValues hooks)
|
|
||||||
);
|
|
||||||
|
|
||||||
parallelHookStageConfig =
|
parallelHookStageConfig =
|
||||||
stage:
|
stage:
|
||||||
if
|
if
|
||||||
@@ -404,6 +394,7 @@ let
|
|||||||
},
|
},
|
||||||
checkSpecs ? { },
|
checkSpecs ? { },
|
||||||
rawHookEntries ? { },
|
rawHookEntries ? { },
|
||||||
|
lefthookConfig ? { },
|
||||||
extraPackages ? [ ],
|
extraPackages ? [ ],
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
@@ -425,13 +416,13 @@ let
|
|||||||
settings.formatter = { } // formatting.settings;
|
settings.formatter = { } // formatting.settings;
|
||||||
};
|
};
|
||||||
|
|
||||||
normalizedChecks = lib.mapAttrs (name: check: normalizeCheck pkgs name check) checkSpecs;
|
normalizedLefthookConfig = normalizeLefthookConfig "lefthook config" lefthookConfig;
|
||||||
hooks = mergeUniqueAttrs "hook" rawHookEntries normalizedChecks;
|
|
||||||
lefthookCheck = lefthookNix.lib.${system}.run {
|
lefthookCheck = lefthookNix.lib.${system}.run {
|
||||||
inherit src;
|
inherit src;
|
||||||
config = lib.foldl' lib.recursiveUpdate { } (
|
config = lib.foldl' lib.recursiveUpdate { } (
|
||||||
[
|
[
|
||||||
(parallelHookStageConfig "pre-commit")
|
(parallelHookStageConfig "pre-commit")
|
||||||
|
(parallelHookStageConfig "pre-push")
|
||||||
(lib.setAttrByPath [ "pre-commit" "commands" "treefmt" ] {
|
(lib.setAttrByPath [ "pre-commit" "commands" "treefmt" ] {
|
||||||
run = "${treefmtEval.config.build.wrapper}/bin/treefmt --ci {staged_files}";
|
run = "${treefmtEval.config.build.wrapper}/bin/treefmt --ci {staged_files}";
|
||||||
})
|
})
|
||||||
@@ -442,8 +433,9 @@ let
|
|||||||
run = "${pkgs.gitlint}/bin/gitlint --staged --msg-filename {1}";
|
run = "${pkgs.gitlint}/bin/gitlint --staged --msg-filename {1}";
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
++ builtins.map parallelHookStageConfig (hookStages hooks)
|
++ lib.mapAttrsToList (name: check: checkToLefthookConfig pkgs name check) checkSpecs
|
||||||
++ lib.mapAttrsToList hookToLefthookConfig hooks
|
++ lib.mapAttrsToList hookToLefthookConfig rawHookEntries
|
||||||
|
++ [ normalizedLefthookConfig ]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
selectedCheckOutputs = {
|
selectedCheckOutputs = {
|
||||||
@@ -641,6 +633,7 @@ rec {
|
|||||||
settings = { };
|
settings = { };
|
||||||
};
|
};
|
||||||
checks = { };
|
checks = { };
|
||||||
|
lefthook = { };
|
||||||
release = null;
|
release = null;
|
||||||
} rawConfig;
|
} rawConfig;
|
||||||
release =
|
release =
|
||||||
@@ -675,6 +668,7 @@ rec {
|
|||||||
preToolHook ? "",
|
preToolHook ? "",
|
||||||
extraShellHook ? "",
|
extraShellHook ? "",
|
||||||
additionalHooks ? { },
|
additionalHooks ? { },
|
||||||
|
lefthook ? { },
|
||||||
tools ? [ ],
|
tools ? [ ],
|
||||||
includeStandardPackages ? true,
|
includeStandardPackages ? true,
|
||||||
formatters ? { },
|
formatters ? { },
|
||||||
@@ -714,6 +708,7 @@ rec {
|
|||||||
;
|
;
|
||||||
formatting = normalizedFormatting;
|
formatting = normalizedFormatting;
|
||||||
rawHookEntries = additionalHooks;
|
rawHookEntries = additionalHooks;
|
||||||
|
lefthookConfig = lefthook;
|
||||||
shellConfig = shellConfig;
|
shellConfig = shellConfig;
|
||||||
tools = legacyTools;
|
tools = legacyTools;
|
||||||
extraPackages =
|
extraPackages =
|
||||||
@@ -793,6 +788,7 @@ rec {
|
|||||||
tools = [ ];
|
tools = [ ];
|
||||||
shell = { };
|
shell = { };
|
||||||
checks = { };
|
checks = { };
|
||||||
|
lefthook = { };
|
||||||
packages = { };
|
packages = { };
|
||||||
apps = { };
|
apps = { };
|
||||||
}
|
}
|
||||||
@@ -805,6 +801,9 @@ rec {
|
|||||||
strictTools = builtins.map (tool: normalizeStrictTool pkgs tool) perSystemResult.tools;
|
strictTools = builtins.map (tool: normalizeStrictTool pkgs tool) perSystemResult.tools;
|
||||||
duplicateToolNames = duplicateStrings (builtins.map (tool: tool.name) strictTools);
|
duplicateToolNames = duplicateStrings (builtins.map (tool: tool.name) strictTools);
|
||||||
mergedChecks = mergeUniqueAttrs "check" normalizedConfig.checks perSystemResult.checks;
|
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 { });
|
shellConfig = lib.recursiveUpdate normalizedConfig.shell (perSystemResult.shell or { });
|
||||||
env =
|
env =
|
||||||
if duplicateToolNames != [ ] then
|
if duplicateToolNames != [ ] then
|
||||||
@@ -820,6 +819,7 @@ rec {
|
|||||||
formatting = normalizedConfig.formatting;
|
formatting = normalizedConfig.formatting;
|
||||||
tools = strictTools;
|
tools = strictTools;
|
||||||
checkSpecs = mergedChecks;
|
checkSpecs = mergedChecks;
|
||||||
|
lefthookConfig = mergedLefthookConfig;
|
||||||
shellConfig = shellConfig;
|
shellConfig = shellConfig;
|
||||||
extraPackages = perSystemResult.shell.packages or [ ];
|
extraPackages = perSystemResult.shell.packages or [ ];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ repo-lib.lib.mkRepo {
|
|||||||
};
|
};
|
||||||
|
|
||||||
checks = { };
|
checks = { };
|
||||||
|
lefthook = { };
|
||||||
|
|
||||||
release = null; # or attrset below
|
release = null; # or attrset below
|
||||||
};
|
};
|
||||||
@@ -43,6 +44,7 @@ repo-lib.lib.mkRepo {
|
|||||||
tools = [ ];
|
tools = [ ];
|
||||||
shell.packages = [ ];
|
shell.packages = [ ];
|
||||||
checks = { };
|
checks = { };
|
||||||
|
lefthook = { };
|
||||||
packages = { };
|
packages = { };
|
||||||
apps = { };
|
apps = { };
|
||||||
};
|
};
|
||||||
@@ -116,6 +118,34 @@ Rules:
|
|||||||
- The command is wrapped as a script and connected into `lefthook.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.
|
- `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
|
## Tools
|
||||||
|
|
||||||
Preferred shape in `perSystem.tools`:
|
Preferred shape in `perSystem.tools`:
|
||||||
@@ -270,6 +300,7 @@ If the user asks for a webhook after the tag exists remotely:
|
|||||||
- `extraPackages`
|
- `extraPackages`
|
||||||
- `preToolHook`
|
- `preToolHook`
|
||||||
- `extraShellHook`
|
- `extraShellHook`
|
||||||
|
- `lefthook`
|
||||||
- `additionalHooks`
|
- `additionalHooks`
|
||||||
- old `tools = [ { name; bin; versionCmd; color; } ]`
|
- old `tools = [ { name; bin; versionCmd; color; } ]`
|
||||||
- `features.oxfmt`
|
- `features.oxfmt`
|
||||||
|
|||||||
@@ -67,6 +67,14 @@
|
|||||||
# };
|
# };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# 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:
|
# repo-lib also installs built-in hooks for:
|
||||||
# - treefmt / nixfmt on `pre-commit`
|
# - treefmt / nixfmt on `pre-commit`
|
||||||
# - gitleaks on `pre-commit`
|
# - gitleaks on `pre-commit`
|
||||||
|
|||||||
103
tests/release.sh
103
tests/release.sh
@@ -263,6 +263,39 @@ write_mk_repo_command_tool_flake() {
|
|||||||
EOF
|
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
|
||||||
|
}
|
||||||
|
|
||||||
write_tool_failure_flake() {
|
write_tool_failure_flake() {
|
||||||
local repo_dir="$1"
|
local repo_dir="$1"
|
||||||
cat >"$repo_dir/flake.nix" <<EOF
|
cat >"$repo_dir/flake.nix" <<EOF
|
||||||
@@ -868,6 +901,38 @@ run_set_prerelease_then_full_case() {
|
|||||||
echo "[test] PASS: $case_name" >&2
|
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() {
|
run_set_stable_then_full_noop_case() {
|
||||||
local case_name="set stable then full fails with no-op"
|
local case_name="set stable then full fails with no-op"
|
||||||
|
|
||||||
@@ -1158,6 +1223,38 @@ run_mk_repo_command_tool_case() {
|
|||||||
echo "[test] PASS: $case_name" >&2
|
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 '\"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_tool_failure_case() {
|
run_mk_repo_tool_failure_case() {
|
||||||
local case_name="mkRepo required tools fail shell startup"
|
local case_name="mkRepo required tools fail shell startup"
|
||||||
local workdir
|
local workdir
|
||||||
@@ -1264,6 +1361,7 @@ EOF
|
|||||||
run_case "channel-only from stable bumps patch" "beta" "1.0.1-beta.1"
|
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_case "explicit minor bump keeps requested bump" "minor beta" "1.1.0-beta.1"
|
||||||
run_set_prerelease_then_full_case
|
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_then_full_noop_case
|
||||||
run_set_stable_from_prerelease_requires_full_case
|
run_set_stable_from_prerelease_requires_full_case
|
||||||
run_patch_stable_from_prerelease_requires_full_case
|
run_patch_stable_from_prerelease_requires_full_case
|
||||||
@@ -1271,11 +1369,14 @@ run_structured_release_steps_case
|
|||||||
run_version_metadata_case
|
run_version_metadata_case
|
||||||
run_mk_repo_case
|
run_mk_repo_case
|
||||||
run_mk_repo_command_tool_case
|
run_mk_repo_command_tool_case
|
||||||
|
run_mk_repo_lefthook_case
|
||||||
run_mk_repo_tool_failure_case
|
run_mk_repo_tool_failure_case
|
||||||
run_impure_bootstrap_validation_case
|
run_impure_bootstrap_validation_case
|
||||||
run_legacy_api_eval_case
|
run_legacy_api_eval_case
|
||||||
run_template_eval_case
|
run_template_eval_case
|
||||||
run_release_replace_backref_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
|
echo "[test] All release tests passed" >&2
|
||||||
|
|||||||
Reference in New Issue
Block a user