zcb 0.0.6 → 0.0.7

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2023 dylanaubrey
3
+ Copyright (c) 2023 Dylan Aubrey
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,8 +1,315 @@
1
- # zod-config-builder
1
+ # zcb
2
2
 
3
3
  Build configs with type safety from zod schema.
4
4
 
5
- [![npm version](https://badge.fury.io/js/%40zod-config-builder.svg)](https://badge.fury.io/js/%40zod-config-builder)
5
+ [![npm version](https://badge.fury.io/js/zcb.svg)](https://badge.fury.io/js/zcb)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
7
 
8
- **WORK IN PROGRESS**
8
+ Define a configuration schema with [`zod`](https://github.com/colinhacks/zod) and use the output of that in `zcb` to create a config builder with autocomplete and setter value autocomplete.
9
+
10
+ Use our cli module to build and/or watch a config builder file and transform it into a literally typed config object.
11
+
12
+ Import that literally typed config in your components along with `zcb` and create a config reader with scoping abilities, config path autocomplete and return value preview.
13
+
14
+ ## Installation
15
+
16
+ ```sh
17
+ npm install zcb
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ * [Create schema](#create-schema)
23
+ * [Create config builder](#create-config-builder)
24
+ * [Transform config builder](#transform-config-builder)
25
+ * [Create config reader](#create-config-reader)
26
+
27
+ ### Create schema
28
+
29
+ Create the schema for your configuration like in the example below.
30
+
31
+ ```ts
32
+ // ./schema.ts
33
+ import { z } from 'zod';
34
+ import { countryCodes } from '../data/countryCodes.ts';
35
+ import { countryNames } from '../data/countryNames.ts';
36
+ import { distanceUnits } from '../data/distanceUnits.ts';
37
+ import { languageCodes } from '../data/languageCodes.ts';
38
+ import { timezones } from '../data/timezones.ts';
39
+
40
+ export const baseSectionSchema = z.object({
41
+ name: z.string(),
42
+ });
43
+
44
+ export type SectionType = z.infer<typeof baseSectionSchema> & {
45
+ sections?: SectionType[];
46
+ };
47
+
48
+ export const sectionSchema: z.ZodType<SectionType> = baseSectionSchema.extend({
49
+ sections: z.lazy(() => sectionSchema.array()).optional(),
50
+ });
51
+
52
+ export const pageSchema = z.object({
53
+ name: z.string(),
54
+ path: z.string().optional(),
55
+ queryParams: z.array(z.string()).optional(),
56
+ sections: z.array(sectionSchema).optional(),
57
+ });
58
+
59
+ export type PageType = z.infer<typeof pageSchema>;
60
+
61
+ const baseRouteSchema = z.object({
62
+ aliases: z.array(z.string()).optional(),
63
+ page: z.string(),
64
+ path: z.string(),
65
+ });
66
+
67
+ export type RouteType = z.infer<typeof baseRouteSchema> & {
68
+ routes?: RouteType[];
69
+ };
70
+
71
+ export const routeSchema: z.ZodType<RouteType> = baseRouteSchema.extend({
72
+ routes: z.lazy(() => routeSchema.array()).optional(),
73
+ });
74
+
75
+ export const configSchema = z.object({
76
+ countryCode: z.enum(countryCodes).optional(),
77
+ countryName: z.enum(countryNames).optional(),
78
+ distanceUnit: z.enum(distanceUnits).optional(),
79
+ languageCodes: z.array(z.enum(languageCodes)).optional(),
80
+ locales: z.array(z.string().regex(/[a-z]{2}_[A-Z]{2}/)).optional(),
81
+ name: z.string().optional(),
82
+ pages: z.record(pageSchema).optional(),
83
+ routes: z.array(routeSchema).optional(),
84
+ timeouts: z.record(z.number()).optional(),
85
+ timezone: z.enum(timezones).optional(),
86
+ });
87
+
88
+ export type ConfigType = z.infer<typeof configSchema>;
89
+ ```
90
+
91
+ ### Create config builder
92
+
93
+ Then use the schema and its types to create a config builder and build out your configuration like in the example below. The config builder comes with method autocompletion and value type validation. It is important to default export the config builder.
94
+
95
+ ```ts
96
+ // ./configBuilder.ts
97
+ import kebabCase from 'lodash/kebabCase.js';
98
+ import { createConfigBuilder } from 'zcb';
99
+ import {
100
+ type ConfigType,
101
+ type PageType,
102
+ type RouteType,
103
+ type SectionType,
104
+ configSchema,
105
+ pageSchema,
106
+ routeSchema,
107
+ sectionSchema,
108
+ } from './schema.ts';
109
+
110
+ const configBuilder = createConfigBuilder<ConfigType>(configSchema);
111
+ const routeBuilder = createConfigBuilder<RouteType>(routeSchema, { path: ({ page }) => kebabCase(page) });
112
+ const pageBuilder = createConfigBuilder<PageType>(pageSchema);
113
+ const sectionBuilder = createConfigBuilder<SectionType>(sectionSchema);
114
+ const subsectionBuilder = sectionBuilder.fork();
115
+
116
+ configBuilder
117
+ .countryCode('GB')
118
+ .countryName('United Kingdom')
119
+ .distanceUnit('km')
120
+ .languageCodes(['en'])
121
+ .locales(({ countryCode, languageCodes }) =>
122
+ languageCodes?.length && countryCode ? languageCodes.map(code => `${code}_${countryCode}`) : []
123
+ )
124
+ .name('alpha')
125
+ .pages({
126
+ contactDetails: pageBuilder
127
+ .name('contactDetails')
128
+ .sections([
129
+ sectionBuilder.name('header').flush(),
130
+ sectionBuilder
131
+ .name('body')
132
+ .sections([subsectionBuilder.name('main').flush(), subsectionBuilder.name('sidebar').flush()])
133
+ .flush(),
134
+ sectionBuilder.name('footer').flush(),
135
+ ])
136
+ .flush(),
137
+ personalDetails: pageBuilder
138
+ .name('personalDetails')
139
+ .sections([
140
+ sectionBuilder.name('header').flush(),
141
+ sectionBuilder
142
+ .name('body')
143
+ .sections([subsectionBuilder.name('main').flush(), subsectionBuilder.name('sidebar').flush()])
144
+ .flush(),
145
+ sectionBuilder.name('footer').flush(),
146
+ ])
147
+ .flush(),
148
+ })
149
+ .routes([routeBuilder.page('personalDetails').flush(), routeBuilder.page('contactDetails').flush()])
150
+ .timeouts({ apollo: 10_000 })
151
+ .timezone('Europe/London');
152
+
153
+ export default configBuilder;
154
+ ```
155
+
156
+ #### builder API
157
+
158
+ **disable: `() => ConfigBuilder`**
159
+
160
+ Use to disable a slice of config. Disabled slices are removed from the config when the config builder is transformed into a literally typed object in the cli build/watch step.
161
+
162
+ **errors: `() => ZodIssue[]`**
163
+
164
+ Use to validate the config against the schema and return any errors. The primary use for this is internal within the cli build/watch step.
165
+
166
+ **experiment: `() => ConfigBuilder`**
167
+
168
+ Use to assign an experiment ID to a slice of config. If an experiment callback file is provided to the cli build/watch step, this ID is used as a marker for where to inject experiment configuration.
169
+
170
+ **extend: `(value: ConfigBuilder) => void`**
171
+
172
+ Use to extend an existing config builder.
173
+
174
+ **flush: `() => JsonObject`**
175
+
176
+ Use to flush the values from a config builder so that it can be immediately reused.
177
+
178
+ **fork: `() => ConfigBuilder`**
179
+
180
+ Create a clone of a config builder. Useful if you need to use the same config builder within itself.
181
+
182
+ **toJson: `() => string`**
183
+
184
+ Returns the config values as a pretty-printed JSON string.
185
+
186
+ **validate: `() => boolean`**
187
+
188
+ Use to validate the config against the schema and return true/false. The primary use for this is internal within the cli build/watch step.
189
+
190
+ **values: `() => JsonObject`**
191
+
192
+ Use to return the config values as an object.
193
+
194
+ ---
195
+
196
+ ### Transform config builder
197
+
198
+ Use the script below or its `build` equivalent to transform a config builder file into a file that default exports a literally typed object like the one in the following example.
199
+
200
+ ```sh
201
+ npx zcb watch ./configBuilder.ts ./builtConfig.ts
202
+ ```
203
+
204
+ ```ts
205
+ // ./builtConfig.ts
206
+ /* eslint-disable prettier/prettier, import/no-default-export, unicorn/numeric-separators-style */
207
+ /* This file is autogenerated, do not edit directly, your changes will not perist. */
208
+
209
+ export default {
210
+ countryCode: "GB",
211
+ countryName: "United Kingdom",
212
+ distanceUnit: "km",
213
+ languageCodes: [
214
+ "en"
215
+ ],
216
+ locales: [
217
+ "en_GB"
218
+ ],
219
+ name: "alpha",
220
+ pages: {
221
+ contactDetails: {
222
+ name: "contactDetails",
223
+ sections: [
224
+ {
225
+ name: "header"
226
+ },
227
+ {
228
+ name: "body",
229
+ sections: [
230
+ {
231
+ name: "main"
232
+ },
233
+ {
234
+ name: "sidebar"
235
+ }
236
+ ]
237
+ },
238
+ {
239
+ name: "footer"
240
+ }
241
+ ]
242
+ },
243
+ personalDetails: {
244
+ name: "personalDetails",
245
+ sections: [
246
+ {
247
+ name: "header"
248
+ },
249
+ {
250
+ name: "body",
251
+ sections: [
252
+ {
253
+ name: "main"
254
+ },
255
+ {
256
+ name: "sidebar"
257
+ }
258
+ ]
259
+ },
260
+ {
261
+ name: "footer"
262
+ }
263
+ ]
264
+ }
265
+ },
266
+ routes: [
267
+ {
268
+ page: "personalDetails",
269
+ path: "personal-details"
270
+ },
271
+ {
272
+ page: "contactDetails",
273
+ path: "contact-details"
274
+ }
275
+ ],
276
+ timeouts: {
277
+ apollo: 10000
278
+ },
279
+ timezone: "Europe/London"
280
+ } as const;
281
+ ```
282
+
283
+ ### Create config reader
284
+
285
+ Then use the autogenerated config to create a config reader that you can access config values with. The config reader comes with config path autocomplete and return value preview. The autogenerated config will always be a default import.
286
+
287
+ ```ts
288
+ import builtConfig from '.builtConfig.ts';
289
+ import { createConfigReader } from 'zcb';
290
+
291
+ const reader = createConfigReader(builtConfig);
292
+ // scope config path autocompletion and validation
293
+ const scopedReader = reader.scope('pages.contactDetails')
294
+ .scope('sections.1.sections')
295
+ .scope('0');
296
+ // reader config path autocompletion and validation
297
+ // value and type preview
298
+ const value = scopedReader('name');
299
+ ```
300
+
301
+ #### reader API
302
+
303
+ **scope: `(value: string) => Get<Config, string>`**
304
+
305
+ Use to scope a reader to a slice of config, rather than having to pass in the full config path every time.
306
+
307
+ ---
308
+
309
+ ## Changelog
310
+
311
+ Check out the [features, fixes and more](CHANGELOG.md) that go into each major, minor and patch version.
312
+
313
+ ## License
314
+
315
+ zcb is [MIT Licensed](LICENSE).
package/bin/zcb.mjs CHANGED
@@ -1,3 +1,3 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env -S node --loader ts-node/esm
2
2
  const { cli } = await import('../dist/main/cli.mjs'); // eslint-disable-line import/no-unresolved
3
3
  cli();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zcb",
3
3
  "description": "Build configs with type safety from zod schema.",
4
- "version": "0.0.6",
4
+ "version": "0.0.7",
5
5
  "author": "Dylan Aubrey",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/badbatch/zod-config-builder",
@@ -48,23 +48,23 @@
48
48
  "@commitlint/config-conventional": "^17.6.3",
49
49
  "@commitlint/prompt-cli": "^17.6.3",
50
50
  "@jest/globals": "^29.5.0",
51
- "@repodog/babel-config": "^1.0.0",
52
- "@repodog/cli": "^1.1.1",
53
- "@repodog/commitlint-config": "^1.0.0",
54
- "@repodog/eslint-config": "^1.0.0",
55
- "@repodog/jest-config": "^1.0.0",
56
- "@repodog/markdownlint-config": "^1.0.0",
57
- "@repodog/prettier-config": "^1.0.0",
58
- "@repodog/rollup-config": "^1.0.0",
59
- "@repodog/syncpack-config": "1.0.0",
60
- "@repodog/ts-config": "^1.0.0",
51
+ "@repodog/babel-config": "^1.1.3",
52
+ "@repodog/cli": "^1.1.4",
53
+ "@repodog/commitlint-config": "^1.1.3",
54
+ "@repodog/eslint-config": "^1.1.3",
55
+ "@repodog/jest-config": "^1.1.3",
56
+ "@repodog/markdownlint-config": "^1.1.3",
57
+ "@repodog/prettier-config": "^1.1.3",
58
+ "@repodog/rollup-config": "^1.1.3",
59
+ "@repodog/syncpack-config": "^1.1.3",
60
+ "@repodog/ts-config": "^1.1.3",
61
61
  "@rollup/plugin-babel": "^6.0.3",
62
62
  "@rollup/plugin-image": "^3.0.2",
63
63
  "@rollup/plugin-json": "^6.0.0",
64
64
  "@rollup/plugin-node-resolve": "^15.0.2",
65
65
  "@rollup/plugin-terser": "^0.4.1",
66
66
  "@types/fs-extra": "^11.0.1",
67
- "@types/jest": "^29.5.1",
67
+ "@types/jest": "^25.1.3",
68
68
  "@types/json-schema": "^7.0.11",
69
69
  "@types/lodash": "^4.14.191",
70
70
  "@types/node": "^18.11.18",
@@ -90,7 +90,7 @@
90
90
  "eslint-plugin-sort-destructure-keys": "^1.5.0",
91
91
  "eslint-plugin-sort-keys-fix": "^1.1.2",
92
92
  "eslint-plugin-typescript-sort-keys": "^2.3.0",
93
- "eslint-plugin-unicorn": "^47.0.0",
93
+ "eslint-plugin-unicorn": "^46.0.1",
94
94
  "generate-changelog": "^1.8.0",
95
95
  "husky": "^8.0.3",
96
96
  "identity-obj-proxy": "^3.0.0",
@@ -108,6 +108,11 @@
108
108
  "type-fest": "^3.10.0",
109
109
  "typescript": "^5.0.3"
110
110
  },
111
+ "keywords": [
112
+ "config",
113
+ "configuration",
114
+ "zod"
115
+ ],
111
116
  "scripts": {
112
117
  "build": "pnpm run clean:dist && pnpm run compile",
113
118
  "clean:deps": "del-cli ./node_modules",
@@ -124,6 +129,7 @@
124
129
  "test": "node --require=suppress-experimental-warnings --experimental-vm-modules node_modules/jest/bin/jest.js",
125
130
  "type-check": "tsc --noEmit",
126
131
  "validate": "syncpack format && syncpack lint-semver-ranges && pnpm run build && pnpm run lint && pnpm run type-check && pnpm run test",
132
+ "zcb:build": "NODE_OPTIONS=\"--loader ts-node/esm\" node ./bin/zcb.mjs build ./src/__testUtils__/configBuilder.ts ./src/__testUtils__/builtConfig.ts",
127
133
  "zcb:watch": "NODE_OPTIONS=\"--loader ts-node/esm\" node ./bin/zcb.mjs watch ./src/__testUtils__/configBuilder.ts ./src/__testUtils__/builtConfig.ts"
128
134
  }
129
135
  }
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable prettier/prettier, import/no-default-export, unicorn/numeric-separators-style */
2
+ /* This file is autogenerated, do not edit directly, your changes will not perist. */
2
3
 
3
4
  export default {
4
5
  countryCode: "GB",
package/src/cli.ts CHANGED
@@ -2,85 +2,65 @@ import { watchFile } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
3
  import shelljs from 'shelljs';
4
4
  import yargs from 'yargs';
5
- import type { ExperimentsCallback } from './types.ts';
6
- import { writeConfig } from './utils/writeConfig.ts';
5
+ import { importValidateWriteConfig } from './utils/importValidateWriteConfig.ts';
6
+
7
+ export enum Commands {
8
+ BUILD = 'build',
9
+ WATCH = 'watch',
10
+ }
11
+
12
+ const generateArguments = (cmdYargs: yargs.Argv) =>
13
+ cmdYargs
14
+ .positional('input-file', {
15
+ demandOption: true,
16
+ desc: 'The relative path to the config builder root file',
17
+ type: 'string',
18
+ })
19
+ .positional('output-file', {
20
+ demandOption: true,
21
+ desc: 'The relative path to the output config file',
22
+ type: 'string',
23
+ })
24
+ .option('experiments-callback-file', {
25
+ desc: 'The relative path to the experiment callback file',
26
+ type: 'string',
27
+ });
7
28
 
8
29
  export const cli = () => {
9
30
  yargs
10
31
  .command(
11
- 'watch <inputFile> <outputFile>',
12
- 'watch a config builder and write ',
13
- cmdYargs =>
14
- cmdYargs
15
- .positional('inputFile', {
16
- demandOption: true,
17
- desc: 'The relative path to the config builder root file',
18
- type: 'string',
19
- })
20
- .positional('outputFile', {
21
- demandOption: true,
22
- desc: 'The relative path to the output config file',
23
- type: 'string',
24
- })
25
- .option('experiments-callback', {
26
- desc: 'The relative path to the experiment callback file',
27
- type: 'string',
28
- }),
32
+ 'watch <input-file> <output-file>',
33
+ 'Watch a config builder and write config',
34
+ cmdYargs => generateArguments(cmdYargs),
29
35
  argv => {
30
- const fullWatchFilePath = resolve(process.cwd(), argv.inputFile);
31
- shelljs.echo(`zcd watch => watching file: ${argv.inputFile}`);
32
-
33
- watchFile(fullWatchFilePath, () => {
34
- import(fullWatchFilePath)
35
- .then(
36
- ({
37
- default: configBuilder,
38
- }: {
39
- default: ReturnType<typeof import('./createConfigBuilder.ts')['createConfigBuilder']>;
40
- }) => {
41
- shelljs.echo('zcd watch => file change detected');
42
-
43
- if (!configBuilder.validate()) {
44
- shelljs.echo('zcd watch => invalid config');
45
- shelljs.echo(`zcd watch => errors:\n${JSON.stringify(configBuilder.errors(), undefined, 2)}\n`);
46
- shelljs.exit(1);
47
- }
48
-
49
- shelljs.echo('zcd watch => valid config');
50
- shelljs.echo(`zcd watch => config values:\n${configBuilder.toJson()}\n`);
51
-
52
- if (argv['experiments-callback']) {
53
- import(resolve(process.cwd(), argv['experiments-callback']))
54
- .then(({ default: experimentsCallback }: { default: ExperimentsCallback }) => {
55
- void writeConfig(configBuilder.values(), { experimentsCallback, outputFile: argv.outputFile });
56
- })
57
- .catch((error: unknown) => {
58
- if (error instanceof Error) {
59
- shelljs.echo(`zcd watch => error message: ${error.message}`);
60
-
61
- if (error.stack) {
62
- shelljs.echo(`zcd watch => error stack:\n${error.stack}\n`);
63
- }
64
- }
65
- });
36
+ shelljs.echo(`zcd watch => watching file: ${argv['input-file']}`);
66
37
 
67
- return;
68
- }
38
+ watchFile(resolve(process.cwd(), argv['input-file']), () => {
39
+ shelljs.echo('zcd watch => file change detected');
69
40
 
70
- void writeConfig(configBuilder.values(), { outputFile: argv.outputFile });
71
- }
72
- )
73
- .catch((error: unknown) => {
74
- if (error instanceof Error) {
75
- shelljs.echo(`zcd watch => error message: ${error.message}`);
76
-
77
- if (error.stack) {
78
- shelljs.echo(`zcd watch => error stack:\n${error.stack}\n`);
79
- }
80
- }
81
- });
41
+ importValidateWriteConfig(
42
+ argv['input-file'],
43
+ argv['output-file'],
44
+ Commands.WATCH,
45
+ argv['experiments-callback-file']
46
+ );
82
47
  });
83
48
  }
84
49
  )
50
+ .command(
51
+ 'build <input-file> <output-file>',
52
+ 'Write config from a config builder',
53
+ cmdYargs => generateArguments(cmdYargs),
54
+ argv => {
55
+ shelljs.echo(`zcd build => building file: ${argv['input-file']}`);
56
+
57
+ importValidateWriteConfig(
58
+ argv['input-file'],
59
+ argv['output-file'],
60
+ Commands.BUILD,
61
+ argv['experiments-callback-file']
62
+ );
63
+ }
64
+ )
85
65
  .help().argv;
86
66
  };
@@ -8,6 +8,7 @@ import {
8
8
  pageSchema,
9
9
  routeSchema,
10
10
  } from './__testUtils__/schema.ts';
11
+ import { RESERVED_KEYWORDS } from './utils/isPropertyReservedWord.ts';
11
12
 
12
13
  describe('createConfigBuilder', () => {
13
14
  describe('when a user passes in a schema with a root type other than "object"', () => {
@@ -23,7 +24,7 @@ describe('createConfigBuilder', () => {
23
24
 
24
25
  describe('when a user uses a key in the schema that is a reserved keyword', () => {
25
26
  it('should throw an error', async () => {
26
- const { RESERVED_KEYWORDS, createConfigBuilder } = await import('./createConfigBuilder.ts');
27
+ const { createConfigBuilder } = await import('./createConfigBuilder.ts');
27
28
 
28
29
  const invalidSchema = z.object({
29
30
  values: z.string(),
@@ -5,7 +5,7 @@ import { cloneNonEnumerableValues } from './transformers/cloneNonEnumerableValue
5
5
  import { arrayHasInvalidDefaults } from './utils/arrayHasInvalidDefaults.ts';
6
6
  import { isDerivedValueCallback } from './utils/isDerivedValueCallback.ts';
7
7
  import { isInvalidPropertyOverride } from './utils/isInvalidPropertyOverride.ts';
8
- import { isPropertyReservedWord } from './utils/isPropertyReservedWord.ts';
8
+ import { RESERVED_KEYWORDS, isPropertyReservedWord } from './utils/isPropertyReservedWord.ts';
9
9
  import { isSchemaValid } from './utils/isSchemaValid.ts';
10
10
  import { isValidPropertyDefinition } from './utils/isValidPropertyDefinition.ts';
11
11
  import { isValidValue } from './utils/isValidValue.ts';
@@ -13,18 +13,6 @@ import { objectHasInvalidDefaults } from './utils/objectHasInvalidDefaults.ts';
13
13
  import { recordHasInvalidDefaults } from './utils/recordHasInvalidDefaults.ts';
14
14
  import { transformConfigSync } from './utils/transformConfig.ts';
15
15
 
16
- export const RESERVED_KEYWORDS = new Set([
17
- 'disable',
18
- 'errors',
19
- 'experiment',
20
- 'extend',
21
- 'flush',
22
- 'fork',
23
- 'toJson',
24
- 'validate',
25
- 'values',
26
- ]);
27
-
28
16
  export const createConfigBuilder = <ZodTypes>(
29
17
  zodSchema: z.ZodSchema,
30
18
  derivedValueCallbacks: Partial<
package/src/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from './createConfigBuilder.ts';
2
+ export * from './createConfigReader.ts';
2
3
  export * from './data/countryCodes.ts';
3
4
  export * from './data/countryNames.ts';
4
5
  export * from './data/distanceUnits.ts';
5
6
  export * from './data/languageCodes.ts';
6
7
  export * from './data/timezones.ts';
8
+ export * from './types.ts';
@@ -1,3 +1,4 @@
1
1
  /* eslint-disable prettier/prettier, import/no-default-export, unicorn/numeric-separators-style */
2
+ /* This file is autogenerated, do not edit directly, your changes will not perist. */
2
3
 
3
4
  export default {{{ config }}} as const;
@@ -0,0 +1,59 @@
1
+ import { resolve } from 'node:path';
2
+ import shelljs from 'shelljs';
3
+ import type { Commands } from '../cli.ts';
4
+ import type { ExperimentsCallback } from '../types.ts';
5
+ import { writeConfig } from './writeConfig.ts';
6
+
7
+ export const importValidateWriteConfig = (
8
+ inputFile: string,
9
+ outputFile: string,
10
+ command: Commands,
11
+ experimentCallbackFile?: string
12
+ ) => {
13
+ import(resolve(process.cwd(), inputFile))
14
+ .then(
15
+ ({
16
+ default: configBuilder,
17
+ }: {
18
+ default: ReturnType<typeof import('../createConfigBuilder.ts')['createConfigBuilder']>;
19
+ }) => {
20
+ if (!configBuilder.validate()) {
21
+ shelljs.echo(`zcd ${command} => invalid config`);
22
+ shelljs.echo(`zcd ${command} => errors:\n${JSON.stringify(configBuilder.errors(), undefined, 2)}\n`);
23
+ shelljs.exit(1);
24
+ }
25
+
26
+ shelljs.echo('zcd ${command} => valid config');
27
+ shelljs.echo(`zcd ${command} => config values:\n${configBuilder.toJson()}\n`);
28
+
29
+ if (experimentCallbackFile) {
30
+ import(resolve(process.cwd(), experimentCallbackFile))
31
+ .then(({ default: experimentsCallback }: { default: ExperimentsCallback }) => {
32
+ void writeConfig(configBuilder.values(), { experimentsCallback, outputFile });
33
+ })
34
+ .catch((error: unknown) => {
35
+ if (error instanceof Error) {
36
+ shelljs.echo(`zcd ${command} => error message: ${error.message}`);
37
+
38
+ if (error.stack) {
39
+ shelljs.echo(`zcd ${command} => error stack:\n${error.stack}\n`);
40
+ }
41
+ }
42
+ });
43
+
44
+ return;
45
+ }
46
+
47
+ void writeConfig(configBuilder.values(), { outputFile });
48
+ }
49
+ )
50
+ .catch((error: unknown) => {
51
+ if (error instanceof Error) {
52
+ shelljs.echo(`zcd ${command} => error message: ${error.message}`);
53
+
54
+ if (error.stack) {
55
+ shelljs.echo(`zcd ${command} => error stack:\n${error.stack}\n`);
56
+ }
57
+ }
58
+ });
59
+ };
@@ -1,3 +1,13 @@
1
- import { RESERVED_KEYWORDS } from '../createConfigBuilder.ts';
1
+ export const RESERVED_KEYWORDS = new Set([
2
+ 'disable',
3
+ 'errors',
4
+ 'experiment',
5
+ 'extend',
6
+ 'flush',
7
+ 'fork',
8
+ 'toJson',
9
+ 'validate',
10
+ 'values',
11
+ ]);
2
12
 
3
13
  export const isPropertyReservedWord = (propertyName: string) => RESERVED_KEYWORDS.has(propertyName);
@@ -30,5 +30,5 @@ export const writeConfig = async <Config extends object>(
30
30
 
31
31
  shelljs.echo(`zcd watch => writing to file: ${outputFile}`);
32
32
  shelljs.echo(`zcd watch => content to write:\n${output}\n`);
33
- outputFileSync(outputFile, output);
33
+ outputFileSync(resolve(process.cwd(), outputFile), output);
34
34
  };