Skip to content

Commit 896ffb8

Browse files
committed
Merge remote-tracking branch 'origin/main' into HEAD
2 parents 4c9a8f8 + a76a805 commit 896ffb8

File tree

11 files changed

+494
-90
lines changed

11 files changed

+494
-90
lines changed

.eslintrc.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module.exports = {
33
env: {
44
es6: true,
55
jest: true,
6+
node: true,
67
},
78
extends: [
89
'eslint:recommended',
@@ -88,5 +89,11 @@ module.exports = {
8889
'no-restricted-globals': 'off',
8990
},
9091
},
92+
{
93+
files: ['src/tools/**/*.ts'],
94+
rules: {
95+
'no-restricted-globals': 'off',
96+
},
97+
},
9198
],
9299
};

README.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,78 @@ When publishing releases, the following rules apply:
5050
**Note**: The release will not be published if:
5151
- A pre-release is marked as "latest"
5252
- A pre-release label is used without checking "Set as pre-release"
53+
54+
## Tools
55+
56+
### Bootstrap Configuration
57+
58+
You can generate a bootstrap configuration string from either the command line or programmatically via the
59+
ConfigurationWireHelper class.
60+
61+
The tool allows you to specify the target SDK this configuration will be used on. It is important to correctly specify
62+
the intended SDK, as this determines whether the configuration is obfuscated (for client SDKs) or not (for server SDKs).
63+
64+
#### Command Line Usage
65+
66+
**Install as a project dependency:**
67+
```bash
68+
# Install as a dependency
69+
npm install --save-dev @eppo/js-client-sdk-common
70+
71+
# or, with yarn
72+
yarn add --dev @eppo/js-client-sdk-common
73+
```
74+
75+
Common usage examples:
76+
```bash
77+
# Basic usage
78+
yarn bootstrap-config --key <sdkKey> --output bootstrap-config.json
79+
80+
# With custom SDK name (default is 'js-client-sdk')
81+
yarn bootstrap-config --key <sdkKey> --sdk android
82+
83+
# With custom base URL
84+
yarn bootstrap-config --key <sdkKey> --base-url https://api.custom-domain.com
85+
86+
# Output configuration to stdout
87+
yarn bootstrap-config --key <sdkKey>
88+
89+
# Show help
90+
yarn bootstrap-config --help
91+
```
92+
93+
The tool accepts the following arguments:
94+
- `--key, -k`: SDK key (required, can also be set via EPPO_SDK_KEY environment variable)
95+
- `--sdk`: Target SDK name (default: 'js-client-sdk')
96+
- `--base-url`: Custom base URL for the API
97+
- `--output, -o`: Output file path (if not specified, outputs to console)
98+
- `--help, -h`: Show help
99+
100+
#### Programmatic Usage
101+
```typescript
102+
import { ConfigurationHelper } from '@eppo/js-client-sdk-common';
103+
104+
async function getBootstrapConfig() {
105+
// Initialize the helper
106+
const helper = ConfigurationHelper.build(
107+
'your-sdk-key',
108+
{
109+
sdkName: 'android', // optional: target SDK name (default: 'js-client-sdk')
110+
baseUrl: 'https://api.custom-domain.com', // optional: custom base URL
111+
});
112+
113+
// Fetch the configuration
114+
const config = await helper.fetchConfiguration();
115+
const configString = config.toString();
116+
117+
// You are responsible to transport this string to the client
118+
const clientInitialData = {eppoConfig: eppoConfigString};
119+
120+
// Client-side
121+
const client = getInstance();
122+
const initialConfig = configurationFromString(clientInitialData.eppoConfig);
123+
client.setInitialConfig(configurationFromString(configString));
124+
}
125+
```
126+
127+
The tool will output a JSON string containing the configuration wire format that can be used to bootstrap Eppo SDKs.

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
"typecheck": "tsc",
3232
"test": "yarn test:unit",
3333
"test:unit": "NODE_ENV=test jest '.*\\.spec\\.ts'",
34-
"obfuscate-mock-ufc": "ts-node test/writeObfuscatedMockUFC"
34+
"obfuscate-mock-ufc": "ts-node test/writeObfuscatedMockUFC",
35+
"bootstrap-config": "ts-node src/tools/get-bootstrap-config"
3536
},
3637
"jsdelivr": "dist/eppo-sdk.js",
3738
"repository": {
@@ -55,7 +56,7 @@
5556
"@typescript-eslint/parser": "^5.13.0",
5657
"eslint": "^8.17.0",
5758
"eslint-config-prettier": "^10.0.1",
58-
"eslint-import-resolver-typescript": "^3.7.0",
59+
"eslint-import-resolver-typescript": "^4.3.4",
5960
"eslint-plugin-import": "^2.25.4",
6061
"eslint-plugin-prettier": "5.2.3",
6162
"eslint-plugin-promise": "^7.2.1",
@@ -80,7 +81,8 @@
8081
"pino": "^9.5.0",
8182
"semver": "^7.5.4",
8283
"spark-md5": "^3.0.2",
83-
"uuid": "^11.0.5"
84+
"uuid": "^11.0.5",
85+
"yargs": "^17.7.2"
8486
},
8587
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
8688
}

src/client/eppo-client-assignment-details.spec.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import * as fs from 'fs';
22

