Skip to content
Open
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 docs/commands/openfeature.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ openfeature [flags]
* [openfeature compare](openfeature_compare.md) - Compare two feature flag manifests
* [openfeature generate](openfeature_generate.md) - Generate typesafe OpenFeature accessors.
* [openfeature init](openfeature_init.md) - Initialize a new project
* [openfeature manifest](openfeature_manifest.md) - Manage flag manifest files
* [openfeature pull](openfeature_pull.md) - Pull a flag manifest from a remote source
* [openfeature version](openfeature_version.md) - Print the version number of the OpenFeature CLI

34 changes: 34 additions & 0 deletions docs/commands/openfeature_manifest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!-- markdownlint-disable-file -->
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
## openfeature manifest

Manage flag manifest files

### Synopsis

Commands for managing OpenFeature flag manifest files.

```
openfeature manifest [flags]
```

### Options

```
-h, --help help for manifest
```

### Options inherited from parent commands

```
--debug Enable debug logging
-m, --manifest string Path to the flag manifest (default "flags.json")
--no-input Disable interactive prompts
```

### SEE ALSO

* [openfeature](openfeature.md) - CLI for OpenFeature.
* [openfeature manifest add](openfeature_manifest_add.md) - Add a new flag to the manifest
* [openfeature manifest list](openfeature_manifest_list.md) - List all flags in the manifest

51 changes: 51 additions & 0 deletions docs/commands/openfeature_manifest_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!-- markdownlint-disable-file -->
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
## openfeature manifest add

Add a new flag to the manifest

### Synopsis

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

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

# Add a string flag with description
openfeature manifest add welcome-message --type string --default-value "Hello!" --description "Welcome message for users"

# Add an integer flag
openfeature manifest add max-retries --type integer --default-value 3

# Add a float flag
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"}'

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

### Options

```
-d, --default-value string Default value for the flag (required)
--description string Description of the flag
-h, --help help for add
-t, --type string Type of the flag (boolean, string, integer, float, object) (default "boolean")
```

### Options inherited from parent commands

```
--debug Enable debug logging
-m, --manifest string Path to the flag manifest (default "flags.json")
--no-input Disable interactive prompts
```

### SEE ALSO

* [openfeature manifest](openfeature_manifest.md) - Manage flag manifest files

32 changes: 32 additions & 0 deletions docs/commands/openfeature_manifest_list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!-- markdownlint-disable-file -->
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
## openfeature manifest list

List all flags in the manifest

### Synopsis

Display all flags defined in the manifest file with their configuration.

```
openfeature manifest list [flags]
```

### Options

```
-h, --help help for list
```

### Options inherited from parent commands

```
--debug Enable debug logging
-m, --manifest string Path to the flag manifest (default "flags.json")
--no-input Disable interactive prompts
```

### SEE ALSO

* [openfeature manifest](openfeature_manifest.md) - Manage flag manifest files

28 changes: 28 additions & 0 deletions internal/cmd/manifest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cmd

import (
"github.com/spf13/cobra"
)

func GetManifestCmd() *cobra.Command {
manifestCmd := &cobra.Command{
Use: "manifest",
Short: "Manage flag manifest files",
Long: `Commands for managing OpenFeature flag manifest files.`,
RunE: func(cmd *cobra.Command, args []string) error {
return cmd.Help()
},
SilenceErrors: true,
SilenceUsage: true,
DisableSuggestions: false,
SuggestionsMinimumDistance: 2,
}

// Add subcommands
manifestCmd.AddCommand(GetManifestAddCmd())
manifestCmd.AddCommand(GetManifestListCmd())

addStabilityInfo(manifestCmd)

return manifestCmd
}
186 changes: 186 additions & 0 deletions internal/cmd/manifest_add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package cmd

import (
"encoding/json"
"errors"
"fmt"
"strconv"
"strings"

"github.com/open-feature/cli/internal/config"
"github.com/open-feature/cli/internal/filesystem"
"github.com/open-feature/cli/internal/flagset"
"github.com/open-feature/cli/internal/logger"
"github.com/open-feature/cli/internal/manifest"
"github.com/pterm/pterm"
"github.com/spf13/afero"
"github.com/spf13/cobra"
)

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

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

