@featurevisor/core 1.26.0 → 1.27.1

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 (102) hide show
  1. package/.eslintcache +1 -1
  2. package/CHANGELOG.md +22 -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/revision.js.html +1 -1
  8. package/coverage/lcov-report/lib/builder/traffic.js.html +1 -1
  9. package/coverage/lcov-report/lib/tester/checkIfObjectsAreEqual.js.html +1 -1
  10. package/coverage/lcov-report/lib/tester/index.html +1 -1
  11. package/coverage/lcov-report/lib/tester/matrix.js.html +1 -1
  12. package/coverage/lcov-report/src/builder/allocator.ts.html +1 -1
  13. package/coverage/lcov-report/src/builder/index.html +1 -1
  14. package/coverage/lcov-report/src/builder/revision.ts.html +1 -1
  15. package/coverage/lcov-report/src/builder/traffic.ts.html +1 -1
  16. package/coverage/lcov-report/src/tester/checkIfObjectsAreEqual.ts.html +1 -1
  17. package/coverage/lcov-report/src/tester/index.html +1 -1
  18. package/coverage/lcov-report/src/tester/matrix.ts.html +1 -1
  19. package/lib/assess-distribution/index.d.ts +2 -0
  20. package/lib/assess-distribution/index.js +37 -1
  21. package/lib/assess-distribution/index.js.map +1 -1
  22. package/lib/benchmark/index.d.ts +2 -0
  23. package/lib/benchmark/index.js +43 -1
  24. package/lib/benchmark/index.js.map +1 -1
  25. package/lib/builder/buildProject.d.ts +2 -0
  26. package/lib/builder/buildProject.js +48 -1
  27. package/lib/builder/buildProject.js.map +1 -1
  28. package/lib/cli/cli.d.ts +26 -0
  29. package/lib/cli/cli.js +133 -0
  30. package/lib/cli/cli.js.map +1 -0
  31. package/lib/cli/index.d.ts +2 -0
  32. package/lib/cli/index.js +19 -0
  33. package/lib/cli/index.js.map +1 -0
  34. package/lib/cli/plugins.d.ts +4 -0
  35. package/lib/cli/plugins.js +37 -0
  36. package/lib/cli/plugins.js.map +1 -0
  37. package/lib/config/projectConfig.d.ts +3 -0
  38. package/lib/config/projectConfig.js +69 -1
  39. package/lib/config/projectConfig.js.map +1 -1
  40. package/lib/evaluate/index.d.ts +2 -0
  41. package/lib/evaluate/index.js +35 -1
  42. package/lib/evaluate/index.js.map +1 -1
  43. package/lib/find-duplicate-segments/index.d.ts +2 -0
  44. package/lib/find-duplicate-segments/index.js +34 -1
  45. package/lib/find-duplicate-segments/index.js.map +1 -1
  46. package/lib/find-usage/index.d.ts +2 -0
  47. package/lib/find-usage/index.js +50 -1
  48. package/lib/find-usage/index.js.map +1 -1
  49. package/lib/generate-code/index.d.ts +2 -0
  50. package/lib/generate-code/index.js +31 -1
  51. package/lib/generate-code/index.js.map +1 -1
  52. package/lib/index.d.ts +1 -0
  53. package/lib/index.js +1 -0
  54. package/lib/index.js.map +1 -1
  55. package/lib/info/index.d.ts +2 -0
  56. package/lib/info/index.js +28 -1
  57. package/lib/info/index.js.map +1 -1
  58. package/lib/init/index.d.ts +2 -0
  59. package/lib/init/index.js +65 -1
  60. package/lib/init/index.js.map +1 -1
  61. package/lib/linter/featureSchema.d.ts +71 -1
  62. package/lib/linter/featureSchema.js +147 -8
  63. package/lib/linter/featureSchema.js.map +1 -1
  64. package/lib/linter/lintProject.d.ts +3 -0
  65. package/lib/linter/lintProject.js +344 -163
  66. package/lib/linter/lintProject.js.map +1 -1
  67. package/lib/linter/printError.js +1 -3
  68. package/lib/linter/printError.js.map +1 -1
  69. package/lib/restore/index.d.ts +2 -0
  70. package/lib/restore/index.js +28 -1
  71. package/lib/restore/index.js.map +1 -1
  72. package/lib/site/index.d.ts +2 -2
  73. package/lib/site/index.js +86 -14
  74. package/lib/site/index.js.map +1 -1
  75. package/lib/tester/cliFormat.d.ts +1 -0
  76. package/lib/tester/cliFormat.js +2 -1
  77. package/lib/tester/cliFormat.js.map +1 -1
  78. package/lib/tester/testProject.d.ts +2 -0
  79. package/lib/tester/testProject.js +52 -1
  80. package/lib/tester/testProject.js.map +1 -1
  81. package/package.json +3 -2
  82. package/src/assess-distribution/index.ts +32 -0
  83. package/src/benchmark/index.ts +40 -0
  84. package/src/builder/buildProject.ts +42 -0
  85. package/src/cli/cli.ts +105 -0
  86. package/src/cli/index.ts +2 -0
  87. package/src/cli/plugins.ts +38 -0
  88. package/src/config/projectConfig.ts +28 -0
  89. package/src/evaluate/index.ts +30 -0
  90. package/src/find-duplicate-segments/index.ts +28 -0
  91. package/src/find-usage/index.ts +44 -0
  92. package/src/generate-code/index.ts +25 -0
  93. package/src/index.ts +1 -0
  94. package/src/info/index.ts +19 -0
  95. package/src/init/index.ts +21 -0
  96. package/src/linter/featureSchema.ts +194 -10
  97. package/src/linter/lintProject.ts +170 -18
  98. package/src/linter/printError.ts +1 -3
  99. package/src/restore/index.ts +19 -0
  100. package/src/site/index.ts +46 -2
  101. package/src/tester/cliFormat.ts +1 -0
  102. package/src/tester/testProject.ts +46 -0
