Skip to content
Closed
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
86 changes: 68 additions & 18 deletions internal/cmd/fetcher/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,13 @@ func newRootCommand(name string) *appcmd.Command {
}

type createdPlugin struct {
org string
name string
pluginDir string
previousVersion string
newVersion string
org string
name string
pluginDir string
previousVersion string
newVersion string
previousReleaseURL string
releaseURL string
}

func (p createdPlugin) String() string {
Expand Down Expand Up @@ -382,9 +384,11 @@ func updatePluginDeps(ctx context.Context, logger *slog.Logger, content []byte,

// pluginToCreate represents a plugin that needs a new version created.
type pluginToCreate struct {
pluginDir string
previousVersion string
newVersion string
pluginDir string
previousVersion string
newVersion string
previousReleaseURL string
releaseURL string
}

type runOption func(*runOptions)
Expand Down Expand Up @@ -499,16 +503,52 @@ func run(
latestPluginVersions[p.Name] = pending.newVersion

created = append(created, createdPlugin{
org: filepath.Base(filepath.Dir(pending.pluginDir)),
name: filepath.Base(pending.pluginDir),
pluginDir: pending.pluginDir,
previousVersion: pending.previousVersion,
newVersion: pending.newVersion,
org: filepath.Base(filepath.Dir(pending.pluginDir)),
name: filepath.Base(pending.pluginDir),
pluginDir: pending.pluginDir,
previousVersion: pending.previousVersion,
newVersion: pending.newVersion,
previousReleaseURL: pending.previousReleaseURL,
releaseURL: pending.releaseURL,
})
}
return created, nil
}

// pluginReleaseURL returns the release URL for a new plugin version, or empty string if not determinable.
// For GitHub sources, reads the previous version's buf.plugin.yaml to determine
// whether the repo uses v-prefixed tags (e.g. v1.2.3) or bare tags (e.g. 1.2.3).
func pluginReleaseURL(pluginDir, previousVersion, newVersion string, src source.Source) string {
bare := strings.TrimPrefix(newVersion, "v")
switch {
case src.GitHub != nil:
tag := newVersion // default: v-prefixed
// Check the previous buf.plugin.yaml to see which tag format the repo uses.
prevYAML := filepath.Join(pluginDir, previousVersion, "buf.plugin.yaml")
if content, err := os.ReadFile(prevYAML); err == nil {
prevBare := strings.TrimPrefix(previousVersion, "v")
// If the file contains the bare version but NOT the v-prefixed version
// in a blob/tree URL, the repo uses bare tags.
if strings.Contains(string(content), "/blob/"+prevBare) && !strings.Contains(string(content), "/blob/v"+prevBare) {
tag = bare
}
}
return "https://github.com/" + src.GitHub.Owner + "/" + src.GitHub.Repository + "/releases/tag/" + tag
case src.GoProxy != nil:
return "https://pkg.go.dev/" + src.GoProxy.Name + "@" + newVersion
case src.NPMRegistry != nil:
return "https://www.npmjs.com/package/" + src.NPMRegistry.Name + "/v/" + bare
case src.Maven != nil:
groupPath := strings.ReplaceAll(src.Maven.Group, ".", "/")
return "https://mvnrepository.com/artifact/" + groupPath + "/" + src.Maven.Name + "/" + bare
case src.Crates != nil:
return "https://crates.io/crates/" + src.Crates.CrateName + "/" + bare
case src.DartFlutter != nil:
return "https://pub.dev/packages/" + src.DartFlutter.Name + "/versions/" + bare
}
return ""
}

// fetchPendingCreations iterates over source configs, fetches the latest
// version for each enabled plugin, and returns a map of plugin directories
// that need a new version created.
Expand Down Expand Up @@ -582,9 +622,11 @@ func fetchPendingCreations(
}

pendingCreations[pluginDir] = &pluginToCreate{
pluginDir: pluginDir,
previousVersion: previousVersion,
newVersion: newVersion,
pluginDir: pluginDir,
previousVersion: previousVersion,
newVersion: newVersion,
previousReleaseURL: pluginReleaseURL(pluginDir, previousVersion, previousVersion, config.Source),
releaseURL: pluginReleaseURL(pluginDir, previousVersion, newVersion, config.Source),
}
}
return pendingCreations, nil
Expand Down Expand Up @@ -908,10 +950,18 @@ func generatePRBody(created []createdPlugin) string {
}
fmt.Fprintf(&sb, "### %s\n", g.name)
for _, p := range g.plugins {
prevVersionLink := p.previousVersion
if p.previousReleaseURL != "" {
prevVersionLink = "[" + p.previousVersion + "](" + p.previousReleaseURL + ")"
}
newVersionLink := p.newVersion
if p.releaseURL != "" {
newVersionLink = "[" + p.newVersion + "](" + p.releaseURL + ")"
}
if p.org == communityOrg {
fmt.Fprintf(&sb, "- %s → %s\n", p.previousVersion, p.newVersion)
fmt.Fprintf(&sb, "- %s → %s\n", prevVersionLink, newVersionLink)
} else {
fmt.Fprintf(&sb, "- %s: %s → %s\n", p.name, p.previousVersion, p.newVersion)
fmt.Fprintf(&sb, "- %s: %s → %s\n", p.name, prevVersionLink, newVersionLink)
}
}
}
Expand Down
134 changes: 134 additions & 0 deletions internal/cmd/fetcher/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,140 @@ COPY --from=consumer /binary /usr/local/bin/protoc-gen-consumer
))
}

