From da0da5fc691a2b69de4ab1cb0be18cf65e09a01c Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Wed, 23 Apr 2025 10:29:51 +1000 Subject: [PATCH] Add `wails3 tool version` command --- docs/src/content/docs/changelog.mdx | 2 +- docs/src/content/docs/guides/cli.mdx | 26 +++ v3/cmd/wails3/main.go | 1 + v3/internal/commands/tool_version.go | 123 ++++++++++++ v3/internal/commands/tool_version_test.go | 231 ++++++++++++++++++++++ 5 files changed, 382 insertions(+), 1 deletion(-) create mode 100644 v3/internal/commands/tool_version.go create mode 100644 v3/internal/commands/tool_version_test.go diff --git a/docs/src/content/docs/changelog.mdx b/docs/src/content/docs/changelog.mdx index 3670469f9..145942306 100644 --- a/docs/src/content/docs/changelog.mdx +++ b/docs/src/content/docs/changelog.mdx @@ -80,7 +80,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `SetMenu()` on window to allow for setting a menu on a window by [@leaanthony](https://github.com/leaanthony) - Add Notification support by [@popaprozac] in [#4098](https://github.com/wailsapp/wails/pull/4098) -  Add File Association support for mac by [@wimaha](https://github.com/wimaha) in [#4177](https://github.com/wailsapp/wails/pull/4177) - +- Add `wails3 tool version` for semantic version bumping by [@leaanthony](https://github.com/leaanthony) ### Fixed - Fixed Windows+Linux Edit Menu issues by [@leaanthony](https://github.com/leaanthony) in [#3f78a3a](https://github.com/wailsapp/wails/commit/3f78a3a8ce7837e8b32242c8edbbed431c68c062) diff --git a/docs/src/content/docs/guides/cli.mdx b/docs/src/content/docs/guides/cli.mdx index 89035e7e5..d10766503 100644 --- a/docs/src/content/docs/guides/cli.mdx +++ b/docs/src/content/docs/guides/cli.mdx @@ -325,6 +325,32 @@ Shows build information about the application. wails3 tool buildinfo ``` +### `tool version` +Bumps a semantic version based on the provided flags. + +```bash +wails3 tool version [flags] +``` + +#### Flags +| Flag | Description | Default | +|---------------|--------------------------------------------------|---------| +| `-v` | Current version to bump | | +| `-major` | Bump major version | `false` | +| `-minor` | Bump minor version | `false` | +| `-patch` | Bump patch version | `false` | +| `-prerelease` | Bump prerelease version (e.g., alpha.5 to alpha.6) | `false` | + +The command follows the precedence order: major > minor > patch > prerelease. It preserves the "v" prefix if present in the input version, as well as any prerelease and metadata components. + +Example usage: +```bash +wails3 tool version -v 1.2.3 -major # Output: 2.0.0 +wails3 tool version -v v1.2.3 -minor # Output: v1.3.0 +wails3 tool version -v 1.2.3-alpha -patch # Output: 1.2.4-alpha +wails3 tool version -v v3.0.0-alpha.5 -prerelease # Output: v3.0.0-alpha.6 +``` + ### `tool package` Generates Linux packages (deb, rpm, archlinux). diff --git a/v3/cmd/wails3/main.go b/v3/cmd/wails3/main.go index 967cc946f..945a865df 100644 --- a/v3/cmd/wails3/main.go +++ b/v3/cmd/wails3/main.go @@ -79,6 +79,7 @@ func main() { tool.NewSubCommandFunction("cp", "Copy files", commands.Cp) tool.NewSubCommandFunction("buildinfo", "Show Build Info", commands.BuildInfo) tool.NewSubCommandFunction("package", "Generate Linux packages (deb, rpm, archlinux)", commands.ToolPackage) + tool.NewSubCommandFunction("version", "Bump semantic version", commands.ToolVersion) app.NewSubCommandFunction("version", "Print the version", commands.Version) app.NewSubCommand("sponsor", "Sponsor the project").Action(openSponsor) diff --git a/v3/internal/commands/tool_version.go b/v3/internal/commands/tool_version.go new file mode 100644 index 000000000..fe0deb5ec --- /dev/null +++ b/v3/internal/commands/tool_version.go @@ -0,0 +1,123 @@ +package commands + +import ( + "fmt" + "strconv" + "strings" + + "github.com/wailsapp/wails/v3/internal/github" +) + +type ToolVersionOptions struct { + Version string `name:"v" description:"Current version to bump"` + Major bool `name:"major" description:"Bump major version"` + Minor bool `name:"minor" description:"Bump minor version"` + Patch bool `name:"patch" description:"Bump patch version"` + Prerelease bool `name:"prerelease" description:"Bump prerelease version (e.g., alpha.5 to alpha.6)"` +} + +// bumpPrerelease increments the numeric part of a prerelease string +// For example, "alpha.5" becomes "alpha.6" +func bumpPrerelease(prerelease string) string { + // If prerelease is empty, return it as is + if prerelease == "" { + return prerelease + } + + // Split the prerelease string by dots + parts := strings.Split(prerelease, ".") + + // If there's only one part (e.g., "alpha"), return it as is + if len(parts) == 1 { + return prerelease + } + + // Try to parse the last part as a number + lastPart := parts[len(parts)-1] + num, err := strconv.Atoi(lastPart) + if err != nil { + // If the last part is not a number, return the prerelease as is + return prerelease + } + + // Increment the number + num++ + + // Replace the last part with the incremented number + parts[len(parts)-1] = strconv.Itoa(num) + + // Join the parts back together + return strings.Join(parts, ".") +} + +// ToolVersion bumps a semantic version based on the provided flags +func ToolVersion(options *ToolVersionOptions) error { + DisableFooter = true + + if options.Version == "" { + return fmt.Errorf("please provide a version using the -v flag") + } + + // Check if the version has a "v" prefix + hasVPrefix := false + versionStr := options.Version + if len(versionStr) > 0 && versionStr[0] == 'v' { + hasVPrefix = true + versionStr = versionStr[1:] + } + + // Parse the current version + semver, err := github.NewSemanticVersion(versionStr) + if err != nil { + return fmt.Errorf("invalid version format: %v", err) + } + + // Get the current version components + major := semver.Version.Major() + minor := semver.Version.Minor() + patch := semver.Version.Patch() + prerelease := semver.Version.Prerelease() + metadata := semver.Version.Metadata() + + // Check if at least one flag is specified + if !options.Major && !options.Minor && !options.Patch && !options.Prerelease { + return fmt.Errorf("please specify one of -major, -minor, -patch, or -prerelease") + } + + // Bump the version based on the flags (major takes precedence over minor, which takes precedence over patch) + if options.Major { + major++ + minor = 0 + patch = 0 + } else if options.Minor { + minor++ + patch = 0 + } else if options.Patch { + patch++ + } else if options.Prerelease { + // If only prerelease flag is specified, bump the prerelease version + if prerelease == "" { + return fmt.Errorf("cannot bump prerelease version: no prerelease part in the version") + } + prerelease = bumpPrerelease(prerelease) + } + + // Format the new version + newVersion := fmt.Sprintf("%d.%d.%d", major, minor, patch) + if prerelease != "" { + newVersion += "-" + prerelease + } + if metadata != "" { + newVersion += "+" + metadata + } + + // Add the "v" prefix back if it was present in the input + if hasVPrefix { + newVersion = "v" + newVersion + } + + // Print the new version + fmt.Println(newVersion) + + return nil +} diff --git a/v3/internal/commands/tool_version_test.go b/v3/internal/commands/tool_version_test.go new file mode 100644 index 000000000..1da58585f --- /dev/null +++ b/v3/internal/commands/tool_version_test.go @@ -0,0 +1,231 @@ +package commands + +import ( + "bytes" + "io" + "os" + "strings" + "testing" +) + +func TestToolVersion(t *testing.T) { + // Create a table of test cases + testCases := []struct { + name string + options ToolVersionOptions + expectedOutput string + expectedError bool + }{ + { + name: "Bump major version", + options: ToolVersionOptions{ + Version: "1.2.3", + Major: true, + }, + expectedOutput: "2.0.0", + expectedError: false, + }, + { + name: "Bump minor version", + options: ToolVersionOptions{ + Version: "1.2.3", + Minor: true, + }, + expectedOutput: "1.3.0", + expectedError: false, + }, + { + name: "Bump patch version", + options: ToolVersionOptions{ + Version: "1.2.3", + Patch: true, + }, + expectedOutput: "1.2.4", + expectedError: false, + }, + { + name: "Bump major version with v prefix", + options: ToolVersionOptions{ + Version: "v1.2.3", + Major: true, + }, + expectedOutput: "v2.0.0", + expectedError: false, + }, + { + name: "Bump minor version with v prefix", + options: ToolVersionOptions{ + Version: "v1.2.3", + Minor: true, + }, + expectedOutput: "v1.3.0", + expectedError: false, + }, + { + name: "Bump patch version with v prefix", + options: ToolVersionOptions{ + Version: "v1.2.3", + Patch: true, + }, + expectedOutput: "v1.2.4", + expectedError: false, + }, + { + name: "Bump version with prerelease", + options: ToolVersionOptions{ + Version: "1.2.3-alpha", + Patch: true, + }, + expectedOutput: "1.2.4-alpha", + expectedError: false, + }, + { + name: "Bump version with metadata", + options: ToolVersionOptions{ + Version: "1.2.3+build123", + Patch: true, + }, + expectedOutput: "1.2.4+build123", + expectedError: false, + }, + { + name: "Bump version with prerelease and metadata", + options: ToolVersionOptions{ + Version: "1.2.3-alpha+build123", + Patch: true, + }, + expectedOutput: "1.2.4-alpha+build123", + expectedError: false, + }, + { + name: "Bump version with v prefix, prerelease and metadata", + options: ToolVersionOptions{ + Version: "v1.2.3-alpha+build123", + Patch: true, + }, + expectedOutput: "v1.2.4-alpha+build123", + expectedError: false, + }, + { + name: "No version provided", + options: ToolVersionOptions{ + Major: true, + }, + expectedOutput: "", + expectedError: true, + }, + { + name: "No bump flag provided", + options: ToolVersionOptions{ + Version: "1.2.3", + }, + expectedOutput: "", + expectedError: true, + }, + { + name: "Invalid version format", + options: ToolVersionOptions{ + Version: "invalid", + Major: true, + }, + expectedOutput: "", + expectedError: true, + }, + { + name: "Bump prerelease version with numeric component", + options: ToolVersionOptions{ + Version: "1.2.3-alpha.5", + Prerelease: true, + }, + expectedOutput: "1.2.3-alpha.6", + expectedError: false, + }, + { + name: "Bump prerelease version with v prefix", + options: ToolVersionOptions{ + Version: "v1.2.3-alpha.5", + Prerelease: true, + }, + expectedOutput: "v1.2.3-alpha.6", + expectedError: false, + }, + { + name: "Bump prerelease version with metadata", + options: ToolVersionOptions{ + Version: "1.2.3-alpha.5+build123", + Prerelease: true, + }, + expectedOutput: "1.2.3-alpha.6+build123", + expectedError: false, + }, + { + name: "Bump prerelease version without numeric component", + options: ToolVersionOptions{ + Version: "1.2.3-alpha", + Prerelease: true, + }, + expectedOutput: "1.2.3-alpha", + expectedError: false, + }, + { + name: "Bump prerelease version when no prerelease part exists", + options: ToolVersionOptions{ + Version: "1.2.3", + Prerelease: true, + }, + expectedOutput: "", + expectedError: true, + }, + { + name: "Bump prerelease version for issue example v3.0.0-alpha.5", + options: ToolVersionOptions{ + Version: "v3.0.0-alpha.5", + Prerelease: true, + }, + expectedOutput: "v3.0.0-alpha.6", + expectedError: false, + }, + } + + // Run each test case + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Capture stdout + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + // Call the function + err := ToolVersion(&tc.options) + + // Restore stdout + err2 := w.Close() + if err2 != nil { + t.Fail() + } + os.Stdout = oldStdout + + // Read captured output + var buf bytes.Buffer + _, err2 = io.Copy(&buf, r) + if err2 != nil { + t.Fail() + } + + output := strings.TrimSpace(buf.String()) + + // Check error + if tc.expectedError && err == nil { + t.Errorf("Expected error but got none") + } + if !tc.expectedError && err != nil { + t.Errorf("Expected no error but got: %v", err) + } + + // Check output + if !tc.expectedError && output != tc.expectedOutput { + t.Errorf("Expected output '%s' but got '%s'", tc.expectedOutput, output) + } + }) + } +}