Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion dependency_updater/dependency_updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,10 @@ func getVersionAndCommit(ctx context.Context, client *github.Client, dependencie
currentTag := dependencies[dependencyType].Tag
tagPrefix := dependencies[dependencyType].TagPrefix

if dependencies[dependencyType].Tracking == "tag" {
if dependencies[dependencyType].Tracking == "tag" || dependencies[dependencyType].Tracking == "release" {
// Collect all valid tags across all pages, then find the max version
var validTags []*github.RepositoryTag
trackingMode := dependencies[dependencyType].Tracking

for {
tags, resp, err := client.Repositories.ListTags(
Expand All @@ -206,6 +207,19 @@ func getVersionAndCommit(ctx context.Context, client *github.Client, dependencie
continue
}

// Filter based on tracking mode:
// - "release": only stable releases (no prerelease suffix)
// - "tag": releases and RC versions only (exclude -synctest, -alpha, etc.)
if trackingMode == "release" {
if !IsReleaseVersion(*tag.Name, tagPrefix) {
continue
}
} else if trackingMode == "tag" {
if !IsReleaseOrRCVersion(*tag.Name, tagPrefix) {
continue
}
}

// Check if this is a valid upgrade (not a downgrade)
if err := ValidateVersionUpgrade(currentTag, *tag.Name, tagPrefix); err != nil {
continue
Expand Down
44 changes: 44 additions & 0 deletions dependency_updater/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
// rcPattern matches various RC formats: -rc1, -rc.1, -rc-1, -RC1, etc.
var rcPattern = regexp.MustCompile(`(?i)-rc[.-]?(\d+)`)

// rcOnlyPattern is used to check if a version contains ONLY an RC prerelease (not -synctest, -alpha, etc.)
var rcOnlyPattern = regexp.MustCompile(`(?i)^-rc[.-]?\d+$`)

// ParseVersion extracts and normalizes a semantic version from a tag string.
// It handles tagPrefix stripping, v-prefix normalization, and RC format normalization.
func ParseVersion(tag string, tagPrefix string) (*semver.Version, error) {
Expand Down Expand Up @@ -90,3 +93,44 @@ func CompareVersions(v1Tag, v2Tag, tagPrefix string) (int, error) {
}
return v1.Compare(v2), nil
}

// IsReleaseVersion returns true if the tag is a stable release (no prerelease suffix).
// Examples:
// - "v1.0.0" -> true
// - "v1.0.0-rc1" -> false
// - "v1.0.0-synctest.0" -> false
func IsReleaseVersion(tag string, tagPrefix string) bool {
v, err := ParseVersion(tag, tagPrefix)
if err != nil {
return false
}
return v.Prerelease() == ""
}

// IsRCVersion returns true if the tag is a release candidate version.
// This matches versions with -rc, -rc.N, -rc-N, -rcN suffixes.
// Examples:
// - "v1.0.0-rc1" -> true
// - "v1.0.0-rc.2" -> true
// - "v1.0.0" -> false (stable release, not RC)
// - "v1.0.0-synctest.0" -> false (not an RC)
// - "v1.0.0-alpha" -> false (not an RC)
func IsRCVersion(tag string, tagPrefix string) bool {
v, err := ParseVersion(tag, tagPrefix)
if err != nil {
return false
}
prerelease := v.Prerelease()
if prerelease == "" {
return false
}
// Check if the prerelease is ONLY an RC format (e.g., "rc.1", "rc1", "rc-1")
// We need to check the original format before normalization
return rcOnlyPattern.MatchString("-" + prerelease)
}

// IsReleaseOrRCVersion returns true if the tag is either a stable release or an RC version.
// This excludes other prereleases like -alpha, -beta, -synctest, etc.
func IsReleaseOrRCVersion(tag string, tagPrefix string) bool {
return IsReleaseVersion(tag, tagPrefix) || IsRCVersion(tag, tagPrefix)
}
118 changes: 118 additions & 0 deletions dependency_updater/version_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,124 @@ func TestCompareVersions(t *testing.T) {
}
}

func TestIsReleaseVersion(t *testing.T) {
tests := []struct {
tag string
tagPrefix string
want bool
}{
// Stable releases
{"v1.0.0", "", true},
{"v0.2.2", "", true},
{"1.35.3", "", true}, // nethermind style
{"v1.101603.5", "", true}, // op-geth style

// With prefix
{"op-node/v1.16.2", "op-node", true},

// Pre-release versions (should return false)
{"v1.0.0-rc1", "", false},
{"v1.0.0-rc.1", "", false},
{"v1.0.0-rc-1", "", false},
{"v1.0.0-synctest.0", "", false},
{"v1.0.0-alpha", "", false},
{"v1.0.0-beta.1", "", false},
{"op-node/v1.16.6-synctest.0", "op-node", false},
{"op-node/v1.16.3-rc1", "op-node", false},

// Invalid versions (should return false)
{"not-a-version", "", false},
{"", "", false},
}

for _, tt := range tests {
t.Run(tt.tag, func(t *testing.T) {
got := IsReleaseVersion(tt.tag, tt.tagPrefix)
if got != tt.want {
t.Errorf("IsReleaseVersion(%q, %q) = %v, want %v", tt.tag, tt.tagPrefix, got, tt.want)
}
})
}
}

func TestIsRCVersion(t *testing.T) {
tests := []struct {
tag string
tagPrefix string
want bool
}{
// RC versions
{"v1.0.0-rc1", "", true},
{"v1.0.0-rc.1", "", true},
{"v1.0.0-rc-1", "", true},
{"v1.0.0-RC1", "", true},
{"v1.0.0-rc12", "", true},
{"op-node/v1.16.3-rc1", "op-node", true},
{"op-node/v1.16.3-rc.2", "op-node", true},

// Stable releases (not RC)
{"v1.0.0", "", false},
{"v0.2.2", "", false},
{"op-node/v1.16.2", "op-node", false},

// Other pre-release versions (not RC)
{"v1.0.0-synctest.0", "", false},
{"op-node/v1.16.6-synctest.0", "op-node", false},
{"v1.0.0-alpha", "", false},
{"v1.0.0-beta.1", "", false},
{"v1.0.0-alpha.rc1", "", false}, // rc is part of another prerelease

// Invalid versions
{"not-a-version", "", false},
{"", "", false},
}

for _, tt := range tests {
t.Run(tt.tag, func(t *testing.T) {
got := IsRCVersion(tt.tag, tt.tagPrefix)
if got != tt.want {
t.Errorf("IsRCVersion(%q, %q) = %v, want %v", tt.tag, tt.tagPrefix, got, tt.want)
}
})
}
}

func TestIsReleaseOrRCVersion(t *testing.T) {
tests := []struct {
tag string
tagPrefix string
want bool
}{
// Stable releases - should pass
{"v1.0.0", "", true},
{"v0.2.2", "", true},
{"op-node/v1.16.2", "op-node", true},

// RC versions - should pass
{"v1.0.0-rc1", "", true},
{"v1.0.0-rc.1", "", true},
{"op-node/v1.16.3-rc1", "op-node", true},

// Other pre-release versions - should NOT pass
{"v1.0.0-synctest.0", "", false},
{"op-node/v1.16.6-synctest.0", "op-node", false},
{"v1.0.0-alpha", "", false},
{"v1.0.0-beta.1", "", false},

// Invalid versions
{"not-a-version", "", false},
}

for _, tt := range tests {
t.Run(tt.tag, func(t *testing.T) {
got := IsReleaseOrRCVersion(tt.tag, tt.tagPrefix)
if got != tt.want {
t.Errorf("IsReleaseOrRCVersion(%q, %q) = %v, want %v", tt.tag, tt.tagPrefix, got, tt.want)
}
})
}
}

func TestRCVersionOrdering(t *testing.T) {
// Verify that RC versions are ordered correctly
versions := []string{
Expand Down
4 changes: 2 additions & 2 deletions versions.env
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export BASE_RETH_NODE_COMMIT=4a580faec0b560eb65d6ee2f620efb7b044a649d
export BASE_RETH_NODE_COMMIT=bb1b4571bebb8a9cd8ff1ec8758001fdc32758e8
export BASE_RETH_NODE_REPO=https://github.com/base/base.git
export BASE_RETH_NODE_TAG=v0.2.2
export BASE_RETH_NODE_TAG=v0.3.0
export NETHERMIND_COMMIT=d9febbce240491e8f918d41a4ffd06385a746b6c
export NETHERMIND_REPO=https://github.com/NethermindEth/nethermind.git
export NETHERMIND_TAG=1.35.3
Expand Down
14 changes: 7 additions & 7 deletions versions.json
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
{
"base_reth_node": {
"tag": "v0.2.2",
"commit": "4a580faec0b560eb65d6ee2f620efb7b044a649d",
"tag": "v0.3.0",
"commit": "bb1b4571bebb8a9cd8ff1ec8758001fdc32758e8",
"owner": "base",
"repo": "base",
"tracking": "tag"
"tracking": "release"
},
"nethermind": {
"tag": "1.35.3",
"commit": "d9febbce240491e8f918d41a4ffd06385a746b6c",
"owner": "NethermindEth",
"repo": "nethermind",
"tracking": "tag"
"tracking": "release"
},
"op_geth": {
"tag": "v1.101603.5",
"commit": "904a088c5cc1eeec21a1ffa47327dc20a809e642",
"owner": "ethereum-optimism",
"repo": "op-geth",
"tracking": "tag"
"tracking": "release"
},
"op_node": {
"tag": "op-node/v1.16.2",
"commit": "1b8c541060f0d323a7023fbc68fbbc8daf674340",
"tagPrefix": "op-node",
"owner": "ethereum-optimism",
"repo": "optimism",
"tracking": "tag"
"tracking": "release"
},
"op_reth": {
"tag": "v1.9.3",
"commit": "27a8c0f5a6dfb27dea84c5751776ecabdd069646",
"owner": "paradigmxyz",
"repo": "reth",
"tracking": "tag"
"tracking": "release"
}
}