From 7584b4a14f1de28c008d13678d101f02828b7f81 Mon Sep 17 00:00:00 2001 From: johnnyzwu Date: Tue, 14 Jul 2026 19:14:22 +0800 Subject: [PATCH] fix(cli): keep help and schema examples consistent --- internal/cli/examples.go | 30 +++++++++++++++++ internal/cli/schema.go | 12 +++++-- internal/cli/schema_test.go | 67 +++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/internal/cli/examples.go b/internal/cli/examples.go index d66ff20..fd66b7a 100644 --- a/internal/cli/examples.go +++ b/internal/cli/examples.go @@ -21,6 +21,36 @@ func exampleBlocks(examples ...string) string { return strings.Join(blocks, "\n\n") } +func exampleCommands(blocks string) []string { + var examples []string + var lines []string + inExample := false + flush := func() { + example := strings.TrimSpace(strings.Join(lines, "\n")) + if example != "" { + examples = append(examples, example) + } + lines = nil + } + + for _, line := range strings.Split(blocks, "\n") { + if strings.HasPrefix(strings.TrimSpace(line), "Example - ") { + if inExample { + flush() + } + inExample = true + continue + } + if inExample { + lines = append(lines, strings.TrimPrefix(line, " ")) + } + } + if inExample { + flush() + } + return examples +} + func builtinExampleTitle(index int) string { switch index { case 0: diff --git a/internal/cli/schema.go b/internal/cli/schema.go index 0f5bda9..ba56f82 100644 --- a/internal/cli/schema.go +++ b/internal/cli/schema.go @@ -277,6 +277,7 @@ func getAllSchemas() []CommandSchema { } func buildSchemaCatalog(root *cobra.Command) schemaCatalog { + root.InitDefaultHelpCmd() schemas := getAllSchemas() seededByName := map[string]CommandSchema{} for _, schema := range schemas { @@ -285,7 +286,7 @@ func buildSchemaCatalog(root *cobra.Command) schemaCatalog { catalog := schemaCatalog{byName: map[string]CommandSchema{}} seen := map[string]bool{} - walkPublicCommands(root, func(cmd *cobra.Command) { + addCommand := func(cmd *cobra.Command) { commandID := canonicalCommandID(cmd) if commandID == "" || seen[commandID] { return @@ -301,6 +302,9 @@ func buildSchemaCatalog(root *cobra.Command) schemaCatalog { schema.Summary = cmd.Short } schema.Aliases = append([]string(nil), cmd.Aliases...) + if examples := exampleCommands(cmd.Example); len(examples) > 0 { + schema.Examples = examples + } schema.Subcommands = publicSubcommandIDs(cmd) if len(schema.Subcommands) > 0 { schema.Kind = "group" @@ -311,7 +315,11 @@ func buildSchemaCatalog(root *cobra.Command) schemaCatalog { schema = finalizeSchema(schema) catalog.Ordered = append(catalog.Ordered, schema) catalog.byName[schema.Name] = schema - }) + } + walkPublicCommands(root, addCommand) + if helpCmd, _, err := root.Find([]string{"help"}); err == nil && helpCmd != root { + addCommand(helpCmd) + } for _, schema := range schemas { if seen[schema.Name] { diff --git a/internal/cli/schema_test.go b/internal/cli/schema_test.go index 2ea8a71..046ce86 100644 --- a/internal/cli/schema_test.go +++ b/internal/cli/schema_test.go @@ -1,11 +1,78 @@ package cli import ( + "reflect" + "strings" "testing" "github.com/TencentCloudAgentRuntime/ags-cli/internal/command" + "github.com/spf13/cobra" ) +func TestBuildSchemaCatalogUsesRenderedCommandExamples(t *testing.T) { + root := &cobra.Command{Use: "agr"} + root.AddCommand(&cobra.Command{ + Use: "show", + Short: "Show a resource", + Run: func(*cobra.Command, []string) {}, + Example: exampleBlocks( + "agr show resource-1", + "agr show resource-1 -o json\nagr show resource-2 -o json", + ), + }) + + schema, ok := buildSchemaCatalog(root).Lookup(root, "show") + if !ok { + t.Fatal("show schema not found") + } + want := []string{ + "agr show resource-1", + "agr show resource-1 -o json\nagr show resource-2 -o json", + } + if !reflect.DeepEqual(schema.Examples, want) { + t.Fatalf("Examples = %#v, want %#v", schema.Examples, want) + } +} + +func TestSchemaCatalogLeafExamplesMatchHelp(t *testing.T) { + root := RootCmd() + catalog := buildSchemaCatalog(root) + helpSchema, ok := catalog.Lookup(root, "help") + if !ok { + t.Fatal("help schema not found") + } + wantHelpExamples := []string{"agr help instance create", "agr help schema"} + if !reflect.DeepEqual(helpSchema.Examples, wantHelpExamples) { + t.Fatalf("help Examples = %#v, want %#v", helpSchema.Examples, wantHelpExamples) + } + + walkPublicCommands(root, func(cmd *cobra.Command) { + if cmd.HasAvailableSubCommands() { + return + } + commandID := canonicalCommandID(cmd) + schema, ok := catalog.Lookup(root, commandID) + if !ok { + t.Errorf("schema for %s not found", commandID) + return + } + + wantCount := strings.Count(cmd.Example, "Example - ") + if len(schema.Examples) != wantCount { + t.Errorf("%s schema has %d examples, help has %d", commandID, len(schema.Examples), wantCount) + return + } + for _, example := range schema.Examples { + for _, line := range strings.Split(example, "\n") { + line = strings.TrimSpace(line) + if line != "" && !strings.Contains(cmd.Example, line) { + t.Errorf("%s schema example line %q is missing from help", commandID, line) + } + } + } + }) +} + func TestSchemaFromDescriptorInfersEffects(t *testing.T) { tests := []struct { name string