# Add a string flag with description
openfeature manifest add welcome-message --type string --default-value "Hello!" --description "Welcome message for users"

# Add an integer flag
openfeature manifest add max-retries --type integer --default-value 3

# Add a float flag
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),
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)

// 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")
}

// Parse flag type
parsedType, err := parseFlagTypeString(flagType)
if err != nil {
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)
}

// Load existing manifest
var fs *flagset.Flagset
exists, err := afero.Exists(filesystem.FileSystem(), manifestPath)

if err != nil {
return fmt.Errorf("failed to check manifest existence: %w", err)
}

if exists {
fs, err = manifest.LoadFlagSet(manifestPath)
if err != nil {
return fmt.Errorf("failed to load manifest: %w", err)
}
} else {
// If manifest doesn't exist, create a new one
fs = &flagset.Flagset{
Flags: []flagset.Flag{},
}
}

// Check if flag already exists
for _, flag := range fs.Flags {
if flag.Key == flagName {
return fmt.Errorf("flag '%s' already exists in the manifest", flagName)
}
}

// Add new flag
newFlag := flagset.Flag{
Key: flagName,
Type: parsedType,
Description: description,
DefaultValue: defaultValue,
}
fs.Flags = append(fs.Flags, newFlag)

// Write updated manifest
if err := manifest.Write(manifestPath, *fs); err != nil {
return fmt.Errorf("failed to write manifest: %w", err)
}

// Success message
pterm.Success.Printfln("Flag '%s' added successfully to %s", flagName, manifestPath)
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)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we should show the list after adding a new flag since this list may be quite large.

Copy link
Author

Choose a reason for hiding this comment

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

Didn't think of that. But, yeah, I can see it being very annoying if the list is long. I will address that.

pterm.Println("Use the 'generate' command to update type-safe clients with the new flag.")
pterm.Println()

return nil
},
}

// Add command-specific flags
config.AddManifestAddFlags(manifestAddCmd)
addStabilityInfo(manifestAddCmd)

return manifestAddCmd
}

// parseFlagTypeString converts a string flag type to FlagType enum
func parseFlagTypeString(typeStr string) (flagset.FlagType, error) {
switch strings.ToLower(typeStr) {
case "boolean", "bool":
return flagset.BoolType, nil
case "string":
return flagset.StringType, nil
case "integer", "int":
return flagset.IntType, nil
case "float", "number":
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if "number" should be listed here since a number could easily be a float or an integer.

Copy link
Author

Choose a reason for hiding this comment

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

Thanks! You're right. It can be a bit confusing. I will remove number from here.

return flagset.FloatType, nil
case "object", "json":
return flagset.ObjectType, nil
default:
return flagset.UnknownFlagType, fmt.Errorf("unknown flag type: %s", typeStr)
}
}

// parseDefaultValue parses and validates the default value based on flag type
func parseDefaultValue(value string, flagType flagset.FlagType) (interface{}, error) {
switch flagType {
case flagset.BoolType:
switch strings.ToLower(value) {
case "true":
return true, nil
case "false":
return false, nil
default:
return nil, fmt.Errorf("boolean value must be 'true' or 'false', got '%s'", value)
}
case flagset.StringType:
return value, nil
case flagset.IntType:
intVal, err := strconv.Atoi(value)
if err != nil {
return nil, fmt.Errorf("invalid integer value: %s", value)
}
return intVal, nil
case flagset.FloatType:
floatVal, err := strconv.ParseFloat(value, 64)
if err != nil {
return nil, fmt.Errorf("invalid float value: %s", value)
}
return floatVal, nil
case flagset.ObjectType:
var jsonObj interface{}
if err := json.Unmarshal([]byte(value), &jsonObj); err != nil {
return nil, fmt.Errorf("invalid JSON object: %s", err.Error())
}
return jsonObj, nil
default:
return nil, fmt.Errorf("unsupported flag type: %v", flagType)
}
}
Loading
Loading