@featurevisor/core 2.13.0 → 2.14.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 (80) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/coverage/clover.xml +906 -557
  3. package/coverage/coverage-final.json +11 -8
  4. package/coverage/lcov-report/builder/allocator.ts.html +1 -1
  5. package/coverage/lcov-report/builder/buildScopedConditions.ts.html +1 -1
  6. package/coverage/lcov-report/builder/buildScopedDatafile.ts.html +1 -1
  7. package/coverage/lcov-report/builder/buildScopedSegments.ts.html +1 -1
  8. package/coverage/lcov-report/builder/index.html +38 -8
  9. package/coverage/lcov-report/builder/mutateVariables.ts.html +400 -0
  10. package/coverage/lcov-report/builder/mutator.ts.html +796 -0
  11. package/coverage/lcov-report/builder/revision.ts.html +1 -1
  12. package/coverage/lcov-report/builder/traffic.ts.html +1 -1
  13. package/coverage/lcov-report/config/index.html +1 -1
  14. package/coverage/lcov-report/config/projectConfig.ts.html +1 -1
  15. package/coverage/lcov-report/datasource/adapter.ts.html +1 -1
  16. package/coverage/lcov-report/datasource/datasource.ts.html +4 -4
  17. package/coverage/lcov-report/datasource/filesystemAdapter.ts.html +23 -23
  18. package/coverage/lcov-report/datasource/index.html +1 -1
  19. package/coverage/lcov-report/datasource/index.ts.html +1 -1
  20. package/coverage/lcov-report/index.html +28 -28
  21. package/coverage/lcov-report/linter/attributeSchema.ts.html +1 -1
  22. package/coverage/lcov-report/linter/checkCircularDependency.ts.html +1 -1
  23. package/coverage/lcov-report/linter/checkPercentageExceedingSlot.ts.html +1 -1
  24. package/coverage/lcov-report/linter/conditionSchema.ts.html +7 -7
  25. package/coverage/lcov-report/linter/featureSchema.ts.html +789 -318
  26. package/coverage/lcov-report/linter/groupSchema.ts.html +1 -1
  27. package/coverage/lcov-report/linter/index.html +34 -19
  28. package/coverage/lcov-report/linter/lintProject.ts.html +25 -25
  29. package/coverage/lcov-report/linter/mutationNotation.ts.html +1309 -0
  30. package/coverage/lcov-report/linter/printError.ts.html +1 -1
  31. package/coverage/lcov-report/linter/schema.ts.html +69 -69
  32. package/coverage/lcov-report/linter/segmentSchema.ts.html +1 -1
  33. package/coverage/lcov-report/linter/testSchema.ts.html +5 -5
  34. package/coverage/lcov-report/list/index.html +1 -1
  35. package/coverage/lcov-report/list/matrix.ts.html +1 -1
  36. package/coverage/lcov-report/parsers/index.html +1 -1
  37. package/coverage/lcov-report/parsers/index.ts.html +1 -1
  38. package/coverage/lcov-report/parsers/json.ts.html +1 -1
  39. package/coverage/lcov-report/parsers/yml.ts.html +2 -2
  40. package/coverage/lcov-report/tester/cliFormat.ts.html +1 -1
  41. package/coverage/lcov-report/tester/helpers.ts.html +1 -1
  42. package/coverage/lcov-report/tester/index.html +1 -1
  43. package/coverage/lcov-report/utils/git.ts.html +1 -1
  44. package/coverage/lcov-report/utils/index.html +1 -1
  45. package/coverage/lcov.info +1839 -1120
  46. package/lib/builder/buildDatafile.js +8 -3
  47. package/lib/builder/buildDatafile.js.map +1 -1
  48. package/lib/builder/mutateVariables.d.ts +14 -0
  49. package/lib/builder/mutateVariables.js +90 -0
  50. package/lib/builder/mutateVariables.js.map +1 -0
  51. package/lib/builder/mutateVariables.spec.d.ts +1 -0
  52. package/lib/builder/mutateVariables.spec.js +1045 -0
  53. package/lib/builder/mutateVariables.spec.js.map +1 -0
  54. package/lib/builder/mutator.d.ts +20 -0
  55. package/lib/builder/mutator.js +223 -0
  56. package/lib/builder/mutator.js.map +1 -0
  57. package/lib/builder/mutator.spec.d.ts +1 -0
  58. package/lib/builder/mutator.spec.js +368 -0
  59. package/lib/builder/mutator.spec.js.map +1 -0
  60. package/lib/linter/featureSchema.d.ts +7 -7
  61. package/lib/linter/featureSchema.js +138 -62
  62. package/lib/linter/featureSchema.js.map +1 -1
  63. package/lib/linter/featureSchema.spec.js +150 -0
  64. package/lib/linter/featureSchema.spec.js.map +1 -1
  65. package/lib/linter/mutationNotation.d.ts +47 -0
  66. package/lib/linter/mutationNotation.js +381 -0
  67. package/lib/linter/mutationNotation.js.map +1 -0
  68. package/lib/linter/mutationNotation.spec.d.ts +1 -0
  69. package/lib/linter/mutationNotation.spec.js +549 -0
  70. package/lib/linter/mutationNotation.spec.js.map +1 -0
  71. package/package.json +2 -2
  72. package/src/builder/buildDatafile.ts +29 -3
  73. package/src/builder/mutateVariables.spec.ts +1134 -0
  74. package/src/builder/mutateVariables.ts +105 -0
  75. package/src/builder/mutator.spec.ts +413 -0
  76. package/src/builder/mutator.ts +237 -0
  77. package/src/linter/featureSchema.spec.ts +182 -0
  78. package/src/linter/featureSchema.ts +254 -97
  79. package/src/linter/mutationNotation.spec.ts +642 -0
  80. package/src/linter/mutationNotation.ts +408 -0
