Skip to content

Commit 3bb2ab7

Browse files
committed
feat(manifest): add 'list' command to display all flags in the manifest
1 parent 558c203 commit 3bb2ab7

File tree

6 files changed

+541
-2
lines changed

6 files changed

+541
-2
lines changed

internal/cmd/manifest.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ func GetManifestCmd() *cobra.Command {
2020

2121
// Add subcommands
2222
manifestCmd.AddCommand(GetManifestAddCmd())
23+
manifestCmd.AddCommand(GetManifestListCmd())
2324

2425
addStabilityInfo(manifestCmd)
2526

internal/cmd/manifest_add.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ Examples:
105105
logger.Default.Debug(fmt.Sprintf("Added flag: name=%s, type=%s, defaultValue=%v, description=%s",
106106
flagName, flagType, defaultValue, description))
107107

108+
// Display all current flags
109+
displayFlagList(fs, manifestPath)
110+
pterm.Println("Use the 'generate' command to update type-safe clients with the new flag.")
111+
pterm.Println()
112+
108113
return nil
109114
},
110115
}
@@ -169,4 +174,4 @@ func parseDefaultValue(value string, flagType flagset.FlagType) (interface{}, er
169174
default:
170175
return nil, fmt.Errorf("unsupported flag type: %v", flagType)
171176
}
172-
}
177+
}

internal/cmd/manifest_add_test.go

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package cmd
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"testing"
67

78
"github.com/open-feature/cli/internal/config"
89
"github.com/open-feature/cli/internal/filesystem"
10+
"github.com/pterm/pterm"
911
"github.com/spf13/afero"
1012
"github.com/stretchr/testify/assert"
1113
"github.com/stretchr/testify/require"
@@ -360,4 +362,68 @@ func TestManifestAddCmd_CreateNewManifest(t *testing.T) {
360362
assert.Equal(t, "boolean", flag["flagType"])
361363
assert.Equal(t, true, flag["defaultValue"])
362364
assert.Equal(t, "The first flag in a new manifest", flag["description"])
363-
}
365+
}
366+
367+
func TestManifestAddCmd_DisplaysListAfterAdd(t *testing.T) {
368+
// Setup
369+
fs := afero.NewMemMapFs()
370+
filesystem.SetFileSystem(fs)
371+
372+
// Create existing manifest with one flag
373+
existingManifest := `{
374+
"$schema": "https://raw.githubusercontent.com/open-feature/cli/refs/heads/main/schema/v0/flag-manifest.json",
375+
"flags": {
376+
"existing-flag": {
377+
"flagType": "string",
378+
"defaultValue": "test",
379+
"description": "An existing flag"
380+
}
381+
}
382+
}`
383+
err := afero.WriteFile(fs, "flags.json", []byte(existingManifest), 0644)
384+
require.NoError(t, err)
385+
386+
// Enable pterm output and capture it
387+
pterm.EnableOutput()
388+
defer pterm.DisableOutput()
389+
390+
buf := &bytes.Buffer{}
391+
oldStdout := pterm.DefaultTable.Writer
392+
oldSection := pterm.DefaultSection.Writer
393+
oldInfo := pterm.Info.Writer
394+
oldSuccess := pterm.Success.Writer
395+
pterm.DefaultTable.Writer = buf
396+
pterm.DefaultSection.Writer = buf
397+
pterm.Info.Writer = buf
398+
pterm.Success.Writer = buf
399+
defer func() {
400+
pterm.DefaultTable.Writer = oldStdout
401+
pterm.DefaultSection.Writer = oldSection
402+
pterm.Info.Writer = oldInfo
403+
pterm.Success.Writer = oldSuccess
404+
}()
405+
406+
// Create command and execute
407+
cmd := GetManifestCmd()
408+
config.AddRootFlags(cmd)
409+
410+
cmd.SetArgs([]string{
411+
"add", "new-flag",
412+
"--default-value", "true",
413+
"--description", "A new flag",
414+
"-m", "flags.json",
415+
})
416+
417+
// Execute command
418+
err = cmd.Execute()
419+
require.NoError(t, err)
420+
421+
// Validate output contains list of all flags
422+
output := buf.String()
423+
assert.Contains(t, output, "existing-flag", "Output should contain existing flag")
424+
assert.Contains(t, output, "new-flag", "Output should contain newly added flag")
425+
assert.Contains(t, output, "(2)", "Output should show total count of 2 flags")
426+
assert.Contains(t, output, "string", "Output should show flag types")
427+
assert.Contains(t, output, "boolean", "Output should show flag types")
428+
}
429+

