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
19 changes: 18 additions & 1 deletion .github/workflows/pr-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,21 @@ jobs:
echo "::error file=Makefile::Doc generation produced diff. Run 'make generate-docs' and commit results."
git diff
exit 1
fi
fi

csharp-test:
name: 'C# Generator Test'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'

- name: Set up Docker
uses: docker/setup-buildx-action@v2

- name: 'Run C# integration test'
run: ./test/csharp-integration/test-compilation.sh
17 changes: 17 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,23 @@ We welcome contributions for new generators to extend the functionality of the O

11. **Address Feedback**: Be responsive to feedback from the maintainers. Make any necessary changes and update your pull request as needed.

### Integration Tests

To verify that generated code compiles correctly, the project includes integration tests, for example, for c#:

```bash
# Test the C# generator output
make test-csharp
```

This will:
1. Build the CLI
2. Generate a C# client
3. Compile the C# code in a Docker container
4. Validate that the code compiles correctly

Consider adding more integration tests for new generators.

## Templates

### Data
Expand Down
5 changes: 5 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ test:
@go test -v ./...
@echo "Tests passed successfully!"

.PHONY: test-csharp
test-csharp:
@echo "Running C# integration test..."
@./test/csharp-integration/test-compilation.sh

generate-docs:
@echo "Generating documentation..."
@go run ./docs/generate-commands.go
Expand Down
17 changes: 8 additions & 9 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ 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
Expand All @@ -31,7 +31,6 @@ func initializeConfig(cmd *cobra.Command, bindPrefix string) error {
} 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)
Expand All @@ -50,24 +49,24 @@ func initializeConfig(cmd *cobra.Command, bindPrefix string) error {

// Build configuration paths from most specific to least specific
configPaths := []string{}

// Check the most specific path (e.g., generate.go.package-name)
if bindPrefix != "" {
configPaths = append(configPaths, bindPrefix + "." + f.Name)
configPaths = append(configPaths, bindPrefix+"."+f.Name)

// Check parent paths (e.g., generate.package-name)
parts := strings.Split(bindPrefix, ".")
for i := len(parts) - 1; i > 0; i-- {
parentPath := strings.Join(parts[:i], ".") + "." + f.Name
configPaths = append(configPaths, parentPath)
}
}

// 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) {
Expand All @@ -81,7 +80,7 @@ func initializeConfig(cmd *cobra.Command, bindPrefix string) error {
}
}
}

// Log the final value for the flag
logger.Default.Debug(fmt.Sprintf("Final flag value: %s=%s", f.Name, f.Value.String()))
})
Expand Down
132 changes: 92 additions & 40 deletions cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/open-feature/cli/internal/config"
"github.com/open-feature/cli/internal/flagset"
"github.com/open-feature/cli/internal/generators"
"github.com/open-feature/cli/internal/generators/csharp"
"github.com/open-feature/cli/internal/generators/golang"
"github.com/open-feature/cli/internal/generators/nodejs"
"github.com/open-feature/cli/internal/generators/python"
Expand All @@ -14,6 +15,32 @@ import (
"github.com/spf13/cobra"
)

func GetGenerateCmd() *cobra.Command {
generateCmd := &cobra.Command{
Use: "generate",
Short: "Generate typesafe OpenFeature accessors.",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return initializeConfig(cmd, "generate")
},
RunE: func(cmd *cobra.Command, args []string) error {
cmd.Println("Available generators:")
return generators.DefaultManager.PrintGeneratorsTable()
},
}

// Add generate flags using the config package
config.AddGenerateFlags(generateCmd)

// Add all registered generator commands
for _, subCmd := range generators.DefaultManager.GetCommands() {
generateCmd.AddCommand(subCmd)
}

addStabilityInfo(generateCmd)

return generateCmd
}

// addStabilityInfo adds stability information to the command's help template before "Usage:"
func addStabilityInfo(cmd *cobra.Command) {
// Only modify commands that have a stability annotation
Expand All @@ -37,7 +64,7 @@ func addStabilityInfo(cmd *cobra.Command) {
}
}