@@ -0,0 +1,2 @@
1
+ export * from "./cli";
2
+ export * from "./plugins";
@@ -0,0 +1,38 @@
1
+ import type { Plugin } from "./cli";
2
+
3
+ import { initPlugin } from "../init";
4
+ import { lintPlugin } from "../linter";
5
+ import { buildPlugin } from "../builder";
6
+ import { restorePlugin } from "../restore";
7
+ import { testPlugin } from "../tester";
8
+ import { generateCodePlugin } from "../generate-code";
9
+ import { findDuplicateSegmentsPlugin } from "../find-duplicate-segments";
10
+ import { findUsagePlugin } from "../find-usage";
11
+ import { benchmarkPlugin } from "../benchmark";
12
+ import { configPlugin } from "../config";
13
+ import { evaluatePlugin } from "../evaluate";
14
+ import { assessDistributionPlugin } from "../assess-distribution";
15
+ import { infoPlugin } from "../info";
16
+ import { sitePlugin } from "../site";
17
+
18
+ // that do not require an existing project
19
+ export const nonProjectPlugins: Plugin[] = [initPlugin];
20
+
21
+ // that require an existing Featurevisor project
22
+ export const projectBasedPlugins: Plugin[] = [
23
+ lintPlugin,
24
+ buildPlugin,
25
+ restorePlugin,
26
+ testPlugin,
27
+ generateCodePlugin,
28
+ findDuplicateSegmentsPlugin,
29
+ findUsagePlugin,
30
+ benchmarkPlugin,
31
+ configPlugin,
32
+ evaluatePlugin,
33
+ assessDistributionPlugin,
34
+ infoPlugin,
35
+ sitePlugin,
36
+ ];
37
+
38
+ export const commonPlugins: Plugin[] = [];
@@ -4,6 +4,7 @@ import { BucketBy } from "@featurevisor/types";
4
4
 
5
5
  import { Parser, parsers } from "./parsers";
6
6
  import { FilesystemAdapter } from "../datasource/filesystemAdapter";
7
+ import type { Plugin } from "../cli";
7
8
 
8
9
  export const FEATURES_DIRECTORY_NAME = "features";
9
10
  export const SEGMENTS_DIRECTORY_NAME = "segments";
@@ -46,6 +47,7 @@ export interface ProjectConfig {
46
47
  siteExportDirectoryPath: string;
47
48
  enforceCatchAllRule?: boolean;
48
49
  adapter: any; // @TODO: type this properly later
50
+ plugins: Plugin[];
49
51
  }
50
52
 
51
53
  // rootDirectoryPath: path to the root directory of the project (without ending with a slash)
