@featurevisor/core 1.5.1 → 1.6.0

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 (60) hide show
  1. package/.eslintcache +1 -1
  2. package/CHANGELOG.md +11 -0
  3. package/coverage/clover.xml +2 -2
  4. package/coverage/lcov-report/index.html +1 -1
  5. package/coverage/lcov-report/lib/builder/allocator.js.html +1 -1
  6. package/coverage/lcov-report/lib/builder/index.html +1 -1
  7. package/coverage/lcov-report/lib/builder/traffic.js.html +1 -1
  8. package/coverage/lcov-report/lib/tester/checkIfObjectsAreEqual.js.html +1 -1
  9. package/coverage/lcov-report/lib/tester/index.html +1 -1
  10. package/coverage/lcov-report/lib/tester/matrix.js.html +1 -1
  11. package/coverage/lcov-report/src/builder/allocator.ts.html +1 -1
  12. package/coverage/lcov-report/src/builder/index.html +1 -1
  13. package/coverage/lcov-report/src/builder/traffic.ts.html +1 -1
  14. package/coverage/lcov-report/src/tester/checkIfObjectsAreEqual.ts.html +1 -1
  15. package/coverage/lcov-report/src/tester/index.html +1 -1
  16. package/coverage/lcov-report/src/tester/matrix.ts.html +1 -1
  17. package/lib/linter/attributeSchema.d.ts +17 -2
  18. package/lib/linter/attributeSchema.js +13 -11
  19. package/lib/linter/attributeSchema.js.map +1 -1
  20. package/lib/linter/checkPercentageExceedingSlot.d.ts +3 -0
  21. package/lib/linter/checkPercentageExceedingSlot.js +86 -0
  22. package/lib/linter/checkPercentageExceedingSlot.js.map +1 -0
  23. package/lib/linter/conditionSchema.d.ts +2 -2
  24. package/lib/linter/conditionSchema.js +112 -57
  25. package/lib/linter/conditionSchema.js.map +1 -1
  26. package/lib/linter/featureSchema.d.ts +229 -2
  27. package/lib/linter/featureSchema.js +195 -139
  28. package/lib/linter/featureSchema.js.map +1 -1
  29. package/lib/linter/groupSchema.d.ts +32 -2
  30. package/lib/linter/groupSchema.js +28 -97
  31. package/lib/linter/groupSchema.js.map +1 -1
  32. package/lib/linter/lintProject.js +169 -118
  33. package/lib/linter/lintProject.js.map +1 -1
  34. package/lib/linter/printError.d.ts +2 -0
  35. package/lib/linter/printError.js +20 -0
  36. package/lib/linter/printError.js.map +1 -0
  37. package/lib/linter/segmentSchema.d.ts +14 -2
  38. package/lib/linter/segmentSchema.js +12 -10
  39. package/lib/linter/segmentSchema.js.map +1 -1
  40. package/lib/linter/testSchema.d.ts +90 -2
  41. package/lib/linter/testSchema.js +49 -38
  42. package/lib/linter/testSchema.js.map +1 -1
  43. package/lib/tester/cliFormat.d.ts +1 -0
  44. package/lib/tester/cliFormat.js +2 -1
  45. package/lib/tester/cliFormat.js.map +1 -1
  46. package/package.json +4 -4
  47. package/src/linter/attributeSchema.ts +11 -9
  48. package/src/linter/checkPercentageExceedingSlot.ts +41 -0
  49. package/src/linter/conditionSchema.ts +120 -97
  50. package/src/linter/featureSchema.ts +241 -177
  51. package/src/linter/groupSchema.ts +38 -54
  52. package/src/linter/lintProject.ts +144 -62
  53. package/src/linter/printError.ts +21 -0
  54. package/src/linter/segmentSchema.ts +10 -8
  55. package/src/linter/testSchema.ts +67 -50
  56. package/src/tester/cliFormat.ts +1 -0
  57. package/lib/linter/printJoiError.d.ts +0 -2
  58. package/lib/linter/printJoiError.js +0 -14
  59. package/lib/linter/printJoiError.js.map +0 -1
  60. package/src/linter/printJoiError.ts +0 -11
@@ -1,16 +1,18 @@
1
1
  // for use in node only
