Skip to content
Closed
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
42 changes: 42 additions & 0 deletions cmd/compare.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cmd

import (
"fmt"

"github.com/open-feature/cli/internal/manifest"
"github.com/spf13/cobra"
)

func GetCompareCmd() *cobra.Command {
return &cobra.Command{
Use: "compare",
Short: "Compare two manifest files",
Long: `Compare two manifest files and list the changes`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 2 {
return fmt.Errorf("please provide two manifest files to compare")
}

oldManifest, err := manifest.Load(args[0])
if err != nil {
return fmt.Errorf("failed to load old manifest: %w", err)
}

newManifest, err := manifest.Load(args[1])
if err != nil {
return fmt.Errorf("failed to load new manifest: %w", err)
}

changes, err := manifest.Compare(oldManifest, newManifest)
if err != nil {
return fmt.Errorf("failed to compare manifests: %w", err)
}

for _, change := range changes {
fmt.Printf("%s: %s\n", change.Type, change.Path)
}

return nil
},
}
}
7 changes: 4 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func Execute(version string, commit string, date string) {

func GetRootCmd() *cobra.Command {
// Execute all parent's persistent hooks
cobra.EnableTraverseRunHooks =true
cobra.EnableTraverseRunHooks = true

rootCmd := &cobra.Command{
Use: "openfeature",
Expand All @@ -46,8 +46,8 @@ func GetRootCmd() *cobra.Command {

return nil
},
SilenceErrors: true,
SilenceUsage: true,
SilenceErrors: true,
SilenceUsage: true,
DisableSuggestions: false,
SuggestionsMinimumDistance: 2,
DisableAutoGenTag: true,
Expand All @@ -60,6 +60,7 @@ func GetRootCmd() *cobra.Command {
rootCmd.AddCommand(GetVersionCmd())
rootCmd.AddCommand(GetInitCmd())
rootCmd.AddCommand(GetGenerateCmd())
rootCmd.AddCommand(GetCompareCmd())

// Add a custom error handler after the command is created
rootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error {
Expand Down
65 changes: 65 additions & 0 deletions internal/manifest/compare_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package manifest

import (
"reflect"
"sort"
"testing"
)

func TestCompareDifferentManifests(t *testing.T) {
oldManifest := &Manifest{
Flags: map[string]any{
"flag1": "value1",
"flag2": "value2",
},
}

newManifest := &Manifest{
Flags: map[string]any{
"flag1": "value1",
"flag2": "newValue2",
"flag3": "value3",
},
}

changes, err := Compare(oldManifest, newManifest)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

expectedChanges := []Change{
{Type: "change", Path: "flags.flag2", OldValue: "value2", NewValue: "newValue2"},
{Type: "add", Path: "flags.flag3", NewValue: "value3"},
}

sortChanges(changes)
sortChanges(expectedChanges)

if !reflect.DeepEqual(changes, expectedChanges) {
t.Errorf("expected %v, got %v", expectedChanges, changes)
}
}

func TestCompareIdenticalManifests(t *testing.T) {
manifest := &Manifest{
Flags: map[string]any{
"flag1": "value1",
"flag2": "value2",
},
}

changes, err := Compare(manifest, manifest)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if len(changes) != 0 {
t.Errorf("expected no changes, got %v", changes)
}
}

func sortChanges(changes []Change) {
sort.Slice(changes, func(i, j int) bool {
return changes[i].Path < changes[j].Path
})
}
19 changes: 18 additions & 1 deletion internal/manifest/manage.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"

"github.com/open-feature/cli/internal/filesystem"
"github.com/spf13/afero"
)

type initManifest struct {
Expand All @@ -14,7 +15,7 @@ type initManifest struct {
// Create creates a new manifest file at the given path.
func Create(path string) error {
m := &initManifest{
Schema: "https://raw.githubusercontent.com/open-feature/cli/refs/heads/main/schema/v0/flag_manifest.json",
Schema: "https://raw.githubusercontent.com/open-feature/cli/refs/heads/main/schema/v0/flag_manifest.json",
Manifest: Manifest{
Flags: map[string]any{},
},
Expand All @@ -25,3 +26,19 @@ func Create(path string) error {
}
return filesystem.WriteFile(path, formattedInitManifest)
}

// Load loads a manifest from a JSON file, unmarshals it, and returns a Manifest object.
func Load(path string) (*Manifest, error) {
fs := filesystem.FileSystem()
data, err := afero.ReadFile(fs, path)
if err != nil {
return nil, err
}

var m Manifest
if err := json.Unmarshal(data, &m); err != nil {
return nil, err
}

return &m, nil
}