@@ -1,7 +1,13 @@
1
- import type { Schema } from "@featurevisor/types";
1
+ import type { Schema, SchemaType } from "@featurevisor/types";
2
2
  import { z } from "zod";
3
3
 
4
4
  import { ProjectConfig } from "../config";
5
+ import {
6
+ isMutationKey,
7
+ validateMutationKey,
8
+ parsePathMapKey,
9
+ resolveSchemaAtPath,
10
+ } from "./mutationNotation";
5
11
  import {
6
12
  valueZodSchema,
7
13
  propertyTypeEnum,
@@ -250,27 +256,42 @@ function valueDeepEqual(a: unknown, b: unknown): boolean {
250
256
  return false;
251
257
  }
252
258
 
259
+ type SchemaLikeForRequired = {
260
+ type?: string;
261
+ properties?: Record<string, unknown>;
262
+ required?: string[];
263
+ items?: unknown;
264
+ schema?: string;
265
+ oneOf?: unknown[];
266
+ };
267
+
253
268
  /**
254
269
  * Recursively validates that every `required` array (at this level and in nested
255
270
  * object/array schemas) only contains keys that exist in the same level's `properties`.
256
271
  * Adds Zod issues with the correct path for invalid required field names.
272
+ * When schemasByKey is provided, resolves schema references (schema: key) so that
273
+ * referenced and nested schemas are validated too.
257
274
  */
258
275
  function refineRequiredKeysInSchema(
259
- schema: {
260
- type?: string;
261
- properties?: Record<string, unknown>;
262
- required?: string[];
263
- items?: unknown;
264
- },
276
+ schema: SchemaLikeForRequired,
265
277
  pathPrefix: (string | number)[],
266
278
  ctx: z.RefinementCtx,
279
+ schemasByKey?: Record<string, Schema>,
267
280
  ): void {
268
281
  if (!schema || typeof schema !== "object") return;
269
282
 
270
- const effectiveType = schema.type;
271
- const properties = schema.properties;
272
- const required = schema.required;
273
- const items = schema.items;
283
+ // Resolve schema reference so we validate the referenced schema (and can recurse into it)
284
+ let current: SchemaLikeForRequired = schema;
285
+ if (schema.schema && schemasByKey?.[schema.schema]) {
286
+ current = schemasByKey[schema.schema] as SchemaLikeForRequired;
287
+ refineRequiredKeysInSchema(current, pathPrefix, ctx, schemasByKey);
288
+ return;
289
+ }
290
+
291
+ const effectiveType = current.type;
292
+ const properties = current.properties;
293
+ const required = current.required;
294
+ const items = current.items;
274
295
 
275
296
  if (
276
297
  effectiveType === "object" &&
@@ -296,9 +317,10 @@ function refineRequiredKeysInSchema(
296
317
  const nested = properties[key];
297
318
  if (nested && typeof nested === "object") {
298
319
  refineRequiredKeysInSchema(
299
- nested as Parameters<typeof refineRequiredKeysInSchema>[0],
320
+ nested as SchemaLikeForRequired,
300
321
  [...pathPrefix, "properties", key],
301
322
  ctx,
323
+ schemasByKey,
302
324
  );
303
325
  }
304
326
  }
@@ -306,20 +328,22 @@ function refineRequiredKeysInSchema(
306
328
 
307
329
  if (items && typeof items === "object" && !Array.isArray(items)) {
308
330
  refineRequiredKeysInSchema(
309
- items as Parameters<typeof refineRequiredKeysInSchema>[0],
331
+ items as SchemaLikeForRequired,
310
332
  [...pathPrefix, "items"],
311
333
  ctx,
334
+ schemasByKey,
312
335
  );
313
336
  }
314
337
 
315
- const oneOf = (schema as { oneOf?: unknown[] }).oneOf;
338
+ const oneOf = (current as { oneOf?: unknown[] }).oneOf;
316
339
  if (oneOf && Array.isArray(oneOf)) {
317
340
  oneOf.forEach((branch, i) => {
318
341
  if (branch && typeof branch === "object") {
319
342
  refineRequiredKeysInSchema(
320
- branch as Parameters<typeof refineRequiredKeysInSchema>[0],
343
+ branch as SchemaLikeForRequired,
321
344
  [...pathPrefix, "oneOf", i],
322
345
  ctx,
346
+ schemasByKey,
323
347
  );
324
348
  }
325
349
  });
@@ -504,15 +528,21 @@ function refineVariableValueObject(
504
528
  }
505
529
  }
506
530
 
531
+ /** Schema or variable schema (e.g. type "json", defaultValue); used for value validation. */
532
+ type VariableSchemaLike =
533
+ | (Omit<Schema, "type"> & { type?: SchemaType | "json" })
534
+ | null
535
+ | undefined;
536
+
507
537
  function superRefineVariableValue(
508
538
  projectConfig: ProjectConfig,
509
- variableSchema,
510
- variableValue,
511
- path,
512
- ctx,
539
+ variableSchema: VariableSchemaLike,
540
+ variableValue: unknown,
541
+ path: (string | number)[],
542
+ ctx: z.RefinementCtx,
513
543
  variableKey?: string,
514
544
  schemasByKey?: Record<string, Schema>,
515
- ) {
545
+ ): void {
516
546
  const label = getVariableLabel(variableSchema, variableKey, path);
517
547
 
518
548
  if (!variableSchema) {
@@ -854,6 +884,66 @@ function superRefineVariableValue(
854
884
  });
855
885
  }
856
886
 
887
+ /**
888
+ * Validate a single variable entry (key + value) in variables map.
889
+ * Key may be a root variable name or mutation notation (e.g. "config.width", "items[0].name").
890
+ */
891
+ function refineVariableEntry(
892
+ projectConfig: ProjectConfig,
893
+ variableSchemaByKey: Record<string, unknown>,
894
+ variableKey: string,
895
+ variableValue: unknown,
896
+ path: (string | number)[],
897
+ ctx: z.RefinementCtx,
898
+ schemasByKey?: Record<string, Schema>,
899
+ ): void {
900
+ if (isMutationKey(variableKey)) {
901
+ const validation = validateMutationKey(
902
+ variableKey,
903
+ variableSchemaByKey as Record<string, Schema>,
904
+ schemasByKey,
905
+ );
906
+ if (!validation.valid && validation.error) {
907
+ ctx.addIssue({
908
+ code: z.ZodIssueCode.custom,
909
+ message: validation.error,
910
+ path,
911
+ });
912
+ return;
913
+ }
914
+ if (validation.valueSchema) {
915
+ superRefineVariableValue(
916
+ projectConfig,
917
+ validation.valueSchema,
918
+ variableValue,
919
+ path,
920
+ ctx,
921
+ variableKey,
922
+ schemasByKey,
923
+ );
924
+ }
925
+ return;
926
+ }
927
+ const variableSchema = variableSchemaByKey[variableKey];
928
+ if (!variableSchema) {
929
+ ctx.addIssue({
930
+ code: z.ZodIssueCode.custom,
931
+ message: `Variable "${variableKey}" is not defined in \`variablesSchema\`.`,
932
+ path,
933
+ });
934
+ return;
935
+ }
936
+ superRefineVariableValue(
937
+ projectConfig,
938
+ variableSchema as Parameters<typeof superRefineVariableValue>[1],
939
+ variableValue,
940
+ path,
941
+ ctx,
942
+ variableKey,
943
+ schemasByKey,
944
+ );
945
+ }
946
+
857
947
  function refineForce({
858
948
  ctx,
859
949
  parsedFeature, // eslint-disable-line
@@ -879,24 +969,15 @@ function refineForce({
879
969
  // force[n].variables[key]
880
970
  if (f.variables) {
881
971
  Object.keys(f.variables).forEach((variableKey) => {
882
- const variableSchema = variableSchemaByKey[variableKey];
883
- if (!variableSchema) {
884
- ctx.addIssue({
885
- code: z.ZodIssueCode.custom,
886
- message: `Variable "${variableKey}" is not defined in \`variablesSchema\`.`,
887
- path: pathPrefix.concat([fN, "variables", variableKey]),
888
- });
889
- } else {
890
- superRefineVariableValue(
891
- projectConfig,
892
- variableSchema,
893
- f.variables[variableKey],
894
- pathPrefix.concat([fN, "variables", variableKey]),
895
- ctx,
896
- variableKey,
897
- schemasByKey,
898
- );
899
- }
972
+ refineVariableEntry(
973
+ projectConfig,
974
+ variableSchemaByKey,
975
+ variableKey,
976
+ f.variables[variableKey],
977
+ pathPrefix.concat([fN, "variables", variableKey]),
978
+ ctx,
979
+ schemasByKey,
980
+ );
900
981
  });
901
982
  }
902
983
  });
@@ -916,24 +997,15 @@ function refineRules({
916
997
  // rules[n].variables[key]
917
998
  if (rule.variables) {
918
999
  Object.keys(rule.variables).forEach((variableKey) => {
919
- const variableSchema = variableSchemaByKey[variableKey];
920
- if (!variableSchema) {
921
- ctx.addIssue({
922
- code: z.ZodIssueCode.custom,
923
- message: `Variable "${variableKey}" is not defined in \`variablesSchema\`.`,
924
- path: pathPrefix.concat([ruleN, "variables", variableKey]),
925
- });
926
- } else {
927
- superRefineVariableValue(
928
- projectConfig,
929
- variableSchema,
930
- rule.variables[variableKey],
931
- pathPrefix.concat([ruleN, "variables", variableKey]),
932
- ctx,
933
- variableKey,
934
- schemasByKey,
935
- );
936
- }
1000
+ refineVariableEntry(
1001
+ projectConfig,
1002
+ variableSchemaByKey,
1003
+ variableKey,
1004
+ rule.variables[variableKey],
1005
+ pathPrefix.concat([ruleN, "variables", variableKey]),
1006
+ ctx,
1007
+ schemasByKey,
1008
+ );
937
1009
  });
938
1010
  }
939
1011
 
@@ -1014,6 +1086,7 @@ export function getFeatureZodSchema(
1014
1086
  ) {
1015
1087
  const schemaZodSchema = getSchemaZodSchema(availableSchemaKeys);
1016
1088
  const variableValueZodSchema = valueZodSchema;
1089
+ const variableValueOrNullZodSchema = z.union([valueZodSchema, z.null()]);
1017
1090
 
1018
1091
  const variationValueZodSchema = z.string().min(1);
1019
1092
 
@@ -1061,7 +1134,7 @@ export function getFeatureZodSchema(
1061
1134
 
1062
1135
  enabled: z.boolean().optional(),
1063
1136
  variation: variationValueZodSchema.optional(),
1064
- variables: z.record(variableValueZodSchema).optional(),
1137
+ variables: z.record(variableValueOrNullZodSchema).optional(),
1065
1138
  variationWeights: z.record(z.number().min(0).max(100)).optional(),
1066
1139
  })
1067
1140
  .strict(),
@@ -1109,7 +1182,7 @@ export function getFeatureZodSchema(
1109
1182
  segments: groupSegmentsZodSchema,
1110
1183
  enabled: z.boolean().optional(),
1111
1184
  variation: variationValueZodSchema.optional(),
1112
- variables: z.record(variableValueZodSchema).optional(),
1185
+ variables: z.record(variableValueOrNullZodSchema).optional(),
1113
1186
  })
1114
1187
  .strict(),
1115
1188
  z
@@ -1117,7 +1190,7 @@ export function getFeatureZodSchema(
1117
1190
  conditions: conditionsZodSchema,
1118
1191
  enabled: z.boolean().optional(),
1119
1192
  variation: variationValueZodSchema.optional(),
1120
- variables: z.record(variableValueZodSchema).optional(),
1193
+ variables: z.record(variableValueOrNullZodSchema).optional(),
1121
1194
  })
1122
1195
  .strict(),
1123
1196
  ]),
@@ -1299,11 +1372,12 @@ export function getFeatureZodSchema(
1299
1372
  });
1300
1373
  return;
1301
1374
  }
1302
- // Validate required ⊆ properties at this level and in all nested object schemas
1375
+ // Validate required ⊆ properties at this level and in all nested object schemas (resolve refs when schemasByKey provided)
1303
1376
  refineRequiredKeysInSchema(
1304
- variableSchema as Parameters<typeof refineRequiredKeysInSchema>[0],
1377
+ variableSchema as SchemaLikeForRequired,
1305
1378
  [],
1306
1379
  ctx,
1380
+ schemasByKey,
1307
1381
  );
1308
1382
  }),