@@ -73,6 +75,7 @@ export function getProjectConfig(rootDirectoryPath: string): ProjectConfig {
73
75
  siteExportDirectoryPath: path.join(rootDirectoryPath, SITE_EXPORT_DIRECTORY_NAME),
74
76
 
75
77
  enforceCatchAllRule: false,
78
+ plugins: [],
76
79
  };
77
80
 
78
81
  const configModulePath = path.join(rootDirectoryPath, CONFIG_MODULE_NAME);
@@ -134,3 +137,28 @@ export function showProjectConfig(
134
137
  console.log(` - ${key.padEnd(longestKeyLength, " ")}: ${projectConfig[key]}`);
135
138
  }
136
139
  }
140
+
141
+ export const configPlugin: Plugin = {
142
+ command: "config",
143
+ handler: async ({ rootDirectoryPath, parsed }) => {
144
+ const projectConfig = getProjectConfig(rootDirectoryPath);
145
+ showProjectConfig(projectConfig, {
146
+ print: parsed.print,
147
+ pretty: parsed.pretty,
148
+ });
149
+ },
150
+ examples: [
151
+ {
152
+ command: "config",
153
+ description: "show the project configuration",
154
+ },
155
+ {
156
+ command: "config --print",
157
+ description: "show the project configuration as JSON",
158
+ },
159
+ {
160
+ command: "config --print --pretty",
161
+ description: "show the project configuration (as pretty JSON)",
162
+ },
163
+ ],
164
+ };
@@ -11,6 +11,7 @@ import {
11
11
  import { Dependencies } from "../dependencies";
12
12
  import { SCHEMA_VERSION } from "../config";
13
13
  import { buildDatafile } from "../builder";
14
+ import { Plugin } from "../cli";
14
15
 
15
16
  function printEvaluationDetails(evaluation: Evaluation) {
16
17
  const ignoreKeys = ["featureKey", "variableKey", "traffic", "force"];
@@ -191,3 +192,32 @@ export async function evaluateFeature(deps: Dependencies, options: EvaluateOptio
191
192
  console.log("No variables defined.");
192
193
  }
193
194
  }
195
+
196
+ export const evaluatePlugin: Plugin = {
197
+ command: "evaluate",
198
+ handler: async ({ rootDirectoryPath, projectConfig, datasource, parsed }) => {
199
+ await evaluateFeature(
200
+ {
201
+ rootDirectoryPath,
202
+ projectConfig,
203
+ datasource,
204
+ options: parsed,
205
+ },
206
+ {
207
+ environment: parsed.environment,
208
+ feature: parsed.feature,
209
+ context: parsed.context ? JSON.parse(parsed.context) : {},
210
+ print: parsed.print,
211
+ pretty: parsed.pretty,
212
+ verbose: parsed.verbose,
213
+ },
214
+ );
215
+ },
216
+ examples: [
217
+ {
218
+ command:
219
+ 'evaluate --environment=production --feature=my_feature --context=\'{"userId": "123"}\'',
220
+ description: "evaluate a feature against provided context",
221
+ },
222
+ ],
223
+ };
@@ -1,5 +1,6 @@
1
1
  import { findDuplicateSegments, DuplicateSegmentsOptions } from "./findDuplicateSegments";
2
2
  import { Dependencies } from "../dependencies";
3
+ import { Plugin } from "../cli";
3
4
 
4
5
  export async function findDuplicateSegmentsInProject(
5
6
  deps: Dependencies,
@@ -26,3 +27,30 @@ export async function findDuplicateSegmentsInProject(
26
27
  }
27
28
  });
28
29
  }
30
+
31
+ export const findDuplicateSegmentsPlugin: Plugin = {
32
+ command: "find-duplicate-segments",
33
+ handler: async ({ rootDirectoryPath, projectConfig, datasource, parsed }) => {
34
+ await findDuplicateSegmentsInProject(
35
+ {
36
+ rootDirectoryPath,
37
+ projectConfig,
38
+ datasource,
39
+ options: parsed,
40
+ },
41
+ {
42
+ authors: parsed.authors,
43
+ },
44
+ );
45
+ },
46
+ examples: [
47
+ {
48
+ command: "find-duplicate-segments",
49
+ description: "Find duplicate segments in the project",
50
+ },
51
+ {
52
+ command: "find-duplicate-segments --authors",
53
+ description: "Find duplicate segments in the project and list authors",
54
+ },
55
+ ],
56
+ };
@@ -1,6 +1,7 @@
1
1
  import { Condition, FeatureKey, SegmentKey, AttributeKey } from "@featurevisor/types";
