From 24345be9bc6aac4edd7336dd27a82b0a086011fd Mon Sep 17 00:00:00 2001 From: "Philip K. Warren" Date: Tue, 17 Feb 2026 15:41:46 -0600 Subject: [PATCH] Re-enable the ability to run fetcher on a single plugin Update the fetcher command to accept an `--include` multi-valued flag, where each value is either an org or org/plugin. If specified, only those included plugins will have their versions updated by the fetcher. --- internal/cmd/fetcher/main.go | 77 +++++++++++++++++++++++++++++-- internal/cmd/fetcher/main_test.go | 2 +- 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/internal/cmd/fetcher/main.go b/internal/cmd/fetcher/main.go index b9b5b8a4c..e8d549ca1 100644 --- a/internal/cmd/fetcher/main.go +++ b/internal/cmd/fetcher/main.go @@ -19,6 +19,7 @@ import ( "buf.build/go/app/appext" "github.com/bufbuild/buf/private/bufpkg/bufremoteplugin/bufremotepluginconfig" "github.com/bufbuild/buf/private/pkg/encoding" + "github.com/spf13/pflag" "golang.org/x/mod/semver" "github.com/bufbuild/plugins/internal/docker" @@ -33,6 +34,53 @@ var ( errNoVersions = errors.New("no versions found") ) +type flags struct { + include []string +} + +func (f *flags) Bind(flagSet *pflag.FlagSet) { + flagSet.StringArrayVar( + &f.include, + "include", + nil, + `Only fetch plugins matching these patterns (org or org/name). May be specified multiple times.`, + ) +} + +type pluginFilter struct { + orgs map[string]struct{} + plugins map[string]struct{} +} + +func newPluginFilter(includes []string) *pluginFilter { + if len(includes) == 0 { + return nil + } + f := &pluginFilter{ + orgs: make(map[string]struct{}), + plugins: make(map[string]struct{}), + } + for _, pattern := range includes { + if strings.Contains(pattern, "/") { + f.plugins[pattern] = struct{}{} + } else { + f.orgs[pattern] = struct{}{} + } + } + return f +} + +func (f *pluginFilter) includes(org, name string) bool { + if f == nil { + return true + } + if _, ok := f.orgs[org]; ok { + return true + } + _, ok := f.plugins[org+"/"+name] + return ok +} + // Fetcher is an interface for fetching plugin versions from external sources. type Fetcher interface { Fetch(ctx context.Context, config *source.Config) (string, error) @@ -44,13 +92,14 @@ func main() { func newRootCommand(name string) *appcmd.Command { builder := appext.NewBuilder(name) + f := &flags{} return &appcmd.Command{ - Use: name + " ", + Use: name + " [directory]", Short: "Fetches latest plugin versions from external sources.", - Args: appcmd.ExactArgs(1), + Args: appcmd.MaximumNArgs(1), Run: builder.NewRunFunc(func(ctx context.Context, container appext.Container) error { client := fetchclient.New(ctx) - created, err := run(ctx, container, client) + created, err := run(ctx, container, client, f) if err != nil { return fmt.Errorf("failed to fetch versions: %w", err) } @@ -59,6 +108,7 @@ func newRootCommand(name string) *appcmd.Command { } return nil }), + BindFlags: f.Bind, BindPersistentFlags: builder.BindRoot, } } @@ -312,8 +362,17 @@ type pluginToCreate struct { newVersion string } -func run(ctx context.Context, container appext.Container, fetcher Fetcher) ([]createdPlugin, error) { - root := container.Arg(0) +func run(ctx context.Context, container appext.Container, fetcher Fetcher, f *flags) ([]createdPlugin, error) { + var root string + if container.NumArgs() > 0 { + root = container.Arg(0) + } else { + var err error + root, err = os.Getwd() + if err != nil { + return nil, err + } + } logger := container.Logger() now := time.Now() defer func() { @@ -350,6 +409,7 @@ func run(ctx context.Context, container appext.Container, fetcher Fetcher) ([]cr } // First pass: fetch all new versions and determine which plugins need updates + filter := newPluginFilter(f.include) latestVersions := make(map[string]string, len(configs)) pendingCreations := make(map[string]*pluginToCreate) // keyed by plugin directory @@ -358,6 +418,13 @@ func run(ctx context.Context, container appext.Container, fetcher Fetcher) ([]cr logger.Info("skipping source", slog.String("filename", config.Filename)) continue } + configDir := filepath.Dir(config.Filename) + pluginName := filepath.Base(configDir) + pluginOrg := filepath.Base(filepath.Dir(configDir)) + if !filter.includes(pluginOrg, pluginName) { + logger.Debug("skipping source (not in --include list)", slog.String("filename", config.Filename)) + continue + } newVersion := latestVersions[config.CacheKey()] if newVersion == "" { newVersion, err = fetcher.Fetch(ctx, config) diff --git a/internal/cmd/fetcher/main_test.go b/internal/cmd/fetcher/main_test.go index dd5168093..1a02e46fe 100644 --- a/internal/cmd/fetcher/main_test.go +++ b/internal/cmd/fetcher/main_test.go @@ -170,7 +170,7 @@ func TestRunDependencyOrdering(t *testing.T) { // Run the fetcher container := newTestContainer(t, tmpDir) - created, err := run(ctx, container, fetcher) + created, err := run(ctx, container, fetcher, &flags{}) require.NoError(t, err) // Verify plugins were created in dependency order