Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*.so
*.dylib
cli
bin/

# Test binary, built with `go test -c`
*.test
Expand Down
20 changes: 19 additions & 1 deletion docs/commands/openfeature_manifest_add.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,22 @@ Add a new flag to the manifest

Add a new flag to the manifest file with the specified configuration.

Interactive Mode:
When the flag key or other values are omitted, the command prompts interactively for missing values:
- Flag key (if not provided as argument)
- Flag type (defaults to boolean if not specified)
- Default value (required)
- Description (optional, press Enter to skip)

Use --no-input to disable interactive prompts (required for CI/automation).

Examples:
# Interactive mode - prompts for key, type, value, and description
openfeature manifest add

# Interactive mode with key - prompts for type, value, and description
openfeature manifest add new-feature

# Add a boolean flag (default type)
openfeature manifest add new-feature --default-value false

Expand All @@ -23,9 +38,12 @@ Examples:

# Add an object flag
openfeature manifest add config --type object --default-value '{"key":"value"}'

# Disable interactive prompts (for automation)
openfeature manifest add my-flag --default-value true --no-input

```
openfeature manifest add [flag-name] [flags]
openfeature manifest add [flag-key] [flags]
```

### Options
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/spf13/viper v1.20.1
github.com/stretchr/testify v1.11.1
github.com/xeipuuv/gojsonschema v1.2.0
golang.org/x/term v0.35.0
golang.org/x/text v0.29.0
gopkg.in/yaml.v3 v3.0.1
)
Expand Down Expand Up @@ -82,7 +83,6 @@ require (
golang.org/x/net v0.44.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/term v0.35.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect
google.golang.org/grpc v1.75.1 // indirect
Expand Down
118 changes: 102 additions & 16 deletions internal/cmd/manifest_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,26 @@ import (

func GetManifestAddCmd() *cobra.Command {
manifestAddCmd := &cobra.Command{
Use: "add [flag-name]",
Use: "add [flag-key]",
Short: "Add a new flag to the manifest",
Long: `Add a new flag to the manifest file with the specified configuration.

Interactive Mode:
When the flag key or other values are omitted, the command prompts interactively for missing values:
- Flag key (if not provided as argument)
- Flag type (defaults to boolean if not specified)
- Default value (required)
- Description (optional, press Enter to skip)

Use --no-input to disable interactive prompts (required for CI/automation).

Examples:
# Interactive mode - prompts for key, type, value, and description
openfeature manifest add

# Interactive mode with key - prompts for type, value, and description
openfeature manifest add new-feature

# Add a boolean flag (default type)
openfeature manifest add new-feature --default-value false

Expand All @@ -37,23 +52,55 @@ Examples:
openfeature manifest add discount-rate --type float --default-value 0.15

# Add an object flag
openfeature manifest add config --type object --default-value '{"key":"value"}'`,
Args: cobra.ExactArgs(1),
openfeature manifest add config --type object --default-value '{"key":"value"}'

# Disable interactive prompts (for automation)
openfeature manifest add my-flag --default-value true --no-input`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) > 1 {
return fmt.Errorf("too many arguments: expected 0 or 1 flag-key, got %d\n\nUsage: %s", len(args), cmd.Use)
}
return nil
},
PreRunE: func(cmd *cobra.Command, args []string) error {
return initializeConfig(cmd, "manifest.add")
},
RunE: func(cmd *cobra.Command, args []string) error {
flagName := args[0]
manifestPath := config.GetManifestPath(cmd)
noInput := config.ShouldDisableInteractivePrompts(cmd)

// Handle flag key: use argument if provided, otherwise prompt if interactive mode
var flagName string
if len(args) > 0 {
flagName = args[0]
} else {
if noInput {
return errors.New("flag-key argument is required when --no-input is set")
}
// Prompt for flag key
promptText := "Enter flag key (e.g., 'my-feature', 'enable-new-ui')"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@beeme1mr this is a good idea, the prompting will help us in demo, we won't need to remember the cli flags/args as much

keyInput, err := pterm.DefaultInteractiveTextInput.WithDefaultText("").Show(promptText)
if err != nil {
return fmt.Errorf("failed to prompt for flag key: %w", err)
}
flagName = strings.TrimSpace(keyInput)
if flagName == "" {
return errors.New("flag key cannot be empty")
}
}

// Get flag configuration from command flags
flagType, _ := cmd.Flags().GetString("type")
defaultValueStr, _ := cmd.Flags().GetString("default-value")
description, _ := cmd.Flags().GetString("description")

// Validate that default-value is provided
if !cmd.Flags().Changed("default-value") {
return errors.New("--default-value is required")
// Handle flag type: prompt if not changed and not --no-input
if !cmd.Flags().Changed("type") && !noInput {
selectedType, err := promptForFlagType(flagName)
if err != nil {
return fmt.Errorf("failed to get flag type: %w", err)
}
flagType = selectedType
}

// Parse flag type
Expand All @@ -62,10 +109,36 @@ Examples:
return fmt.Errorf("invalid flag type: %w", err)
}

// Parse and validate default value
defaultValue, err := parseDefaultValue(defaultValueStr, parsedType)
if err != nil {
return fmt.Errorf("invalid default value for type %s: %w", flagType, err)
// Handle default-value: prompt if missing and not --no-input
var defaultValue interface{}
if !cmd.Flags().Changed("default-value") {
if noInput {
return errors.New("--default-value is required")
}
// Prompt for default value
defaultValue, err = promptForDefaultValue(&flagset.Flag{
Key: flagName,
Type: parsedType,
})
if err != nil {
return fmt.Errorf("failed to get default value: %w", err)
}
} else {
// Parse and validate default value from flag
defaultValue, err = parseDefaultValue(defaultValueStr, parsedType)
if err != nil {
return fmt.Errorf("invalid default value for type %s: %w", flagType, err)
}
}

// Handle description: prompt if missing and not --no-input
if !cmd.Flags().Changed("description") && !noInput {
promptText := fmt.Sprintf("Enter description for flag '%s' (press Enter to skip)", flagName)
descInput, err := pterm.DefaultInteractiveTextInput.WithDefaultText("").Show(promptText)
if err != nil {
return fmt.Errorf("failed to prompt for description: %w", err)
}
description = descInput
}

// Load existing manifest
Expand Down Expand Up @@ -114,11 +187,6 @@ Examples:
logger.Default.Debug(fmt.Sprintf("Added flag: name=%s, type=%s, defaultValue=%v, description=%s",
flagName, flagType, defaultValue, description))

// Display all current flags
displayFlagList(fs, manifestPath)
pterm.Println("Use the 'generate' command to update type-safe clients with the new flag.")
pterm.Println()

return nil
},
}
Expand Down Expand Up @@ -184,3 +252,21 @@ func parseDefaultValue(value string, flagType flagset.FlagType) (interface{}, er
return nil, fmt.Errorf("unsupported flag type: %v", flagType)
}
}

// promptForFlagType prompts the user to select a flag type
func promptForFlagType(flagName string) (string, error) {
prompt := fmt.Sprintf("Select type for flag '%s'", flagName)
options := []string{"boolean", "string", "integer", "float", "object"}

selectedType, err := pterm.DefaultInteractiveSelect.
WithOptions(options).
WithDefaultOption("boolean").
WithFilter(false).
Show(prompt)

if err != nil {
return "", fmt.Errorf("failed to prompt for flag type: %w", err)
}

return selectedType, nil
}
Loading
Loading