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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +8 -0
  3. package/bin/zcb.mjs +3 -0
  4. package/package.json +129 -0
  5. package/src/__testUtils__/builtConfig.ts +74 -0
  6. package/src/__testUtils__/config.ts +32 -0
  7. package/src/__testUtils__/configBuilder.ts +58 -0
  8. package/src/__testUtils__/schema.ts +56 -0
  9. package/src/cli.ts +86 -0
  10. package/src/createConfigBuilder.test.ts +523 -0
  11. package/src/createConfigBuilder.ts +234 -0
  12. package/src/createConfigReader.test.ts +45 -0
  13. package/src/createConfigReader.ts +12 -0
  14. package/src/data/countryCodes.ts +247 -0
  15. package/src/data/countryNames.ts +207 -0
  16. package/src/data/distanceUnits.ts +1 -0
  17. package/src/data/languageCodes.ts +185 -0
  18. package/src/data/timezones.ts +419 -0
  19. package/src/index.ts +6 -0
  20. package/src/templates/config.ts.hbs +3 -0
  21. package/src/transformers/cloneNonEnumerableValues.ts +24 -0
  22. package/src/transformers/removeDisabledSlices.ts +17 -0
  23. package/src/transformers/setupExperiments.ts +24 -0
  24. package/src/types.ts +58 -0
  25. package/src/utils/__snapshots__/transformConfig.test.ts.snap +151 -0
  26. package/src/utils/arrayHasInvalidDefaults.ts +6 -0
  27. package/src/utils/isDerivedValueCallback.ts +3 -0
  28. package/src/utils/isInvalidPropertyOverride.ts +3 -0
  29. package/src/utils/isPropertyReservedWord.ts +3 -0
  30. package/src/utils/isSchemaValid.ts +3 -0
  31. package/src/utils/isValidPropertyDefinition.ts +5 -0
  32. package/src/utils/isValidValue.ts +27 -0
  33. package/src/utils/objectHasInvalidDefaults.ts +5 -0
  34. package/src/utils/recordHasInvalidDefaults.ts +6 -0
  35. package/src/utils/transformConfig.test.ts +19 -0
  36. package/src/utils/transformConfig.ts +119 -0
  37. package/src/utils/writeConfig.ts +34 -0
  38. package/tsconfig.build.json +13 -0
  39. 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,6 @@
1
+ import type { JSONSchema7 } from 'json-schema';
2
+
3
+ export const arrayHasInvalidDefaults = (propertyDefinition: JSONSchema7) => {
4
+ const { items } = propertyDefinition;
5
+ return items && typeof items === 'object' && 'default' in items;
6
+ };
@@ -0,0 +1,3 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
+ export const isDerivedValueCallback = <T extends (c: any) => any>(value: any): value is T =>
3
+ typeof value === 'function';
@@ -0,0 +1,3 @@
1
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2
+ export const isInvalidPropertyOverride = (propertyValue: any, override = false) =>
3
+ !override && propertyValue !== undefined;
@@ -0,0 +1,3 @@
1
+ import { RESERVED_KEYWORDS } from '../createConfigBuilder.ts';
2
+
3
+ export const isPropertyReservedWord = (propertyName: string) => RESERVED_KEYWORDS.has(propertyName);
@@ -0,0 +1,3 @@
1
+ import type { JSONSchema7 } from 'json-schema';
2
+
3
+ export const isSchemaValid = (schema: JSONSchema7) => schema.type !== 'object';
@@ -0,0 +1,5 @@
1
+ import type { JSONSchema7, JSONSchema7Definition } from 'json-schema';
2
+
3
+ export const isValidPropertyDefinition = (
4
+ propertyDefinition: JSONSchema7Definition | undefined
5
+ ): propertyDefinition is JSONSchema7 => typeof propertyDefinition === 'object';
@@ -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
+ };
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "@repodog/ts-config/build.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist/types"
6
+ },
7
+ "include": [
8
+ "src/**/*"
9
+ ],
10
+ "exclude": [
11
+ "**/*.test.*"
12
+ ]
13
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "@repodog/ts-config/index.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src"
5
+ },
6
+ "include": [
7
+ "src/**/*"
8
+ ]
9
+ }