Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for configurable conventional commit prefixes #130

Merged
merged 1 commit into from
Jun 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/nsv.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ jobs:
with:
token: ${{ secrets.GH_NSV }}
env:
# skipcq: SCT-A000
GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}"
GPG_PASSPHRASE: "${{ secrets.GPG_PASSPHRASE }}"
GPG_TRUST_LEVEL: "${{ secrets.GPG_TRUST_LEVEL }}"
35 changes: 25 additions & 10 deletions cmd/next.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,22 @@ var nextLongDesc = `Generate the next semantic version based on the conventional

Environment Variables:

| Name | Description |
|------------|----------------------------------------------------------------|
| LOG_LEVEL | the level of logging when printing to stderr (default: info) |
| NO_COLOR | switch to using an ASCII color profile within the terminal |
| NO_LOG | disable all log output |
| NSV_FORMAT | provide a go template for changing the default version format |
| NSV_PRETTY | pretty-print the output of the next semantic version in a |
| | given format. The format can be one of either full or compact. |
| | Must be used in conjunction with NSV_SHOW (default: full) |
| NSV_SHOW | show how the next semantic version was generated |`
| Name | Description |
|--------------------|----------------------------------------------------------------|
| LOG_LEVEL | the level of logging when printing to stderr (default: info) |
| NO_COLOR | switch to using an ASCII color profile within the terminal |
| NO_LOG | disable all log output |
| NSV_FORMAT | provide a go template for changing the default version format |
| NSV_MAJOR_PREFIXES | a comma separated list of conventional commit prefixes for |
| | triggering a major semantic version increment |
| NSV_MINOR_PREFIXES | a comma separated list of conventional commit prefixes for |
| | triggering a minor semantic version increment |
| NSV_PATCH_PREFIXES | a comma separated list of conventional commit prefixes for |
| | triggering a patch semantic version increment |
| NSV_PRETTY | pretty-print the output of the next semantic version in a |
| | given format. The format can be one of either full or compact. |
| | Must be used in conjunction with NSV_SHOW (default: full) |
| NSV_SHOW | show how the next semantic version was generated |`

func nextCmd(opts *Options) *cobra.Command {
cmd := &cobra.Command{
Expand Down Expand Up @@ -78,6 +84,12 @@ func nextCmd(opts *Options) *cobra.Command {

flags := cmd.Flags()
flags.StringVarP(&opts.VersionFormat, "format", "f", "", "provide a go template for changing the default version format")
flags.StringSliceVar(&opts.MajorPrefixes, "major-prefixes", []string{}, "a comma separated list of conventional commit prefixes for "+
"triggering a major semantic version increment")
flags.StringSliceVar(&opts.MinorPrefixes, "minor-prefixes", []string{}, "a comma separated list of conventional commit prefixes for "+
"triggering a minor semantic version increment")
flags.StringSliceVar(&opts.PatchPrefixes, "patch-prefixes", []string{}, "a comma separated list of conventional commit prefixes for "+
"triggering a patch semantic version increment")
flags.StringVarP(&opts.Pretty, "pretty", "p", string(tui.Full), "pretty-print the output of the next semantic version in a given format. "+
"The format can be one of either full or compact. Must be used in conjunction with --show")
flags.BoolVarP(&opts.Show, "show", "s", false, "show how the next semantic version was generated")
Expand Down Expand Up @@ -126,7 +138,10 @@ func nextVersions(gitc *git.Client, opts *Options) ([]*nsv.Next, error) {
var vers []*nsv.Next
for _, path := range opts.Paths {
next, err := nsv.NextVersion(gitc, nsv.Options{
MajorPrefixes: opts.MajorPrefixes,
MinorPrefixes: opts.MinorPrefixes,
Logger: opts.Logger,
PatchPrefixes: opts.PatchPrefixes,
Path: path,
VersionFormat: opts.VersionFormat,
})
Expand Down
2 changes: 1 addition & 1 deletion cmd/playground.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cmd

import (
"github.com/caarlos0/env/v9"
"github.com/caarlos0/env/v11"
"github.com/purpleclay/nsv/internal/nsv"
"github.com/purpleclay/nsv/internal/tui"
"github.com/spf13/cobra"
Expand Down
5 changes: 4 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"os"
"runtime"

"github.com/caarlos0/env/v9"
"github.com/caarlos0/env/v11"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/log"
"github.com/muesli/termenv"
Expand All @@ -21,9 +21,12 @@ type Options struct {
Err io.Writer `env:"-"`
Logger *log.Logger `env:"_"`
LogLevel string `env:"LOG_LEVEL"`
MajorPrefixes []string `env:"NSV_MAJOR_PREFIXES"`
MinorPrefixes []string `env:"NSV_MINOR_PREFIXES"`
NoColor bool `env:"NO_COLOR"`
NoLog bool `env:"NO_LOG"`
Out io.Writer `env:"-"`
PatchPrefixes []string `env:"NSV_PATCH_PREFIXES"`
Paths []string `env:"-"`
Pretty string `env:"NSV_PRETTY"`
Show bool `env:"NSV_SHOW"`
Expand Down
36 changes: 24 additions & 12 deletions cmd/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,24 @@ your repository.

Environment Variables:

| Name | Description |
|-----------------|----------------------------------------------------------------|
| LOG_LEVEL | the level of logging when printing to stderr (default: info) |
| NO_COLOR | switch to using an ASCII color profile within the terminal |
| NO_LOG | disable all log output |
| NSV_FORMAT | provide a go template for changing the default version format |
| NSV_PRETTY | pretty-print the output of the next semantic version in a |
| | given format. The format can be one of either full or compact. |
| | Must be used in conjunction with NSV_SHOW (default: full) |
| NSV_SHOW | show how the next semantic version was generated |
| NSV_TAG_MESSAGE | a custom message for the tag, supports go text templates. The |
| | default is: "chore: tagged release {{.Tag}}" |`
| Name | Description |
|--------------------|----------------------------------------------------------------|
| LOG_LEVEL | the level of logging when printing to stderr (default: info) |
| NO_COLOR | switch to using an ASCII color profile within the terminal |
| NO_LOG | disable all log output |
| NSV_FORMAT | provide a go template for changing the default version format |
| NSV_MAJOR_PREFIXES | a comma separated list of conventional commit prefixes for |
| | triggering a major semantic version increment |
| NSV_MINOR_PREFIXES | a comma separated list of conventional commit prefixes for |
| | triggering a minor semantic version increment |
| NSV_PATCH_PREFIXES | a comma separated list of conventional commit prefixes for |
| | triggering a patch semantic version increment |
| NSV_PRETTY | pretty-print the output of the next semantic version in a |
| | given format. The format can be one of either full or compact. |
| | Must be used in conjunction with NSV_SHOW (default: full) |
| NSV_SHOW | show how the next semantic version was generated |
| NSV_TAG_MESSAGE | a custom message for the tag, supports go text templates. The |
| | default is: "chore: tagged release {{.Tag}}" |`

func tagCmd(opts *Options) *cobra.Command {
cmd := &cobra.Command{
Expand Down Expand Up @@ -102,6 +108,12 @@ func tagCmd(opts *Options) *cobra.Command {
flags := cmd.Flags()
flags.StringVarP(&opts.VersionFormat, "format", "f", "", "provide a go template for changing the default version format")
flags.StringVarP(&opts.TagMessage, "message", "m", "chore: tagged release {{.Tag}}", "a custom message for the tag, supports go text templates")
flags.StringSliceVar(&opts.MajorPrefixes, "major-prefixes", []string{}, "a comma separated list of conventional commit prefixes for "+
"triggering a major semantic version increment")
flags.StringSliceVar(&opts.MinorPrefixes, "minor-prefixes", []string{}, "a comma separated list of conventional commit prefixes for "+
"triggering a minor semantic version increment")
flags.StringSliceVar(&opts.PatchPrefixes, "patch-prefixes", []string{}, "a comma separated list of conventional commit prefixes for "+
"triggering a patch semantic version increment")
flags.StringVarP(&opts.Pretty, "pretty", "p", string(tui.Full), "pretty-print the output of the next semantic version in a given format. "+
"The format can be one of either full or compact. Must be used in conjunction with --show")
flags.BoolVarP(&opts.Show, "show", "s", false, "show how the next semantic version was generated")
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.22.2

require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/caarlos0/env/v9 v9.0.0
github.com/caarlos0/env/v11 v11.0.1
github.com/charmbracelet/lipgloss v0.11.0
github.com/charmbracelet/log v0.4.0
github.com/muesli/mango-cobra v1.2.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
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/caarlos0/env/v9 v9.0.0 h1:SI6JNsOA+y5gj9njpgybykATIylrRMklbs5ch6wO6pc=
github.com/caarlos0/env/v9 v9.0.0/go.mod h1:ye5mlCVMYh6tZ+vCgrs/B95sj88cg5Tlnc0XIzgZ020=
github.com/caarlos0/env/v11 v11.0.1 h1:A8dDt9Ub9ybqRSUF3fQc/TA/gTam2bKT4Pit+cwrsPs=
github.com/caarlos0/env/v11 v11.0.1/go.mod h1:2RC3HQu8BQqtEK3V4iHPxj0jOdWdbPpWJ6pOueeU1xM=
github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g=
github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
Expand Down
2 changes: 1 addition & 1 deletion internal/nsv/command.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package nsv

Check failure on line 1 in internal/nsv/command.go

View workflow job for this annotation

GitHub Actions / security-checks / govulncheck

package requires newer Go version go1.22

import (
"strings"
Expand Down Expand Up @@ -28,7 +28,7 @@

func DetectCommand(log []git.LogEntry) (Command, Match) {
command := Command{}
match := Match{Index: noMatch}
match := NoMatch

for i, entry := range log {
msg := strings.TrimSpace(entry.Message)
Expand Down
90 changes: 70 additions & 20 deletions internal/nsv/conventional.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,61 @@ import (

const (
colonSpace = ": "
featUpper = "FEAT"
fixUpper = "FIX"
breaking = "BREAKING CHANGE: "
breakingHyphen = "BREAKING-CHANGE: "
breakingBang = '!'
noMatch = -1
noMatchIdx = -1
)

func DetectIncrement(log []git.LogEntry) (Increment, Match) {
var (
minorDefault = []string{"FEAT"}
patchDefault = []string{"FIX"}
NoMatch = Match{Index: noMatchIdx}
)

type ConventionalStrategy struct {
MajorPrefixes []string
MinorPrefixes []string
PatchPrefixes []string
}

func Angular() ConventionalStrategy {
return ConventionalStrategy{
MajorPrefixes: []string{},
MinorPrefixes: minorDefault,
PatchPrefixes: patchDefault,
}
}

func AngularMerge(major, minor, patch []string) ConventionalStrategy {
minorPrefs := minorDefault
if len(minor) > 0 {
minorPrefs = toUpper(minor)
}
patchPrefs := patchDefault
if len(patch) > 0 {
patchPrefs = toUpper(patch)
}

return ConventionalStrategy{
MajorPrefixes: toUpper(major),
MinorPrefixes: minorPrefs,
PatchPrefixes: patchPrefs,
}
}

func toUpper(s []string) []string {
upper := make([]string, 0, len(s))
for _, se := range s {
upper = append(upper, strings.ToUpper(se))
}

return upper
}

func (s ConventionalStrategy) DetectIncrement(log []git.LogEntry) (Increment, Match) {
mode := NoIncrement
match := Match{Index: noMatch}
match := NoMatch

for i, entry := range log {
// Check for the existence of a conventional commit type
Expand All @@ -28,6 +72,7 @@ func DetectIncrement(log []git.LogEntry) (Increment, Match) {
}

leadingType := strings.ToUpper(entry.Message[:idx])

if leadingType[idx-1] == breakingBang {
return MajorIncrement, Match{Index: i, End: idx}
}
Expand All @@ -36,19 +81,18 @@ func DetectIncrement(log []git.LogEntry) (Increment, Match) {
return MajorIncrement, Match{Index: i, Start: start, End: end}
}

// Only feat and fix types now make a difference. Both have the same first letter
if leadingType[0] != featUpper[0] {
continue
if contains(s.MajorPrefixes, leadingType) {
return MajorIncrement, Match{Index: i, End: idx}
}

if mode == MinorIncrement && match.Index > noMatch {
if mode == MinorIncrement && match.Index > noMatchIdx {
continue
}

if contains(leadingType, featUpper) {
if contains(s.MinorPrefixes, leadingType) {
mode = MinorIncrement
match = Match{Index: i, End: idx}
} else if contains(leadingType, fixUpper) {
} else if contains(s.PatchPrefixes, leadingType) {
mode = PatchIncrement
match = Match{Index: i, End: idx}
}
Expand All @@ -57,16 +101,22 @@ func DetectIncrement(log []git.LogEntry) (Increment, Match) {
return mode, match
}

func contains(str, prefix string) bool {
if str == prefix {
return true
func contains(prefixes []string, str string) bool {
if len(prefixes) == 0 {
return false
}

if strings.HasPrefix(str, prefix) {
if len(str) > len(prefix) &&
(str[len(prefix)] == '(' && str[len(str)-1] == ')') {
for _, prefix := range prefixes {
if str == prefix {
return true
}

if strings.HasPrefix(str, prefix) {
if len(str) > len(prefix) &&
(str[len(prefix)] == '(' && str[len(str)-1] == ')') {
return true
}
}
}

return false
Expand All @@ -75,14 +125,14 @@ func contains(str, prefix string) bool {
func multilineBreaking(msg string) (bool, int, int) {
n := strings.Count(msg, "\n")
if n == 0 {
return false, noMatch, noMatch
return false, noMatchIdx, noMatchIdx
}

idx := strings.LastIndex(msg, "\n")
if idx == len(msg) {
// There is a newline at the end of the string, so jump back one
if idx = strings.LastIndex(msg[:len(msg)-1], "\n"); idx == -1 {
return false, noMatch, noMatch
return false, noMatchIdx, noMatchIdx
}
}

Expand All @@ -92,5 +142,5 @@ func multilineBreaking(msg string) (bool, int, int) {
return true, idx + 1, (idx + len(breaking)) - 1
}

return false, noMatch, noMatch
return false, noMatchIdx, noMatchIdx
}
Loading
Loading