-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #218 from projectdiscovery/add_versionbump_script
add version bump script
- Loading branch information
Showing
3 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
} |