internal/cmd/manifest_list.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package cmd
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/open-feature/cli/internal/config"
8+
"github.com/open-feature/cli/internal/flagset"
9+
"github.com/open-feature/cli/internal/manifest"
10+
"github.com/pterm/pterm"
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func GetManifestListCmd() *cobra.Command {
15+
manifestListCmd := &cobra.Command{
16+
Use: "list",
17+
Short: "List all flags in the manifest",
18+
Long: `Display all flags defined in the manifest file with their configuration.`,
19+
Args: cobra.NoArgs,
20+
PreRunE: func(cmd *cobra.Command, args []string) error {
21+
return initializeConfig(cmd, "manifest.list")
22+
},
23+
RunE: func(cmd *cobra.Command, args []string) error {
24+
manifestPath := config.GetManifestPath(cmd)
25+
26+
// Load existing manifest
27+
fs, err := manifest.LoadFlagSet(manifestPath)
28+
if err != nil {
29+
return fmt.Errorf("failed to load manifest: %w", err)
30+
}
31+
32+
displayFlagList(fs, manifestPath)
33+
return nil
34+
},
35+
}
36+
37+
// Add command-specific flags
38+
config.AddManifestListFlags(manifestListCmd)
39+
addStabilityInfo(manifestListCmd)
40+
41+
return manifestListCmd
42+
}
43+
44+
// displayFlagList prints a formatted table of all flags in the flagset
45+
func displayFlagList(fs *flagset.Flagset, manifestPath string) {
46+
if len(fs.Flags) == 0 {
47+
pterm.Info.Println("No flags found in manifest")
48+
return
49+
}
50+
51+
// Print header
52+
pterm.DefaultSection.Println(fmt.Sprintf("Flags in %s (%d)", manifestPath, len(fs.Flags)))
53+
54+
// Create table data
55+
tableData := pterm.TableData{
56+
{"Key", "Type", "Default Value", "Description"},
57+
}
58+
59+
for _, flag := range fs.Flags {
60+
// Format default value for display
61+
defaultValueStr := formatValue(flag.DefaultValue)
62+
63+
// Truncate description if too long
64+
description := flag.Description
65+
if len(description) > 50 {
66+
description = description[:47] + "..."
67+
}
68+
69+
tableData = append(tableData, []string{
70+
flag.Key,
71+
flag.Type.String(),
72+
defaultValueStr,
73+
description,
74+
})
75+
}
76+
77+
// Render table
78+
pterm.DefaultTable.WithHasHeader().WithData(tableData).Render()
79+
}
80+
81+
// formatValue converts a value to a string representation suitable for display
82+
func formatValue(value interface{}) string {
83+
switch v := value.(type) {
84+
case string:
85+
if len(v) > 30 {
86+
return fmt.Sprintf("\"%s...\"", v[:27])
87+
}
88+
return fmt.Sprintf("\"%s\"", v)
89+
case bool, int, float64:
90+
return fmt.Sprintf("%v", v)
91+
case map[string]interface{}, []interface{}:
92+
jsonBytes, err := json.Marshal(v)
93+
if err != nil {
94+
return fmt.Sprintf("%v", v)
95+
}
96+
jsonStr := string(jsonBytes)
97+
if len(jsonStr) > 30 {
98+
return jsonStr[:27] + "..."
99+
}
100+
return jsonStr
101+
default:
102+
return fmt.Sprintf("%v", v)
103+
}
104+
}

0 commit comments

Comments
 (0)