@featurevisor/core 1.35.3 → 2.0.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 (252) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +0 -6
  3. package/coverage/clover.xml +321 -237
  4. package/coverage/coverage-final.json +8 -8
  5. package/coverage/lcov-report/index.html +77 -47
  6. package/coverage/lcov-report/lib/builder/allocator.js.html +14 -14
  7. package/coverage/lcov-report/lib/builder/index.html +16 -16
  8. package/coverage/lcov-report/lib/builder/revision.js.html +3 -3
  9. package/coverage/lcov-report/lib/builder/traffic.js.html +88 -64
  10. package/coverage/lcov-report/lib/list/index.html +116 -0
  11. package/coverage/lcov-report/lib/{tester → list}/matrix.js.html +90 -66
  12. package/coverage/lcov-report/lib/tester/helpers.js.html +295 -0
  13. package/coverage/lcov-report/lib/tester/index.html +20 -35
  14. package/coverage/lcov-report/src/builder/allocator.ts.html +2 -2
  15. package/coverage/lcov-report/src/builder/index.html +15 -15
  16. package/coverage/lcov-report/src/builder/revision.ts.html +1 -1
  17. package/coverage/lcov-report/src/builder/traffic.ts.html +96 -24
  18. package/coverage/lcov-report/src/list/index.html +116 -0
  19. package/coverage/lcov-report/src/{tester → list}/matrix.ts.html +87 -21
  20. package/coverage/lcov-report/src/tester/helpers.ts.html +313 -0
  21. package/coverage/lcov-report/src/tester/index.html +20 -35
  22. package/coverage/lcov.info +592 -436
  23. package/lib/assess-distribution/index.d.ts +1 -1
  24. package/lib/assess-distribution/index.js +102 -162
  25. package/lib/assess-distribution/index.js.map +1 -1
  26. package/lib/benchmark/index.js +87 -143
  27. package/lib/benchmark/index.js.map +1 -1
  28. package/lib/builder/allocator.d.ts +1 -1
  29. package/lib/builder/allocator.js +12 -12
  30. package/lib/builder/allocator.js.map +1 -1
  31. package/lib/builder/allocator.spec.js +22 -22
  32. package/lib/builder/allocator.spec.js.map +1 -1
  33. package/lib/builder/buildDatafile.d.ts +4 -3
  34. package/lib/builder/buildDatafile.js +311 -388
  35. package/lib/builder/buildDatafile.js.map +1 -1
  36. package/lib/builder/buildProject.d.ts +2 -1
  37. package/lib/builder/buildProject.js +96 -183
  38. package/lib/builder/buildProject.js.map +1 -1
  39. package/lib/builder/convertToV1.d.ts +10 -0
  40. package/lib/builder/convertToV1.js +119 -0
  41. package/lib/builder/convertToV1.js.map +1 -0
  42. package/lib/builder/getFeatureRanges.d.ts +1 -1
  43. package/lib/builder/getFeatureRanges.js +32 -105
  44. package/lib/builder/getFeatureRanges.js.map +1 -1
  45. package/lib/builder/hashes.d.ts +4 -0
  46. package/lib/builder/hashes.js +70 -0
  47. package/lib/builder/hashes.js.map +1 -0
  48. package/lib/builder/revision.js +2 -2
  49. package/lib/builder/revision.js.map +1 -1
  50. package/lib/builder/revision.spec.js +1 -1
  51. package/lib/builder/revision.spec.js.map +1 -1
  52. package/lib/builder/traffic.d.ts +1 -1
  53. package/lib/builder/traffic.js +57 -49
  54. package/lib/builder/traffic.js.map +1 -1
  55. package/lib/builder/traffic.spec.js +14 -14
  56. package/lib/builder/traffic.spec.js.map +1 -1
  57. package/lib/cli/cli.js +60 -129
  58. package/lib/cli/cli.js.map +1 -1
  59. package/lib/cli/plugins.js +14 -16
  60. package/lib/cli/plugins.js.map +1 -1
  61. package/lib/config/parsers.js +1 -1
  62. package/lib/config/parsers.js.map +1 -1
  63. package/lib/config/projectConfig.d.ts +8 -6
  64. package/lib/config/projectConfig.js +31 -72
  65. package/lib/config/projectConfig.js.map +1 -1
  66. package/lib/datasource/adapter.d.ts +1 -1
  67. package/lib/datasource/adapter.js +2 -5
  68. package/lib/datasource/adapter.js.map +1 -1
  69. package/lib/datasource/datasource.d.ts +2 -1
  70. package/lib/datasource/datasource.js +107 -148
  71. package/lib/datasource/datasource.js.map +1 -1
  72. package/lib/datasource/filesystemAdapter.d.ts +1 -1
  73. package/lib/datasource/filesystemAdapter.js +224 -360
  74. package/lib/datasource/filesystemAdapter.js.map +1 -1
  75. package/lib/evaluate/index.d.ts +1 -1
  76. package/lib/evaluate/index.js +120 -188
  77. package/lib/evaluate/index.js.map +1 -1
  78. package/lib/find-duplicate-segments/findDuplicateSegments.d.ts +1 -1
  79. package/lib/find-duplicate-segments/findDuplicateSegments.js +40 -128
  80. package/lib/find-duplicate-segments/findDuplicateSegments.js.map +1 -1
  81. package/lib/find-duplicate-segments/index.js +27 -82
  82. package/lib/find-duplicate-segments/index.js.map +1 -1
  83. package/lib/find-usage/index.d.ts +7 -5
  84. package/lib/find-usage/index.js +333 -507
  85. package/lib/find-usage/index.js.map +1 -1
  86. package/lib/generate-code/index.js +36 -91
  87. package/lib/generate-code/index.js.map +1 -1
  88. package/lib/generate-code/typescript.js +117 -157
  89. package/lib/generate-code/typescript.js.map +1 -1
  90. package/lib/index.d.ts +0 -1
  91. package/lib/index.js +0 -1
  92. package/lib/index.js.map +1 -1
  93. package/lib/info/index.js +45 -133
  94. package/lib/info/index.js.map +1 -1
  95. package/lib/init/index.d.ts +1 -1
  96. package/lib/init/index.js +16 -64
  97. package/lib/init/index.js.map +1 -1
  98. package/lib/linter/attributeSchema.d.ts +21 -6
  99. package/lib/linter/attributeSchema.js +18 -4
  100. package/lib/linter/attributeSchema.js.map +1 -1
  101. package/lib/linter/checkCircularDependency.d.ts +1 -1
  102. package/lib/linter/checkCircularDependency.js +22 -80
  103. package/lib/linter/checkCircularDependency.js.map +1 -1
  104. package/lib/linter/checkPercentageExceedingSlot.d.ts +1 -1
  105. package/lib/linter/checkPercentageExceedingSlot.js +36 -76
  106. package/lib/linter/checkPercentageExceedingSlot.js.map +1 -1
  107. package/lib/linter/conditionSchema.d.ts +1 -1
  108. package/lib/linter/conditionSchema.js +89 -41
  109. package/lib/linter/conditionSchema.js.map +1 -1
  110. package/lib/linter/featureSchema.d.ts +345 -197
  111. package/lib/linter/featureSchema.js +313 -172
  112. package/lib/linter/featureSchema.js.map +1 -1
  113. package/lib/linter/groupSchema.js +6 -6
  114. package/lib/linter/groupSchema.js.map +1 -1
  115. package/lib/linter/lintProject.js +306 -480
  116. package/lib/linter/lintProject.js.map +1 -1
  117. package/lib/linter/printError.js +7 -7
  118. package/lib/linter/printError.js.map +1 -1
  119. package/lib/linter/segmentSchema.js +2 -2
  120. package/lib/linter/segmentSchema.js.map +1 -1
  121. package/lib/linter/testSchema.d.ts +155 -3
  122. package/lib/linter/testSchema.js +47 -17
  123. package/lib/linter/testSchema.js.map +1 -1
  124. package/lib/list/index.d.ts +1 -0
  125. package/lib/list/index.js +349 -517
  126. package/lib/list/index.js.map +1 -1
  127. package/lib/{tester → list}/matrix.d.ts +1 -1
  128. package/lib/{tester → list}/matrix.js +50 -42
  129. package/lib/list/matrix.js.map +1 -0
  130. package/lib/{tester → list}/matrix.spec.js +7 -7
  131. package/lib/list/matrix.spec.js.map +1 -0
  132. package/lib/site/exportSite.js +25 -71
  133. package/lib/site/exportSite.js.map +1 -1
  134. package/lib/site/generateHistory.d.ts +1 -1
  135. package/lib/site/generateHistory.js +26 -82
  136. package/lib/site/generateHistory.js.map +1 -1
  137. package/lib/site/generateSiteSearchIndex.d.ts +1 -1
  138. package/lib/site/generateSiteSearchIndex.js +182 -259
  139. package/lib/site/generateSiteSearchIndex.js.map +1 -1
  140. package/lib/site/getLastModifiedFromHistory.d.ts +1 -1
  141. package/lib/site/getLastModifiedFromHistory.js +2 -2
  142. package/lib/site/getLastModifiedFromHistory.js.map +1 -1
  143. package/lib/site/getOwnerAndRepoFromUrl.js +6 -6
  144. package/lib/site/getOwnerAndRepoFromUrl.js.map +1 -1
  145. package/lib/site/getRelativePaths.js +7 -7
  146. package/lib/site/getRelativePaths.js.map +1 -1
  147. package/lib/site/getRepoDetails.js +20 -20
  148. package/lib/site/getRepoDetails.js.map +1 -1
  149. package/lib/site/index.js +25 -73
  150. package/lib/site/index.js.map +1 -1
  151. package/lib/site/serveSite.js +10 -10
  152. package/lib/site/serveSite.js.map +1 -1
  153. package/lib/tester/helpers.d.ts +2 -0
  154. package/lib/tester/helpers.js +71 -0
  155. package/lib/tester/helpers.js.map +1 -0
  156. package/lib/tester/helpers.spec.js +115 -0
  157. package/lib/tester/helpers.spec.js.map +1 -0
  158. package/lib/tester/index.d.ts +0 -1
  159. package/lib/tester/index.js +0 -1
  160. package/lib/tester/index.js.map +1 -1
  161. package/lib/tester/prettyDuration.js +11 -11
  162. package/lib/tester/prettyDuration.js.map +1 -1
  163. package/lib/tester/printTestResult.d.ts +1 -1
  164. package/lib/tester/printTestResult.js +35 -15
  165. package/lib/tester/printTestResult.js.map +1 -1
  166. package/lib/tester/testFeature.d.ts +4 -2
  167. package/lib/tester/testFeature.js +264 -226
  168. package/lib/tester/testFeature.js.map +1 -1
  169. package/lib/tester/testProject.d.ts +3 -7
  170. package/lib/tester/testProject.js +145 -246
  171. package/lib/tester/testProject.js.map +1 -1
  172. package/lib/tester/testSegment.d.ts +5 -2
  173. package/lib/tester/testSegment.js +65 -102
  174. package/lib/tester/testSegment.js.map +1 -1
  175. package/lib/utils/extractKeys.d.ts +2 -1
  176. package/lib/utils/extractKeys.js +57 -12
  177. package/lib/utils/extractKeys.js.map +1 -1
  178. package/lib/utils/git.d.ts +1 -1
  179. package/lib/utils/git.js +23 -23
  180. package/lib/utils/git.js.map +1 -1
  181. package/lib/utils/pretty.js +2 -4
  182. package/lib/utils/pretty.js.map +1 -1
  183. package/package.json +5 -6
  184. package/src/assess-distribution/index.ts +3 -2
  185. package/src/benchmark/index.ts +3 -3
  186. package/src/builder/allocator.spec.ts +1 -1
  187. package/src/builder/allocator.ts +1 -1
  188. package/src/builder/buildDatafile.ts +161 -124
  189. package/src/builder/buildProject.ts +6 -3
  190. package/src/builder/convertToV1.ts +166 -0
  191. package/src/builder/getFeatureRanges.ts +1 -1
  192. package/src/builder/hashes.ts +109 -0
  193. package/src/builder/traffic.ts +40 -16
  194. package/src/cli/cli.ts +1 -1
  195. package/src/cli/plugins.ts +0 -2
  196. package/src/config/projectConfig.ts +13 -10
  197. package/src/datasource/adapter.ts +1 -1
  198. package/src/datasource/datasource.ts +23 -2
  199. package/src/datasource/filesystemAdapter.ts +11 -12
  200. package/src/evaluate/index.ts +7 -6
  201. package/src/find-duplicate-segments/findDuplicateSegments.ts +1 -1
  202. package/src/find-usage/index.ts +111 -44
  203. package/src/generate-code/index.ts +1 -3
  204. package/src/generate-code/typescript.ts +7 -29
  205. package/src/index.ts +0 -1
  206. package/src/info/index.ts +2 -2
  207. package/src/init/index.ts +2 -2
  208. package/src/linter/attributeSchema.ts +18 -2
  209. package/src/linter/checkCircularDependency.ts +1 -1
  210. package/src/linter/checkPercentageExceedingSlot.ts +28 -8
  211. package/src/linter/conditionSchema.ts +66 -10
  212. package/src/linter/featureSchema.ts +312 -116
  213. package/src/linter/lintProject.ts +9 -4
  214. package/src/linter/testSchema.ts +42 -3
  215. package/src/list/index.ts +18 -30
  216. package/src/{tester → list}/matrix.ts +33 -11
  217. package/src/site/exportSite.ts +2 -4
  218. package/src/site/generateHistory.ts +1 -1
  219. package/src/site/generateSiteSearchIndex.ts +58 -50
  220. package/src/site/getLastModifiedFromHistory.ts +1 -1
  221. package/src/tester/helpers.spec.ts +149 -0
  222. package/src/tester/helpers.ts +76 -0
  223. package/src/tester/index.ts +0 -1
  224. package/src/tester/printTestResult.ts +25 -3
  225. package/src/tester/testFeature.ts +270 -124
  226. package/src/tester/testProject.ts +28 -49
  227. package/src/tester/testSegment.ts +48 -40
  228. package/src/utils/extractKeys.ts +58 -1
  229. package/src/utils/git.ts +1 -1
  230. package/tsconfig.cjs.json +1 -0
  231. package/coverage/lcov-report/lib/tester/checkIfObjectsAreEqual.js.html +0 -151
  232. package/coverage/lcov-report/src/tester/checkIfObjectsAreEqual.ts.html +0 -157
  233. package/lib/restore/index.d.ts +0 -4
  234. package/lib/restore/index.js +0 -91
  235. package/lib/restore/index.js.map +0 -1
  236. package/lib/tester/checkIfArraysAreEqual.d.ts +0 -1
  237. package/lib/tester/checkIfArraysAreEqual.js +0 -18
  238. package/lib/tester/checkIfArraysAreEqual.js.map +0 -1
  239. package/lib/tester/checkIfObjectsAreEqual.d.ts +0 -1
  240. package/lib/tester/checkIfObjectsAreEqual.js +0 -23
  241. package/lib/tester/checkIfObjectsAreEqual.js.map +0 -1
  242. package/lib/tester/checkIfObjectsAreEqual.spec.js +0 -26
  243. package/lib/tester/checkIfObjectsAreEqual.spec.js.map +0 -1
  244. package/lib/tester/matrix.js.map +0 -1
  245. package/lib/tester/matrix.spec.js.map +0 -1
  246. package/src/restore/index.ts +0 -42
  247. package/src/tester/checkIfArraysAreEqual.ts +0 -16
  248. package/src/tester/checkIfObjectsAreEqual.spec.ts +0 -31
  249. package/src/tester/checkIfObjectsAreEqual.ts +0 -24
  250. /package/lib/{tester → list}/matrix.spec.d.ts +0 -0
  251. /package/lib/tester/{checkIfObjectsAreEqual.spec.d.ts → helpers.spec.d.ts} +0 -0
  252. /package/src/{tester → list}/matrix.spec.ts +0 -0
