diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 4fc4b13..cd44f9e 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -25,6 +25,11 @@ builds: - arm64 goamd64: - v1 + ldflags: + - -s -w + - -X github.com/jaevans/semvertool/cmd.version={{.Version}} + - -X github.com/jaevans/semvertool/cmd.commit={{.Commit}} + - -X github.com/jaevans/semvertool/cmd.date={{.Date}} archives: - format: binary diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..1edc994 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,53 @@ +/* +Copyright © 2025 James Evans +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var ( + version = "dev" + commit = "none" + date = "unknown" +) + +// versionCmd represents the version command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Print the version information", + Long: `Print the version information for semvertool. + +If the tool was built from a tagged release, it shows the version tag. +If it was built from an untagged commit (snapshot), it includes commit information.`, + Run: runVersion, +} + +func init() { + rootCmd.AddCommand(versionCmd) +} + +func runVersion(cmd *cobra.Command, args []string) { + // If version is "dev", this is a development build (not from goreleaser) + if version == "dev" { + fmt.Printf("semvertool %s (snapshot)\n", version) + fmt.Printf(" commit: %s\n", commit) + fmt.Printf(" built: %s\n", date) + } else { + // This is a goreleaser build + // Check if it's a snapshot build (goreleaser adds commit info for non-tag builds) + // commit will be "none" or empty for tagged releases + if commit != "none" && commit != "" { + // Snapshot build - show version with commit info + fmt.Printf("semvertool %s (snapshot)\n", version) + fmt.Printf(" commit: %s\n", commit) + fmt.Printf(" built: %s\n", date) + } else { + // Tagged release - show clean version + fmt.Printf("semvertool %s\n", version) + } + } +} diff --git a/cmd/version_test.go b/cmd/version_test.go new file mode 100644 index 0000000..3b64f92 --- /dev/null +++ b/cmd/version_test.go @@ -0,0 +1,108 @@ +/* +Copyright © 2025 James Evans +*/ +package cmd + +import ( + "bytes" + "io" + "os" + "strings" + "testing" +) + +func TestVersionCommand(t *testing.T) { + tests := []struct { + name string + version string + commit string + date string + expectedOutput []string // strings that should be in output + notExpected []string // strings that should NOT be in output + }{ + { + name: "dev build", + version: "dev", + commit: "none", + date: "unknown", + expectedOutput: []string{"dev", "snapshot", "commit:", "built:"}, + notExpected: []string{}, + }, + { + name: "tagged release", + version: "v1.2.3", + commit: "", + date: "", + expectedOutput: []string{"v1.2.3"}, + notExpected: []string{"snapshot", "commit:", "built:"}, + }, + { + name: "snapshot with commit", + version: "v0.0.0-next", + commit: "abc123def", + date: "2025-01-01T10:00:00Z", + expectedOutput: []string{"v0.0.0-next", "snapshot", "commit:", "abc123def", "built:", "2025-01-01T10:00:00Z"}, + notExpected: []string{}, + }, + { + name: "tagged release with commit set to none", + version: "v2.0.0", + commit: "none", + date: "2025-01-01T10:00:00Z", + expectedOutput: []string{"v2.0.0"}, + notExpected: []string{"snapshot", "commit:", "built:"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Save original values + origVersion := version + origCommit := commit + origDate := date + + // Set test values + version = tt.version + commit = tt.commit + date = tt.date + + // Restore original values after test + defer func() { + version = origVersion + commit = origCommit + date = origDate + }() + + // Capture output + old := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + // Run the command + runVersion(versionCmd, []string{}) + + // Restore stdout + w.Close() + os.Stdout = old + + // Read captured output + var buf bytes.Buffer + _, _ = io.Copy(&buf, r) + output := buf.String() + + // Check expected strings are present + for _, expected := range tt.expectedOutput { + if !strings.Contains(output, expected) { + t.Errorf("Expected output to contain %q, got: %s", expected, output) + } + } + + // Check unexpected strings are NOT present + for _, notExpected := range tt.notExpected { + if strings.Contains(output, notExpected) { + t.Errorf("Expected output NOT to contain %q, got: %s", notExpected, output) + } + } + }) + } +}