@featurevisor/core 2.17.0 → 2.19.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 (106) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/coverage/clover.xml +1692 -1196
  3. package/coverage/coverage-final.json +29 -22
  4. package/coverage/lcov-report/builder/allocator.ts.html +26 -26
  5. package/coverage/lcov-report/builder/buildDatafile.ts.html +2017 -0
  6. package/coverage/lcov-report/builder/buildScopedConditions.ts.html +35 -35
  7. package/coverage/lcov-report/builder/buildScopedDatafile.ts.html +213 -45
  8. package/coverage/lcov-report/builder/buildScopedSegments.ts.html +28 -28
  9. package/coverage/lcov-report/builder/convertToV1.ts.html +583 -0
  10. package/coverage/lcov-report/builder/getFeatureRanges.ts.html +268 -0
  11. package/coverage/lcov-report/builder/hashes.ts.html +412 -0
  12. package/coverage/lcov-report/builder/index.html +90 -30
  13. package/coverage/lcov-report/builder/mutateVariables.ts.html +66 -45
  14. package/coverage/lcov-report/builder/mutator.ts.html +95 -95
  15. package/coverage/lcov-report/builder/revision.ts.html +1 -1
  16. package/coverage/lcov-report/builder/traffic.ts.html +60 -57
  17. package/coverage/lcov-report/config/index.html +1 -1
  18. package/coverage/lcov-report/config/index.ts.html +2 -2
  19. package/coverage/lcov-report/config/projectConfig.ts.html +28 -28
  20. package/coverage/lcov-report/datasource/adapter.ts.html +2 -2
  21. package/coverage/lcov-report/datasource/datasource.ts.html +4 -4
  22. package/coverage/lcov-report/datasource/filesystemAdapter.ts.html +34 -34
  23. package/coverage/lcov-report/datasource/index.html +1 -1
  24. package/coverage/lcov-report/datasource/index.ts.html +1 -1
  25. package/coverage/lcov-report/index.html +39 -39
  26. package/coverage/lcov-report/linter/attributeSchema.ts.html +1 -1
  27. package/coverage/lcov-report/linter/checkCircularDependency.ts.html +1 -1
  28. package/coverage/lcov-report/linter/checkPercentageExceedingSlot.ts.html +1 -1
  29. package/coverage/lcov-report/linter/conditionSchema.ts.html +17 -17
  30. package/coverage/lcov-report/linter/featureSchema.ts.html +879 -417
  31. package/coverage/lcov-report/linter/groupSchema.ts.html +1 -1
  32. package/coverage/lcov-report/linter/index.html +34 -34
  33. package/coverage/lcov-report/linter/lintProject.ts.html +25 -25
  34. package/coverage/lcov-report/linter/mutationNotation.ts.html +224 -122
  35. package/coverage/lcov-report/linter/printError.ts.html +1 -1
  36. package/coverage/lcov-report/linter/schema.ts.html +175 -85
  37. package/coverage/lcov-report/linter/segmentSchema.ts.html +1 -1
  38. package/coverage/lcov-report/linter/testSchema.ts.html +5 -5
  39. package/coverage/lcov-report/list/index.html +1 -1
  40. package/coverage/lcov-report/list/matrix.ts.html +1 -1
  41. package/coverage/lcov-report/parsers/index.html +1 -1
  42. package/coverage/lcov-report/parsers/index.ts.html +4 -4
  43. package/coverage/lcov-report/parsers/json.ts.html +2 -2
  44. package/coverage/lcov-report/parsers/yml.ts.html +6 -6
  45. package/coverage/lcov-report/tester/cliFormat.ts.html +1 -1
  46. package/coverage/lcov-report/tester/helpers.ts.html +1 -1
  47. package/coverage/lcov-report/tester/index.html +1 -1
  48. package/coverage/lcov-report/utils/extractKeys.ts.html +493 -0
  49. package/coverage/lcov-report/utils/git.ts.html +3 -3
  50. package/coverage/lcov-report/utils/index.html +54 -9
  51. package/coverage/lcov-report/utils/index.ts.html +94 -0
  52. package/coverage/lcov-report/utils/pretty.ts.html +121 -0
  53. package/coverage/lcov.info +3206 -2260
  54. package/json-schema/attribute.json +25 -9
  55. package/json-schema/feature.json +319 -238
  56. package/json-schema/segment.json +76 -45
  57. package/lib/builder/buildDatafile.js +50 -3
  58. package/lib/builder/buildDatafile.js.map +1 -1
  59. package/lib/builder/buildDatafile.spec.d.ts +1 -0
  60. package/lib/builder/buildDatafile.spec.js +233 -0
  61. package/lib/builder/buildDatafile.spec.js.map +1 -0
  62. package/lib/builder/buildScopedDatafile.js +37 -1
  63. package/lib/builder/buildScopedDatafile.js.map +1 -1
  64. package/lib/builder/buildScopedDatafile.spec.js +289 -0
  65. package/lib/builder/buildScopedDatafile.spec.js.map +1 -1
  66. package/lib/builder/mutateVariables.d.ts +1 -1
  67. package/lib/builder/mutateVariables.js +4 -1
  68. package/lib/builder/mutateVariables.js.map +1 -1
  69. package/lib/builder/mutateVariables.spec.js +29 -0
  70. package/lib/builder/mutateVariables.spec.js.map +1 -1
  71. package/lib/builder/traffic.js +1 -0
  72. package/lib/builder/traffic.js.map +1 -1
  73. package/lib/builder/traffic.spec.js +26 -0
  74. package/lib/builder/traffic.spec.js.map +1 -1
  75. package/lib/generate-code/typescript.js +63 -20
  76. package/lib/generate-code/typescript.js.map +1 -1
  77. package/lib/linter/featureSchema.d.ts +237 -22
  78. package/lib/linter/featureSchema.js +175 -84
  79. package/lib/linter/featureSchema.js.map +1 -1
  80. package/lib/linter/featureSchema.spec.js +268 -0
  81. package/lib/linter/featureSchema.spec.js.map +1 -1
  82. package/lib/linter/mutationNotation.js +54 -19
  83. package/lib/linter/mutationNotation.js.map +1 -1
  84. package/lib/linter/mutationNotation.spec.js +49 -0
  85. package/lib/linter/mutationNotation.spec.js.map +1 -1
  86. package/lib/linter/schema.d.ts +1 -0
  87. package/lib/linter/schema.js +13 -0
  88. package/lib/linter/schema.js.map +1 -1
  89. package/lib/linter/schema.spec.js +51 -0
  90. package/lib/linter/schema.spec.js.map +1 -1
  91. package/package.json +5 -5
  92. package/src/builder/buildDatafile.spec.ts +267 -0
  93. package/src/builder/buildDatafile.ts +85 -4
  94. package/src/builder/buildScopedDatafile.spec.ts +330 -0
  95. package/src/builder/buildScopedDatafile.ts +57 -1
  96. package/src/builder/mutateVariables.spec.ts +45 -0
  97. package/src/builder/mutateVariables.ts +7 -0
  98. package/src/builder/traffic.spec.ts +33 -0
  99. package/src/builder/traffic.ts +1 -0
  100. package/src/generate-code/typescript.ts +87 -20
  101. package/src/linter/featureSchema.spec.ts +315 -0
  102. package/src/linter/featureSchema.ts +269 -115
  103. package/src/linter/mutationNotation.spec.ts +62 -0
  104. package/src/linter/mutationNotation.ts +47 -13
  105. package/src/linter/schema.spec.ts +72 -0
  106. package/src/linter/schema.ts +30 -0
