@featurevisor/core 2.27.0 → 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 (389) hide show
  1. package/coverage/clover.xml +4991 -3130
  2. package/coverage/coverage-final.json +63 -39
  3. package/coverage/lcov-report/block-navigation.js +1 -1
  4. package/coverage/lcov-report/index.html +248 -98
  5. package/coverage/lcov-report/sorter.js +21 -7
  6. package/coverage/lcov-report/src/assess-distribution/index.html +116 -0
  7. package/coverage/lcov-report/src/assess-distribution/index.ts.html +754 -0
  8. package/coverage/lcov-report/{list → src/benchmark}/index.html +30 -30
  9. package/coverage/lcov-report/src/benchmark/index.ts.html +793 -0
  10. package/coverage/lcov-report/{builder → src/builder}/allocator.ts.html +38 -38
  11. package/coverage/lcov-report/{builder/buildScopedConditions.ts.html → src/builder/applyContextToConditions.ts.html} +80 -65
  12. package/coverage/lcov-report/{builder/buildScopedDatafile.ts.html → src/builder/applyContextToDatafile.ts.html} +92 -92
  13. package/coverage/lcov-report/{builder/buildScopedSegments.ts.html → src/builder/applyContextToSegments.ts.html} +103 -67
  14. package/coverage/lcov-report/{builder → src/builder}/buildDatafile.ts.html +336 -306
  15. package/coverage/lcov-report/src/builder/buildProject.ts.html +1117 -0
  16. package/coverage/lcov-report/src/builder/buildRuntimeDatafiles.ts.html +256 -0
  17. package/coverage/lcov-report/{builder → src/builder}/getFeatureRanges.ts.html +22 -22
  18. package/coverage/lcov-report/{builder → src/builder}/hashes.ts.html +50 -50
  19. package/coverage/lcov-report/{builder → src/builder}/index.html +114 -84
  20. package/coverage/lcov-report/{tester/cliFormat.ts.html → src/builder/index.ts.html} +17 -29
  21. package/coverage/lcov-report/{builder → src/builder}/mutateVariables.ts.html +55 -55
  22. package/coverage/lcov-report/{builder → src/builder}/mutator.ts.html +118 -118
  23. package/coverage/lcov-report/{builder → src/builder}/revision.ts.html +15 -15
  24. package/coverage/lcov-report/{builder → src/builder}/traffic.ts.html +86 -86
  25. package/coverage/lcov-report/src/cli/cli.ts.html +400 -0
  26. package/coverage/lcov-report/{tester → src/cli}/index.html +41 -41
  27. package/coverage/lcov-report/src/cli/plugins.ts.html +214 -0
  28. package/coverage/lcov-report/{config → src/config}/index.html +31 -31
  29. package/coverage/lcov-report/{config → src/config}/index.ts.html +12 -12
  30. package/coverage/lcov-report/{config → src/config}/projectConfig.ts.html +312 -96
  31. package/coverage/lcov-report/{datasource → src/datasource}/adapter.ts.html +28 -73
  32. package/coverage/lcov-report/{datasource → src/datasource}/datasource.ts.html +191 -104
  33. package/coverage/lcov-report/{datasource → src/datasource}/filesystemAdapter.ts.html +233 -635
  34. package/coverage/lcov-report/{datasource → src/datasource}/index.html +48 -48
  35. package/coverage/lcov-report/{datasource → src/datasource}/index.ts.html +14 -14
  36. package/coverage/lcov-report/src/evaluate/index.html +116 -0
  37. package/coverage/lcov-report/src/evaluate/index.ts.html +814 -0
  38. package/coverage/lcov-report/src/find-duplicate-segments/findDuplicateSegments.ts.html +313 -0
  39. package/coverage/lcov-report/src/find-duplicate-segments/index.html +131 -0
  40. package/coverage/lcov-report/src/find-duplicate-segments/index.ts.html +307 -0
  41. package/coverage/lcov-report/src/find-usage/index.html +116 -0
  42. package/coverage/lcov-report/src/find-usage/index.ts.html +1780 -0
  43. package/coverage/lcov-report/src/generate-code/index.html +131 -0
  44. package/coverage/lcov-report/src/generate-code/index.ts.html +403 -0
  45. package/coverage/lcov-report/{generate-code → src/generate-code}/typescript.ts.html +266 -1361
  46. package/coverage/lcov-report/{generate-code → src}/index.html +38 -23
  47. package/coverage/lcov-report/src/info/index.html +116 -0
  48. package/coverage/lcov-report/src/info/index.ts.html +472 -0
  49. package/coverage/lcov-report/src/init/index.html +116 -0
  50. package/coverage/lcov-report/src/init/index.ts.html +280 -0
  51. package/coverage/lcov-report/{linter → src/linter}/attributeSchema.ts.html +160 -157
  52. package/coverage/lcov-report/{linter → src/linter}/checkCircularDependency.ts.html +25 -25
  53. package/coverage/lcov-report/{linter → src/linter}/checkPercentageExceedingSlot.ts.html +42 -42
  54. package/coverage/lcov-report/{linter → src/linter}/conditionSchema.ts.html +207 -207
  55. package/coverage/lcov-report/{linter → src/linter}/featureSchema.ts.html +462 -471
  56. package/coverage/lcov-report/{linter → src/linter}/groupSchema.ts.html +25 -22
  57. package/coverage/lcov-report/{linter → src/linter}/index.html +129 -99
  58. package/coverage/lcov-report/src/linter/index.ts.html +88 -0
  59. package/coverage/lcov-report/{linter → src/linter}/lintProject.ts.html +740 -332
  60. package/coverage/lcov-report/{linter → src/linter}/mutationNotation.ts.html +130 -130
  61. package/coverage/lcov-report/{linter → src/linter}/printError.ts.html +27 -27
  62. package/coverage/lcov-report/{linter → src/linter}/schema.ts.html +153 -150
  63. package/coverage/lcov-report/{linter → src/linter}/segmentSchema.ts.html +18 -15
  64. package/coverage/lcov-report/src/linter/targetSchema.ts.html +223 -0
  65. package/coverage/lcov-report/{linter → src/linter}/testSchema.ts.html +54 -48
  66. package/coverage/lcov-report/{linter → src/linter}/zodHelpers.ts.html +17 -17
  67. package/coverage/lcov-report/src/list/index.html +131 -0
  68. package/coverage/lcov-report/src/list/index.ts.html +2065 -0
  69. package/coverage/lcov-report/{list → src/list}/matrix.ts.html +43 -58
  70. package/coverage/lcov-report/src/promoter/index.html +116 -0
  71. package/coverage/lcov-report/src/promoter/index.ts.html +3883 -0
  72. package/coverage/lcov-report/src/sets.ts.html +313 -0
  73. package/coverage/lcov-report/src/targeting.ts.html +403 -0
  74. package/coverage/lcov-report/src/tester/cliFormat.ts.html +133 -0
  75. package/coverage/lcov-report/{tester → src/tester}/helpers.ts.html +15 -15
  76. package/coverage/lcov-report/src/tester/index.html +206 -0
  77. package/coverage/lcov-report/src/tester/prettyDuration.ts.html +187 -0
  78. package/coverage/lcov-report/src/tester/printTestResult.ts.html +337 -0
  79. package/coverage/lcov-report/src/tester/testFeature.ts.html +1186 -0
  80. package/coverage/lcov-report/src/tester/testProject.ts.html +1018 -0
  81. package/coverage/lcov-report/src/tester/testSegment.ts.html +316 -0
  82. package/coverage/lcov-report/{utils → src/utils}/extractKeys.ts.html +68 -68
  83. package/coverage/lcov-report/{utils → src/utils}/git.ts.html +44 -68
  84. package/coverage/lcov-report/{utils → src/utils}/index.html +44 -44
  85. package/coverage/lcov-report/{utils → src/utils}/index.ts.html +14 -14
  86. package/coverage/lcov-report/{utils → src/utils}/pretty.ts.html +20 -20
  87. package/coverage/lcov.info +10121 -5723
  88. package/jest.config.js +13 -4
  89. package/lib/assess-distribution/index.d.ts +1 -1
  90. package/lib/assess-distribution/index.js +59 -40
  91. package/lib/assess-distribution/index.js.map +1 -1
  92. package/lib/benchmark/index.d.ts +8 -5
  93. package/lib/benchmark/index.js +82 -65
  94. package/lib/benchmark/index.js.map +1 -1
  95. package/lib/builder/applyContextToConditions.d.ts +5 -0
  96. package/lib/builder/{buildScopedConditions.js → applyContextToConditions.js} +19 -14
  97. package/lib/builder/applyContextToConditions.js.map +1 -0
  98. package/lib/builder/applyContextToDatafile.d.ts +2 -0
  99. package/lib/builder/{buildScopedDatafile.js → applyContextToDatafile.js} +26 -26
  100. package/lib/builder/applyContextToDatafile.js.map +1 -0
  101. package/lib/builder/applyContextToSegments.d.ts +5 -0
  102. package/lib/builder/{buildScopedSegments.js → applyContextToSegments.js} +19 -14
  103. package/lib/builder/applyContextToSegments.js.map +1 -0
  104. package/lib/builder/buildDatafile.d.ts +9 -6
  105. package/lib/builder/buildDatafile.js +86 -51
  106. package/lib/builder/buildDatafile.js.map +1 -1
  107. package/lib/builder/buildProject.d.ts +17 -3
  108. package/lib/builder/buildProject.js +151 -59
  109. package/lib/builder/buildProject.js.map +1 -1
  110. package/lib/builder/buildRuntimeDatafiles.d.ts +12 -0
  111. package/lib/builder/buildRuntimeDatafiles.js +35 -0
  112. package/lib/builder/buildRuntimeDatafiles.js.map +1 -0
  113. package/lib/builder/getFeatureRanges.js +34 -1
  114. package/lib/builder/getFeatureRanges.js.map +1 -1
  115. package/lib/builder/hashes.js +34 -1
  116. package/lib/builder/hashes.js.map +1 -1
  117. package/lib/builder/index.d.ts +0 -1
  118. package/lib/builder/index.js +0 -1
  119. package/lib/builder/index.js.map +1 -1
  120. package/lib/cli/cli.js +0 -4
  121. package/lib/cli/cli.js.map +1 -1
  122. package/lib/cli/plugins.js +7 -2
  123. package/lib/cli/plugins.js.map +1 -1
  124. package/lib/config/projectConfig.d.ts +17 -17
  125. package/lib/config/projectConfig.js +101 -15
  126. package/lib/config/projectConfig.js.map +1 -1
  127. package/lib/datasource/adapter.d.ts +7 -9
  128. package/lib/datasource/adapter.js +0 -7
  129. package/lib/datasource/adapter.js.map +1 -1
  130. package/lib/datasource/datasource.d.ts +15 -5
  131. package/lib/datasource/datasource.js +37 -9
  132. package/lib/datasource/datasource.js.map +1 -1
  133. package/lib/datasource/filesystemAdapter.d.ts +4 -10
  134. package/lib/datasource/filesystemAdapter.js +111 -149
  135. package/lib/datasource/filesystemAdapter.js.map +1 -1
  136. package/lib/evaluate/index.d.ts +1 -7
  137. package/lib/evaluate/index.js +81 -67
  138. package/lib/evaluate/index.js.map +1 -1
  139. package/lib/find-duplicate-segments/findDuplicateSegments.js +34 -1
  140. package/lib/find-duplicate-segments/findDuplicateSegments.js.map +1 -1
  141. package/lib/find-duplicate-segments/index.js +21 -13
  142. package/lib/find-duplicate-segments/index.js.map +1 -1
  143. package/lib/find-usage/index.js +57 -41
  144. package/lib/find-usage/index.js.map +1 -1
  145. package/lib/generate-code/index.d.ts +1 -1
  146. package/lib/generate-code/index.js +64 -21
  147. package/lib/generate-code/index.js.map +1 -1
  148. package/lib/generate-code/typescript.d.ts +1 -1
  149. package/lib/generate-code/typescript.js +59 -333
  150. package/lib/generate-code/typescript.js.map +1 -1
  151. package/lib/index.d.ts +2 -1
  152. package/lib/index.js +2 -1
  153. package/lib/index.js.map +1 -1
  154. package/lib/info/index.js +57 -16
  155. package/lib/info/index.js.map +1 -1
  156. package/lib/init/index.d.ts +2 -2
  157. package/lib/init/index.js +39 -3
  158. package/lib/init/index.js.map +1 -1
  159. package/lib/linter/attributeSchema.d.ts +1 -0
  160. package/lib/linter/attributeSchema.js +1 -0
  161. package/lib/linter/attributeSchema.js.map +1 -1
  162. package/lib/linter/conditionSchema.js +5 -5
  163. package/lib/linter/conditionSchema.js.map +1 -1
  164. package/lib/linter/featureSchema.d.ts +4 -1
  165. package/lib/linter/featureSchema.js +15 -13
  166. package/lib/linter/featureSchema.js.map +1 -1
  167. package/lib/linter/groupSchema.d.ts +1 -0
  168. package/lib/linter/groupSchema.js +1 -0
  169. package/lib/linter/groupSchema.js.map +1 -1
  170. package/lib/linter/lintProject.d.ts +1 -3
  171. package/lib/linter/lintProject.js +198 -68
  172. package/lib/linter/lintProject.js.map +1 -1
  173. package/lib/linter/schema.js +1 -0
  174. package/lib/linter/schema.js.map +1 -1
  175. package/lib/linter/segmentSchema.d.ts +1 -0
  176. package/lib/linter/segmentSchema.js +1 -0
  177. package/lib/linter/segmentSchema.js.map +1 -1
  178. package/lib/linter/targetSchema.d.ts +16 -0
  179. package/lib/linter/targetSchema.js +40 -0
  180. package/lib/linter/targetSchema.js.map +1 -0
  181. package/lib/linter/testSchema.d.ts +4 -3
  182. package/lib/linter/testSchema.js +9 -4
  183. package/lib/linter/testSchema.js.map +1 -1
  184. package/lib/list/index.d.ts +1 -0
  185. package/lib/list/index.js +131 -12
  186. package/lib/list/index.js.map +1 -1
  187. package/lib/list/matrix.js +3 -7
  188. package/lib/list/matrix.js.map +1 -1
  189. package/lib/promoter/index.d.ts +50 -0
  190. package/lib/promoter/index.js +880 -0
  191. package/lib/promoter/index.js.map +1 -0
  192. package/lib/sets.d.ts +11 -0
  193. package/lib/sets.js +81 -0
  194. package/lib/sets.js.map +1 -0
  195. package/lib/targeting.d.ts +14 -0
  196. package/lib/targeting.js +82 -0
  197. package/lib/targeting.js.map +1 -0
  198. package/lib/tester/cliFormat.d.ts +7 -1
  199. package/lib/tester/cliFormat.js +11 -2
  200. package/lib/tester/cliFormat.js.map +1 -1
  201. package/lib/tester/printTestResult.js +2 -2
  202. package/lib/tester/printTestResult.js.map +1 -1
  203. package/lib/tester/testFeature.d.ts +0 -1
  204. package/lib/tester/testFeature.js +8 -25
  205. package/lib/tester/testFeature.js.map +1 -1
  206. package/lib/tester/testProject.d.ts +2 -3
  207. package/lib/tester/testProject.js +93 -86
  208. package/lib/tester/testProject.js.map +1 -1
  209. package/lib/tester/testSegment.d.ts +1 -1
  210. package/lib/tester/testSegment.js +3 -21
  211. package/lib/tester/testSegment.js.map +1 -1
  212. package/lib/utils/git.js +45 -19
  213. package/lib/utils/git.js.map +1 -1
  214. package/package.json +12 -12
  215. package/src/assess-distribution/index.ts +72 -52
  216. package/src/benchmark/index.ts +124 -91
  217. package/src/builder/{buildScopedConditions.spec.ts → applyContextToConditions.spec.ts} +160 -142
  218. package/src/builder/{buildScopedConditions.ts → applyContextToConditions.ts} +19 -14
  219. package/src/builder/{buildScopedDatafile.spec.ts → applyContextToDatafile.spec.ts} +141 -81
  220. package/src/builder/{buildScopedDatafile.ts → applyContextToDatafile.ts} +33 -33
  221. package/src/builder/{buildScopedSegments.spec.ts → applyContextToSegments.spec.ts} +171 -151
  222. package/src/builder/{buildScopedSegments.ts → applyContextToSegments.ts} +26 -14
  223. package/src/builder/buildDatafile.spec.ts +192 -14
  224. package/src/builder/buildDatafile.ts +74 -64
  225. package/src/builder/buildProject.ts +171 -86
  226. package/src/builder/buildRuntimeDatafiles.ts +57 -0
  227. package/src/builder/index.ts +0 -1
  228. package/src/cli/cli.ts +0 -5
  229. package/src/cli/plugins.ts +7 -2
  230. package/src/config/projectConfig.spec.ts +171 -8
  231. package/src/config/projectConfig.ts +100 -28
  232. package/src/datasource/adapter.ts +8 -23
  233. package/src/datasource/datasource.ts +50 -21
  234. package/src/datasource/filesystemAdapter.spec.ts +133 -74
  235. package/src/datasource/filesystemAdapter.ts +93 -227
  236. package/src/evaluate/index.ts +109 -97
  237. package/src/find-duplicate-segments/index.ts +34 -16
  238. package/src/find-usage/index.ts +89 -44
  239. package/src/generate-code/index.ts +36 -24
  240. package/src/generate-code/typescript.spec.ts +106 -0
  241. package/src/generate-code/typescript.ts +33 -398
  242. package/src/index.ts +2 -1
  243. package/src/info/index.ts +72 -16
  244. package/src/init/index.ts +1 -1
  245. package/src/linter/attributeSchema.ts +1 -0
  246. package/src/linter/conditionSchema.spec.ts +23 -3
  247. package/src/linter/conditionSchema.ts +5 -5
  248. package/src/linter/featureSchema.spec.ts +107 -18
  249. package/src/linter/featureSchema.ts +25 -28
  250. package/src/linter/groupSchema.ts +1 -0
  251. package/src/linter/lintProject.spec.ts +131 -92
  252. package/src/linter/lintProject.ts +233 -97
  253. package/src/linter/schema.ts +1 -0
  254. package/src/linter/segmentSchema.spec.ts +5 -3
  255. package/src/linter/segmentSchema.ts +1 -0
  256. package/src/linter/targetSchema.spec.ts +159 -0
  257. package/src/linter/targetSchema.ts +46 -0
  258. package/src/linter/testSchema.spec.ts +70 -5
  259. package/src/linter/testSchema.ts +12 -10
  260. package/src/list/index.spec.ts +135 -0
  261. package/src/list/index.ts +188 -12
  262. package/src/list/matrix.ts +3 -8
  263. package/src/promoter/index.spec.ts +617 -0
  264. package/src/promoter/index.ts +1266 -0
  265. package/src/sets.ts +76 -0
  266. package/src/targeting.spec.ts +74 -0
  267. package/src/targeting.ts +106 -0
  268. package/src/tester/cliFormat.ts +11 -1
  269. package/src/tester/printTestResult.ts +12 -3
  270. package/src/tester/testFeature.ts +10 -36
  271. package/src/tester/testProject.spec.ts +50 -0
  272. package/src/tester/testProject.ts +89 -140
  273. package/src/tester/testSegment.ts +5 -22
  274. package/src/utils/git.ts +13 -21
  275. package/tsconfig.cjs.json +5 -1
  276. package/tsconfig.typecheck.json +9 -0
  277. package/coverage/lcov-report/builder/convertToV1.ts.html +0 -583
  278. package/lib/builder/allocator.spec.d.ts +0 -1
  279. package/lib/builder/allocator.spec.js +0 -85
  280. package/lib/builder/allocator.spec.js.map +0 -1
  281. package/lib/builder/buildDatafile.spec.d.ts +0 -1
  282. package/lib/builder/buildDatafile.spec.js +0 -233
  283. package/lib/builder/buildDatafile.spec.js.map +0 -1
  284. package/lib/builder/buildScopedConditions.d.ts +0 -5
  285. package/lib/builder/buildScopedConditions.js.map +0 -1
  286. package/lib/builder/buildScopedConditions.spec.d.ts +0 -1
  287. package/lib/builder/buildScopedConditions.spec.js +0 -2167
  288. package/lib/builder/buildScopedConditions.spec.js.map +0 -1
  289. package/lib/builder/buildScopedDatafile.d.ts +0 -2
  290. package/lib/builder/buildScopedDatafile.js.map +0 -1
  291. package/lib/builder/buildScopedDatafile.spec.d.ts +0 -1
  292. package/lib/builder/buildScopedDatafile.spec.js +0 -2277
  293. package/lib/builder/buildScopedDatafile.spec.js.map +0 -1
  294. package/lib/builder/buildScopedSegments.d.ts +0 -5
  295. package/lib/builder/buildScopedSegments.js.map +0 -1
  296. package/lib/builder/buildScopedSegments.spec.d.ts +0 -1
  297. package/lib/builder/buildScopedSegments.spec.js +0 -1062
  298. package/lib/builder/buildScopedSegments.spec.js.map +0 -1
  299. package/lib/builder/convertToV1.d.ts +0 -10
  300. package/lib/builder/convertToV1.js +0 -119
  301. package/lib/builder/convertToV1.js.map +0 -1
  302. package/lib/builder/mutateVariables.spec.d.ts +0 -1
  303. package/lib/builder/mutateVariables.spec.js +0 -1074
  304. package/lib/builder/mutateVariables.spec.js.map +0 -1
  305. package/lib/builder/mutator.spec.d.ts +0 -1
  306. package/lib/builder/mutator.spec.js +0 -368
  307. package/lib/builder/mutator.spec.js.map +0 -1
  308. package/lib/builder/revision.spec.d.ts +0 -1
  309. package/lib/builder/revision.spec.js +0 -21
  310. package/lib/builder/revision.spec.js.map +0 -1
  311. package/lib/builder/traffic.spec.d.ts +0 -1
  312. package/lib/builder/traffic.spec.js +0 -872
  313. package/lib/builder/traffic.spec.js.map +0 -1
  314. package/lib/config/projectConfig.spec.d.ts +0 -1
  315. package/lib/config/projectConfig.spec.js +0 -24
  316. package/lib/config/projectConfig.spec.js.map +0 -1
  317. package/lib/datasource/filesystemAdapter.spec.d.ts +0 -1
  318. package/lib/datasource/filesystemAdapter.spec.js +0 -88
  319. package/lib/datasource/filesystemAdapter.spec.js.map +0 -1
  320. package/lib/generate-code/typescript.spec.d.ts +0 -1
  321. package/lib/generate-code/typescript.spec.js +0 -78
  322. package/lib/generate-code/typescript.spec.js.map +0 -1
  323. package/lib/linter/attributeSchema.spec.d.ts +0 -1
  324. package/lib/linter/attributeSchema.spec.js +0 -266
  325. package/lib/linter/attributeSchema.spec.js.map +0 -1
  326. package/lib/linter/conditionSchema.spec.d.ts +0 -1
  327. package/lib/linter/conditionSchema.spec.js +0 -380
  328. package/lib/linter/conditionSchema.spec.js.map +0 -1
  329. package/lib/linter/featureSchema.spec.d.ts +0 -1
  330. package/lib/linter/featureSchema.spec.js +0 -1552
  331. package/lib/linter/featureSchema.spec.js.map +0 -1
  332. package/lib/linter/lintProject.spec.d.ts +0 -1
  333. package/lib/linter/lintProject.spec.js +0 -211
  334. package/lib/linter/lintProject.spec.js.map +0 -1
  335. package/lib/linter/mutationNotation.spec.d.ts +0 -1
  336. package/lib/linter/mutationNotation.spec.js +0 -598
  337. package/lib/linter/mutationNotation.spec.js.map +0 -1
  338. package/lib/linter/schema.spec.d.ts +0 -1
  339. package/lib/linter/schema.spec.js +0 -535
  340. package/lib/linter/schema.spec.js.map +0 -1
  341. package/lib/linter/segmentSchema.spec.d.ts +0 -1
  342. package/lib/linter/segmentSchema.spec.js +0 -303
  343. package/lib/linter/segmentSchema.spec.js.map +0 -1
  344. package/lib/linter/testSchema.spec.d.ts +0 -1
  345. package/lib/linter/testSchema.spec.js +0 -162
  346. package/lib/linter/testSchema.spec.js.map +0 -1
  347. package/lib/list/matrix.spec.d.ts +0 -1
  348. package/lib/list/matrix.spec.js +0 -42
  349. package/lib/list/matrix.spec.js.map +0 -1
  350. package/lib/site/exportSite.d.ts +0 -2
  351. package/lib/site/exportSite.js +0 -30
  352. package/lib/site/exportSite.js.map +0 -1
  353. package/lib/site/generateHistory.d.ts +0 -3
  354. package/lib/site/generateHistory.js +0 -31
  355. package/lib/site/generateHistory.js.map +0 -1
  356. package/lib/site/generateSiteSearchIndex.d.ts +0 -4
  357. package/lib/site/generateSiteSearchIndex.js +0 -195
  358. package/lib/site/generateSiteSearchIndex.js.map +0 -1
  359. package/lib/site/getLastModifiedFromHistory.d.ts +0 -2
  360. package/lib/site/getLastModifiedFromHistory.js +0 -18
  361. package/lib/site/getLastModifiedFromHistory.js.map +0 -1
  362. package/lib/site/getOwnerAndRepoFromUrl.d.ts +0 -4
  363. package/lib/site/getOwnerAndRepoFromUrl.js +0 -20
  364. package/lib/site/getOwnerAndRepoFromUrl.js.map +0 -1
  365. package/lib/site/getRelativePaths.d.ts +0 -6
  366. package/lib/site/getRelativePaths.js +0 -15
  367. package/lib/site/getRelativePaths.js.map +0 -1
  368. package/lib/site/getRepoDetails.d.ts +0 -8
  369. package/lib/site/getRepoDetails.js +0 -48
  370. package/lib/site/getRepoDetails.js.map +0 -1
  371. package/lib/site/index.d.ts +0 -2
  372. package/lib/site/index.js +0 -43
  373. package/lib/site/index.js.map +0 -1
  374. package/lib/site/serveSite.d.ts +0 -2
  375. package/lib/site/serveSite.js +0 -54
  376. package/lib/site/serveSite.js.map +0 -1
  377. package/lib/tester/helpers.spec.d.ts +0 -1
  378. package/lib/tester/helpers.spec.js +0 -115
  379. package/lib/tester/helpers.spec.js.map +0 -1
  380. package/src/builder/convertToV1.ts +0 -166
  381. package/src/site/exportSite.ts +0 -42
  382. package/src/site/generateHistory.ts +0 -40
  383. package/src/site/generateSiteSearchIndex.ts +0 -258
  384. package/src/site/getLastModifiedFromHistory.ts +0 -21
  385. package/src/site/getOwnerAndRepoFromUrl.ts +0 -17
  386. package/src/site/getRelativePaths.ts +0 -24
  387. package/src/site/getRepoDetails.ts +0 -62
  388. package/src/site/index.ts +0 -46
  389. package/src/site/serveSite.ts +0 -58
