From f92f8588dc0577d704951c7aa8a7cbc23d9c06d7 Mon Sep 17 00:00:00 2001 From: Lucas Modrich <2934597+lucasmodrich@users.noreply.github.com> Date: Sat, 21 Feb 2026 14:15:56 +1100 Subject: [PATCH] feat: embed Windows VERSIONINFO resource in gwtm.exe Add a build-ignored gen_versioninfo.go helper that writes versioninfo.json from the release version string, and wire goversioninfo into the GoReleaser pre-build hooks so resource_windows.syso is compiled and linked for Windows builds. This populates the Details tab in Windows Explorer (description, version, copyright, product name) for gwtm.exe. Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 4 + .goreleaser.yml | 5 + cmd/git-worktree-manager/gen_versioninfo.go | 119 ++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 cmd/git-worktree-manager/gen_versioninfo.go diff --git a/.gitignore b/.gitignore index 4090114..6b81963 100644 --- a/.gitignore +++ b/.gitignore @@ -22,5 +22,9 @@ node_modules/ .DS_Store Thumbs.db +# Windows build resources (generated) +cmd/git-worktree-manager/versioninfo.json +cmd/git-worktree-manager/resource_windows.syso + # Local Claude Settings (machine-specific; skills are tracked separately) .claude/settings.local.json diff --git a/.goreleaser.yml b/.goreleaser.yml index 6bdead2..f94905a 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -4,6 +4,7 @@ before: hooks: - go mod tidy - go test ./... + - go install github.com/josephspurrier/goversioninfo/cmd/goversioninfo@latest builds: - id: gwtm @@ -18,6 +19,10 @@ builds: goarch: - amd64 - arm64 + hooks: + pre: + - cmd: go run ./cmd/git-worktree-manager/gen_versioninfo.go {{ .Version }} + - cmd: goversioninfo -64 -o cmd/git-worktree-manager/resource_windows.syso cmd/git-worktree-manager/versioninfo.json ldflags: - -s -w - -X main.version={{.Version}} diff --git a/cmd/git-worktree-manager/gen_versioninfo.go b/cmd/git-worktree-manager/gen_versioninfo.go new file mode 100644 index 0000000..ce2fee4 --- /dev/null +++ b/cmd/git-worktree-manager/gen_versioninfo.go @@ -0,0 +1,119 @@ +//go:build ignore + +// gen_versioninfo.go generates versioninfo.json for goversioninfo. +// Usage: go run ./cmd/git-worktree-manager/gen_versioninfo.go +// Example: go run ./cmd/git-worktree-manager/gen_versioninfo.go 2.1.0 +package main + +import ( + "encoding/json" + "fmt" + "os" + "strconv" + "strings" +) + +type FixedFileInfo struct { + FileVersion [4]uint16 `json:"FileVersion"` + ProductVersion [4]uint16 `json:"ProductVersion"` + FileFlagsMask string `json:"FileFlagsMask"` + FileFlags string `json:"FileFlags"` + FileOS string `json:"FileOS"` + FileType string `json:"FileType"` + FileSubType string `json:"FileSubType"` +} + +type StringFileInfo struct { + Comments string `json:"Comments"` + CompanyName string `json:"CompanyName"` + FileDescription string `json:"FileDescription"` + FileVersion string `json:"FileVersion"` + InternalName string `json:"InternalName"` + LegalCopyright string `json:"LegalCopyright"` + LegalTrademarks string `json:"LegalTrademarks"` + OriginalFilename string `json:"OriginalFilename"` + PrivateBuild string `json:"PrivateBuild"` + ProductName string `json:"ProductName"` + ProductVersion string `json:"ProductVersion"` + SpecialBuild string `json:"SpecialBuild"` +} + +type Translation struct { + LangID string `json:"LangID"` + CharsetID string `json:"CharsetID"` +} + +type VarFileInfo struct { + Translation Translation `json:"Translation"` +} + +type VersionInfo struct { + FixedFileInfo FixedFileInfo `json:"FixedFileInfo"` + StringFileInfo StringFileInfo `json:"StringFileInfo"` + VarFileInfo VarFileInfo `json:"VarFileInfo"` + IconPath string `json:"IconPath"` + ManifestPath string `json:"ManifestPath"` +} + +func main() { + if len(os.Args) < 2 { + fmt.Fprintln(os.Stderr, "usage: gen_versioninfo.go ") + os.Exit(1) + } + + ver := strings.TrimPrefix(os.Args[1], "v") + parts := strings.Split(ver, ".") + if len(parts) < 3 { + fmt.Fprintf(os.Stderr, "invalid version %q: expected Major.Minor.Patch\n", ver) + os.Exit(1) + } + + major, _ := strconv.ParseUint(parts[0], 10, 16) + minor, _ := strconv.ParseUint(parts[1], 10, 16) + patch, _ := strconv.ParseUint(parts[2], 10, 16) + + fileVer := fmt.Sprintf("%d.%d.%d.0", major, minor, patch) + prodVer := fmt.Sprintf("%d.%d.%d", major, minor, patch) + + vi := VersionInfo{ + FixedFileInfo: FixedFileInfo{ + FileVersion: [4]uint16{uint16(major), uint16(minor), uint16(patch), 0}, + ProductVersion: [4]uint16{uint16(major), uint16(minor), uint16(patch), 0}, + FileFlagsMask: "3f", + FileFlags: "00", + FileOS: "040004", + FileType: "01", + FileSubType: "00", + }, + StringFileInfo: StringFileInfo{ + CompanyName: "Lucas Modrich", + FileDescription: "Git Worktree Manager", + FileVersion: fileVer, + InternalName: "gwtm", + LegalCopyright: "Copyright \u00a9 2026 Lucas Modrich", + OriginalFilename: "gwtm.exe", + ProductName: "gwtm", + ProductVersion: prodVer, + }, + VarFileInfo: VarFileInfo{ + Translation: Translation{ + LangID: "0409", + CharsetID: "04B0", + }, + }, + } + + data, err := json.MarshalIndent(vi, "", " ") + if err != nil { + fmt.Fprintln(os.Stderr, "json marshal error:", err) + os.Exit(1) + } + + outPath := "cmd/git-worktree-manager/versioninfo.json" + if err := os.WriteFile(outPath, data, 0644); err != nil { + fmt.Fprintln(os.Stderr, "write error:", err) + os.Exit(1) + } + + fmt.Println("wrote", outPath) +}