diff --git a/cmd/config.go b/cmd/config.go index ba635bd..54dc44b 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/open-feature/cli/internal/logger" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -17,6 +18,8 @@ func initializeConfig(cmd *cobra.Command, bindPrefix string) error { // Set the config file name and path v.SetConfigName(".openfeature") v.AddConfigPath(".") + + logger.Default.Debug("Looking for .openfeature config file in current directory") // Read the config file if err := v.ReadInConfig(); err != nil { @@ -24,18 +27,24 @@ func initializeConfig(cmd *cobra.Command, bindPrefix string) error { if _, ok := err.(viper.ConfigFileNotFoundError); !ok { return err } + logger.Default.Debug("No config file found, using defaults and environment variables") + } else { + logger.Default.Debug(fmt.Sprintf("Using config file: %s", v.ConfigFileUsed())) } + // Track which flags were set directly via command line cmdLineFlags := make(map[string]bool) cmd.Flags().Visit(func(f *pflag.Flag) { cmdLineFlags[f.Name] = true + logger.Default.Debug(fmt.Sprintf("Flag set via command line: %s=%s", f.Name, f.Value.String())) }) // Apply the configuration values cmd.Flags().VisitAll(func(f *pflag.Flag) { // Skip if flag was set on command line if cmdLineFlags[f.Name] { + logger.Default.Debug(fmt.Sprintf("Skipping config for %s: already set via command line", f.Name)) return } @@ -57,14 +66,24 @@ func initializeConfig(cmd *cobra.Command, bindPrefix string) error { // Check the base path (e.g., package-name) configPaths = append(configPaths, f.Name) + logger.Default.Debug(fmt.Sprintf("Looking for config value for flag %s in paths: %s", f.Name, strings.Join(configPaths, ", "))) + // Try each path in order until we find a match for _, path := range configPaths { if v.IsSet(path) { val := v.Get(path) - _ = f.Value.Set(fmt.Sprintf("%v", val)) - break + err := f.Value.Set(fmt.Sprintf("%v", val)) + if err != nil { + logger.Default.Debug(fmt.Sprintf("Error setting flag %s from config: %v", f.Name, err)) + } else { + logger.Default.Debug(fmt.Sprintf("Set flag %s=%s from config path %s", f.Name, val, path)) + break + } } } + + // Log the final value for the flag + logger.Default.Debug(fmt.Sprintf("Final flag value: %s=%s", f.Name, f.Value.String())) }) return nil diff --git a/cmd/generate.go b/cmd/generate.go index 6e99631..f62c31f 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -9,6 +9,7 @@ import ( "github.com/open-feature/cli/internal/generators/golang" "github.com/open-feature/cli/internal/generators/nodejs" "github.com/open-feature/cli/internal/generators/react" + "github.com/open-feature/cli/internal/logger" "github.com/spf13/cobra" ) @@ -50,6 +51,8 @@ func GetGenerateNodeJSCmd() *cobra.Command { manifestPath := config.GetManifestPath(cmd) outputPath := config.GetOutputPath(cmd) + logger.Default.GenerationStarted("Node.js") + params := generators.Params[nodejs.Params]{ OutputPath: outputPath, Custom: nodejs.Params{}, @@ -60,10 +63,14 @@ func GetGenerateNodeJSCmd() *cobra.Command { } generator := nodejs.NewGenerator(flagset) + logger.Default.Debug("Executing Node.js generator") err = generator.Generate(¶ms) if err != nil { return err } + + logger.Default.GenerationComplete("Node.js") + return nil }, } @@ -87,6 +94,8 @@ func GetGenerateReactCmd() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { manifestPath := config.GetManifestPath(cmd) outputPath := config.GetOutputPath(cmd) + + logger.Default.GenerationStarted("React") params := generators.Params[react.Params]{ OutputPath: outputPath, @@ -98,10 +107,14 @@ func GetGenerateReactCmd() *cobra.Command { } generator := react.NewGenerator(flagset) + logger.Default.Debug("Executing React generator") err = generator.Generate(¶ms) if err != nil { return err } + + logger.Default.GenerationComplete("React") + return nil }, } @@ -126,6 +139,8 @@ func GetGenerateGoCmd() *cobra.Command { goPackageName := config.GetGoPackageName(cmd) manifestPath := config.GetManifestPath(cmd) outputPath := config.GetOutputPath(cmd) + + logger.Default.GenerationStarted("Go") params := generators.Params[golang.Params]{ OutputPath: outputPath, @@ -140,10 +155,14 @@ func GetGenerateGoCmd() *cobra.Command { } generator := golang.NewGenerator(flagset) + logger.Default.Debug("Executing Go generator") err = generator.Generate(¶ms) if err != nil { return err } + + logger.Default.GenerationComplete("Go") + return nil }, } diff --git a/cmd/init.go b/cmd/init.go index 385cf53..9ca5bf5 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -5,6 +5,7 @@ import ( "github.com/open-feature/cli/internal/config" "github.com/open-feature/cli/internal/filesystem" + "github.com/open-feature/cli/internal/logger" "github.com/open-feature/cli/internal/manifest" "github.com/pterm/pterm" "github.com/spf13/cobra" @@ -24,23 +25,28 @@ func GetInitCmd() *cobra.Command { manifestExists, _ := filesystem.Exists(manifestPath) if manifestExists && !override { + logger.Default.Debug(fmt.Sprintf("Manifest file already exists at %s", manifestPath)) confirmMessage := fmt.Sprintf("An existing manifest was found at %s. Would you like to override it?", manifestPath) shouldOverride, _ := pterm.DefaultInteractiveConfirm.Show(confirmMessage) // Print a blank line for better readability. pterm.Println() if !shouldOverride { - pterm.Info.Println("No changes were made.") + logger.Default.Info("No changes were made.") return nil } + + logger.Default.Debug("User confirmed override of existing manifest") } - pterm.Info.Println("Initializing project...") + logger.Default.Info("Initializing project...") err := manifest.Create(manifestPath) if err != nil { + logger.Default.Error(fmt.Sprintf("Failed to create manifest: %v", err)) return err } - pterm.Info.Printfln("Manifest created at %s", pterm.LightWhite(manifestPath)) - pterm.Success.Println("Project initialized.") + + logger.Default.FileCreated(manifestPath) + logger.Default.Success("Project initialized.") return nil }, } diff --git a/cmd/root.go b/cmd/root.go index 9b38759..993145b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,10 +1,11 @@ package cmd import ( + "fmt" "os" "github.com/open-feature/cli/internal/config" - "github.com/pterm/pterm" + "github.com/open-feature/cli/internal/logger" "github.com/spf13/cobra" ) @@ -22,7 +23,7 @@ func Execute(version string, commit string, date string) { Commit = commit Date = date if err := GetRootCmd().Execute(); err != nil { - pterm.Error.Println(err) + logger.Default.Error(err.Error()) os.Exit(1) } } @@ -36,14 +37,15 @@ func GetRootCmd() *cobra.Command { Short: "CLI for OpenFeature.", Long: `CLI for OpenFeature related functionalities.`, PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + debug, _ := cmd.Flags().GetBool("debug") + logger.Default.SetDebug(debug) + logger.Default.Debug("Debug logging enabled") return initializeConfig(cmd, "") }, RunE: func(cmd *cobra.Command, args []string) error { printBanner() - pterm.Println() - pterm.Println("To see all the options, try 'openfeature --help'") - pterm.Println() - + logger.Default.Println(""); + logger.Default.Println("To see all the options, try 'openfeature --help'") return nil }, SilenceErrors: true, @@ -63,8 +65,8 @@ func GetRootCmd() *cobra.Command { // Add a custom error handler after the command is created rootCmd.SetFlagErrorFunc(func(cmd *cobra.Command, err error) error { - pterm.Error.Printf("Invalid flag: %s", err) - pterm.Println("Run 'openfeature --help' for usage information") + logger.Default.Error(fmt.Sprintf("Invalid flag: %s", err)) + logger.Default.Info("Run 'openfeature --help' for usage information") return err }) diff --git a/cmd/version.go b/cmd/version.go index ade0898..dab3c41 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -4,6 +4,7 @@ import ( "fmt" "runtime/debug" + "github.com/open-feature/cli/internal/logger" "github.com/spf13/cobra" ) @@ -14,21 +15,25 @@ func GetVersionCmd() *cobra.Command { Long: ``, Run: func(cmd *cobra.Command, args []string) { if Version == "dev" { + logger.Default.Debug("Development version detected, attempting to get build info") details, ok := debug.ReadBuildInfo() if ok && details.Main.Version != "" && details.Main.Version != "(devel)" { Version = details.Main.Version for _, i := range details.Settings { if i.Key == "vcs.time" { Date = i.Value + logger.Default.Debug(fmt.Sprintf("Found build date: %s", Date)) } if i.Key == "vcs.revision" { Commit = i.Value + logger.Default.Debug(fmt.Sprintf("Found commit: %s", Commit)) } } } } - fmt.Printf("OpenFeature CLI: %s (%s), built at: %s\n", Version, Commit, Date) + versionInfo := fmt.Sprintf("OpenFeature CLI: %s (%s), built at: %s", Version, Commit, Date) + logger.Default.Info(versionInfo) }, } diff --git a/docs/commands/openfeature.md b/docs/commands/openfeature.md index 2283500..f9c8efc 100644 --- a/docs/commands/openfeature.md +++ b/docs/commands/openfeature.md @@ -15,6 +15,7 @@ openfeature [flags] ### Options ``` + --debug Enable debug logging -h, --help help for openfeature -m, --manifest string Path to the flag manifest (default "flags.json") --no-input Disable interactive prompts diff --git a/docs/commands/openfeature_generate.md b/docs/commands/openfeature_generate.md index e1e4075..99fb3ad 100644 --- a/docs/commands/openfeature_generate.md +++ b/docs/commands/openfeature_generate.md @@ -18,6 +18,7 @@ openfeature generate [flags] ### 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 ``` diff --git a/docs/commands/openfeature_generate_go.md b/docs/commands/openfeature_generate_go.md index ec989e6..e0d5a28 100644 --- a/docs/commands/openfeature_generate_go.md +++ b/docs/commands/openfeature_generate_go.md @@ -25,6 +25,7 @@ openfeature generate go [flags] ### 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 -o, --output string Path to where the generated files should be saved diff --git a/docs/commands/openfeature_generate_nodejs.md b/docs/commands/openfeature_generate_nodejs.md index 2d8b9c3..d4cf458 100644 --- a/docs/commands/openfeature_generate_nodejs.md +++ b/docs/commands/openfeature_generate_nodejs.md @@ -24,6 +24,7 @@ openfeature generate nodejs [flags] ### 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 -o, --output string Path to where the generated files should be saved diff --git a/docs/commands/openfeature_generate_react.md b/docs/commands/openfeature_generate_react.md index ab6a9c2..fc2e6bb 100644 --- a/docs/commands/openfeature_generate_react.md +++ b/docs/commands/openfeature_generate_react.md @@ -24,6 +24,7 @@ openfeature generate react [flags] ### 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 -o, --output string Path to where the generated files should be saved diff --git a/docs/commands/openfeature_init.md b/docs/commands/openfeature_init.md index 5769fb7..22eb382 100644 --- a/docs/commands/openfeature_init.md +++ b/docs/commands/openfeature_init.md @@ -22,6 +22,7 @@ openfeature init [flags] ### 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 ``` diff --git a/docs/commands/openfeature_version.md b/docs/commands/openfeature_version.md index abb85dd..bc12f7f 100644 --- a/docs/commands/openfeature_version.md +++ b/docs/commands/openfeature_version.md @@ -17,6 +17,7 @@ openfeature version [flags] ### 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 ``` diff --git a/internal/config/flags.go b/internal/config/flags.go index 34bfa2a..86e5dfb 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -6,6 +6,7 @@ import ( // Flag name constants to avoid duplication const ( + DebugFlagName = "debug" ManifestFlagName = "manifest" OutputFlagName = "output" NoInputFlagName = "no-input" @@ -24,6 +25,7 @@ const ( func AddRootFlags(cmd *cobra.Command) { cmd.PersistentFlags().StringP(ManifestFlagName, "m", DefaultManifestPath, "Path to the flag manifest") cmd.PersistentFlags().Bool(NoInputFlagName, false, "Disable interactive prompts") + cmd.PersistentFlags().Bool(DebugFlagName, false, "Enable debug logging") } // AddGenerateFlags adds the common generate flags to the given command diff --git a/internal/generators/generators.go b/internal/generators/generators.go index d075b6f..7a11fba 100644 --- a/internal/generators/generators.go +++ b/internal/generators/generators.go @@ -10,6 +10,7 @@ import ( "github.com/open-feature/cli/internal/filesystem" "github.com/open-feature/cli/internal/flagset" + "github.com/open-feature/cli/internal/logger" ) // Represents the stability level of a generator @@ -46,6 +47,8 @@ func (g *CommonGenerator) GenerateFile(customFunc template.FuncMap, tmpl string, funcs := defaultFuncs() maps.Copy(funcs, customFunc) + logger.Default.Debug(fmt.Sprintf("Generating file: %s", name)) + generatorTemplate, err := template.New("generator").Funcs(funcs).Parse(tmpl) if err != nil { return fmt.Errorf("error initializing template: %v", err) @@ -60,5 +63,14 @@ func (g *CommonGenerator) GenerateFile(customFunc template.FuncMap, tmpl string, return fmt.Errorf("error executing template: %v", err) } - return filesystem.WriteFile(filepath.Join(params.OutputPath, name), buf.Bytes()) + fullPath := filepath.Join(params.OutputPath, name) + if err := filesystem.WriteFile(fullPath, buf.Bytes()); err != nil { + logger.Default.FileFailed(fullPath, err) + return err + } + + // Log successful file creation + logger.Default.FileCreated(fullPath) + + return nil } diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..bb63d24 --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,117 @@ +package logger + +import ( + "path/filepath" + + "github.com/pterm/pterm" +) + +// Logger provides methods for logging different types of messages +type Logger interface { + // Println logs a message without logging level + Println(message string) + // Info logs general information + Info(message string) + // Success logs successful operations + Success(message string) + // Warning logs warnings + Warning(message string) + // Error logs errors + Error(message string) + // Debug logs debug information (only when debug mode is enabled) + Debug(message string) + // SetDebug enables or disables debug mode + SetDebug(enabled bool) + // IsDebugEnabled returns whether debug mode is enabled + IsDebugEnabled() bool + // FileCreated logs a file creation event + FileCreated(path string) + // FileFailed logs a file creation failure + FileFailed(path string, err error) + // GenerationStarted logs the start of a generation process + GenerationStarted(generatorType string) + // GenerationComplete logs the completion of a generation process + GenerationComplete(generatorType string) +} + +// DefaultLogger is the default implementation of Logger +type DefaultLogger struct { + debugEnabled bool +} + +// New creates a new DefaultLogger +func New() *DefaultLogger { + return &DefaultLogger{ + debugEnabled: false, + } +} + +// SetDebug enables or disables debug mode +func (l *DefaultLogger) SetDebug(enabled bool) { + l.debugEnabled = enabled + if enabled { + pterm.EnableDebugMessages() + } +} + +// IsDebugEnabled returns whether debug mode is enabled +func (l *DefaultLogger) IsDebugEnabled() bool { + return l.debugEnabled +} + +// Println logs a message without logging level +func (l *DefaultLogger) Println(message string) { + pterm.Println(message) +} + +// Info logs general information +func (l *DefaultLogger) Info(message string) { + pterm.Info.Println(message) +} + +// Success logs successful operations +func (l *DefaultLogger) Success(message string) { + pterm.Success.Println(message) +} + +// Warning logs warnings +func (l *DefaultLogger) Warning(message string) { + pterm.Warning.Println(message) +} + +// Error logs errors +func (l *DefaultLogger) Error(message string) { + pterm.Error.Println(message) +} + +// Debug logs debug information (only when debug mode is enabled) +func (l *DefaultLogger) Debug(message string) { + if l.debugEnabled { + pterm.Debug.Println(message) + } +} + +// FileCreated logs a file creation event +func (l *DefaultLogger) FileCreated(path string) { + prettyPath := pterm.LightWhite(filepath.Clean(path)) + pterm.Success.Printf("Created %s\n", prettyPath) +} + +// FileFailed logs a file creation failure +func (l *DefaultLogger) FileFailed(path string, err error) { + prettyPath := pterm.LightWhite(filepath.Clean(path)) + pterm.Error.Printf("Failed to create %s: %v\n", prettyPath, err) +} + +// GenerationStarted logs the start of a generation process +func (l *DefaultLogger) GenerationStarted(generatorType string) { + pterm.Info.Printf("Generating a typesafe client for %s\n", generatorType) +} + +// GenerationComplete logs the completion of a generation process +func (l *DefaultLogger) GenerationComplete(generatorType string) { + pterm.Success.Printf("Successfully generated client. Happy coding!\n") +} + +// Default is a singleton instance of DefaultLogger +var Default Logger = New()