2
2
 
3
3
  import { Dependencies } from "../dependencies";
4
+ import { Plugin } from "../cli";
4
5
  import {
5
6
  extractAttributeKeysFromConditions,
6
7
  extractSegmentKeysFromGroupSegments,
@@ -371,3 +372,46 @@ export async function findUsageInProject(deps: Dependencies, options: FindUsageO
371
372
 
372
373
  console.log("Please specify a segment or attribute.");
373
374
  }
375
+
376
+ export const findUsagePlugin: Plugin = {
377
+ command: "find-usage",
378
+ handler: async ({ rootDirectoryPath, projectConfig, datasource, parsed }) => {
379
+ await findUsageInProject(
380
+ {
381
+ rootDirectoryPath,
382
+ projectConfig,
383
+ datasource,
384
+ options: parsed,
385
+ },
386
+ {
387
+ segment: parsed.segment,
388
+ attribute: parsed.attribute,
389
+ unusedSegments: parsed.unusedSegments,
390
+ unusedAttributes: parsed.unusedAttributes,
391
+ authors: parsed.authors,
392
+ },
393
+ );
394
+ },
395
+ examples: [
396
+ {
397
+ command: "find-usage --segment=<segmentKey>",
398
+ description: "Find usage of a segment",
399
+ },
400
+ {
401
+ command: "find-usage --attribute=<attributeKey>",
402
+ description: "Find usage of an attribute",
403
+ },
404
+ {
405
+ command: "find-usage --unused-segments",
406
+ description: "Find unused segments",
407
+ },
408
+ {
409
+ command: "find-usage --unused-attributes",
410
+ description: "Find unused attributes",
411
+ },
412
+ {
413
+ command: "find-usage --authors",
414
+ description: "List authors of the usage",
415
+ },
416
+ ],
417
+ };
@@ -5,6 +5,7 @@ import * as mkdirp from "mkdirp";
5
5
 
6
6
  import { generateTypeScriptCodeForProject } from "./typescript";
7
7
  import { Dependencies } from "../dependencies";
8
+ import { Plugin } from "../cli";
8
9
 
9
10
  export const ALLOWED_LANGUAGES_FOR_CODE_GENERATION = ["typescript"];
10
11
 
@@ -50,3 +51,27 @@ export async function generateCodeForProject(
50
51
 
51
52
  throw new Error(`Language ${cliOptions.language} is not supported`);
52
53
  }
54
+
55
+ export const generateCodePlugin: Plugin = {
56
+ command: "generate-code",
57
+ handler: async function ({ rootDirectoryPath, projectConfig, datasource, parsed }) {
58
+ await generateCodeForProject(
59
+ {
60
+ rootDirectoryPath,
61
+ projectConfig,
62
+ datasource,
63
+ options: parsed,
64
+ },
65
+ {
66
+ language: parsed.language,
67
+ outDir: parsed.outDir,
68
+ },
69
+ );
70
+ },
71
+ examples: [
72
+ {
73
+ command: "generate-code --language typescript --out-dir src/generated",
74
+ description: "Generate TypeScript code for the project",
75
+ },
76
+ ],
77
+ };
package/src/index.ts CHANGED
@@ -14,3 +14,4 @@ export * from "./benchmark";
14
14
  export * from "./evaluate";
15
15
  export * from "./assess-distribution";
16
16
  export * from "./info";
17
+ export * from "./cli";
package/src/info/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Dependencies } from "../dependencies";
2
2
  import { getMatrixCombinations } from "../tester/matrix";
3
+ import { Plugin } from "../cli";
3
4
 