func GetGenerateNodeJSCmd() *cobra.Command {
func getGenerateNodeJSCmd() *cobra.Command {
nodeJSCmd := &cobra.Command{
Use: "nodejs",
Short: "Generate typesafe Node.js client.",
Expand Down Expand Up @@ -69,9 +96,9 @@ func GetGenerateNodeJSCmd() *cobra.Command {
if err != nil {
return err
}

logger.Default.GenerationComplete("Node.js")

return nil
},
}
Expand All @@ -81,7 +108,7 @@ func GetGenerateNodeJSCmd() *cobra.Command {
return nodeJSCmd
}

func GetGenerateReactCmd() *cobra.Command {
func getGenerateReactCmd() *cobra.Command {
reactCmd := &cobra.Command{
Use: "react",
Short: "Generate typesafe React Hooks.",
Expand All @@ -95,7 +122,7 @@ 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]{
Expand All @@ -113,9 +140,9 @@ func GetGenerateReactCmd() *cobra.Command {
if err != nil {
return err
}

logger.Default.GenerationComplete("React")

return nil
},
}
Expand All @@ -125,7 +152,57 @@ func GetGenerateReactCmd() *cobra.Command {
return reactCmd
}

func GetGenerateGoCmd() *cobra.Command {
func getGenerateCSharpCmd() *cobra.Command {
csharpCmd := &cobra.Command{
Use: "csharp",
Short: "Generate typesafe C# client.",
Long: `Generate typesafe C# client compatible with the OpenFeature .NET SDK.`,
Annotations: map[string]string{
"stability": string(generators.Alpha),
},
PreRunE: func(cmd *cobra.Command, args []string) error {
return initializeConfig(cmd, "generate.csharp")
},
RunE: func(cmd *cobra.Command, args []string) error {
namespace := config.GetCSharpNamespace(cmd)
manifestPath := config.GetManifestPath(cmd)
outputPath := config.GetOutputPath(cmd)

logger.Default.GenerationStarted("C#")

params := generators.Params[csharp.Params]{
OutputPath: outputPath,
Custom: csharp.Params{
Namespace: namespace,
},
}
flagset, err := flagset.Load(manifestPath)
if err != nil {
return err
}

generator := csharp.NewGenerator(flagset)
logger.Default.Debug("Executing C# generator")
err = generator.Generate(&params)
if err != nil {
return err
}

logger.Default.GenerationComplete("C#")

return nil
},
}

// Add C#-specific flags
config.AddCSharpGenerateFlags(csharpCmd)

addStabilityInfo(csharpCmd)

return csharpCmd
}

func getGenerateGoCmd() *cobra.Command {
goCmd := &cobra.Command{
Use: "go",
Short: "Generate typesafe accessors for OpenFeature.",
Expand All @@ -140,7 +217,7 @@ 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]{
Expand All @@ -161,9 +238,9 @@ func GetGenerateGoCmd() *cobra.Command {
if err != nil {
return err
}

logger.Default.GenerationComplete("Go")

return nil
},
}
Expand Down Expand Up @@ -219,34 +296,9 @@ func getGeneratePythonCmd() *cobra.Command {

func init() {
// Register generators with the manager
generators.DefaultManager.Register(GetGenerateReactCmd)
generators.DefaultManager.Register(GetGenerateGoCmd)
generators.DefaultManager.Register(GetGenerateNodeJSCmd)
generators.DefaultManager.Register(getGenerateReactCmd)
generators.DefaultManager.Register(getGenerateGoCmd)
generators.DefaultManager.Register(getGenerateNodeJSCmd)
generators.DefaultManager.Register(getGeneratePythonCmd)
}

func GetGenerateCmd() *cobra.Command {
generateCmd := &cobra.Command{
Use: "generate",
Short: "Generate typesafe OpenFeature accessors.",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return initializeConfig(cmd, "generate")
},
RunE: func(cmd *cobra.Command, args []string) error {
cmd.Println("Available generators:")
return generators.DefaultManager.PrintGeneratorsTable()
},
}

// Add generate flags using the config package
config.AddGenerateFlags(generateCmd)

// Add all registered generator commands
for _, subCmd := range generators.DefaultManager.GetCommands() {
generateCmd.AddCommand(subCmd)
}

addStabilityInfo(generateCmd)

return generateCmd
generators.DefaultManager.Register(getGenerateCSharpCmd)
}
Loading
Loading