@featurevisor/core 2.26.1 → 3.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 (390) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/coverage/clover.xml +4991 -3130
  3. package/coverage/coverage-final.json +63 -39
  4. package/coverage/lcov-report/block-navigation.js +1 -1
  5. package/coverage/lcov-report/index.html +248 -98
  6. package/coverage/lcov-report/sorter.js +21 -7
  7. package/coverage/lcov-report/src/assess-distribution/index.html +116 -0
  8. package/coverage/lcov-report/src/assess-distribution/index.ts.html +754 -0
  9. package/coverage/lcov-report/{list → src/benchmark}/index.html +30 -30
  10. package/coverage/lcov-report/src/benchmark/index.ts.html +793 -0
  11. package/coverage/lcov-report/{builder → src/builder}/allocator.ts.html +38 -38
  12. package/coverage/lcov-report/{builder/buildScopedConditions.ts.html → src/builder/applyContextToConditions.ts.html} +80 -65
  13. package/coverage/lcov-report/{builder/buildScopedDatafile.ts.html → src/builder/applyContextToDatafile.ts.html} +92 -92
  14. package/coverage/lcov-report/{builder/buildScopedSegments.ts.html → src/builder/applyContextToSegments.ts.html} +103 -67
  15. package/coverage/lcov-report/{builder → src/builder}/buildDatafile.ts.html +336 -306
  16. package/coverage/lcov-report/src/builder/buildProject.ts.html +1117 -0
  17. package/coverage/lcov-report/src/builder/buildRuntimeDatafiles.ts.html +256 -0
  18. package/coverage/lcov-report/{builder → src/builder}/getFeatureRanges.ts.html +22 -22
  19. package/coverage/lcov-report/{builder → src/builder}/hashes.ts.html +50 -50
  20. package/coverage/lcov-report/{builder → src/builder}/index.html +114 -84
  21. package/coverage/lcov-report/{tester/cliFormat.ts.html → src/builder/index.ts.html} +17 -29
  22. package/coverage/lcov-report/{builder → src/builder}/mutateVariables.ts.html +55 -55
  23. package/coverage/lcov-report/{builder → src/builder}/mutator.ts.html +118 -118
  24. package/coverage/lcov-report/{builder → src/builder}/revision.ts.html +15 -15
  25. package/coverage/lcov-report/{builder → src/builder}/traffic.ts.html +86 -86
  26. package/coverage/lcov-report/src/cli/cli.ts.html +400 -0
  27. package/coverage/lcov-report/{tester → src/cli}/index.html +41 -41
  28. package/coverage/lcov-report/src/cli/plugins.ts.html +214 -0
  29. package/coverage/lcov-report/{config → src/config}/index.html +31 -31
  30. package/coverage/lcov-report/{config → src/config}/index.ts.html +12 -12
  31. package/coverage/lcov-report/{config → src/config}/projectConfig.ts.html +312 -96
  32. package/coverage/lcov-report/{datasource → src/datasource}/adapter.ts.html +28 -73
  33. package/coverage/lcov-report/{datasource → src/datasource}/datasource.ts.html +191 -104
  34. package/coverage/lcov-report/{datasource → src/datasource}/filesystemAdapter.ts.html +233 -635
  35. package/coverage/lcov-report/{datasource → src/datasource}/index.html +48 -48
  36. package/coverage/lcov-report/{datasource → src/datasource}/index.ts.html +14 -14
  37. package/coverage/lcov-report/src/evaluate/index.html +116 -0
  38. package/coverage/lcov-report/src/evaluate/index.ts.html +814 -0
  39. package/coverage/lcov-report/src/find-duplicate-segments/findDuplicateSegments.ts.html +313 -0
  40. package/coverage/lcov-report/src/find-duplicate-segments/index.html +131 -0
  41. package/coverage/lcov-report/src/find-duplicate-segments/index.ts.html +307 -0
  42. package/coverage/lcov-report/src/find-usage/index.html +116 -0
  43. package/coverage/lcov-report/src/find-usage/index.ts.html +1780 -0
  44. package/coverage/lcov-report/src/generate-code/index.html +131 -0
  45. package/coverage/lcov-report/src/generate-code/index.ts.html +403 -0
  46. package/coverage/lcov-report/{generate-code → src/generate-code}/typescript.ts.html +266 -1361
  47. package/coverage/lcov-report/{generate-code → src}/index.html +38 -23
  48. package/coverage/lcov-report/src/info/index.html +116 -0
  49. package/coverage/lcov-report/src/info/index.ts.html +472 -0
  50. package/coverage/lcov-report/src/init/index.html +116 -0
  51. package/coverage/lcov-report/src/init/index.ts.html +280 -0
  52. package/coverage/lcov-report/{linter → src/linter}/attributeSchema.ts.html +160 -157
  53. package/coverage/lcov-report/{linter → src/linter}/checkCircularDependency.ts.html +25 -25
  54. package/coverage/lcov-report/{linter → src/linter}/checkPercentageExceedingSlot.ts.html +42 -42
  55. package/coverage/lcov-report/{linter → src/linter}/conditionSchema.ts.html +207 -207
  56. package/coverage/lcov-report/{linter → src/linter}/featureSchema.ts.html +462 -471
  57. package/coverage/lcov-report/{linter → src/linter}/groupSchema.ts.html +25 -22
  58. package/coverage/lcov-report/{linter → src/linter}/index.html +129 -99
  59. package/coverage/lcov-report/src/linter/index.ts.html +88 -0
  60. package/coverage/lcov-report/{linter → src/linter}/lintProject.ts.html +740 -332
  61. package/coverage/lcov-report/{linter → src/linter}/mutationNotation.ts.html +130 -130
  62. package/coverage/lcov-report/{linter → src/linter}/printError.ts.html +27 -27
  63. package/coverage/lcov-report/{linter → src/linter}/schema.ts.html +153 -150
  64. package/coverage/lcov-report/{linter → src/linter}/segmentSchema.ts.html +18 -15
  65. package/coverage/lcov-report/src/linter/targetSchema.ts.html +223 -0
  66. package/coverage/lcov-report/{linter → src/linter}/testSchema.ts.html +54 -48
  67. package/coverage/lcov-report/{linter → src/linter}/zodHelpers.ts.html +17 -17
  68. package/coverage/lcov-report/src/list/index.html +131 -0
  69. package/coverage/lcov-report/src/list/index.ts.html +2065 -0
  70. package/coverage/lcov-report/{list → src/list}/matrix.ts.html +43 -58
  71. package/coverage/lcov-report/src/promoter/index.html +116 -0
  72. package/coverage/lcov-report/src/promoter/index.ts.html +3883 -0
  73. package/coverage/lcov-report/src/sets.ts.html +313 -0
  74. package/coverage/lcov-report/src/targeting.ts.html +403 -0
  75. package/coverage/lcov-report/src/tester/cliFormat.ts.html +133 -0
  76. package/coverage/lcov-report/{tester → src/tester}/helpers.ts.html +15 -15
  77. package/coverage/lcov-report/src/tester/index.html +206 -0
  78. package/coverage/lcov-report/src/tester/prettyDuration.ts.html +187 -0
  79. package/coverage/lcov-report/src/tester/printTestResult.ts.html +337 -0
  80. package/coverage/lcov-report/src/tester/testFeature.ts.html +1186 -0
  81. package/coverage/lcov-report/src/tester/testProject.ts.html +1018 -0
  82. package/coverage/lcov-report/src/tester/testSegment.ts.html +316 -0
  83. package/coverage/lcov-report/{utils → src/utils}/extractKeys.ts.html +68 -68
  84. package/coverage/lcov-report/{utils → src/utils}/git.ts.html +44 -68
  85. package/coverage/lcov-report/{utils → src/utils}/index.html +44 -44
  86. package/coverage/lcov-report/{utils → src/utils}/index.ts.html +14 -14
  87. package/coverage/lcov-report/{utils → src/utils}/pretty.ts.html +20 -20
  88. package/coverage/lcov.info +10121 -5723
  89. package/jest.config.js +13 -4
  90. package/lib/assess-distribution/index.d.ts +1 -1
  91. package/lib/assess-distribution/index.js +59 -40
  92. package/lib/assess-distribution/index.js.map +1 -1
  93. package/lib/benchmark/index.d.ts +8 -5
  94. package/lib/benchmark/index.js +82 -65
  95. package/lib/benchmark/index.js.map +1 -1
  96. package/lib/builder/applyContextToConditions.d.ts +5 -0
  97. package/lib/builder/{buildScopedConditions.js → applyContextToConditions.js} +19 -14
  98. package/lib/builder/applyContextToConditions.js.map +1 -0
  99. package/lib/builder/applyContextToDatafile.d.ts +2 -0
  100. package/lib/builder/{buildScopedDatafile.js → applyContextToDatafile.js} +26 -26
  101. package/lib/builder/applyContextToDatafile.js.map +1 -0
  102. package/lib/builder/applyContextToSegments.d.ts +5 -0
  103. package/lib/builder/{buildScopedSegments.js → applyContextToSegments.js} +19 -14
  104. package/lib/builder/applyContextToSegments.js.map +1 -0
  105. package/lib/builder/buildDatafile.d.ts +9 -6
  106. package/lib/builder/buildDatafile.js +86 -51
  107. package/lib/builder/buildDatafile.js.map +1 -1
  108. package/lib/builder/buildProject.d.ts +17 -3
  109. package/lib/builder/buildProject.js +151 -59
  110. package/lib/builder/buildProject.js.map +1 -1
  111. package/lib/builder/buildRuntimeDatafiles.d.ts +12 -0
  112. package/lib/builder/buildRuntimeDatafiles.js +35 -0
  113. package/lib/builder/buildRuntimeDatafiles.js.map +1 -0
  114. package/lib/builder/getFeatureRanges.js +34 -1
  115. package/lib/builder/getFeatureRanges.js.map +1 -1
  116. package/lib/builder/hashes.js +34 -1
  117. package/lib/builder/hashes.js.map +1 -1
  118. package/lib/builder/index.d.ts +0 -1
  119. package/lib/builder/index.js +0 -1
  120. package/lib/builder/index.js.map +1 -1
  121. package/lib/cli/cli.js +0 -4
  122. package/lib/cli/cli.js.map +1 -1
  123. package/lib/cli/plugins.js +7 -2
  124. package/lib/cli/plugins.js.map +1 -1
  125. package/lib/config/projectConfig.d.ts +17 -17
  126. package/lib/config/projectConfig.js +101 -15
  127. package/lib/config/projectConfig.js.map +1 -1
  128. package/lib/datasource/adapter.d.ts +7 -9
  129. package/lib/datasource/adapter.js +0 -7
  130. package/lib/datasource/adapter.js.map +1 -1
  131. package/lib/datasource/datasource.d.ts +15 -5
  132. package/lib/datasource/datasource.js +37 -9
  133. package/lib/datasource/datasource.js.map +1 -1
  134. package/lib/datasource/filesystemAdapter.d.ts +4 -10
  135. package/lib/datasource/filesystemAdapter.js +111 -149
  136. package/lib/datasource/filesystemAdapter.js.map +1 -1
  137. package/lib/evaluate/index.d.ts +1 -7
  138. package/lib/evaluate/index.js +81 -67
  139. package/lib/evaluate/index.js.map +1 -1
  140. package/lib/find-duplicate-segments/findDuplicateSegments.js +34 -1
  141. package/lib/find-duplicate-segments/findDuplicateSegments.js.map +1 -1
  142. package/lib/find-duplicate-segments/index.js +21 -13
  143. package/lib/find-duplicate-segments/index.js.map +1 -1
  144. package/lib/find-usage/index.js +57 -41
  145. package/lib/find-usage/index.js.map +1 -1
  146. package/lib/generate-code/index.d.ts +1 -1
  147. package/lib/generate-code/index.js +64 -21
  148. package/lib/generate-code/index.js.map +1 -1
  149. package/lib/generate-code/typescript.d.ts +1 -1
  150. package/lib/generate-code/typescript.js +59 -333
  151. package/lib/generate-code/typescript.js.map +1 -1
  152. package/lib/index.d.ts +2 -1
  153. package/lib/index.js +2 -1
  154. package/lib/index.js.map +1 -1
  155. package/lib/info/index.js +57 -16
  156. package/lib/info/index.js.map +1 -1
  157. package/lib/init/index.d.ts +2 -2
  158. package/lib/init/index.js +39 -3
  159. package/lib/init/index.js.map +1 -1
  160. package/lib/linter/attributeSchema.d.ts +1 -0
  161. package/lib/linter/attributeSchema.js +1 -0
  162. package/lib/linter/attributeSchema.js.map +1 -1
  163. package/lib/linter/conditionSchema.js +5 -5
  164. package/lib/linter/conditionSchema.js.map +1 -1
  165. package/lib/linter/featureSchema.d.ts +4 -1
  166. package/lib/linter/featureSchema.js +15 -13
  167. package/lib/linter/featureSchema.js.map +1 -1
  168. package/lib/linter/groupSchema.d.ts +1 -0
  169. package/lib/linter/groupSchema.js +1 -0
  170. package/lib/linter/groupSchema.js.map +1 -1
  171. package/lib/linter/lintProject.d.ts +1 -3
  172. package/lib/linter/lintProject.js +198 -68
  173. package/lib/linter/lintProject.js.map +1 -1
  174. package/lib/linter/schema.js +1 -0
  175. package/lib/linter/schema.js.map +1 -1
  176. package/lib/linter/segmentSchema.d.ts +1 -0
  177. package/lib/linter/segmentSchema.js +1 -0
  178. package/lib/linter/segmentSchema.js.map +1 -1
  179. package/lib/linter/targetSchema.d.ts +16 -0
  180. package/lib/linter/targetSchema.js +40 -0
  181. package/lib/linter/targetSchema.js.map +1 -0
  182. package/lib/linter/testSchema.d.ts +4 -3
  183. package/lib/linter/testSchema.js +9 -4
  184. package/lib/linter/testSchema.js.map +1 -1
  185. package/lib/list/index.d.ts +1 -0
  186. package/lib/list/index.js +131 -12
  187. package/lib/list/index.js.map +1 -1
  188. package/lib/list/matrix.js +3 -7
  189. package/lib/list/matrix.js.map +1 -1
  190. package/lib/promoter/index.d.ts +50 -0
  191. package/lib/promoter/index.js +880 -0
  192. package/lib/promoter/index.js.map +1 -0
  193. package/lib/sets.d.ts +11 -0
  194. package/lib/sets.js +81 -0
  195. package/lib/sets.js.map +1 -0
  196. package/lib/targeting.d.ts +14 -0
  197. package/lib/targeting.js +82 -0
  198. package/lib/targeting.js.map +1 -0
  199. package/lib/tester/cliFormat.d.ts +7 -1
  200. package/lib/tester/cliFormat.js +11 -2
  201. package/lib/tester/cliFormat.js.map +1 -1
  202. package/lib/tester/printTestResult.js +2 -2
  203. package/lib/tester/printTestResult.js.map +1 -1
  204. package/lib/tester/testFeature.d.ts +0 -1
  205. package/lib/tester/testFeature.js +8 -25
  206. package/lib/tester/testFeature.js.map +1 -1
  207. package/lib/tester/testProject.d.ts +2 -3
  208. package/lib/tester/testProject.js +93 -86
  209. package/lib/tester/testProject.js.map +1 -1
  210. package/lib/tester/testSegment.d.ts +1 -1
  211. package/lib/tester/testSegment.js +3 -21
  212. package/lib/tester/testSegment.js.map +1 -1
  213. package/lib/utils/git.js +45 -19
  214. package/lib/utils/git.js.map +1 -1
  215. package/package.json +12 -12
  216. package/src/assess-distribution/index.ts +72 -52
  217. package/src/benchmark/index.ts +124 -91
  218. package/src/builder/{buildScopedConditions.spec.ts → applyContextToConditions.spec.ts} +160 -142
  219. package/src/builder/{buildScopedConditions.ts → applyContextToConditions.ts} +19 -14
  220. package/src/builder/{buildScopedDatafile.spec.ts → applyContextToDatafile.spec.ts} +141 -81
  221. package/src/builder/{buildScopedDatafile.ts → applyContextToDatafile.ts} +33 -33
  222. package/src/builder/{buildScopedSegments.spec.ts → applyContextToSegments.spec.ts} +171 -151
  223. package/src/builder/{buildScopedSegments.ts → applyContextToSegments.ts} +26 -14
  224. package/src/builder/buildDatafile.spec.ts +192 -14
  225. package/src/builder/buildDatafile.ts +74 -64
  226. package/src/builder/buildProject.ts +171 -86
  227. package/src/builder/buildRuntimeDatafiles.ts +57 -0
  228. package/src/builder/index.ts +0 -1
  229. package/src/cli/cli.ts +0 -5
  230. package/src/cli/plugins.ts +7 -2
  231. package/src/config/projectConfig.spec.ts +171 -8
  232. package/src/config/projectConfig.ts +100 -28
  233. package/src/datasource/adapter.ts +8 -23
  234. package/src/datasource/datasource.ts +50 -21
  235. package/src/datasource/filesystemAdapter.spec.ts +133 -74
  236. package/src/datasource/filesystemAdapter.ts +93 -227
  237. package/src/evaluate/index.ts +109 -97
  238. package/src/find-duplicate-segments/index.ts +34 -16
  239. package/src/find-usage/index.ts +89 -44
  240. package/src/generate-code/index.ts +36 -24
  241. package/src/generate-code/typescript.spec.ts +106 -0
  242. package/src/generate-code/typescript.ts +33 -398
  243. package/src/index.ts +2 -1
  244. package/src/info/index.ts +72 -16
  245. package/src/init/index.ts +1 -1
  246. package/src/linter/attributeSchema.ts +1 -0
  247. package/src/linter/conditionSchema.spec.ts +23 -3
  248. package/src/linter/conditionSchema.ts +5 -5
  249. package/src/linter/featureSchema.spec.ts +107 -18
  250. package/src/linter/featureSchema.ts +25 -28
  251. package/src/linter/groupSchema.ts +1 -0
  252. package/src/linter/lintProject.spec.ts +131 -92
  253. package/src/linter/lintProject.ts +233 -97
  254. package/src/linter/schema.ts +1 -0
  255. package/src/linter/segmentSchema.spec.ts +5 -3
  256. package/src/linter/segmentSchema.ts +1 -0
  257. package/src/linter/targetSchema.spec.ts +159 -0
  258. package/src/linter/targetSchema.ts +46 -0
  259. package/src/linter/testSchema.spec.ts +70 -5
  260. package/src/linter/testSchema.ts +12 -10
  261. package/src/list/index.spec.ts +135 -0
  262. package/src/list/index.ts +188 -12
  263. package/src/list/matrix.ts +3 -8
  264. package/src/promoter/index.spec.ts +617 -0
  265. package/src/promoter/index.ts +1266 -0
  266. package/src/sets.ts +76 -0
  267. package/src/targeting.spec.ts +74 -0
  268. package/src/targeting.ts +106 -0
  269. package/src/tester/cliFormat.ts +11 -1
  270. package/src/tester/printTestResult.ts +12 -3
  271. package/src/tester/testFeature.ts +10 -36
  272. package/src/tester/testProject.spec.ts +50 -0
  273. package/src/tester/testProject.ts +89 -140
  274. package/src/tester/testSegment.ts +5 -22
  275. package/src/utils/git.ts +13 -21
  276. package/tsconfig.cjs.json +5 -1
  277. package/tsconfig.typecheck.json +9 -0
  278. package/coverage/lcov-report/builder/convertToV1.ts.html +0 -583
  279. package/lib/builder/allocator.spec.d.ts +0 -1
  280. package/lib/builder/allocator.spec.js +0 -85
  281. package/lib/builder/allocator.spec.js.map +0 -1
  282. package/lib/builder/buildDatafile.spec.d.ts +0 -1
  283. package/lib/builder/buildDatafile.spec.js +0 -233
  284. package/lib/builder/buildDatafile.spec.js.map +0 -1
  285. package/lib/builder/buildScopedConditions.d.ts +0 -5
  286. package/lib/builder/buildScopedConditions.js.map +0 -1
  287. package/lib/builder/buildScopedConditions.spec.d.ts +0 -1
  288. package/lib/builder/buildScopedConditions.spec.js +0 -2167
  289. package/lib/builder/buildScopedConditions.spec.js.map +0 -1
  290. package/lib/builder/buildScopedDatafile.d.ts +0 -2
  291. package/lib/builder/buildScopedDatafile.js.map +0 -1
  292. package/lib/builder/buildScopedDatafile.spec.d.ts +0 -1
  293. package/lib/builder/buildScopedDatafile.spec.js +0 -2277
  294. package/lib/builder/buildScopedDatafile.spec.js.map +0 -1
  295. package/lib/builder/buildScopedSegments.d.ts +0 -5
  296. package/lib/builder/buildScopedSegments.js.map +0 -1
  297. package/lib/builder/buildScopedSegments.spec.d.ts +0 -1
  298. package/lib/builder/buildScopedSegments.spec.js +0 -1062
  299. package/lib/builder/buildScopedSegments.spec.js.map +0 -1
  300. package/lib/builder/convertToV1.d.ts +0 -10
  301. package/lib/builder/convertToV1.js +0 -119
  302. package/lib/builder/convertToV1.js.map +0 -1
  303. package/lib/builder/mutateVariables.spec.d.ts +0 -1
  304. package/lib/builder/mutateVariables.spec.js +0 -1074
  305. package/lib/builder/mutateVariables.spec.js.map +0 -1
  306. package/lib/builder/mutator.spec.d.ts +0 -1
  307. package/lib/builder/mutator.spec.js +0 -368
  308. package/lib/builder/mutator.spec.js.map +0 -1
  309. package/lib/builder/revision.spec.d.ts +0 -1
  310. package/lib/builder/revision.spec.js +0 -21
  311. package/lib/builder/revision.spec.js.map +0 -1
  312. package/lib/builder/traffic.spec.d.ts +0 -1
  313. package/lib/builder/traffic.spec.js +0 -872
  314. package/lib/builder/traffic.spec.js.map +0 -1
  315. package/lib/config/projectConfig.spec.d.ts +0 -1
  316. package/lib/config/projectConfig.spec.js +0 -24
  317. package/lib/config/projectConfig.spec.js.map +0 -1
  318. package/lib/datasource/filesystemAdapter.spec.d.ts +0 -1
  319. package/lib/datasource/filesystemAdapter.spec.js +0 -88
  320. package/lib/datasource/filesystemAdapter.spec.js.map +0 -1
  321. package/lib/generate-code/typescript.spec.d.ts +0 -1
  322. package/lib/generate-code/typescript.spec.js +0 -78
  323. package/lib/generate-code/typescript.spec.js.map +0 -1
  324. package/lib/linter/attributeSchema.spec.d.ts +0 -1
  325. package/lib/linter/attributeSchema.spec.js +0 -266
  326. package/lib/linter/attributeSchema.spec.js.map +0 -1
  327. package/lib/linter/conditionSchema.spec.d.ts +0 -1
  328. package/lib/linter/conditionSchema.spec.js +0 -380
  329. package/lib/linter/conditionSchema.spec.js.map +0 -1
  330. package/lib/linter/featureSchema.spec.d.ts +0 -1
  331. package/lib/linter/featureSchema.spec.js +0 -1552
  332. package/lib/linter/featureSchema.spec.js.map +0 -1
  333. package/lib/linter/lintProject.spec.d.ts +0 -1
  334. package/lib/linter/lintProject.spec.js +0 -211
  335. package/lib/linter/lintProject.spec.js.map +0 -1
  336. package/lib/linter/mutationNotation.spec.d.ts +0 -1
  337. package/lib/linter/mutationNotation.spec.js +0 -598
  338. package/lib/linter/mutationNotation.spec.js.map +0 -1
  339. package/lib/linter/schema.spec.d.ts +0 -1
  340. package/lib/linter/schema.spec.js +0 -535
  341. package/lib/linter/schema.spec.js.map +0 -1
  342. package/lib/linter/segmentSchema.spec.d.ts +0 -1
  343. package/lib/linter/segmentSchema.spec.js +0 -303
  344. package/lib/linter/segmentSchema.spec.js.map +0 -1
  345. package/lib/linter/testSchema.spec.d.ts +0 -1
  346. package/lib/linter/testSchema.spec.js +0 -162
  347. package/lib/linter/testSchema.spec.js.map +0 -1
  348. package/lib/list/matrix.spec.d.ts +0 -1
  349. package/lib/list/matrix.spec.js +0 -42
  350. package/lib/list/matrix.spec.js.map +0 -1
  351. package/lib/site/exportSite.d.ts +0 -2
  352. package/lib/site/exportSite.js +0 -30
  353. package/lib/site/exportSite.js.map +0 -1
  354. package/lib/site/generateHistory.d.ts +0 -3
  355. package/lib/site/generateHistory.js +0 -31
  356. package/lib/site/generateHistory.js.map +0 -1
  357. package/lib/site/generateSiteSearchIndex.d.ts +0 -4
  358. package/lib/site/generateSiteSearchIndex.js +0 -195
  359. package/lib/site/generateSiteSearchIndex.js.map +0 -1
  360. package/lib/site/getLastModifiedFromHistory.d.ts +0 -2
  361. package/lib/site/getLastModifiedFromHistory.js +0 -18
  362. package/lib/site/getLastModifiedFromHistory.js.map +0 -1
  363. package/lib/site/getOwnerAndRepoFromUrl.d.ts +0 -4
  364. package/lib/site/getOwnerAndRepoFromUrl.js +0 -20
  365. package/lib/site/getOwnerAndRepoFromUrl.js.map +0 -1
  366. package/lib/site/getRelativePaths.d.ts +0 -6
  367. package/lib/site/getRelativePaths.js +0 -15
  368. package/lib/site/getRelativePaths.js.map +0 -1
  369. package/lib/site/getRepoDetails.d.ts +0 -8
  370. package/lib/site/getRepoDetails.js +0 -48
  371. package/lib/site/getRepoDetails.js.map +0 -1
  372. package/lib/site/index.d.ts +0 -2
  373. package/lib/site/index.js +0 -43
  374. package/lib/site/index.js.map +0 -1
  375. package/lib/site/serveSite.d.ts +0 -2
  376. package/lib/site/serveSite.js +0 -54
  377. package/lib/site/serveSite.js.map +0 -1
  378. package/lib/tester/helpers.spec.d.ts +0 -1
  379. package/lib/tester/helpers.spec.js +0 -115
  380. package/lib/tester/helpers.spec.js.map +0 -1
  381. package/src/builder/convertToV1.ts +0 -166
  382. package/src/site/exportSite.ts +0 -42
  383. package/src/site/generateHistory.ts +0 -40
  384. package/src/site/generateSiteSearchIndex.ts +0 -258
  385. package/src/site/getLastModifiedFromHistory.ts +0 -21
  386. package/src/site/getOwnerAndRepoFromUrl.ts +0 -17
  387. package/src/site/getRelativePaths.ts +0 -24
  388. package/src/site/getRepoDetails.ts +0 -62
  389. package/src/site/index.ts +0 -46
  390. package/src/site/serveSite.ts +0 -58