@@ -1,19 +1,22 @@
1
- import { SCHEMA_VERSION, ProjectConfig } from "../config";
1
+ import * as path from "path";
2
+
3
+ import { ProjectConfig } from "../config";
2
4
  import { Datasource } from "../datasource";
3
5
 
4
6
  import { getNextRevision } from "./revision";
5
7
  import { buildDatafile, getCustomDatafile } from "./buildDatafile";
6
- import { buildScopedDatafile } from "./buildScopedDatafile";
8
+ import { applyContextToDatafile } from "./applyContextToDatafile";
7
9
  import { Dependencies } from "../dependencies";
8
10
  import { Plugin } from "../cli";
9
- import type { Scope } from "../config";
10
11
 
11
- import type { DatafileContent } from "@featurevisor/types";
12
+ import type { DatafileContent, Target } from "@featurevisor/types";
13
+ import { assertProjectSetJsonSelection, getProjectSetExecutions, printSetHeader } from "../sets";
14
+ import { CLI_COLOR_CYAN, CLI_FORMAT_BOLD, CLI_FORMAT_GREEN, colorize } from "../tester/cliFormat";
15
+ import { resolveTargets } from "../targeting";
12
16
 
13
17
  export interface BuildCLIOptions {
14
18
  revision?: string;
15
19
  revisionFromHash?: boolean;
16
- schemaVersion?: string;
17
20
 
18
21
  // all three together
19
22
  environment?: string;
@@ -24,8 +27,8 @@ export interface BuildCLIOptions {
24
27
  inflate?: number;
25
28
  datafilesDir?: string;
26
29
 
27
- tag?: string;
28
- scope?: string; // scope name only
30
+ target?: string | string[];
31
+ set?: string;
29
32
  }
30
33
 
31
34
  function getFeaturevisorVersion(): string {
@@ -39,91 +42,53 @@ function getFeaturevisorVersion(): string {
39
42
  }
40
43
  }
41
44
 
45
+ function getEnvironmentLabel(environment: string | false) {
46
+ return environment === false ? "No environment" : `Environment "${environment}"`;
47
+ }
48
+
42
49
  async function buildForEnvironment({
43
50
  projectConfig,
44
51
  datasource,
45
52
  nextRevision,
46
53
  environment,
47
- tags,
48
- scopes,
54
+ targets,
49
55
  cliOptions,
50
56
  }: {
51
57
  projectConfig: ProjectConfig;
52
58
  datasource: Datasource;
53
59
  nextRevision: string;
54
60
  environment: string | false;
55
- tags: string[];
56
- scopes?: Scope[];
61
+ targets: Array<Target & { key: string }>;
57
62
  cliOptions: BuildCLIOptions;
58
63
  }) {
59
- console.log(`\nBuilding datafiles for environment: ${environment}`);
64
+ console.log("");
65
+ console.log(CLI_FORMAT_BOLD, getEnvironmentLabel(environment));
60
66
 
61
67
  const existingState = await datasource.readState(environment);
62
68
  const featurevisorVersion = getFeaturevisorVersion();
63
69
 
64
- // by tag
65
- for (const tag of tags) {
66
- console.log(`\n => Tag: ${tag}`);
70
+ for (const target of targets) {
71
+ console.log(` ${colorize("Target", CLI_COLOR_CYAN)}: ${target.key}`);
67
72
 
68
- const datafileContent = await buildDatafile(
73
+ const datafileContent = await buildTargetDatafile({
69
74
  projectConfig,
70
75
  datasource,
71
- {
72
- schemaVersion: cliOptions.schemaVersion || SCHEMA_VERSION,
73
- revision: nextRevision,
74
- revisionFromHash: cliOptions.revisionFromHash,
75
- environment: environment,
76
- tag: tag,
77
- inflate: cliOptions.inflate,
78
- featurevisorVersion,
79
- },
76
+ target,
77
+ environment,
80
78
  existingState,
81
- );
79
+ revision: nextRevision,
80
+ revisionFromHash: cliOptions.revisionFromHash,
81
+ inflate: cliOptions.inflate,
82
+ featurevisorVersion,
83
+ });
82
84
 
83
- // write datafile for environment/tag
84
- await datasource.writeDatafile(datafileContent as DatafileContent, {
85
+ await datasource.writeDatafile(datafileContent, {
85
86
  environment,
86
- tag,
87
+ target: target.key,
87
88
  datafilesDir: cliOptions.datafilesDir,
88
89
  });
89
90
  }
90
91
 
91
- // by scope
92
- if (scopes) {
93
- for (const scope of scopes) {
94
- console.log(`\n => Scope: ${scope.name}`);
95
-
96
- const datafileContent = await buildDatafile(
97
- projectConfig,
98
- datasource,
99
- {
100
- schemaVersion: cliOptions.schemaVersion || SCHEMA_VERSION,
101
- revision: nextRevision,
102
- revisionFromHash: cliOptions.revisionFromHash,
103
- environment: environment,
104
- tag: scope.tag,
105
- tags: scope.tags,
106
- inflate: cliOptions.inflate,
107
- featurevisorVersion,
108
- },
109
- existingState,
110
- );
111
-
112
- const scopedDatafileContent = buildScopedDatafile(
113
- datafileContent as DatafileContent,
114
- scope.context,
115
- );
116
-
117
- // write scoped datafile
118
- await datasource.writeDatafile(scopedDatafileContent, {
119
- environment,
120
- tag: scope.tag,
121
- scope: scope,
122
- datafilesDir: cliOptions.datafilesDir,
123
- });
124
- }
125
- }
126
-
127
92
  if (typeof cliOptions.stateFiles === "undefined" || cliOptions.stateFiles) {
128
93
  // write state for environment
129
94
  await datasource.writeState(environment, existingState);
@@ -133,6 +98,51 @@ async function buildForEnvironment({
133
98
  }
134
99
  }
135
100
 
101
+ export async function buildTargetDatafile({
102
+ projectConfig,
103
+ datasource,
104
+ target,
105
+ environment,
106
+ existingState,
107
+ revision,
108
+ revisionFromHash,
109
+ inflate,
110
+ featurevisorVersion,
111
+ }: {
112
+ projectConfig: ProjectConfig;
113
+ datasource: Datasource;
114
+ target: Target;
115
+ environment: string | false;
116
+ existingState: Awaited<ReturnType<Datasource["readState"]>>;
117
+ revision: string;
118
+ revisionFromHash?: boolean;
119
+ inflate?: number;
120
+ featurevisorVersion?: string;
121
+ }): Promise<DatafileContent> {
122
+ const datafileContent = await buildDatafile(
123
+ projectConfig,
124
+ datasource,
125
+ {
126
+ revision,
127
+ revisionFromHash,
128
+ environment,
129
+ tag: target.tag,
130
+ tags: target.tags,
131
+ includeFeatures: target.includeFeatures,
132
+ excludeFeatures: target.excludeFeatures,
133
+ inflate,
134
+ featurevisorVersion,
135
+ },
136
+ existingState,
137
+ );
138
+
139
+ if (target.context) {
140
+ return applyContextToDatafile(datafileContent as DatafileContent, target.context);
141
+ }
142
+
143
+ return datafileContent as DatafileContent;
144
+ }
145
+
136
146
  export async function buildProject(deps: Dependencies, cliOptions: BuildCLIOptions = {}) {
137
147
  const { projectConfig, datasource } = deps;
138
148
 
@@ -146,24 +156,38 @@ export async function buildProject(deps: Dependencies, cliOptions: BuildCLIOptio
146
156
  * This way we centralize the datafile generation in one place,
147
157
  * while tests can be run anywhere else.
148
158
  */
149
- if (cliOptions.environment && cliOptions.json) {
150
- const scope = cliOptions.scope
151
- ? projectConfig.scopes?.find((scope) => scope.name === cliOptions.scope)
152
- : undefined;
159
+ if (cliOptions.json) {
160
+ const environment = cliOptions.environment || false;
161
+
162
+ if (Array.isArray(projectConfig.environments) && !cliOptions.environment) {
163
+ throw new Error("Pass --environment=<environment> when printing a datafile.");
164
+ }
165
+
166
+ const targets = await resolveTargets(datasource, cliOptions.target, {
167
+ defaultToAll: false,
168
+ requireTargets: false,
169
+ });
170
+ if (targets.length > 1) {
171
+ throw new Error("Only one --target can be used with --json or --print.");
172
+ }
173
+ const target = targets[0];
153
174
 
154
175
  let datafileContent = await getCustomDatafile({
155
176
  featureKey: cliOptions.feature,
156
- environment: cliOptions.environment,
177
+ environment,
157
178
  projectConfig,
158
179
  datasource,
159
180
  revision: cliOptions.revision,
160
- schemaVersion: cliOptions.schemaVersion,
161
- tag: cliOptions.tag,
162
- tags: scope?.tags,
181
+ inflate: cliOptions.inflate,
182
+ tag: target?.tag,
183
+ tags: target?.tags,
184
+ includeFeatures: target?.includeFeatures,
185
+ excludeFeatures: target?.excludeFeatures,
186
+ featurevisorVersion: getFeaturevisorVersion(),
163
187
  });
164
188
 
165
- if (scope) {
166
- datafileContent = buildScopedDatafile(datafileContent as DatafileContent, scope.context);
189
+ if (target?.context) {
190
+ datafileContent = applyContextToDatafile(datafileContent as DatafileContent, target.context);
167
191
  }
168
192
 
169
193
  if (cliOptions.pretty) {
@@ -178,10 +202,13 @@ export async function buildProject(deps: Dependencies, cliOptions: BuildCLIOptio
178
202
  /**
179
203
  * Regular build process that writes to disk.
180
204
  */
181
- const { tags, environments, scopes } = projectConfig;
205
+ const { environments } = projectConfig;
206
+ const targets = await resolveTargets(datasource, cliOptions.target);
182
207
 
183
208
  const currentRevision = await datasource.readRevision();
184
- console.log("\nCurrent revision:", currentRevision);
209
+ console.log("");
210
+ console.log(CLI_FORMAT_BOLD, "Building Featurevisor datafiles");
211
+ console.log(` Current revision: ${currentRevision}`);
185
212
 
186
213
  const nextRevision =
187
214
  (cliOptions.revision && cliOptions.revision.toString()) || getNextRevision(currentRevision);
@@ -194,33 +221,91 @@ export async function buildProject(deps: Dependencies, cliOptions: BuildCLIOptio
194
221
  datasource,
195
222
  nextRevision,
196
223
  environment,
197
- tags,
198
- scopes,
224
+ targets,
199
225
  cliOptions,
200
226
  });
201
227
  }
202
228
  }
203
229
 
204
230
  // no environment
205
- if (environments === false) {
231
+ if (!Array.isArray(environments)) {
206
232
  await buildForEnvironment({
207
233
  projectConfig,
208
234
  datasource,
209
235
  nextRevision,
210
236
  environment: false,
211
- tags,
212
- scopes,
237
+ targets,
213
238
  cliOptions,
214
239
  });
215
240
  }
216
241
 
217
- console.log("\nLatest revision:", nextRevision);
242
+ console.log("");
243
+ console.log(CLI_FORMAT_GREEN, "Datafiles built");
244
+ console.log(CLI_FORMAT_BOLD, `Latest revision: ${nextRevision}`);
245
+ }
246
+
247
+ export async function buildProjectSets(deps: Dependencies, cliOptions: BuildCLIOptions = {}) {
248
+ const { projectConfig, datasource } = deps;
249
+
250
+ assertProjectSetJsonSelection(projectConfig, cliOptions.set, cliOptions.json);
251
+
252
+ const executions = await getProjectSetExecutions(projectConfig, datasource, cliOptions.set);
253
+ const currentRevision = await datasource.readRevision();
254
+ const nextRevision =
255
+ (cliOptions.revision && cliOptions.revision.toString()) || getNextRevision(currentRevision);
256
+
257
+ if (projectConfig.sets && !cliOptions.json) {
258
+ console.log("");
259
+ console.log(CLI_FORMAT_BOLD, "Building Featurevisor sets");
260
+ console.log(` Sets: ${executions.map((execution) => execution.set).join(", ")}`);
261
+ console.log(` Current project revision: ${currentRevision}`);
262
+ }
263
+
264
+ for (const execution of executions) {
265
+ printSetHeader(projectConfig, execution.set, cliOptions.json);
266
+
267
+ const executionCliOptions =
268
+ projectConfig.sets && cliOptions.datafilesDir
269
+ ? {
270
+ ...cliOptions,
271
+ datafilesDir: path.join(cliOptions.datafilesDir, execution.set),
272
+ }
273
+ : cliOptions;
274
+
275
+ await buildProject(
276
+ {
277
+ ...deps,
278
+ projectConfig: execution.projectConfig,
279
+ datasource: execution.datasource,
280
+ },
281
+ {
282
+ ...executionCliOptions,
283
+ revision: projectConfig.sets ? nextRevision : cliOptions.revision,
284
+ },
285
+ );
286
+ }
287
+
288
+ if (
289
+ projectConfig.sets &&
290
+ !cliOptions.json &&
291
+ (typeof cliOptions.stateFiles === "undefined" || cliOptions.stateFiles) &&
292
+ !cliOptions.revision
293
+ ) {
294
+ await datasource.writeRevision(nextRevision);
295
+ console.log("");
296
+ console.log(CLI_FORMAT_GREEN, "Featurevisor sets built");
297
+ console.log(CLI_FORMAT_BOLD, `Latest project revision: ${nextRevision}`);
298
+ }
218
299
  }
219
300
 
220
301
  export const buildPlugin: Plugin = {
221
302
  command: "build",
222
303
  handler: async function ({ rootDirectoryPath, projectConfig, datasource, parsed }) {
223
- await buildProject(
304
+ if (parsed.print) {
305
+ parsed.json = true;
306
+ }
307
+
308
+ await buildProjectSets(
224
309
  {
225
310
  rootDirectoryPath,
226
311
  projectConfig,
@@ -233,7 +318,7 @@ export const buildPlugin: Plugin = {
233
318
  examples: [
234
319
  {
235
320
  command: "build",
236
- description: "build datafiles for all environments and tags",
321
+ description: "build datafiles for all environments and targets",
237
322
  },
238
323
  {
239
324
  command: "build --revision=123",
@@ -244,7 +329,7 @@ export const buildPlugin: Plugin = {
244
329
  description: "build datafiles for production environment",
245
330
  },
246
331
  {
247
- command: "build --print --environment=production --feature=featureKey",
332
+ command: "build --print --environment=production --feature=featureKey --target=web",
248
333
  description: "print datafile for a feature in production environment",
249
334
  },
250
335
  {
@@ -0,0 +1,57 @@
1
+ import type { DatafileContent } from "@featurevisor/types";
2
+
3
+ import type { Dependencies } from "../dependencies";
4
+ import { resolveTargets } from "../targeting";
5
+ import { buildDatafile } from "./buildDatafile";
6
+ import { buildTargetDatafile } from "./buildProject";
7
+
8
+ export interface RuntimeDatafile {
9
+ target?: string;
10
+ datafile: DatafileContent;
11
+ }
12
+
13
+ export async function buildRuntimeDatafiles(
14
+ deps: Dependencies,
15
+ options: {
16
+ environment: string | false;
17
+ target?: string | string[];
18
+ revision: string;
19
+ inflate?: number;
20
+ },
21
+ ): Promise<RuntimeDatafile[]> {
22
+ const { projectConfig, datasource } = deps;
23
+ const existingState = await datasource.readState(options.environment);
24
+ const targets = await resolveTargets(datasource, options.target, {
25
+ defaultToAll: false,
26
+ requireTargets: false,
27
+ });
28
+
29
+ if (targets.length === 0) {
30
+ const datafile = await buildDatafile(
31
+ projectConfig,
32
+ datasource,
33
+ {
34
+ revision: options.revision,
35
+ environment: options.environment,
36
+ inflate: options.inflate,
37
+ },
38
+ existingState,
39
+ );
40
+ return [{ datafile }];
41
+ }
42
+
43
+ return Promise.all(
44
+ targets.map(async (target) => ({
45
+ target: target.key,
46
+ datafile: await buildTargetDatafile({
47
+ projectConfig,
48
+ datasource,
49
+ target,
50
+ environment: options.environment,
51
+ existingState,
52
+ revision: options.revision,
53
+ inflate: options.inflate,
54
+ }),
55
+ })),
56
+ );
57
+ }
@@ -1,3 +1,2 @@
1
1
  export * from "./buildDatafile";
2
- export * from "./buildScopedDatafile";
3
2
  export * from "./buildProject";
package/src/cli/cli.ts CHANGED
@@ -51,11 +51,6 @@ export async function runCLI(runnerOptions: RunnerOptions) {
51
51
  y = y.command({
52
52
  command: plugin.command,
53
53
  handler: async function (parsed: ParsedOptions) {
54
- // @NOTE: in future, allow yargs options to be defined via plugins
55
- if (parsed.schemaVersion && typeof parsed.schemaVersion !== "string") {
56
- parsed.schemaVersion = parsed.schemaVersion.toString();
57
- }
58
-
59
54
  try {
60
55
  const result = await plugin.handler({
61
56
  rootDirectoryPath,
@@ -1,4 +1,5 @@
1
1
  import type { Plugin } from "./cli";
2
+ import { createCatalogPlugin } from "@featurevisor/catalog";
2
3
 
3
4
  import { initPlugin } from "../init";
4
5
  import { lintPlugin } from "../linter";
@@ -13,7 +14,8 @@ import { evaluatePlugin } from "../evaluate";
13
14
  import { assessDistributionPlugin } from "../assess-distribution";
14
15
  import { infoPlugin } from "../info";
15
16
  import { listPlugin } from "../list";
16
- import { sitePlugin } from "../site";
17
+ import { promotePlugin } from "../promoter";
18
+ import { getProjectSetExecutions } from "../sets";
17
19
 
18
20
  // that do not require an existing project
19
21
  export const nonProjectPlugins: Plugin[] = [initPlugin];
@@ -32,7 +34,10 @@ export const projectBasedPlugins: Plugin[] = [
32
34
  assessDistributionPlugin,
33
35
  infoPlugin,
34
36
  listPlugin,
35
- sitePlugin,
37
+ promotePlugin,
38
+ createCatalogPlugin({
39
+ getProjectSetExecutions,
40
+ }),
36
41
  ];
37
42
 
38
43
  export const commonPlugins: Plugin[] = [];
@@ -2,7 +2,12 @@ import * as fs from "fs";
2
2
  import * as os from "os";
3
3
  import * as path from "path";
4
4
 
5
- import { getProjectConfig, ENVIRONMENTS_DIRECTORY_NAME } from "./projectConfig";
5
+ import {
6
+ getProjectConfig,
7
+ getProjectConfigForSet,
8
+ SETS_DIRECTORY_NAME,
9
+ TARGETS_DIRECTORY_NAME,
10
+ } from "./projectConfig";
6
11
 
7
12
  function createTempProject(configBody: string) {
8
13
  const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "featurevisor-project-config-"));
@@ -11,21 +16,179 @@ function createTempProject(configBody: string) {
11
16
  }
12
17
 
13
18
  describe("core: projectConfig", () => {
14
- it("sets splitByEnvironment=false and environmentsDirectoryPath by default", () => {
19
+ it("sets project defaults", () => {
15
20
  const root = createTempProject("module.exports = {};");
16
21
  const config = getProjectConfig(root);
17
22
 
18
- expect(config.splitByEnvironment).toBe(false);
19
- expect(config.environmentsDirectoryPath).toBe(path.join(root, ENVIRONMENTS_DIRECTORY_NAME));
23
+ expect(config.environments).toBeUndefined();
24
+ expect(config.namespaceCharacter).toBe(".");
25
+ expect(config.sets).toBe(false);
26
+ expect(config.promotionFlows).toBeUndefined();
27
+ expect(config.setsDirectoryPath).toBe(path.join(root, SETS_DIRECTORY_NAME));
28
+ expect(config.targetsDirectoryPath).toBe(path.join(root, TARGETS_DIRECTORY_NAME));
29
+ expect("scopes" in config).toBe(false);
30
+ expect("siteExportDirectoryPath" in config).toBe(false);
20
31
  });
21
32
 
22
- it("throws when splitByEnvironment=true and environments=false", () => {
33
+ it("silently ignores obsolete scopes configuration", () => {
23
34
  const root = createTempProject(
24
- "module.exports = { splitByEnvironment: true, environments: false };",
35
+ 'module.exports = { scopes: [{ name: "web", context: { platform: "web" } }] };',
25
36
  );
37
+ const config = getProjectConfig(root);
38
+
39
+ expect("scopes" in config).toBe(false);
40
+ });
41
+
42
+ it("throws when sets is not boolean", () => {
43
+ const root = createTempProject("module.exports = { sets: 'yes' };");
44
+
45
+ expect(() => getProjectConfig(root)).toThrow("Invalid sets: yes. It must be a boolean.");
46
+ });
47
+
48
+ it("accepts environments when it is an array of strings", () => {
49
+ const root = createTempProject('module.exports = { environments: ["staging", "production"] };');
50
+ const config = getProjectConfig(root);
51
+
52
+ expect(config.environments).toEqual(["staging", "production"]);
53
+ });
54
+
55
+ it("throws when environments is present but not an array of strings", () => {
56
+ const cases = [
57
+ {
58
+ config: "module.exports = { environments: false };",
59
+ message: "Invalid environments: false. It must be an array of strings when defined.",
60
+ },
61
+ {
62
+ config: 'module.exports = { environments: "production" };',
63
+ message: "Invalid environments: production. It must be an array of strings when defined.",
64
+ },
65
+ {
66
+ config: "module.exports = { environments: { production: true } };",
67
+ message:
68
+ "Invalid environments: [object Object]. It must be an array of strings when defined.",
69
+ },
70
+ {
71
+ config: 'module.exports = { environments: ["production", 1] };',
72
+ message: "Invalid environments[1]: 1. It must be a string.",
73
+ },
74
+ ];
75
+
76
+ for (const testCase of cases) {
77
+ const root = createTempProject(testCase.config);
78
+
79
+ expect(() => getProjectConfig(root)).toThrow(testCase.message);
80
+ }
81
+ });
82
+
83
+ it("accepts custom namespaceCharacter values including slash", () => {
84
+ const root = createTempProject('module.exports = { namespaceCharacter: "/" };');
85
+ const config = getProjectConfig(root);
86
+
87
+ expect(config.namespaceCharacter).toBe("/");
88
+ });
89
+
90
+ it("throws when namespaceCharacter is not a non-empty string", () => {
91
+ const cases = [
92
+ {
93
+ config: "module.exports = { namespaceCharacter: '' };",
94
+ message: "Invalid namespaceCharacter: . It must be a non-empty string.",
95
+ },
96
+ {
97
+ config: "module.exports = { namespaceCharacter: 1 };",
98
+ message: "Invalid namespaceCharacter: 1. It must be a non-empty string.",
99
+ },
100
+ ];
101
+
102
+ for (const testCase of cases) {
103
+ const root = createTempProject(testCase.config);
26
104
 
27
- expect(() => getProjectConfig(root)).toThrow(
28
- "Invalid configuration: splitByEnvironment=true requires environments to be an array",
105
+ expect(() => getProjectConfig(root)).toThrow(testCase.message);
106
+ }
107
+ });
108
+
109
+ it("accepts valid promotionFlows object rules", () => {
110
+ const root = createTempProject(
111
+ [
112
+ "module.exports = {",
113
+ " promotionFlows: [",
114
+ ' { from: "dev", to: "staging" },',
115
+ ' { from: "staging", to: "production" },',
116
+ " ],",
117
+ "};",
118
+ "",
119
+ ].join("\n"),
120
+ );
121
+
122
+ const config = getProjectConfig(root);
123
+
124
+ expect(config.promotionFlows).toEqual([
125
+ { from: "dev", to: "staging" },
126
+ { from: "staging", to: "production" },
127
+ ]);
128
+ });
129
+
130
+ it("rejects invalid promotionFlows shapes", () => {
131
+ const cases = [
132
+ {
133
+ config: "module.exports = { promotionFlows: true };",
134
+ message: "Invalid promotionFlows: true. It must be an array.",
135
+ },
136
+ {
137
+ config: 'module.exports = { promotionFlows: ["dev"] };',
138
+ message:
139
+ 'Invalid promotionFlows[0]: dev. Each entry must be an object with exactly "from" and "to" string fields.',
140
+ },
141
+ {
142
+ config: 'module.exports = { promotionFlows: [{ from: "dev" }] };',
143
+ message:
144
+ 'Invalid promotionFlows[0]: {"from":"dev"}. Each entry must contain exactly "from" and "to".',
145
+ },
146
+ {
147
+ config:
148
+ 'module.exports = { promotionFlows: [{ from: "dev", to: "staging", note: true }] };',
149
+ message:
150
+ 'Invalid promotionFlows[0]: {"from":"dev","to":"staging","note":true}. Each entry must contain exactly "from" and "to".',
151
+ },
152
+ {
153
+ config: 'module.exports = { promotionFlows: [{ from: "dev", to: 1 }] };',
154
+ message:
155
+ 'Invalid promotionFlows[0]: {"from":"dev","to":1}. "from" and "to" must be strings.',
156
+ },
157
+ ];
158
+
159
+ for (const testCase of cases) {
160
+ const root = createTempProject(testCase.config);
161
+
162
+ expect(() => getProjectConfig(root)).toThrow(testCase.message);
163
+ }
164
+ });
165
+
166
+ it("remaps project config paths for a set", () => {
167
+ const root = createTempProject("module.exports = { sets: true };");
168
+ const config = getProjectConfig(root);
169
+ const setConfig = getProjectConfigForSet(config, "staging");
170
+ const setRoot = path.join(root, "sets", "staging");
171
+
172
+ expect(setConfig.featuresDirectoryPath).toBe(path.join(setRoot, "features"));
173
+ expect(setConfig.environments).toBeUndefined();
174
+ expect(setConfig.namespaceCharacter).toBe(".");
175
+ expect(setConfig.segmentsDirectoryPath).toBe(path.join(setRoot, "segments"));
176
+ expect(setConfig.attributesDirectoryPath).toBe(path.join(setRoot, "attributes"));
177
+ expect(setConfig.groupsDirectoryPath).toBe(path.join(setRoot, "groups"));
178
+ expect(setConfig.schemasDirectoryPath).toBe(path.join(setRoot, "schemas"));
179
+ expect(setConfig.targetsDirectoryPath).toBe(path.join(setRoot, "targets"));
180
+ expect(setConfig.testsDirectoryPath).toBe(path.join(setRoot, "tests"));
181
+ expect(setConfig.stateDirectoryPath).toBe(path.join(root, ".featurevisor", "sets", "staging"));
182
+ expect(setConfig.datafilesDirectoryPath).toBe(path.join(root, "datafiles", "staging"));
183
+ });
184
+
185
+ it("preserves configured environments when remapping project config for a set", () => {
186
+ const root = createTempProject(
187
+ 'module.exports = { sets: true, environments: ["staging", "production"] };',
29
188
  );
189
+ const config = getProjectConfig(root);
190
+ const setConfig = getProjectConfigForSet(config, "staging");
191
+
192
+ expect(setConfig.environments).toEqual(["staging", "production"]);
30
193
  });
31
194
  });