3-
import { IAssignmentTestCase, readMockUfcConfiguration } from '../../test/testHelpers';
3+
import {
4+
AssignmentVariationValue,
5+
IAssignmentTestCase,
6+
readMockUfcConfiguration,
7+
} from '../../test/testHelpers';
48
import { AllocationEvaluationCode } from '../flag-evaluation-details-builder';
59
import { Variation, VariationType } from '../interfaces';
610
import { OperatorType } from '../rules';
@@ -291,8 +295,8 @@ describe('EppoClient get*AssignmentDetails', () => {
291295
flagKey: string,
292296
subjectKey: string,
293297
subjectAttributes: Record<string, AttributeType>,
294-
defaultValue: boolean | string | number | object,
295-
) => IAssignmentDetails<boolean | string | number | object>;
298+
defaultValue: AssignmentVariationValue,
299+
) => IAssignmentDetails<AssignmentVariationValue>;
296300
if (!assignmentFn) {
297301
throw new Error(`Unknown variation type: ${variationType}`);
298302
}

src/evaluator.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,6 @@ export class Evaluator {
185185
configFormat ?? '',
186186
);
187187
} catch (err: any) {
188-
console.error('>>>>', err);
189188
const flagEvaluationDetails = flagEvaluationDetailsBuilder.gracefulBuild(
190189
'ASSIGNMENT_ERROR',
191190
`Assignment Error: ${err.message}`,

src/http-client.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface IUniversalFlagConfigResponse {
4141
}
4242

4343
export interface IBanditParametersResponse {
44+
updatedAt: string; // ISO formatted string
4445
bandits: Record<string, BanditParameters>;
4546
}
4647

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import * as fs from 'fs';
2+
3+
import type { CommandModule } from 'yargs';
4+
5+
import ConfigurationRequestor from '../../configuration-requestor';
6+
import { BroadcastChannel } from '../../broadcast';
7+
import FetchHttpClient from '../../http-client';
8+
import ApiEndpoints from '../../api-endpoints';
9+
import { LIB_VERSION } from '../../version';
10+
11+
export const bootstrapConfigCommand: CommandModule = {
12+
command: 'bootstrap-config',
13+
describe: 'Generate a bootstrap configuration string',
14+
builder: (yargs) => {
15+
return yargs.options({
16+
key: {
17+
type: 'string',
18+
description: 'SDK key',
19+
alias: 'k',
20+
default: process.env.EPPO_SDK_KEY,
21+
},
22+
sdk: {
23+
type: 'string',
24+
description: 'Target SDK name',
25+
default: 'js-client-sdk',
26+
},
27+
'base-url': {
28+
type: 'string',
29+
description: 'Base URL for the API',
30+
},
31+
output: {
32+
type: 'string',
33+
description: 'Output file path',
34+
alias: 'o',
35+
},
36+
});
37+
},
38+
handler: async (argv) => {
39+
if (!argv.key) {
40+
console.error('Error: SDK key is required');
41+
console.error('Provide it either as:');
42+
console.error('- Command line argument: --key <sdkKey> or -k <sdkKey>');
43+
console.error('- Environment variable: EPPO_SDK_KEY');
44+
process.exit(1);
45+
}
46+
47+
try {
48+
const apiEndpoints = new ApiEndpoints({
49+
baseUrl: argv['base-url'] as string,
50+
queryParams: {
51+
apiKey: argv.key as string,
52+
sdkName: argv.sdk as string,
53+
sdkVersion: LIB_VERSION,
54+
},
55+
});
56+
57+
const httpClient = new FetchHttpClient(apiEndpoints, 10000);
58+
59+
const requestor = new ConfigurationRequestor(httpClient, new BroadcastChannel(), {
60+
wantsBandits: true,
61+
});
62+
63+
const config = await requestor.fetchConfiguration();
64+
65+
if (!config) {
66+
console.error('Error: Failed to fetch configuration');
67+
process.exit(1);
68+
}
69+
70+
const jsonConfig = JSON.stringify(config, null, 2);
71+
72+
if (argv.output && typeof argv.output === 'string') {
73+
fs.writeFileSync(argv.output, jsonConfig);
74+
console.log(`Configuration written to ${argv.output}`);
75+
} else {
76+
console.log('Configuration:');
77+
console.log('--------------------------------');
78+
console.log(jsonConfig);
79+
console.log('--------------------------------');
80+
}
81+
} catch (error) {
82+
console.error('Error fetching configuration:', error);
83+
process.exit(1);
84+
}
85+
},
86+
};

src/tools/get-bootstrap-config.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import yargs from 'yargs';
2+
import { hideBin } from 'yargs/helpers';
3+
4+
import { bootstrapConfigCommand } from './commands/bootstrap-config';
5+
6+
/**
7+
* Script to run the bootstrap-config command directly.
8+
*
9+
* For usage, run: `ts-node src/tools/get-bootstrap-config.ts --help`
10+
*/
11+
async function main() {
12+
await yargs(hideBin(process.argv))
13+
.command({
14+
command: '$0',
15+
describe: bootstrapConfigCommand.describe,
16+
builder: bootstrapConfigCommand.builder,
17+
handler: bootstrapConfigCommand.handler,
18+
})
19+
.help()
20+
.alias('help', 'h')
21+
.parse();
22+
}
23+
24+
main().catch((error) => {
25+
console.error('Error in main:', error);
26+
process.exit(1);
27+
});

src/tools/node-shim.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Required type shape for `process`.
2+
// We don't pin this project to node so eslint complains about the use of `process`. We declare a type shape here to
3+
// appease the linter.
4+
export declare const process: {
5+
exit: (code: number) => void;
6+
env: { [key: string]: string | undefined };
7+
argv: string[];
8+
};

0 commit comments

Comments
 (0)