zcb 0.0.6
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/LICENSE +21 -0
- package/README.md +8 -0
- package/bin/zcb.mjs +3 -0
- package/package.json +129 -0
- package/src/__testUtils__/builtConfig.ts +74 -0
- package/src/__testUtils__/config.ts +32 -0
- package/src/__testUtils__/configBuilder.ts +58 -0
- package/src/__testUtils__/schema.ts +56 -0
- package/src/cli.ts +86 -0
- package/src/createConfigBuilder.test.ts +523 -0
- package/src/createConfigBuilder.ts +234 -0
- package/src/createConfigReader.test.ts +45 -0
- package/src/createConfigReader.ts +12 -0
- package/src/data/countryCodes.ts +247 -0
- package/src/data/countryNames.ts +207 -0
- package/src/data/distanceUnits.ts +1 -0
- package/src/data/languageCodes.ts +185 -0
- package/src/data/timezones.ts +419 -0
- package/src/index.ts +6 -0
- package/src/templates/config.ts.hbs +3 -0
- package/src/transformers/cloneNonEnumerableValues.ts +24 -0
- package/src/transformers/removeDisabledSlices.ts +17 -0
- package/src/transformers/setupExperiments.ts +24 -0
- package/src/types.ts +58 -0
- package/src/utils/__snapshots__/transformConfig.test.ts.snap +151 -0
- package/src/utils/arrayHasInvalidDefaults.ts +6 -0
- package/src/utils/isDerivedValueCallback.ts +3 -0
- package/src/utils/isInvalidPropertyOverride.ts +3 -0
- package/src/utils/isPropertyReservedWord.ts +3 -0
- package/src/utils/isSchemaValid.ts +3 -0
- package/src/utils/isValidPropertyDefinition.ts +5 -0
- package/src/utils/isValidValue.ts +27 -0
- package/src/utils/objectHasInvalidDefaults.ts +5 -0
- package/src/utils/recordHasInvalidDefaults.ts +6 -0
- package/src/utils/transformConfig.test.ts +19 -0
- package/src/utils/transformConfig.ts +119 -0
- package/src/utils/writeConfig.ts +34 -0
- package/tsconfig.build.json +13 -0
- package/tsconfig.json +9 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`transformConfig when no handlers are passed in should return the correct config 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"countryCode": "GB",
|
|
6
|
+
"countryName": "United Kingdom",
|
|
7
|
+
"distanceUnit": "km",
|
|
8
|
+
"languageCodes": [
|
|
9
|
+
"en",
|
|
10
|
+
],
|
|
11
|
+
"locales": [
|
|
12
|
+
"en_GB",
|
|
13
|
+
],
|
|
14
|
+
"name": "alpha",
|
|
15
|
+
"pages": {
|
|
16
|
+
"contactDetails": {
|
|
17
|
+
"name": "contactDetails",
|
|
18
|
+
"sections": [
|
|
19
|
+
{
|
|
20
|
+
"name": "header",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "body",
|
|
24
|
+
"sections": [
|
|
25
|
+
{
|
|
26
|
+
"name": "main",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "sidebar",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "footer",
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
"personalDetails": {
|
|
39
|
+
"name": "personalDetails",
|
|
40
|
+
"sections": [
|
|
41
|
+
{
|
|
42
|
+
"name": "header",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "body",
|
|
46
|
+
"sections": [
|
|
47
|
+
{
|
|
48
|
+
"name": "main",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "sidebar",
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "footer",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
"routes": [
|
|
62
|
+
{
|
|
63
|
+
"page": "personalDetails",
|
|
64
|
+
"path": "personal-details",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"page": "contactDetails",
|
|
68
|
+
"path": "contact-details",
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
"timeouts": {
|
|
72
|
+
"apollo": 10000,
|
|
73
|
+
},
|
|
74
|
+
"timezone": "Europe/London",
|
|
75
|
+
}
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
exports[`transformConfigSync when no handlers are passed in should return the correct config 1`] = `
|
|
79
|
+
{
|
|
80
|
+
"countryCode": "GB",
|
|
81
|
+
"countryName": "United Kingdom",
|
|
82
|
+
"distanceUnit": "km",
|
|
83
|
+
"languageCodes": [
|
|
84
|
+
"en",
|
|
85
|
+
],
|
|
86
|
+
"locales": [
|
|
87
|
+
"en_GB",
|
|
88
|
+
],
|
|
89
|
+
"name": "alpha",
|
|
90
|
+
"pages": {
|
|
91
|
+
"contactDetails": {
|
|
92
|
+
"name": "contactDetails",
|
|
93
|
+
"sections": [
|
|
94
|
+
{
|
|
95
|
+
"name": "header",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "body",
|
|
99
|
+
"sections": [
|
|
100
|
+
{
|
|
101
|
+
"name": "main",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"name": "sidebar",
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"name": "footer",
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
"personalDetails": {
|
|
114
|
+
"name": "personalDetails",
|
|
115
|
+
"sections": [
|
|
116
|
+
{
|
|
117
|
+
"name": "header",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"name": "body",
|
|
121
|
+
"sections": [
|
|
122
|
+
{
|
|
123
|
+
"name": "main",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"name": "sidebar",
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "footer",
|
|
132
|
+
},
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
"routes": [
|
|
137
|
+
{
|
|
138
|
+
"page": "personalDetails",
|
|
139
|
+
"path": "personal-details",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"page": "contactDetails",
|
|
143
|
+
"path": "contact-details",
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
"timeouts": {
|
|
147
|
+
"apollo": 10000,
|
|
148
|
+
},
|
|
149
|
+
"timezone": "Europe/London",
|
|
150
|
+
}
|
|
151
|
+
`;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import isPlainObject from 'lodash/isPlainObject.js';
|
|
2
|
+
|
|
3
|
+
export const isValidValue = (value: unknown, depth = 0): boolean => {
|
|
4
|
+
if (isPlainObject(value)) {
|
|
5
|
+
const object = value as Record<string, unknown>;
|
|
6
|
+
|
|
7
|
+
if (object.__zcb === true) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (depth < 1 && Object.values(object).every(v => isValidValue(v, depth + 1))) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
if (value.every(v => isValidValue(v, depth + 1))) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return true;
|
|
27
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { JSONSchema7 } from 'json-schema';
|
|
2
|
+
|
|
3
|
+
export const objectHasInvalidDefaults = (propertyDefinition: JSONSchema7) =>
|
|
4
|
+
propertyDefinition.properties &&
|
|
5
|
+
Object.values(propertyDefinition.properties).some(value => typeof value === 'object' && 'default' in value);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { JSONSchema7 } from 'json-schema';
|
|
2
|
+
|
|
3
|
+
export const recordHasInvalidDefaults = (propertyDefinition: JSONSchema7) =>
|
|
4
|
+
propertyDefinition.additionalProperties &&
|
|
5
|
+
typeof propertyDefinition.additionalProperties === 'object' &&
|
|
6
|
+
'default' in propertyDefinition.additionalProperties;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import configBuilder from '../__testUtils__/configBuilder.ts';
|
|
2
|
+
|
|
3
|
+
describe('transformConfig', () => {
|
|
4
|
+
describe('when no handlers are passed in', () => {
|
|
5
|
+
it('should return the correct config', async () => {
|
|
6
|
+
const { transformConfig } = await import('./transformConfig.ts');
|
|
7
|
+
await expect(transformConfig(configBuilder.values())).resolves.toMatchSnapshot();
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
describe('transformConfigSync', () => {
|
|
13
|
+
describe('when no handlers are passed in', () => {
|
|
14
|
+
it('should return the correct config', async () => {
|
|
15
|
+
const { transformConfigSync } = await import('./transformConfig.ts');
|
|
16
|
+
expect(transformConfigSync(configBuilder.values())).toMatchSnapshot();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import castArray from 'lodash/castArray.js';
|
|
2
|
+
import isPlainObject from 'lodash/isPlainObject.js';
|
|
3
|
+
import {
|
|
4
|
+
type AnyRecord,
|
|
5
|
+
type TransformConfigHandler,
|
|
6
|
+
TransformConfigHandlerAction,
|
|
7
|
+
type TransformConfigHandlerSync,
|
|
8
|
+
} from '../types.ts';
|
|
9
|
+
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
const transformArray = (list: any[], handler: TransformConfigHandler | TransformConfigHandler[]): Promise<any[]> =>
|
|
12
|
+
Promise.all(
|
|
13
|
+
list.map(entry => {
|
|
14
|
+
if (isPlainObject(entry)) {
|
|
15
|
+
return transformConfig(entry, handler);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (Array.isArray(entry)) {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
20
|
+
return transformArray(entry, handler);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
24
|
+
return entry;
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export const transformConfig = async <Config extends AnyRecord>(
|
|
29
|
+
config: Config,
|
|
30
|
+
handler: TransformConfigHandler | TransformConfigHandler[] = []
|
|
31
|
+
): Promise<Config> => {
|
|
32
|
+
let transform = {} as Config;
|
|
33
|
+
const handlers = castArray(handler);
|
|
34
|
+
|
|
35
|
+
for (const callback of handlers) {
|
|
36
|
+
const result = await Promise.resolve(callback(transform, config));
|
|
37
|
+
|
|
38
|
+
if (result.action === TransformConfigHandlerAction.DELETE_NODE) {
|
|
39
|
+
return {} as Config;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (result.value) {
|
|
43
|
+
transform = result.value;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (result.action === TransformConfigHandlerAction.SKIP_NODE) {
|
|
47
|
+
return transform;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const key in config) {
|
|
52
|
+
const value = config[key];
|
|
53
|
+
|
|
54
|
+
if (isPlainObject(value)) {
|
|
55
|
+
transform[key] = await transformConfig(value, handler);
|
|
56
|
+
} else if (Array.isArray(value)) {
|
|
57
|
+
transform[key] = (await transformArray(value, handler)) as Config[Extract<keyof Config, string>];
|
|
58
|
+
} else {
|
|
59
|
+
transform[key] = value;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return transform;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
|
+
const transformArraySync = (list: any[], handler: TransformConfigHandlerSync | TransformConfigHandlerSync[]): any[] =>
|
|
68
|
+
list.map(entry => {
|
|
69
|
+
if (isPlainObject(entry)) {
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
71
|
+
return transformConfigSync(entry, handler);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (Array.isArray(entry)) {
|
|
75
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
76
|
+
return transformArraySync(entry, handler);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
80
|
+
return entry;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export const transformConfigSync = <Config extends AnyRecord>(
|
|
84
|
+
config: Config,
|
|
85
|
+
handler: TransformConfigHandlerSync | TransformConfigHandlerSync[] = []
|
|
86
|
+
): Config => {
|
|
87
|
+
let transform = {} as Config;
|
|
88
|
+
const handlers = castArray(handler);
|
|
89
|
+
|
|
90
|
+
for (const callback of handlers) {
|
|
91
|
+
const result = callback(transform, config);
|
|
92
|
+
|
|
93
|
+
if (result.action === TransformConfigHandlerAction.DELETE_NODE) {
|
|
94
|
+
return {} as Config;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (result.value) {
|
|
98
|
+
transform = result.value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (result.action === TransformConfigHandlerAction.SKIP_NODE) {
|
|
102
|
+
return transform;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
for (const key in config) {
|
|
107
|
+
const value = config[key];
|
|
108
|
+
|
|
109
|
+
if (isPlainObject(value)) {
|
|
110
|
+
transform[key] = transformConfigSync(value, handler);
|
|
111
|
+
} else if (Array.isArray(value)) {
|
|
112
|
+
transform[key] = transformArraySync(value, handler) as Config[Extract<keyof Config, string>];
|
|
113
|
+
} else {
|
|
114
|
+
transform[key] = value;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return transform;
|
|
119
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { outputFileSync } from 'fs-extra/esm';
|
|
2
|
+
import Handlebars from 'handlebars';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import shelljs from 'shelljs';
|
|
7
|
+
import { removeDisabledSlices } from '../transformers/removeDisabledSlices.ts';
|
|
8
|
+
import { setupExperiments } from '../transformers/setupExperiments.ts';
|
|
9
|
+
import type { TransformConfigHandler, WriteConfigOptions } from '../types.ts';
|
|
10
|
+
import { transformConfig } from './transformConfig.ts';
|
|
11
|
+
|
|
12
|
+
export const writeConfig = async <Config extends object>(
|
|
13
|
+
config: Config,
|
|
14
|
+
{ experimentsCallback, outputFile }: WriteConfigOptions
|
|
15
|
+
) => {
|
|
16
|
+
const handlers: TransformConfigHandler[] = [removeDisabledSlices];
|
|
17
|
+
|
|
18
|
+
if (experimentsCallback) {
|
|
19
|
+
handlers.push(setupExperiments(experimentsCallback));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const template = Handlebars.compile(
|
|
23
|
+
readFileSync(resolve(dirname(fileURLToPath(import.meta.url)), './templates/config.ts.hbs'), { encoding: 'utf8' })
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const output = template({ config: JSON.stringify(await transformConfig(config, handlers), undefined, 2) }).replaceAll(
|
|
27
|
+
/"([A-Za-z][\dA-Za-z]+)":/g,
|
|
28
|
+
'$1:'
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
shelljs.echo(`zcd watch => writing to file: ${outputFile}`);
|
|
32
|
+
shelljs.echo(`zcd watch => content to write:\n${output}\n`);
|
|
33
|
+
outputFileSync(outputFile, output);
|
|
34
|
+
};
|