zcb 0.3.4 → 0.3.5
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 +1 -1
- package/dist/cjs/cli.cjs +1 -1
- package/dist/cjs/cli.cjs.map +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/templates/config.ts.hbs +1 -1
- package/dist/esm/cli.mjs +1 -1
- package/dist/esm/cli.mjs.map +1 -1
- package/dist/esm/index.mjs +1 -1
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm/templates/config.ts.hbs +1 -1
- package/dist/production.analysis.txt +25 -25
- package/dist/types/cjs/cli.d.cts +7 -1
- package/dist/types/cjs/cli.d.cts.map +1 -1
- package/dist/types/cjs/createConfigBuilder.d.cts +3 -3
- package/dist/types/cjs/createConfigBuilder.d.cts.map +1 -1
- package/dist/types/cjs/createConfigParser.d.cts.map +1 -1
- package/dist/types/cjs/transformers/runExperiments.d.cts.map +1 -1
- package/dist/types/cjs/utils/arrayHasInvalidDefaults.d.cts +2 -2
- package/dist/types/cjs/utils/arrayHasInvalidDefaults.d.cts.map +1 -1
- package/dist/types/cjs/utils/collateDefaultValues.d.cts +2 -2
- package/dist/types/cjs/utils/collateDefaultValues.d.cts.map +1 -1
- package/dist/types/cjs/utils/collateObjectPropertyDefaults.d.cts +2 -2
- package/dist/types/cjs/utils/collateObjectPropertyDefaults.d.cts.map +1 -1
- package/dist/types/cjs/utils/importValidateTransformWriteConfig.d.cts +2 -2
- package/dist/types/cjs/utils/importValidateTransformWriteConfig.d.cts.map +1 -1
- package/dist/types/cjs/utils/isInvalidPropertyOverride.d.cts.map +1 -1
- package/dist/types/cjs/utils/isPropertyReservedWord.d.cts.map +1 -1
- package/dist/types/cjs/utils/isSchemaValid.d.cts +2 -2
- package/dist/types/cjs/utils/isSchemaValid.d.cts.map +1 -1
- package/dist/types/cjs/utils/isValidPropertyDefinition.d.cts +2 -2
- package/dist/types/cjs/utils/isValidPropertyDefinition.d.cts.map +1 -1
- package/dist/types/cjs/utils/objectPropertyHasDefaults.d.cts +2 -2
- package/dist/types/cjs/utils/objectPropertyHasDefaults.d.cts.map +1 -1
- package/dist/types/cjs/utils/recordHasInvalidDefaults.d.cts +2 -2
- package/dist/types/cjs/utils/recordHasInvalidDefaults.d.cts.map +1 -1
- package/dist/types/cjs/utils/transformConfig.d.cts.map +1 -1
- package/dist/types/cjs/utils/transformWriteConfig.d.cts.map +1 -1
- package/dist/types/esm/cli.d.ts +7 -1
- package/dist/types/esm/cli.d.ts.map +1 -1
- package/dist/types/esm/createConfigBuilder.d.ts +3 -3
- package/dist/types/esm/createConfigBuilder.d.ts.map +1 -1
- package/dist/types/esm/createConfigParser.d.ts.map +1 -1
- package/dist/types/esm/transformers/runExperiments.d.ts.map +1 -1
- package/dist/types/esm/utils/arrayHasInvalidDefaults.d.ts +2 -2
- package/dist/types/esm/utils/arrayHasInvalidDefaults.d.ts.map +1 -1
- package/dist/types/esm/utils/collateDefaultValues.d.ts +2 -2
- package/dist/types/esm/utils/collateDefaultValues.d.ts.map +1 -1
- package/dist/types/esm/utils/collateObjectPropertyDefaults.d.ts +2 -2
- package/dist/types/esm/utils/collateObjectPropertyDefaults.d.ts.map +1 -1
- package/dist/types/esm/utils/importValidateTransformWriteConfig.d.ts +2 -2
- package/dist/types/esm/utils/importValidateTransformWriteConfig.d.ts.map +1 -1
- package/dist/types/esm/utils/isInvalidPropertyOverride.d.ts.map +1 -1
- package/dist/types/esm/utils/isPropertyReservedWord.d.ts.map +1 -1
- package/dist/types/esm/utils/isSchemaValid.d.ts +2 -2
- package/dist/types/esm/utils/isSchemaValid.d.ts.map +1 -1
- package/dist/types/esm/utils/isValidPropertyDefinition.d.ts +2 -2
- package/dist/types/esm/utils/isValidPropertyDefinition.d.ts.map +1 -1
- package/dist/types/esm/utils/objectPropertyHasDefaults.d.ts +2 -2
- package/dist/types/esm/utils/objectPropertyHasDefaults.d.ts.map +1 -1
- package/dist/types/esm/utils/recordHasInvalidDefaults.d.ts +2 -2
- package/dist/types/esm/utils/recordHasInvalidDefaults.d.ts.map +1 -1
- package/dist/types/esm/utils/transformConfig.d.ts.map +1 -1
- package/dist/types/esm/utils/transformWriteConfig.d.ts.map +1 -1
- package/dist/types/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +45 -43
- package/pnpm-workspace.yaml +4 -0
- package/src/__testUtils__/builtConfig.ts +1 -1
- package/src/__testUtils__/configBuilder.ts +28 -22
- package/src/__testUtils__/schema.ts +2 -2
- package/src/cli.ts +9 -3
- package/src/createConfigBuilder.test.ts +27 -23
- package/src/createConfigBuilder.ts +40 -42
- package/src/createConfigParser.ts +1 -1
- package/src/createConfigReader.ts +2 -2
- package/src/templates/config.ts.hbs +1 -1
- package/src/transformers/cloneNonEnumerableValues.ts +1 -1
- package/src/transformers/runExperiments.ts +16 -5
- package/src/utils/arrayHasInvalidDefaults.ts +3 -3
- package/src/utils/collateDefaultValues.ts +5 -5
- package/src/utils/collateObjectPropertyDefaults.ts +4 -2
- package/src/utils/importValidateTransformWriteConfig.ts +25 -13
- package/src/utils/isInvalidPropertyOverride.ts +1 -1
- package/src/utils/isPropertyReservedWord.ts +1 -1
- package/src/utils/isSchemaValid.ts +2 -2
- package/src/utils/isValidPropertyDefinition.ts +3 -3
- package/src/utils/objectPropertyHasDefaults.ts +3 -3
- package/src/utils/recordHasInvalidDefaults.ts +3 -3
- package/src/utils/transformConfig.ts +16 -4
- package/src/utils/transformWriteConfig.test.ts +1 -1
- package/src/utils/transformWriteConfig.ts +6 -4
- package/dist/types/cjs/utils/jsonStringifyReplacer.d.cts +0 -109
- package/dist/types/cjs/utils/jsonStringifyReplacer.d.cts.map +0 -1
- package/dist/types/esm/utils/jsonStringifyReplacer.d.ts +0 -109
- package/dist/types/esm/utils/jsonStringifyReplacer.d.ts.map +0 -1
- package/src/__snapshots__/createConfigBuilder.test.ts.snap +0 -30
- package/src/utils/jsonStringifyReplacer.ts +0 -6
package/package.json
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zcb",
|
|
3
3
|
"description": "Build configs with type safety from zod schema.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.5",
|
|
5
5
|
"author": "miami-man",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/badbatch/zod-config-builder",
|
|
8
|
-
"repository":
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "badbatch/zod-config-builder"
|
|
11
|
+
},
|
|
9
12
|
"bugs": "https://github.com/badbatch/zod-config-builder/issues",
|
|
10
13
|
"type": "module",
|
|
11
14
|
"bin": {
|
|
12
15
|
"zcb": "./bin/zcb.js"
|
|
13
16
|
},
|
|
14
|
-
"main": "./dist/cjs/index.
|
|
17
|
+
"main": "./dist/cjs/index.mjs",
|
|
15
18
|
"module": "./dist/esm/index.mjs",
|
|
16
|
-
"types": "./dist/types/cjs/index.d.
|
|
19
|
+
"types": "./dist/types/cjs/index.d.ts",
|
|
17
20
|
"imports": {
|
|
18
21
|
"#*": "./src/*"
|
|
19
22
|
},
|
|
@@ -42,56 +45,55 @@
|
|
|
42
45
|
"@types/fs-extra": "^11.0.4",
|
|
43
46
|
"@types/json-schema": "^7.0.15",
|
|
44
47
|
"@types/lodash-es": "^4.17.12",
|
|
45
|
-
"@types/shelljs": "^0.
|
|
46
|
-
"@types/uuid": "^
|
|
47
|
-
"@types/yargs": "^17.0.
|
|
48
|
-
"core-js": "^3.
|
|
49
|
-
"fs-extra": "^11.3.
|
|
50
|
-
"handlebars": "^4.7.
|
|
48
|
+
"@types/shelljs": "^0.10.0",
|
|
49
|
+
"@types/uuid": "^11.0.0",
|
|
50
|
+
"@types/yargs": "^17.0.35",
|
|
51
|
+
"core-js": "^3.49.0",
|
|
52
|
+
"fs-extra": "^11.3.6",
|
|
53
|
+
"handlebars": "^4.7.9",
|
|
51
54
|
"json-schema": "^0.4.0",
|
|
52
|
-
"lodash-es": "^4.
|
|
55
|
+
"lodash-es": "^4.18.1",
|
|
53
56
|
"shelljs": "^0.10.0",
|
|
54
57
|
"ts-node": "^10.9.2",
|
|
55
58
|
"ts-toolbelt": "^9.6.0",
|
|
56
|
-
"type-fest": "^
|
|
57
|
-
"uuid": "^
|
|
59
|
+
"type-fest": "^5.8.0",
|
|
60
|
+
"uuid": "^14.0.1",
|
|
58
61
|
"yargs": "^18.0.0",
|
|
59
|
-
"zod": "^
|
|
60
|
-
"zod-to-json-schema": "^3.
|
|
62
|
+
"zod": "^4.4.3",
|
|
63
|
+
"zod-to-json-schema": "^3.25.2"
|
|
61
64
|
},
|
|
62
65
|
"devDependencies": {
|
|
63
|
-
"@commitlint/cli": "^
|
|
64
|
-
"@jest/globals": "^
|
|
65
|
-
"@repodog/cli": "^1.6.
|
|
66
|
-
"@repodog/commitlint-config": "^
|
|
67
|
-
"@repodog/eslint-config": "^9.0.
|
|
68
|
-
"@repodog/eslint-config-jest": "^9.0.
|
|
69
|
-
"@repodog/jest-config": "^
|
|
70
|
-
"@repodog/markdownlint-config": "^1.2.
|
|
71
|
-
"@repodog/prettier-config": "^3.0.
|
|
72
|
-
"@repodog/rollup-config": "^4.0.
|
|
73
|
-
"@repodog/swc-config": "^1.0.
|
|
74
|
-
"@repodog/syncpack-config": "^
|
|
75
|
-
"@repodog/ts-config": "^
|
|
76
|
-
"@rollup/plugin-swc": "^0.
|
|
77
|
-
"@swc/core": "^1.
|
|
66
|
+
"@commitlint/cli": "^21.2.1",
|
|
67
|
+
"@jest/globals": "^30.4.1",
|
|
68
|
+
"@repodog/cli": "^1.6.59",
|
|
69
|
+
"@repodog/commitlint-config": "^21.0.2",
|
|
70
|
+
"@repodog/eslint-config": "^9.0.29",
|
|
71
|
+
"@repodog/eslint-config-jest": "^9.0.9",
|
|
72
|
+
"@repodog/jest-config": "^30.0.4",
|
|
73
|
+
"@repodog/markdownlint-config": "^1.2.7",
|
|
74
|
+
"@repodog/prettier-config": "^3.0.5",
|
|
75
|
+
"@repodog/rollup-config": "^4.0.17",
|
|
76
|
+
"@repodog/swc-config": "^1.0.15",
|
|
77
|
+
"@repodog/syncpack-config": "^15.0.3",
|
|
78
|
+
"@repodog/ts-config": "^6.0.3",
|
|
79
|
+
"@rollup/plugin-swc": "^0.4.1",
|
|
80
|
+
"@swc/core": "^1.15.43",
|
|
78
81
|
"@swc/jest": "^0.2.39",
|
|
79
|
-
"@types/jest": "^
|
|
82
|
+
"@types/jest": "^30.0.0",
|
|
80
83
|
"@types/node": "^24.2.1",
|
|
81
|
-
"cts-types": "^0.0.
|
|
82
|
-
"del-cli": "^
|
|
83
|
-
"eslint": "^9.
|
|
84
|
+
"cts-types": "^0.0.11",
|
|
85
|
+
"del-cli": "^7.0.0",
|
|
86
|
+
"eslint": "^9.39.4",
|
|
84
87
|
"generate-changelog": "^1.8.0",
|
|
85
88
|
"husky": "^9.1.7",
|
|
86
|
-
"jest": "^
|
|
87
|
-
"jest-environment-
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"rollup": "^4.46.2",
|
|
89
|
+
"jest": "^30.4.2",
|
|
90
|
+
"jest-environment-node": "^30.4.1",
|
|
91
|
+
"markdownlint-cli2": "^0.23.0",
|
|
92
|
+
"prettier": "^3.9.5",
|
|
93
|
+
"rollup": "^4.62.2",
|
|
92
94
|
"suppress-experimental-warnings": "^2.0.0",
|
|
93
|
-
"syncpack": "^
|
|
94
|
-
"typescript": "^
|
|
95
|
+
"syncpack": "^15.3.2",
|
|
96
|
+
"typescript": "^6.0.3"
|
|
95
97
|
},
|
|
96
98
|
"keywords": [
|
|
97
99
|
"config",
|
|
@@ -113,7 +115,7 @@
|
|
|
113
115
|
"lint:code": "eslint .",
|
|
114
116
|
"lint:docs": "markdownlint-cli2 --config \".markdownlint.json\" \"**/*.md\" \"!**/node_modules/**\"",
|
|
115
117
|
"repodog": "repodog",
|
|
116
|
-
"syncpack": "syncpack format
|
|
118
|
+
"syncpack": "syncpack format --check && syncpack lint",
|
|
117
119
|
"test": "COMPILER=swc node --require=suppress-experimental-warnings --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
118
120
|
"type-check": "tsc --noEmit",
|
|
119
121
|
"validate": "pnpm run syncpack && pnpm run build && pnpm run lint && pnpm run type-check && pnpm run test",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* eslint-disable
|
|
1
|
+
/* eslint-disable import-x/no-default-export, unicorn/numeric-separators-style, unicorn/no-null */
|
|
2
2
|
/* This file is autogenerated, do not edit directly, your changes will not perist. */
|
|
3
3
|
|
|
4
4
|
export default {
|
|
@@ -17,6 +17,32 @@ const pageBuilder = createConfigBuilder<PageType>(pageSchema);
|
|
|
17
17
|
const sectionBuilder = createConfigBuilder<SectionType>(sectionSchema);
|
|
18
18
|
const subsectionBuilder = sectionBuilder.$fork();
|
|
19
19
|
|
|
20
|
+
const contactDetailsPage = pageBuilder
|
|
21
|
+
.name('contactDetails')
|
|
22
|
+
.sections([
|
|
23
|
+
sectionBuilder.name('header').$flush(),
|
|
24
|
+
sectionBuilder
|
|
25
|
+
.name('body')
|
|
26
|
+
.sections([subsectionBuilder.name('main').$flush(), subsectionBuilder.name('sidebar').$flush()])
|
|
27
|
+
.$flush(),
|
|
28
|
+
sectionBuilder.name('footer').$flush(),
|
|
29
|
+
])
|
|
30
|
+
.$flush();
|
|
31
|
+
|
|
32
|
+
const personalDetailPage = pageBuilder
|
|
33
|
+
.name('personalDetails')
|
|
34
|
+
.sections([
|
|
35
|
+
sectionBuilder.name('header').$flush(),
|
|
36
|
+
sectionBuilder
|
|
37
|
+
.name('body')
|
|
38
|
+
.sections([subsectionBuilder.name('main').$flush(), subsectionBuilder.name('sidebar').$flush()])
|
|
39
|
+
.$flush(),
|
|
40
|
+
sectionBuilder.name('footer').$flush(),
|
|
41
|
+
])
|
|
42
|
+
.$flush();
|
|
43
|
+
|
|
44
|
+
// Required for how builder is executed
|
|
45
|
+
// eslint-disable-next-line unicorn/no-top-level-side-effects
|
|
20
46
|
configBuilder
|
|
21
47
|
.countryCode('GB')
|
|
22
48
|
// Want to test using null value
|
|
@@ -29,28 +55,8 @@ configBuilder
|
|
|
29
55
|
languageCodes?.length && countryCode ? languageCodes.map(code => `${code}_${countryCode}`) : [],
|
|
30
56
|
)
|
|
31
57
|
.pages({
|
|
32
|
-
contactDetails:
|
|
33
|
-
|
|
34
|
-
.sections([
|
|
35
|
-
sectionBuilder.name('header').$flush(),
|
|
36
|
-
sectionBuilder
|
|
37
|
-
.name('body')
|
|
38
|
-
.sections([subsectionBuilder.name('main').$flush(), subsectionBuilder.name('sidebar').$flush()])
|
|
39
|
-
.$flush(),
|
|
40
|
-
sectionBuilder.name('footer').$flush(),
|
|
41
|
-
])
|
|
42
|
-
.$flush(),
|
|
43
|
-
personalDetails: pageBuilder
|
|
44
|
-
.name('personalDetails')
|
|
45
|
-
.sections([
|
|
46
|
-
sectionBuilder.name('header').$flush(),
|
|
47
|
-
sectionBuilder
|
|
48
|
-
.name('body')
|
|
49
|
-
.sections([subsectionBuilder.name('main').$flush(), subsectionBuilder.name('sidebar').$flush()])
|
|
50
|
-
.$flush(),
|
|
51
|
-
sectionBuilder.name('footer').$flush(),
|
|
52
|
-
])
|
|
53
|
-
.$flush(),
|
|
58
|
+
contactDetails: contactDetailsPage,
|
|
59
|
+
personalDetails: personalDetailPage,
|
|
54
60
|
})
|
|
55
61
|
.routes([routeBuilder.page('personalDetails').$flush(), routeBuilder.page('contactDetails').$flush()])
|
|
56
62
|
.timeouts({ apollo: 10_000 })
|
|
@@ -48,9 +48,9 @@ export const configSchema = z.object({
|
|
|
48
48
|
languageCodes: z.array(z.enum(languageCodes)).optional(),
|
|
49
49
|
locales: z.array(z.string().regex(/[a-z]{2}_[A-Z]{2}/)).optional(),
|
|
50
50
|
name: z.string().optional(),
|
|
51
|
-
pages: z.record(pageSchema).optional(),
|
|
51
|
+
pages: z.record(z.string(), pageSchema).optional(),
|
|
52
52
|
routes: z.array(routeSchema).optional(),
|
|
53
|
-
timeouts: z.record(z.number()).optional(),
|
|
53
|
+
timeouts: z.record(z.string(), z.number()).optional(),
|
|
54
54
|
timezone: z.enum(timezones).optional(),
|
|
55
55
|
});
|
|
56
56
|
|
package/src/cli.ts
CHANGED
|
@@ -5,9 +5,15 @@ import yargs, { type Argv } from 'yargs';
|
|
|
5
5
|
import { hideBin } from 'yargs/helpers';
|
|
6
6
|
import { importValidateTransformWriteConfig } from './utils/importValidateTransformWriteConfig.ts';
|
|
7
7
|
|
|
8
|
-
export type
|
|
8
|
+
export type CliCommands = 'build' | 'watch';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
export type CliArguments = Argv<{
|
|
11
|
+
'experiments-callback-file'?: string;
|
|
12
|
+
'input-file': string;
|
|
13
|
+
'output-file': string;
|
|
14
|
+
}>;
|
|
15
|
+
|
|
16
|
+
const generateArguments = (cmdYargs: Argv): CliArguments =>
|
|
11
17
|
cmdYargs
|
|
12
18
|
.positional('input-file', {
|
|
13
19
|
demandOption: true,
|
|
@@ -24,7 +30,7 @@ const generateArguments = (cmdYargs: Argv) =>
|
|
|
24
30
|
type: 'string',
|
|
25
31
|
});
|
|
26
32
|
|
|
27
|
-
export const cli = () => {
|
|
33
|
+
export const cli = (): void => {
|
|
28
34
|
// yargs does not provide a way to pass generic to type args.
|
|
29
35
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
30
36
|
const argv = yargs(hideBin(process.argv)) as Argv<{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { kebabCase } from 'lodash-es';
|
|
2
|
-
import {
|
|
2
|
+
import { z } from 'zod';
|
|
3
3
|
import {
|
|
4
4
|
type ConfigType,
|
|
5
5
|
type PageType,
|
|
@@ -112,13 +112,14 @@ describe('createConfigBuilder', () => {
|
|
|
112
112
|
__experiment: 'FEAT_ALPHA@0.0.1',
|
|
113
113
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
114
114
|
pages: expect.objectContaining({
|
|
115
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, unicorn/max-nested-calls
|
|
116
116
|
contactDetails: expect.objectContaining({
|
|
117
117
|
__experiment: 'FEAT_BRAVO@0.0.1',
|
|
118
118
|
}),
|
|
119
119
|
}),
|
|
120
120
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
121
121
|
routes: expect.arrayContaining([
|
|
122
|
+
// eslint-disable-next-line unicorn/max-nested-calls
|
|
122
123
|
expect.objectContaining({
|
|
123
124
|
__experiment: 'FEAT_CHARLIE@0.0.1',
|
|
124
125
|
}),
|
|
@@ -153,13 +154,14 @@ describe('createConfigBuilder', () => {
|
|
|
153
154
|
__disabled: true,
|
|
154
155
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
155
156
|
pages: expect.objectContaining({
|
|
156
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, unicorn/max-nested-calls
|
|
157
158
|
contactDetails: expect.objectContaining({
|
|
158
159
|
__disabled: true,
|
|
159
160
|
}),
|
|
160
161
|
}),
|
|
161
162
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
162
163
|
routes: expect.arrayContaining([
|
|
164
|
+
// eslint-disable-next-line unicorn/max-nested-calls
|
|
163
165
|
expect.objectContaining({
|
|
164
166
|
__disabled: true,
|
|
165
167
|
}),
|
|
@@ -223,7 +225,7 @@ describe('createConfigBuilder', () => {
|
|
|
223
225
|
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
224
226
|
|
|
225
227
|
const extendedSchema = configSchema.extend({
|
|
226
|
-
flags: z.record(z.boolean()).optional().default({ alpha: true, bravo: false, charlie: false }),
|
|
228
|
+
flags: z.record(z.string(), z.boolean()).optional().default({ alpha: true, bravo: false, charlie: false }),
|
|
227
229
|
});
|
|
228
230
|
|
|
229
231
|
const config = createConfigBuilder<z.infer<typeof extendedSchema>>(extendedSchema);
|
|
@@ -329,7 +331,7 @@ describe('createConfigBuilder', () => {
|
|
|
329
331
|
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
330
332
|
|
|
331
333
|
const extendedSchema = configSchema.extend({
|
|
332
|
-
flags: z.record(z.boolean().default(true)).optional(),
|
|
334
|
+
flags: z.record(z.string(), z.boolean().default(true)).optional(),
|
|
333
335
|
});
|
|
334
336
|
|
|
335
337
|
expect(() => createConfigBuilder<z.infer<typeof extendedSchema>>(extendedSchema)).toThrow(
|
|
@@ -635,27 +637,29 @@ describe('createConfigBuilder', () => {
|
|
|
635
637
|
});
|
|
636
638
|
|
|
637
639
|
describe('when a user stringifies the config', () => {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const extendedSchema = configSchema.extend({
|
|
643
|
-
schema: z.instanceof(ZodType),
|
|
644
|
-
});
|
|
645
|
-
|
|
646
|
-
const config = createConfigBuilder<z.infer<typeof extendedSchema>>(extendedSchema);
|
|
640
|
+
it('should stringify the value to JSON correctly', async () => {
|
|
641
|
+
const { createConfigBuilder } = await import('./createConfigBuilder.ts');
|
|
642
|
+
const config = createConfigBuilder<ConfigType>(configSchema);
|
|
647
643
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
.optional(),
|
|
644
|
+
config
|
|
645
|
+
.countryCode('GB')
|
|
646
|
+
.languageCodes(['en'])
|
|
647
|
+
.locales(({ countryCode, languageCodes }) =>
|
|
648
|
+
// eslint-disable-next-line jest/no-conditional-in-test
|
|
649
|
+
languageCodes?.length && countryCode ? languageCodes.map(code => `${code}_${countryCode}`) : [],
|
|
655
650
|
);
|
|
656
651
|
|
|
657
|
-
|
|
658
|
-
|
|
652
|
+
expect(config.$toJson()).toMatchInlineSnapshot(`
|
|
653
|
+
"{
|
|
654
|
+
"countryCode": "GB",
|
|
655
|
+
"languageCodes": [
|
|
656
|
+
"en"
|
|
657
|
+
],
|
|
658
|
+
"locales": [
|
|
659
|
+
"en_GB"
|
|
660
|
+
]
|
|
661
|
+
}"
|
|
662
|
+
`);
|
|
659
663
|
});
|
|
660
664
|
});
|
|
661
665
|
});
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { type JSONSchema7 } from 'json-schema';
|
|
2
1
|
import { cloneDeep, merge } from 'lodash-es';
|
|
3
2
|
import { v4 as uuidV4 } from 'uuid';
|
|
4
|
-
import {
|
|
5
|
-
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
3
|
+
import { ZodError, z } from 'zod';
|
|
6
4
|
import { cloneNonEnumerableValues } from './transformers/cloneNonEnumerableValues.ts';
|
|
7
5
|
import { collateDefaultValues } from './utils/collateDefaultValues.ts';
|
|
8
6
|
import { isDerivedValueCallback } from './utils/isDerivedValueCallback.ts';
|
|
@@ -10,7 +8,6 @@ import { isInvalidPropertyOverride } from './utils/isInvalidPropertyOverride.ts'
|
|
|
10
8
|
import { RESERVED_KEYWORDS, isPropertyReservedWord } from './utils/isPropertyReservedWord.ts';
|
|
11
9
|
import { isSchemaValid } from './utils/isSchemaValid.ts';
|
|
12
10
|
import { isValidValue } from './utils/isValidValue.ts';
|
|
13
|
-
import { jsonStringifyReplacer } from './utils/jsonStringifyReplacer.ts';
|
|
14
11
|
import { transformConfigSync } from './utils/transformConfig.ts';
|
|
15
12
|
|
|
16
13
|
export type ConfigBuilder<ZodTypes> = {
|
|
@@ -20,7 +17,7 @@ export type ConfigBuilder<ZodTypes> = {
|
|
|
20
17
|
) => ConfigBuilder<ZodTypes>;
|
|
21
18
|
} & {
|
|
22
19
|
$disable: () => ConfigBuilder<ZodTypes>;
|
|
23
|
-
$errors: () => ZodError['
|
|
20
|
+
$errors: () => ZodError['issues'];
|
|
24
21
|
$experiment: (key: string) => ConfigBuilder<ZodTypes>;
|
|
25
22
|
$extend: (configBuilder: ConfigBuilder<ZodTypes>) => ConfigBuilder<ZodTypes>;
|
|
26
23
|
$flush: () => ZodTypes;
|
|
@@ -38,7 +35,7 @@ export type CreateConfigBuilderOptions = {
|
|
|
38
35
|
};
|
|
39
36
|
|
|
40
37
|
export const createConfigBuilder = <ZodTypes>(
|
|
41
|
-
zodSchema: z.
|
|
38
|
+
zodSchema: z.ZodType,
|
|
42
39
|
options: CreateConfigBuilderOptions = {},
|
|
43
40
|
derivedValueCallbacks: Partial<
|
|
44
41
|
Record<
|
|
@@ -58,9 +55,7 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
58
55
|
|
|
59
56
|
type DerivedValueCallback<K extends keyof Config = keyof Config> = (c: Config) => Config[K];
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
63
|
-
const jsonSchema = zodToJsonSchema(zodSchema) as JSONSchema7;
|
|
58
|
+
const jsonSchema = z.toJSONSchema(zodSchema);
|
|
64
59
|
|
|
65
60
|
if (isSchemaValid(jsonSchema)) {
|
|
66
61
|
throw new Error(`The root type of a config schema must be "object", but received "${String(jsonSchema.type)}"`);
|
|
@@ -68,29 +63,31 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
68
63
|
|
|
69
64
|
const uuid = options.overrides?.uuid ?? uuidV4;
|
|
70
65
|
|
|
71
|
-
const addNonEnumeralBaseProperties = (conf: Config) => {
|
|
72
|
-
Object.
|
|
73
|
-
|
|
74
|
-
enumerable: false,
|
|
75
|
-
value: true,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
Object.defineProperty(conf, '__id', {
|
|
79
|
-
configurable: false,
|
|
80
|
-
enumerable: false,
|
|
81
|
-
value: uuid(),
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
if (options.type) {
|
|
85
|
-
Object.defineProperty(conf, '__type', {
|
|
66
|
+
const addNonEnumeralBaseProperties = (conf: Config): void => {
|
|
67
|
+
Object.defineProperties(conf, {
|
|
68
|
+
__id: {
|
|
86
69
|
configurable: false,
|
|
87
70
|
enumerable: false,
|
|
88
|
-
value:
|
|
89
|
-
}
|
|
90
|
-
|
|
71
|
+
value: uuid(),
|
|
72
|
+
},
|
|
73
|
+
__zcb: {
|
|
74
|
+
configurable: false,
|
|
75
|
+
enumerable: false,
|
|
76
|
+
value: true,
|
|
77
|
+
},
|
|
78
|
+
...(options.type
|
|
79
|
+
? {
|
|
80
|
+
__type: {
|
|
81
|
+
configurable: false,
|
|
82
|
+
enumerable: false,
|
|
83
|
+
value: options.type,
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
: undefined),
|
|
87
|
+
});
|
|
91
88
|
};
|
|
92
89
|
|
|
93
|
-
const createInitialConfig = () => {
|
|
90
|
+
const createInitialConfig = (): Config => {
|
|
94
91
|
// Not casting here as loads of downstream impacts that make
|
|
95
92
|
// subsequent typing really difficult.
|
|
96
93
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -102,7 +99,7 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
102
99
|
let config = createInitialConfig();
|
|
103
100
|
let callbacks: Partial<Record<keyof Config, DerivedValueCallback>> = { ...derivedValueCallbacks };
|
|
104
101
|
|
|
105
|
-
// Aimed at making it easier for
|
|
102
|
+
// Aimed at making it easier for TypeScript to derive type.
|
|
106
103
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
107
104
|
const configBuilder = {
|
|
108
105
|
$disable: () => {
|
|
@@ -119,9 +116,7 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
119
116
|
zodSchema.parse(configBuilder.$values());
|
|
120
117
|
return [];
|
|
121
118
|
} catch (error: unknown) {
|
|
122
|
-
|
|
123
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
124
|
-
return (error as ZodError).errors;
|
|
119
|
+
return error instanceof ZodError ? error.issues : [];
|
|
125
120
|
}
|
|
126
121
|
},
|
|
127
122
|
$experiment: (key: string) => {
|
|
@@ -136,7 +131,7 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
136
131
|
$extend: (builder: ConfigBuilder<Config>) => {
|
|
137
132
|
config = transformConfigSync<Config>(builder.$values(), [cloneNonEnumerableValues]);
|
|
138
133
|
// @ts-expect-error private property
|
|
139
|
-
// As __callbacks is private property,
|
|
134
|
+
// As __callbacks is private property, TypeScript doesn't know what type is.
|
|
140
135
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
141
136
|
callbacks = { ...builder.__callbacks } as Partial<Record<keyof Config, DerivedValueCallback>>;
|
|
142
137
|
},
|
|
@@ -146,7 +141,7 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
146
141
|
return values;
|
|
147
142
|
},
|
|
148
143
|
$fork: () => createConfigBuilder<Config>(zodSchema, options, derivedValueCallbacks, initialValues),
|
|
149
|
-
$toJson: () => JSON.stringify(configBuilder.$values(),
|
|
144
|
+
$toJson: () => JSON.stringify(configBuilder.$values(), undefined, 2),
|
|
150
145
|
$validate: () => {
|
|
151
146
|
try {
|
|
152
147
|
zodSchema.parse(configBuilder.$values());
|
|
@@ -157,13 +152,15 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
157
152
|
}
|
|
158
153
|
},
|
|
159
154
|
$values: () => {
|
|
160
|
-
for (const
|
|
161
|
-
|
|
155
|
+
for (const propertyName in callbacks) {
|
|
156
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
157
|
+
const castPropertyName = propertyName as keyof Config;
|
|
158
|
+
// eslint-disable-next-line unicorn/no-unsafe-property-key
|
|
159
|
+
const callback = callbacks[castPropertyName];
|
|
162
160
|
|
|
163
161
|
if (callback) {
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
config[property as keyof Config] = callback(config);
|
|
162
|
+
// eslint-disable-next-line unicorn/no-unsafe-property-key
|
|
163
|
+
config[castPropertyName] = callback(config);
|
|
167
164
|
}
|
|
168
165
|
}
|
|
169
166
|
|
|
@@ -190,12 +187,12 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
190
187
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
191
188
|
const castPropertyName = propertyName as keyof Config;
|
|
192
189
|
|
|
193
|
-
if (!jsonSchema.required?.includes(propertyName) && !(propertyName
|
|
190
|
+
if (!jsonSchema.required?.includes(propertyName) && !Object.hasOwn(config, propertyName)) {
|
|
194
191
|
// @ts-expect-error Optional properties we want to exist in the config with undefined initial values.
|
|
195
192
|
config[propertyName] = undefined;
|
|
196
193
|
}
|
|
197
194
|
|
|
198
|
-
// Aimed at making it easier for
|
|
195
|
+
// Aimed at making it easier for TypeScript to derive type.
|
|
199
196
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
200
197
|
configBuilder[castPropertyName] = ((value: Config[keyof Config] | DerivedValueCallback, override?: boolean) => {
|
|
201
198
|
if (isInvalidPropertyOverride(config[castPropertyName], override)) {
|
|
@@ -206,7 +203,6 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
206
203
|
);
|
|
207
204
|
}
|
|
208
205
|
|
|
209
|
-
let propertyValue: Config[keyof Config];
|
|
210
206
|
const MAX_DEPTH = 1;
|
|
211
207
|
|
|
212
208
|
if (!isValidValue(value)) {
|
|
@@ -217,6 +213,8 @@ export const createConfigBuilder = <ZodTypes>(
|
|
|
217
213
|
);
|
|
218
214
|
}
|
|
219
215
|
|
|
216
|
+
let propertyValue: Config[keyof Config];
|
|
217
|
+
|
|
220
218
|
if (isDerivedValueCallback<DerivedValueCallback>(value)) {
|
|
221
219
|
callbacks[castPropertyName] = value;
|
|
222
220
|
propertyValue = value(config);
|
|
@@ -5,7 +5,7 @@ import { transformConfig } from './utils/transformConfig.ts';
|
|
|
5
5
|
export const createConfigParser = async <Config extends object>(
|
|
6
6
|
config: Config,
|
|
7
7
|
{ experimentsCallback }: ConfigParserOptions = {},
|
|
8
|
-
) => {
|
|
8
|
+
): Promise<Config> => {
|
|
9
9
|
const handlers: TransformConfigHandler[] = [];
|
|
10
10
|
|
|
11
11
|
if (experimentsCallback) {
|
|
@@ -9,7 +9,7 @@ export interface ConfigReader<Config extends object> {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export const createConfigReader = <Config extends object>(config: Config): ConfigReader<Config> => {
|
|
12
|
-
// Aimed at reducing the amount of work for
|
|
12
|
+
// Aimed at reducing the amount of work for TypeScript to resolve type.
|
|
13
13
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
14
14
|
return {
|
|
15
15
|
read: <P extends Path<Config>>(path: P, variables?: Record<string, string | number>) => {
|
|
@@ -32,7 +32,7 @@ export const createConfigReader = <Config extends object>(config: Config): Confi
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
return Object.keys(variables).reduce<string>((acc, key) => {
|
|
35
|
-
return acc.replace(new RegExp(`{{${key}}}`), String(variables[key]));
|
|
35
|
+
return acc.replace(new RegExp(`{{${key}}}`), () => String(variables[key]));
|
|
36
36
|
}, output);
|
|
37
37
|
},
|
|
38
38
|
scope: <S extends Scope<Config>>(scope: S) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* eslint-disable
|
|
1
|
+
/* eslint-disable import-x/no-default-export, unicorn/numeric-separators-style, unicorn/no-null */
|
|
2
2
|
/* This file is autogenerated, do not edit directly, your changes will not perist. */
|
|
3
3
|
|
|
4
4
|
export default {{{ config }}} as const;
|
|
@@ -10,7 +10,7 @@ export const cloneNonEnumerableValues: TransformConfigHandlerSync = <
|
|
|
10
10
|
config: Config2,
|
|
11
11
|
) => {
|
|
12
12
|
for (const nonEnumerableKey of NON_ENUMERABLE_KEYS) {
|
|
13
|
-
if (nonEnumerableKey
|
|
13
|
+
if (Object.hasOwn(config, nonEnumerableKey)) {
|
|
14
14
|
Object.defineProperty(clone, nonEnumerableKey, {
|
|
15
15
|
configurable: false,
|
|
16
16
|
enumerable: false,
|
|
@@ -1,16 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type AnyRecord,
|
|
3
|
+
type Buckets,
|
|
4
|
+
type RunExperimentsCallback,
|
|
5
|
+
type TransformConfigHandler,
|
|
6
|
+
type TransformConfigHandlerReturnType,
|
|
7
|
+
} from '../types.ts';
|
|
2
8
|
|
|
3
9
|
export const runExperiments =
|
|
4
10
|
(callback: RunExperimentsCallback): TransformConfigHandler =>
|
|
5
|
-
async <Config extends AnyRecord>(
|
|
11
|
+
async <Config extends AnyRecord>(
|
|
12
|
+
clone: Config,
|
|
13
|
+
config: Config,
|
|
14
|
+
): Promise<TransformConfigHandlerReturnType<Config>> => {
|
|
6
15
|
if ('__experiment' in config && typeof config.__experiment === 'object') {
|
|
7
|
-
// As __experiment is private property,
|
|
16
|
+
// As __experiment is private property, TypeScript doesn't know what type is.
|
|
8
17
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
9
18
|
const { buckets, id } = config.__experiment as { buckets: Buckets<Config>; id: string };
|
|
10
19
|
const bucket = await callback(id);
|
|
11
20
|
|
|
12
|
-
if (
|
|
13
|
-
|
|
21
|
+
if (Object.hasOwn(buckets, bucket)) {
|
|
22
|
+
// The check above essentially makes sure the value cannot be null
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
24
|
+
return buckets[bucket]!;
|
|
14
25
|
}
|
|
15
26
|
}
|
|
16
27
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type JSONSchema } from 'zod/v4/core';
|
|
2
2
|
|
|
3
|
-
export const arrayHasInvalidDefaults = (propertyDefinition:
|
|
3
|
+
export const arrayHasInvalidDefaults = (propertyDefinition: JSONSchema.JSONSchema): boolean => {
|
|
4
4
|
const { items } = propertyDefinition;
|
|
5
|
-
return items && typeof items === 'object' && 'default' in items;
|
|
5
|
+
return !!items && typeof items === 'object' && 'default' in items;
|
|
6
6
|
};
|