@@ -1,64 +1,62 @@
1
1
  import * as path from "path";
2
2
 
3
- import type { BucketBy, Context, Tag } from "@featurevisor/types";
3
+ import type { BucketBy } from "@featurevisor/types";
4
4
  import { Parser, parsers } from "@featurevisor/parsers";
5
5
 
6
6
  import { FilesystemAdapter } from "../datasource/filesystemAdapter";
7
7
  import type { Plugin } from "../cli";
8
- import type { BuildTags } from "../builder/buildDatafile";
8
+ import { CLI_COLOR_CYAN, CLI_FORMAT_BOLD, colorize } from "../tester/cliFormat";
9
9
 
10
10
  export const FEATURES_DIRECTORY_NAME = "features";
11
11
  export const SEGMENTS_DIRECTORY_NAME = "segments";
12
12
  export const ATTRIBUTES_DIRECTORY_NAME = "attributes";
13
13
  export const GROUPS_DIRECTORY_NAME = "groups";
14
14
  export const SCHEMAS_DIRECTORY_NAME = "schemas";
15
+ export const TARGETS_DIRECTORY_NAME = "targets";
15
16
  export const TESTS_DIRECTORY_NAME = "tests";
16
17
  export const STATE_DIRECTORY_NAME = ".featurevisor";
