From 9e023651f1fae3cca553815e8c1ec9eecd76f1fe Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Tue, 14 Jan 2025 18:13:32 +0300 Subject: [PATCH] feat: add lefthook option for custom path or command (#927) --- docs/mdbook/SUMMARY.md | 1 + docs/mdbook/configuration/README.md | 1 + docs/mdbook/configuration/lefthook.md | 62 +++++++++++++++++++++++++++ internal/config/config.go | 2 + internal/config/load.go | 3 ++ internal/lefthook/install.go | 1 + internal/templates/hook.tmpl | 11 +++-- internal/templates/templates.go | 4 ++ schema.json | 6 ++- testdata/lefthook_option.txt | 26 +++++++++++ 10 files changed, 113 insertions(+), 4 deletions(-) create mode 100644 docs/mdbook/configuration/lefthook.md create mode 100644 testdata/lefthook_option.txt diff --git a/docs/mdbook/SUMMARY.md b/docs/mdbook/SUMMARY.md index faaf4c2d..4bdfc44c 100644 --- a/docs/mdbook/SUMMARY.md +++ b/docs/mdbook/SUMMARY.md @@ -29,6 +29,7 @@ - [`colors`](./configuration/colors.md) - [`no_tty`](./configuration/no_tty.md) - [`extends`](./configuration/extends.md) + - [`lefthook`](./configuration/lefthook.md) - [`min_version`](./configuration/min_version.md) - [`output`](./configuration/output.md) - [`skip_output`](./configuration/skip_output.md) diff --git a/docs/mdbook/configuration/README.md b/docs/mdbook/configuration/README.md index 9dca48a8..689ae589 100644 --- a/docs/mdbook/configuration/README.md +++ b/docs/mdbook/configuration/README.md @@ -21,6 +21,7 @@ Lefthook also merges an extra config with the name `lefthook-local`. All support - [`colors`](./colors.md) - [`no_tty`](./no_tty.md) - [`extends`](./extends.md) +- [`lefthook`](./lefthook.md) - [`min_version`](./min_version.md) - [`output`](./output.md) - [`skip_output`](./skip_output.md) diff --git a/docs/mdbook/configuration/lefthook.md b/docs/mdbook/configuration/lefthook.md new file mode 100644 index 00000000..2780794b --- /dev/null +++ b/docs/mdbook/configuration/lefthook.md @@ -0,0 +1,62 @@ +## `lefthook` + +**Default:** `null` + +Provide a full path to lefthook executable or a command to run lefthook. Bourne shell (`sh`) syntax is supported. + +> **Important:** This option does not merge from `remotes` or `extends` for security reasons. But it gets merged from lefthook local config if specified. + +There are three reasons you may want to specify `lefthook`: + +1. You want to force using specific lefthook version from your dependencies (e.g. npm package) +1. You use OnP loader for your JS/TS project, and your `package.json` with lefthook dependency locates in a subfolder +1. You want to make sure you use concrete lefthook executable path and want to defined it in `lefthook-local.yml` + +### Examples + +#### Specify lefthook executable + +```yml +# lefthook.yml + +lefthook: /usr/bin/lefthook + +pre-commit: + jobs: + - run: yarn lint +``` + +#### Specify a command to run lefthook + +```yml +# lefthook.yml + +lefthook: | + cd project-with-lefthook + pnpm lefthook + +pre-commit: + jobs: + - run: yarn lint + root: project-with-lefthook +``` + +#### Force using a version from Rubygems + +```yml +# lefthook.yml + +lefthook: bundle exec lefthook + +pre-commit: + jobs: + - run: bundle exec rubocop {staged_files} +``` + +#### Enable debug logs + +```yml +# lefthook-local.yml + +lefthook: lefthook --verbose +``` diff --git a/internal/config/config.go b/internal/config/config.go index ead554f1..db82c235 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -27,6 +27,8 @@ const ( type Config struct { MinVersion string `json:"min_version,omitempty" jsonschema:"description=Specify a minimum version for the lefthook binary" koanf:"min_version" mapstructure:"min_version,omitempty"` + Lefthook string `json:"lefthook,omitempty" jsonschema:"description=Lefthook executable path or command" mapstructure:"lefthook,omitempty"` + SourceDir string `json:"source_dir,omitempty" jsonschema:"default=.lefthook/,description=Change a directory for script files. Directory for script files contains folders with git hook names which contain script files." koanf:"source_dir" mapstructure:"source_dir,omitempty"` SourceDirLocal string `json:"source_dir_local,omitempty" jsonschema:"default=.lefthook-local/,description=Change a directory for local script files (not stored in VCS)" koanf:"source_dir_local" mapstructure:"source_dir_local,omitempty"` diff --git a/internal/config/load.go b/internal/config/load.go index 3914d4aa..b8a9e5b4 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -114,6 +114,9 @@ func LoadKoanf(filesystem afero.Fs, repo *git.Repository) (*koanf.Koanf, *koanf. return nil, nil, err } + // Don't allow to set `lefthook` field from a remote config + secondary.Delete("lefthook") + // Load optional local config (e.g. lefthook-local.yml) var noLocal bool if err := loadOne(secondary, filesystem, repo.RootPath, localConfigNames); err != nil { diff --git a/internal/lefthook/install.go b/internal/lefthook/install.go index 566bdfbb..38b0b345 100644 --- a/internal/lefthook/install.go +++ b/internal/lefthook/install.go @@ -225,6 +225,7 @@ func (l *Lefthook) createHooksIfNeeded(cfg *config.Config, checkHashSum, force b Rc: cfg.Rc, AssertLefthookInstalled: cfg.AssertLefthookInstalled, Roots: roots, + LefthookExe: cfg.Lefthook, } if err = l.addHook(hook, templateArgs); err != nil { return fmt.Errorf("could not add the hook: %w", err) diff --git a/internal/templates/hook.tmpl b/internal/templates/hook.tmpl index a3bc1f3a..0bebf2ed 100644 --- a/internal/templates/hook.tmpl +++ b/internal/templates/hook.tmpl @@ -18,15 +18,20 @@ call_lefthook() if test -n "$LEFTHOOK_BIN" then "$LEFTHOOK_BIN" "$@" + {{ if .LefthookExe -}} + elif test -n "{{ .LefthookExe }}" + then + {{ .LefthookExe }} "$@" + {{ end -}} elif lefthook{{.Extension}} -h >/dev/null 2>&1 then lefthook{{.Extension}} "$@" - {{if .Extension -}} + {{ if .Extension -}} {{/* Check if lefthook.bat exists. Ruby bundler creates such a wrapper */ -}} elif lefthook.bat -h >/dev/null 2>&1 then lefthook.bat "$@" - {{end -}} + {{ end -}} else dir="$(git rev-parse --show-toplevel)" osArch=$(uname | tr '[:upper:]' '[:lower:]') @@ -57,7 +62,7 @@ call_lefthook() elif test -f "$dir/{{.}}/node_modules/lefthook/bin/index.js" then "$dir/{{.}}/node_modules/lefthook/bin/index.js" "$@" - {{end}} + {{ end }} elif bundle exec lefthook -h >/dev/null 2>&1 then bundle exec lefthook "$@" diff --git a/internal/templates/templates.go b/internal/templates/templates.go index 3692795c..2a834fd2 100644 --- a/internal/templates/templates.go +++ b/internal/templates/templates.go @@ -5,6 +5,7 @@ import ( "embed" "fmt" "runtime" + "strings" "text/template" ) @@ -15,6 +16,7 @@ var templatesFS embed.FS type Args struct { Rc string + LefthookExe string AssertLefthookInstalled bool Roots []string } @@ -22,6 +24,7 @@ type Args struct { type hookTmplData struct { HookName string Extension string + LefthookExe string Rc string Roots []string AssertLefthookInstalled bool @@ -36,6 +39,7 @@ func Hook(hookName string, args Args) []byte { Rc: args.Rc, AssertLefthookInstalled: args.AssertLefthookInstalled, Roots: args.Roots, + LefthookExe: strings.ReplaceAll(strings.TrimSpace(args.LefthookExe), "\n", ";"), }) if err != nil { panic(err) diff --git a/schema.json b/schema.json index cfc176ce..6fb3f7d5 100644 --- a/schema.json +++ b/schema.json @@ -392,12 +392,16 @@ "type": "object" } }, - "$comment": "Last updated on 2025.01.10.", + "$comment": "Last updated on 2025.01.14.", "properties": { "min_version": { "type": "string", "description": "Specify a minimum version for the lefthook binary" }, + "lefthook": { + "type": "string", + "description": "Lefthook executable path or command" + }, "source_dir": { "type": "string", "description": "Change a directory for script files. Directory for script files contains folders with git hook names which contain script files.", diff --git a/testdata/lefthook_option.txt b/testdata/lefthook_option.txt new file mode 100644 index 00000000..f94511fd --- /dev/null +++ b/testdata/lefthook_option.txt @@ -0,0 +1,26 @@ +exec git init +exec lefthook install +exec git config user.email "you@example.com" +exec git config user.name "Your Name" +exec git add -A +exec git commit -m 'must show debug logs' +stderr 'injected' +stdout '[lefthook]' + +-- lefthook.yml -- +lefthook: | + echo 'injected' + lefthook -v + +output: + - execution +pre-commit: + jobs: + - run: echo {all_files} + glob: "*.txt" + +-- file.txt -- +sometext + +-- file.js -- +somecode