2
- import * as Joi from "joi";
2
+ import * as path from "path";
3
3
 
4
- import { getAttributeJoiSchema } from "./attributeSchema";
5
- import { getConditionsJoiSchema } from "./conditionSchema";
6
- import { getSegmentJoiSchema } from "./segmentSchema";
7
- import { getGroupJoiSchema } from "./groupSchema";
8
- import { getFeatureJoiSchema } from "./featureSchema";
9
- import { getTestsJoiSchema } from "./testSchema";
4
+ import { getAttributeZodSchema } from "./attributeSchema";
5
+ import { getConditionsZodSchema } from "./conditionSchema";
6
+ import { getSegmentZodSchema } from "./segmentSchema";
7
+ import { getGroupZodSchema } from "./groupSchema";
8
+ import { getFeatureZodSchema } from "./featureSchema";
9
+ import { getTestsZodSchema } from "./testSchema";
10
10
 
11
11
  import { checkForCircularDependencyInRequired } from "./checkCircularDependency";
12
- import { printJoiError } from "./printJoiError";
12
+ import { checkForFeatureExceedingGroupSlotPercentage } from "./checkPercentageExceedingSlot";
13
+ import { printZodError } from "./printError";
13
14
  import { Dependencies } from "../dependencies";
15
+ import { CLI_FORMAT_RED, CLI_FORMAT_UNDERLINE } from "../tester/cliFormat";
14
16
 
