Yeet those builds out!
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: enforce semver in package versions (#17)

* chore: disable copilot

Signed-off-by: Xe Iaso <me@xeiaso.net>

* feat(internal): additional version and package build validations

This adds additional test logic to ensure that versions passed to
package build functions are semantic versions and that the version
strings can be properly parsed after the package is built.

Signed-off-by: Xe Iaso <me@xeiaso.net>

* chore: go mod tidy

Signed-off-by: Xe Iaso <me@xeiaso.net>

* test(internal): test an intentionally failing build

Signed-off-by: Xe Iaso <me@xeiaso.net>

---------

Signed-off-by: Xe Iaso <me@xeiaso.net>

authored by

Xe Iaso and committed by
GitHub
178f1796 56e6fa97

+184 -2
+5
.vscode/settings.json
···
··· 1 + { 2 + "github.copilot.enable": { 3 + "*": false 4 + } 5 + }
+6 -1
go.mod
··· 4 5 require ( 6 al.essio.dev/pkg/shellescape v1.6.0 7 github.com/Songmu/gitconfig v0.2.0 8 github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c 9 github.com/goreleaser/nfpm/v2 v2.42.0 10 github.com/pkg/errors v0.9.1 11 ) 12 13 require ( ··· 15 github.com/AlekSi/pointer v1.2.0 // indirect 16 github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect 17 github.com/Masterminds/goutils v1.1.1 // indirect 18 - github.com/Masterminds/semver/v3 v3.3.1 // indirect 19 github.com/Masterminds/sprig/v3 v3.3.0 // indirect 20 github.com/Microsoft/go-winio v0.6.2 // indirect 21 github.com/ProtonMail/go-crypto v1.1.6 // indirect ··· 44 github.com/huandu/xstrings v1.5.0 // indirect 45 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 46 github.com/kevinburke/ssh_config v1.2.0 // indirect 47 github.com/klauspost/compress v1.18.0 // indirect 48 github.com/klauspost/pgzip v1.2.6 // indirect 49 github.com/mattn/go-colorable v0.1.13 // indirect ··· 58 github.com/spf13/cast v1.7.1 // indirect 59 github.com/ulikunitz/xz v0.5.12 // indirect 60 github.com/xanzy/ssh-agent v0.3.3 // indirect 61 gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect 62 golang.org/x/crypto v0.37.0 // indirect 63 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect ··· 72 gopkg.in/warnings.v0 v0.1.2 // indirect 73 gopkg.in/yaml.v3 v3.0.1 // indirect 74 honnef.co/go/tools v0.6.1 // indirect 75 ) 76 77 tool (
··· 4 5 require ( 6 al.essio.dev/pkg/shellescape v1.6.0 7 + github.com/Masterminds/semver/v3 v3.3.1 8 github.com/Songmu/gitconfig v0.2.0 9 + github.com/cavaliergopher/rpm v1.3.0 10 github.com/dop251/goja v0.0.0-20250309171923-bcd7cc6bf64c 11 github.com/goreleaser/nfpm/v2 v2.42.0 12 github.com/pkg/errors v0.9.1 13 + pault.ag/go/debian v0.18.0 14 ) 15 16 require ( ··· 18 github.com/AlekSi/pointer v1.2.0 // indirect 19 github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect 20 github.com/Masterminds/goutils v1.1.1 // indirect 21 github.com/Masterminds/sprig/v3 v3.3.0 // indirect 22 github.com/Microsoft/go-winio v0.6.2 // indirect 23 github.com/ProtonMail/go-crypto v1.1.6 // indirect ··· 46 github.com/huandu/xstrings v1.5.0 // indirect 47 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 48 github.com/kevinburke/ssh_config v1.2.0 // indirect 49 + github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d // indirect 50 github.com/klauspost/compress v1.18.0 // indirect 51 github.com/klauspost/pgzip v1.2.6 // indirect 52 github.com/mattn/go-colorable v0.1.13 // indirect ··· 61 github.com/spf13/cast v1.7.1 // indirect 62 github.com/ulikunitz/xz v0.5.12 // indirect 63 github.com/xanzy/ssh-agent v0.3.3 // indirect 64 + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 65 gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect 66 golang.org/x/crypto v0.37.0 // indirect 67 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect ··· 76 gopkg.in/warnings.v0 v0.1.2 // indirect 77 gopkg.in/yaml.v3 v3.0.1 // indirect 78 honnef.co/go/tools v0.6.1 // indirect 79 + pault.ag/go/topsort v0.1.1 // indirect 80 ) 81 82 tool (
+8
go.sum
··· 36 github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= 37 github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= 38 github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= 39 github.com/cli/browser v1.1.0/go.mod h1:HKMQAt9t12kov91Mn7RfZxyJQQgWgyS/3SZswlZ5iTI= 40 github.com/cli/go-gh v0.1.0 h1:kMqFmC3ECBrV2UKzlOHjNOTTchExVc5tjNHtCqk/zYk= 41 github.com/cli/go-gh v0.1.0/go.mod h1:eTGWl99EMZ+3Iau5C6dHyGAJRRia65MtdBtuhWc+84o= ··· 121 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 122 github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 123 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 124 github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 125 github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 126 github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= ··· 272 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 273 honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= 274 honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4=
··· 36 github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk= 37 github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM= 38 github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc= 39 + github.com/cavaliergopher/rpm v1.3.0 h1:UHX46sasX8MesUXXQ+UbkFLUX4eUWTlEcX8jcnRBIgI= 40 + github.com/cavaliergopher/rpm v1.3.0/go.mod h1:vEumo1vvtrHM1Ov86f6+k8j7zNKOxQfHDCAIcR/36ZI= 41 github.com/cli/browser v1.1.0/go.mod h1:HKMQAt9t12kov91Mn7RfZxyJQQgWgyS/3SZswlZ5iTI= 42 github.com/cli/go-gh v0.1.0 h1:kMqFmC3ECBrV2UKzlOHjNOTTchExVc5tjNHtCqk/zYk= 43 github.com/cli/go-gh v0.1.0/go.mod h1:eTGWl99EMZ+3Iau5C6dHyGAJRRia65MtdBtuhWc+84o= ··· 123 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 124 github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= 125 github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= 126 + github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d h1:RnWZeH8N8KXfbwMTex/KKMYMj0FJRCF6tQubUuQ02GM= 127 + github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d/go.mod h1:phT/jsRPBAEqjAibu1BurrabCBNTYiVI+zbmyCZJY6Q= 128 github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 129 github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 130 github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= ··· 276 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 277 honnef.co/go/tools v0.6.1 h1:R094WgE8K4JirYjBaOpz/AvTyUu/3wbmAoskKN/pxTI= 278 honnef.co/go/tools v0.6.1/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= 279 + pault.ag/go/debian v0.18.0 h1:nr0iiyOU5QlG1VPnhZLNhnCcHx58kukvBJp+dvaM6CQ= 280 + pault.ag/go/debian v0.18.0/go.mod h1:JFl0XWRCv9hWBrB5MDDZjA5GSEs1X3zcFK/9kCNIUmE= 281 + pault.ag/go/topsort v0.1.1 h1:L0QnhUly6LmTv0e3DEzbN2q6/FGgAcQvaEw65S53Bg4= 282 + pault.ag/go/topsort v0.1.1/go.mod h1:r1kc/L0/FZ3HhjezBIPaNVhkqv8L0UJ9bxRuHRVZ0q4=
+5
internal/git_test.go
··· 25 input: "1.0.0", 26 want: "1.0.0", 27 }, 28 } { 29 t.Run(tt.name, func(t *testing.T) { 30 yeet.ForceGitVersion = &tt.input
··· 25 input: "1.0.0", 26 want: "1.0.0", 27 }, 28 + { 29 + name: "with version with v and -", 30 + input: "v1.0.0-abc123", 31 + want: "1.0.0-abc123", 32 + }, 33 } { 34 t.Run(tt.name, func(t *testing.T) { 35 yeet.ForceGitVersion = &tt.input
+5
internal/mkdeb/mkdeb.go
··· 8 "runtime" 9 "time" 10 11 "github.com/TecharoHQ/yeet/internal" 12 "github.com/TecharoHQ/yeet/internal/pkgmeta" 13 "github.com/goreleaser/nfpm/v2" ··· 26 } 27 } 28 }() 29 30 os.MkdirAll("./var", 0755) 31 os.WriteFile(filepath.Join("./var", ".gitignore"), []byte("*\n!.gitignore"), 0644)
··· 8 "runtime" 9 "time" 10 11 + "github.com/Masterminds/semver/v3" 12 "github.com/TecharoHQ/yeet/internal" 13 "github.com/TecharoHQ/yeet/internal/pkgmeta" 14 "github.com/goreleaser/nfpm/v2" ··· 27 } 28 } 29 }() 30 + 31 + if _, err := semver.NewVersion(p.Version); err != nil { 32 + return "", err 33 + } 34 35 os.MkdirAll("./var", 0755) 36 os.WriteFile(filepath.Join("./var", ".gitignore"), []byte("*\n!.gitignore"), 0644)
+26
internal/mkdeb/mkdeb_test.go
···
··· 1 + package mkdeb 2 + 3 + import ( 4 + "testing" 5 + 6 + "github.com/TecharoHQ/yeet/internal/yeettest" 7 + "pault.ag/go/debian/deb" 8 + ) 9 + 10 + func TestBuild(t *testing.T) { 11 + fname := yeettest.BuildHello(t, Build, "1.0.0", true) 12 + 13 + debFile, close, err := deb.LoadFile(fname) 14 + if err != nil { 15 + t.Fatalf("failed to load deb file: %v", err) 16 + } 17 + defer close() 18 + 19 + if debFile.Control.Version.Empty() { 20 + t.Error("version is empty") 21 + } 22 + } 23 + 24 + func TestBuildError(t *testing.T) { 25 + yeettest.BuildHello(t, Build, ".0.0", false) 26 + }
+5
internal/mkrpm/mkrpm.go
··· 8 "runtime" 9 "time" 10 11 "github.com/TecharoHQ/yeet/internal" 12 "github.com/TecharoHQ/yeet/internal/pkgmeta" 13 "github.com/goreleaser/nfpm/v2" ··· 26 } 27 } 28 }() 29 30 os.MkdirAll("./var", 0755) 31 os.WriteFile(filepath.Join("./var", ".gitignore"), []byte("*\n!.gitignore"), 0644)
··· 8 "runtime" 9 "time" 10 11 + "github.com/Masterminds/semver/v3" 12 "github.com/TecharoHQ/yeet/internal" 13 "github.com/TecharoHQ/yeet/internal/pkgmeta" 14 "github.com/goreleaser/nfpm/v2" ··· 27 } 28 } 29 }() 30 + 31 + if _, err := semver.NewVersion(p.Version); err != nil { 32 + return "", err 33 + } 34 35 os.MkdirAll("./var", 0755) 36 os.WriteFile(filepath.Join("./var", ".gitignore"), []byte("*\n!.gitignore"), 0644)
+37
internal/mkrpm/mkrpm_test.go
···
··· 1 + package mkrpm 2 + 3 + import ( 4 + "os" 5 + "testing" 6 + 7 + "github.com/Masterminds/semver/v3" 8 + "github.com/TecharoHQ/yeet/internal/yeettest" 9 + "github.com/cavaliergopher/rpm" 10 + ) 11 + 12 + func TestBuild(t *testing.T) { 13 + fname := yeettest.BuildHello(t, Build, "1.0.0", true) 14 + 15 + pkg, err := rpm.Open(fname) 16 + if err != nil { 17 + t.Fatalf("failed to open rpm file: %v", err) 18 + } 19 + 20 + version, err := semver.NewVersion(pkg.Version()) 21 + if err != nil { 22 + t.Fatalf("failed to parse version: %v", err) 23 + } 24 + if version == nil { 25 + t.Error("version is nil") 26 + } 27 + 28 + fin, err := os.Open(fname) 29 + if err != nil { 30 + t.Fatalf("failed to open rpm file: %v", err) 31 + } 32 + defer fin.Close() 33 + } 34 + 35 + func TestBuildError(t *testing.T) { 36 + yeettest.BuildHello(t, Build, ".0.0", false) 37 + }
+4
internal/mktarball/mktarball.go
··· 10 "path/filepath" 11 "runtime" 12 13 "github.com/TecharoHQ/yeet/internal" 14 "github.com/TecharoHQ/yeet/internal/pkgmeta" 15 ) ··· 29 } 30 } 31 }() 32 33 os.MkdirAll("./var", 0755) 34 os.WriteFile(filepath.Join("./var", ".gitignore"), []byte("*\n!.gitignore"), 0644)
··· 10 "path/filepath" 11 "runtime" 12 13 + "github.com/Masterminds/semver/v3" 14 "github.com/TecharoHQ/yeet/internal" 15 "github.com/TecharoHQ/yeet/internal/pkgmeta" 16 ) ··· 30 } 31 } 32 }() 33 + if _, err := semver.NewVersion(p.Version); err != nil { 34 + return "", err 35 + } 36 37 os.MkdirAll("./var", 0755) 38 os.WriteFile(filepath.Join("./var", ".gitignore"), []byte("*\n!.gitignore"), 0644)
+15
internal/mktarball/mktarball_test.go
···
··· 1 + package mktarball 2 + 3 + import ( 4 + "testing" 5 + 6 + "github.com/TecharoHQ/yeet/internal/yeettest" 7 + ) 8 + 9 + func TestBuild(t *testing.T) { 10 + yeettest.BuildHello(t, Build, "1.0.0", true) 11 + } 12 + 13 + func TestBuildError(t *testing.T) { 14 + yeettest.BuildHello(t, Build, ".0.0", false) 15 + }
+7
internal/testdata/hello/main.go
···
··· 1 + package main 2 + 3 + import "fmt" 4 + 5 + func main() { 6 + fmt.Println("Hello, world!") 7 + }
+8 -1
internal/yeet/yeet.go
··· 10 "strings" 11 "time" 12 13 "github.com/pkg/errors" 14 ) 15 ··· 83 return "", err 84 } 85 86 - return strings.TrimSuffix(s, "\n"), nil 87 } 88 89 // DockerTag tags a docker image
··· 10 "strings" 11 "time" 12 13 + "github.com/Masterminds/semver/v3" 14 "github.com/pkg/errors" 15 ) 16 ··· 84 return "", err 85 } 86 87 + ver, err := semver.NewVersion(strings.TrimSuffix(s, "\n")) 88 + if err != nil { 89 + // probably no git tag 90 + return "devel", nil 91 + } 92 + 93 + return ver.String(), nil 94 } 95 96 // DockerTag tags a docker image
+53
internal/yeettest/buildpackage.go
···
··· 1 + package yeettest 2 + 3 + import ( 4 + "os" 5 + "path/filepath" 6 + "runtime" 7 + "testing" 8 + 9 + "github.com/TecharoHQ/yeet/internal/pkgmeta" 10 + "github.com/TecharoHQ/yeet/internal/yeet" 11 + ) 12 + 13 + type Impl func(p pkgmeta.Package) (string, error) 14 + 15 + func BuildHello(t *testing.T, build Impl, version string, fatal bool) string { 16 + t.Helper() 17 + 18 + p := pkgmeta.Package{ 19 + Name: "hello", 20 + Version: version, 21 + Description: "Hello world", 22 + Homepage: "https://example.com", 23 + License: "MIT", 24 + Platform: runtime.GOOS, 25 + Goarch: runtime.GOARCH, 26 + Build: func(p pkgmeta.BuildInput) { 27 + yeet.ShouldWork(t.Context(), nil, yeet.WD, "go", "build", "-o", filepath.Join(p.Bin, "hello"), "../testdata/hello") 28 + }, 29 + } 30 + 31 + foutpath, err := build(p) 32 + switch fatal { 33 + case true: 34 + if err != nil { 35 + t.Fatalf("Build() error = %v", err) 36 + } 37 + case false: 38 + if err != nil { 39 + t.Logf("Build() error = %v", err) 40 + } 41 + return "" 42 + } 43 + 44 + if foutpath == "" { 45 + t.Fatal("Build() returned empty path") 46 + } 47 + 48 + t.Cleanup(func() { 49 + os.RemoveAll(filepath.Dir(foutpath)) 50 + }) 51 + 52 + return foutpath 53 + }