4
5
  export async function showProjectInfo(deps: Dependencies) {
5
6
  const { datasource } = deps;
@@ -52,3 +53,21 @@ export async function showProjectInfo(deps: Dependencies) {
52
53
 
53
54
  console.log(" - Total assertions: ", assertionsCount);
54
55
  }
56
+
57
+ export const infoPlugin: Plugin = {
58
+ command: "info",
59
+ handler: async function ({ rootDirectoryPath, projectConfig, datasource, parsed }) {
60
+ await showProjectInfo({
61
+ rootDirectoryPath,
62
+ projectConfig,
63
+ datasource,
64
+ options: parsed,
65
+ });
66
+ },
67
+ examples: [
68
+ {
69
+ command: "info",
70
+ description: "show various stats for the project",
71
+ },
72
+ ],
73
+ };
package/src/init/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import axios from "axios";
2
2
  import * as tar from "tar";
3
3
 
4
+ import { Plugin } from "../cli";
5
+
4
6
  export const DEFAULT_EXAMPLE = "example-yml";
5
7
 
6
8
  export const EXAMPLES_ORG_NAME = "fahad19";
@@ -42,3 +44,22 @@ export function initProject(
42
44
  });
43
45
  });
44
46
  }
47
+
48
+ export const initPlugin: Plugin = {
49
+ command: "init",
50
+ handler: async function (options) {
51
+ const { rootDirectoryPath, parsed } = options;
52
+
53
+ await initProject(rootDirectoryPath, parsed.example);
54
+ },
55
+ examples: [
56
+ {
57
+ command: "init",
58
+ description: "scaffold a new project in current directory",
59
+ },
60
+ {
61
+ command: "init --example=exampleName",
62
+ description: "scaffold a new project in current directory from known example",
63
+ },
64
+ ],
65
+ };
@@ -4,6 +4,124 @@ import { ProjectConfig } from "../config";
4
4
 
5
5
  const tagRegex = /^[a-z0-9-]+$/;
6
6
 
