From 29be18f5cdc6d4fbea30bf55398204015bab1ae0 Mon Sep 17 00:00:00 2001 From: "zhaojunlin.0405" Date: Thu, 2 Jul 2026 17:27:03 +0800 Subject: [PATCH] fix: decouple --json shorthand registration from default format injection --- shortcuts/common/runner.go | 55 ++++++++- .../common/runner_json_shorthand_test.go | 104 ++++++++++++++++++ 2 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 shortcuts/common/runner_json_shorthand_test.go diff --git a/shortcuts/common/runner.go b/shortcuts/common/runner.go index 468148c2c..b3be6c308 100644 --- a/shortcuts/common/runner.go +++ b/shortcuts/common/runner.go @@ -1171,6 +1171,57 @@ func registerShortcutFlags(cmd *cobra.Command, f *cmdutil.Factory, s *Shortcut) registerShortcutFlagsWithContext(context.Background(), cmd, f, s) } +// shortcutDeclaresJSONFlag reports whether the shortcut itself declares a flag +// named "json" in its Flags list (custom semantics, e.g. event +subscribe's +// pretty-print switch or base +record-search's request-body payload). +// Framework-injected flags never appear in s.Flags, so this cleanly separates +// "self-declared json" from "injected shorthand". +func shortcutDeclaresJSONFlag(s *Shortcut) bool { + for _, fl := range s.Flags { + if fl.Name == "json" { + return true + } + } + return false +} + +// shortcutFormatSupportsJSON reports whether the command's format flag accepts +// "json": a self-declared format supports it only when its Enum lists "json"; +// a framework-injected default format (no format entry in s.Flags) always does. +func shortcutFormatSupportsJSON(s *Shortcut) bool { + for _, fl := range s.Flags { + if fl.Name == "format" { + return slices.Contains(fl.Enum, "json") + } + } + return true // framework-injected: json (default) | pretty | table | ndjson | csv +} + +// ensureJSONShorthand registers --json as a shorthand for --format json when: +// 1. the command has a format flag (self-declared or framework-injected), AND +// 2. that format supports "json" (see shortcutFormatSupportsJSON), AND +// 3. no flag named "json" is registered yet — pflag panics on duplicate +// registration, and commands that declare their own --json (event +// +subscribe, base +record-search/-get) keep their custom semantics. +func ensureJSONShorthand(cmd *cobra.Command, s *Shortcut) { + // A shortcut that declares its own "json" flag defines custom semantics + // (e.g. pretty-print switch, request-body payload) — never a shorthand. + if shortcutDeclaresJSONFlag(s) { + return + } + if cmd.Flags().Lookup("format") == nil { + return + } + if !shortcutFormatSupportsJSON(s) { + return + } + // Safety net: pflag panics on duplicate registration. + if cmd.Flags().Lookup("json") != nil { + return + } + cmd.Flags().Bool("json", false, "shorthand for --format json") +} + func registerShortcutFlagsWithContext(ctx context.Context, cmd *cobra.Command, f *cmdutil.Factory, s *Shortcut) { for _, fl := range s.Flags { desc := fl.Desc @@ -1234,10 +1285,8 @@ func registerShortcutFlagsWithContext(ctx context.Context, cmd *cobra.Command, f cmdutil.RegisterFlagCompletion(cmd, "format", func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { return []string{"json", "pretty", "table", "ndjson", "csv"}, cobra.ShellCompDirectiveNoFileComp }) - if cmd.Flags().Lookup("json") == nil { - cmd.Flags().Bool("json", false, "shorthand for --format json") - } } + ensureJSONShorthand(cmd, s) if s.Risk == "high-risk-write" { cmd.Flags().Bool("yes", false, "confirm high-risk operation") } diff --git a/shortcuts/common/runner_json_shorthand_test.go b/shortcuts/common/runner_json_shorthand_test.go new file mode 100644 index 000000000..34239cf52 --- /dev/null +++ b/shortcuts/common/runner_json_shorthand_test.go @@ -0,0 +1,104 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package common + +import ( + "context" + "testing" + + "github.com/spf13/cobra" + + "github.com/larksuite/cli/internal/cmdutil" +) + +const jsonShorthandUsage = "shorthand for --format json" + +func mountTestShortcut(t *testing.T, s Shortcut) *cobra.Command { + t.Helper() + f, _, _, _ := cmdutil.TestFactory(t, nil) + parent := &cobra.Command{Use: "root"} + s.Mount(parent, f) + cmd, _, err := parent.Find([]string{s.Command}) + if err != nil { + t.Fatalf("Find() error = %v", err) + } + return cmd +} + +// 自定义 format 且 Enum 含 json → 注册简写(本次修复的核心行为) +func TestJSONShorthand_CustomFormatWithJSONEnum_Registered(t *testing.T) { + cmd := mountTestShortcut(t, Shortcut{ + Service: "mail", Command: "+fake-triage", Description: "x", + Flags: []Flag{{Name: "format", Default: "table", Enum: []string{"table", "json", "data"}, Desc: "fmt"}}, + Execute: func(context.Context, *RuntimeContext) error { return nil }, + }) + fl := cmd.Flags().Lookup("json") + if fl == nil { + t.Fatal("--json not registered for custom-format shortcut whose Enum contains json") + } + if fl.Usage != jsonShorthandUsage { + t.Errorf("usage = %q, want %q", fl.Usage, jsonShorthandUsage) + } + // 默认输出格式不被改变 + if def := cmd.Flags().Lookup("format").DefValue; def != "table" { + t.Errorf("format default = %q, want table", def) + } +} + +// 自定义 format 但 Enum 不含 json → 不注册 +func TestJSONShorthand_CustomFormatWithoutJSONEnum_NotRegistered(t *testing.T) { + cmd := mountTestShortcut(t, Shortcut{ + Service: "x", Command: "+no-json", Description: "x", + Flags: []Flag{{Name: "format", Default: "csv", Enum: []string{"csv", "table"}, Desc: "fmt"}}, + Execute: func(context.Context, *RuntimeContext) error { return nil }, + }) + if cmd.Flags().Lookup("json") != nil { + t.Fatal("--json must NOT be registered when format Enum lacks json") + } +} + +// 自定义 format 但无 Enum(现状 triage 形态)→ 不注册(Enum 是判定依据) +func TestJSONShorthand_CustomFormatNoEnum_NotRegistered(t *testing.T) { + cmd := mountTestShortcut(t, Shortcut{ + Service: "x", Command: "+legacy", Description: "x", + Flags: []Flag{{Name: "format", Default: "table", Desc: "fmt"}}, + Execute: func(context.Context, *RuntimeContext) error { return nil }, + }) + if cmd.Flags().Lookup("json") != nil { + t.Fatal("--json must NOT be registered when format has no Enum metadata") + } +} + +// 自声明 json flag(subscribe 的 pretty / record-search 的请求体)→ 不覆盖、不 panic、语义保留 +func TestJSONShorthand_SelfDeclaredJSON_Preserved(t *testing.T) { + cmd := mountTestShortcut(t, Shortcut{ + Service: "event", Command: "+fake-subscribe", Description: "x", + Flags: []Flag{ + {Name: "json", Type: "bool", Desc: "pretty-print JSON instead of NDJSON"}, + }, + Execute: func(context.Context, *RuntimeContext) error { return nil }, + }) + fl := cmd.Flags().Lookup("json") + if fl == nil { + t.Fatal("self-declared --json missing") + } + if fl.Usage != "pretty-print JSON instead of NDJSON" { + t.Errorf("self-declared --json usage overwritten: %q", fl.Usage) + } +} + +// 无自定义 format(普通命令)→ 注入默认 format + 简写(现状回归) +func TestJSONShorthand_DefaultInjectedFormat_StillRegistered(t *testing.T) { + cmd := mountTestShortcut(t, Shortcut{ + Service: "im", Command: "+plain", Description: "x", + Execute: func(context.Context, *RuntimeContext) error { return nil }, + }) + fl := cmd.Flags().Lookup("json") + if fl == nil { + t.Fatal("--json missing on default-format shortcut (regression)") + } + if fl.Usage != jsonShorthandUsage { + t.Errorf("usage = %q, want %q", fl.Usage, jsonShorthandUsage) + } +}