17
18
  export const DATAFILES_DIRECTORY_NAME = "datafiles";
18
19
  export const DATAFILE_NAME_PATTERN = "featurevisor-%s.json";
19
20
  export const REVISION_FILE_NAME = "REVISION";
20
- export const SITE_EXPORT_DIRECTORY_NAME = "out";
21
- export const ENVIRONMENTS_DIRECTORY_NAME = "environments";
21
+ export const CATALOG_DIRECTORY_NAME = "catalog";
22
+ export const SETS_DIRECTORY_NAME = "sets";
22
23
 
23
24
  export const CONFIG_MODULE_NAME = "featurevisor.config.js";
24
25
  export const ROOT_DIR_PLACEHOLDER = "<rootDir>";
25
26
 
26
- export const DEFAULT_ENVIRONMENTS = ["staging", "production"];
27
+ export const DEFAULT_NAMESPACE_CHARACTER = ".";
27
28
  export const DEFAULT_TAGS = ["all"];
28
29
  export const DEFAULT_BUCKET_BY_ATTRIBUTE = "userId";
30
+ export const DEFAULT_SETS = false;
29
31
 
30
32
  export const DEFAULT_PRETTY_STATE = true;
31
33
  export const DEFAULT_PRETTY_DATAFILE = false;
32
34
 
