@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
@@ -0,0 +1,880 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.promotePlugin = void 0;
37
+ exports.promoteProjectSets = promoteProjectSets;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const linter_1 = require("../linter");
41
+ const cliFormat_1 = require("../tester/cliFormat");
42
+ const prettyDuration_1 = require("../tester/prettyDuration");
43
+ function isPromotable(entity) {
44
+ return entity?.promotable !== false;
45
+ }
46
+ function toArray(value) {
47
+ if (typeof value === "undefined") {
48
+ return [];
49
+ }
50
+ return Array.isArray(value) ? value : [value];
51
+ }
52
+ function isPlainObject(value) {
53
+ return typeof value === "object" && value !== null && !Array.isArray(value);
54
+ }
55
+ function deepEqual(left, right) {
56
+ return JSON.stringify(left) === JSON.stringify(right);
57
+ }
58
+ function deepMergeWithPolicy(destination, source, policy, conflicts, pathSegments = []) {
59
+ if (typeof destination === "undefined")
60
+ return source;
61
+ if (typeof source === "undefined")
62
+ return destination;
63
+ const conflictPath = pathSegments.join(".") || "<root>";
64
+ if (Array.isArray(destination) && Array.isArray(source)) {
65
+ if (!deepEqual(destination, source)) {
66
+ conflicts.push({ path: conflictPath, source, destination });
67
+ }
68
+ return policy === "destination" ? destination : source;
69
+ }
70
+ if (isPlainObject(destination) && isPlainObject(source)) {
71
+ const result = { ...destination };
72
+ for (const key of Object.keys(source)) {
73
+ result[key] = deepMergeWithPolicy(result[key], source[key], policy, conflicts, [
74
+ ...pathSegments,
75
+ key,
76
+ ]);
77
+ }
78
+ return result;
79
+ }
80
+ if (!deepEqual(destination, source)) {
81
+ conflicts.push({ path: conflictPath, source, destination });
82
+ }
83
+ return policy === "destination" ? destination : source;
84
+ }
85
+ function matchesPattern(key, patterns) {
86
+ if (patterns.length === 0) {
87
+ return false;
88
+ }
89
+ return patterns.some((pattern) => {
90
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
91
+ return new RegExp(`^${escaped}$`).test(key);
92
+ });
93
+ }
94
+ function matchesTags(candidateTags, selector) {
95
+ if (!selector)
96
+ return true;
97
+ if (Array.isArray(selector)) {
98
+ return selector.some((tag) => candidateTags.includes(tag));
99
+ }
100
+ if ("or" in selector) {
101
+ return selector.or.some((tag) => candidateTags.includes(tag));
102
+ }
103
+ return selector.and.every((tag) => candidateTags.includes(tag));
104
+ }
105
+ function matchesTarget(featureKey, feature, target) {
106
+ const featureTags = feature.tags || [];
107
+ if (feature.archived === true)
108
+ return false;
109
+ if (target.tag && !featureTags.includes(target.tag))
110
+ return false;
111
+ if (target.tags && !matchesTags(featureTags, target.tags))
112
+ return false;
113
+ const included = target.includeFeatures
114
+ ? matchesPattern(featureKey, toArray(target.includeFeatures))
115
+ : true;
116
+ const excluded = target.excludeFeatures
117
+ ? matchesPattern(featureKey, toArray(target.excludeFeatures))
118
+ : false;
119
+ return included && !excluded;
120
+ }
121
+ function withoutKey(entity) {
122
+ const { key: _key, ...rest } = entity; // eslint-disable-line @typescript-eslint/no-unused-vars
123
+ return rest;
124
+ }
125
+ async function safeRead(keys, read) {
126
+ const entries = await Promise.all(keys.map(async (key) => [key, await read(key)]));
127
+ return Object.fromEntries(entries);
128
+ }
129
+ async function readDestination(key, read) {
130
+ try {
131
+ return await read(key);
132
+ }
133
+ catch {
134
+ return undefined;
135
+ }
136
+ }
137
+ function formatLintPreflightErrors(set, errors) {
138
+ const preview = errors
139
+ .slice(0, 5)
140
+ .map((error) => `${error.filePath}: ${error.message}`)
141
+ .join("\n");
142
+ const suffix = errors.length > 5 ? `\n...and ${errors.length - 5} more` : "";
143
+ return `Set "${set}" failed preflight lint with ${errors.length} error(s).\n${preview}${suffix}`;
144
+ }
145
+ async function assertSetLintsClean(set, projectConfig, datasource) {
146
+ const result = await (0, linter_1.lintProject)({
147
+ rootDirectoryPath: path.resolve(projectConfig.setsDirectoryPath, ".."),
148
+ projectConfig,
149
+ datasource,
150
+ options: {},
151
+ }, { json: true });
152
+ if (result.hasError) {
153
+ throw new Error(formatLintPreflightErrors(set, result.errors));
154
+ }
155
+ }
156
+ function assertAllowedPromotionFlow(projectConfig, from, to) {
157
+ const allowedFlows = projectConfig.promotionFlows;
158
+ if (typeof allowedFlows === "undefined") {
159
+ return;
160
+ }
161
+ const isAllowed = allowedFlows.some((flow) => flow.from === from && flow.to === to);
162
+ if (isAllowed) {
163
+ return;
164
+ }
165
+ const allowedList = allowedFlows.map((flow) => `${flow.from} -> ${flow.to}`).join(", ") || "none";
166
+ throw new Error(`Promotion from "${from}" to "${to}" is not allowed by this project's configured promotionFlows.\nAllowed flows: ${allowedList}.\nChoose one of the allowed promotion paths or update featurevisor.config.js if this flow should be permitted.`);
167
+ }
168
+ function collectGroupSegmentKeys(value, result) {
169
+ if (!value || value === "*")
170
+ return;
171
+ if (typeof value === "string") {
172
+ result.add(value);
173
+ return;
174
+ }
175
+ if (Array.isArray(value)) {
176
+ value.forEach((entry) => collectGroupSegmentKeys(entry, result));
177
+ return;
178
+ }
179
+ if ("and" in value)
180
+ value.and.forEach((entry) => collectGroupSegmentKeys(entry, result));
181
+ if ("or" in value)
182
+ value.or.forEach((entry) => collectGroupSegmentKeys(entry, result));
183
+ if ("not" in value)
184
+ value.not.forEach((entry) => collectGroupSegmentKeys(entry, result));
185
+ }
186
+ function collectConditionDependencies(value, segments, attributes) {
187
+ if (!value || value === "*")
188
+ return;
189
+ if (typeof value === "string") {
190
+ segments.add(value);
191
+ return;
192
+ }
193
+ if (Array.isArray(value)) {
194
+ value.forEach((entry) => collectConditionDependencies(entry, segments, attributes));
195
+ return;
196
+ }
197
+ if ("attribute" in value) {
198
+ attributes.add(value.attribute.split(".")[0]);
199
+ return;
200
+ }
201
+ if ("and" in value)
202
+ value.and.forEach((entry) => collectConditionDependencies(entry, segments, attributes));
203
+ if ("or" in value)
204
+ value.or.forEach((entry) => collectConditionDependencies(entry, segments, attributes));
205
+ if ("not" in value)
206
+ value.not.forEach((entry) => collectConditionDependencies(entry, segments, attributes));
207
+ }
208
+ function collectSchemaReferences(value, schemas) {
209
+ if (!value || typeof value !== "object") {
210
+ return;
211
+ }
212
+ if (Array.isArray(value)) {
213
+ value.forEach((entry) => collectSchemaReferences(entry, schemas));
214
+ return;
215
+ }
216
+ const record = value;
217
+ if (typeof record.schema === "string") {
218
+ schemas.add(record.schema);
219
+ }
220
+ Object.values(record).forEach((entry) => collectSchemaReferences(entry, schemas));
221
+ }
222
+ function collectFeatureDependencies(feature, features, featureKeys, segmentKeys, attributeKeys, schemaKeys) {
223
+ if (typeof feature.bucketBy === "string") {
224
+ attributeKeys.add(feature.bucketBy);
225
+ }
226
+ else if (Array.isArray(feature.bucketBy)) {
227
+ feature.bucketBy.forEach((attributeKey) => attributeKeys.add(attributeKey));
228
+ }
229
+ else if (feature.bucketBy && "or" in feature.bucketBy) {
230
+ feature.bucketBy.or.forEach((attributeKey) => attributeKeys.add(attributeKey));
231
+ }
232
+ collectSchemaReferences(feature.variablesSchema, schemaKeys);
233
+ for (const required of feature.required || []) {
234
+ const requiredKey = typeof required === "string" ? required : required.key;
235
+ if (!featureKeys.has(requiredKey)) {
236
+ featureKeys.add(requiredKey);
237
+ const requiredFeature = features[requiredKey];
238
+ if (requiredFeature) {
239
+ collectFeatureDependencies(requiredFeature, features, featureKeys, segmentKeys, attributeKeys, schemaKeys);
240
+ }
241
+ }
242
+ }
243
+ for (const rules of Object.values((feature.rules || {}))) {
244
+ const ruleEntries = Array.isArray(rules) ? rules : [rules];
245
+ for (const rule of ruleEntries) {
246
+ if (!isPromotable(rule)) {
247
+ continue;
248
+ }
249
+ collectGroupSegmentKeys(rule?.segments, segmentKeys);
250
+ Object.values(rule?.variableOverrides || {}).forEach((overrides) => {
251
+ (overrides || []).forEach((override) => {
252
+ collectGroupSegmentKeys(override.segments, segmentKeys);
253
+ collectConditionDependencies(override.conditions, segmentKeys, attributeKeys);
254
+ });
255
+ });
256
+ }
257
+ }
258
+ for (const force of Object.values((feature.force || {}))) {
259
+ const forceEntries = Array.isArray(force) ? force : [force];
260
+ for (const forceEntry of forceEntries) {
261
+ collectGroupSegmentKeys(forceEntry?.segments, segmentKeys);
262
+ collectConditionDependencies(forceEntry?.conditions, segmentKeys, attributeKeys);
263
+ }
264
+ }
265
+ }
266
+ function mergeRuleArray(destination, source, policy, conflicts, pathSegments) {
267
+ if (typeof source === "undefined")
268
+ return destination;
269
+ if (typeof destination === "undefined")
270
+ return source.filter((rule) => isPromotable(rule));
271
+ const mergedRuleKeys = new Set();
272
+ const mergedRules = [];
273
+ for (const sourceRule of source) {
274
+ if (!isPromotable(sourceRule)) {
275
+ continue;
276
+ }
277
+ const destinationRule = destination.find((rule) => rule.key === sourceRule.key);
278
+ mergedRuleKeys.add(sourceRule.key);
279
+ if (destinationRule && !isPromotable(destinationRule)) {
280
+ mergedRules.push(destinationRule);
281
+ continue;
282
+ }
283
+ mergedRules.push(deepMergeWithPolicy(destinationRule, sourceRule, policy, conflicts, [
284
+ ...pathSegments,
285
+ sourceRule.key,
286
+ ]));
287
+ }
288
+ for (const destinationRule of destination) {
289
+ if (!mergedRuleKeys.has(destinationRule.key)) {
290
+ mergedRules.push(destinationRule);
291
+ }
292
+ }
293
+ return mergedRules;
294
+ }
295
+ function isRulesByEnvironment(value) {
296
+ return isPlainObject(value) && !Array.isArray(value);
297
+ }
298
+ function mergeRules(destination, source, policy, conflicts) {
299
+ if (Array.isArray(source) || Array.isArray(destination)) {
300
+ return mergeRuleArray(Array.isArray(destination) ? destination : undefined, Array.isArray(source) ? source : undefined, policy, conflicts, ["rules"]);
301
+ }
302
+ if (isRulesByEnvironment(source) || isRulesByEnvironment(destination)) {
303
+ const environmentKeys = new Set([
304
+ ...Object.keys((destination || {})),
305
+ ...Object.keys((source || {})),
306
+ ]);
307
+ const result = {};
308
+ for (const environment of Array.from(environmentKeys).sort()) {
309
+ const merged = mergeRuleArray(destination?.[environment], source?.[environment], policy, conflicts, ["rules", environment]);
310
+ if (typeof merged !== "undefined") {
311
+ result[environment] = merged;
312
+ }
313
+ }
314
+ return Object.keys(result).length > 0 ? result : undefined;
315
+ }
316
+ return source;
317
+ }
318
+ function mergeFeature(featureKey, destination, source, policy, conflicts) {
319
+ const sourceWithoutRules = { ...source, rules: undefined };
320
+ const destinationWithoutRules = destination ? { ...destination, rules: undefined } : undefined;
321
+ const featureConflicts = [];
322
+ const mergedFeature = deepMergeWithPolicy(destinationWithoutRules, sourceWithoutRules, policy, featureConflicts);
323
+ const ruleConflicts = [];
324
+ const mergedRules = mergeRules(destination?.rules, source.rules, policy, ruleConflicts);
325
+ conflicts.push(...featureConflicts.map((conflict) => ({
326
+ type: "feature",
327
+ key: featureKey,
328
+ ...conflict,
329
+ })), ...ruleConflicts.map((conflict) => ({
330
+ type: "feature",
331
+ key: featureKey,
332
+ ...conflict,
333
+ })));
334
+ return {
335
+ ...mergedFeature,
336
+ rules: mergedRules,
337
+ };
338
+ }
339
+ async function getPromotionPlan(sourceDatasource, destinationDatasource, options) {
340
+ const includeFeatures = toArray(options.includeFeatures);
341
+ const excludeFeatures = toArray(options.excludeFeatures);
342
+ const targetSelectors = toArray(options.target);
343
+ const tagSelectors = toArray(options.tag);
344
+ const availableTags = sourceDatasource.getConfig().tags;
345
+ const unknownTag = tagSelectors.find((tag) => !availableTags.includes(tag));
346
+ if (unknownTag) {
347
+ throw new Error(`Unknown source tag "${unknownTag}". Available tags: ${availableTags.join(", ") || "none"}.`);
348
+ }
349
+ const hasNoFilters = includeFeatures.length === 0 &&
350
+ excludeFeatures.length === 0 &&
351
+ targetSelectors.length === 0 &&
352
+ tagSelectors.length === 0;
353
+ const [featureKeys, segmentKeys, attributeKeys, groupKeys, schemaKeys, targetKeys, testKeys] = await Promise.all([
354
+ sourceDatasource.listFeatures(),
355
+ sourceDatasource.listSegments(),
356
+ sourceDatasource.listAttributes(),
357
+ sourceDatasource.listGroups(),
358
+ sourceDatasource.listSchemas(),
359
+ sourceDatasource.listTargets(),
360
+ sourceDatasource.listTests(),
361
+ ]);
362
+ const [features, segments, attributes, groups, schemas, targets, tests] = await Promise.all([
363
+ safeRead(featureKeys, (key) => sourceDatasource.readFeature(key)),
364
+ safeRead(segmentKeys, (key) => sourceDatasource.readSegment(key)),
365
+ safeRead(attributeKeys, (key) => sourceDatasource.readAttribute(key)),
366
+ safeRead(groupKeys, (key) => sourceDatasource.readGroup(key)),
367
+ safeRead(schemaKeys, (key) => sourceDatasource.readSchema(key)),
368
+ safeRead(targetKeys, (key) => sourceDatasource.readTarget(key)),
369
+ safeRead(testKeys, (key) => sourceDatasource.readTest(key)),
370
+ ]);
371
+ const promotedFeatureKeys = new Set();
372
+ const promotedSegmentKeys = new Set();
373
+ const promotedAttributeKeys = new Set();
374
+ const promotedGroupKeys = new Set();
375
+ const promotedSchemaKeys = new Set();
376
+ const promotedTargetKeys = new Set();
377
+ const selectedTargets = [];
378
+ for (const key of targetSelectors) {
379
+ const target = targets[key];
380
+ if (!target) {
381
+ throw new Error(`Unknown source target "${key}". Available targets: ${targetKeys.join(", ") || "none"}.`);
382
+ }
383
+ selectedTargets.push(target);
384
+ promotedTargetKeys.add(key);
385
+ }
386
+ if (hasNoFilters) {
387
+ featureKeys.forEach((key) => promotedFeatureKeys.add(key));
388
+ segmentKeys.forEach((key) => promotedSegmentKeys.add(key));
389
+ attributeKeys.forEach((key) => promotedAttributeKeys.add(key));
390
+ groupKeys.forEach((key) => promotedGroupKeys.add(key));
391
+ schemaKeys.forEach((key) => promotedSchemaKeys.add(key));
392
+ targetKeys.forEach((key) => promotedTargetKeys.add(key));
393
+ }
394
+ else {
395
+ for (const key of featureKeys) {
396
+ const feature = features[key];
397
+ const matchesIncludes = includeFeatures.length === 0 || matchesPattern(key, includeFeatures);
398
+ const matchesExcludes = matchesPattern(key, excludeFeatures);
399
+ const matchesSelectedTags = tagSelectors.length === 0 || tagSelectors.some((tag) => (feature.tags || []).includes(tag));
400
+ const matchesSelectedTargets = selectedTargets.length === 0 ||
401
+ selectedTargets.some((target) => matchesTarget(key, feature, target));
402
+ if (matchesIncludes && !matchesExcludes && matchesSelectedTags && matchesSelectedTargets) {
403
+ promotedFeatureKeys.add(key);
404
+ }
405
+ }
406
+ if (promotedFeatureKeys.size === 0 && !options.allowEmpty) {
407
+ throw new Error("No source features matched the promotion filters.");
408
+ }
409
+ }
410
+ let previousFeatureCount = -1;
411
+ while (previousFeatureCount !== promotedFeatureKeys.size) {
412
+ previousFeatureCount = promotedFeatureKeys.size;
413
+ for (const key of Array.from(promotedFeatureKeys)) {
414
+ const feature = features[key];
415
+ if (feature) {
416
+ collectFeatureDependencies(feature, features, promotedFeatureKeys, promotedSegmentKeys, promotedAttributeKeys, promotedSchemaKeys);
417
+ }
418
+ }
419
+ for (const key of groupKeys) {
420
+ const group = groups[key];
421
+ const groupFeatureKeys = group.slots
422
+ .map((slot) => slot.feature)
423
+ .filter((featureKey) => typeof featureKey === "string");
424
+ if (groupFeatureKeys.some((featureKey) => promotedFeatureKeys.has(featureKey))) {
425
+ promotedGroupKeys.add(key);
426
+ groupFeatureKeys.forEach((featureKey) => promotedFeatureKeys.add(featureKey));
427
+ }
428
+ }
429
+ }
430
+ const pendingSegments = Array.from(promotedSegmentKeys);
431
+ for (let index = 0; index < pendingSegments.length; index++) {
432
+ const segmentKey = pendingSegments[index];
433
+ const segment = segments[segmentKey];
434
+ if (!segment)
435
+ continue;
436
+ const beforeSize = promotedSegmentKeys.size;
437
+ collectConditionDependencies(segment.conditions, promotedSegmentKeys, promotedAttributeKeys);
438
+ if (promotedSegmentKeys.size > beforeSize) {
439
+ pendingSegments.push(...Array.from(promotedSegmentKeys).filter((key) => !pendingSegments.includes(key)));
440
+ }
441
+ }
442
+ const pendingSchemas = Array.from(promotedSchemaKeys);
443
+ for (let index = 0; index < pendingSchemas.length; index++) {
444
+ const schemaKey = pendingSchemas[index];
445
+ const schema = schemas[schemaKey];
446
+ if (!schema)
447
+ continue;
448
+ const beforeSize = promotedSchemaKeys.size;
449
+ collectSchemaReferences(schema, promotedSchemaKeys);
450
+ if (promotedSchemaKeys.size > beforeSize) {
451
+ pendingSchemas.push(...Array.from(promotedSchemaKeys).filter((key) => !pendingSchemas.includes(key)));
452
+ }
453
+ }
454
+ const promotedTestKeys = testKeys.filter((key) => {
455
+ const test = tests[key];
456
+ return promotedFeatureKeys.has(test.feature) || promotedSegmentKeys.has(test.segment);
457
+ });
458
+ const plans = [];
459
+ async function plan(type, key, source, readDestinationEntity, merge = (destination, sourceValue, conflicts) => {
460
+ const entityConflicts = [];
461
+ const merged = deepMergeWithPolicy(destination, sourceValue, options.conflicts, entityConflicts);
462
+ conflicts.push(...entityConflicts.map((conflict) => ({ type, key, ...conflict })));
463
+ return merged;
464
+ }) {
465
+ const cleanedSource = withoutKey(source);
466
+ const destination = await readDestination(key, readDestinationEntity);
467
+ const cleanedDestination = destination ? withoutKey(destination) : undefined;
468
+ if (cleanedDestination && (!isPromotable(cleanedSource) || !isPromotable(cleanedDestination))) {
469
+ plans.push({
470
+ type,
471
+ key,
472
+ source: cleanedSource,
473
+ destination: cleanedDestination,
474
+ merged: cleanedDestination,
475
+ conflicts: [],
476
+ });
477
+ return;
478
+ }
479
+ const conflicts = [];
480
+ const merged = merge(cleanedDestination, cleanedSource, conflicts);
481
+ plans.push({
482
+ type,
483
+ key,
484
+ source: cleanedSource,
485
+ destination: cleanedDestination,
486
+ merged,
487
+ conflicts,
488
+ });
489
+ }
490
+ for (const key of Array.from(promotedAttributeKeys).sort()) {
491
+ if (attributes[key])
492
+ await plan("attribute", key, attributes[key], (entryKey) => destinationDatasource.readAttribute(entryKey));
493
+ }
494
+ for (const key of Array.from(promotedSegmentKeys).sort()) {
495
+ if (segments[key])
496
+ await plan("segment", key, segments[key], (entryKey) => destinationDatasource.readSegment(entryKey));
497
+ }
498
+ for (const key of Array.from(promotedGroupKeys).sort()) {
499
+ if (groups[key])
500
+ await plan("group", key, groups[key], (entryKey) => destinationDatasource.readGroup(entryKey));
501
+ }
502
+ for (const key of Array.from(promotedSchemaKeys).sort()) {
503
+ if (schemas[key])
504
+ await plan("schema", key, schemas[key], (entryKey) => destinationDatasource.readSchema(entryKey));
505
+ }
506
+ for (const key of Array.from(promotedFeatureKeys).sort()) {
507
+ if (features[key])
508
+ await plan("feature", key, features[key], (entryKey) => destinationDatasource.readFeature(entryKey), (destination, source, conflicts) => mergeFeature(key, destination, source, options.conflicts, conflicts));
509
+ }
510
+ for (const key of Array.from(promotedTargetKeys).sort()) {
511
+ if (targets[key])
512
+ await plan("target", key, targets[key], (entryKey) => destinationDatasource.readTarget(entryKey));
513
+ }
514
+ for (const key of promotedTestKeys.sort()) {
515
+ if (tests[key])
516
+ await plan("test", key, tests[key], (entryKey) => destinationDatasource.readTest(entryKey));
517
+ }
518
+ return plans;
519
+ }
520
+ async function writePlan(destinationDatasource, plans) {
521
+ for (const plan of plans) {
522
+ if (deepEqual(plan.destination, plan.merged)) {
523
+ continue;
524
+ }
525
+ if (plan.type === "attribute")
526
+ await destinationDatasource.writeAttribute(plan.key, plan.merged);
527
+ if (plan.type === "segment")
528
+ await destinationDatasource.writeSegment(plan.key, plan.merged);
529
+ if (plan.type === "group")
530
+ await destinationDatasource.writeGroup(plan.key, plan.merged);
531
+ if (plan.type === "schema")
532
+ await destinationDatasource.writeSchema(plan.key, plan.merged);
533
+ if (plan.type === "target")
534
+ await destinationDatasource.writeTarget(plan.key, plan.merged);
535
+ if (plan.type === "feature")
536
+ await destinationDatasource.writeFeature(plan.key, plan.merged);
537
+ if (plan.type === "test")
538
+ await destinationDatasource.writeTest(plan.key, plan.merged);
539
+ }
540
+ }
541
+ function normalizeConflictPolicy(value) {
542
+ if (typeof value === "undefined" || value === false) {
543
+ return "source";
544
+ }
545
+ if (value === "source" || value === "destination" || value === "fail") {
546
+ return value;
547
+ }
548
+ throw new Error(`Invalid --conflicts value "${String(value)}". Use source, destination, or fail.`);
549
+ }
550
+ function normalizeAuditFormat(value) {
551
+ if (typeof value === "undefined" || value === false || value === "false") {
552
+ return false;
553
+ }
554
+ if (value === true || value === "true") {
555
+ return "json";
556
+ }
557
+ if (value === "json" || value === "markdown") {
558
+ return value;
559
+ }
560
+ throw new Error(`Invalid --audit value "${String(value)}". Use json or markdown.`);
561
+ }
562
+ function getTimestamp() {
563
+ const date = new Date();
564
+ const pad = (value) => (value < 10 ? `0${value}` : String(value));
565
+ return [
566
+ date.getUTCFullYear(),
567
+ pad(date.getUTCMonth() + 1),
568
+ pad(date.getUTCDate()),
569
+ "T",
570
+ pad(date.getUTCHours()),
571
+ pad(date.getUTCMinutes()),
572
+ pad(date.getUTCSeconds()),
573
+ ].join("");
574
+ }
575
+ function getEntityFilePath(projectConfig, type, key) {
576
+ const directories = {
577
+ attribute: projectConfig.attributesDirectoryPath,
578
+ segment: projectConfig.segmentsDirectoryPath,
579
+ feature: projectConfig.featuresDirectoryPath,
580
+ group: projectConfig.groupsDirectoryPath,
581
+ schema: projectConfig.schemasDirectoryPath,
582
+ target: projectConfig.targetsDirectoryPath,
583
+ test: projectConfig.testsDirectoryPath,
584
+ };
585
+ const extension = projectConfig.parser.extension || "yml";
586
+ const pathSegments = key.split(projectConfig.namespaceCharacter);
587
+ if (type === "test" &&
588
+ pathSegments.length > 1 &&
589
+ pathSegments[pathSegments.length - 1] === "spec") {
590
+ pathSegments[pathSegments.length - 2] = `${pathSegments[pathSegments.length - 2]}.spec`;
591
+ pathSegments.pop();
592
+ }
593
+ return path.join(directories[type], ...pathSegments) + `.${extension}`;
594
+ }
595
+ function formatProjectPath(projectConfig, filePath) {
596
+ const root = path.resolve(projectConfig.setsDirectoryPath, "..");
597
+ const relative = path.relative(root, filePath);
598
+ return relative.startsWith("..") ? filePath : relative;
599
+ }
600
+ async function getPromotionAuditFilePath(projectConfig, result, format) {
601
+ const extension = format === "markdown" ? "md" : "json";
602
+ const baseFileName = `${getTimestamp()}-${result.from}-to-${result.to}`;
603
+ const directoryPath = path.join(projectConfig.stateDirectoryPath, "promotions");
604
+ let suffix = 0;
605
+ while (true) {
606
+ const fileName = `${baseFileName}${suffix === 0 ? "" : `-${suffix}`}.${extension}`;
607
+ const filePath = path.join(directoryPath, fileName);
608
+ if (!fs.existsSync(filePath)) {
609
+ return filePath;
610
+ }
611
+ suffix++;
612
+ }
613
+ }
614
+ function getAuditPayload(result) {
615
+ return {
616
+ from: result.from,
617
+ to: result.to,
618
+ apply: result.apply,
619
+ filters: result.filters,
620
+ dependencies: result.dependencies,
621
+ files: result.files,
622
+ conflicts: result.conflicts.map((conflict) => ({
623
+ type: conflict.type,
624
+ key: conflict.key,
625
+ path: conflict.path,
626
+ source: conflict.source,
627
+ destination: conflict.destination,
628
+ })),
629
+ duration: result.duration,
630
+ };
631
+ }
632
+ function stringifyMarkdownAudit(result) {
633
+ const lines = [
634
+ "# Featurevisor Promotion",
635
+ "",
636
+ `- From: ${result.from}`,
637
+ `- To: ${result.to}`,
638
+ `- Mode: ${result.apply ? "apply" : "preview"}`,
639
+ `- Conflicts: ${result.filters.conflicts}`,
640
+ `- Duration: ${(0, prettyDuration_1.prettyDuration)(result.duration)}`,
641
+ "",
642
+ "## Dependencies",
643
+ "",
644
+ `- Attributes: ${result.dependencies.attribute}`,
645
+ `- Segments: ${result.dependencies.segment}`,
646
+ `- Features: ${result.dependencies.feature}`,
647
+ `- Groups: ${result.dependencies.group}`,
648
+ `- Schemas: ${result.dependencies.schema}`,
649
+ `- Targets: ${result.dependencies.target}`,
650
+ `- Tests: ${result.dependencies.test}`,
651
+ "",
652
+ "## Files",
653
+ "",
654
+ ];
655
+ for (const [label, files] of [
656
+ ["Created", result.files.created],
657
+ ["Updated", result.files.updated],
658
+ ["Unchanged", result.files.unchanged],
659
+ ]) {
660
+ lines.push(`### ${label}`, "");
661
+ lines.push(...(files.length > 0 ? files.map((filePath) => `- ${filePath}`) : ["- None"]));
662
+ lines.push("");
663
+ }
664
+ if (result.conflicts.length > 0) {
665
+ lines.push("## Conflicts", "");
666
+ lines.push(...result.conflicts.map((conflict) => `- ${conflict.type} ${conflict.key} at ${conflict.path}`));
667
+ lines.push("");
668
+ }
669
+ return `${lines.join("\n")}\n`;
670
+ }
671
+ async function writePromotionAudit(projectConfig, result, format) {
672
+ const filePath = await getPromotionAuditFilePath(projectConfig, result, format);
673
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
674
+ const content = format === "markdown"
675
+ ? stringifyMarkdownAudit(result)
676
+ : `${JSON.stringify(getAuditPayload(result), null, 2)}\n`;
677
+ await fs.promises.writeFile(filePath, content);
678
+ return formatProjectPath(projectConfig, filePath);
679
+ }
680
+ async function promoteProjectSets(projectConfig, datasource, options) {
681
+ const startTime = Date.now();
682
+ const conflictPolicy = normalizeConflictPolicy(options.conflicts);
683
+ const auditFormat = normalizeAuditFormat(options.audit);
684
+ if (!projectConfig.sets) {
685
+ throw new Error("Promotion is only available when `sets: true` is configured.");
686
+ }
687
+ if (!options.from)
688
+ throw new Error("Pass --from=<set>.");
689
+ if (!options.to)
690
+ throw new Error("Pass --to=<set>.");
691
+ if (options.from === options.to)
692
+ throw new Error("--from and --to must be different sets.");
693
+ const sets = await datasource.listSets();
694
+ if (!sets.includes(options.from)) {
695
+ throw new Error(`Unknown source set "${options.from}". Available sets: ${sets.join(", ") || "none"}.`);
696
+ }
697
+ if (!sets.includes(options.to)) {
698
+ throw new Error(`Unknown destination set "${options.to}". Available sets: ${sets.join(", ") || "none"}.`);
699
+ }
700
+ assertAllowedPromotionFlow(projectConfig, options.from, options.to);
701
+ const sourceDatasource = datasource.forSet(options.from);
702
+ const destinationDatasource = datasource.forSet(options.to);
703
+ await assertSetLintsClean(options.from, sourceDatasource.getConfig(), sourceDatasource);
704
+ await assertSetLintsClean(options.to, destinationDatasource.getConfig(), destinationDatasource);
705
+ const plans = await getPromotionPlan(sourceDatasource, destinationDatasource, {
706
+ includeFeatures: options.includeFeatures || [],
707
+ excludeFeatures: options.excludeFeatures || [],
708
+ target: options.target || [],
709
+ tag: options.tag || [],
710
+ allowEmpty: options.allowEmpty === true,
711
+ conflicts: conflictPolicy,
712
+ });
713
+ const conflicts = plans.flatMap((plan) => plan.conflicts);
714
+ if (conflictPolicy === "fail" && conflicts.length > 0) {
715
+ const preview = conflicts
716
+ .slice(0, 5)
717
+ .map((conflict) => `${conflict.type} "${conflict.key}" at ${conflict.path}`)
718
+ .join("\n");
719
+ const suffix = conflicts.length > 5 ? `\n...and ${conflicts.length - 5} more` : "";
720
+ throw new Error(`Promotion has ${conflicts.length} conflict(s) and --conflicts=fail was used.\n${preview}${suffix}`);
721
+ }
722
+ if (options.apply === true) {
723
+ await writePlan(destinationDatasource, plans);
724
+ }
725
+ const created = plans
726
+ .filter((plan) => !plan.destination)
727
+ .map((plan) => formatProjectPath(projectConfig, getEntityFilePath(destinationDatasource.getConfig(), plan.type, plan.key)));
728
+ const updated = plans
729
+ .filter((plan) => plan.destination && !deepEqual(plan.destination, plan.merged))
730
+ .map((plan) => formatProjectPath(projectConfig, getEntityFilePath(destinationDatasource.getConfig(), plan.type, plan.key)));
731
+ const unchanged = plans
732
+ .filter((plan) => plan.destination && deepEqual(plan.destination, plan.merged))
733
+ .map((plan) => formatProjectPath(projectConfig, getEntityFilePath(destinationDatasource.getConfig(), plan.type, plan.key)));
734
+ const dependencies = {
735
+ attribute: plans.filter((plan) => plan.type === "attribute").length,
736
+ segment: plans.filter((plan) => plan.type === "segment").length,
737
+ feature: plans.filter((plan) => plan.type === "feature").length,
738
+ group: plans.filter((plan) => plan.type === "group").length,
739
+ schema: plans.filter((plan) => plan.type === "schema").length,
740
+ target: plans.filter((plan) => plan.type === "target").length,
741
+ test: plans.filter((plan) => plan.type === "test").length,
742
+ };
743
+ const result = {
744
+ from: options.from,
745
+ to: options.to,
746
+ apply: options.apply === true,
747
+ duration: Date.now() - startTime,
748
+ filters: {
749
+ includeFeatures: toArray(options.includeFeatures),
750
+ excludeFeatures: toArray(options.excludeFeatures),
751
+ targets: toArray(options.target),
752
+ tags: toArray(options.tag),
753
+ conflicts: conflictPolicy,
754
+ },
755
+ dependencies,
756
+ files: { created, updated, unchanged },
757
+ conflicts,
758
+ };
759
+ if (result.apply && auditFormat) {
760
+ result.auditFilePath = await writePromotionAudit(projectConfig, result, auditFormat);
761
+ }
762
+ return result;
763
+ }
764
+ function printFileGroup(label, files, color) {
765
+ if (files.length === 0) {
766
+ return;
767
+ }
768
+ console.log(cliFormat_1.CLI_FORMAT_BOLD, label);
769
+ for (const filePath of files) {
770
+ console.log(` ${(0, cliFormat_1.colorize)(filePath, color)}`);
771
+ }
772
+ console.log("");
773
+ }
774
+ function printConflictPreview(conflicts) {
775
+ if (conflicts.length === 0) {
776
+ return;
777
+ }
778
+ console.log(cliFormat_1.CLI_FORMAT_BOLD, "Conflicts");
779
+ for (const conflict of conflicts.slice(0, 10)) {
780
+ console.log(` ${(0, cliFormat_1.colorize)(conflict.type, cliFormat_1.CLI_COLOR_YELLOW)} ${conflict.key} ${(0, cliFormat_1.colorize)(conflict.path, cliFormat_1.CLI_COLOR_DIM)}`);
781
+ }
782
+ if (conflicts.length > 10) {
783
+ console.log(` ${(0, cliFormat_1.colorize)(`...and ${conflicts.length - 10} more`, cliFormat_1.CLI_COLOR_DIM)}`);
784
+ }
785
+ console.log("");
786
+ }
787
+ function printPromoteResult(result, options = {}) {
788
+ console.log("");
789
+ console.log(cliFormat_1.CLI_FORMAT_BOLD, "Promoting Featurevisor set definitions");
790
+ console.log(` From: ${result.from}`);
791
+ console.log(` To: ${result.to}`);
792
+ console.log(` Mode: ${result.apply ? "apply" : "preview"}`);
793
+ if (result.filters.includeFeatures.length > 0) {
794
+ console.log(` Include features: ${result.filters.includeFeatures.join(", ")}`);
795
+ }
796
+ if (result.filters.excludeFeatures.length > 0) {
797
+ console.log(` Exclude features: ${result.filters.excludeFeatures.join(", ")}`);
798
+ }
799
+ if (result.filters.targets.length > 0) {
800
+ console.log(` Targets: ${result.filters.targets.join(", ")}`);
801
+ }
802
+ if (result.filters.tags.length > 0) {
803
+ console.log(` Tags: ${result.filters.tags.join(", ")}`);
804
+ }
805
+ console.log(` Conflict policy: ${result.filters.conflicts}`);
806
+ console.log("");
807
+ console.log(` Dependencies: ${result.dependencies.attribute} attributes, ${result.dependencies.segment} segments, ${result.dependencies.feature} features, ${result.dependencies.group} groups, ${result.dependencies.schema} schemas, ${result.dependencies.target} targets, ${result.dependencies.test} tests`);
808
+ console.log(` Created: ${result.files.created.length}`);
809
+ console.log(` Updated: ${result.files.updated.length}`);
810
+ console.log(` Unchanged: ${result.files.unchanged.length}`);
811
+ console.log(` Conflicts: ${result.conflicts.length}`);
812
+ console.log("");
813
+ printFileGroup("Created", result.files.created, cliFormat_1.CLI_COLOR_GREEN);
814
+ printFileGroup("Updated", result.files.updated, cliFormat_1.CLI_COLOR_YELLOW);
815
+ if (options.showUnchanged === true) {
816
+ printFileGroup("Unchanged", result.files.unchanged, cliFormat_1.CLI_COLOR_DIM);
817
+ }
818
+ printConflictPreview(result.conflicts);
819
+ if (result.auditFilePath) {
820
+ console.log(` Audit: ${(0, cliFormat_1.colorize)(result.auditFilePath, cliFormat_1.CLI_COLOR_CYAN)}`);
821
+ console.log("");
822
+ }
823
+ console.log(cliFormat_1.CLI_FORMAT_GREEN, result.apply ? "Promotion applied" : "Promotion preview complete");
824
+ console.log(cliFormat_1.CLI_FORMAT_BOLD, `Time: ${(0, prettyDuration_1.prettyDuration)(result.duration)}`);
825
+ }
826
+ exports.promotePlugin = {
827
+ command: "promote",
828
+ handler: async ({ projectConfig, datasource, parsed }) => {
829
+ const result = await promoteProjectSets(projectConfig, datasource, {
830
+ from: parsed.from,
831
+ to: parsed.to,
832
+ includeFeatures: parsed.includeFeatures,
833
+ excludeFeatures: parsed.excludeFeatures,
834
+ target: parsed.target,
835
+ tag: parsed.tag,
836
+ conflicts: parsed.conflicts,
837
+ allowEmpty: parsed.allowEmpty === true || parsed.allowEmpty === "true",
838
+ apply: parsed.apply === true || parsed.apply === "true",
839
+ audit: parsed.audit,
840
+ });
841
+ printPromoteResult(result, {
842
+ showUnchanged: parsed.showUnchanged === true || parsed.showUnchanged === "true",
843
+ });
844
+ },
845
+ examples: [
846
+ {
847
+ command: "promote --from=dev --to=staging",
848
+ description: "preview definitions that can be promoted from one set to another",
849
+ },
850
+ {
851
+ command: "promote --from=dev --to=staging --target=web",
852
+ description: "preview the target and every definition needed to build its datafile",
853
+ },
854
+ {
855
+ command: "promote --from=dev --to=staging --tag=web",
856
+ description: "preview definitions affecting features with a tag",
857
+ },
858
+ {
859
+ command: 'promote --from=dev --to=staging --includeFeatures="checkout*"',
860
+ description: "preview definitions affecting matching features",
861
+ },
862
+ {
863
+ command: "promote --from=dev --to=staging --conflicts=fail",
864
+ description: "fail instead of overwriting conflicting destination fields",
865
+ },
866
+ {
867
+ command: "promote --from=dev --to=staging --apply",
868
+ description: "apply a promotion and write destination files",
869
+ },
870
+ {
871
+ command: "promote --from=dev --to=staging --apply --audit=markdown",
872
+ description: "write a promotion audit file",
873
+ },
874
+ {
875
+ command: "promote --from=dev --to=staging --showUnchanged",
876
+ description: "preview a promotion and include unchanged entries",
877
+ },
878
+ ],
879
+ };
880
+ //# sourceMappingURL=index.js.map