diff --git a/.commitlint-scope.yaml b/.commitlint-scope.yaml new file mode 100644 index 0000000..409b60f --- /dev/null +++ b/.commitlint-scope.yaml @@ -0,0 +1,21 @@ +#$schema: https://github.com/thumbrise/commitlint-scope/blob/main/docs/schema/config.v3.json + +# Scope parsing customization. Not required, if you follow common conventional header. In example: 'type!(scope): subject' +#scopeRegex: ^[a-z]+(?:\((?P[^)]+)\))?!?:\s + +# Patterns list: each item specifies a list of scopes and the corresponding file glob patterns. +patterns: +# - scopes: ["auth"] +# files: ["services/auth/**"] +# +# - scopes: ["migrations", "sql"] +# files: ["database/migrations/*.sql"] +# +# - scopes: ["frontend", "assets"] +# files: ["**/assets/**", "**/frontend/**"] +# +# - scopes: ["docs", "md"] +# files: ["**/*.md"] +# +# - scopes: ["some.dot.scope", "any-anotherscope"] +# files: ["**/rail.v1.json"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bdc837..f4e1458 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,7 @@ jobs: args: --help - name: Run lint - run: task lint + run: golangci-lint run commitlint: name: Lint commit messages @@ -59,6 +59,25 @@ jobs: - name: Validate PR commits with commitlint run: npx commitlint --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }} --verbose + commitlint-scope: + name: Lint commit scopes + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Lint commit scopes + run: | + docker run --rm \ + -v "${{ github.workspace }}:/repo" \ + -w /repo \ + thumbrise/commitlint-scope:latest-alpine \ + run \ + --from ${{ github.event.pull_request.base.sha }} \ + --to ${{ github.event.pull_request.head.sha }} \ + --verbose + test: name: Test runs-on: ubuntu-latest diff --git a/README.md b/README.md index 314f4d5..48c6928 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ docker run --rm -v "$(pwd):/repo" -w /repo thumbrise/commitlint-scope:latest-alp ### Init -Generate .commitlint-scope.yml file. +Generate .commitlint-scope.yaml file. ```shell commitlint-scope init diff --git a/Taskfile.yaml b/Taskfile.yaml index 61ede0b..dd1bffb 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -10,6 +10,7 @@ tasks: lint: desc: Run lint with auto-fix where possible (dev workflow) cmds: + - docker run --rm -v "$(pwd):/repo" -w /repo thumbrise/commitlint-scope:latest-alpine run --from main --to HEAD - golangci-lint run --fix test: diff --git a/cmd/commands/init.go b/cmd/commands/init.go index 7530401..4a76015 100644 --- a/cmd/commands/init.go +++ b/cmd/commands/init.go @@ -40,7 +40,7 @@ patterns: ` const ( - InitConfigFileName = validator.ConfigName + ".yml" + InitConfigFileName = validator.ConfigName + ".yaml" InitConfigFileMode = 0o600 ) diff --git a/go.mod b/go.mod index 18e1f32..92599e6 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,9 @@ require ( github.com/fatih/color v1.19.0 github.com/go-viper/mapstructure/v2 v2.5.0 github.com/gobwas/glob v0.2.3 - github.com/spf13/viper v1.21.0 + github.com/knadh/koanf/parsers/yaml v1.1.0 + github.com/knadh/koanf/providers/file v1.2.1 + github.com/knadh/koanf/v2 v2.3.2 github.com/stretchr/testify v1.11.1 github.com/urfave/cli/v3 v3.9.0 ) @@ -21,28 +23,19 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jedib0t/go-pretty/v6 v6.7.8 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect - github.com/knadh/koanf/parsers/yaml v1.1.0 // indirect github.com/knadh/koanf/providers/env v1.1.0 // indirect - github.com/knadh/koanf/providers/file v1.2.1 // indirect github.com/knadh/koanf/providers/posflag v1.0.1 // indirect github.com/knadh/koanf/providers/structs v1.0.0 // indirect - github.com/knadh/koanf/v2 v2.3.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rs/zerolog v1.34.0 // indirect - github.com/sagikazarmark/locafero v0.11.0 // indirect - github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect - github.com/spf13/afero v1.15.0 // indirect - github.com/spf13/cast v1.10.0 // indirect github.com/spf13/cobra v1.10.2 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.3 // indirect - github.com/subosito/gotenv v1.6.0 // indirect github.com/vektra/mockery/v3 v3.7.0 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect diff --git a/go.sum b/go.sum index 5d4f527..f83af5a 100644 --- a/go.sum +++ b/go.sum @@ -11,8 +11,6 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= @@ -59,8 +57,6 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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= @@ -71,29 +67,17 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= -github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= -github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= -github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= -github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= -github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= -github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= -github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/urfave/cli/v3 v3.9.0 h1:AV9lIiPv3ukYnxunaCUsHnEozptYmDN2F0+yWqLMn/c= github.com/urfave/cli/v3 v3.9.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/vektra/mockery/v3 v3.7.0 h1:Dd0EeaOcRJBVP9n3oYOVPV7KdPaaE3EcwTppaZIsFSM= diff --git a/pkg/validator/config.go b/pkg/validator/config.go index a7cce16..8cca906 100644 --- a/pkg/validator/config.go +++ b/pkg/validator/config.go @@ -3,61 +3,70 @@ package validator import ( "errors" "fmt" + "os" "reflect" "regexp" "github.com/go-viper/mapstructure/v2" - "github.com/spf13/viper" + "github.com/knadh/koanf/parsers/yaml" + "github.com/knadh/koanf/providers/file" + "github.com/knadh/koanf/v2" ) -const ConfigName = ".commitlint-scope" +const ConfigName = ".commitlint-scope.yaml" type PatternItem struct { - Scopes []string `mapstructure:"scopes"` - Files []string `mapstructure:"files"` + Scopes []string `koanf:"scopes"` + Files []string `koanf:"files"` } type Config struct { - ScopeRegex *regexp.Regexp `mapstructure:"scopeRegex"` - Patterns []PatternItem `mapstructure:"patterns"` + ScopeRegex *regexp.Regexp `koanf:"scopeRegex"` + Patterns []PatternItem `koanf:"patterns"` } -var ErrConfigRead = errors.New("error reading config") +var ( + ErrConfigRead = errors.New("error reading config") + ErrRegexDecode = errors.New("expected string for regexp decode") +) func LoadConfig() (Config, error) { - v := viper.New() - v.SetConfigName(ConfigName) - v.AddConfigPath(".") - v.SetDefault("scopeRegex", regexp.MustCompile(`^[a-z]+(?:\((?P[^)]+)\))?!?:\s`)) + k := koanf.New(".") - var cfg Config + defaultRegex := `^[a-z]+(?:\((?P[^)]+)\))?!?:\s` + _ = k.Set("scopeRegex", defaultRegex) - if err := v.ReadInConfig(); err != nil { - if _, ok := errors.AsType[viper.ConfigFileNotFoundError](err); !ok { + if err := k.Load(file.Provider(ConfigName), yaml.Parser()); err != nil { + if !os.IsNotExist(err) { return Config{}, fmt.Errorf("%w: %w", ErrConfigRead, err) } } - if err := v.Unmarshal(&cfg, regexDecode); err != nil { + var cfg Config + + unmarshalConf := koanf.UnmarshalConf{ + DecoderConfig: &mapstructure.DecoderConfig{ + Result: &cfg, + DecodeHook: regexDecodeHook, + }, + } + + if err := k.UnmarshalWithConf("", &cfg, unmarshalConf); err != nil { return Config{}, fmt.Errorf("%w: %w", ErrConfigRead, err) } return cfg, nil } -var ErrRegexDecode = errors.New("expected string for regexp decode") - -func regexDecode(cfg *mapstructure.DecoderConfig) { - cfg.DecodeHook = func(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) { - if from.Kind() == reflect.String && to == reflect.TypeOf(®exp.Regexp{}) { - val, ok := data.(string) - if !ok { - return nil, fmt.Errorf("%w got %T", ErrRegexDecode, data) - } - - return regexp.Compile(val) +func regexDecodeHook(from reflect.Type, to reflect.Type, data interface{}) (interface{}, error) { + if from.Kind() == reflect.String && to == reflect.TypeOf(®exp.Regexp{}) { + val, ok := data.(string) + if !ok { + return nil, fmt.Errorf("%w got %T", ErrRegexDecode, data) } - return data, nil + return regexp.Compile(val) } + + return data, nil } diff --git a/pkg/validator/config_test.go b/pkg/validator/config_test.go index 36ba475..c18087c 100644 --- a/pkg/validator/config_test.go +++ b/pkg/validator/config_test.go @@ -88,7 +88,7 @@ patterns: require.NoError(t, os.Chdir(dir)) if tt.yaml != "" { - err = os.WriteFile(filepath.Join(dir, ".commitlint-scope.yaml"), []byte(tt.yaml), 0o644) + err = os.WriteFile(filepath.Join(dir, validator.ConfigName), []byte(tt.yaml), 0o644) require.NoError(t, err) }