Skip to content

Commit

Permalink
Merge pull request #218 from projectdiscovery/add_versionbump_script
Browse files Browse the repository at this point in the history
add version bump script
  • Loading branch information
Mzack9999 committed Jul 28, 2023
2 parents 7dd6026 + 9e7f22d commit 322768d
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 0 deletions.
16 changes: 16 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# scripts
The package contains various scripts


## versionbump
This Go script can automatically bump the semantic version number defined in a Go source file. It parses the specified Go source file with `go/ast`, finds the given variable (which is assumed to contain a semantic version string), increments the specified part of the version number (major, minor, or patch) with `github.com/Masterminds/semver/v3`, and rewrites the file with the updated version.

```
go run versionbump.go -file /path/to/your/file.go -var YourVersionVariable
```

By default, the patch version is incremented. To increment the major or minor versions instead, specify -part major or -part minor respectively:

```
go run versionbump.go -file /path/to/your/file.go -var YourVersionVariable -part minor
```
104 changes: 104 additions & 0 deletions scripts/versionbump/versionbump.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package main

import (
"flag"
"fmt"
"go/ast"
"go/parser"
"go/printer"
"go/token"
"os"
"path/filepath"
"strconv"

semver "github.com/Masterminds/semver/v3"
)

func bumpVersion(fileName, varName, part string) (string, string, error) {
absPath, err := filepath.Abs(fileName)
if err != nil {
return "", "", fmt.Errorf("unable to get absolute path: %v", err)
}

fset := token.NewFileSet()
node, err := parser.ParseFile(fset, absPath, nil, parser.ParseComments)
if err != nil {
return "", "", fmt.Errorf("could not parse file: %w", err)
}

var oldVersion, newVersion string

ast.Inspect(node, func(n ast.Node) bool {
if v, ok := n.(*ast.GenDecl); ok {
for _, spec := range v.Specs {
if s, ok := spec.(*ast.ValueSpec); ok {
for idx, id := range s.Names {
if id.Name == varName {
oldVersion, _ = strconv.Unquote(s.Values[idx].(*ast.BasicLit).Value)
v, err := semver.NewVersion(oldVersion)
if err != nil || v.String() == "" {
return false
}
var vInc func() semver.Version
switch part {
case "major":
vInc = v.IncMajor
case "minor":
vInc = v.IncMinor
case "", "patch":
vInc = v.IncPatch
default:
return false
}
newVersion = "v" + vInc().String()
s.Values[idx].(*ast.BasicLit).Value = fmt.Sprintf("`%s`", newVersion)
return false
}
}
}
}
}
return true
})

if newVersion == "" {
return oldVersion, newVersion, fmt.Errorf("failed to update the version")
}

f, err := os.OpenFile(fileName, os.O_RDWR, 0666)
if err != nil {
return oldVersion, newVersion, fmt.Errorf("could not open file: %w", err)
}
defer f.Close()

if err := printer.Fprint(f, fset, node); err != nil {
return oldVersion, newVersion, fmt.Errorf("could not write to file: %w", err)
}

return oldVersion, newVersion, nil
}

func main() {
var (
fileName string
varName string
part string
)

flag.StringVar(&fileName, "file", "", "Go source file to parse")
flag.StringVar(&varName, "var", "", "Variable to update")
flag.StringVar(&part, "part", "patch", "Version part to increment (major, minor, patch)")

flag.Parse()

if fileName == "" || varName == "" {
fmt.Println("Error: Both -file and -var are required")
os.Exit(1)
}
oldVersion, newVersion, err := bumpVersion(fileName, varName, part)
if err != nil {
fmt.Printf("Error bumping version: %v\n", err)
os.Exit(1)
}
fmt.Printf("Bump from %s to %s\n", oldVersion, newVersion)
}
135 changes: 135 additions & 0 deletions scripts/versionbump/versionbump_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package main

import (
"os"
"os/exec"
"testing"

"github.com/stretchr/testify/assert"
)

func TestBumpVersionCLI(t *testing.T) {
testCases := []struct {
name string
fileContent string
varName string
part string
expectedErr bool
expectedOutput string
}{
{
name: "Test v1.0.0",
fileContent: "package main\n\nvar version = \"v1.0.0\"",
varName: "version",
part: "patch",
expectedErr: false,
expectedOutput: "Bump from v1.0.0 to v1.0.1\n",
},
{
name: "Test 1.0.8",
fileContent: "package main\n\nvar version = \"1.0.8\"",
varName: "version",
part: "patch",
expectedErr: false,
expectedOutput: "Bump from 1.0.8 to v1.0.9\n",
},
{
name: "Test v1.0.9-dev",
fileContent: "package main\n\nvar version = \"v1.0.9-dev\"",
varName: "version",
part: "patch",
expectedErr: false,
expectedOutput: "Bump from v1.0.9-dev to v1.0.9\n",
},
{
name: "Test 1.0.85.1",
fileContent: "package main\n\nvar version = \"1.0.85.1\"",
varName: "version",
part: "patch",
expectedErr: true,
},
{
name: "Test with unwanted prefixes/suffixes: with x prefix",
fileContent: "package main\n\nvar version = \"x1.0.0\"",
varName: "version",
part: "patch",
expectedErr: true,
},
{
name: "Test with unwanted prefixes/suffixes: with x suffix",
fileContent: "package main\n\nvar version = \"1.0.0x\"",
varName: "version",
part: "patch",
expectedErr: true,
},
{
name: "Test with unwanted prefixes/suffixes: with new lines",
fileContent: "package main\n\nvar version = \"\n\n1.0.0\"",
varName: "version",
part: "patch",
expectedErr: true,
},
{
name: "Test with unwanted prefixes/suffixes: with space",
fileContent: "package main\n\nvar version = \" 1.0.0\"",
varName: "version",
part: "patch",
expectedErr: true,
},
{
name: "Test with unwanted prefixes/suffixes: with multiple dot",
fileContent: "package main\n\nvar version = \"1.0..0\"",
varName: "version",
part: "patch",
expectedErr: true,
},
{
name: "Test with negative numbers",
fileContent: "package main\n\nvar version = \"-1.0.0\"",
varName: "version",
part: "patch",
expectedErr: true,
},
{
name: "Test with unparseable numbers",
fileContent: "package main\n\nvar version = \"1.0.X\"",
varName: "version",
part: "patch",
expectedErr: true,
},
{
name: "Test with big numbers",
fileContent: "package main\n\nvar version = \"1.0.111111111111111111111111111111111111111111111111111111111111111111111111\"",
varName: "minor",
part: "patch",
expectedErr: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Create a temporary file and write the content to it
tempFile, err := os.CreateTemp(os.TempDir(), "prefix-")
if err != nil {
t.Fatalf("Cannot create temporary file: %s", err)
}

defer os.Remove(tempFile.Name())

_, err = tempFile.Write([]byte(tc.fileContent))
if err != nil {
t.Fatalf("Failed to write to temporary file: %s", err)
}

cmd := exec.Command("go", "run", "versionbump.go", "-file", tempFile.Name(), "-var", tc.varName, "-part", tc.part)

output, err := cmd.CombinedOutput()
if tc.expectedErr {
assert.NotNil(t, err)
} else {
assert.Nil(t, err)
assert.Contains(t, string(output), tc.expectedOutput)
}
})
}
}

0 comments on commit 322768d

Please sign in to comment.