zcb 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -11
- package/dist/main/cli.mjs +182 -0
- package/dist/main/cli.mjs.map +1 -0
- package/dist/main/index.mjs +321 -0
- package/dist/main/index.mjs.map +1 -0
- package/dist/main/templates/config.ts.hbs +4 -0
- package/package.json +1 -1
- package/src/__testUtils__/configReader.ts +7 -0
- package/src/cli.ts +3 -3
- package/src/createConfigBuilder.ts +6 -5
- package/src/createConfigParser.ts +16 -0
- package/src/createConfigReader.test.ts +9 -13
- package/src/createConfigReader.ts +4 -7
- package/src/index.ts +1 -0
- package/src/transformers/cloneNonEnumerableValues.ts +2 -2
- package/src/transformers/removeDisabledSlices.ts +7 -2
- package/src/transformers/runExperiments.ts +24 -0
- package/src/transformers/setupExperiments.ts +10 -6
- package/src/types.ts +30 -6
- package/src/utils/{importValidateWriteConfig.ts → importValidateTransformWriteConfig.ts} +7 -6
- package/src/utils/{writeConfig.ts → transformWriteConfig.ts} +1 -1
|
@@ -2,6 +2,7 @@ import { type JSONSchema7 } from 'json-schema';
|
|
|
2
2
|
import { type ZodError, type z } from 'zod';
|
|
3
3
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
4
4
|
import { cloneNonEnumerableValues } from './transformers/cloneNonEnumerableValues.ts';
|
|
5
|
+
import { NonEmumeralProperties } from './types.ts';
|
|
5
6
|
import { arrayHasInvalidDefaults } from './utils/arrayHasInvalidDefaults.ts';
|
|
6
7
|
import { isDerivedValueCallback } from './utils/isDerivedValueCallback.ts';
|
|
7
8
|
import { isInvalidPropertyOverride } from './utils/isInvalidPropertyOverride.ts';
|
|
@@ -53,7 +54,7 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
53
54
|
|
|
54
55
|
let config = initialValues as Config;
|
|
55
56
|
|
|
56
|
-
Object.defineProperty(config,
|
|
57
|
+
Object.defineProperty(config, NonEmumeralProperties.ZCB, {
|
|
57
58
|
configurable: false,
|
|
58
59
|
enumerable: false,
|
|
59
60
|
value: true,
|
|
@@ -63,7 +64,7 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
63
64
|
|
|
64
65
|
const configBuilder = {
|
|
65
66
|
disable: () => {
|
|
66
|
-
Object.defineProperty(config,
|
|
67
|
+
Object.defineProperty(config, NonEmumeralProperties.DISABLED, {
|
|
67
68
|
configurable: false,
|
|
68
69
|
enumerable: false,
|
|
69
70
|
value: true,
|
|
@@ -80,7 +81,7 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
80
81
|
}
|
|
81
82
|
},
|
|
82
83
|
experiment: (key: string) => {
|
|
83
|
-
Object.defineProperty(config,
|
|
84
|
+
Object.defineProperty(config, NonEmumeralProperties.EXPERIMENT, {
|
|
84
85
|
configurable: false,
|
|
85
86
|
enumerable: false,
|
|
86
87
|
value: key,
|
|
@@ -97,7 +98,7 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
97
98
|
const values = configBuilder.values();
|
|
98
99
|
config = {} as Config;
|
|
99
100
|
|
|
100
|
-
Object.defineProperty(config,
|
|
101
|
+
Object.defineProperty(config, NonEmumeralProperties.ZCB, {
|
|
101
102
|
configurable: false,
|
|
102
103
|
enumerable: false,
|
|
103
104
|
value: true,
|
|
@@ -129,7 +130,7 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
129
130
|
},
|
|
130
131
|
} as unknown as ConfigBuilder;
|
|
131
132
|
|
|
132
|
-
Object.defineProperty(configBuilder,
|
|
133
|
+
Object.defineProperty(configBuilder, NonEmumeralProperties.CALLBACKS, {
|
|
133
134
|
configurable: false,
|
|
134
135
|
enumerable: false,
|
|
135
136
|
value: callbacks,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { runExperiments } from './transformers/runExperiments.ts';
|
|
2
|
+
import type { ConfigParserOptions, TransformConfigHandler } from './types.ts';
|
|
3
|
+
import { transformConfig } from './utils/transformConfig.ts';
|
|
4
|
+
|
|
5
|
+
export const createConfigParser = async <Config extends object>(
|
|
6
|
+
config: Config,
|
|
7
|
+
{ experimentsCallback }: ConfigParserOptions = {}
|
|
8
|
+
) => {
|
|
9
|
+
const handlers: TransformConfigHandler[] = [];
|
|
10
|
+
|
|
11
|
+
if (experimentsCallback) {
|
|
12
|
+
handlers.push(runExperiments(experimentsCallback));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return handlers.length > 0 ? await transformConfig(config, handlers) : config;
|
|
16
|
+
};
|
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createReader } from './__testUtils__/configReader.ts';
|
|
2
2
|
|
|
3
3
|
describe('createConfigReader', () => {
|
|
4
4
|
describe('when a user accesses a known property', () => {
|
|
5
|
-
it('should return the correct value',
|
|
6
|
-
const
|
|
7
|
-
const reader = createConfigReader(config);
|
|
5
|
+
it('should return the correct value', () => {
|
|
6
|
+
const reader = createReader();
|
|
8
7
|
const value = reader('countryCode');
|
|
9
8
|
expect(value).toBe('GB');
|
|
10
9
|
});
|
|
11
10
|
});
|
|
12
11
|
|
|
13
12
|
describe('when a user accesses a known nested property', () => {
|
|
14
|
-
it('should return the correct value',
|
|
15
|
-
const
|
|
16
|
-
const reader = createConfigReader(config);
|
|
13
|
+
it('should return the correct value', () => {
|
|
14
|
+
const reader = createReader();
|
|
17
15
|
const value = reader('pages.contactDetails.name');
|
|
18
16
|
expect(value).toBe('contactDetails');
|
|
19
17
|
});
|
|
@@ -21,9 +19,8 @@ describe('createConfigReader', () => {
|
|
|
21
19
|
|
|
22
20
|
describe('when the reader is scoped', () => {
|
|
23
21
|
describe('when a user accesses a known property', () => {
|
|
24
|
-
it('should return the correct value',
|
|
25
|
-
const
|
|
26
|
-
const reader = createConfigReader(config);
|
|
22
|
+
it('should return the correct value', () => {
|
|
23
|
+
const reader = createReader();
|
|
27
24
|
const scopedReader = reader.scope('pages.contactDetails');
|
|
28
25
|
const value = scopedReader('name');
|
|
29
26
|
expect(value).toBe('contactDetails');
|
|
@@ -33,9 +30,8 @@ describe('createConfigReader', () => {
|
|
|
33
30
|
|
|
34
31
|
describe('when the reader is scoped multiple times', () => {
|
|
35
32
|
describe('when a user accesses a known property', () => {
|
|
36
|
-
it('should return the correct value',
|
|
37
|
-
const
|
|
38
|
-
const reader = createConfigReader(config);
|
|
33
|
+
it('should return the correct value', () => {
|
|
34
|
+
const reader = createReader();
|
|
39
35
|
const scopedReader = reader.scope('pages.contactDetails').scope('sections.1.sections').scope('0');
|
|
40
36
|
const value = scopedReader('name');
|
|
41
37
|
expect(value).toBe('main');
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import get from 'lodash/get.js';
|
|
2
|
-
import type { Get
|
|
3
|
-
import type {
|
|
2
|
+
import type { Get } from 'type-fest';
|
|
3
|
+
import type { Path, Scope } from './types.ts';
|
|
4
4
|
|
|
5
5
|
export const createConfigReader = <Config extends object>(config: Config) => {
|
|
6
|
-
const configReader = <
|
|
7
|
-
|
|
8
|
-
configReader.scope = <Scope extends Join<Paths<Config>, '.'>>(scope: Scope) =>
|
|
9
|
-
createConfigReader(get(config, scope) as Get<Config, Scope>);
|
|
10
|
-
|
|
6
|
+
const configReader = <P extends Path<Config>>(path: P) => get(config, path);
|
|
7
|
+
configReader.scope = <S extends Scope<Config>>(scope: S) => createConfigReader(get(config, scope) as Get<Config, S>);
|
|
11
8
|
return configReader;
|
|
12
9
|
};
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AnyRecord, NonEmumeralProperties, type TransformConfigHandlerSync } from '../types.ts';
|
|
2
2
|
|
|
3
|
-
const NON_ENUMERABLE_KEYS = new Set(
|
|
3
|
+
const NON_ENUMERABLE_KEYS = new Set(Object.values(NonEmumeralProperties));
|
|
4
4
|
|
|
5
5
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
6
|
export const cloneNonEnumerableValues: TransformConfigHandlerSync = <Config extends AnyRecord>(
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type AnyRecord,
|
|
3
|
+
NonEmumeralProperties,
|
|
4
|
+
TransformConfigHandlerAction,
|
|
5
|
+
type TransformConfigHandlerSync,
|
|
6
|
+
} from '../types.ts';
|
|
2
7
|
|
|
3
8
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
9
|
export const removeDisabledSlices: TransformConfigHandlerSync = <Config extends AnyRecord>(
|
|
5
10
|
clone: Config,
|
|
6
11
|
config: Config
|
|
7
12
|
) => {
|
|
8
|
-
if (
|
|
13
|
+
if (NonEmumeralProperties.DISABLED in config) {
|
|
9
14
|
return {
|
|
10
15
|
action: TransformConfigHandlerAction.DELETE_NODE,
|
|
11
16
|
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AnyRecord,
|
|
3
|
+
type Buckets,
|
|
4
|
+
NonEmumeralProperties,
|
|
5
|
+
type RunExperimentsCallback,
|
|
6
|
+
type TransformConfigHandler,
|
|
7
|
+
} from '../types.ts';
|
|
8
|
+
|
|
9
|
+
export const runExperiments =
|
|
10
|
+
(callback: RunExperimentsCallback): TransformConfigHandler =>
|
|
11
|
+
async <Config extends AnyRecord>(clone: Config, config: Config) => {
|
|
12
|
+
if (NonEmumeralProperties.EXPERIMENT in config && typeof config.__experiment === 'object') {
|
|
13
|
+
const { buckets, id } = config.__experiment as { buckets: Buckets<Config>; id: string };
|
|
14
|
+
const bucket = await callback(id);
|
|
15
|
+
|
|
16
|
+
if (bucket && bucket in buckets && buckets[bucket]) {
|
|
17
|
+
return buckets[bucket]!;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
value: clone,
|
|
23
|
+
};
|
|
24
|
+
};
|
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
type AnyRecord,
|
|
3
|
+
NonEmumeralProperties,
|
|
4
|
+
type SetupExperimentsCallback,
|
|
5
|
+
type TransformConfigHandler,
|
|
6
|
+
} from '../types.ts';
|
|
2
7
|
|
|
3
8
|
export const setupExperiments =
|
|
4
|
-
(callback:
|
|
9
|
+
(callback: SetupExperimentsCallback): TransformConfigHandler =>
|
|
5
10
|
async <Config extends AnyRecord>(clone: Config, config: Config) => {
|
|
6
|
-
if (
|
|
7
|
-
const
|
|
11
|
+
if (NonEmumeralProperties.EXPERIMENT in config && typeof config.__experiment === 'string') {
|
|
12
|
+
const buckets = await callback(config.__experiment, clone, config);
|
|
8
13
|
|
|
9
14
|
// @ts-expect-error private property
|
|
10
15
|
clone.__experiment = {
|
|
11
|
-
|
|
16
|
+
buckets,
|
|
12
17
|
id: config.__experiment,
|
|
13
|
-
value,
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
return {
|
package/src/types.ts
CHANGED
|
@@ -1,19 +1,38 @@
|
|
|
1
1
|
import type { List } from 'ts-toolbelt';
|
|
2
|
-
import type { Includes } from 'type-fest';
|
|
2
|
+
import type { Includes, Join } from 'type-fest';
|
|
3
3
|
|
|
4
4
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
5
5
|
export type AnyRecord = Record<string, any>;
|
|
6
6
|
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
export type Buckets<Config extends AnyRecord> = Record<string, Experiment<Config>>;
|
|
8
|
+
|
|
9
|
+
export interface ConfigParserOptions {
|
|
10
|
+
experimentsCallback?: RunExperimentsCallback;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface Experiment<Config extends AnyRecord> {
|
|
14
|
+
action?: TransformConfigHandlerAction;
|
|
15
|
+
value?: Config;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export enum NonEmumeralProperties {
|
|
19
|
+
CALLBACKS = '__callbacks',
|
|
20
|
+
DISABLED = '__disabled',
|
|
21
|
+
EXPERIMENT = '__experiment',
|
|
22
|
+
ZCB = '__zcb',
|
|
10
23
|
}
|
|
11
24
|
|
|
12
|
-
export type
|
|
25
|
+
export type Path<Config extends object> = Join<Leaves<Config>, '.'>;
|
|
26
|
+
|
|
27
|
+
export type RunExperimentsCallback = (id: string) => Promise<string>;
|
|
28
|
+
|
|
29
|
+
export type Scope<Config extends object> = Join<Paths<Config>, '.'>;
|
|
30
|
+
|
|
31
|
+
export type SetupExperimentsCallback = <Config extends AnyRecord>(
|
|
13
32
|
id: string,
|
|
14
33
|
clone: Config,
|
|
15
34
|
config: Config
|
|
16
|
-
) => Promise<
|
|
35
|
+
) => Promise<Buckets<Config>>;
|
|
17
36
|
|
|
18
37
|
export type TransformConfigHandler = <Config extends AnyRecord>(
|
|
19
38
|
clone: Config,
|
|
@@ -35,6 +54,11 @@ export interface TransformConfigHandlerReturnType<Config extends AnyRecord> {
|
|
|
35
54
|
value?: Config;
|
|
36
55
|
}
|
|
37
56
|
|
|
57
|
+
export interface WriteConfigOptions {
|
|
58
|
+
experimentsCallback?: SetupExperimentsCallback;
|
|
59
|
+
outputFile: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
38
62
|
export type Leaves<T, Path extends string[] = []> = T extends string
|
|
39
63
|
? Path
|
|
40
64
|
: {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
2
|
import shelljs from 'shelljs';
|
|
3
3
|
import type { Commands } from '../cli.ts';
|
|
4
|
-
import type {
|
|
5
|
-
import {
|
|
4
|
+
import type { SetupExperimentsCallback } from '../types.ts';
|
|
5
|
+
import { transformWriteConfig } from './transformWriteConfig.ts';
|
|
6
6
|
|
|
7
|
-
export const
|
|
7
|
+
export const importValidateTransformWriteConfig = (
|
|
8
8
|
inputFile: string,
|
|
9
9
|
outputFile: string,
|
|
10
10
|
command: Commands,
|
|
@@ -19,6 +19,7 @@ export const importValidateWriteConfig = (
|
|
|
19
19
|
}) => {
|
|
20
20
|
if (!configBuilder.validate()) {
|
|
21
21
|
shelljs.echo(`zcd ${command} => invalid config`);
|
|
22
|
+
shelljs.echo(`zcd ${command} => config values:\n${configBuilder.toJson()}\n`);
|
|
22
23
|
shelljs.echo(`zcd ${command} => errors:\n${JSON.stringify(configBuilder.errors(), undefined, 2)}\n`);
|
|
23
24
|
shelljs.exit(1);
|
|
24
25
|
}
|
|
@@ -28,8 +29,8 @@ export const importValidateWriteConfig = (
|
|
|
28
29
|
|
|
29
30
|
if (experimentCallbackFile) {
|
|
30
31
|
import(resolve(process.cwd(), experimentCallbackFile))
|
|
31
|
-
.then(({ default: experimentsCallback }: { default:
|
|
32
|
-
void
|
|
32
|
+
.then(({ default: experimentsCallback }: { default: SetupExperimentsCallback }) => {
|
|
33
|
+
void transformWriteConfig(configBuilder.values(), { experimentsCallback, outputFile });
|
|
33
34
|
})
|
|
34
35
|
.catch((error: unknown) => {
|
|
35
36
|
if (error instanceof Error) {
|
|
@@ -44,7 +45,7 @@ export const importValidateWriteConfig = (
|
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
void
|
|
48
|
+
void transformWriteConfig(configBuilder.values(), { outputFile });
|
|
48
49
|
}
|
|
49
50
|
)
|
|
50
51
|
.catch((error: unknown) => {
|
|
@@ -9,7 +9,7 @@ import { setupExperiments } from '../transformers/setupExperiments.ts';
|
|
|
9
9
|
import type { TransformConfigHandler, WriteConfigOptions } from '../types.ts';
|
|
10
10
|
import { transformConfig } from './transformConfig.ts';
|
|
11
11
|
|
|
12
|
-
export const
|
|
12
|
+
export const transformWriteConfig = async <Config extends object>(
|
|
13
13
|
config: Config,
|
|
14
14
|
{ experimentsCallback, outputFile }: WriteConfigOptions
|
|
15
15
|
) => {
|