@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,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
  });