From 0e6a4861368f69c8ed109f44464f94240567f110 Mon Sep 17 00:00:00 2001 From: Michael Samper Date: Wed, 8 Jan 2025 15:57:29 -0800 Subject: [PATCH] feat: Create a Growthbook server side provider (#938) Signed-off-by: Michael Samper --- .release-please-manifest.json | 3 +- libs/providers/growthbook/.eslintrc.json | 25 +++ libs/providers/growthbook/README.md | 45 +++++ libs/providers/growthbook/babel.config.json | 3 + libs/providers/growthbook/jest.config.ts | 10 + libs/providers/growthbook/package.json | 17 ++ libs/providers/growthbook/project.json | 76 ++++++++ libs/providers/growthbook/src/index.ts | 1 + .../src/lib/growthbook-provider.spec.ts | 182 ++++++++++++++++++ .../growthbook/src/lib/growthbook-provider.ts | 114 +++++++++++ .../src/lib/translate-result.spec.ts | 74 +++++++ .../growthbook/src/lib/translate-result.ts | 33 ++++ libs/providers/growthbook/tsconfig.json | 22 +++ libs/providers/growthbook/tsconfig.lib.json | 10 + libs/providers/growthbook/tsconfig.spec.json | 9 + package-lock.json | 8 +- package.json | 2 +- release-please-config.json | 7 + tsconfig.base.json | 1 + 19 files changed, 636 insertions(+), 6 deletions(-) create mode 100644 libs/providers/growthbook/.eslintrc.json create mode 100644 libs/providers/growthbook/README.md create mode 100644 libs/providers/growthbook/babel.config.json create mode 100644 libs/providers/growthbook/jest.config.ts create mode 100644 libs/providers/growthbook/package.json create mode 100644 libs/providers/growthbook/project.json create mode 100644 libs/providers/growthbook/src/index.ts create mode 100644 libs/providers/growthbook/src/lib/growthbook-provider.spec.ts create mode 100644 libs/providers/growthbook/src/lib/growthbook-provider.ts create mode 100644 libs/providers/growthbook/src/lib/translate-result.spec.ts create mode 100644 libs/providers/growthbook/src/lib/translate-result.ts create mode 100644 libs/providers/growthbook/tsconfig.json create mode 100644 libs/providers/growthbook/tsconfig.lib.json create mode 100644 libs/providers/growthbook/tsconfig.spec.json diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b91980a0c..c342e4a95 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -18,5 +18,6 @@ "libs/providers/multi-provider-web": "0.0.3", "libs/providers/growthbook-client": "0.1.2", "libs/providers/config-cat-web": "0.1.3", - "libs/shared/config-cat-core": "0.1.0" + "libs/shared/config-cat-core": "0.1.0", + "libs/providers/growthbook": "0.1.1" } diff --git a/libs/providers/growthbook/.eslintrc.json b/libs/providers/growthbook/.eslintrc.json new file mode 100644 index 000000000..3230caf3d --- /dev/null +++ b/libs/providers/growthbook/.eslintrc.json @@ -0,0 +1,25 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": "error" + } + } + ] +} diff --git a/libs/providers/growthbook/README.md b/libs/providers/growthbook/README.md new file mode 100644 index 000000000..b831112d2 --- /dev/null +++ b/libs/providers/growthbook/README.md @@ -0,0 +1,45 @@ +# growthbook Provider + +## Installation + +``` +$ npm install @openfeature/growthbook-provider +``` + +## Example Setup + +```typescript +import { GrowthBookClient, ClientOptions, InitOptions } from '@growthbook/growthbook'; +import { GrowthbookProvider } from '@openfeature/growthbook-provider'; +import { OpenFeature } from '@openfeature/server-sdk'; + +/* + * Configure your GrowthBook instance with GrowthBook context + * @see https://docs.growthbook.io/lib/js#step-1-configure-your-app + */ +const gbClientOptions: ClientOptions = { + apiHost: 'https://cdn.growthbook.io', + clientKey: 'sdk-abc123', + // Only required if you have feature encryption enabled in GrowthBook + decryptionKey: 'key_abc123', +}; + +/* + * optional init options + * @see https://docs.growthbook.io/lib/js#switching-to-init + */ +const initOptions: InitOptions = { + timeout: 2000, + streaming: true, +}; + +OpenFeature.setProvider(new GrowthbookProvider(gbClientOptions, initOptions)); +``` + +## Building + +Run `nx package providers-growthbook` to build the library. + +## Running unit tests + +Run `nx test providers-growthbook` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/providers/growthbook/babel.config.json b/libs/providers/growthbook/babel.config.json new file mode 100644 index 000000000..d7bf474d1 --- /dev/null +++ b/libs/providers/growthbook/babel.config.json @@ -0,0 +1,3 @@ +{ + "presets": [["minify", { "builtIns": false }]] +} diff --git a/libs/providers/growthbook/jest.config.ts b/libs/providers/growthbook/jest.config.ts new file mode 100644 index 000000000..18779f811 --- /dev/null +++ b/libs/providers/growthbook/jest.config.ts @@ -0,0 +1,10 @@ +/* eslint-disable */ +export default { + displayName: 'providers-growthbook', + preset: '../../../jest.preset.js', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../../coverage/libs/providers/growthbook', +}; diff --git a/libs/providers/growthbook/package.json b/libs/providers/growthbook/package.json new file mode 100644 index 000000000..1fcb9b3fd --- /dev/null +++ b/libs/providers/growthbook/package.json @@ -0,0 +1,17 @@ +{ + "name": "@openfeature/growthbook-provider", + "version": "0.1.1", + "dependencies": { + "tslib": "^2.3.0" + }, + "main": "./src/index.js", + "typings": "./src/index.d.ts", + "scripts": { + "publish-if-not-exists": "cp $NPM_CONFIG_USERCONFIG .npmrc && if [ \"$(npm show $npm_package_name@$npm_package_version version)\" = \"$(npm run current-version -s)\" ]; then echo 'already published, skipping'; else npm publish --access public; fi", + "current-version": "echo $npm_package_version" + }, + "peerDependencies": { + "@growthbook/growthbook": "^1.3.1", + "@openfeature/server-sdk": "^1.13.0" + } +} diff --git a/libs/providers/growthbook/project.json b/libs/providers/growthbook/project.json new file mode 100644 index 000000000..40f76627d --- /dev/null +++ b/libs/providers/growthbook/project.json @@ -0,0 +1,76 @@ +{ + "name": "providers-growthbook", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/providers/growthbook/src", + "projectType": "library", + "targets": { + "publish": { + "executor": "nx:run-commands", + "options": { + "command": "npm run publish-if-not-exists", + "cwd": "dist/libs/providers/growthbook" + }, + "dependsOn": [ + { + "projects": "self", + "target": "package" + } + ] + }, + "lint": { + "executor": "@nx/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["libs/providers/growthbook/**/*.ts", "libs/providers/growthbook/package.json"] + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/providers/growthbook/jest.config.ts", + "passWithNoTests": true + }, + "configurations": { + "ci": { + "ci": true, + "codeCoverage": true + } + } + }, + "package": { + "executor": "@nx/rollup:rollup", + "outputs": ["{options.outputPath}"], + "options": { + "project": "libs/providers/growthbook/package.json", + "outputPath": "dist/libs/providers/growthbook", + "entryFile": "libs/providers/growthbook/src/index.ts", + "tsConfig": "libs/providers/growthbook/tsconfig.lib.json", + "buildableProjectDepsInPackageJsonType": "dependencies", + "compiler": "tsc", + "generateExportsField": true, + "umdName": "growthbook", + "external": "all", + "format": ["cjs", "esm"], + "assets": [ + { + "glob": "package.json", + "input": "./assets", + "output": "./src/" + }, + { + "glob": "LICENSE", + "input": "./", + "output": "./" + }, + { + "glob": "README.md", + "input": "./libs/providers/growthbook", + "output": "./" + } + ] + } + } + }, + "tags": [] +} diff --git a/libs/providers/growthbook/src/index.ts b/libs/providers/growthbook/src/index.ts new file mode 100644 index 000000000..854ccc7b3 --- /dev/null +++ b/libs/providers/growthbook/src/index.ts @@ -0,0 +1 @@ +export * from './lib/growthbook-provider'; diff --git a/libs/providers/growthbook/src/lib/growthbook-provider.spec.ts b/libs/providers/growthbook/src/lib/growthbook-provider.spec.ts new file mode 100644 index 000000000..bb92c6cef --- /dev/null +++ b/libs/providers/growthbook/src/lib/growthbook-provider.spec.ts @@ -0,0 +1,182 @@ +import { ClientOptions, GrowthBookClient, InitOptions } from '@growthbook/growthbook'; +import { GrowthbookProvider } from './growthbook-provider'; +import { Client, OpenFeature } from '@openfeature/server-sdk'; + +jest.mock('@growthbook/growthbook'); + +const testFlagKey = 'flag-key'; +const growthbookOptionsMock: ClientOptions = { + apiHost: 'http://api.growthbook.io', + clientKey: 'sdk-test-key', + globalAttributes: { + id: 1, + }, +}; + +const initOptionsMock: InitOptions = { + timeout: 5000, +}; + +describe('GrowthbookProvider', () => { + let gbProvider: GrowthbookProvider; + let ofClient: Client; + + beforeAll(() => { + gbProvider = new GrowthbookProvider(growthbookOptionsMock, initOptionsMock); + OpenFeature.setProvider(gbProvider); + ofClient = OpenFeature.getClient(); + }); + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should be and instance of GrowthbookProvider', () => { + expect(new GrowthbookProvider(growthbookOptionsMock, initOptionsMock)).toBeInstanceOf(GrowthbookProvider); + }); + + describe('constructor', () => { + it('should set the growthbook options & initOptions correctly', () => { + const provider = new GrowthbookProvider(growthbookOptionsMock, initOptionsMock); + + expect(provider['options']).toEqual(growthbookOptionsMock); + expect(provider['_initOptions']).toEqual(initOptionsMock); + }); + }); + + describe('initialize', () => { + const provider = new GrowthbookProvider(growthbookOptionsMock, initOptionsMock); + + it('should call growthbook initialize function with correct arguments', async () => { + const evalContext = { serverIp: '10.1.1.1' }; + await provider.initialize({ serverIp: '10.1.1.1' }); + + const options = { + ...provider['options'], + globalAttributes: { ...provider['options'].globalAttributes, ...evalContext }, + }; + + expect(GrowthBookClient).toHaveBeenCalledWith(options); + expect(provider['_client']?.init).toHaveBeenCalledWith(initOptionsMock); + }); + }); + + describe('resolveBooleanEvaluation', () => { + it('handles correct return types for boolean variations', async () => { + jest.spyOn(GrowthBookClient.prototype, 'evalFeature').mockImplementation(() => ({ + value: true, + source: 'experiment', + on: true, + off: false, + ruleId: 'test', + experimentResult: { + value: true, + variationId: 1, + key: 'treatment', + inExperiment: true, + hashAttribute: 'id', + hashValue: 'abc', + featureId: testFlagKey, + }, + })); + + const res = await ofClient.getBooleanDetails(testFlagKey, false); + expect(res).toEqual({ + flagKey: testFlagKey, + flagMetadata: {}, + value: true, + reason: 'experiment', + variant: 'treatment', + }); + }); + }); + + describe('resolveStringEvaluation', () => { + it('handles correct return types for string variations', async () => { + jest.spyOn(GrowthBookClient.prototype, 'evalFeature').mockImplementation(() => ({ + value: 'Experiment fearlessly, deliver confidently', + source: 'experiment', + on: true, + off: false, + ruleId: 'test', + experimentResult: { + value: 'Experiment fearlessly, deliver confidently', + variationId: 1, + key: 'treatment', + inExperiment: true, + hashAttribute: 'id', + hashValue: 'abc', + featureId: testFlagKey, + }, + })); + + const res = await ofClient.getStringDetails(testFlagKey, ''); + expect(res).toEqual({ + flagKey: testFlagKey, + flagMetadata: {}, + value: 'Experiment fearlessly, deliver confidently', + reason: 'experiment', + variant: 'treatment', + }); + }); + }); + + describe('resolveNumberEvaluation', () => { + it('handles correct return types for number variations', async () => { + jest.spyOn(GrowthBookClient.prototype, 'evalFeature').mockImplementation(() => ({ + value: 12345, + source: 'experiment', + on: true, + off: false, + ruleId: 'test', + experimentResult: { + value: 12345, + variationId: 1, + key: 'treatment', + inExperiment: true, + hashAttribute: 'id', + hashValue: 'abc', + featureId: testFlagKey, + }, + })); + + const res = await ofClient.getNumberDetails(testFlagKey, 1); + expect(res).toEqual({ + flagKey: testFlagKey, + flagMetadata: {}, + value: 12345, + reason: 'experiment', + variant: 'treatment', + }); + }); + }); + + describe('resolveObjectEvaluation', () => { + it('handles correct return types for object variations', async () => { + jest.spyOn(GrowthBookClient.prototype, 'evalFeature').mockImplementation(() => ({ + value: { test: true }, + source: 'experiment', + on: true, + off: false, + ruleId: 'test', + experimentResult: { + value: { test: true }, + variationId: 1, + key: 'treatment', + inExperiment: true, + hashAttribute: 'id', + hashValue: 'abc', + featureId: testFlagKey, + }, + })); + + const res = await ofClient.getObjectDetails(testFlagKey, {}); + expect(res).toEqual({ + flagKey: testFlagKey, + flagMetadata: {}, + value: { test: true }, + reason: 'experiment', + variant: 'treatment', + }); + }); + }); +}); diff --git a/libs/providers/growthbook/src/lib/growthbook-provider.ts b/libs/providers/growthbook/src/lib/growthbook-provider.ts new file mode 100644 index 000000000..63b0f5468 --- /dev/null +++ b/libs/providers/growthbook/src/lib/growthbook-provider.ts @@ -0,0 +1,114 @@ +import { ClientOptions, GrowthBookClient, InitOptions } from '@growthbook/growthbook'; +import { + EvaluationContext, + Provider, + JsonValue, + ResolutionDetails, + OpenFeatureEventEmitter, + GeneralError, + ProviderEvents, +} from '@openfeature/server-sdk'; +import translateResult from './translate-result'; + +export class GrowthbookProvider implements Provider { + metadata = { + name: GrowthbookProvider.name, + }; + + readonly runsOn = 'server'; + private _client?: GrowthBookClient; + private readonly options: ClientOptions; + private _initOptions?: InitOptions; + public readonly events = new OpenFeatureEventEmitter(); + + constructor(growthbookOptions: ClientOptions, initOptions?: InitOptions) { + this.options = growthbookOptions; + this._initOptions = initOptions; + } + + private get client(): GrowthBookClient { + if (!this._client) { + throw new GeneralError('Provider is not initialized'); + } + return this._client; + } + + // the global (or static) context is passed to the initialization function + async initialize(evalContext?: EvaluationContext): Promise { + // Use context to construct the instance to instantiate GrowthBook + const globalContext = { + globalAttributes: { ...this.options.globalAttributes, ...evalContext }, + }; + this._client = new GrowthBookClient({ ...this.options, ...globalContext }); + + await this.client.init(this._initOptions); + + // Monkey-patch the setPayload function to fire an event + const setPayload = this._client.setPayload.bind(this._client); + + this._client.setPayload = async (...args) => { + await setPayload(...args); + this.events.emit(ProviderEvents.ConfigurationChanged); + }; + } + + async onClose(): Promise { + return this.client.destroy(); + } + + async resolveBooleanEvaluation( + flagKey: string, + defaultValue: boolean, + context: EvaluationContext, + ): Promise> { + const userContext = { + attributes: context, + }; + + const res = this.client.evalFeature(flagKey, userContext); + + return translateResult(res, defaultValue); + } + + async resolveStringEvaluation( + flagKey: string, + defaultValue: string, + context: EvaluationContext, + ): Promise> { + const userContext = { + attributes: context, + }; + + const res = this.client.evalFeature(flagKey, userContext); + + return translateResult(res, defaultValue); + } + + async resolveNumberEvaluation( + flagKey: string, + defaultValue: number, + context: EvaluationContext, + ): Promise> { + const userContext = { + attributes: context, + }; + + const res = this.client.evalFeature(flagKey, userContext); + + return translateResult(res, defaultValue); + } + + async resolveObjectEvaluation( + flagKey: string, + defaultValue: U, + context: EvaluationContext, + ): Promise> { + const userContext = { + attributes: context, + }; + + const res = this.client.evalFeature(flagKey, userContext); + + return translateResult(res, defaultValue); + } +} diff --git a/libs/providers/growthbook/src/lib/translate-result.spec.ts b/libs/providers/growthbook/src/lib/translate-result.spec.ts new file mode 100644 index 000000000..ae084a1ba --- /dev/null +++ b/libs/providers/growthbook/src/lib/translate-result.spec.ts @@ -0,0 +1,74 @@ +import { TypeMismatchError } from '@openfeature/core'; +import translateResult from './translate-result'; + +describe('translateResult', () => { + it('does populate the errorCode correctly when there is an error', () => { + const translated = translateResult( + { + value: true, + source: 'unknownFeature', + on: true, + off: false, + ruleId: 'test', + experimentResult: { + value: true, + variationId: 1, + key: 'treatment', + inExperiment: true, + hashAttribute: 'id', + hashValue: 'abc', + featureId: 'testFlagKey', + }, + }, + false, + ); + expect(translated.errorCode).toEqual('FLAG_NOT_FOUND'); + }); + + it('does not populate the errorCode when there is not an error', () => { + const translated = translateResult( + { + value: true, + source: 'defaultValue', + on: true, + off: false, + ruleId: 'test', + experimentResult: { + value: true, + variationId: 1, + key: 'treatment', + inExperiment: true, + hashAttribute: 'id', + hashValue: 'abc', + featureId: 'testFlagKey', + }, + }, + false, + ); + expect(translated.errorCode).toBeUndefined(); + }); + + it('throws an error when result type differs from defaultValue type', () => { + expect(() => + translateResult( + { + value: 'test', + source: 'defaultValue', + on: true, + off: false, + ruleId: 'test', + experimentResult: { + value: 'test', + variationId: 1, + key: 'treatment', + inExperiment: true, + hashAttribute: 'id', + hashValue: 'abc', + featureId: 'testFlagKey', + }, + }, + false, + ), + ).toThrow(TypeMismatchError); + }); +}); diff --git a/libs/providers/growthbook/src/lib/translate-result.ts b/libs/providers/growthbook/src/lib/translate-result.ts new file mode 100644 index 000000000..374dc4ad4 --- /dev/null +++ b/libs/providers/growthbook/src/lib/translate-result.ts @@ -0,0 +1,33 @@ +import { FeatureResult } from '@growthbook/growthbook'; +import { ErrorCode, ResolutionDetails, TypeMismatchError } from '@openfeature/web-sdk'; + +const FEATURE_RESULT_ERRORS = ['unknownFeature', 'cyclicPrerequisite']; + +function translateError(errorKind?: string): ErrorCode { + switch (errorKind) { + case 'unknownFeature': + return ErrorCode.FLAG_NOT_FOUND; + case 'cyclicPrerequisite': + return ErrorCode.PARSE_ERROR; + default: + return ErrorCode.GENERAL; + } +} + +export default function translateResult(result: FeatureResult, defaultValue: T): ResolutionDetails { + if (result.value !== null && typeof result.value !== typeof defaultValue) { + throw new TypeMismatchError(`Expected flag type ${typeof defaultValue} but got ${typeof result.value}`); + } + + const resolution: ResolutionDetails = { + value: result.value === null ? defaultValue : result.value, + reason: result.source, + variant: result.experimentResult?.key, + }; + + if (FEATURE_RESULT_ERRORS.includes(result.source)) { + resolution.errorCode = translateError(result.source); + } + + return resolution; +} diff --git a/libs/providers/growthbook/tsconfig.json b/libs/providers/growthbook/tsconfig.json new file mode 100644 index 000000000..140e5a783 --- /dev/null +++ b/libs/providers/growthbook/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "module": "ES6", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/providers/growthbook/tsconfig.lib.json b/libs/providers/growthbook/tsconfig.lib.json new file mode 100644 index 000000000..4befa7f09 --- /dev/null +++ b/libs/providers/growthbook/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/libs/providers/growthbook/tsconfig.spec.json b/libs/providers/growthbook/tsconfig.spec.json new file mode 100644 index 000000000..b2ee74a6b --- /dev/null +++ b/libs/providers/growthbook/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/package-lock.json b/package-lock.json index ba0011be0..7581e3cd8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@connectrpc/connect-web": "^1.4.0", "@flipt-io/flipt": "^1.2.0", "@flipt-io/flipt-client-browser": "^0.3.1", - "@growthbook/growthbook": "^1.0.0", + "@growthbook/growthbook": "^1.3.1", "@grpc/grpc-js": "^1.9.13", "@opentelemetry/api": "^1.3.0", "@protobuf-ts/grpc-transport": "^2.9.0", @@ -2431,9 +2431,9 @@ "integrity": "sha512-1MFuQuHRENnzVooxrfQjFBLNBfE5uGBJmF2NuPFXTYMZn+sGelFovuNVuKlHqegI3Dqzz9Al2qJlkeFo+MhHxg==" }, "node_modules/@growthbook/growthbook": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@growthbook/growthbook/-/growthbook-1.0.0.tgz", - "integrity": "sha512-qfVhZcubsGjJI8oNjZp5g69N7c+NMxWl3WS05Sa93V6qNjE9Uz2PZqj7Qj+6iTEOZ1ignmEsIBJEKfdlpa9INg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@growthbook/growthbook/-/growthbook-1.3.1.tgz", + "integrity": "sha512-ewtwq6+86rRKwcYUXEmBVR1JuiEIYZhxow/Z52qyAxJwEHdXmpS4Yk8sVeVD9bphCwE2r0zuifxFkBxmnIL4Mg==", "dependencies": { "dom-mutator": "^0.6.0" }, diff --git a/package.json b/package.json index be1f1ca79..9db94f989 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "@connectrpc/connect-web": "^1.4.0", "@flipt-io/flipt": "^1.2.0", "@flipt-io/flipt-client-browser": "^0.3.1", - "@growthbook/growthbook": "^1.0.0", + "@growthbook/growthbook": "^1.3.1", "@grpc/grpc-js": "^1.9.13", "@opentelemetry/api": "^1.3.0", "@protobuf-ts/grpc-transport": "^2.9.0", diff --git a/release-please-config.json b/release-please-config.json index 4746c948f..131ecdc42 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -141,6 +141,13 @@ "bump-minor-pre-major": true, "bump-patch-for-minor-pre-major": true, "versioning": "default" + }, + "libs/providers/growthbook": { + "release-type": "node", + "prerelease": true, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "versioning": "default" } }, "changelog-sections": [ diff --git a/tsconfig.base.json b/tsconfig.base.json index 4a9e01f6c..782492ddd 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -30,6 +30,7 @@ "@openfeature/go-feature-flag-provider": ["libs/providers/go-feature-flag/src/index.ts"], "@openfeature/go-feature-flag-web-provider": ["libs/providers/go-feature-flag-web/src/index.ts"], "@openfeature/growthbook-client-provider": ["libs/providers/growthbook-client/src/index.ts"], + "@openfeature/growthbook-provider": ["libs/providers/growthbook/src/index.ts"], "@openfeature/hooks-open-telemetry": ["libs/hooks/open-telemetry/src/index.ts"], "@openfeature/launchdarkly-client-provider": ["libs/providers/launchdarkly-client/src/index.ts"], "@openfeature/multi-provider": ["libs/providers/multi-provider/src/index.ts"],