15
17
  export async function lintProject(deps: Dependencies): Promise<boolean> {
16
18
  const { projectConfig, datasource } = deps;
@@ -21,26 +23,59 @@ export async function lintProject(deps: Dependencies): Promise<boolean> {
21
23
  const availableSegmentKeys: string[] = [];
22
24
  const availableFeatureKeys: string[] = [];
23
25
 
26
+ function getFullPathFromKey(type: string, key: string, relative = false) {
27
+ const fileName = `${key}.${datasource.getExtension()}`;
28
+ let fullPath = "";
29
+
30
+ if (type === "attribute") {
31
+ fullPath = path.join(projectConfig.attributesDirectoryPath, fileName);
32
+ } else if (type === "segment") {
33
+ fullPath = path.join(projectConfig.segmentsDirectoryPath, fileName);
34
+ } else if (type === "feature") {
35
+ fullPath = path.join(projectConfig.featuresDirectoryPath, fileName);
36
+ } else if (type === "group") {
37
+ fullPath = path.join(projectConfig.groupsDirectoryPath, fileName);
38
+ } else if (type === "test") {
39
+ fullPath = path.join(projectConfig.testsDirectoryPath, fileName);
40
+ } else {
41
+ throw new Error(`Unknown type: ${type}`);
42
+ }
43
+
44
+ if (relative) {
45
+ fullPath = path.relative(process.cwd(), fullPath);
46
+ }
47
+
48
+ return fullPath;
49
+ }
50
+
24
51
  // lint attributes
25
52
  const attributes = await datasource.listAttributes();
26
53
  console.log(`Linting ${attributes.length} attributes...\n`);
27
54
 
28
- const attributeJoiSchema = getAttributeJoiSchema();
55
+ const attributeZodSchema = getAttributeZodSchema();
29
56
 
30
57
  for (const key of attributes) {
58
+ const fullPath = getFullPathFromKey("attribute", key);
59
+
31
60
  try {
32
61
  const parsed = await datasource.readAttribute(key);
33
62
  availableAttributeKeys.push(key);
34
- await attributeJoiSchema.validateAsync(parsed);
35
- } catch (e) {
36
- console.log(" =>", key);
37
- console.log("");
38
63
 
39
- if (e instanceof Joi.ValidationError) {
40
- printJoiError(e);
41
- } else {
42
- console.log(e);
64
+ const result = attributeZodSchema.safeParse(parsed);
65
+
66
+ if (!result.success) {
67
+ console.log(CLI_FORMAT_UNDERLINE, fullPath);
68
+
69
+ if ("error" in result) {
70
+ printZodError(result.error);
71
+ }
72
+
73
+ hasError = true;
43
74
  }
75
+ } catch (e) {
76
+ console.log(CLI_FORMAT_UNDERLINE, fullPath);
77
+ console.log("");
78
+ console.log(e);
44
79
 
45
80
  hasError = true;
46
81
  }
@@ -50,23 +85,34 @@ export async function lintProject(deps: Dependencies): Promise<boolean> {
50
85
  const segments = await datasource.listSegments();
51
86
  console.log(`\nLinting ${segments.length} segments...\n`);
52
87
 
53
- const conditionsJoiSchema = getConditionsJoiSchema(projectConfig, availableAttributeKeys);
54
- const segmentJoiSchema = getSegmentJoiSchema(projectConfig, conditionsJoiSchema);
88
+ const conditionsZodSchema = getConditionsZodSchema(
89
+ projectConfig,
90
+ availableAttributeKeys as [string, ...string[]],
91
+ );
92
+ const segmentZodSchema = getSegmentZodSchema(projectConfig, conditionsZodSchema);
55
93
 
56
94
  for (const key of segments) {
95
+ const fullPath = getFullPathFromKey("segment", key);
96
+
57
97
  try {
58
98
  const parsed = await datasource.readSegment(key);
59
99
  availableSegmentKeys.push(key);
60
- await segmentJoiSchema.validateAsync(parsed);
61
- } catch (e) {
62
- console.log(" =>", key);
63
- console.log("");
64
100
 
65
- if (e instanceof Joi.ValidationError) {
66
- printJoiError(e);
67
- } else {
68
- console.log(e);
101
+ const result = segmentZodSchema.safeParse(parsed);
102
+
103
+ if (!result.success) {
104
+ console.log(CLI_FORMAT_UNDERLINE, fullPath);
105
+
106
+ if ("error" in result) {
107
+ printZodError(result.error);
108
+ }
109
+
110
+ hasError = true;
69
111
  }
112
+ } catch (e) {
113
+ console.log(CLI_FORMAT_UNDERLINE, fullPath);
114
+ console.log("");
115
+ console.log(e);
70
116
 
71
117
  hasError = true;
72
118
  }
@@ -76,30 +122,37 @@ export async function lintProject(deps: Dependencies): Promise<boolean> {
76
122
  const features = await datasource.listFeatures();
77
123
  console.log(`\nLinting ${features.length} features...\n`);
78
124
 
79
- const featureJoiSchema = getFeatureJoiSchema(
125
+ const featureZodSchema = getFeatureZodSchema(
80
126
  projectConfig,
81
- conditionsJoiSchema,
82
- availableSegmentKeys,
83
- availableFeatureKeys,
127
+ conditionsZodSchema,
128
+ availableAttributeKeys as [string, ...string[]],
129
+ availableSegmentKeys as [string, ...string[]],
130
+ features as [string, ...string[]],
84
131
  );
85
132
 
86
133
  for (const key of features) {
134
+ const fullPath = getFullPathFromKey("feature", key);
87
135
  let parsed;
88
136
 
89
137
  try {
90
138
  parsed = await datasource.readFeature(key);
91
139
  availableFeatureKeys.push(key);
92
140
 
93
- await featureJoiSchema.validateAsync(parsed);
94
- } catch (e) {
95
- console.log(" =>", key);
96
- console.log("");
141
+ const result = featureZodSchema.safeParse(parsed);
142
+
143
+ if (!result.success) {
144
+ console.log(CLI_FORMAT_UNDERLINE, fullPath);
145
+
146
+ if ("error" in result) {
147
+ printZodError(result.error);
148
+ }
97
149
 
98
- if (e instanceof Joi.ValidationError) {
99
- printJoiError(e);
100
- } else {
101
- console.log(e);
150
+ hasError = true;
102
151
  }
152
+ } catch (e) {
153
+ console.log(CLI_FORMAT_UNDERLINE, fullPath);
154
+ console.log("");
155
+ console.log(e);
103
156
 
104
157
  hasError = true;
105
158
  }
@@ -108,8 +161,8 @@ export async function lintProject(deps: Dependencies): Promise<boolean> {
108
161
  try {
109
162
  await checkForCircularDependencyInRequired(datasource, key, parsed.required);
110
163
  } catch (e) {
111
- console.log(" =>", key);
112
- console.log(" => Error:", e.message);
164
+ console.log(CLI_FORMAT_UNDERLINE, fullPath);
165
+ console.log(CLI_FORMAT_RED, ` => Error: ${e.message}`);
113
166
  hasError = true;
114
167
  }
115
168
  }
@@ -120,24 +173,43 @@ export async function lintProject(deps: Dependencies): Promise<boolean> {
120
173
  console.log(`\nLinting ${groups.length} groups...\n`);
121
174
 
122
175
  // @TODO: feature it slots can be from availableFeatureKeys only
123
- const groupJoiSchema = getGroupJoiSchema(projectConfig, datasource, availableFeatureKeys);
176
+ const groupZodSchema = getGroupZodSchema(projectConfig, datasource, availableFeatureKeys);
124
177
 
125
178
  for (const key of groups) {
179
+ const fullPath = getFullPathFromKey("group", key);
180
+ let parsed;
181
+
126
182
  try {
127
- const parsed = await datasource.readGroup(key);
128
- await groupJoiSchema.validateAsync(parsed);
129
- } catch (e) {
130
- console.log(" =>", key);
131
- console.log("");
183
+ parsed = await datasource.readGroup(key);
184
+
185
+ const result = groupZodSchema.safeParse(parsed);
132
186
 
133
- if (e instanceof Joi.ValidationError) {
134
- printJoiError(e);
135
- } else {
136
- console.log(e);
187
+ if (!result.success) {
188
+ console.log(CLI_FORMAT_UNDERLINE, fullPath);
189
+
190
+ if ("error" in result) {
191
+ printZodError(result.error);
192
+ }
193
+
194
+ hasError = true;
137
195
  }
196
+ } catch (e) {
197
+ console.log(CLI_FORMAT_UNDERLINE, fullPath);
198
+ console.log("");
199
+ console.log(e);
138
200
 
139
201
  hasError = true;
140
202
  }
203
+
204
+ if (parsed) {
205
+ try {
206
+ await checkForFeatureExceedingGroupSlotPercentage(datasource, parsed, availableFeatureKeys);
207
+ } catch (e) {
208
+ console.log(CLI_FORMAT_UNDERLINE, fullPath);
209
+ console.log(CLI_FORMAT_RED, ` => Error: ${e.message}`);
210
+ hasError = true;
211
+ }
212
+ }
141
213
  }
142
214
 
143
215
  // @TODO: feature cannot exist in multiple groups
@@ -146,25 +218,35 @@ export async function lintProject(deps: Dependencies): Promise<boolean> {
146
218
  const tests = await datasource.listTests();
147
219
  console.log(`\nLinting ${tests.length} tests...\n`);
148
220
 
149
- const testsJoiSchema = getTestsJoiSchema(
221
+ const testsZodSchema = getTestsZodSchema(
150
222
  projectConfig,
151
- availableFeatureKeys,
152
- availableSegmentKeys,
223
+ availableFeatureKeys as [string, ...string[]],
224
+ availableSegmentKeys as [string, ...string[]],
153
225
  );
154
226
 
155
227
  for (const key of tests) {
228
+ const fullPath = getFullPathFromKey("test", key);
229
+
156
230
  try {
157
231
  const parsed = await datasource.readTest(key);
158
- await testsJoiSchema.validateAsync(parsed);
159
- } catch (e) {
160
- console.log(" =>", key);
161
- console.log("");
162
232
 
163
- if (e instanceof Joi.ValidationError) {
164
- printJoiError(e);
165
- } else {
166
- console.log(e);
233
+ const result = testsZodSchema.safeParse(parsed);
234
+
235
+ if (!result.success) {
236
+ console.log(CLI_FORMAT_UNDERLINE, fullPath);
237
+
238
+ if ("error" in result) {
239
+ printZodError(result.error);
240
+
241
+ process.exit(1);
242
+ }
243
+
244
+ hasError = true;
167
245
  }
246
+ } catch (e) {
247
+ console.log(CLI_FORMAT_UNDERLINE, fullPath);
248
+ console.log("");
249
+ console.log(e);
168
250
 
169
251
  hasError = true;
170
252
  }
@@ -0,0 +1,21 @@
1
+ import { ZodError } from "zod";
2
+
3
+ import { CLI_FORMAT_RED } from "../tester/cliFormat";
4
+
5
+ export function printZodError(e: ZodError) {
6
+ const { issues } = e;
7
+
8
+ issues.forEach((issue) => {
9
+ console.error(CLI_FORMAT_RED, ` => Error: ${issue.message}`);
10
+ console.error(" Path:", issue.path.join("."));
11
+
12
+ const receivedValue = (issue as any).received;
13
+ if (typeof receivedValue !== "undefined" && receivedValue !== "undefined") {
14
+ console.error(" Value:", receivedValue);
15
+ }
16
+
17
+ if (issues.length > 1) {
18
+ console.error("");
19
+ }
20
+ });
21
+ }
@@ -1,13 +1,15 @@
1
- import * as Joi from "joi";
1
+ import { z } from "zod";
2
2
 
3
3
  import { ProjectConfig } from "../config";
4
4
 
5
- export function getSegmentJoiSchema(projectConfig: ProjectConfig, conditionsJoiSchema) {
6
- const segmentJoiSchema = Joi.object({
7
- archived: Joi.boolean().optional(),
8
- description: Joi.string().required(),
9
- conditions: conditionsJoiSchema.required(),
10
- });
5
+ export function getSegmentZodSchema(projectConfig: ProjectConfig, conditionsZodSchema) {
6
+ const segmentZodSchema = z
7
+ .object({
8
+ archived: z.boolean().optional(),
9
+ description: z.string(),
10
+ conditions: conditionsZodSchema,
11
+ })
12
+ .strict();
11
13
 
12
- return segmentJoiSchema;
14
+ return segmentZodSchema;
13
15
  }
@@ -1,62 +1,79 @@
1
- import * as Joi from "joi";
1
+ import { z } from "zod";
2
2
 
3
3
  import { ProjectConfig } from "../config";
4
4
 
5
- export function getTestsJoiSchema(
5
+ export function getTestsZodSchema(
6
6
  projectConfig: ProjectConfig,
7
- availableFeatureKeys: string[],
8
- availableSegmentKeys: string[],
7
+ availableFeatureKeys: [string, ...string[]],
8
+ availableSegmentKeys: [string, ...string[]],
9
9
  ) {
10
- const segmentTestJoiSchema = Joi.object({
11
- segment: Joi.string()
12
- .valid(...availableSegmentKeys)
13
- .required(),
14
- assertions: Joi.array().items(
15
- Joi.object({
16
- matrix: Joi.object().optional(), // @TODO: make it stricter
17
- description: Joi.string().optional(),
18
- context: Joi.object(),
19
- expectedToMatch: Joi.boolean(),
20
- }),
21
- ),
22
- });
10
+ const segmentTestZodSchema = z
11
+ .object({
12
+ segment: z.string().refine(
13
+ (value) => availableSegmentKeys.includes(value),
14
+ (value) => ({
15
+ message: `Unknown segment "${value}"`,
16
+ }),
17
+ ),
18
+ assertions: z.array(
19
+ z
20
+ .object({
21
+ matrix: z.record(z.unknown()).optional(), // @TODO: make it stricter
22
+ description: z.string().optional(),
23
+ context: z.record(z.unknown()),
24
+ expectedToMatch: z.boolean(),
25
+ })
26
+ .strict(),
27
+ ),
28
+ })
29
+ .strict();
23
30
 
24
- const featureTestJoiSchema = Joi.object({
25
- feature: Joi.string()
26
- .valid(...availableFeatureKeys)
27
- .required(),
28
- assertions: Joi.array().items(
29
- Joi.object({
30
- matrix: Joi.object().optional(), // @TODO: make it stricter
31
- description: Joi.string().optional(),
32
- at: Joi.alternatives().try(
33
- Joi.number().precision(3).min(0).max(100).required(),
31
+ const featureTestZodSchema = z
32
+ .object({
33
+ feature: z.string().refine(
34
+ (value) => availableFeatureKeys.includes(value),
35
+ (value) => ({
36
+ message: `Unknown feature "${value}"`,
37
+ }),
38
+ ),
39
+ assertions: z.array(
40
+ z
41
+ .object({
42
+ matrix: z.record(z.unknown()).optional(), // @TODO: make it stricter
43
+ description: z.string().optional(),
44
+ at: z.union([
45
+ z.number().min(0).max(100),
34
46
 
35
- // because of supporting matrix
36
- Joi.string().required(),
37
- ),
38
- environment: Joi.string()
39
- .custom((value, helpers) => {
40
- if (value.indexOf("${{") === 0) {
41
- // allow unknown strings for matrix
42
- return value;
43
- }
47
+ // because of supporting matrix
48
+ z.string(),
49
+ ]),
50
+ environment: z.string().refine(
51
+ (value) => {
52
+ if (value.indexOf("${{") === 0) {
53
+ // allow unknown strings for matrix
54
+ return true;
55
+ }
44
56
 
45
- // otherwise only known environments should be passed
46
- if (projectConfig.environments.includes(value)) {
47
- return value;
48
- }
57
+ // otherwise only known environments should be passed
58
+ if (projectConfig.environments.includes(value)) {
59
+ return true;
60
+ }
49
61
 
50
- return helpers.error("any.invalid");
62
+ return false;
63
+ },
64
+ (value) => ({
65
+ message: `Unknown environment "${value}"`,
66
+ }),
67
+ ),
68
+ context: z.record(z.unknown()),
69
+ expectedToBeEnabled: z.boolean(),
70
+ expectedVariation: z.string().optional(),
71
+ expectedVariables: z.record(z.unknown()).optional(),
51
72
  })
52
- .required(),
53
- context: Joi.object().required(),
54
- expectedToBeEnabled: Joi.boolean().required(),
55
- expectedVariation: Joi.alternatives().try(Joi.string(), Joi.number(), Joi.boolean()),
56
- expectedVariables: Joi.object(),
57
- }),
58
- ),
59
- });
73
+ .strict(),
74
+ ),
75
+ })
76
+ .strict();
60
77
 
61
- return Joi.alternatives().try(segmentTestJoiSchema, featureTestJoiSchema);
78
+ return z.union([segmentTestZodSchema, featureTestZodSchema]);
62
79
  }
@@ -2,3 +2,4 @@ export const CLI_FORMAT_RED = "\x1b[31m%s\x1b[0m";
2
2
  export const CLI_FORMAT_GREEN = "\x1b[32m%s\x1b[0m";
3
3
 
4
4
  export const CLI_FORMAT_BOLD = "\x1b[1m%s\x1b[0m";
5
+ export const CLI_FORMAT_UNDERLINE = "\x1b[4m%s\x1b[0m";
@@ -1,2 +0,0 @@
1
- import * as Joi from "joi";
2
- export declare function printJoiError(e: Joi.ValidationError): void;
@@ -1,14 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.printJoiError = void 0;
4
- function printJoiError(e) {
5
- var details = e.details;
6
- details.forEach(function (detail) {
7
- var _a;
8
- console.error(" => Error:", detail.message);
9
- console.error(" => Path:", detail.path.join("."));
10
- console.error(" => Value:", (_a = detail.context) === null || _a === void 0 ? void 0 : _a.value);
11
- });
12
- }
13
- exports.printJoiError = printJoiError;
14
- //# sourceMappingURL=printJoiError.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"printJoiError.js","sourceRoot":"","sources":["../../src/linter/printJoiError.ts"],"names":[],"mappings":";;;AAEA,SAAgB,aAAa,CAAC,CAAsB;IAC1C,IAAA,OAAO,GAAK,CAAC,QAAN,CAAO;IAEtB,OAAO,CAAC,OAAO,CAAC,UAAC,MAAM;;QACrB,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QAChD,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACtD,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,MAAA,MAAM,CAAC,OAAO,0CAAE,KAAK,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;AACL,CAAC;AARD,sCAQC"}
@@ -1,11 +0,0 @@
1
- import * as Joi from "joi";
2
-
3
- export function printJoiError(e: Joi.ValidationError) {
4
- const { details } = e;
5
-
6
- details.forEach((detail) => {
7
- console.error(" => Error:", detail.message);
8
- console.error(" => Path:", detail.path.join("."));
9
- console.error(" => Value:", detail.context?.value);
10
- });
11
- }