From 1e1d13e667e1a8f208399c32beaef17b5764f7ea Mon Sep 17 00:00:00 2001 From: HappySean Date: Tue, 16 Jun 2026 20:51:08 +0800 Subject: [PATCH] [FIX] apply instant compat flags during API load --- cli/api.go | 13 ++++++++++- cli/api_test.go | 42 +++++++++++++++++++++++++++++++++++ cli/cli.go | 1 + cmd/surf/compat_flags_test.go | 3 ++- cmd/surf/main.go | 17 +++++++++++++- 5 files changed, 73 insertions(+), 3 deletions(-) diff --git a/cli/api.go b/cli/api.go index 5cc48e7..22cd324 100644 --- a/cli/api.go +++ b/cli/api.go @@ -45,6 +45,7 @@ func (a *API) Merge(other API) { } var loaders []Loader +var operationCommandHooks []func(*cobra.Command) // Loader is used to detect and load an API spec, turning it into CLI commands. type Loader interface { @@ -58,6 +59,12 @@ func AddLoader(loader Loader) { loaders = append(loaders, loader) } +// AddOperationCommandHook registers a callback that can customize generated +// operation commands before they are added to the Cobra command tree. +func AddOperationCommandHook(hook func(*cobra.Command)) { + operationCommandHooks = append(operationCommandHooks, hook) +} + func setupRootFromAPI(root *cobra.Command, api *API) { if root.Short == "" { root.Short = api.Short @@ -73,7 +80,11 @@ func setupRootFromAPI(root *cobra.Command, api *API) { group := &cobra.Group{ID: op.Group, Title: groupName} root.AddGroup(group) } - root.AddCommand(op.Command()) + cmd := op.Command() + for _, hook := range operationCommandHooks { + hook(cmd) + } + root.AddCommand(cmd) } } diff --git a/cli/api_test.go b/cli/api_test.go index b577762..37b88ce 100644 --- a/cli/api_test.go +++ b/cli/api_test.go @@ -74,3 +74,45 @@ func TestBadSpecURL(t *testing.T) { _, err := Load("https://api.example.com", &cobra.Command{}) assert.Error(t, err) } + +func TestLoadAppliesOperationCommandHooks(t *testing.T) { + reset(false) + viper.Set("rsh-no-cache", true) + + called := false + AddOperationCommandHook(func(cmd *cobra.Command) { + if cmd.Name() != "search-web" { + return + } + called = true + cmd.Flags().Bool("hooked", false, "hooked by test") + }) + + AddLoader(&overrideLoader{ + load: func(entrypoint, spec url.URL, resp *http.Response) (API, error) { + return API{ + Operations: []Operation{ + { + Name: "search web", + Method: http.MethodGet, + URITemplate: "https://api.example.com/search", + }, + }, + }, nil + }, + }) + + configs["hook-test"] = &APIConfig{ + Base: "https://api.example.com", + SpecFiles: []string{"testdata/petstore.json"}, + } + + root := &cobra.Command{Use: "test"} + _, err := Load("https://api.example.com", root) + assert.NoError(t, err) + assert.True(t, called) + + cmd, _, err := root.Find([]string{"search-web"}) + assert.NoError(t, err) + assert.NotNil(t, cmd.Flags().Lookup("hooked")) +} diff --git a/cli/cli.go b/cli/cli.go index 880116b..0f4ec24 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -193,6 +193,7 @@ func Init(name string, version string) { encodings = map[string]ContentEncoding{} linkParsers = []LinkParser{} loaders = []Loader{} + operationCommandHooks = nil // Determine if we are using a TTY or colored output is forced-on. tty := false diff --git a/cmd/surf/compat_flags_test.go b/cmd/surf/compat_flags_test.go index afe802f..9254543 100644 --- a/cmd/surf/compat_flags_test.go +++ b/cmd/surf/compat_flags_test.go @@ -21,11 +21,12 @@ func TestHiddenCompatRootFlags(t *testing.T) { func TestSearchWebQueryCompatAlias(t *testing.T) { cmd := &cobra.Command{ - Use: "search-web", + Use: "search-web [flags]", Run: func(cmd *cobra.Command, args []string) {}, } cmd.Flags().String("q", "", "query") addHiddenCompatOperationFlags(cmd) + addHiddenCompatOperationFlags(cmd) f := cmd.Flags().Lookup("query") if f == nil { diff --git a/cmd/surf/main.go b/cmd/surf/main.go index 1d3b318..5845e54 100644 --- a/cmd/surf/main.go +++ b/cmd/surf/main.go @@ -60,6 +60,7 @@ func main() { cli.Init("surf", version) cli.Defaults() cli.AddLoader(openapi.New()) + cli.AddOperationCommandHook(addHiddenCompatOperationFlags) // Send Cobra diagnostics (deprecation warnings, usage errors) to stderr // so they don't pollute JSON output on stdout. @@ -153,6 +154,9 @@ func main() { // Errors are still printed by cli.Run()'s error handling. cmd.SilenceErrors = true cmd.SilenceUsage = true + for _, child := range cmd.Commands() { + addHiddenCompatOperationFlags(child) + } // Catch unknown commands that fall through to the API subcommand. // Without this, `surf nonexistent` shows the API subcommand's @@ -253,7 +257,10 @@ func addHiddenCompatRootFlags(root *cobra.Command) { } func addHiddenCompatOperationFlags(cmd *cobra.Command) { - if cmd.Name() != "search-web" { + if !isSearchWebCommand(cmd) { + return + } + if cmd.Flags().Lookup("query") != nil { return } @@ -281,6 +288,14 @@ func addHiddenCompatOperationFlags(cmd *cobra.Command) { } } +func isSearchWebCommand(cmd *cobra.Command) bool { + if cmd.Name() == "search-web" { + return true + } + fields := strings.Fields(cmd.Use) + return len(fields) > 0 && fields[0] == "search-web" +} + // needsCachedAPI reports whether the current argv invokes a command that // requires the cached OpenAPI spec. Meta commands (auth, sync, help, version, // install, completion, telemetry, feedback, catalog) work without it.