package/src/list/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {
1
+ import type {
2
2
  ParsedFeature,
3
3
  Segment,
4
4
  Attribute,
@@ -10,7 +10,7 @@ import {
10
10
 
11
11
  import { Dependencies } from "../dependencies";
12
12
  import { Plugin } from "../cli";
13
- import { getFeatureAssertionsFromMatrix, getSegmentAssertionsFromMatrix } from "../tester";
13
+ import { getFeatureAssertionsFromMatrix, getSegmentAssertionsFromMatrix } from "./matrix";
14
14
 
15
15
  async function getEntitiesWithTests(
16
16
  deps: Dependencies,
@@ -39,7 +39,7 @@ async function getEntitiesWithTests(
39
39
  };
40
40
  }
41
41
 
42
- async function listEntities<T>(deps: Dependencies, entityType): Promise<T[]> {
42
+ export async function listEntities<T>(deps: Dependencies, entityType): Promise<T[]> {
43
43
  const { datasource, options } = deps;
44
44
 
45
45
  const result: T[] = [];
@@ -111,16 +111,10 @@ async function listEntities<T>(deps: Dependencies, entityType): Promise<T[]> {
111
111
  }
112
112
 
113
113
  // --disabledIn=<environment>
114
- if (
115
- options.disabledIn &&
116
- parsedFeature.environments &&
117
- parsedFeature.environments[options.disabledIn]
118
- ) {
119
- const disabledInEnvironment = parsedFeature.environments[options.disabledIn].rules.every(
120
- (rule) => {
121
- return rule.percentage === 0;
122
- },
123
- );
114
+ if (options.disabledIn && parsedFeature.rules && parsedFeature.rules[options.disabledIn]) {
115
+ const disabledInEnvironment = parsedFeature.rules[options.disabledIn].every((rule) => {
116
+ return rule.percentage === 0;
117
+ });
124
118
 
125
119
  if (!disabledInEnvironment) {
126
120
  continue;
@@ -128,16 +122,10 @@ async function listEntities<T>(deps: Dependencies, entityType): Promise<T[]> {
128
122
  }
129
123
 
130
124
  // --enabledIn=<environment>
131
- if (
132
- options.enabledIn &&
133
- parsedFeature.environments &&
134
- parsedFeature.environments[options.enabledIn]
135
- ) {
136
- const enabledInEnvironment = parsedFeature.environments[options.enabledIn].rules.some(
137
- (rule) => {
138
- return rule.percentage > 0;
139
- },
140
- );
125
+ if (options.enabledIn && parsedFeature.rules && parsedFeature.rules[options.enabledIn]) {
126
+ const enabledInEnvironment = parsedFeature.rules[options.enabledIn].some((rule) => {
127
+ return rule.percentage > 0;
128
+ });
141
129
 
142
130
  if (!enabledInEnvironment) {
143
131
  continue;
@@ -168,12 +156,7 @@ async function listEntities<T>(deps: Dependencies, entityType): Promise<T[]> {
168
156
  ? options.variable
169
157
  : [options.variable];
170
158
 
171
- let variablesInFeature: string[] = [];
172
- if (Array.isArray(parsedFeature.variablesSchema)) {
173
- variablesInFeature = parsedFeature.variablesSchema.map((variable) => variable.key);
174
- } else if (parsedFeature.variablesSchema) {
175
- variablesInFeature = Object.keys(parsedFeature.variablesSchema);
176
- }
159
+ let variablesInFeature: string[] = Object.keys(parsedFeature.variablesSchema || {});
177
160
 
178
161
  const hasVariables = lookForVariables.every((variable) =>
179
162
  variablesInFeature.includes(variable),
@@ -338,6 +321,11 @@ async function listEntities<T>(deps: Dependencies, entityType): Promise<T[]> {
338
321
  const testEntityType = (test as TestSegment).segment ? "segment" : "feature";
339
322
  let testAssertions = test.assertions;
340
323
 
324
+ // --entityType=<type>
325
+ if (options.entityType && options.entityType !== testEntityType) {
326
+ continue;
327
+ }
328
+
341
329
  // --apply-matrix
342
330
  if (options.applyMatrix) {
343
331
  if (testEntityType === "feature") {
@@ -428,7 +416,7 @@ function printResult({ result, entityType, options }) {
428
416
  }
429
417
 
430
418
  export async function listProject(deps: Dependencies) {
431
- const { rootDirectoryPath, projectConfig, datasource, options } = deps;
419
+ const { options } = deps;
432
420
 
433
421
  // features
434
422
  if (options.features) {
@@ -1,4 +1,4 @@
1
- import { AssertionMatrix, FeatureAssertion, SegmentAssertion } from "@featurevisor/types";
1
+ import type { AssertionMatrix, FeatureAssertion, SegmentAssertion } from "@featurevisor/types";
2
2
 
3
3
  function generateCombinations(
4
4
  keys: string[],
@@ -73,8 +73,8 @@ export function applyCombinationToFeatureAssertion(
73
73
  );
74
74
 
75
75
  // context
76
- flattenedAssertion.context = Object.keys(flattenedAssertion.context).reduce((acc, key) => {
77
- acc[key] = applyCombinationToValue(flattenedAssertion.context[key], combination);
76
+ flattenedAssertion.context = Object.keys(flattenedAssertion.context || {}).reduce((acc, key) => {
77
+ acc[key] = applyCombinationToValue(flattenedAssertion.context?.[key], combination);
78
78
 
79
79
  return acc;
80
80
  }, {});
@@ -105,9 +105,20 @@ export function getFeatureAssertionsFromMatrix(
105
105
  ): FeatureAssertion[] {
106
106
  if (!assertionWithMatrix.matrix) {
107
107
  const assertion = { ...assertionWithMatrix };
108
- assertion.description = `Assertion #${aIndex + 1}:${assertion.environment ? ` (${assertion.environment})` : ""} ${
109
- assertion.description || `at ${assertion.at}%`
110
- }`;
108
+
109
+ let suffix;
110
+
111
+ if (assertion.environment) {
112
+ suffix = ` (${assertion.environment})`;
113
+ }
114
+
115
+ if (assertion.description) {
116
+ suffix = `: ${assertion.description}`;
117
+ } else {
118
+ suffix = `: at ${assertion.at}%`;
119
+ }
120
+
121
+ assertion.description = `Assertion #${aIndex + 1}${suffix}`;
111
122
 
112
123
  return [assertion];
113
124
  }
@@ -118,9 +129,20 @@ export function getFeatureAssertionsFromMatrix(
118
129
  for (let cIndex = 0; cIndex < combinations.length; cIndex++) {
119
130
  const combination = combinations[cIndex];
120
131
  const assertion = applyCombinationToFeatureAssertion(combination, assertionWithMatrix);
121
- assertion.description = `Assertion #${aIndex + 1}:${assertion.environment ? ` (${assertion.environment})` : ""} ${
122
- assertion.description || `at ${assertion.at}%`
123
- }`;
132
+
133
+ let suffix;
134
+
135
+ if (assertion.environment) {
136
+ suffix = ` (${assertion.environment})`;
137
+ }
138
+
139
+ if (assertion.description) {
140
+ suffix = `: ${assertion.description}`;
141
+ } else {
142
+ suffix = `: at ${assertion.at}%`;
143
+ }
144
+
145
+ assertion.description = `Assertion #${aIndex + 1}${suffix}`;
124
146
 
125
147
  assertions.push(assertion);
126
148
  }
@@ -161,8 +183,8 @@ export function getSegmentAssertionsFromMatrix(
161
183
  ): SegmentAssertion[] {
162
184
  if (!assertionWithMatrix.matrix) {
163
185
  const assertion = { ...assertionWithMatrix };
164
- assertion.description = `Assertion #${aIndex + 1}: ${
165
- assertion.description || `#${aIndex + 1}`
186
+ assertion.description = `Assertion #${aIndex + 1}${
187
+ assertion.description ? `: ${assertion.description}` : ""
166
188
  }`;
167
189
 
168
190
  return [assertion];
@@ -1,8 +1,6 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
3
 
4
- import * as mkdirp from "mkdirp";
5
-
6
4
  import { generateHistory } from "./generateHistory";
7
5
  import { getRepoDetails } from "./getRepoDetails";
8
6
  import { generateSiteSearchIndex } from "./generateSiteSearchIndex";
@@ -13,7 +11,7 @@ export async function exportSite(deps: Dependencies) {
13
11
 
14
12
  const hasError = false;
15
13
 
16
- mkdirp.sync(projectConfig.siteExportDirectoryPath);
14
+ fs.mkdirSync(projectConfig.siteExportDirectoryPath, { recursive: true });
17
15
 
18
16
  const sitePackagePath = path.dirname(require.resolve("@featurevisor/site/package.json"));
19
17
 
@@ -35,7 +33,7 @@ export async function exportSite(deps: Dependencies) {
35
33
 
36
34
  // copy datafiles
37
35
  fs.cpSync(
38
- projectConfig.outputDirectoryPath,
36
+ projectConfig.datafilesDirectoryPath,
39
37
  path.join(projectConfig.siteExportDirectoryPath, "datafiles"),
40
38
  { recursive: true },
41
39
  );
@@ -1,7 +1,7 @@
1
1
  import * as path from "path";
2
2
  import * as fs from "fs";
3
3
 
4
- import { HistoryEntry } from "@featurevisor/types";
4
+ import type { HistoryEntry } from "@featurevisor/types";
5
5
  import { Dependencies } from "../dependencies";
6
6
 
7
7
  export async function generateHistory(deps: Dependencies): Promise<HistoryEntry[]> {
@@ -1,4 +1,4 @@
1
- import {
1
+ import type {
2
2
  HistoryEntry,
3
3
  SearchIndex,
4
4
  AttributeKey,
@@ -87,13 +87,15 @@ export async function generateSiteSearchIndex(
87
87
 
88
88
  if (Array.isArray(parsed.variations)) {
89
89
  parsed.variations.forEach((variation) => {
90
- if (!variation.variables) {
90
+ if (!variation.variableOverrides) {
91
91
  return;
92
92
  }
93
93
 
94
- variation.variables.forEach((v) => {
95
- if (v.overrides) {
96
- v.overrides.forEach((o) => {
94
+ Object.keys(variation.variableOverrides).forEach((variableKey) => {
95
+ const overrides = variation.variableOverrides?.[variableKey];
96
+
97
+ if (overrides) {
98
+ overrides.forEach((o) => {
97
99
  if (o.conditions) {
98
100
  extractAttributeKeysFromConditions(o.conditions).forEach((attributeKey) => {
99
101
  if (!attributesUsedInFeatures[attributeKey]) {
@@ -119,12 +121,28 @@ export async function generateSiteSearchIndex(
119
121
  });
120
122
  }
121
123
 
122
- // with environments
123
- if (parsed.environments) {
124
- Object.keys(parsed.environments).forEach((environmentKey) => {
125
- const env = parsed.environments[environmentKey];
124
+ // rules
125
+ if (parsed.rules) {
126
+ if (!Array.isArray(parsed.rules)) {
127
+ // with environments
128
+ Object.keys(parsed.rules).forEach((environmentKey) => {
129
+ const rules = parsed.rules?.[environmentKey];
130
+
131
+ rules.forEach((rule) => {
132
+ if (rule.segments && rule.segments !== "*") {
133
+ extractSegmentKeysFromGroupSegments(rule.segments).forEach((segmentKey) => {
134
+ if (!segmentsUsedInFeatures[segmentKey]) {
135
+ segmentsUsedInFeatures[segmentKey] = new Set();
136
+ }
126
137
 
127
- env.rules.forEach((rule) => {
138
+ segmentsUsedInFeatures[segmentKey].add(entityName);
139
+ });
140
+ }
141
+ });
142
+ });
143
+ } else {
144
+ // no environments
145
+ parsed.rules.forEach((rule) => {
128
146
  if (rule.segments && rule.segments !== "*") {
129
147
  extractSegmentKeysFromGroupSegments(rule.segments).forEach((segmentKey) => {
130
148
  if (!segmentsUsedInFeatures[segmentKey]) {
@@ -135,9 +153,15 @@ export async function generateSiteSearchIndex(
135
153
  });
136
154
  }
137
155
  });
156
+ }
157
+ }
138
158
 
139
- if (env.force) {
140
- env.force.forEach((force) => {
159
+ // force
160
+ if (parsed.force) {
161
+ if (!Array.isArray(parsed.force)) {
162
+ // with environments
163
+ Object.keys(parsed.force).forEach((environmentKey) => {
164
+ parsed.force?.[environmentKey].forEach((force) => {
141
165
  if (force.segments && force.segments !== "*") {
142
166
  extractSegmentKeysFromGroupSegments(force.segments).forEach((segmentKey) => {
143
167
  if (!segmentsUsedInFeatures[segmentKey]) {
@@ -158,47 +182,31 @@ export async function generateSiteSearchIndex(
158
182
  });
159
183
  }
160
184
  });
161
- }
162
- });
163
- }
164
-
165
- // no environments
166
- if (parsed.rules) {
167
- parsed.rules.forEach((rule) => {
168
- if (rule.segments && rule.segments !== "*") {
169
- extractSegmentKeysFromGroupSegments(rule.segments).forEach((segmentKey) => {
170
- if (!segmentsUsedInFeatures[segmentKey]) {
171
- segmentsUsedInFeatures[segmentKey] = new Set();
172
- }
173
-
174
- segmentsUsedInFeatures[segmentKey].add(entityName);
175
- });
176
- }
177
- });
178
- }
179
-
180
- if (parsed.force) {
181
- parsed.force.forEach((force) => {
182
- if (force.segments && force.segments !== "*") {
183
- extractSegmentKeysFromGroupSegments(force.segments).forEach((segmentKey) => {
184
- if (!segmentsUsedInFeatures[segmentKey]) {
185
- segmentsUsedInFeatures[segmentKey] = new Set();
186
- }
185
+ });
186
+ } else {
187
+ // no environments
188
+ parsed.force.forEach((force) => {
189
+ if (force.segments && force.segments !== "*") {
190
+ extractSegmentKeysFromGroupSegments(force.segments).forEach((segmentKey) => {
191
+ if (!segmentsUsedInFeatures[segmentKey]) {
192
+ segmentsUsedInFeatures[segmentKey] = new Set();
193
+ }
187
194
 
188
- segmentsUsedInFeatures[segmentKey].add(entityName);
189
- });
190
- }
195
+ segmentsUsedInFeatures[segmentKey].add(entityName);
196
+ });
197
+ }
191
198
 
192
- if (force.conditions) {
193
- extractAttributeKeysFromConditions(force.conditions).forEach((attributeKey) => {
194
- if (!attributesUsedInFeatures[attributeKey]) {
195
- attributesUsedInFeatures[attributeKey] = new Set();
196
- }
199
+ if (force.conditions) {
200
+ extractAttributeKeysFromConditions(force.conditions).forEach((attributeKey) => {
201
+ if (!attributesUsedInFeatures[attributeKey]) {
202
+ attributesUsedInFeatures[attributeKey] = new Set();
203
+ }
197
204
 
198
- attributesUsedInFeatures[attributeKey].add(entityName);
199
- });
200
- }
201
- });
205
+ attributesUsedInFeatures[attributeKey].add(entityName);
206
+ });
207
+ }
208
+ });
209
+ }
202
210
  }
203
211
 
204
212
  // push
@@ -1,4 +1,4 @@
1
- import { HistoryEntry, LastModified } from "@featurevisor/types";
1
+ import type { HistoryEntry, LastModified } from "@featurevisor/types";
2
2
 
3
3
  export function getLastModifiedFromHistory(
4
4
  fullHistory: HistoryEntry[],
@@ -0,0 +1,149 @@
1
+ import { checkIfObjectsAreEqual, checkIfArraysAreEqual } from "./helpers";
2
+
3
+ describe("helpers", function () {
4
+ describe("checkIfObjectsAreEqual", function () {
5
+ it("should return true for two empty objects", () => {
6
+ expect(checkIfObjectsAreEqual({}, {})).toBe(true);
7
+ });
8
+
9
+ it("should return true for objects with same keys and values", () => {
10
+ expect(checkIfObjectsAreEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true);
11
+ });
12
+
13
+ it("should return false for objects with different keys", () => {
14
+ expect(checkIfObjectsAreEqual({ a: 1 }, { b: 1 })).toBe(false);
15
+ });
16
+
17
+ it("should return false for objects with same keys but different values", () => {
18
+ expect(checkIfObjectsAreEqual({ a: 1 }, { a: 2 })).toBe(false);
19
+ });
20
+
21
+ it("should return false if one object has extra keys", () => {
22
+ expect(checkIfObjectsAreEqual({ a: 1, b: 2 }, { a: 1 })).toBe(false);
23
+ expect(checkIfObjectsAreEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false);
24
+ });
25
+
26
+ it("should return true for nested objects that are deeply equal", () => {
27
+ expect(checkIfObjectsAreEqual({ a: { b: 2 }, c: 3 }, { a: { b: 2 }, c: 3 })).toBe(true);
28
+ });
29
+
30
+ it("should return false for nested objects that are not deeply equal", () => {
31
+ expect(checkIfObjectsAreEqual({ a: { b: 2 }, c: 3 }, { a: { b: 3 }, c: 3 })).toBe(false);
32
+ });
33
+
34
+ // it("should return false if types differ", () => {
35
+ // expect(checkIfObjectsAreEqual({ a: 1 }, null as any)).toBe(false);
36
+ // expect(checkIfObjectsAreEqual(null as any, { a: 1 })).toBe(false);
37
+ // expect(checkIfObjectsAreEqual([], {})).toBe(false);
38
+ // });
39
+
40
+ it("should return true for objects with array values that are equal", () => {
41
+ expect(checkIfObjectsAreEqual({ a: [1, 2, 3] }, { a: [1, 2, 3] })).toBe(true);
42
+ });
43
+
44
+ it("should return false for objects with array values that are not equal", () => {
45
+ expect(checkIfObjectsAreEqual({ a: [1, 2, 3] }, { a: [1, 2, 4] })).toBe(false);
46
+ });
47
+
48
+ it("should return true for objects with different key order", () => {
49
+ expect(checkIfObjectsAreEqual({ a: 1, b: 2 }, { b: 2, a: 1 })).toBe(true);
50
+ });
51
+
52
+ it("should return true for objects with undefined values if both have them", () => {
53
+ expect(checkIfObjectsAreEqual({ a: undefined }, { a: undefined })).toBe(true);
54
+ });
55
+
56
+ it("should return false for objects with one undefined and one missing key", () => {
57
+ expect(checkIfObjectsAreEqual({ a: undefined }, {})).toBe(false);
58
+ });
59
+ });
60
+
61
+ describe("checkIfArraysAreEqual", function () {
62
+ it("should return true for two empty arrays", () => {
63
+ expect(checkIfArraysAreEqual([], [])).toBe(true);
64
+ });
65
+
66
+ it("should return true for arrays with same elements in same order", () => {
67
+ expect(checkIfArraysAreEqual([1, 2, 3], [1, 2, 3])).toBe(true);
68
+ });
69
+
70
+ it("should return false for arrays with same elements in different order", () => {
71
+ expect(checkIfArraysAreEqual([1, 2, 3], [3, 2, 1])).toBe(false);
72
+ });
73
+
74
+ it("should return false for arrays of different lengths", () => {
75
+ expect(checkIfArraysAreEqual([1, 2], [1, 2, 3])).toBe(false);
76
+ expect(checkIfArraysAreEqual([1, 2, 3], [1, 2])).toBe(false);
77
+ });
78
+
79
+ it("should return false for arrays with different elements", () => {
80
+ expect(checkIfArraysAreEqual([1, 2, 3], [1, 2, 4])).toBe(false);
81
+ });
82
+
83
+ it("should return true for nested arrays that are deeply equal", () => {
84
+ expect(checkIfArraysAreEqual([[1, 2], [3]], [[1, 2], [3]])).toBe(true);
85
+ });
86
+
87
+ it("should return false for nested arrays that are not deeply equal", () => {
88
+ expect(checkIfArraysAreEqual([[1, 2], [3]], [[1, 2], [4]])).toBe(false);
89
+ });
90
+
91
+ it("should return true for arrays with objects that are deeply equal", () => {
92
+ expect(checkIfArraysAreEqual([{ a: 1 }, { b: 2 }], [{ a: 1 }, { b: 2 }])).toBe(true);
93
+ });
94
+
95
+ it("should return false for arrays with objects that are not deeply equal", () => {
96
+ expect(checkIfArraysAreEqual([{ a: 1 }, { b: 2 }], [{ a: 1 }, { b: 3 }])).toBe(false);
97
+ });
98
+
99
+ it("should return false if types differ", () => {
100
+ expect(checkIfArraysAreEqual([1, 2, 3], null as any)).toBe(false);
101
+ expect(checkIfArraysAreEqual(null as any, [1, 2, 3])).toBe(false);
102
+ expect(checkIfArraysAreEqual({} as any, [])).toBe(false);
103
+ });
104
+
105
+ it("should return true for arrays with undefined values if both have them in same positions", () => {
106
+ expect(checkIfArraysAreEqual([1, undefined, 3], [1, undefined, 3])).toBe(true);
107
+ });
108
+
109
+ it("should return false for arrays with undefined values in different positions", () => {
110
+ expect(checkIfArraysAreEqual([1, undefined, 3], [1, 3, undefined])).toBe(false);
111
+ });
112
+
113
+ it("should return true for arrays with different reference but same primitive values", () => {
114
+ expect(checkIfArraysAreEqual([1, 2, 3], [1, 2, 3])).toBe(true);
115
+ });
116
+
117
+ it("should return true for arrays with nested objects and arrays that are deeply equal", () => {
118
+ expect(
119
+ checkIfArraysAreEqual([{ a: [1, 2], b: { c: 3 } }], [{ a: [1, 2], b: { c: 3 } }]),
120
+ ).toBe(true);
121
+ });
122
+
123
+ it("should return false for arrays with nested objects and arrays that are not deeply equal", () => {
124
+ expect(
125
+ checkIfArraysAreEqual([{ a: [1, 2], b: { c: 3 } }], [{ a: [1, 2], b: { c: 4 } }]),
126
+ ).toBe(false);
127
+ });
128
+
129
+ it("should return false for arrays with extra undefined at the end", () => {
130
+ expect(checkIfArraysAreEqual([1, 2], [1, 2, undefined])).toBe(false);
131
+ });
132
+
133
+ // it("should return true for arrays with NaN values in same positions", () => {
134
+ // expect(checkIfArraysAreEqual([NaN, 2], [NaN, 2])).toBe(true);
135
+ // });
136
+
137
+ // it("should return false for arrays with NaN values in different positions", () => {
138
+ // expect(checkIfArraysAreEqual([2, NaN], [NaN, 2])).toBe(false);
139
+ // });
140
+
141
+ it("should return true for arrays with null values in same positions", () => {
142
+ expect(checkIfArraysAreEqual([null, 2], [null, 2])).toBe(true);
143
+ });
144
+
145
+ it("should return false for arrays with null values in different positions", () => {
146
+ expect(checkIfArraysAreEqual([2, null], [null, 2])).toBe(false);
147
+ });
148
+ });
149
+ });
@@ -0,0 +1,76 @@
1
+ export function checkIfObjectsAreEqual(obj1: any, obj2: any): boolean {
2
+ if (obj1 === obj2) {
3
+ return true;
4
+ }
5
+
6
+ if (typeof obj1 !== "object" || obj1 === null || typeof obj2 !== "object" || obj2 === null) {
7
+ return false;
8
+ }
9
+
10
+ const keys1 = Object.keys(obj1);
11
+ const keys2 = Object.keys(obj2);
12
+
13
+ if (keys1.length !== keys2.length) {
14
+ return false;
15
+ }
16
+
17
+ for (const key of keys1) {
18
+ if (!keys2.includes(key)) {
19
+ return false;
20
+ }
21
+
22
+ const val1 = obj1[key];
23
+ const val2 = obj2[key];
24
+
25
+ if (Array.isArray(val1) && Array.isArray(val2)) {
26
+ if (!checkIfArraysAreEqual(val1, val2)) {
27
+ return false;
28
+ }
29
+ } else if (typeof val1 === "object" && typeof val2 === "object") {
30
+ if (!checkIfObjectsAreEqual(val1, val2)) {
31
+ return false;
32
+ }
33
+ } else if (val1 !== val2) {
34
+ return false;
35
+ }
36
+ }
37
+ return true;
38
+ }
39
+
40
+ export function checkIfArraysAreEqual(arr1: any[], arr2: any[]): boolean {
41
+ if (arr1 === arr2) {
42
+ return true;
43
+ }
44
+
45
+ if (!Array.isArray(arr1) || !Array.isArray(arr2)) {
46
+ return false;
47
+ }
48
+
49
+ if (arr1.length !== arr2.length) {
50
+ return false;
51
+ }
52
+
53
+ for (let i = 0; i < arr1.length; i++) {
54
+ const val1 = arr1[i];
55
+ const val2 = arr2[i];
56
+
57
+ if (Array.isArray(val1) && Array.isArray(val2)) {
58
+ if (!checkIfArraysAreEqual(val1, val2)) {
59
+ return false;
60
+ }
61
+ } else if (
62
+ typeof val1 === "object" &&
63
+ typeof val2 === "object" &&
64
+ val1 !== null &&
65
+ val2 !== null
66
+ ) {
67
+ if (!checkIfObjectsAreEqual(val1, val2)) {
68
+ return false;
69
+ }
70
+ } else if (val1 !== val2) {
71
+ return false;
72
+ }
73
+ }
74
+
75
+ return true;
76
+ }
@@ -1,3 +1,2 @@
1
1
  export * from "./testProject";
2
2
  export * from "./testFeature";
3
- export * from "./matrix";
@@ -1,4 +1,4 @@
1
- import { TestResult } from "@featurevisor/types";
1
+ import type { TestResult } from "@featurevisor/types";
2
2
 
3
3
  import { CLI_FORMAT_BOLD, CLI_FORMAT_RED } from "./cliFormat";
4
4
  import { prettyDuration } from "./prettyDuration";
@@ -35,16 +35,38 @@ export function printTestResult(testResult: TestResult, relativeTestFilePath, ro
35
35
  return;
36
36
  }
37
37
 
38
+ let section: string = error.type;
39
+
40
+ if (error.type === "flag") {
41
+ section = "expectedToBeEnabled";
42
+ } else if (error.type === "variation") {
43
+ section = "expectedVariation";
44
+ } else if (error.type === "variable") {
45
+ section = "expectedVariables";
46
+ }
47
+
48
+ if (error.details && error.details.childIndex !== undefined) {
49
+ section = `children[${error.details.childIndex}].${section}`;
50
+ }
51
+
38
52
  if (error.type === "variable") {
39
53
  const variableKey = (error.details as any).variableKey;
40
54
 
41
- console.log(CLI_FORMAT_RED, ` => variable key: ${variableKey}`);
55
+ console.log(CLI_FORMAT_RED, ` => ${section}.${variableKey}:`);
42
56
  console.log(CLI_FORMAT_RED, ` => expected: ${error.expected}`);
43
57
  console.log(CLI_FORMAT_RED, ` => received: ${error.actual}`);
44
58
  } else {
59
+ if (error.type === "evaluation") {
60
+ if (error.details && error.details.variableKey) {
61
+ section = `${section}.variables.${error.details.variableKey}.${error.details.evaluationKey}`;
62
+ } else if (error.details && error.details.evaluationType) {
63
+ section = `${section}.${error.details.evaluationType}.${error.details.evaluationKey}`;
64
+ }
65
+ }
66
+
45
67
  console.log(
46
68
  CLI_FORMAT_RED,
47
- ` => ${error.type}: expected "${error.expected}", received "${error.actual}"`,
69
+ ` => ${section}: expected "${error.expected}", received "${error.actual}"`,
48
70
  );
49
71
  }
50
72
  });