33
35
  export const DEFAULT_PARSER: Parser = "yml";
34
36
 
35
- export const SCHEMA_VERSION = "2"; // default schema version
36
-
37
- export interface Scope {
38
- name: string;
39
- context: Context;
40
- tag?: Tag;
41
- tags?: BuildTags;
42
- }
43
-
44
37
  export interface ProjectConfig {
38
+ promotionFlows?: Array<{
39
+ from: string;
40
+ to: string;
41
+ }>;
42
+ namespaceCharacter: string;
45
43
  featuresDirectoryPath: string;
46
44
  segmentsDirectoryPath: string;
47
45
  attributesDirectoryPath: string;
48
46
  groupsDirectoryPath: string;
49
47
  schemasDirectoryPath: string;
48
+ targetsDirectoryPath: string;
50
49
  testsDirectoryPath: string;
51
50
  stateDirectoryPath: string;
52
51
  datafilesDirectoryPath: string;
53
52
  datafileNamePattern: string;
54
53
  revisionFileName: string;
55
- siteExportDirectoryPath: string;
56
- environmentsDirectoryPath: string;
54
+ catalogDirectoryPath: string;
55
+ setsDirectoryPath: string;
57
56
 
58
- environments: string[] | false;
59
- splitByEnvironment: boolean;
57
+ environments?: string[];
58
+ sets: boolean;
60
59
  tags: string[];
61
- scopes?: Scope[];
62
60
 
63
61
  adapter: any; // @NOTE: type this properly later
64
62
  plugins: Plugin[];
@@ -80,9 +78,11 @@ export interface ProjectConfig {
80
78
  // rootDirectoryPath: path to the root directory of the project (without ending with a slash)
81
79
  export function getProjectConfig(rootDirectoryPath: string): ProjectConfig {
82
80
  const baseConfig: ProjectConfig = {
83
- environments: DEFAULT_ENVIRONMENTS,
81
+ environments: undefined,
82
+ sets: DEFAULT_SETS,
83
+ promotionFlows: undefined,
84
+ namespaceCharacter: DEFAULT_NAMESPACE_CHARACTER,
84
85
  tags: DEFAULT_TAGS,
85
- scopes: [],
86
86
  defaultBucketBy: "userId",
87
87
 
88
88
  parser: DEFAULT_PARSER,
@@ -94,21 +94,21 @@ export function getProjectConfig(rootDirectoryPath: string): ProjectConfig {
94
94
  adapter: FilesystemAdapter,
95
95
 
96
96
  featuresDirectoryPath: path.join(rootDirectoryPath, FEATURES_DIRECTORY_NAME),
97
- environmentsDirectoryPath: path.join(rootDirectoryPath, ENVIRONMENTS_DIRECTORY_NAME),
97
+ setsDirectoryPath: path.join(rootDirectoryPath, SETS_DIRECTORY_NAME),
98
98
  segmentsDirectoryPath: path.join(rootDirectoryPath, SEGMENTS_DIRECTORY_NAME),
99
99
  attributesDirectoryPath: path.join(rootDirectoryPath, ATTRIBUTES_DIRECTORY_NAME),
100
100
  groupsDirectoryPath: path.join(rootDirectoryPath, GROUPS_DIRECTORY_NAME),
101
101
  schemasDirectoryPath: path.join(rootDirectoryPath, SCHEMAS_DIRECTORY_NAME),
102
+ targetsDirectoryPath: path.join(rootDirectoryPath, TARGETS_DIRECTORY_NAME),
102
103
  testsDirectoryPath: path.join(rootDirectoryPath, TESTS_DIRECTORY_NAME),
103
104
  stateDirectoryPath: path.join(rootDirectoryPath, STATE_DIRECTORY_NAME),
104
105
  datafilesDirectoryPath: path.join(rootDirectoryPath, DATAFILES_DIRECTORY_NAME),
105
106
  datafileNamePattern: DATAFILE_NAME_PATTERN,
106
107
  revisionFileName: REVISION_FILE_NAME,
107
- siteExportDirectoryPath: path.join(rootDirectoryPath, SITE_EXPORT_DIRECTORY_NAME),
108
+ catalogDirectoryPath: path.join(rootDirectoryPath, CATALOG_DIRECTORY_NAME),
108
109
 
109
110
  enforceCatchAllRule: false,
110
111
  plugins: [],
111
- splitByEnvironment: false,
112
112
 
113
113
  maxVariableStringLength: undefined,
114
114
  maxVariableArrayStringifiedLength: undefined,
@@ -141,15 +141,83 @@ export function getProjectConfig(rootDirectoryPath: string): ProjectConfig {
141
141
  finalConfig.parser = parsers[finalConfig.parser];
142
142
  }
143
143
 
144
- if (finalConfig.splitByEnvironment && finalConfig.environments === false) {
144
+ if (typeof finalConfig.sets !== "boolean") {
145
+ throw new Error(`Invalid sets: ${finalConfig.sets}. It must be a boolean.`);
146
+ }
147
+
148
+ if (typeof finalConfig.environments !== "undefined") {
149
+ if (!Array.isArray(finalConfig.environments)) {
150
+ throw new Error(
151
+ `Invalid environments: ${finalConfig.environments}. It must be an array of strings when defined.`,
152
+ );
153
+ }
154
+
155
+ finalConfig.environments.forEach((environment: unknown, index: number) => {
156
+ if (typeof environment !== "string") {
157
+ throw new Error(`Invalid environments[${index}]: ${environment}. It must be a string.`);
158
+ }
159
+ });
160
+ }
161
+
162
+ if (
163
+ typeof finalConfig.namespaceCharacter !== "string" ||
164
+ finalConfig.namespaceCharacter.length === 0
165
+ ) {
145
166
  throw new Error(
146
- "Invalid configuration: splitByEnvironment=true requires environments to be an array",
167
+ `Invalid namespaceCharacter: ${finalConfig.namespaceCharacter}. It must be a non-empty string.`,
147
168
  );
148
169
  }
149
170
 
171
+ if (typeof finalConfig.promotionFlows !== "undefined") {
172
+ if (!Array.isArray(finalConfig.promotionFlows)) {
173
+ throw new Error(
174
+ `Invalid promotionFlows: ${finalConfig.promotionFlows}. It must be an array.`,
175
+ );
176
+ }
177
+
178
+ finalConfig.promotionFlows.forEach((entry: any, index: number) => {
179
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
180
+ throw new Error(
181
+ `Invalid promotionFlows[${index}]: ${entry}. Each entry must be an object with exactly "from" and "to" string fields.`,
182
+ );
183
+ }
184
+
185
+ const keys = Object.keys(entry).sort();
186
+
187
+ if (keys.length !== 2 || keys[0] !== "from" || keys[1] !== "to") {
188
+ throw new Error(
189
+ `Invalid promotionFlows[${index}]: ${JSON.stringify(entry)}. Each entry must contain exactly "from" and "to".`,
190
+ );
191
+ }
192
+
193
+ if (typeof entry.from !== "string" || typeof entry.to !== "string") {
194
+ throw new Error(
195
+ `Invalid promotionFlows[${index}]: ${JSON.stringify(entry)}. "from" and "to" must be strings.`,
196
+ );
197
+ }
198
+ });
199
+ }
200
+
150
201
  return finalConfig as ProjectConfig;
151
202
  }
152
203
 
204
+ export function getProjectConfigForSet(projectConfig: ProjectConfig, set: string): ProjectConfig {
205
+ const setRootDirectoryPath = path.join(projectConfig.setsDirectoryPath, set);
206
+
207
+ return {
208
+ ...projectConfig,
209
+ featuresDirectoryPath: path.join(setRootDirectoryPath, FEATURES_DIRECTORY_NAME),
210
+ segmentsDirectoryPath: path.join(setRootDirectoryPath, SEGMENTS_DIRECTORY_NAME),
211
+ attributesDirectoryPath: path.join(setRootDirectoryPath, ATTRIBUTES_DIRECTORY_NAME),
212
+ groupsDirectoryPath: path.join(setRootDirectoryPath, GROUPS_DIRECTORY_NAME),
213
+ schemasDirectoryPath: path.join(setRootDirectoryPath, SCHEMAS_DIRECTORY_NAME),
214
+ targetsDirectoryPath: path.join(setRootDirectoryPath, TARGETS_DIRECTORY_NAME),
215
+ testsDirectoryPath: path.join(setRootDirectoryPath, TESTS_DIRECTORY_NAME),
216
+ stateDirectoryPath: path.join(projectConfig.stateDirectoryPath, SETS_DIRECTORY_NAME, set),
217
+ datafilesDirectoryPath: path.join(projectConfig.datafilesDirectoryPath, set),
218
+ };
219
+ }
220
+
153
221
  export interface ShowProjectConfigOptions {
154
222
  json?: boolean;
155
223
  pretty?: boolean;
@@ -167,7 +235,9 @@ export function showProjectConfig(
167
235
  return;
168
236
  }
169
237
 
170
- console.log("\nProject configuration:\n");
238
+ console.log("");
239
+ console.log(CLI_FORMAT_BOLD, "Project configuration");
240
+ console.log("");
171
241
 
172
242
  const keys = Object.keys(projectConfig);
173
243
  const longestKeyLength = keys.reduce((acc, key) => (key.length > acc ? key.length : acc), 0);
@@ -178,7 +248,9 @@ export function showProjectConfig(
178
248
  continue;
179
249
  }
180
250
 
181
- console.log(` - ${key.padEnd(longestKeyLength, " ")}: ${projectConfig[key]}`);
251
+ console.log(
252
+ ` ${colorize(key.padEnd(longestKeyLength, " "), CLI_COLOR_CYAN)}: ${projectConfig[key]}`,
253
+ );
182
254
  }
183
255
  }
184
256
 
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
1
  import type {
3
2
  DatafileContent,
4
3
  EnvironmentKey,
@@ -9,23 +8,21 @@ import type {
9
8
  EntityType,
10
9
  } from "@featurevisor/types";
11
10
 
12
- import type { Scope } from "../config";
13
-
14
11
  export interface DatafileOptions {
15
12
  environment: EnvironmentKey | false;
16
- tag?: string;
17
- scope?: Scope;
13
+ target?: string;
18
14
  datafilesDir?: string;
19
15
  }
20
16
 
21
- export type FeatureEnvironmentProperty = "rules" | "force" | "expose";
22
-
23
- export interface FeatureSourcePaths {
24
- baseFilePath: string;
25
- environmentFilePaths: Record<string, string>;
17
+ export interface DatafileFile {
18
+ path: string;
19
+ size: number;
20
+ gzipSize: number;
26
21
  }
27
22
 
28
23
  export abstract class Adapter {
24
+ abstract listSets(): Promise<string[]>;
25
+
29
26
  // entities
30
27
  abstract listEntities(entityType: EntityType): Promise<string[]>;
31
28
  abstract entityExists(entityType: EntityType, entityKey: string): Promise<boolean>;
@@ -41,6 +38,7 @@ export abstract class Adapter {
41
38
  ): Promise<void>;
42
39
 
43
40
  // datafile
41
+ listDatafiles?(): Promise<DatafileFile[]>;
44
42
  abstract readDatafile(options: DatafileOptions): Promise<DatafileContent>;
45
43
  abstract writeDatafile(datafileContent: DatafileContent, options: DatafileOptions): Promise<void>;
46
44
 
@@ -55,17 +53,4 @@ export abstract class Adapter {
55
53
  entityType?: EntityType,
56
54
  entityKey?: string,
57
55
  ): Promise<Commit>;
58
-
59
- // feature source helpers (used by lint/history attribution)
60
- async getFeatureSourcePaths(_featureKey: string): Promise<FeatureSourcePaths | undefined> {
61
- return undefined;
62
- }
63
-
64
- async getFeaturePropertySourcePath(
65
- _featureKey: string,
66
- _property: FeatureEnvironmentProperty,
67
- _environment?: string,
68
- ): Promise<string | undefined> {
69
- return undefined;
70
- }
71
56
  }
@@ -13,29 +13,45 @@ import type {
13
13
  EntityType,
14
14
  SchemaKey,
15
15
  Schema,
16
+ Target,
17
+ TargetKey,
16
18
  } from "@featurevisor/types";
17
19
 
18
- import { ProjectConfig } from "../config";
20
+ import { getProjectConfigForSet, ProjectConfig } from "../config";
19
21
  import type { CustomParser } from "@featurevisor/parsers";
20
22
 
21
- import {
22
- Adapter,
23
- DatafileOptions,
24
- FeatureEnvironmentProperty,
25
- FeatureSourcePaths,
26
- } from "./adapter";
23
+ import { Adapter, DatafileFile, DatafileOptions } from "./adapter";
27
24
 
28
25
  export class Datasource {
29
26
  private adapter: Adapter;
27
+ private rootConfig: ProjectConfig;
30
28
 
31
29
  constructor(
32
30
  private config: ProjectConfig,
33
31
  private rootDirectoryPath?: string,
32
+ private set?: string,
34
33
  ) {
35
- this.adapter = new config.adapter(config, rootDirectoryPath);
34
+ this.rootConfig = config;
35
+ this.config = set ? getProjectConfigForSet(this.rootConfig, set) : config;
36
+ this.adapter = new this.config.adapter(this.config, rootDirectoryPath);
37
+ }
38
+
39
+ getConfig() {
40
+ return this.config;
41
+ }
42
+
43
+ getSet() {
44
+ return this.set;
45
+ }
46
+
47
+ forSet(set: string) {
48
+ return new Datasource(this.rootConfig, this.rootDirectoryPath, set);
49
+ }
50
+
51
+ listSets() {
52
+ return this.adapter.listSets();
36
53
  }
37
54
 
38
- // @NOTE: only site generator needs it, find a way to get it out of here later
39
55
  getExtension() {
40
56
  return (this.config.parser as CustomParser).extension;
41
57
  }
@@ -65,6 +81,10 @@ export class Datasource {
65
81
  /**
66
82
  * Datafile
67
83
  */
84
+ listDatafiles(): Promise<DatafileFile[]> {
85
+ return this.adapter.listDatafiles ? this.adapter.listDatafiles() : Promise.resolve([]);
86
+ }
87
+
68
88
  readDatafile(options: DatafileOptions) {
69
89
  return this.adapter.readDatafile(options);
70
90
  }
@@ -98,18 +118,6 @@ export class Datasource {
98
118
  return this.adapter.deleteEntity("feature", featureKey);
99
119
  }
100
120
 
101
- getFeatureSourcePaths(featureKey: FeatureKey): Promise<FeatureSourcePaths | undefined> {
102
- return this.adapter.getFeatureSourcePaths(featureKey);
103
- }
104
-
105
- getFeaturePropertySourcePath(
106
- featureKey: FeatureKey,
107
- property: FeatureEnvironmentProperty,
108
- environment?: string,
109
- ): Promise<string | undefined> {
110
- return this.adapter.getFeaturePropertySourcePath(featureKey, property, environment);
111
- }
112
-
113
121
  async getRequiredFeaturesChain(
114
122
  featureKey: FeatureKey,
115
123
  chain = new Set<FeatureKey>(),
@@ -244,6 +252,27 @@ export class Datasource {
244
252
  return this.adapter.deleteEntity("schema", schemaKey);
245
253
  }
246
254
 
255
+ // targets
256
+ listTargets() {
257
+ return this.adapter.listEntities("target");
258
+ }
259
+
260
+ targetExists(targetKey: TargetKey) {
261
+ return this.adapter.entityExists("target", targetKey);
262
+ }
263
+
264
+ readTarget(targetKey: TargetKey) {
265
+ return this.adapter.readEntity<Target>("target", targetKey);
266
+ }
267
+
268
+ writeTarget(targetKey: TargetKey, target: Target) {
269
+ return this.adapter.writeEntity<Target>("target", targetKey, target);
270
+ }
271
+
272
+ deleteTarget(targetKey: TargetKey) {
273
+ return this.adapter.deleteEntity("target", targetKey);
274
+ }
275
+
247
276
  // tests
248
277
  listTests() {
249
278
  return this.adapter.listEntities("test");
@@ -4,100 +4,131 @@ import * as path from "path";
4
4
 
5
5
  import { getProjectConfig } from "../config";
6
6
  import { Datasource } from "./datasource";
7
+ import { getExistingStateFilePath } from "./filesystemAdapter";
7
8
 
8
9
  function writeFile(filePath: string, content: string) {
9
10
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
10
11
  fs.writeFileSync(filePath, content, "utf8");
11
12
  }
12
13
 
13
- function createSplitProject(configBody: string) {
14
+ function createProject(configBody: string) {
14
15
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "featurevisor-fs-adapter-"));
15
16
  writeFile(path.join(root, "featurevisor.config.js"), configBody);
16
17
 
17
18
  return root;
18
19
  }
19
20
 
20
- describe("core: filesystemAdapter (splitByEnvironment)", () => {
21
- it("merges environment files into readFeature output", async () => {
22
- const root = createSplitProject(
23
- "module.exports = { environments: ['staging', 'production'], splitByEnvironment: true };",
24
- );
21
+ describe("core: filesystemAdapter", () => {
22
+ it("lists generated datafiles as sorted relative paths", async () => {
23
+ const root = createProject("module.exports = {};");
24
+ writeFile(path.join(root, "datafiles", "production", "featurevisor-all.json"), "{}");
25
+ writeFile(path.join(root, "datafiles", "staging", "nested", "featurevisor-web.json"), "abc");
26
+ writeFile(path.join(root, "datafiles", "staging", "featurevisor-mobile.txt"), "abcd");
27
+ writeFile(path.join(root, "datafiles", "REVISION"), "1");
28
+ writeFile(path.join(root, "datafiles", ".DS_Store"), "");
29
+
30
+ const datasource = new Datasource(getProjectConfig(root), root);
31
+
32
+ await expect(datasource.listDatafiles()).resolves.toMatchObject([
33
+ { path: "production/featurevisor-all.json", size: 2, gzipSize: expect.any(Number) },
34
+ { path: "staging/featurevisor-mobile.txt", size: 4, gzipSize: expect.any(Number) },
35
+ { path: "staging/nested/featurevisor-web.json", size: 3, gzipSize: expect.any(Number) },
36
+ ]);
37
+ });
38
+
39
+ it("lists nested entity paths with dot namespace character by default", async () => {
40
+ const root = createProject("module.exports = {};");
25
41
 
26
42
  writeFile(
27
- path.join(root, "features", "checkout.yml"),
43
+ path.join(root, "features", "checkout", "page.yml"),
28
44
  [
29
- "key: checkout",
30
- "description: Checkout feature",
45
+ "description: Checkout page",
31
46
  "tags:",
32
47
  " - all",
33
48
  "bucketBy: userId",
49
+ "rules:",
50
+ " staging:",
51
+ " - key: everyone",
52
+ " segments: '*'",
53
+ " percentage: 100",
54
+ " production:",
55
+ " - key: everyone",
56
+ " segments: '*'",
57
+ " percentage: 100",
34
58
  ].join("\n"),
35
59
  );
36
-
37
- writeFile(
38
- path.join(root, "environments", "staging", "checkout.yml"),
39
- ["rules:", " - key: everyone", " segments: '*'", " percentage: 100"].join("\n"),
40
- );
41
-
42
60
  writeFile(
43
- path.join(root, "environments", "production", "checkout.yml"),
61
+ path.join(root, "segments", "countries", "germany.yml"),
44
62
  [
45
- "rules:",
46
- " - key: everyone",
47
- " segments: '*'",
48
- " percentage: 0",
49
- "expose: false",
63
+ "description: Germany",
64
+ "conditions:",
65
+ " - attribute: country",
66
+ " operator: equals",
67
+ " value: de",
50
68
  ].join("\n"),
51
69
  );
52
70
 
53
71
  const config = getProjectConfig(root);
54
72
  const datasource = new Datasource(config, root);
55
- const feature = await datasource.readFeature("checkout");
56
- const rulesByEnvironment = feature.rules as Record<string, any[]>;
57
- const exposeByEnvironment = feature.expose as Record<string, unknown>;
58
73
 
59
- expect(rulesByEnvironment?.staging?.[0]?.percentage).toBe(100);
60
- expect(rulesByEnvironment?.production?.[0]?.percentage).toBe(0);
61
- expect(exposeByEnvironment?.production).toBe(false);
74
+ await expect(datasource.listFeatures()).resolves.toEqual(["checkout.page"]);
75
+ await expect(datasource.listSegments()).resolves.toEqual(["countries.germany"]);
76
+ await datasource.writeTarget("apps.web", {
77
+ description: "Web app",
78
+ tags: ["all"],
79
+ context: { platform: "web" },
80
+ });
81
+ await expect(datasource.listTargets()).resolves.toEqual(["apps.web"]);
82
+ await expect(datasource.readTarget("apps.web")).resolves.toMatchObject({
83
+ description: "Web app",
84
+ tags: ["all"],
85
+ });
86
+ await datasource.deleteTarget("apps.web");
87
+ await expect(datasource.listTargets()).resolves.toEqual([]);
88
+ await expect(datasource.readFeature("checkout.page")).resolves.toMatchObject({
89
+ description: "Checkout page",
90
+ });
62
91
  });
63
92
 
64
- it("throws when any environment feature file is missing", async () => {
65
- const root = createSplitProject(
66
- "module.exports = { environments: ['staging', 'production'], splitByEnvironment: true };",
67
- );
68
-
69
- writeFile(
70
- path.join(root, "features", "myFeature.yml"),
71
- ["key: myFeature", "description: My feature", "tags:", " - all", "bucketBy: userId"].join(
72
- "\n",
73
- ),
74
- );
93
+ it("keeps slash-separated keys when namespaceCharacter is slash", async () => {
94
+ const root = createProject('module.exports = { namespaceCharacter: "/" };');
75
95
 
76
96
  writeFile(
77
- path.join(root, "environments", "staging", "myFeature.yml"),
78
- ["rules:", " - key: everyone", " segments: '*'", " percentage: 100"].join("\n"),
97
+ path.join(root, "features", "checkout", "page.yml"),
98
+ [
99
+ "description: Checkout page",
100
+ "tags:",
101
+ " - all",
102
+ "bucketBy: userId",
103
+ "rules:",
104
+ " staging:",
105
+ " - key: everyone",
106
+ " segments: '*'",
107
+ " percentage: 100",
108
+ " production:",
109
+ " - key: everyone",
110
+ " segments: '*'",
111
+ " percentage: 100",
112
+ ].join("\n"),
79
113
  );
80
114
 
81
115
  const config = getProjectConfig(root);
82
116
  const datasource = new Datasource(config, root);
83
117
 
84
- await expect(datasource.readFeature("myFeature")).rejects.toMatchObject({
85
- featurevisorFilePath: expect.stringContaining(
86
- path.join("environments", "production", "myFeature.yml"),
87
- ),
118
+ await expect(datasource.listFeatures()).resolves.toEqual(["checkout/page"]);
119
+ await expect(datasource.readFeature("checkout/page")).resolves.toMatchObject({
120
+ description: "Checkout page",
88
121
  });
89
122
  });
90
123
 
91
- it("throws when base feature file still defines rules in split mode", async () => {
92
- const root = createSplitProject(
93
- "module.exports = { environments: ['staging'], splitByEnvironment: true };",
94
- );
124
+ it("lists sets and reads entities from selected set", async () => {
125
+ const root = createProject("module.exports = { sets: true };");
95
126
 
96
127
  writeFile(
97
- path.join(root, "features", "foo.yml"),
128
+ path.join(root, "sets", "storefront", "features", "banner.yml"),
98
129
  [
99
- "key: foo",
100
- "description: Foo",
130
+ "key: banner",
131
+ "description: Storefront banner",
101
132
  "tags:",
102
133
  " - all",
103
134
  "bucketBy: userId",
@@ -106,48 +137,76 @@ describe("core: filesystemAdapter (splitByEnvironment)", () => {
106
137
  " - key: everyone",
107
138
  " segments: '*'",
108
139
  " percentage: 100",
140
+ " production:",
141
+ " - key: everyone",
142
+ " segments: '*'",
143
+ " percentage: 100",
109
144
  ].join("\n"),
110
145
  );
111
146
 
112
147
  writeFile(
113
- path.join(root, "environments", "staging", "foo.yml"),
114
- ["rules:", " - key: everyone", " segments: '*'", " percentage: 100"].join("\n"),
148
+ path.join(root, "sets", "admin", "features", "banner.yml"),
149
+ [
150
+ "key: banner",
151
+ "description: Admin banner",
152
+ "tags:",
153
+ " - all",
154
+ "bucketBy: userId",
155
+ "rules:",
156
+ " staging:",
157
+ " - key: everyone",
158
+ " segments: '*'",
159
+ " percentage: 0",
160
+ " production:",
161
+ " - key: everyone",
162
+ " segments: '*'",
163
+ " percentage: 0",
164
+ ].join("\n"),
115
165
  );
116
166
 
117
167
  const config = getProjectConfig(root);
118
168
  const datasource = new Datasource(config, root);
119
169
 
120
- await expect(datasource.readFeature("foo")).rejects.toThrow(
121
- "base file must not define rules, force, or expose when splitByEnvironment=true",
170
+ await expect(datasource.listSets()).resolves.toEqual(["admin", "storefront"]);
171
+
172
+ const storefrontDatasource = datasource.forSet("storefront");
173
+ const feature = await storefrontDatasource.readFeature("banner");
174
+
175
+ expect(feature.description).toBe("Storefront banner");
176
+ expect(storefrontDatasource.getSet()).toBe("storefront");
177
+ expect(storefrontDatasource.getConfig().featuresDirectoryPath).toBe(
178
+ path.join(root, "sets", "storefront", "features"),
122
179
  );
180
+ expect(storefrontDatasource.getConfig().targetsDirectoryPath).toBe(
181
+ path.join(root, "sets", "storefront", "targets"),
182
+ );
183
+ await storefrontDatasource.writeTarget("web", { description: "Storefront web" });
184
+ await expect(storefrontDatasource.listTargets()).resolves.toEqual(["web"]);
123
185
  });
124
186
 
125
- it("throws when environment file has unknown keys", async () => {
126
- const root = createSplitProject(
127
- "module.exports = { environments: ['staging'], splitByEnvironment: true };",
128
- );
187
+ it("writes compact state when prettyState is disabled", async () => {
188
+ const root = createProject("module.exports = { prettyState: false };");
189
+ const config = getProjectConfig(root);
190
+ const datasource = new Datasource(config, root);
191
+ const state = { features: {} };
129
192
 
130
- writeFile(
131
- path.join(root, "features", "foo.yml"),
132
- ["key: foo", "description: Foo", "tags:", " - all", "bucketBy: userId"].join("\n"),
133
- );
193
+ await datasource.writeState(false, state);
134
194
 
135
- writeFile(
136
- path.join(root, "environments", "staging", "foo.yml"),
137
- [
138
- "rules:",
139
- " - key: everyone",
140
- " segments: '*'",
141
- " percentage: 100",
142
- "extraKey: not allowed",
143
- ].join("\n"),
195
+ expect(fs.readFileSync(getExistingStateFilePath(config, false), "utf8")).toBe(
196
+ JSON.stringify(state),
144
197
  );
198
+ });
145
199
 
200
+ it("writes formatted state when prettyState is enabled", async () => {
201
+ const root = createProject("module.exports = { prettyState: true };");
146
202
  const config = getProjectConfig(root);
147
203
  const datasource = new Datasource(config, root);
204
+ const state = { features: {} };
205
+
206
+ await datasource.writeState(false, state);
148
207
 
149
- await expect(datasource.readFeature("foo")).rejects.toThrow(
150
- "Unknown key(s) in environment feature file: extraKey",
208
+ expect(fs.readFileSync(getExistingStateFilePath(config, false), "utf8")).toBe(
209
+ JSON.stringify(state, null, 2),
151
210
  );
152
211
  });
153
212
  });