@featurevisor/core 2.9.0 → 2.10.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.
- package/CHANGELOG.md +11 -0
- package/coverage/clover.xml +2 -2
- package/coverage/lcov-report/builder/allocator.ts.html +1 -1
- package/coverage/lcov-report/builder/buildScopedConditions.ts.html +1 -1
- package/coverage/lcov-report/builder/buildScopedDatafile.ts.html +1 -1
- package/coverage/lcov-report/builder/buildScopedSegments.ts.html +1 -1
- package/coverage/lcov-report/builder/index.html +1 -1
- package/coverage/lcov-report/builder/revision.ts.html +1 -1
- package/coverage/lcov-report/builder/traffic.ts.html +1 -1
- package/coverage/lcov-report/index.html +1 -1
- package/coverage/lcov-report/list/index.html +1 -1
- package/coverage/lcov-report/list/matrix.ts.html +1 -1
- package/coverage/lcov-report/parsers/index.html +1 -1
- package/coverage/lcov-report/parsers/json.ts.html +1 -1
- package/coverage/lcov-report/parsers/yml.ts.html +1 -1
- package/coverage/lcov-report/tester/helpers.ts.html +1 -1
- package/coverage/lcov-report/tester/index.html +1 -1
- package/lib/generate-code/typescript.js +150 -16
- package/lib/generate-code/typescript.js.map +1 -1
- package/lib/linter/featureSchema.d.ts +114 -100
- package/lib/linter/featureSchema.js +222 -80
- package/lib/linter/featureSchema.js.map +1 -1
- package/lib/linter/propertySchema.d.ts +5 -0
- package/lib/linter/propertySchema.js +43 -0
- package/lib/linter/propertySchema.js.map +1 -0
- package/package.json +5 -5
- package/src/generate-code/typescript.ts +168 -18
- package/src/linter/featureSchema.ts +282 -95
- package/src/linter/propertySchema.ts +47 -0
|
@@ -1,23 +1,156 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
import { ProjectConfig } from "../config";
|
|
4
|
+
import { valueZodSchema, propertyTypeEnum, getPropertyZodSchema } from "./propertySchema";
|
|
4
5
|
|
|
5
6
|
const tagRegex = /^[a-z0-9-]+$/;
|
|
6
7
|
|
|
7
|
-
function
|
|
8
|
-
|
|
8
|
+
function isArrayOfStrings(value: unknown): value is string[] {
|
|
9
|
+
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isFlatObjectValue(value: unknown): boolean {
|
|
13
|
+
return (
|
|
14
|
+
value === null ||
|
|
15
|
+
typeof value === "string" ||
|
|
16
|
+
typeof value === "number" ||
|
|
17
|
+
typeof value === "boolean"
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getVariableLabel(variableSchema, variableKey, path) {
|
|
22
|
+
return (
|
|
23
|
+
variableKey ??
|
|
24
|
+
variableSchema?.key ??
|
|
25
|
+
(path.length > 0 ? String(path[path.length - 1]) : "variable")
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function typeOfValue(value: unknown): string {
|
|
30
|
+
if (value === null) return "null";
|
|
31
|
+
if (value === undefined) return "undefined";
|
|
32
|
+
if (Array.isArray(value)) return "array";
|
|
33
|
+
return typeof value;
|
|
34
|
+
}
|
|
9
35
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Validates a variable value against an array schema. Recursively validates each item
|
|
38
|
+
* when the schema defines `items` (nested arrays/objects use the same refinement).
|
|
39
|
+
*/
|
|
40
|
+
function refineVariableValueArray(
|
|
41
|
+
projectConfig: ProjectConfig,
|
|
42
|
+
variableSchema: { items?: unknown; type: string },
|
|
43
|
+
variableValue: unknown[],
|
|
44
|
+
path: (string | number)[],
|
|
45
|
+
ctx: z.RefinementCtx,
|
|
46
|
+
variableKey?: string,
|
|
47
|
+
): void {
|
|
48
|
+
const label = getVariableLabel(variableSchema, variableKey, path);
|
|
49
|
+
const itemSchema = variableSchema.items;
|
|
50
|
+
|
|
51
|
+
if (itemSchema) {
|
|
52
|
+
variableValue.forEach((item, index) => {
|
|
53
|
+
superRefineVariableValue(projectConfig, itemSchema, item, [...path, index], ctx, variableKey);
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
if (!isArrayOfStrings(variableValue)) {
|
|
57
|
+
ctx.addIssue({
|
|
58
|
+
code: z.ZodIssueCode.custom,
|
|
59
|
+
message: `Variable "${label}" (type array): when \`items\` is not set, array must contain only strings; found non-string element.`,
|
|
60
|
+
path,
|
|
61
|
+
});
|
|
13
62
|
}
|
|
14
|
-
}
|
|
63
|
+
}
|
|
15
64
|
|
|
16
|
-
|
|
65
|
+
if (projectConfig.maxVariableArrayStringifiedLength) {
|
|
66
|
+
const stringified = JSON.stringify(variableValue);
|
|
67
|
+
if (stringified.length > projectConfig.maxVariableArrayStringifiedLength) {
|
|
68
|
+
ctx.addIssue({
|
|
69
|
+
code: z.ZodIssueCode.custom,
|
|
70
|
+
message: `Variable "${label}" array is too long (${stringified.length} characters), max length is ${projectConfig.maxVariableArrayStringifiedLength}`,
|
|
71
|
+
path,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
17
75
|
}
|
|
18
76
|
|
|
19
|
-
|
|
20
|
-
|
|
77
|
+
/**
|
|
78
|
+
* Validates a variable value against an object schema. Recursively validates each property
|
|
79
|
+
* when the schema defines `properties` (nested objects/arrays use the same refinement).
|
|
80
|
+
*/
|
|
81
|
+
function refineVariableValueObject(
|
|
82
|
+
projectConfig: ProjectConfig,
|
|
83
|
+
variableSchema: {
|
|
84
|
+
properties?: Record<string, unknown>;
|
|
85
|
+
required?: string[];
|
|
86
|
+
type: string;
|
|
87
|
+
},
|
|
88
|
+
variableValue: Record<string, unknown>,
|
|
89
|
+
path: (string | number)[],
|
|
90
|
+
ctx: z.RefinementCtx,
|
|
91
|
+
variableKey?: string,
|
|
92
|
+
): void {
|
|
93
|
+
const label = getVariableLabel(variableSchema, variableKey, path);
|
|
94
|
+
const schemaProperties = variableSchema.properties;
|
|
95
|
+
|
|
96
|
+
if (schemaProperties && typeof schemaProperties === "object") {
|
|
97
|
+
const requiredKeys =
|
|
98
|
+
variableSchema.required && variableSchema.required.length > 0
|
|
99
|
+
? variableSchema.required
|
|
100
|
+
: Object.keys(schemaProperties);
|
|
101
|
+
|
|
102
|
+
for (const key of requiredKeys) {
|
|
103
|
+
if (!Object.prototype.hasOwnProperty.call(variableValue, key)) {
|
|
104
|
+
ctx.addIssue({
|
|
105
|
+
code: z.ZodIssueCode.custom,
|
|
106
|
+
message: `Missing required property "${key}" in variable "${label}"`,
|
|
107
|
+
path: [...path, key],
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const key of Object.keys(variableValue)) {
|
|
113
|
+
const propSchema = schemaProperties[key];
|
|
114
|
+
if (!propSchema) {
|
|
115
|
+
ctx.addIssue({
|
|
116
|
+
code: z.ZodIssueCode.custom,
|
|
117
|
+
message: `Unknown property "${key}" in variable "${label}" (not in schema)`,
|
|
118
|
+
path: [...path, key],
|
|
119
|
+
});
|
|
120
|
+
} else {
|
|
121
|
+
superRefineVariableValue(
|
|
122
|
+
projectConfig,
|
|
123
|
+
propSchema,
|
|
124
|
+
variableValue[key],
|
|
125
|
+
[...path, key],
|
|
126
|
+
ctx,
|
|
127
|
+
key,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
for (const key of Object.keys(variableValue)) {
|
|
133
|
+
const propValue = variableValue[key];
|
|
134
|
+
if (!isFlatObjectValue(propValue)) {
|
|
135
|
+
ctx.addIssue({
|
|
136
|
+
code: z.ZodIssueCode.custom,
|
|
137
|
+
message: `Variable "${label}" is a flat object (no \`properties\` in schema); property "${key}" must be a primitive (string, number, boolean, or null), got: ${typeof propValue}`,
|
|
138
|
+
path: [...path, key],
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (projectConfig.maxVariableObjectStringifiedLength) {
|
|
145
|
+
const stringified = JSON.stringify(variableValue);
|
|
146
|
+
if (stringified.length > projectConfig.maxVariableObjectStringifiedLength) {
|
|
147
|
+
ctx.addIssue({
|
|
148
|
+
code: z.ZodIssueCode.custom,
|
|
149
|
+
message: `Variable "${label}" object is too long (${stringified.length} characters), max length is ${projectConfig.maxVariableObjectStringifiedLength}`,
|
|
150
|
+
path,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
21
154
|
}
|
|
22
155
|
|
|
23
156
|
function superRefineVariableValue(
|
|
@@ -26,17 +159,16 @@ function superRefineVariableValue(
|
|
|
26
159
|
variableValue,
|
|
27
160
|
path,
|
|
28
161
|
ctx,
|
|
162
|
+
variableKey?: string,
|
|
29
163
|
) {
|
|
30
|
-
|
|
31
|
-
let message = `Unknown variable with value: ${variableValue}`;
|
|
32
|
-
|
|
33
|
-
if (path.length > 0) {
|
|
34
|
-
const lastPath = path[path.length - 1];
|
|
164
|
+
const label = getVariableLabel(variableSchema, variableKey, path);
|
|
35
165
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
166
|
+
if (!variableSchema) {
|
|
167
|
+
const variableName =
|
|
168
|
+
path.length > 0 && typeof path[path.length - 1] === "string"
|
|
169
|
+
? String(path[path.length - 1])
|
|
170
|
+
: "variable";
|
|
171
|
+
const message = `Variable "${variableName}" is used but not defined in variablesSchema. Define it under variablesSchema first, then use it here.`;
|
|
40
172
|
|
|
41
173
|
ctx.addIssue({
|
|
42
174
|
code: z.ZodIssueCode.custom,
|
|
@@ -47,14 +179,28 @@ function superRefineVariableValue(
|
|
|
47
179
|
return;
|
|
48
180
|
}
|
|
49
181
|
|
|
50
|
-
//
|
|
51
|
-
if (
|
|
182
|
+
// Require a value (no undefined) for every variable usage
|
|
183
|
+
if (variableValue === undefined) {
|
|
184
|
+
ctx.addIssue({
|
|
185
|
+
code: z.ZodIssueCode.custom,
|
|
186
|
+
message: `Variable "${label}" value is required (got undefined).`,
|
|
187
|
+
path,
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const expectedType = variableSchema.type;
|
|
193
|
+
const gotType = typeOfValue(variableValue);
|
|
194
|
+
|
|
195
|
+
// string — only string allowed
|
|
196
|
+
if (expectedType === "string") {
|
|
52
197
|
if (typeof variableValue !== "string") {
|
|
53
198
|
ctx.addIssue({
|
|
54
199
|
code: z.ZodIssueCode.custom,
|
|
55
|
-
message: `
|
|
200
|
+
message: `Variable "${label}" (type string) must be a string; got ${gotType}.`,
|
|
56
201
|
path,
|
|
57
202
|
});
|
|
203
|
+
return;
|
|
58
204
|
}
|
|
59
205
|
|
|
60
206
|
if (
|
|
@@ -63,7 +209,7 @@ function superRefineVariableValue(
|
|
|
63
209
|
) {
|
|
64
210
|
ctx.addIssue({
|
|
65
211
|
code: z.ZodIssueCode.custom,
|
|
66
|
-
message: `Variable "${
|
|
212
|
+
message: `Variable "${label}" value is too long (${variableValue.length} characters), max length is ${projectConfig.maxVariableStringLength}`,
|
|
67
213
|
path,
|
|
68
214
|
});
|
|
69
215
|
}
|
|
@@ -71,94 +217,123 @@ function superRefineVariableValue(
|
|
|
71
217
|
return;
|
|
72
218
|
}
|
|
73
219
|
|
|
74
|
-
// integer,
|
|
75
|
-
if (
|
|
220
|
+
// integer — only integer number allowed (no NaN, no Infinity, no float)
|
|
221
|
+
if (expectedType === "integer") {
|
|
76
222
|
if (typeof variableValue !== "number") {
|
|
77
223
|
ctx.addIssue({
|
|
78
224
|
code: z.ZodIssueCode.custom,
|
|
79
|
-
message: `
|
|
225
|
+
message: `Variable "${label}" (type integer) must be a number; got ${gotType}.`,
|
|
226
|
+
path,
|
|
227
|
+
});
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (!Number.isFinite(variableValue)) {
|
|
231
|
+
ctx.addIssue({
|
|
232
|
+
code: z.ZodIssueCode.custom,
|
|
233
|
+
message: `Variable "${label}" (type integer) must be a finite number; got ${variableValue}.`,
|
|
234
|
+
path,
|
|
235
|
+
});
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (!Number.isInteger(variableValue)) {
|
|
239
|
+
ctx.addIssue({
|
|
240
|
+
code: z.ZodIssueCode.custom,
|
|
241
|
+
message: `Variable "${label}" (type integer) must be an integer; got ${variableValue}.`,
|
|
80
242
|
path,
|
|
81
243
|
});
|
|
82
244
|
}
|
|
83
|
-
|
|
84
245
|
return;
|
|
85
246
|
}
|
|
86
247
|
|
|
87
|
-
//
|
|
88
|
-
if (
|
|
89
|
-
if (typeof variableValue !== "
|
|
248
|
+
// double — only finite number allowed
|
|
249
|
+
if (expectedType === "double") {
|
|
250
|
+
if (typeof variableValue !== "number") {
|
|
90
251
|
ctx.addIssue({
|
|
91
252
|
code: z.ZodIssueCode.custom,
|
|
92
|
-
message: `
|
|
253
|
+
message: `Variable "${label}" (type double) must be a number; got ${gotType}.`,
|
|
254
|
+
path,
|
|
255
|
+
});
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!Number.isFinite(variableValue)) {
|
|
259
|
+
ctx.addIssue({
|
|
260
|
+
code: z.ZodIssueCode.custom,
|
|
261
|
+
message: `Variable "${label}" (type double) must be a finite number; got ${variableValue}.`,
|
|
93
262
|
path,
|
|
94
263
|
});
|
|
95
264
|
}
|
|
96
|
-
|
|
97
265
|
return;
|
|
98
266
|
}
|
|
99
267
|
|
|
100
|
-
//
|
|
101
|
-
if (
|
|
102
|
-
if (
|
|
268
|
+
// boolean — only boolean allowed
|
|
269
|
+
if (expectedType === "boolean") {
|
|
270
|
+
if (typeof variableValue !== "boolean") {
|
|
103
271
|
ctx.addIssue({
|
|
104
272
|
code: z.ZodIssueCode.custom,
|
|
105
|
-
message: `
|
|
273
|
+
message: `Variable "${label}" (type boolean) must be a boolean; got ${gotType}.`,
|
|
106
274
|
path,
|
|
107
275
|
});
|
|
108
276
|
}
|
|
109
|
-
|
|
110
|
-
if (projectConfig.maxVariableArrayStringifiedLength) {
|
|
111
|
-
const stringified = JSON.stringify(variableValue);
|
|
112
|
-
|
|
113
|
-
if (stringified.length > projectConfig.maxVariableArrayStringifiedLength) {
|
|
114
|
-
ctx.addIssue({
|
|
115
|
-
code: z.ZodIssueCode.custom,
|
|
116
|
-
message: `Variable "${variableSchema.key}" array is too long (${stringified.length} characters), max length is ${projectConfig.maxVariableArrayStringifiedLength}`,
|
|
117
|
-
path,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
277
|
return;
|
|
123
278
|
}
|
|
124
279
|
|
|
125
|
-
//
|
|
126
|
-
if (
|
|
127
|
-
if (
|
|
280
|
+
// array — only array allowed; without items schema = array of strings
|
|
281
|
+
if (expectedType === "array") {
|
|
282
|
+
if (!Array.isArray(variableValue)) {
|
|
128
283
|
ctx.addIssue({
|
|
129
284
|
code: z.ZodIssueCode.custom,
|
|
130
|
-
message: `
|
|
285
|
+
message: `Variable "${label}" (type array) must be an array; got ${gotType}.`,
|
|
131
286
|
path,
|
|
132
287
|
});
|
|
288
|
+
return;
|
|
133
289
|
}
|
|
290
|
+
refineVariableValueArray(projectConfig, variableSchema, variableValue, path, ctx, variableKey);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
134
293
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
294
|
+
// object — only plain object allowed (no null, no array)
|
|
295
|
+
if (expectedType === "object") {
|
|
296
|
+
if (
|
|
297
|
+
typeof variableValue !== "object" ||
|
|
298
|
+
variableValue === null ||
|
|
299
|
+
Array.isArray(variableValue)
|
|
300
|
+
) {
|
|
301
|
+
ctx.addIssue({
|
|
302
|
+
code: z.ZodIssueCode.custom,
|
|
303
|
+
message: `Variable "${label}" (type object) must be a plain object; got ${gotType}.`,
|
|
304
|
+
path,
|
|
305
|
+
});
|
|
306
|
+
return;
|
|
145
307
|
}
|
|
146
|
-
|
|
308
|
+
refineVariableValueObject(
|
|
309
|
+
projectConfig,
|
|
310
|
+
variableSchema,
|
|
311
|
+
variableValue as Record<string, unknown>,
|
|
312
|
+
path,
|
|
313
|
+
ctx,
|
|
314
|
+
variableKey,
|
|
315
|
+
);
|
|
147
316
|
return;
|
|
148
317
|
}
|
|
149
318
|
|
|
150
|
-
// json
|
|
151
|
-
if (
|
|
319
|
+
// json — only string containing valid JSON allowed
|
|
320
|
+
if (expectedType === "json") {
|
|
321
|
+
if (typeof variableValue !== "string") {
|
|
322
|
+
ctx.addIssue({
|
|
323
|
+
code: z.ZodIssueCode.custom,
|
|
324
|
+
message: `Variable "${label}" (type json) must be a string (JSON string); got ${gotType}.`,
|
|
325
|
+
path,
|
|
326
|
+
});
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
152
329
|
try {
|
|
153
|
-
JSON.parse(variableValue
|
|
330
|
+
JSON.parse(variableValue);
|
|
154
331
|
|
|
155
332
|
if (projectConfig.maxVariableJSONStringifiedLength) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (stringified.length > projectConfig.maxVariableJSONStringifiedLength) {
|
|
333
|
+
if (variableValue.length > projectConfig.maxVariableJSONStringifiedLength) {
|
|
159
334
|
ctx.addIssue({
|
|
160
335
|
code: z.ZodIssueCode.custom,
|
|
161
|
-
message: `Variable "${
|
|
336
|
+
message: `Variable "${label}" JSON is too long (${variableValue.length} characters), max length is ${projectConfig.maxVariableJSONStringifiedLength}`,
|
|
162
337
|
path,
|
|
163
338
|
});
|
|
164
339
|
}
|
|
@@ -167,13 +342,20 @@ function superRefineVariableValue(
|
|
|
167
342
|
} catch (e) {
|
|
168
343
|
ctx.addIssue({
|
|
169
344
|
code: z.ZodIssueCode.custom,
|
|
170
|
-
message: `
|
|
345
|
+
message: `Variable "${label}" (type json) must be a valid JSON string; parse failed.`,
|
|
171
346
|
path,
|
|
172
347
|
});
|
|
173
348
|
}
|
|
174
349
|
|
|
175
350
|
return;
|
|
176
351
|
}
|
|
352
|
+
|
|
353
|
+
// Unknown variable type — schema is invalid or unsupported
|
|
354
|
+
ctx.addIssue({
|
|
355
|
+
code: z.ZodIssueCode.custom,
|
|
356
|
+
message: `Variable "${label}" has unknown or unsupported type "${String(expectedType)}" in variablesSchema.`,
|
|
357
|
+
path,
|
|
358
|
+
});
|
|
177
359
|
}
|
|
178
360
|
|
|
179
361
|
function refineForce({
|
|
@@ -206,6 +388,7 @@ function refineForce({
|
|
|
206
388
|
f.variables[variableKey],
|
|
207
389
|
pathPrefix.concat([fN, "variables", variableKey]),
|
|
208
390
|
ctx,
|
|
391
|
+
variableKey,
|
|
209
392
|
);
|
|
210
393
|
});
|
|
211
394
|
}
|
|
@@ -231,6 +414,7 @@ function refineRules({
|
|
|
231
414
|
rule.variables[variableKey],
|
|
232
415
|
pathPrefix.concat([ruleN, "variables", variableKey]),
|
|
233
416
|
ctx,
|
|
417
|
+
variableKey,
|
|
234
418
|
);
|
|
235
419
|
});
|
|
236
420
|
}
|
|
@@ -308,21 +492,10 @@ export function getFeatureZodSchema(
|
|
|
308
492
|
availableSegmentKeys: [string, ...string[]],
|
|
309
493
|
availableFeatureKeys: [string, ...string[]],
|
|
310
494
|
) {
|
|
495
|
+
const propertyZodSchema = getPropertyZodSchema();
|
|
496
|
+
const variableValueZodSchema = valueZodSchema;
|
|
497
|
+
|
|
311
498
|
const variationValueZodSchema = z.string().min(1);
|
|
312
|
-
const variableValueZodSchema = z.union([
|
|
313
|
-
z.string(),
|
|
314
|
-
z.number(),
|
|
315
|
-
z.boolean(),
|
|
316
|
-
z.array(z.string()),
|
|
317
|
-
z.record(z.unknown()).refine(
|
|
318
|
-
(value) => {
|
|
319
|
-
return isFlatObject(value);
|
|
320
|
-
},
|
|
321
|
-
{
|
|
322
|
-
message: "object is not flat",
|
|
323
|
-
},
|
|
324
|
-
),
|
|
325
|
-
]);
|
|
326
499
|
|
|
327
500
|
const plainGroupSegment = z.string().refine(
|
|
328
501
|
(value) => value === "*" || availableSegmentKeys.includes(value),
|
|
@@ -500,11 +673,19 @@ export function getFeatureZodSchema(
|
|
|
500
673
|
z
|
|
501
674
|
.object({
|
|
502
675
|
deprecated: z.boolean().optional(),
|
|
503
|
-
|
|
676
|
+
|
|
677
|
+
type: z.union([z.literal("json"), propertyTypeEnum]),
|
|
678
|
+
// array: when omitted, treated as array of strings
|
|
679
|
+
items: propertyZodSchema.optional(),
|
|
680
|
+
// object: when omitted, treated as flat object (primitive values only)
|
|
681
|
+
properties: z.record(propertyZodSchema).optional(),
|
|
682
|
+
|
|
504
683
|
description: z.string().optional(),
|
|
684
|
+
|
|
505
685
|
defaultValue: variableValueZodSchema,
|
|
506
|
-
useDefaultWhenDisabled: z.boolean().optional(),
|
|
507
686
|
disabledValue: variableValueZodSchema.optional(),
|
|
687
|
+
|
|
688
|
+
useDefaultWhenDisabled: z.boolean().optional(),
|
|
508
689
|
})
|
|
509
690
|
.strict(),
|
|
510
691
|
)
|
|
@@ -625,16 +806,20 @@ export function getFeatureZodSchema(
|
|
|
625
806
|
variableSchema.defaultValue,
|
|
626
807
|
["variablesSchema", variableKey, "defaultValue"],
|
|
627
808
|
ctx,
|
|
809
|
+
variableKey,
|
|
628
810
|
);
|
|
629
811
|
|
|
630
|
-
// disabledValue
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
812
|
+
// disabledValue (only when present)
|
|
813
|
+
if (variableSchema.disabledValue !== undefined) {
|
|
814
|
+
superRefineVariableValue(
|
|
815
|
+
projectConfig,
|
|
816
|
+
variableSchema,
|
|
817
|
+
variableSchema.disabledValue,
|
|
818
|
+
["variablesSchema", variableKey, "disabledValue"],
|
|
819
|
+
ctx,
|
|
820
|
+
variableKey,
|
|
821
|
+
);
|
|
822
|
+
}
|
|
638
823
|
});
|
|
639
824
|
|
|
640
825
|
// variations
|
|
@@ -654,6 +839,7 @@ export function getFeatureZodSchema(
|
|
|
654
839
|
variableValue,
|
|
655
840
|
["variations", variationN, "variables", variableKey],
|
|
656
841
|
ctx,
|
|
842
|
+
variableKey,
|
|
657
843
|
);
|
|
658
844
|
|
|
659
845
|
// variations[n].variableOverrides[n].value
|
|
@@ -676,6 +862,7 @@ export function getFeatureZodSchema(
|
|
|
676
862
|
"value",
|
|
677
863
|
],
|
|
678
864
|
ctx,
|
|
865
|
+
variableKey,
|
|
679
866
|
);
|
|
680
867
|
});
|
|
681
868
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { PropertySchema, Value } from "@featurevisor/types";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
// Recursive schema for Value: boolean | string | number | ObjectValue | Value[]
|
|
5
|
+
export const valueZodSchema: z.ZodType<Value> = z.lazy(() =>
|
|
6
|
+
z.union([
|
|
7
|
+
z.boolean(),
|
|
8
|
+
z.string(),
|
|
9
|
+
z.number(),
|
|
10
|
+
// | Date // @TODO: support in future
|
|
11
|
+
z.record(z.string(), valueZodSchema),
|
|
12
|
+
z.array(valueZodSchema),
|
|
13
|
+
]),
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
// @TODO: support "date" in future
|
|
17
|
+
// @TODO: consider "semver" in future
|
|
18
|
+
// @TODO: consider "url" in future
|
|
19
|
+
export const propertyTypeEnum = z.enum([
|
|
20
|
+
"boolean",
|
|
21
|
+
"string",
|
|
22
|
+
"integer",
|
|
23
|
+
"double",
|
|
24
|
+
"object",
|
|
25
|
+
"array",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
export function getPropertyZodSchema() {
|
|
29
|
+
const propertyZodSchema: z.ZodType<PropertySchema> = z.lazy(() =>
|
|
30
|
+
z
|
|
31
|
+
.object({
|
|
32
|
+
description: z.string().optional(),
|
|
33
|
+
type: propertyTypeEnum.optional(),
|
|
34
|
+
// enum?: Value[]; const?: Value;
|
|
35
|
+
// Numeric: maximum?, minimum?
|
|
36
|
+
// String: maxLength?, minLength?, pattern?
|
|
37
|
+
items: propertyZodSchema.optional(),
|
|
38
|
+
// maxItems?, minItems?, uniqueItems?
|
|
39
|
+
required: z.array(z.string()).optional(),
|
|
40
|
+
properties: z.record(z.string(), propertyZodSchema).optional(),
|
|
41
|
+
// Annotations: default?: Value; examples?: Value[];
|
|
42
|
+
})
|
|
43
|
+
.strict(),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return propertyZodSchema;
|
|
47
|
+
}
|