diff --git a/Makefile b/Makefile
index b889d85..4a1602d 100644
--- a/Makefile
+++ b/Makefile
@@ -10,7 +10,10 @@ help:
@echo " test-integration - Run all integration tests"
@echo " test-integration-csharp - Run C# integration tests"
@echo " test-integration-go - Run Go integration tests"
+ @echo " test-integration-java - Run Java integration tests"
+ @echo " test-integration-nestjs - Run NestJS integration tests"
@echo " test-integration-nodejs - Run NodeJS integration tests"
+ @echo " test-integration-react - Run React integration tests"
@echo " generate - Generate all code (API clients, docs, schema)"
@echo " generate-api - Generate API clients from OpenAPI specs"
@echo " generate-docs - Generate documentation"
@@ -67,6 +70,21 @@ test-integration-angular:
@echo "Running Angular integration test with Dagger..."
@go run ./test/integration/cmd/angular/run.go
+.PHONY: test-integration-java
+test-integration-java:
+ @echo "Running Java integration test with Dagger..."
+ @go run ./test/integration/cmd/java/run.go
+
+.PHONY: test-integration-nestjs
+test-integration-nestjs:
+ @echo "Running NestJS integration test with Dagger..."
+ @go run ./test/integration/cmd/nestjs/run.go
+
+.PHONY: test-integration-react
+test-integration-react:
+ @echo "Running React integration test with Dagger..."
+ @go run ./test/integration/cmd/react/run.go
+
.PHONY: test-integration
test-integration:
@echo "Running all integration tests with Dagger..."
diff --git a/test/integration/cmd/java/run.go b/test/integration/cmd/java/run.go
new file mode 100644
index 0000000..3caca9f
--- /dev/null
+++ b/test/integration/cmd/java/run.go
@@ -0,0 +1,99 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "dagger.io/dagger"
+ "github.com/open-feature/cli/test/integration"
+)
+
+// Test implements the integration test for the Java generator
+type Test struct {
+ // ProjectDir is the absolute path to the root of the project
+ ProjectDir string
+ // TestDir is the absolute path to the test directory
+ TestDir string
+}
+
+// New creates a new Test
+func New(projectDir, testDir string) *Test {
+ return &Test{
+ ProjectDir: projectDir,
+ TestDir: testDir,
+ }
+}
+
+// Run executes the Java integration test using Dagger
+func (t *Test) Run(ctx context.Context, client *dagger.Client) (*dagger.Container, error) {
+ // Source code container
+ source := client.Host().Directory(t.ProjectDir)
+ testFiles := client.Host().Directory(t.TestDir, dagger.HostDirectoryOpts{
+ Include: []string{"pom.xml", "src/**/*.java"},
+ })
+
+ // Build the CLI
+ cli := client.Container().
+ From("golang:1.24-alpine").
+ WithExec([]string{"apk", "add", "--no-cache", "git"}).
+ WithDirectory("/src", source).
+ WithWorkdir("/src").
+ WithExec([]string{"go", "mod", "tidy"}).
+ WithExec([]string{"go", "mod", "download"}).
+ WithExec([]string{"go", "build", "-o", "cli", "./cmd/openfeature"})
+
+ // Generate Java client
+ generated := cli.WithExec([]string{
+ "./cli", "generate", "java",
+ "--manifest=/src/sample/sample_manifest.json",
+ "--output=/tmp/generated",
+ "--package-name=dev.openfeature.generated",
+ })
+
+ // Get generated files
+ generatedFiles := generated.Directory("/tmp/generated")
+
+ // Test Java compilation with the generated files
+ javaContainer := client.Container().
+ From("maven:3.9-eclipse-temurin-21-alpine").
+ WithWorkdir("/app").
+ WithDirectory("/app", testFiles).
+ WithDirectory("/app/src/main/java/dev/openfeature/generated", generatedFiles).
+ WithExec([]string{"mvn", "clean", "compile", "-B", "-q"}).
+ WithExec([]string{"mvn", "exec:java", "-Dexec.mainClass=dev.openfeature.Main", "-q"})
+
+ return javaContainer, nil
+}
+
+// Name returns the name of the integration test
+func (t *Test) Name() string {
+ return "java"
+}
+
+func main() {
+ ctx := context.Background()
+
+ // Get project root
+ projectDir, err := filepath.Abs(os.Getenv("PWD"))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to get project dir: %v\n", err)
+ os.Exit(1)
+ }
+
+ // Get test directory
+ testDir, err := filepath.Abs(filepath.Join(projectDir, "test/java-integration"))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to get test dir: %v\n", err)
+ os.Exit(1)
+ }
+
+ // Create and run the Java integration test
+ test := New(projectDir, testDir)
+
+ if err := integration.RunTest(ctx, test); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ os.Exit(1)
+ }
+}
diff --git a/test/integration/cmd/nestjs/run.go b/test/integration/cmd/nestjs/run.go
new file mode 100644
index 0000000..29179d4
--- /dev/null
+++ b/test/integration/cmd/nestjs/run.go
@@ -0,0 +1,99 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "dagger.io/dagger"
+ "github.com/open-feature/cli/test/integration"
+)
+
+// Test implements the integration test for the NestJS generator
+type Test struct {
+ // ProjectDir is the absolute path to the root of the project
+ ProjectDir string
+ // TestDir is the absolute path to the test directory
+ TestDir string
+}
+
+// New creates a new Test
+func New(projectDir, testDir string) *Test {
+ return &Test{
+ ProjectDir: projectDir,
+ TestDir: testDir,
+ }
+}
+
+// Run executes the NestJS integration test using Dagger
+func (t *Test) Run(ctx context.Context, client *dagger.Client) (*dagger.Container, error) {
+ // Source code container
+ source := client.Host().Directory(t.ProjectDir)
+ testFiles := client.Host().Directory(t.TestDir, dagger.HostDirectoryOpts{
+ Include: []string{"package.json", "tsconfig.json", "src/**/*.ts"},
+ })
+
+ // Build the CLI
+ cli := client.Container().
+ From("golang:1.24-alpine").
+ WithExec([]string{"apk", "add", "--no-cache", "git"}).
+ WithDirectory("/src", source).
+ WithWorkdir("/src").
+ WithExec([]string{"go", "mod", "tidy"}).
+ WithExec([]string{"go", "mod", "download"}).
+ WithExec([]string{"go", "build", "-o", "cli", "./cmd/openfeature"})
+
+ // Generate NestJS client
+ generated := cli.WithExec([]string{
+ "./cli", "generate", "nestjs",
+ "--manifest=/src/sample/sample_manifest.json",
+ "--output=/tmp/generated",
+ })
+
+ // Get generated files
+ generatedFiles := generated.Directory("/tmp/generated")
+
+ // Test NestJS compilation with the generated files
+ nestjsContainer := client.Container().
+ From("node:20-alpine").
+ WithWorkdir("/app").
+ WithDirectory("/app", testFiles).
+ WithDirectory("/app/src/generated", generatedFiles).
+ WithExec([]string{"npm", "install"}).
+ WithExec([]string{"npm", "run", "build"}).
+ WithExec([]string{"node", "dist/main.js"})
+
+ return nestjsContainer, nil
+}
+
+// Name returns the name of the integration test
+func (t *Test) Name() string {
+ return "nestjs"
+}
+
+func main() {
+ ctx := context.Background()
+
+ // Get project root
+ projectDir, err := filepath.Abs(os.Getenv("PWD"))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to get project dir: %v\n", err)
+ os.Exit(1)
+ }
+
+ // Get test directory
+ testDir, err := filepath.Abs(filepath.Join(projectDir, "test/nestjs-integration"))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to get test dir: %v\n", err)
+ os.Exit(1)
+ }
+
+ // Create and run the NestJS integration test
+ test := New(projectDir, testDir)
+
+ if err := integration.RunTest(ctx, test); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ os.Exit(1)
+ }
+}
diff --git a/test/integration/cmd/react/run.go b/test/integration/cmd/react/run.go
new file mode 100644
index 0000000..fb50b62
--- /dev/null
+++ b/test/integration/cmd/react/run.go
@@ -0,0 +1,99 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "dagger.io/dagger"
+ "github.com/open-feature/cli/test/integration"
+)
+
+// Test implements the integration test for the React generator
+type Test struct {
+ // ProjectDir is the absolute path to the root of the project
+ ProjectDir string
+ // TestDir is the absolute path to the test directory
+ TestDir string
+}
+
+// New creates a new Test
+func New(projectDir, testDir string) *Test {
+ return &Test{
+ ProjectDir: projectDir,
+ TestDir: testDir,
+ }
+}
+
+// Run executes the React integration test using Dagger
+func (t *Test) Run(ctx context.Context, client *dagger.Client) (*dagger.Container, error) {
+ // Source code container
+ source := client.Host().Directory(t.ProjectDir)
+ testFiles := client.Host().Directory(t.TestDir, dagger.HostDirectoryOpts{
+ Include: []string{"package.json", "tsconfig.json", "src/**/*.ts", "src/**/*.tsx"},
+ })
+
+ // Build the CLI
+ cli := client.Container().
+ From("golang:1.24-alpine").
+ WithExec([]string{"apk", "add", "--no-cache", "git"}).
+ WithDirectory("/src", source).
+ WithWorkdir("/src").
+ WithExec([]string{"go", "mod", "tidy"}).
+ WithExec([]string{"go", "mod", "download"}).
+ WithExec([]string{"go", "build", "-o", "cli", "./cmd/openfeature"})
+
+ // Generate React client
+ generated := cli.WithExec([]string{
+ "./cli", "generate", "react",
+ "--manifest=/src/sample/sample_manifest.json",
+ "--output=/tmp/generated",
+ })
+
+ // Get generated files
+ generatedFiles := generated.Directory("/tmp/generated")
+
+ // Test React compilation with the generated files
+ reactContainer := client.Container().
+ From("node:20-alpine").
+ WithWorkdir("/app").
+ WithDirectory("/app", testFiles).
+ WithDirectory("/app/src/generated", generatedFiles).
+ WithExec([]string{"npm", "install"}).
+ WithExec([]string{"npm", "run", "build"}).
+ WithExec([]string{"node", "dist/test.js"})
+
+ return reactContainer, nil
+}
+
+// Name returns the name of the integration test
+func (t *Test) Name() string {
+ return "react"
+}
+
+func main() {
+ ctx := context.Background()
+
+ // Get project root
+ projectDir, err := filepath.Abs(os.Getenv("PWD"))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to get project dir: %v\n", err)
+ os.Exit(1)
+ }
+
+ // Get test directory
+ testDir, err := filepath.Abs(filepath.Join(projectDir, "test/react-integration"))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to get test dir: %v\n", err)
+ os.Exit(1)
+ }
+
+ // Create and run the React integration test
+ test := New(projectDir, testDir)
+
+ if err := integration.RunTest(ctx, test); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ os.Exit(1)
+ }
+}
diff --git a/test/integration/cmd/run.go b/test/integration/cmd/run.go
index 5923c26..176b22d 100644
--- a/test/integration/cmd/run.go
+++ b/test/integration/cmd/run.go
@@ -45,6 +45,33 @@ func main() {
os.Exit(1)
}
+ // Run the Java integration test
+ javaCmd := exec.Command("go", "run", "github.com/open-feature/cli/test/integration/cmd/java")
+ javaCmd.Stdout = os.Stdout
+ javaCmd.Stderr = os.Stderr
+ if err := javaCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error running Java integration test: %v\n", err)
+ os.Exit(1)
+ }
+
+ // Run the NestJS integration test
+ nestjsCmd := exec.Command("go", "run", "github.com/open-feature/cli/test/integration/cmd/nestjs")
+ nestjsCmd.Stdout = os.Stdout
+ nestjsCmd.Stderr = os.Stderr
+ if err := nestjsCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error running NestJS integration test: %v\n", err)
+ os.Exit(1)
+ }
+
+ // Run the React integration test
+ reactCmd := exec.Command("go", "run", "github.com/open-feature/cli/test/integration/cmd/react")
+ reactCmd.Stdout = os.Stdout
+ reactCmd.Stderr = os.Stderr
+ if err := reactCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error running React integration test: %v\n", err)
+ os.Exit(1)
+ }
+
// Add more tests here as they are available
fmt.Println("=== All integration tests passed successfully ===")
diff --git a/test/java-integration/pom.xml b/test/java-integration/pom.xml
new file mode 100644
index 0000000..6525552
--- /dev/null
+++ b/test/java-integration/pom.xml
@@ -0,0 +1,47 @@
+
+
+ 4.0.0
+
+ dev.openfeature
+ cli-java-integration-test
+ 1.0-SNAPSHOT
+
+
+ 11
+ 11
+ UTF-8
+
+
+
+
+
+ dev.openfeature
+ sdk
+ 1.14.0
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ 11
+ 11
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.1.0
+
+ dev.openfeature.Main
+
+
+
+
+
diff --git a/test/java-integration/src/main/java/dev/openfeature/Main.java b/test/java-integration/src/main/java/dev/openfeature/Main.java
new file mode 100644
index 0000000..e994c19
--- /dev/null
+++ b/test/java-integration/src/main/java/dev/openfeature/Main.java
@@ -0,0 +1,107 @@
+package dev.openfeature;
+
+import dev.openfeature.generated.*;
+import dev.openfeature.sdk.*;
+import dev.openfeature.sdk.providers.memory.Flag;
+import dev.openfeature.sdk.providers.memory.InMemoryProvider;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Main {
+ public static void main(String[] args) {
+ try {
+ run();
+ System.out.println("Generated Java code compiles successfully!");
+ } catch (Exception e) {
+ System.err.println("Error: " + e.getMessage());
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void run() throws Exception {
+ // Set up the in-memory provider with test flags
+ Map> flags = new HashMap<>();
+
+ flags.put("discountPercentage", Flag.builder()
+ .variant("default", 0.15)
+ .defaultVariant("default")
+ .build());
+
+ flags.put("enableFeatureA", Flag.builder()
+ .variant("default", false)
+ .defaultVariant("default")
+ .build());
+
+ flags.put("greetingMessage", Flag.builder()
+ .variant("default", "Hello there!")
+ .defaultVariant("default")
+ .build());
+
+ flags.put("usernameMaxLength", Flag.builder()
+ .variant("default", 50)
+ .defaultVariant("default")
+ .build());
+
+ Map themeConfig = new HashMap<>();
+ themeConfig.put("primaryColor", "#007bff");
+ themeConfig.put("secondaryColor", "#6c757d");
+
+ flags.put("themeCustomization", Flag.builder()
+ .variant("default", new Value(themeConfig))
+ .defaultVariant("default")
+ .build());
+
+ InMemoryProvider provider = new InMemoryProvider(flags);
+
+ // Set the provider
+ OpenFeatureAPI.getInstance().setProviderAndWait(provider);
+
+ Client client = OpenFeatureAPI.getInstance().getClient();
+ MutableContext evalContext = new MutableContext();
+
+ // Use the generated code for all flag evaluations
+ Boolean enableFeatureA = EnableFeatureA.value(client, evalContext);
+ System.out.println("enableFeatureA: " + enableFeatureA);
+ FlagEvaluationDetails enableFeatureADetails = EnableFeatureA.valueWithDetails(client, evalContext);
+ if (enableFeatureADetails.getErrorCode() != null) {
+ throw new Exception("Error evaluating boolean flag");
+ }
+
+ Double discount = DiscountPercentage.value(client, evalContext);
+ System.out.printf("Discount Percentage: %.2f%n", discount);
+ FlagEvaluationDetails discountDetails = DiscountPercentage.valueWithDetails(client, evalContext);
+ if (discountDetails.getErrorCode() != null) {
+ throw new Exception("Failed to get discount");
+ }
+
+ String greetingMessage = GreetingMessage.value(client, evalContext);
+ System.out.println("greetingMessage: " + greetingMessage);
+ FlagEvaluationDetails greetingDetails = GreetingMessage.valueWithDetails(client, evalContext);
+ if (greetingDetails.getErrorCode() != null) {
+ throw new Exception("Error evaluating string flag");
+ }
+
+ Integer usernameMaxLength = UsernameMaxLength.value(client, evalContext);
+ System.out.println("usernameMaxLength: " + usernameMaxLength);
+ FlagEvaluationDetails usernameDetails = UsernameMaxLength.valueWithDetails(client, evalContext);
+ if (usernameDetails.getErrorCode() != null) {
+ throw new Exception("Error evaluating int flag");
+ }
+
+ Value themeCustomization = ThemeCustomization.value(client, evalContext);
+ FlagEvaluationDetails themeDetails = ThemeCustomization.valueWithDetails(client, evalContext);
+ if (themeDetails.getErrorCode() != null) {
+ throw new Exception("Error evaluating object flag");
+ }
+ System.out.println("themeCustomization: " + themeCustomization);
+
+ // Test the getKey() method functionality for all flags
+ System.out.println("enableFeatureA flag key: " + EnableFeatureA.getKey());
+ System.out.println("discountPercentage flag key: " + DiscountPercentage.getKey());
+ System.out.println("greetingMessage flag key: " + GreetingMessage.getKey());
+ System.out.println("usernameMaxLength flag key: " + UsernameMaxLength.getKey());
+ System.out.println("themeCustomization flag key: " + ThemeCustomization.getKey());
+ }
+}
diff --git a/test/nestjs-integration/package.json b/test/nestjs-integration/package.json
new file mode 100644
index 0000000..528971e
--- /dev/null
+++ b/test/nestjs-integration/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "cli-nestjs-integration-test",
+ "version": "1.0.0",
+ "description": "Integration test for OpenFeature CLI NestJS generator",
+ "scripts": {
+ "build": "tsc",
+ "start": "node dist/main.js"
+ },
+ "dependencies": {
+ "@nestjs/common": "^10.0.0",
+ "@nestjs/core": "^10.0.0",
+ "@openfeature/nestjs-sdk": "^0.7.0",
+ "@openfeature/server-sdk": "^1.34.0",
+ "reflect-metadata": "^0.1.13",
+ "rxjs": "^7.8.1"
+ },
+ "devDependencies": {
+ "@types/node": "^20.0.0",
+ "typescript": "^5.3.0"
+ }
+}
diff --git a/test/nestjs-integration/src/main.ts b/test/nestjs-integration/src/main.ts
new file mode 100644
index 0000000..f4bf7aa
--- /dev/null
+++ b/test/nestjs-integration/src/main.ts
@@ -0,0 +1,120 @@
+import { NestFactory } from '@nestjs/core';
+import { Module } from '@nestjs/common';
+import { OpenFeatureModule } from '@openfeature/nestjs-sdk';
+import { InMemoryProvider } from '@openfeature/server-sdk';
+import * as generated from './generated';
+
+@Module({
+ imports: [
+ OpenFeatureModule.forRoot({
+ provider: new InMemoryProvider({
+ discountPercentage: {
+ disabled: false,
+ variants: {
+ default: 0.15,
+ },
+ defaultVariant: 'default',
+ },
+ enableFeatureA: {
+ disabled: false,
+ variants: {
+ default: false,
+ },
+ defaultVariant: 'default',
+ },
+ greetingMessage: {
+ disabled: false,
+ variants: {
+ default: 'Hello there!',
+ },
+ defaultVariant: 'default',
+ },
+ usernameMaxLength: {
+ disabled: false,
+ variants: {
+ default: 50,
+ },
+ defaultVariant: 'default',
+ },
+ themeCustomization: {
+ disabled: false,
+ variants: {
+ default: {
+ primaryColor: '#007bff',
+ secondaryColor: '#6c757d',
+ },
+ },
+ defaultVariant: 'default',
+ },
+ }),
+ }),
+ ],
+})
+class AppModule {}
+
+async function bootstrap() {
+ const app = await NestFactory.createApplicationContext(AppModule);
+
+ try {
+ const client = app.get('OPENFEATURE_CLIENT');
+
+ // Use the generated code for all flag evaluations
+ const enableFeatureA = await generated.EnableFeatureA.value(client, {});
+ console.log('enableFeatureA:', enableFeatureA);
+
+ const enableFeatureADetails = await generated.EnableFeatureA.valueWithDetails(client, {});
+ if (enableFeatureADetails.errorCode) {
+ throw new Error('Error evaluating boolean flag');
+ }
+
+ const discount = await generated.DiscountPercentage.value(client, {});
+ console.log('Discount Percentage:', discount.toFixed(2));
+
+ const discountDetails = await generated.DiscountPercentage.valueWithDetails(client, {});
+ if (discountDetails.errorCode) {
+ throw new Error('Failed to get discount');
+ }
+
+ const greetingMessage = await generated.GreetingMessage.value(client, {});
+ console.log('greetingMessage:', greetingMessage);
+
+ const greetingDetails = await generated.GreetingMessage.valueWithDetails(client, {});
+ if (greetingDetails.errorCode) {
+ throw new Error('Error evaluating string flag');
+ }
+
+ const usernameMaxLength = await generated.UsernameMaxLength.value(client, {});
+ console.log('usernameMaxLength:', usernameMaxLength);
+
+ const usernameDetails = await generated.UsernameMaxLength.valueWithDetails(client, {});
+ if (usernameDetails.errorCode) {
+ throw new Error('Error evaluating int flag');
+ }
+
+ const themeCustomization = await generated.ThemeCustomization.value(client, {});
+ console.log('themeCustomization:', themeCustomization);
+
+ const themeDetails = await generated.ThemeCustomization.valueWithDetails(client, {});
+ if (themeDetails.errorCode) {
+ throw new Error('Error evaluating object flag');
+ }
+
+ // Test the getKey() method functionality for all flags
+ console.log('enableFeatureA flag key:', generated.EnableFeatureA.getKey());
+ console.log('discountPercentage flag key:', generated.DiscountPercentage.getKey());
+ console.log('greetingMessage flag key:', generated.GreetingMessage.getKey());
+ console.log('usernameMaxLength flag key:', generated.UsernameMaxLength.getKey());
+ console.log('themeCustomization flag key:', generated.ThemeCustomization.getKey());
+
+ console.log('Generated NestJS code compiles successfully!');
+
+ await app.close();
+ process.exit(0);
+ } catch (error) {
+ console.error('Error:', error);
+ await app.close();
+ process.exit(1);
+ }
+}
+
+bootstrap();
diff --git a/test/nestjs-integration/tsconfig.json b/test/nestjs-integration/tsconfig.json
new file mode 100644
index 0000000..5485d9d
--- /dev/null
+++ b/test/nestjs-integration/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "declaration": true,
+ "removeComments": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "allowSyntheticDefaultImports": true,
+ "target": "ES2021",
+ "sourceMap": true,
+ "outDir": "./dist",
+ "baseUrl": "./",
+ "incremental": true,
+ "skipLibCheck": true,
+ "strictNullChecks": false,
+ "noImplicitAny": false,
+ "strictBindCallApply": false,
+ "forceConsistentCasingInFileNames": false,
+ "noFallthroughCasesInSwitch": false,
+ "esModuleInterop": true
+ }
+}
diff --git a/test/react-integration/package.json b/test/react-integration/package.json
new file mode 100644
index 0000000..9ffea5c
--- /dev/null
+++ b/test/react-integration/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "cli-react-integration-test",
+ "version": "1.0.0",
+ "description": "Integration test for OpenFeature CLI React generator",
+ "scripts": {
+ "build": "tsc",
+ "test": "node dist/test.js"
+ },
+ "dependencies": {
+ "@openfeature/react-sdk": "^1.0.0",
+ "@openfeature/server-sdk": "^1.34.0",
+ "react": "^18.2.0"
+ },
+ "devDependencies": {
+ "@types/node": "^20.0.0",
+ "@types/react": "^18.2.0",
+ "typescript": "^5.3.0"
+ }
+}
diff --git a/test/react-integration/src/test.ts b/test/react-integration/src/test.ts
new file mode 100644
index 0000000..affcc3e
--- /dev/null
+++ b/test/react-integration/src/test.ts
@@ -0,0 +1,69 @@
+import * as generated from './generated';
+
+async function main() {
+ try {
+ // Validate that all generated exports exist and have the expected structure
+ const flags = [
+ { name: 'EnableFeatureA', flag: generated.EnableFeatureA },
+ { name: 'DiscountPercentage', flag: generated.DiscountPercentage },
+ { name: 'GreetingMessage', flag: generated.GreetingMessage },
+ { name: 'UsernameMaxLength', flag: generated.UsernameMaxLength },
+ { name: 'ThemeCustomization', flag: generated.ThemeCustomization },
+ ];
+
+ for (const { name, flag } of flags) {
+ // Validate the flag object has the expected properties
+ if (typeof flag !== 'object' || flag === null) {
+ throw new Error(`${name} is not an object`);
+ }
+
+ // Check for getKey method
+ if (typeof flag.getKey !== 'function') {
+ throw new Error(`${name}.getKey is not a function`);
+ }
+
+ const key = flag.getKey();
+ console.log(`${name} flag key:`, key);
+
+ if (typeof key !== 'string' || key.length === 0) {
+ throw new Error(`${name}.getKey() did not return a valid string`);
+ }
+
+ // Check for useFlag hook
+ if (typeof flag.useFlag !== 'function') {
+ throw new Error(`${name}.useFlag is not a function`);
+ }
+
+ // Check for useFlagWithDetails hook
+ if (typeof flag.useFlagWithDetails !== 'function') {
+ throw new Error(`${name}.useFlagWithDetails is not a function`);
+ }
+ }
+
+ // Verify expected flag keys
+ if (generated.EnableFeatureA.getKey() !== 'enableFeatureA') {
+ throw new Error('EnableFeatureA has incorrect key');
+ }
+ if (generated.DiscountPercentage.getKey() !== 'discountPercentage') {
+ throw new Error('DiscountPercentage has incorrect key');
+ }
+ if (generated.GreetingMessage.getKey() !== 'greetingMessage') {
+ throw new Error('GreetingMessage has incorrect key');
+ }
+ if (generated.UsernameMaxLength.getKey() !== 'usernameMaxLength') {
+ throw new Error('UsernameMaxLength has incorrect key');
+ }
+ if (generated.ThemeCustomization.getKey() !== 'themeCustomization') {
+ throw new Error('ThemeCustomization has incorrect key');
+ }
+
+ console.log('All generated React hooks are properly structured!');
+ console.log('Generated React code compiles successfully!');
+ process.exit(0);
+ } catch (error: any) {
+ console.error('Error:', error.message);
+ process.exit(1);
+ }
+}
+
+main();
diff --git a/test/react-integration/tsconfig.json b/test/react-integration/tsconfig.json
new file mode 100644
index 0000000..1cafe6d
--- /dev/null
+++ b/test/react-integration/tsconfig.json
@@ -0,0 +1,18 @@
+{
+ "compilerOptions": {
+ "target": "ES2021",
+ "module": "commonjs",
+ "lib": ["ES2021"],
+ "jsx": "react",
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "moduleResolution": "node"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules"]
+}