func TestPluginReleaseURL(t *testing.T) {
t.Parallel()

t.Run("github with v-prefixed tags", func(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
pluginDir := filepath.Join(tmpDir, "plugins", "protocolbuffers", "go")
require.NoError(t, os.MkdirAll(filepath.Join(pluginDir, "v1.36.5"), 0755))
require.NoError(t, os.WriteFile(
filepath.Join(pluginDir, "v1.36.5", "buf.plugin.yaml"),
[]byte("license_url: https://github.com/protocolbuffers/protobuf-go/blob/v1.36.5/LICENSE\n"),
0644,
))
src := source.Source{GitHub: &source.GitHubConfig{Owner: "protocolbuffers", Repository: "protobuf-go"}}
url := pluginReleaseURL(pluginDir, "v1.36.5", "v1.37.0", src)
assert.Equal(t, "https://github.com/protocolbuffers/protobuf-go/releases/tag/v1.37.0", url)
})

t.Run("github with bare tags (no v prefix)", func(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
pluginDir := filepath.Join(tmpDir, "plugins", "connectrpc", "swift")
require.NoError(t, os.MkdirAll(filepath.Join(pluginDir, "v1.2.1"), 0755))
require.NoError(t, os.WriteFile(
filepath.Join(pluginDir, "v1.2.1", "buf.plugin.yaml"),
[]byte("license_url: https://github.com/connectrpc/connect-swift/blob/1.2.1/LICENSE\n"),
0644,
))
src := source.Source{GitHub: &source.GitHubConfig{Owner: "connectrpc", Repository: "connect-swift"}}
url := pluginReleaseURL(pluginDir, "v1.2.1", "v1.2.2", src)
assert.Equal(t, "https://github.com/connectrpc/connect-swift/releases/tag/1.2.2", url)
})

t.Run("github falls back to v-prefix when no previous yaml", func(t *testing.T) {
t.Parallel()
tmpDir := t.TempDir()
pluginDir := filepath.Join(tmpDir, "plugins", "test", "myplugin")
require.NoError(t, os.MkdirAll(pluginDir, 0755))
src := source.Source{GitHub: &source.GitHubConfig{Owner: "test", Repository: "myplugin"}}
url := pluginReleaseURL(pluginDir, "v1.0.0", "v1.1.0", src)
assert.Equal(t, "https://github.com/test/myplugin/releases/tag/v1.1.0", url)
})

t.Run("goproxy", func(t *testing.T) {
t.Parallel()
src := source.Source{GoProxy: &source.GoProxyConfig{Name: "google.golang.org/grpc/cmd/protoc-gen-go-grpc"}}
url := pluginReleaseURL("", "", "v1.5.1", src)
assert.Equal(t, "https://pkg.go.dev/google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1", url)
})

t.Run("npm", func(t *testing.T) {
t.Parallel()
src := source.Source{NPMRegistry: &source.NPMRegistryConfig{Name: "@bufbuild/protoc-gen-es"}}
url := pluginReleaseURL("", "", "v2.5.0", src)
assert.Equal(t, "https://www.npmjs.com/package/@bufbuild/protoc-gen-es/v/2.5.0", url)
})

t.Run("crates", func(t *testing.T) {
t.Parallel()
src := source.Source{Crates: &source.CratesConfig{CrateName: "prost"}}
url := pluginReleaseURL("", "", "v0.13.4", src)
assert.Equal(t, "https://crates.io/crates/prost/0.13.4", url)
})

t.Run("maven", func(t *testing.T) {
t.Parallel()
src := source.Source{Maven: &source.MavenConfig{Group: "io.grpc", Name: "grpc-java"}}
url := pluginReleaseURL("", "", "v1.70.0", src)
assert.Equal(t, "https://mvnrepository.com/artifact/io/grpc/grpc-java/1.70.0", url)
})

t.Run("dart_flutter", func(t *testing.T) {
t.Parallel()
src := source.Source{DartFlutter: &source.DartFlutterConfig{Name: "protobuf"}}
url := pluginReleaseURL("", "", "v3.1.0", src)
assert.Equal(t, "https://pub.dev/packages/protobuf/versions/3.1.0", url)
})
}