7
+ function isFlatObject(value) {
8
+ let isFlat = true;
9
+
10
+ Object.keys(value).forEach((key) => {
11
+ if (typeof value[key] === "object") {
12
+ isFlat = false;
13
+ }
14
+ });
15
+
16
+ return isFlat;
17
+ }
18
+
19
+ function isArrayOfStrings(value) {
20
+ return Array.isArray(value) && value.every((v) => typeof v === "string");
21
+ }
22
+
23
+ function superRefineVariableValue(variableSchema, variableValue, path, ctx) {
24
+ if (!variableSchema) {
25
+ let message = `Unknown variable with value: ${variableValue}`;
26
+
27
+ if (path.length > 0) {
28
+ const lastPath = path[path.length - 1];
29
+
30
+ if (typeof lastPath === "string") {
31
+ message = `Unknown variable "${lastPath}" with value: ${variableValue}`;
32
+ }
33
+ }
34
+
35
+ ctx.addIssue({
36
+ code: z.ZodIssueCode.custom,
37
+ message,
38
+ path,
39
+ });
40
+
41
+ return;
42
+ }
43
+
44
+ // string
45
+ if (variableSchema.type === "string") {
46
+ if (typeof variableValue !== "string") {
47
+ ctx.addIssue({
48
+ code: z.ZodIssueCode.custom,
49
+ message: `Invalid value for variable "${variableSchema.key}" (${variableSchema.type}): ${variableValue}`,
50
+ path,
51
+ });
52
+ }
53
+
54
+ return;
55
+ }
56
+
57
+ // integer, double
58
+ if (["integer", "double"].indexOf(variableSchema.type) > -1) {
59
+ if (typeof variableValue !== "number") {
60
+ ctx.addIssue({
61
+ code: z.ZodIssueCode.custom,
62
+ message: `Invalid value for variable "${variableSchema.key}" (${variableSchema.type}): ${variableValue}`,
63
+ path,
64
+ });
65
+ }
66
+
67
+ return;
68
+ }
69
+
70
+ // boolean
71
+ if (variableSchema.type === "boolean") {
72
+ if (typeof variableValue !== "boolean") {
73
+ ctx.addIssue({
74
+ code: z.ZodIssueCode.custom,
75
+ message: `Invalid value for variable "${variableSchema.key}" (${variableSchema.type}): ${variableValue}`,
76
+ path,
77
+ });
78
+ }
79
+
80
+ return;
81
+ }
82
+
83
+ // array
84
+ if (variableSchema.type === "array") {
85
+ if (!Array.isArray(variableValue) || !isArrayOfStrings(variableValue)) {
86
+ ctx.addIssue({
87
+ code: z.ZodIssueCode.custom,
88
+ message: `Invalid value for variable "${variableSchema.key}" (${variableSchema.type}): \n\n${variableValue}\n\n`,
89
+ path,
90
+ });
91
+ }
92
+
93
+ return;
94
+ }
95
+
96
+ // object
97
+ if (variableSchema.type === "object") {
98
+ if (typeof variableValue !== "object" || !isFlatObject(variableValue)) {
99
+ ctx.addIssue({
100
+ code: z.ZodIssueCode.custom,
101
+ message: `Invalid value for variable "${variableSchema.key}" (${variableSchema.type}): \n\n${variableValue}\n\n`,
102
+ path,
103
+ });
104
+ }
105
+
106
+ return;
107
+ }
108
+
109
+ // json
110
+ if (variableSchema.type === "json") {
111
+ try {
112
+ JSON.parse(variableValue as string);
113
+ } catch (e) {
114
+ ctx.addIssue({
115
+ code: z.ZodIssueCode.custom,
116
+ message: `Invalid value for variable "${variableSchema.key}" (${variableSchema.type}): \n\n${variableValue}\n\n`,
117
+ path,
118
+ });
119
+ }
120
+
121
+ return;
122
+ }
123
+ }
124
+
7
125
  export function getFeatureZodSchema(
8
126
  projectConfig: ProjectConfig,
9
127
  conditionsZodSchema,
@@ -19,15 +137,7 @@ export function getFeatureZodSchema(
19
137
  z.array(z.string()),
20
138
  z.record(z.unknown()).refine(
21
139
  (value) => {
22
- let isFlat = true;
23
-
24
- Object.keys(value).forEach((key) => {
25
- if (typeof value[key] === "object") {
26
- isFlat = false;
27
- }
28
- });
29
-
30
- return isFlat;
140
+ return isFlatObject(value);
31
141
  },
32
142
  {
33
143
  message: "object is not flat",
@@ -286,7 +396,81 @@ export function getFeatureZodSchema(
286
396
  .optional(),
287
397
  environments: allEnvironmentsZodSchema,
288
398
  })
289
- .strict();
399
+ .strict()
400
+ .superRefine((value, ctx) => {
401
+ if (!value.variablesSchema) {
402
+ return;
403
+ }
404
+
405
+ const allVariablesSchema = value.variablesSchema;
406
+ const variableSchemaByKey = {};
407
+
408
+ // variablesSchema[n].defaultValue
409
+ allVariablesSchema.forEach((variableSchema, n) => {
410
+ variableSchemaByKey[variableSchema.key] = variableSchema;
411
+
412
+ superRefineVariableValue(
413
+ variableSchema,
414
+ variableSchema.defaultValue,
415
+ ["variablesSchema", n, "defaultValue"],
416
+ ctx,
417
+ );
418
+ });
419
+
420
+ // variations[n].variables[n].value
421
+ if (value.variations) {
422
+ value.variations.forEach((variation, variationN) => {
423
+ if (!variation.variables) {
424
+ return;
425
+ }
426
+
427
+ variation.variables.forEach((variable, variableN) => {
428
+ superRefineVariableValue(
429
+ variableSchemaByKey[variable.key],
430
+ variable.value,
431
+ ["variations", variationN, "variables", variableN, "value"],
432
+ ctx,
433
+ );
434
+
435
+ // variations[n].variables[n].overrides[n].value
436
+ if (variable.overrides) {
437
+ variable.overrides.forEach((override, overrideN) => {
438
+ superRefineVariableValue(
439
+ variableSchemaByKey[variable.key],
440
+ override.value,
441
+ [
442
+ "variations",
443
+ variationN,
444
+ "variables",
445
+ variableN,
446
+ "overrides",
447
+ overrideN,
448
+ "value",
449
+ ],
450
+ ctx,
451
+ );
452
+ });
453
+ }
454
+ });
455
+ });
456
+ }
457
+
458
+ // environments[key].rules[n].variables[key]
459
+ Object.keys(value.environments).forEach((environmentKey) => {
460
+ value.environments[environmentKey].rules.forEach((rule, ruleN) => {
461
+ if (rule.variables) {
462
+ Object.keys(rule.variables).forEach((variableKey) => {
463
+ superRefineVariableValue(
464
+ variableSchemaByKey[variableKey],
465
+ rule.variables[variableKey],
466
+ ["environments", environmentKey, "rules", ruleN, "variables", variableKey],
467
+ ctx,
468
+ );
469
+ });
470
+ }
471
+ });
472
+ });
473
+ });
290
474
 
291
475
  return featureZodSchema;
292
476
  }