1309
1383
  )
@@ -1318,7 +1392,7 @@ export function getFeatureZodSchema(
1318
1392
  description: z.string().optional(),
1319
1393
  value: variationValueZodSchema,
1320
1394
  weight: z.number().min(0).max(100),
1321
- variables: z.record(variableValueZodSchema).optional(),
1395
+ variables: z.record(variableValueOrNullZodSchema).optional(),
1322
1396
  variableOverrides: z
1323
1397
  .record(
1324
1398
  z.array(
@@ -1413,6 +1487,28 @@ export function getFeatureZodSchema(
1413
1487
  variableKeys.forEach((variableKey) => {
1414
1488
  const variableSchema = variableSchemaByKey[variableKey];
1415
1489
 
1490
+ // When variable references a schema by name, ensure it resolves and validate the referenced schema
1491
+ if ("schema" in variableSchema && variableSchema.schema) {
1492
+ const resolvedSchema = resolveVariableSchema(
1493
+ variableSchema as Parameters<typeof resolveVariableSchema>[0],
1494
+ schemasByKey,
1495
+ );
1496
+ if (!resolvedSchema) {
1497
+ ctx.addIssue({
1498
+ code: z.ZodIssueCode.custom,
1499
+ message: `Schema "${variableSchema.schema}" could not be loaded for variable "${variableKey}".`,
1500
+ path: ["variablesSchema", variableKey],
1501
+ });
1502
+ } else {
1503
+ refineRequiredKeysInSchema(
1504
+ resolvedSchema as SchemaLikeForRequired,
1505
+ ["variablesSchema", variableKey],
1506
+ ctx,
1507
+ schemasByKey,
1508
+ );
1509
+ }
1510
+ }
1511
+
1416
1512
  // When type and enum are both present, all enum values must match the type
1417
1513
  const effectiveSchema = resolveVariableSchema(variableSchema, schemasByKey);
1418
1514
  if (
@@ -1487,32 +1583,21 @@ export function getFeatureZodSchema(
1487
1583
  // variations[n].variables[key]
1488
1584
  if (variation.variables) {
1489
1585
  for (const variableKey of Object.keys(variation.variables)) {
1490
- const variableValue = variation.variables[variableKey];
1491
- const variableSchema = variableSchemaByKey[variableKey];
1492
- if (!variableSchema) {
1493
- ctx.addIssue({
1494
- code: z.ZodIssueCode.custom,
1495
- message: `Variable "${variableKey}" is not defined in \`variablesSchema\`.`,
1496
- path: ["variations", variationN, "variables", variableKey],
1497
- });
1498
- } else {
1499
- superRefineVariableValue(
1500
- projectConfig,
1501
- variableSchema,
1502
- variableValue,
1503
- ["variations", variationN, "variables", variableKey],
1504
- ctx,
1505
- variableKey,
1506
- schemasByKey,
1507
- );
1508
- }
1586
+ refineVariableEntry(
1587
+ projectConfig,
1588
+ variableSchemaByKey,
1589
+ variableKey,
1590
+ variation.variables[variableKey],
1591
+ ["variations", variationN, "variables", variableKey],
1592
+ ctx,
1593
+ schemasByKey,
1594
+ );
1509
1595
  }
1510
1596
  }
1511
1597
 
1512
- // variations[n].variableOverrides[key][].value (validated even when variation.variables is absent)
1598
+ // variations[n].variableOverrides[key][].value (path-map or full value)
1513
1599
  if (variation.variableOverrides) {
1514
1600
  for (const variableKey of Object.keys(variation.variableOverrides)) {
1515
- const overrides = variation.variableOverrides[variableKey];
1516
1601
  const variableSchema = variableSchemaByKey[variableKey];
1517
1602
  if (!variableSchema) {
1518
1603
  ctx.addIssue({
@@ -1520,26 +1605,98 @@ export function getFeatureZodSchema(
1520
1605
  message: `Variable "${variableKey}" is not defined in \`variablesSchema\`.`,
1521
1606
  path: ["variations", variationN, "variableOverrides", variableKey],
1522
1607
  });
1523
- } else if (Array.isArray(overrides)) {
1524
- overrides.forEach((override, overrideN) => {
1608
+ continue;
1609
+ }
1610
+ // When variable references a schema by name, ensure it can be resolved
1611
+ const variableSchemaWithRef = variableSchema as { schema?: string };
1612
+ if (variableSchemaWithRef.schema) {
1613
+ const resolvedVarSchema = resolveVariableSchema(
1614
+ variableSchema as Parameters<typeof resolveVariableSchema>[0],
1615
+ schemasByKey,
1616
+ );
1617
+ if (!resolvedVarSchema) {
1618
+ ctx.addIssue({
1619
+ code: z.ZodIssueCode.custom,
1620
+ message: `Schema "${variableSchemaWithRef.schema}" could not be loaded for variable "${variableKey}".`,
1621
+ path: ["variations", variationN, "variableOverrides", variableKey],
1622
+ });
1623
+ continue;
1624
+ }
1625
+ }
1626
+ const overrides = variation.variableOverrides[variableKey];
1627
+ if (!Array.isArray(overrides)) continue;
1628
+ overrides.forEach((override, overrideN) => {
1629
+ const overrideValue = override.value;
1630
+ const valuePath = [
1631
+ "variations",
1632
+ variationN,
1633
+ "variableOverrides",
1634
+ variableKey,
1635
+ overrideN,
1636
+ "value",
1637
+ ];
1638
+ if (
1639
+ typeof overrideValue === "object" &&
1640
+ overrideValue !== null &&
1641
+ !Array.isArray(overrideValue)
1642
+ ) {
1643
+ const pathMap = overrideValue as Record<string, unknown>;
1644
+ for (const pathKey of Object.keys(pathMap)) {
1645
+ const pathSegments = parsePathMapKey(pathKey);
1646
+ if (!pathSegments || pathSegments.length === 0) {
1647
+ ctx.addIssue({
1648
+ code: z.ZodIssueCode.custom,
1649
+ message: `Invalid mutation path "${pathKey}" in variableOverride for "${variableKey}".`,
1650
+ path: [...valuePath, pathKey],
1651
+ });
1652
+ continue;
1653
+ }
1654
+ const atPathSchema = resolveSchemaAtPath(
1655
+ variableSchema as Schema,
1656
+ pathSegments,
1657
+ schemasByKey,
1658
+ );
1659
+ if (!atPathSchema) {
1660
+ const effectiveSchema = resolveVariableSchema(
1661
+ variableSchema as Parameters<typeof resolveVariableSchema>[0],
1662
+ schemasByKey,
1663
+ );
1664
+ const isFlatObject =
1665
+ effectiveSchema?.type === "object" &&
1666
+ (!effectiveSchema.properties ||
1667
+ Object.keys(effectiveSchema.properties).length === 0);
1668
+ if (isFlatObject) {
1669
+ continue;
1670
+ }
1671
+ ctx.addIssue({
1672
+ code: z.ZodIssueCode.custom,
1673
+ message: `Path "${pathKey}" is invalid for variable "${variableKey}" (not in schema).`,
1674
+ path: [...valuePath, pathKey],
1675
+ });
1676
+ continue;
1677
+ }
1678
+ superRefineVariableValue(
1679
+ projectConfig,
1680
+ atPathSchema,
1681
+ pathMap[pathKey],
1682
+ [...valuePath, pathKey],
1683
+ ctx,
1684
+ pathKey,
1685
+ schemasByKey,
1686
+ );
1687
+ }
1688
+ } else {
1525
1689
  superRefineVariableValue(
1526
1690
  projectConfig,
1527
- variableSchema,
1528
- override.value,
1529
- [
1530
- "variations",
1531
- variationN,
1532
- "variableOverrides",
1533
- variableKey,
1534
- overrideN,
1535
- "value",
1536
- ],
1691
+ variableSchema as Parameters<typeof superRefineVariableValue>[1],
1692
+ overrideValue,
1693
+ valuePath,
1537
1694
  ctx,
1538
1695
  variableKey,
1539
1696
  schemasByKey,
1540
1697
  );
1541
- });
1542
- }
1698
+ }
1699
+ });
1543
1700
  }
1544
1701
  }
1545
1702
  });