func TestGeneratePRBody(t *testing.T) {
t.Parallel()

t.Run("single plugin with release URLs", func(t *testing.T) {
t.Parallel()
created := []createdPlugin{
{
org: "connectrpc", name: "swift",
previousVersion: "v1.2.1",
newVersion: "v1.2.2",
previousReleaseURL: "https://github.com/connectrpc/connect-swift/releases/tag/1.2.1",
releaseURL: "https://github.com/connectrpc/connect-swift/releases/tag/1.2.2",
},
}
body := generatePRBody(created)
assert.Equal(t, "### connectrpc\n- swift: [v1.2.1](https://github.com/connectrpc/connect-swift/releases/tag/1.2.1) → [v1.2.2](https://github.com/connectrpc/connect-swift/releases/tag/1.2.2)", body)
})

t.Run("single plugin without release URLs", func(t *testing.T) {
t.Parallel()
created := []createdPlugin{
{org: "protocolbuffers", name: "go", previousVersion: "v1.36.5", newVersion: "v1.37.0"},
}
body := generatePRBody(created)
assert.Equal(t, "### protocolbuffers\n- go: v1.36.5 → v1.37.0", body)
})

t.Run("multiple plugins grouped by org", func(t *testing.T) {
t.Parallel()
created := []createdPlugin{
{
org: "protocolbuffers", name: "go",
previousVersion: "v1.36.5",
newVersion: "v1.37.0",
previousReleaseURL: "https://github.com/protocolbuffers/protobuf-go/releases/tag/v1.36.5",
releaseURL: "https://github.com/protocolbuffers/protobuf-go/releases/tag/v1.37.0",
},
{
org: "protocolbuffers", name: "java",
previousVersion: "v4.28.3",
newVersion: "v4.29.0",
previousReleaseURL: "https://github.com/protocolbuffers/protobuf/releases/tag/v4.28.3",
releaseURL: "https://github.com/protocolbuffers/protobuf/releases/tag/v4.29.0",
},
}
body := generatePRBody(created)
assert.Equal(t, "### protocolbuffers\n- go: [v1.36.5](https://github.com/protocolbuffers/protobuf-go/releases/tag/v1.36.5) → [v1.37.0](https://github.com/protocolbuffers/protobuf-go/releases/tag/v1.37.0)\n- java: [v4.28.3](https://github.com/protocolbuffers/protobuf/releases/tag/v4.28.3) → [v4.29.0](https://github.com/protocolbuffers/protobuf/releases/tag/v4.29.0)", body)
})

t.Run("empty created list", func(t *testing.T) {
t.Parallel()
assert.Empty(t, generatePRBody(nil))
})
}

type testWriter struct {
tb testing.TB
}
Expand Down
Loading