mirror of https://github.com/open-feature/cli.git
feat: add doc gen, move schema path, add tests, fix react gen (#68)
## This PR - moves JSON schema to a dedicated directory - added schema validation tests - fixed React code gen (and tests) - automate CLI doc generation - Loosen JSON schema - ~~Rename default value~~ ### Related Issues Fixes #66 ### Notes It's a big PR that I could break into smaller changes if necessary. --------- Signed-off-by: Michael Beemer <beeme1mr@users.noreply.github.com> Signed-off-by: Michael Beemer <michael.beemer@dynatrace.com>
This commit is contained in:
parent
60955af1a9
commit
68a72ee929
|
|
@ -20,15 +20,22 @@ jobs:
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version-file: 'go.mod'
|
go-version-file: 'go.mod'
|
||||||
- name: Module cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-go-
|
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: go test ./...
|
run: go test ./...
|
||||||
|
|
||||||
|
docs-check:
|
||||||
|
name: Validate docs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: 'go.mod'
|
||||||
|
|
||||||
|
- run: make generate-docs
|
||||||
|
- name: Check no diff
|
||||||
|
run: |
|
||||||
|
if [ ! -z "$(git status --porcelain)" ]; then echo "::error file=Makefile::Doc generation produced diff. Run 'make generate-docs' and commit results."; exit 1; fi
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
@echo "Running tests..."
|
||||||
|
go test -v ./...
|
||||||
|
@echo "Tests passed successfully!"
|
||||||
|
|
||||||
|
generate-docs:
|
||||||
|
@echo "Generating documentation..."
|
||||||
|
go run ./docs/generate-commands.go
|
||||||
|
@echo "Documentation generated successfully!"
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra/doc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GenerateDoc generates cobra docs of the cmd
|
||||||
|
func GenerateDoc(path string) error {
|
||||||
|
linkHandler := func(name string) string {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
filePrepender := func(filename string) string {
|
||||||
|
return "<!-- markdownlint-disable-file -->\n<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := doc.GenMarkdownTreeCustom(rootCmd, path, filePrepender, linkHandler); err != nil {
|
||||||
|
return fmt.Errorf("error generating docs: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
useBooleanFlagDetails,
|
type ReactFlagEvaluationOptions,
|
||||||
useNumberFlagDetails,
|
type ReactFlagEvaluationNoSuspenseOptions,
|
||||||
useStringFlagDetails,
|
useFlag,
|
||||||
|
useSuspenseFlag,
|
||||||
} from "@openfeature/react-sdk";
|
} from "@openfeature/react-sdk";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -14,8 +15,23 @@ import {
|
||||||
* - default value: `0.15`
|
* - default value: `0.15`
|
||||||
* - type: `number`
|
* - type: `number`
|
||||||
*/
|
*/
|
||||||
export const useDiscountPercentage = (options: Parameters<typeof useNumberFlagDetails>[2]) => {
|
export const useDiscountPercentage = (options: ReactFlagEvaluationOptions) => {
|
||||||
return useNumberFlagDetails("discountPercentage", 0.15, options);
|
return useFlag("discountPercentage", 0.15, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discount percentage applied to purchases.
|
||||||
|
*
|
||||||
|
* **Details:**
|
||||||
|
* - flag key: `discountPercentage`
|
||||||
|
* - default value: `0.15`
|
||||||
|
* - type: `number`
|
||||||
|
*
|
||||||
|
* Equivalent to useFlag with options: `{ suspend: true }`
|
||||||
|
* @experimental — Suspense is an experimental feature subject to change in future versions.
|
||||||
|
*/
|
||||||
|
export const useSuspenseDiscountPercentage = (options: ReactFlagEvaluationNoSuspenseOptions) => {
|
||||||
|
return useSuspenseFlag("discountPercentage", 0.15, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -26,8 +42,23 @@ export const useDiscountPercentage = (options: Parameters<typeof useNumberFlagDe
|
||||||
* - default value: `false`
|
* - default value: `false`
|
||||||
* - type: `boolean`
|
* - type: `boolean`
|
||||||
*/
|
*/
|
||||||
export const useEnableFeatureA = (options: Parameters<typeof useBooleanFlagDetails>[2]) => {
|
export const useEnableFeatureA = (options: ReactFlagEvaluationOptions) => {
|
||||||
return useBooleanFlagDetails("enableFeatureA", false, options);
|
return useFlag("enableFeatureA", false, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controls whether Feature A is enabled.
|
||||||
|
*
|
||||||
|
* **Details:**
|
||||||
|
* - flag key: `enableFeatureA`
|
||||||
|
* - default value: `false`
|
||||||
|
* - type: `boolean`
|
||||||
|
*
|
||||||
|
* Equivalent to useFlag with options: `{ suspend: true }`
|
||||||
|
* @experimental — Suspense is an experimental feature subject to change in future versions.
|
||||||
|
*/
|
||||||
|
export const useSuspenseEnableFeatureA = (options: ReactFlagEvaluationNoSuspenseOptions) => {
|
||||||
|
return useSuspenseFlag("enableFeatureA", false, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -38,8 +69,23 @@ export const useEnableFeatureA = (options: Parameters<typeof useBooleanFlagDetai
|
||||||
* - default value: `Hello there!`
|
* - default value: `Hello there!`
|
||||||
* - type: `string`
|
* - type: `string`
|
||||||
*/
|
*/
|
||||||
export const useGreetingMessage = (options: Parameters<typeof useStringFlagDetails>[2]) => {
|
export const useGreetingMessage = (options: ReactFlagEvaluationOptions) => {
|
||||||
return useStringFlagDetails("greetingMessage", "Hello there!", options);
|
return useFlag("greetingMessage", "Hello there!", options);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message to use for greeting users.
|
||||||
|
*
|
||||||
|
* **Details:**
|
||||||
|
* - flag key: `greetingMessage`
|
||||||
|
* - default value: `Hello there!`
|
||||||
|
* - type: `string`
|
||||||
|
*
|
||||||
|
* Equivalent to useFlag with options: `{ suspend: true }`
|
||||||
|
* @experimental — Suspense is an experimental feature subject to change in future versions.
|
||||||
|
*/
|
||||||
|
export const useSuspenseGreetingMessage = (options: ReactFlagEvaluationNoSuspenseOptions) => {
|
||||||
|
return useSuspenseFlag("greetingMessage", "Hello there!", options);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -50,6 +96,21 @@ export const useGreetingMessage = (options: Parameters<typeof useStringFlagDetai
|
||||||
* - default value: `50`
|
* - default value: `50`
|
||||||
* - type: `number`
|
* - type: `number`
|
||||||
*/
|
*/
|
||||||
export const useUsernameMaxLength = (options: Parameters<typeof useNumberFlagDetails>[2]) => {
|
export const useUsernameMaxLength = (options: ReactFlagEvaluationOptions) => {
|
||||||
return useNumberFlagDetails("usernameMaxLength", 50, options);
|
return useFlag("usernameMaxLength", 50, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum allowed length for usernames.
|
||||||
|
*
|
||||||
|
* **Details:**
|
||||||
|
* - flag key: `usernameMaxLength`
|
||||||
|
* - default value: `50`
|
||||||
|
* - type: `number`
|
||||||
|
*
|
||||||
|
* Equivalent to useFlag with options: `{ suspend: true }`
|
||||||
|
* @experimental — Suspense is an experimental feature subject to change in future versions.
|
||||||
|
*/
|
||||||
|
export const useSuspenseUsernameMaxLength = (options: ReactFlagEvaluationNoSuspenseOptions) => {
|
||||||
|
return useSuspenseFlag("usernameMaxLength", 50, options);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
Version string
|
Version = "dev"
|
||||||
Commit string
|
Commit string
|
||||||
Date string
|
Date string
|
||||||
)
|
)
|
||||||
|
|
@ -20,6 +20,7 @@ var rootCmd = &cobra.Command{
|
||||||
Use: "openfeature",
|
Use: "openfeature",
|
||||||
Short: "CLI for OpenFeature.",
|
Short: "CLI for OpenFeature.",
|
||||||
Long: `CLI for OpenFeature related functionalities.`,
|
Long: `CLI for OpenFeature related functionalities.`,
|
||||||
|
DisableAutoGenTag: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!-- markdownlint-disable-file -->
|
||||||
|
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
|
||||||
|
## openfeature
|
||||||
|
|
||||||
|
CLI for OpenFeature.
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
CLI for OpenFeature related functionalities.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for openfeature
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [openfeature generate](openfeature_generate.md) - Code generation for flag accessors for OpenFeature.
|
||||||
|
* [openfeature version](openfeature_version.md) - Print the version number of the OpenFeature CLI
|
||||||
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<!-- markdownlint-disable-file -->
|
||||||
|
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
|
||||||
|
## openfeature generate
|
||||||
|
|
||||||
|
Code generation for flag accessors for OpenFeature.
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Code generation for flag accessors for OpenFeature.
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
--flag_manifest_path string Path to the flag manifest.
|
||||||
|
-h, --help help for generate
|
||||||
|
--output_path string Output path for the codegen
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [openfeature](openfeature.md) - CLI for OpenFeature.
|
||||||
|
* [openfeature generate go](openfeature_generate_go.md) - Generate Golang flag accessors for OpenFeature.
|
||||||
|
* [openfeature generate react](openfeature_generate_react.md) - Generate typesafe React Hooks.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<!-- markdownlint-disable-file -->
|
||||||
|
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
|
||||||
|
## openfeature generate go
|
||||||
|
|
||||||
|
Generate Golang flag accessors for OpenFeature.
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Generate Golang flag accessors for OpenFeature.
|
||||||
|
|
||||||
|
```
|
||||||
|
openfeature generate go [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for go
|
||||||
|
--package_name string Name of the Go package to be generated.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
--flag_manifest_path string Path to the flag manifest.
|
||||||
|
--output_path string Output path for the codegen
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [openfeature generate](openfeature_generate.md) - Code generation for flag accessors for OpenFeature.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<!-- markdownlint-disable-file -->
|
||||||
|
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
|
||||||
|
## openfeature generate react
|
||||||
|
|
||||||
|
Generate typesafe React Hooks.
|
||||||
|
|
||||||
|
### Synopsis
|
||||||
|
|
||||||
|
Generate typesafe React Hooks compatible with the OpenFeature React SDK.
|
||||||
|
|
||||||
|
```
|
||||||
|
openfeature generate react [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for react
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options inherited from parent commands
|
||||||
|
|
||||||
|
```
|
||||||
|
--flag_manifest_path string Path to the flag manifest.
|
||||||
|
--output_path string Output path for the codegen
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [openfeature generate](openfeature_generate.md) - Code generation for flag accessors for OpenFeature.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<!-- markdownlint-disable-file -->
|
||||||
|
<!-- WARNING: THIS DOC IS AUTO-GENERATED. DO NOT EDIT! -->
|
||||||
|
## openfeature version
|
||||||
|
|
||||||
|
Print the version number of the OpenFeature CLI
|
||||||
|
|
||||||
|
```
|
||||||
|
openfeature version [flags]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
```
|
||||||
|
-h, --help help for version
|
||||||
|
```
|
||||||
|
|
||||||
|
### SEE ALSO
|
||||||
|
|
||||||
|
* [openfeature](openfeature.md) - CLI for OpenFeature.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/open-feature/cli/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
const docPath = "./docs/commands"
|
||||||
|
|
||||||
|
// GenerateDoc generates cobra docs of the cmd
|
||||||
|
func main() {
|
||||||
|
if err := cmd.GenerateDoc(docPath); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
|
|
@ -9,12 +9,14 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -1,3 +1,4 @@
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
|
@ -30,6 +31,7 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
flagmanifest "github.com/open-feature/cli/docs/schema/v0"
|
|
||||||
"github.com/open-feature/cli/internal/filesystem"
|
"github.com/open-feature/cli/internal/filesystem"
|
||||||
"github.com/open-feature/cli/internal/flagkeys"
|
"github.com/open-feature/cli/internal/flagkeys"
|
||||||
"github.com/open-feature/cli/internal/generate/types"
|
"github.com/open-feature/cli/internal/generate/types"
|
||||||
|
flagmanifest "github.com/open-feature/cli/schema/v0"
|
||||||
|
|
||||||
"github.com/santhosh-tekuri/jsonschema/v5"
|
"github.com/santhosh-tekuri/jsonschema/v5"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package react
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
|
|
@ -48,32 +47,6 @@ func flagInitParam(flagName string) string {
|
||||||
return strconv.Quote(flagName)
|
return strconv.Quote(flagName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func flagAccessFunc(t types.FlagType) string {
|
|
||||||
switch t {
|
|
||||||
case types.IntType, types.FloatType:
|
|
||||||
return "useNumberFlagDetails"
|
|
||||||
case types.BoolType:
|
|
||||||
return "useBooleanFlagDetails"
|
|
||||||
case types.StringType:
|
|
||||||
return "useStringFlagDetails"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func supportImports(flags []*types.FlagTmplData) []string {
|
|
||||||
imports := make(map[string]struct{})
|
|
||||||
for _, flag := range flags {
|
|
||||||
imports[flagAccessFunc(flag.Type)] = struct{}{}
|
|
||||||
}
|
|
||||||
var result []string
|
|
||||||
for k := range imports {
|
|
||||||
result = append(result, k)
|
|
||||||
}
|
|
||||||
sort.Strings(result)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultValueLiteral(flag *types.FlagTmplData) string {
|
func defaultValueLiteral(flag *types.FlagTmplData) string {
|
||||||
switch flag.Type {
|
switch flag.Type {
|
||||||
case types.StringType:
|
case types.StringType:
|
||||||
|
|
@ -100,8 +73,6 @@ func (g *genImpl) Generate(input types.Input) error {
|
||||||
funcs := template.FuncMap{
|
funcs := template.FuncMap{
|
||||||
"FlagVarName": flagVarName,
|
"FlagVarName": flagVarName,
|
||||||
"FlagInitParam": flagInitParam,
|
"FlagInitParam": flagInitParam,
|
||||||
"FlagAccessFunc": flagAccessFunc,
|
|
||||||
"SupportImports": supportImports,
|
|
||||||
"DefaultValueLiteral": defaultValueLiteral,
|
"DefaultValueLiteral": defaultValueLiteral,
|
||||||
"TypeString": typeString,
|
"TypeString": typeString,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
{{- range $_, $p := SupportImports .Flags}}
|
type ReactFlagEvaluationOptions,
|
||||||
{{$p}},
|
type ReactFlagEvaluationNoSuspenseOptions,
|
||||||
{{- end}}
|
useFlag,
|
||||||
|
useSuspenseFlag,
|
||||||
} from "@openfeature/react-sdk";
|
} from "@openfeature/react-sdk";
|
||||||
{{ range .Flags}}
|
{{ range .Flags}}
|
||||||
/**
|
/**
|
||||||
|
|
@ -11,10 +12,25 @@ import {
|
||||||
*
|
*
|
||||||
* **Details:**
|
* **Details:**
|
||||||
* - flag key: `{{ .Name}}`
|
* - flag key: `{{ .Name}}`
|
||||||
* - default value: `{{ .DefaultValue}}`
|
* - default value: `{{ .DefaultValue }}`
|
||||||
* - type: `{{TypeString .Type}}`
|
* - type: `{{TypeString .Type}}`
|
||||||
*/
|
*/
|
||||||
export const use{{FlagVarName .Name}} = (options: Parameters<typeof {{FlagAccessFunc .Type}}>[2]) => {
|
export const use{{FlagVarName .Name}} = (options: ReactFlagEvaluationOptions) => {
|
||||||
return {{FlagAccessFunc .Type}}({{FlagInitParam .Name}}, {{DefaultValueLiteral .}}, options);
|
return useFlag({{FlagInitParam .Name}}, {{DefaultValueLiteral .}}, options);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {{.Docs}}
|
||||||
|
*
|
||||||
|
* **Details:**
|
||||||
|
* - flag key: `{{ .Name}}`
|
||||||
|
* - default value: `{{ .DefaultValue }}`
|
||||||
|
* - type: `{{TypeString .Type}}`
|
||||||
|
*
|
||||||
|
* Equivalent to useFlag with options: `{ suspend: true }`
|
||||||
|
* @experimental — Suspense is an experimental feature subject to change in future versions.
|
||||||
|
*/
|
||||||
|
export const useSuspense{{FlagVarName .Name}} = (options: ReactFlagEvaluationNoSuspenseOptions) => {
|
||||||
|
return useSuspenseFlag({{FlagInitParam .Name}}, {{DefaultValueLiteral .}}, options);
|
||||||
};
|
};
|
||||||
{{ end}}
|
{{ end}}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"$id": "https://raw.githubusercontent.com/open-feature/cli/refs/heads/main/schema/v0/flag_manifest.json",
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||||
"title": "Flag Manifest",
|
"title": "Flag Manifest",
|
||||||
"description": "Describes a configuration of OpenFeature flags, including info such as their types and default values.",
|
"description": "Describes a configuration of OpenFeature flags, including info such as their types and default values.",
|
||||||
|
|
@ -13,10 +14,13 @@
|
||||||
"$ref": "#/$defs/flag"
|
"$ref": "#/$defs/flag"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false,
|
||||||
|
"minProperties": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["flags"],
|
"required": [
|
||||||
|
"flags"
|
||||||
|
],
|
||||||
"$defs": {
|
"$defs": {
|
||||||
"flag": {
|
"flag": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
|
|
@ -36,30 +40,37 @@
|
||||||
"$ref": "#/$defs/objectType"
|
"$ref": "#/$defs/objectType"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"required": ["flagType", "defaultValue"]
|
"required": [
|
||||||
|
"flagType",
|
||||||
|
"defaultValue"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"booleanType": {
|
"booleanType": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"flagType": {
|
"flagType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["boolean"]
|
"enum": [
|
||||||
|
"boolean"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"defaultValue": {
|
"defaultValue": {
|
||||||
|
"description": "The default value returned in code if a flag evaluation is unsuccessful",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
},
|
},
|
||||||
"stringType": {
|
"stringType": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"flagType": {
|
"flagType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["string"]
|
"enum": [
|
||||||
|
"string"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"defaultValue": {
|
"defaultValue": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|
@ -67,15 +78,16 @@
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
},
|
},
|
||||||
"integerType": {
|
"integerType": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"flagType": {
|
"flagType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["integer"]
|
"enum": [
|
||||||
|
"integer"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"defaultValue": {
|
"defaultValue": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
|
@ -83,15 +95,16 @@
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
},
|
},
|
||||||
"floatType": {
|
"floatType": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"flagType": {
|
"flagType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["float"]
|
"enum": [
|
||||||
|
"float"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"defaultValue": {
|
"defaultValue": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
|
|
@ -99,15 +112,16 @@
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
},
|
},
|
||||||
"objectType": {
|
"objectType": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"flagType": {
|
"flagType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["object"]
|
"enum": [
|
||||||
|
"object"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"defaultValue": {
|
"defaultValue": {
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|
@ -115,8 +129,7 @@
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"additionalProperties": false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,5 +8,5 @@ import _ "embed"
|
||||||
//go:embed flag_manifest.json
|
//go:embed flag_manifest.json
|
||||||
var Schema string
|
var Schema string
|
||||||
|
|
||||||
// SchemaPath proviees the current path and version of flag manifest.
|
// SchemaPath provides the current path and version of flag manifest.
|
||||||
const SchemaPath = "github.com/open-feature/cli/docs/schema/v0/flag_manifest.json"
|
const SchemaPath = "github.com/open-feature/cli/schema/v0/flag_manifest.json"
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package flagmanifest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/santhosh-tekuri/jsonschema/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var compiledFlagManifestSchema *jsonschema.Schema
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sch, err := jsonschema.CompileString(SchemaPath, Schema)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(fmt.Errorf("error compiling JSON schema: %v", err))
|
||||||
|
}
|
||||||
|
compiledFlagManifestSchema = sch
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPositiveFlagManifest(t *testing.T) {
|
||||||
|
if err := walkPath(true, "./tests/positive"); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNegativeFlagManifest(t *testing.T) {
|
||||||
|
if err := walkPath(false, "./tests/negative"); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func walkPath(shouldPass bool, root string) error {
|
||||||
|
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ps := strings.Split(path, ".")
|
||||||
|
if ps[len(ps)-1] != "json" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var v interface{}
|
||||||
|
if err := json.Unmarshal([]byte(file), &v); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = compiledFlagManifestSchema.Validate(v)
|
||||||
|
|
||||||
|
if (err != nil && shouldPass == true) {
|
||||||
|
return fmt.Errorf("file %s should not have failed validation, but did: %s", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err == nil && shouldPass == false) {
|
||||||
|
return fmt.Errorf("file %s should have failed validation, but did not", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"$schema": "../../flag_manifest.json",
|
||||||
|
"flags": {
|
||||||
|
"booleanFlag": {
|
||||||
|
"codeDefault": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"$schema": "../../flag_manifest.json",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"$schema": "../../flag_manifest.json",
|
||||||
|
"flags": {
|
||||||
|
"booleanFlag": {
|
||||||
|
"flagType": "boolean",
|
||||||
|
"defaultValue": true
|
||||||
|
},
|
||||||
|
"stringFlag": {
|
||||||
|
"flagType": "string",
|
||||||
|
"defaultValue": "default"
|
||||||
|
},
|
||||||
|
"integerFlag": {
|
||||||
|
"flagType": "integer",
|
||||||
|
"defaultValue": 50
|
||||||
|
},
|
||||||
|
"floatFlag": {
|
||||||
|
"flagType": "float",
|
||||||
|
"defaultValue": 0.15
|
||||||
|
},
|
||||||
|
"objectFlag": {
|
||||||
|
"flagType": "object",
|
||||||
|
"defaultValue": {
|
||||||
|
"primaryColor": "#007bff",
|
||||||
|
"secondaryColor": "#6c757d"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue