diff --git a/.gitignore b/.gitignore index 4c317ad7..ac70140c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .idea/ /lefthook /lefthook-local.yml +/gen_schema tmp/ dist/ diff --git a/Makefile b/Makefile index 76f86ef8..f0669723 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,9 @@ build: build-with-coverage: go build -cover -ldflags "-s -w -X github.com/evilmartians/lefthook/internal/version.commit=$(COMMIT_HASH)" -o lefthook +jsonschema: + go generate internal/gen/jsonschema.go > schema.json + install: build cp lefthook $$(go env GOPATH)/bin diff --git a/cmd/commands.go b/cmd/commands.go index 4871458f..6310a4c3 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -1,4 +1,4 @@ -//go:build !no_self_update +//go:build !no_self_update && !jsonschema package cmd diff --git a/cmd/commands_no_self_update.go b/cmd/commands_no_self_update.go index 56daa0d5..7fa07cac 100644 --- a/cmd/commands_no_self_update.go +++ b/cmd/commands_no_self_update.go @@ -1,4 +1,4 @@ -//go:build no_self_update +//go:build no_self_update && !jsonschema package cmd diff --git a/go.mod b/go.mod index 21931c2e..fa2c425e 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/charmbracelet/lipgloss v1.0.0 github.com/creack/pty v1.1.24 github.com/gobwas/glob v0.2.3 + github.com/invopop/jsonschema v0.13.0 github.com/knadh/koanf/maps v0.1.1 github.com/knadh/koanf/parsers/json v0.1.0 github.com/knadh/koanf/parsers/toml/v2 v2.1.0 @@ -22,26 +23,30 @@ require ( github.com/schollz/progressbar/v3 v3.17.1 github.com/spf13/afero v1.11.0 github.com/spf13/cobra v1.8.1 + github.com/stoewer/go-strcase v1.3.0 github.com/stretchr/testify v1.9.0 - gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 ) require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.1 // indirect github.com/charmbracelet/x/ansi v0.4.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-viper/mapstructure/v2 v2.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect golang.org/x/tools v0.22.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect ) require ( - github.com/alessio/shellescape v1.4.1 // indirect + github.com/alessio/shellescape v1.4.1 github.com/fatih/color v1.14.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect diff --git a/go.sum b/go.sum index 016b2c8d..7aa2bf99 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,12 @@ github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVK github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/briandowns/spinner v1.23.1 h1:t5fDPmScwUjozhDj4FA46p5acZWIPXYE30qW2Ptu650= github.com/briandowns/spinner v1.23.1/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk= @@ -16,6 +20,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= @@ -26,6 +32,9 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= +github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/parsers/json v0.1.0 h1:dzSZl5pf5bBcW0Acnu20Djleto19T0CfHcvZ14NJ6fU= @@ -44,6 +53,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -66,6 +77,7 @@ github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1n github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -83,8 +95,18 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= @@ -95,10 +117,9 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= -gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 h1:8ajkpB4hXVftY5ko905id+dOnmorcS2CHNxxHLLDcFM= -gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61/go.mod h1:IfMagxm39Ys4ybJrDb7W3Ob8RwxftP0Yy+or/NVz1O8= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/command.go b/internal/config/command.go index d2a4074f..40a1f5f0 100644 --- a/internal/config/command.go +++ b/internal/config/command.go @@ -10,16 +10,16 @@ type Command struct { Run string `json:"run" mapstructure:"run" toml:"run" yaml:"run"` Files string `json:"files,omitempty" mapstructure:"files" toml:"files,omitempty" yaml:",omitempty"` - Skip interface{} `json:"skip,omitempty" mapstructure:"skip" toml:"skip,omitempty,inline" yaml:",omitempty"` - Only interface{} `json:"only,omitempty" mapstructure:"only" toml:"only,omitempty,inline" yaml:",omitempty"` - Tags []string `json:"tags,omitempty" mapstructure:"tags" toml:"tags,omitempty" yaml:",omitempty"` - Env map[string]string `json:"env,omitempty" mapstructure:"env" toml:"env,omitempty" yaml:",omitempty"` + Skip interface{} `json:"skip,omitempty" jsonschema:"oneof_type=boolean;array" mapstructure:"skip" toml:"skip,omitempty,inline" yaml:",omitempty"` + Only interface{} `json:"only,omitempty" jsonschema:"oneof_type=boolean;array" mapstructure:"only" toml:"only,omitempty,inline" yaml:",omitempty"` + Tags []string `json:"tags,omitempty" mapstructure:"tags" toml:"tags,omitempty" yaml:",omitempty"` + Env map[string]string `json:"env,omitempty" mapstructure:"env" toml:"env,omitempty" yaml:",omitempty"` FileTypes []string `json:"file_types,omitempty" koanf:"file_types" mapstructure:"file_types" toml:"file_types,omitempty" yaml:"file_types,omitempty"` - Glob string `json:"glob,omitempty" mapstructure:"glob" toml:"glob,omitempty" yaml:",omitempty"` - Root string `json:"root,omitempty" mapstructure:"root" toml:"root,omitempty" yaml:",omitempty"` - Exclude interface{} `json:"exclude,omitempty" mapstructure:"exclude" toml:"exclude,omitempty" yaml:",omitempty"` + Glob string `json:"glob,omitempty" mapstructure:"glob" toml:"glob,omitempty" yaml:",omitempty"` + Root string `json:"root,omitempty" mapstructure:"root" toml:"root,omitempty" yaml:",omitempty"` + Exclude interface{} `json:"exclude,omitempty" jsonschema:"oneof_type=string;array" mapstructure:"exclude" toml:"exclude,omitempty" yaml:",omitempty"` Priority int `json:"priority,omitempty" mapstructure:"priority" toml:"priority,omitempty" yaml:",omitempty"` FailText string `json:"fail_text,omitempty" koanf:"fail_text" mapstructure:"fail_text" toml:"fail_text,omitempty" yaml:"fail_text,omitempty"` diff --git a/internal/config/config.go b/internal/config/config.go index 3d4f270f..ead554f1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -25,23 +25,34 @@ const ( ) type Config struct { - MinVersion string `koanf:"min_version" mapstructure:"min_version,omitempty"` - SourceDir string `koanf:"source_dir" mapstructure:"source_dir"` - SourceDirLocal string `koanf:"source_dir_local" mapstructure:"source_dir_local"` - Rc string `mapstructure:"rc,omitempty"` - SkipOutput interface{} `koanf:"skip_output" mapstructure:"skip_output,omitempty"` - Output interface{} `mapstructure:"output,omitempty"` - Extends []string `mapstructure:"extends,omitempty"` - NoTTY bool `koanf:"no_tty" mapstructure:"no_tty,omitempty"` - AssertLefthookInstalled bool `koanf:"assert_lefthook_installed" mapstructure:"assert_lefthook_installed,omitempty"` - Colors interface{} `mapstructure:"colors,omitempty"` - SkipLFS bool `koanf:"skip_lfs" mapstructure:"skip_lfs,omitempty"` + MinVersion string `json:"min_version,omitempty" jsonschema:"description=Specify a minimum version for the lefthook binary" koanf:"min_version" mapstructure:"min_version,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"` + + Rc string `json:"rc,omitempty" jsonschema:"description=Provide an rc file - a simple sh script" mapstructure:"rc,omitempty"` + + SkipOutput interface{} `json:"skip_output,omitempty" jsonschema:"oneof_type=boolean;array" koanf:"skip_output" mapstructure:"skip_output,omitempty"` + + Output interface{} `json:"output,omitempty" jsonschema:"oneof_type=boolean;array,description=Manage verbosity by skipping the printing of output of some steps" mapstructure:"output,omitempty"` + + Extends []string `json:"extends,omitempty" jsonschema:"description=Specify files to extend config with" mapstructure:"extends,omitempty"` + + NoTTY bool `json:"no_tty,omitempty" jsonschema:"description=Whether hide spinner and other interactive things" koanf:"no_tty" mapstructure:"no_tty,omitempty"` + + AssertLefthookInstalled bool `json:"assert_lefthook_installed,omitempty" koanf:"assert_lefthook_installed" mapstructure:"assert_lefthook_installed,omitempty"` + + Colors interface{} `json:"colors,omitempty" jsonschema:"description=Enable disable or set your own colors for lefthook output,default=true,oneof_type=boolean;object" mapstructure:"colors,omitempty"` + + SkipLFS bool `json:"skip_lfs,omitempty" jsonschema:"description=Skip running Git LFS hooks (enabled by default)" koanf:"skip_lfs" mapstructure:"skip_lfs,omitempty"` + + Remotes []*Remote `json:"remotes,omitempty" jsonschema:"description=Provide multiple remote configs to use lefthook configurations shared across projects. Lefthook will automatically download and merge configurations into main config." mapstructure:"remotes,omitempty"` // Deprecated: use Remotes - Remote *Remote `mapstructure:"remote,omitempty"` - Remotes []*Remote `mapstructure:"remotes,omitempty"` + Remote *Remote `json:"remote,omitempty" jsonschema:"description=Deprecated: use remotes" mapstructure:"-"` - Hooks map[string]*Hook `mapstructure:"-"` + Hooks map[string]*Hook `jsonschema:"-" mapstructure:"-"` } func (c *Config) Md5() (checksum string, err error) { diff --git a/internal/config/hook.go b/internal/config/hook.go index 75d28a47..6c6b407d 100644 --- a/internal/config/hook.go +++ b/internal/config/hook.go @@ -8,13 +8,13 @@ import ( const CMD = "{cmd}" type Hook struct { - Parallel bool `json:"parallel,omitempty" mapstructure:"parallel" toml:"parallel,omitempty" yaml:",omitempty"` - Piped bool `json:"piped,omitempty" mapstructure:"piped" toml:"piped,omitempty" yaml:",omitempty"` - Follow bool `json:"follow,omitempty" mapstructure:"follow" toml:"follow,omitempty" yaml:",omitempty"` - Files string `json:"files,omitempty" mapstructure:"files" toml:"files,omitempty" yaml:",omitempty"` - ExcludeTags []string `json:"exclude_tags,omitempty" koanf:"exclude_tags" mapstructure:"exclude_tags" toml:"exclude_tags,omitempty" yaml:"exclude_tags,omitempty"` - Skip interface{} `json:"skip,omitempty" mapstructure:"skip" toml:"skip,omitempty,inline" yaml:",omitempty"` - Only interface{} `json:"only,omitempty" mapstructure:"only" toml:"only,omitempty,inline" yaml:",omitempty"` + Parallel bool `json:"parallel,omitempty" mapstructure:"parallel" toml:"parallel,omitempty" yaml:",omitempty"` + Piped bool `json:"piped,omitempty" mapstructure:"piped" toml:"piped,omitempty" yaml:",omitempty"` + Follow bool `json:"follow,omitempty" mapstructure:"follow" toml:"follow,omitempty" yaml:",omitempty"` + Files string `json:"files,omitempty" mapstructure:"files" toml:"files,omitempty" yaml:",omitempty"` + ExcludeTags []string `json:"exclude_tags,omitempty" koanf:"exclude_tags" mapstructure:"exclude_tags" toml:"exclude_tags,omitempty" yaml:"exclude_tags,omitempty"` + Skip interface{} `json:"skip,omitempty" jsonschema:"oneof_type=boolean;array" mapstructure:"skip" toml:"skip,omitempty,inline" yaml:",omitempty"` + Only interface{} `json:"only,omitempty" jsonschema:"oneof_type=boolean;array" mapstructure:"only" toml:"only,omitempty,inline" yaml:",omitempty"` Jobs []*Job `json:"jobs,omitempty" mapstructure:"jobs" toml:"jobs,omitempty" yaml:",omitempty"` diff --git a/internal/config/job.go b/internal/config/job.go index a69e7c9f..287f7307 100644 --- a/internal/config/job.go +++ b/internal/config/job.go @@ -1,10 +1,10 @@ package config type Job struct { - Name string `json:"name,omitempty" mapstructure:"name" toml:"name,omitempty" yaml:",omitempty"` - Run string `json:"run,omitempty" mapstructure:"run" toml:"run,omitempty" yaml:",omitempty"` - Script string `json:"script,omitempty" mapstructure:"script" toml:"script,omitempty" yaml:",omitempty"` - Runner string `json:"runner,omitempty" mapstructure:"runner" toml:"runner,omitempty" yaml:",omitempty"` + Name string `json:"name,omitempty" mapstructure:"name" toml:"name,omitempty" yaml:",omitempty"` + Run string `json:"run,omitempty" jsonschema:"oneof_required=Run a command" mapstructure:"run" toml:"run,omitempty" yaml:",omitempty"` + Script string `json:"script,omitempty" jsonschema:"oneof_required=Run a script" mapstructure:"script" toml:"script,omitempty" yaml:",omitempty"` + Runner string `json:"runner,omitempty" mapstructure:"runner" toml:"runner,omitempty" yaml:",omitempty"` Glob string `json:"glob,omitempty" mapstructure:"glob" toml:"glob,omitempty" yaml:",omitempty"` Root string `json:"root,omitempty" mapstructure:"root" toml:"root,omitempty" yaml:",omitempty"` @@ -20,18 +20,18 @@ type Job struct { UseStdin bool `json:"use_stdin,omitempty" koanf:"use_stdin" mapstructure:"use_stdin" toml:"use_stdin,omitempty" yaml:",omitempty"` StageFixed bool `json:"stage_fixed,omitempty" koanf:"stage_fixed" mapstructure:"stage_fixed" toml:"stage_fixed,omitempty" yaml:"stage_fixed,omitempty"` - Exclude interface{} `json:"exclude,omitempty" mapstructure:"exclude" toml:"exclude,omitempty" yaml:",omitempty"` - Skip interface{} `json:"skip,omitempty" mapstructure:"skip" toml:"skip,omitempty,inline" yaml:",omitempty"` - Only interface{} `json:"only,omitempty" mapstructure:"only" toml:"only,omitempty,inline" yaml:",omitempty"` + Exclude interface{} `json:"exclude,omitempty" jsonschema:"oneof_type=string;array" mapstructure:"exclude" toml:"exclude,omitempty" yaml:",omitempty"` + Skip interface{} `json:"skip,omitempty" jsonschema:"oneof_type=boolean;array" mapstructure:"skip" toml:"skip,omitempty,inline" yaml:",omitempty"` + Only interface{} `json:"only,omitempty" jsonschema:"oneof_type=boolean;array" mapstructure:"only" toml:"only,omitempty,inline" yaml:",omitempty"` - Group *Group `json:"group,omitempty" mapstructure:"group" toml:"group,omitempty" yaml:",omitempty"` + Group *Group `json:"group,omitempty" jsonschema:"oneof_required=Run a group" mapstructure:"group" toml:"group,omitempty" yaml:",omitempty"` } type Group struct { Root string `json:"root,omitempty" mapstructure:"root" toml:"root,omitempty" yaml:",omitempty"` Parallel bool `json:"parallel,omitempty" mapstructure:"parallel" toml:"parallel,omitempty" yaml:",omitempty"` Piped bool `json:"piped,omitempty" mapstructure:"piped" toml:"piped,omitempty" yaml:",omitempty"` - Jobs []*Job `json:"jobs,omitempty" mapstructure:"jobs" toml:"jobs,omitempty" yaml:",omitempty"` + Jobs []*Job `json:"jobs" mapstructure:"jobs" toml:"jobs" yaml:"jobs"` } func (job *Job) PrintableName(id string) string { diff --git a/internal/config/remote.go b/internal/config/remote.go index 15f1ca93..7c1fe6fb 100644 --- a/internal/config/remote.go +++ b/internal/config/remote.go @@ -1,13 +1,18 @@ package config type Remote struct { - GitURL string `json:"git_url,omitempty" koanf:"git_url" mapstructure:"git_url" toml:"git_url" yaml:"git_url"` - Ref string `json:"ref,omitempty" mapstructure:"ref,omitempty" toml:"ref,omitempty" yaml:",omitempty"` - // Deprecated - Config string `json:"config,omitempty" mapstructure:"config,omitempty" toml:"config,omitempty" yaml:",omitempty"` - Configs []string `json:"configs,omitempty" mapstructure:"configs,omitempty" toml:"configs,omitempty" yaml:",omitempty"` - Refetch bool `json:"refetch,omitempty" mapstructure:"refetch,omitempty" toml:"refetch,omitempty" yaml:",omitempty"` - RefetchFrequency string `json:"refetch_frequency,omitempty" koanf:"refetch_frequency" mapstructure:"refetch_frequency,omitempty" toml:"refetch_frequency,omitempty" yaml:",omitempty"` + GitURL string `json:"git_url,omitempty" jsonschema:"description=A URL to Git repository. It will be accessed with privileges of the machine lefthook runs on." koanf:"git_url" mapstructure:"git_url" toml:"git_url" yaml:"git_url"` + + Ref string `json:"ref,omitempty" jsonschema:"description=An optional *branch* or *tag* name" mapstructure:"ref,omitempty" toml:"ref,omitempty" yaml:",omitempty"` + + Configs []string `json:"configs,omitempty" jsonschema:"description=An optional array of config paths from remote's root,default=lefthook.yml" mapstructure:"configs,omitempty" toml:"configs,omitempty" yaml:",omitempty"` + + Refetch bool `json:"refetch,omitempty" jsonschema:"description=Set to true if you want to always refetch the remote" mapstructure:"refetch,omitempty" toml:"refetch,omitempty" yaml:",omitempty"` + + RefetchFrequency string `json:"refetch_frequency,omitempty" jsonschema:"description=Provide a frequency for the remotes refetches,example=24h" koanf:"refetch_frequency" mapstructure:"refetch_frequency,omitempty" toml:"refetch_frequency,omitempty" yaml:",omitempty"` + + // Deprecated: use `configs` + Config string `json:"config,omitempty" jsonschema:"description=Deprecated: use configs" mapstructure:"config,omitempty" toml:"config,omitempty" yaml:",omitempty"` } func (r *Remote) Configured() bool { diff --git a/internal/config/script.go b/internal/config/script.go index 704b507b..77142497 100644 --- a/internal/config/script.go +++ b/internal/config/script.go @@ -6,13 +6,13 @@ import ( ) type Script struct { - Runner string `json:"runner" mapstructure:"runner" toml:"runner" yaml:"runner"` + Runner string `json:"runner,omitempty" mapstructure:"runner" toml:"runner,omitempty" yaml:"runner,omitempty"` - Skip interface{} `json:"skip,omitempty" mapstructure:"skip" toml:"skip,omitempty,inline" yaml:",omitempty"` - Only interface{} `json:"only,omitempty" mapstructure:"only" toml:"only,omitempty,inline" yaml:",omitempty"` - Tags []string `json:"tags,omitempty" mapstructure:"tags" toml:"tags,omitempty" yaml:",omitempty"` - Env map[string]string `json:"env,omitempty" mapstructure:"env" toml:"env,omitempty" yaml:",omitempty"` - Priority int `json:"priority,omitempty" mapstructure:"priority" toml:"priority,omitempty" yaml:",omitempty"` + Skip interface{} `json:"skip,omitempty" jsonschema:"oneof_type=boolean;array" mapstructure:"skip" toml:"skip,omitempty,inline" yaml:",omitempty"` + Only interface{} `json:"only,omitempty" jsonschema:"oneof_type=boolean;array" mapstructure:"only" toml:"only,omitempty,inline" yaml:",omitempty"` + Tags []string `json:"tags,omitempty" mapstructure:"tags" toml:"tags,omitempty" yaml:",omitempty"` + Env map[string]string `json:"env,omitempty" mapstructure:"env" toml:"env,omitempty" yaml:",omitempty"` + Priority int `json:"priority,omitempty" mapstructure:"priority" toml:"priority,omitempty" yaml:",omitempty"` FailText string `json:"fail_text,omitempty" koanf:"fail_text" mapstructure:"fail_text" toml:"fail_text,omitempty" yaml:"fail_text,omitempty"` Interactive bool `json:"interactive,omitempty" mapstructure:"interactive" toml:"interactive,omitempty" yaml:",omitempty"` diff --git a/internal/gen/jsonschema.go b/internal/gen/jsonschema.go new file mode 100644 index 00000000..e55d8c88 --- /dev/null +++ b/internal/gen/jsonschema.go @@ -0,0 +1,64 @@ +package main + +import ( + "encoding/json" + "reflect" + "time" + + "github.com/invopop/jsonschema" + + "github.com/evilmartians/lefthook/internal/config" + "github.com/evilmartians/lefthook/internal/log" +) + +//go:generate go run jsonschema.go +func main() { + r := new(jsonschema.Reflector) + r.ExpandedStruct = true + r.AdditionalFields = func(t reflect.Type) []reflect.StructField { + if t == reflect.TypeOf(config.Config{}) { + return reflect.VisibleFields(reflect.TypeOf(struct { + PreCommit *config.Hook `json:"pre-commit,omitempty"` + ApplypatchMsg *config.Hook `json:"applypatch-msg,omitempty"` + PreApplypatch *config.Hook `json:"pre-applypatch,omitempty"` + PostApplypatch *config.Hook `json:"post-applypatch,omitempty"` + PreMergeCommit *config.Hook `json:"pre-merge-commit,omitempty"` + PrepareCommitMsg *config.Hook `json:"prepare-commit-msg,omitempty"` + CommitMsg *config.Hook `json:"commit-msg,omitempty"` + PostCommit *config.Hook `json:"post-commit,omitempty"` + PreRebase *config.Hook `json:"pre-rebase,omitempty"` + PostCheckout *config.Hook `json:"post-checkout,omitempty"` + PostMerge *config.Hook `json:"post-merge,omitempty"` + PrePush *config.Hook `json:"pre-push,omitempty"` + PreReceive *config.Hook `json:"pre-receive,omitempty"` + Update *config.Hook `json:"update,omitempty"` + ProcReceive *config.Hook `json:"proc-receive,omitempty"` + PostReceive *config.Hook `json:"post-receive,omitempty"` + PostUpdate *config.Hook `json:"post-update,omitempty"` + ReferenceTransaction *config.Hook `json:"reference-transaction,omitempty"` + PushToCheckout *config.Hook `json:"push-to-checkout,omitempty"` + PreAutoGc *config.Hook `json:"pre-auto-gc,omitempty"` + PostRewrite *config.Hook `json:"post-rewrite,omitempty"` + SendemailValidate *config.Hook `json:"sendemail-validate,omitempty"` + FsmonitorWatchman *config.Hook `json:"fsmonitor-watchman,omitempty"` + P4Changelist *config.Hook `json:"p4-changelist,omitempty"` + P4PrepareChangelist *config.Hook `json:"p4-prepare-changelist,omitempty"` + P4PostChangelist *config.Hook `json:"p4-post-changelist,omitempty"` + P4PreSubmit *config.Hook `json:"p4-pre-submit,omitempty"` + PostIndexChange *config.Hook `json:"post-index-change,omitempty"` + }{})) + } + + return []reflect.StructField{} + } + schema := r.Reflect(&config.Config{}) + schema.ID = "https://json.schemastore.org/lefthook.json" + schema.Comments = "Last updated on " + time.Now().Format("2006.01.02") + "." + dumped, err := json.MarshalIndent(schema, "", " ") + if err != nil { + log.Error(err) + return + } + + log.Info(string(dumped)) +} diff --git a/lefthook.yml b/lefthook.yml index 1a753d89..144fcd5d 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -21,3 +21,7 @@ pre-commit: - name: fix typos run: typos --write-changes {staged_files} stage_fixed: true + + - name: update JSON schema + run: go generate internal/gen/jsonschema.go > schema.json && git add schema.json + glob: 'internal/config/*.go' diff --git a/schema.json b/schema.json new file mode 100644 index 00000000..5d8f9cc5 --- /dev/null +++ b/schema.json @@ -0,0 +1,549 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json.schemastore.org/lefthook.json", + "$defs": { + "Command": { + "properties": { + "run": { + "type": "string" + }, + "files": { + "type": "string" + }, + "skip": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "array" + } + ] + }, + "only": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "array" + } + ] + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "file_types": { + "items": { + "type": "string" + }, + "type": "array" + }, + "glob": { + "type": "string" + }, + "root": { + "type": "string" + }, + "exclude": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array" + } + ] + }, + "priority": { + "type": "integer" + }, + "fail_text": { + "type": "string" + }, + "interactive": { + "type": "boolean" + }, + "use_stdin": { + "type": "boolean" + }, + "stage_fixed": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "run" + ] + }, + "Group": { + "properties": { + "root": { + "type": "string" + }, + "parallel": { + "type": "boolean" + }, + "piped": { + "type": "boolean" + }, + "jobs": { + "items": { + "$ref": "#/$defs/Job" + }, + "type": "array" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "jobs" + ] + }, + "Hook": { + "properties": { + "parallel": { + "type": "boolean" + }, + "piped": { + "type": "boolean" + }, + "follow": { + "type": "boolean" + }, + "files": { + "type": "string" + }, + "exclude_tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "skip": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "array" + } + ] + }, + "only": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "array" + } + ] + }, + "jobs": { + "items": { + "$ref": "#/$defs/Job" + }, + "type": "array" + }, + "commands": { + "additionalProperties": { + "$ref": "#/$defs/Command" + }, + "type": "object" + }, + "scripts": { + "additionalProperties": { + "$ref": "#/$defs/Script" + }, + "type": "object" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Job": { + "oneOf": [ + { + "required": [ + "run" + ], + "title": "Run a command" + }, + { + "required": [ + "script" + ], + "title": "Run a script" + }, + { + "required": [ + "group" + ], + "title": "Run a group" + } + ], + "properties": { + "name": { + "type": "string" + }, + "run": { + "type": "string" + }, + "script": { + "type": "string" + }, + "runner": { + "type": "string" + }, + "glob": { + "type": "string" + }, + "root": { + "type": "string" + }, + "files": { + "type": "string" + }, + "fail_text": { + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "file_types": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "interactive": { + "type": "boolean" + }, + "use_stdin": { + "type": "boolean" + }, + "stage_fixed": { + "type": "boolean" + }, + "exclude": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "array" + } + ] + }, + "skip": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "array" + } + ] + }, + "only": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "array" + } + ] + }, + "group": { + "$ref": "#/$defs/Group" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Remote": { + "properties": { + "git_url": { + "type": "string", + "description": "A URL to Git repository. It will be accessed with privileges of the machine lefthook runs on." + }, + "ref": { + "type": "string", + "description": "An optional *branch* or *tag* name" + }, + "configs": { + "items": { + "type": "string" + }, + "type": "array", + "description": "An optional array of config paths from remote's root", + "default": [ + "lefthook.yml" + ] + }, + "refetch": { + "type": "boolean", + "description": "Set to true if you want to always refetch the remote" + }, + "refetch_frequency": { + "type": "string", + "description": "Provide a frequency for the remotes refetches", + "examples": [ + "24h" + ] + }, + "config": { + "type": "string", + "description": "Deprecated: use configs" + } + }, + "additionalProperties": false, + "type": "object" + }, + "Script": { + "properties": { + "runner": { + "type": "string" + }, + "skip": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "array" + } + ] + }, + "only": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "array" + } + ] + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "priority": { + "type": "integer" + }, + "fail_text": { + "type": "string" + }, + "interactive": { + "type": "boolean" + }, + "use_stdin": { + "type": "boolean" + }, + "stage_fixed": { + "type": "boolean" + } + }, + "additionalProperties": false, + "type": "object" + } + }, + "$comment": "Last updated on 2025.01.09.", + "properties": { + "min_version": { + "type": "string", + "description": "Specify a minimum version for the lefthook binary" + }, + "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.", + "default": ".lefthook/" + }, + "source_dir_local": { + "type": "string", + "description": "Change a directory for local script files (not stored in VCS)", + "default": ".lefthook-local/" + }, + "rc": { + "type": "string", + "description": "Provide an rc file - a simple sh script" + }, + "skip_output": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "array" + } + ] + }, + "output": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "array" + } + ], + "description": "Manage verbosity by skipping the printing of output of some steps" + }, + "extends": { + "items": { + "type": "string" + }, + "type": "array", + "description": "Specify files to extend config with" + }, + "no_tty": { + "type": "boolean", + "description": "Whether hide spinner and other interactive things" + }, + "assert_lefthook_installed": { + "type": "boolean" + }, + "colors": { + "oneOf": [ + { + "type": "boolean" + }, + { + "type": "object" + } + ], + "description": "Enable disable or set your own colors for lefthook output" + }, + "skip_lfs": { + "type": "boolean", + "description": "Skip running Git LFS hooks (enabled by default)" + }, + "remotes": { + "items": { + "$ref": "#/$defs/Remote" + }, + "type": "array", + "description": "Provide multiple remote configs to use lefthook configurations shared across projects. Lefthook will automatically download and merge configurations into main config." + }, + "remote": { + "$ref": "#/$defs/Remote", + "description": "Deprecated: use remotes" + }, + "pre-commit": { + "$ref": "#/$defs/Hook" + }, + "applypatch-msg": { + "$ref": "#/$defs/Hook" + }, + "pre-applypatch": { + "$ref": "#/$defs/Hook" + }, + "post-applypatch": { + "$ref": "#/$defs/Hook" + }, + "pre-merge-commit": { + "$ref": "#/$defs/Hook" + }, + "prepare-commit-msg": { + "$ref": "#/$defs/Hook" + }, + "commit-msg": { + "$ref": "#/$defs/Hook" + }, + "post-commit": { + "$ref": "#/$defs/Hook" + }, + "pre-rebase": { + "$ref": "#/$defs/Hook" + }, + "post-checkout": { + "$ref": "#/$defs/Hook" + }, + "post-merge": { + "$ref": "#/$defs/Hook" + }, + "pre-push": { + "$ref": "#/$defs/Hook" + }, + "pre-receive": { + "$ref": "#/$defs/Hook" + }, + "update": { + "$ref": "#/$defs/Hook" + }, + "proc-receive": { + "$ref": "#/$defs/Hook" + }, + "post-receive": { + "$ref": "#/$defs/Hook" + }, + "post-update": { + "$ref": "#/$defs/Hook" + }, + "reference-transaction": { + "$ref": "#/$defs/Hook" + }, + "push-to-checkout": { + "$ref": "#/$defs/Hook" + }, + "pre-auto-gc": { + "$ref": "#/$defs/Hook" + }, + "post-rewrite": { + "$ref": "#/$defs/Hook" + }, + "sendemail-validate": { + "$ref": "#/$defs/Hook" + }, + "fsmonitor-watchman": { + "$ref": "#/$defs/Hook" + }, + "p4-changelist": { + "$ref": "#/$defs/Hook" + }, + "p4-prepare-changelist": { + "$ref": "#/$defs/Hook" + }, + "p4-post-changelist": { + "$ref": "#/$defs/Hook" + }, + "p4-pre-submit": { + "$ref": "#/$defs/Hook" + }, + "post-index-change": { + "$ref": "#/$defs/Hook" + } + }, + "additionalProperties": false, + "type": "object" +}