@@ -610,6 +610,34 @@ describe("featureSchema.ts :: getFeatureZodSchema (variablesSchema and variable
610
610
  "maxLength",
611
611
  );
612
612
  });
613
+
614
+ it("accepts plain-key partial object overrides for structured object schemas", () => {
615
+ expectParseSuccess(
616
+ baseFeature({
617
+ variablesSchema: {
618
+ config: {
619
+ type: "object",
620
+ properties: {
621
+ theme: { type: "string" },
622
+ compact: { type: "boolean" },
623
+ },
624
+ required: ["theme", "compact"],
625
+ defaultValue: { theme: "light", compact: true },
626
+ },
627
+ },
628
+ variations: [
629
+ { value: "control", weight: 50 },
630
+ {
631
+ value: "treatment",
632
+ weight: 50,
633
+ variableOverrides: {
634
+ config: [{ segments: "*", value: { theme: "dark" } }],
635
+ },
636
+ },
637
+ ],
638
+ }),
639
+ );
640
+ });
613
641
  });
614
642
 
615
643
  describe("rules: variables", () => {
@@ -701,6 +729,175 @@ describe("featureSchema.ts :: getFeatureZodSchema (variablesSchema and variable
701
729
  });
702
730
  });
703
731
 
732
+ describe("rules: variableOverrides", () => {
733
+ it("accepts rule variableOverrides with values matching variablesSchema", () => {
734
+ expectParseSuccess(
735
+ baseFeature({
736
+ variablesSchema: {
737
+ config: {
738
+ type: "object",
739
+ properties: {
740
+ title: { type: "string" },
741
+ nested: {
742
+ type: "object",
743
+ properties: {
744
+ count: { type: "integer" },
745
+ },
746
+ },
747
+ },
748
+ defaultValue: {
749
+ title: "default",
750
+ nested: { count: 0 },
751
+ },
752
+ },
753
+ },
754
+ rules: {
755
+ staging: [
756
+ {
757
+ key: "r1",
758
+ segments: "*",
759
+ percentage: 100,
760
+ variableOverrides: {
761
+ config: [
762
+ {
763
+ segments: "countries/germany",
764
+ value: {
765
+ "nested.count": 5,
766
+ },
767
+ },
768
+ ],
769
+ },
770
+ },
771
+ ],
772
+ production: [{ key: "r1", segments: "*", percentage: 100 }],
773
+ },
774
+ }),
775
+ );
776
+ });
777
+
778
+ it("rejects rule variableOverrides for unknown variable key", () => {
779
+ expectParseFailure(
780
+ baseFeature({
781
+ variablesSchema: {
782
+ config: { type: "string", defaultValue: "x" },
783
+ },
784
+ rules: {
785
+ staging: [
786
+ {
787
+ key: "r1",
788
+ segments: "*",
789
+ percentage: 100,
790
+ variableOverrides: {
791
+ unknown: [{ segments: "*", value: "x" }],
792
+ },
793
+ },
794
+ ],
795
+ production: [{ key: "r1", segments: "*", percentage: 100 }],
796
+ },
797
+ }),
798
+ "not defined in",
799
+ );
800
+ });
801
+
802
+ it("rejects mutation notation in rule variableOverrides key", () => {
803
+ expectParseFailure(
804
+ baseFeature({
805
+ variablesSchema: {
806
+ config: {
807
+ type: "object",
808
+ properties: { title: { type: "string" } },
809
+ defaultValue: { title: "x" },
810
+ },
811
+ },
812
+ rules: {
813
+ staging: [
814
+ {
815
+ key: "r1",
816
+ segments: "*",
817
+ percentage: 100,
818
+ variableOverrides: {
819
+ "config.title": [{ segments: "*", value: "y" }],
820
+ },
821
+ },
822
+ ],
823
+ production: [{ key: "r1", segments: "*", percentage: 100 }],
824
+ },
825
+ }),
826
+ "must be a declared variable key",
827
+ );
828
+ });
829
+
830
+ it("rejects rule variableOverride path not present in schema", () => {
831
+ expectParseFailure(
832
+ baseFeature({
833
+ variablesSchema: {
834
+ config: {
835
+ type: "object",
836
+ properties: { title: { type: "string" } },
837
+ defaultValue: { title: "x" },
838
+ },
839
+ },
840
+ rules: {
841
+ staging: [
842
+ {
843
+ key: "r1",
844
+ segments: "*",
845
+ percentage: 100,
846
+ variableOverrides: {
847
+ config: [
848
+ {
849
+ segments: "*",
850
+ value: {
851
+ "nested.missing": "x",
852
+ },
853
+ },
854
+ ],
855
+ },
856
+ },
857
+ ],
858
+ production: [{ key: "r1", segments: "*", percentage: 100 }],
859
+ },
860
+ }),
861
+ "invalid for variable",
862
+ );
863
+ });
864
+
865
+ it("rejects non-null payload for :remove in rule variableOverrides", () => {
866
+ expectParseFailure(
867
+ baseFeature({
868
+ variablesSchema: {
869
+ config: {
870
+ type: "object",
871
+ properties: { title: { type: "string" }, optional: { type: "string" } },
872
+ defaultValue: { title: "x", optional: "y" },
873
+ },
874
+ },
875
+ rules: {
876
+ staging: [
877
+ {
878
+ key: "r1",
879
+ segments: "*",
880
+ percentage: 100,
881
+ variableOverrides: {
882
+ config: [
883
+ {
884
+ conditions: [{ attribute: "country", operator: "equals", value: "de" }],
885
+ value: {
886
+ "optional:remove": "not-null",
887
+ },
888
+ },
889
+ ],
890
+ },
891
+ },
892
+ ],
893
+ production: [{ key: "r1", segments: "*", percentage: 100 }],
894
+ },
895
+ }),
896
+ "must use `null` as value",
897
+ );
898
+ });
899
+ });
900
+
704
901
  describe("force: variables", () => {
705
902
  it("accepts force variables that match variablesSchema", () => {
706
903
  expectParseSuccess(
@@ -852,6 +1049,89 @@ describe("featureSchema.ts :: getFeatureZodSchema (variablesSchema and variable
852
1049
  );
853
1050
  });
854
1051
 
1052
+ it("accepts object variable with additionalProperties only and arbitrary keys", () => {
1053
+ expectParseSuccess(
1054
+ baseFeature({
1055
+ variablesSchema: {
1056
+ labels: {
1057
+ type: "object",
1058
+ additionalProperties: { type: "string" },
1059
+ defaultValue: { title: "Welcome", subtitle: "Hello" },
1060
+ },
1061
+ },
1062
+ }),
1063
+ );
1064
+ });
1065
+
1066
+ it("accepts object variable with properties and additionalProperties together", () => {
1067
+ expectParseSuccess(
1068
+ baseFeature({
1069
+ variablesSchema: {
1070
+ metadata: {
1071
+ type: "object",
1072
+ properties: {
1073
+ fixed: { type: "integer" },
1074
+ },
1075
+ additionalProperties: { type: "string" },
1076
+ required: ["fixed"],
1077
+ defaultValue: { fixed: 1, dynamicKey: "value" },
1078
+ },
1079
+ },
1080
+ }),
1081
+ );
1082
+ });
1083
+
1084
+ it("rejects object variable when unknown key is present and additionalProperties is not defined", () => {
1085
+ expectParseFailure(
1086
+ baseFeature({
1087
+ variablesSchema: {
1088
+ settings: {
1089
+ type: "object",
1090
+ properties: {
1091
+ theme: { type: "string" },
1092
+ },
1093
+ defaultValue: { theme: "light", subtitle: "hello" },
1094
+ },
1095
+ },
1096
+ }),
1097
+ "Unknown property",
1098
+ );
1099
+ });
1100
+
1101
+ it("accepts object variable with additionalProperties that references reusable schema", () => {
1102
+ expectParseSuccess(
1103
+ baseFeature({
1104
+ variablesSchema: {
1105
+ linksByLocale: {
1106
+ type: "object",
1107
+ additionalProperties: { schema: "link" },
1108
+ defaultValue: {
1109
+ en: { title: "Home", url: "/" },
1110
+ de: { title: "Start", url: "/de" },
1111
+ },
1112
+ },
1113
+ },
1114
+ }),
1115
+ );
1116
+ });
1117
+
1118
+ it("rejects object variable when additionalProperties value does not match schema", () => {
1119
+ expectParseFailure(
1120
+ baseFeature({
1121
+ variablesSchema: {
1122
+ linksByLocale: {
1123
+ type: "object",
1124
+ additionalProperties: { schema: "link" },
1125
+ defaultValue: {
1126
+ en: { title: "Home" },
1127
+ },
1128
+ },
1129
+ },
1130
+ }),
1131
+ "Missing required property",
1132
+ );
1133
+ });
1134
+
855
1135
  it("rejects inline object variable when defaultValue is missing required property", () => {
856
1136
  expectParseFailure(
857
1137
  baseFeature({
@@ -1417,5 +1697,40 @@ describe("featureSchema.ts :: getFeatureZodSchema (variablesSchema and variable
1417
1697
  },
1418
1698
  );
1419
1699
  });
1700
+
1701
+ it("additionalProperties type mismatch: error path includes dynamic object key", () => {
1702
+ expectErrorSurfaces(
1703
+ baseFeature({
1704
+ variablesSchema: {
1705
+ labels: {
1706
+ type: "object",
1707
+ additionalProperties: { type: "string" },
1708
+ defaultValue: {
1709
+ title: 123,
1710
+ },
1711
+ },
1712
+ },
1713
+ }),
1714
+ {
1715
+ pathContains: ["variablesSchema", "labels", "defaultValue", "title"],
1716
+ messageContains: "type string",
1717
+ },
1718
+ );
1719
+ });
1720
+
1721
+ it("rejects variable schema with schema reference mixed with inline additionalProperties", () => {
1722
+ expectParseFailure(
1723
+ baseFeature({
1724
+ variablesSchema: {
1725
+ myLink: {
1726
+ schema: "link",
1727
+ additionalProperties: { type: "string" },
1728
+ defaultValue: { title: "Home", url: "/" },
1729
+ },
1730
+ },
1731
+ }),
1732
+ "additionalProperties",
1733
+ );
1734
+ });
1420
1735
  });
1421
1736
  });