@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
@@ -16,15 +16,17 @@ function minimalProjectConfig(): ProjectConfig {
16
16
  attributesDirectoryPath: "",
17
17
  groupsDirectoryPath: "",
18
18
  schemasDirectoryPath: "",
19
+ targetsDirectoryPath: "",
19
20
  testsDirectoryPath: "",
20
21
  stateDirectoryPath: "",
21
22
  datafilesDirectoryPath: "",
22
23
  datafileNamePattern: "",
23
24
  revisionFileName: "",
24
- siteExportDirectoryPath: "",
25
- environmentsDirectoryPath: "",
25
+ catalogDirectoryPath: "",
26
+ setsDirectoryPath: "",
26
27
  environments: ["staging", "production"],
27
- splitByEnvironment: false,
28
+ sets: false,
29
+ namespaceCharacter: ".",
28
30
  tags: ["all"],
29
31
  adapter: {},
30
32
  plugins: [],
@@ -389,6 +391,18 @@ describe("conditionSchema.ts :: getConditionsZodSchema", () => {
389
391
  );
390
392
  });
391
393
 
394
+ it("rejects duplicate regexFlags", () => {
395
+ expectConditionsFailure(
396
+ {
397
+ attribute: "browser.version",
398
+ operator: "matches",
399
+ value: "x",
400
+ regexFlags: "ii",
401
+ },
402
+ "unique",
403
+ );
404
+ });
405
+
392
406
  it("rejects regexFlags when operator is not matches/notMatches", () => {
393
407
  expectConditionsFailure(
394
408
  {
@@ -433,6 +447,12 @@ describe("conditionSchema.ts :: getConditionsZodSchema", () => {
433
447
  });
434
448
  });
435
449
 
450
+ it("rejects empty and/or/not condition arrays", () => {
451
+ expectConditionsFailure({ and: [] });
452
+ expectConditionsFailure({ or: [] });
453
+ expectConditionsFailure({ not: [] });
454
+ });
455
+
436
456
  it("rejects invalid nested conditions", () => {
437
457
  expectConditionsFailure(
438
458
  {
@@ -578,10 +578,10 @@ export function getConditionsZodSchema(
578
578
  return true;
579
579
  }
580
580
 
581
- return /^[gimsuy]{1,}$/.test(value);
581
+ return /^[gimsuy]+$/.test(value) && new Set(value).size === value.length;
582
582
  },
583
583
  {
584
- message: `regexFlags must of one or more of these characters: g, i, m, s, u, y`,
584
+ message: `regexFlags must contain unique characters from: g, i, m, s, u, y`,
585
585
  },
586
586
  )
587
587
  .optional(),
@@ -687,17 +687,17 @@ export function getConditionsZodSchema(
687
687
  const andOrNotConditionZodSchema = z.union([
688
688
  z
689
689
  .object({
690
- and: z.array(z.lazy(() => conditionZodSchema)),
690
+ and: z.array(z.lazy(() => conditionZodSchema)).min(1),
691
691
  })
692
692
  .strict(),
693
693
  z
694
694
  .object({
695
- or: z.array(z.lazy(() => conditionZodSchema)),
695
+ or: z.array(z.lazy(() => conditionZodSchema)).min(1),
696
696
  })
697
697
  .strict(),
698
698
  z
699
699
  .object({
700
- not: z.array(z.lazy(() => conditionZodSchema)),
700
+ not: z.array(z.lazy(() => conditionZodSchema)).min(1),
701
701
  })
702
702
  .strict(),
703
703
  ]);
@@ -19,15 +19,17 @@ function minimalProjectConfig(overrides: Partial<ProjectConfig> = {}): ProjectCo
19
19
  attributesDirectoryPath: "",
20
20
  groupsDirectoryPath: "",
21
21
  schemasDirectoryPath: "",
22
+ targetsDirectoryPath: "",
22
23
  testsDirectoryPath: "",
23
24
  stateDirectoryPath: "",
24
25
  datafilesDirectoryPath: "",
25
26
  datafileNamePattern: "",
26
27
  revisionFileName: "",
27
- siteExportDirectoryPath: "",
28
- environmentsDirectoryPath: "",
28
+ catalogDirectoryPath: "",
29
+ setsDirectoryPath: "",
29
30
  environments: ["staging", "production"],
30
- splitByEnvironment: false,
31
+ sets: false,
32
+ namespaceCharacter: ".",
31
33
  tags: ["all"],
32
34
  adapter: {},
33
35
  plugins: [],
@@ -80,7 +82,7 @@ const TEST_ATTRIBUTE_KEYS: [string, ...string[]] = [
80
82
  "version",
81
83
  "traits",
82
84
  ];
83
- const TEST_SEGMENTS: [string, ...string[]] = ["*", "countries/germany", "countries/france"];
85
+ const TEST_SEGMENTS: [string, ...string[]] = ["*", "countries.germany", "countries.france"];
84
86
  const TEST_FEATURES: [string, ...string[]] = ["testFeature"];
85
87
  const TEST_SCHEMA_KEYS = ["link", "slugSchema"];
86
88
 
@@ -107,8 +109,8 @@ const TEST_SCHEMAS_BY_KEY: Record<string, Schema> = {
107
109
  slugSchema: slugSchemaResolved,
108
110
  };
109
111
 
110
- function getFeatureSchema() {
111
- const projectConfig = minimalProjectConfig();
112
+ function getFeatureSchema(projectConfigOverrides: Partial<ProjectConfig> = {}) {
113
+ const projectConfig = minimalProjectConfig(projectConfigOverrides);
112
114
  const conditionsZodSchema = getConditionsZodSchema(projectConfig, TEST_ATTRIBUTES);
113
115
  return getFeatureZodSchema(
114
116
  projectConfig,
@@ -135,12 +137,18 @@ function baseFeature(overrides: Record<string, unknown> = {}) {
135
137
  };
136
138
  }
137
139
 
138
- function parseFeature(feature: unknown): z.ZodSafeParseResult<unknown> {
139
- return getFeatureSchema().safeParse(feature);
140
+ function parseFeature(
141
+ feature: unknown,
142
+ projectConfigOverrides: Partial<ProjectConfig> = {},
143
+ ): z.ZodSafeParseResult<unknown> {
144
+ return getFeatureSchema(projectConfigOverrides).safeParse(feature);
140
145
  }
141
146
 
142
- function expectParseSuccess(feature: unknown): void {
143
- const result = parseFeature(feature);
147
+ function expectParseSuccess(
148
+ feature: unknown,
149
+ projectConfigOverrides: Partial<ProjectConfig> = {},
150
+ ): void {
151
+ const result = parseFeature(feature, projectConfigOverrides);
144
152
  expect(result.success).toBe(true);
145
153
  if (!result.success) {
146
154
  const err = (result as z.ZodSafeParseError<unknown>).error;
@@ -149,8 +157,12 @@ function expectParseSuccess(feature: unknown): void {
149
157
  }
150
158
  }
151
159
 
152
- function expectParseFailure(feature: unknown, messageSubstring?: string): z.ZodError {
153
- const result = parseFeature(feature);
160
+ function expectParseFailure(
161
+ feature: unknown,
162
+ messageSubstring?: string,
163
+ projectConfigOverrides: Partial<ProjectConfig> = {},
164
+ ): z.ZodError {
165
+ const result = parseFeature(feature, projectConfigOverrides);
154
166
  expect(result.success).toBe(false);
155
167
  if (result.success) throw new Error("Expected parse failure");
156
168
  const err = (result as z.ZodSafeParseError<unknown>).error;
@@ -177,6 +189,58 @@ function expectErrorSurfaces(
177
189
  }
178
190
 
179
191
  describe("featureSchema.ts :: getFeatureZodSchema (variablesSchema and variable values)", () => {
192
+ describe("tags", () => {
193
+ it("accepts missing tags", () => {
194
+ const feature = baseFeature();
195
+ delete feature.tags;
196
+
197
+ expectParseSuccess(feature);
198
+ });
199
+
200
+ it("accepts tags defined in project config", () => {
201
+ expectParseSuccess(baseFeature({ tags: ["all", "beta"] }), { tags: ["all", "beta"] });
202
+ });
203
+
204
+ it("rejects tags missing from project config", () => {
205
+ expectParseFailure(baseFeature({ tags: ["unknown"] }), 'Unknown tag "unknown"');
206
+ });
207
+
208
+ it("rejects duplicate tags", () => {
209
+ expectParseFailure(baseFeature({ tags: ["all", "all"] }), "Duplicate tags found: all, all");
210
+ });
211
+ });
212
+
213
+ describe("rule segments", () => {
214
+ it("rejects empty and/or/not group segment arrays", () => {
215
+ expectParseFailure(
216
+ baseFeature({
217
+ rules: {
218
+ staging: [{ key: "r1", segments: { and: [] }, percentage: 100 }],
219
+ production: [{ key: "r1", segments: "*", percentage: 100 }],
220
+ },
221
+ }),
222
+ );
223
+
224
+ expectParseFailure(
225
+ baseFeature({
226
+ rules: {
227
+ staging: [{ key: "r1", segments: { or: [] }, percentage: 100 }],
228
+ production: [{ key: "r1", segments: "*", percentage: 100 }],
229
+ },
230
+ }),
231
+ );
232
+
233
+ expectParseFailure(
234
+ baseFeature({
235
+ rules: {
236
+ staging: [{ key: "r1", segments: { not: [] }, percentage: 100 }],
237
+ production: [{ key: "r1", segments: "*", percentage: 100 }],
238
+ },
239
+ }),
240
+ );
241
+ });
242
+ });
243
+
180
244
  describe("attribute-aware rule and force conditions", () => {
181
245
  it("accepts force conditions using semver operators on oneOf attributes", () => {
182
246
  expectParseSuccess(
@@ -659,8 +723,8 @@ describe("featureSchema.ts :: getFeatureZodSchema (variablesSchema and variable
659
723
  variables: { slug: "treatment" },
660
724
  variableOverrides: {
661
725
  slug: [
662
- { segments: "countries/germany", value: "de" },
663
- { segments: "countries/france", value: "fr" },
726
+ { segments: "countries.germany", value: "de" },
727
+ { segments: "countries.france", value: "fr" },
664
728
  ],
665
729
  },
666
730
  },
@@ -777,7 +841,7 @@ describe("featureSchema.ts :: getFeatureZodSchema (variablesSchema and variable
777
841
  { key: "r1", segments: "*", percentage: 100 },
778
842
  {
779
843
  key: "r2",
780
- segments: "countries/germany",
844
+ segments: "countries.germany",
781
845
  percentage: 100,
782
846
  variables: { title: "Germany" },
783
847
  },
@@ -799,7 +863,7 @@ describe("featureSchema.ts :: getFeatureZodSchema (variablesSchema and variable
799
863
  { key: "r1", segments: "*", percentage: 100 },
800
864
  {
801
865
  key: "r2",
802
- segments: "countries/germany",
866
+ segments: "countries.germany",
803
867
  percentage: 100,
804
868
  variables: { count: 100 },
805
869
  },
@@ -822,7 +886,7 @@ describe("featureSchema.ts :: getFeatureZodSchema (variablesSchema and variable
822
886
  { key: "r1", segments: "*", percentage: 100 },
823
887
  {
824
888
  key: "r2",
825
- segments: "countries/germany",
889
+ segments: "countries.germany",
826
890
  percentage: 100,
827
891
  variables: { unknownVar: "x" },
828
892
  },
@@ -885,7 +949,7 @@ describe("featureSchema.ts :: getFeatureZodSchema (variablesSchema and variable
885
949
  variableOverrides: {
886
950
  config: [
887
951
  {
888
- segments: "countries/germany",
952
+ segments: "countries.germany",
889
953
  value: {
890
954
  "nested.count": 5,
891
955
  },
@@ -1100,6 +1164,21 @@ describe("featureSchema.ts :: getFeatureZodSchema (variablesSchema and variable
1100
1164
  });
1101
1165
 
1102
1166
  describe("environment maps", () => {
1167
+ it("allows direct rules, force, and expose when environments are omitted", () => {
1168
+ expectParseSuccess(
1169
+ baseFeature({
1170
+ expose: true,
1171
+ force: [{ segments: "*", variation: "control" }],
1172
+ variations: [
1173
+ { value: "control", weight: 50 },
1174
+ { value: "treatment", weight: 50 },
1175
+ ],
1176
+ rules: [{ key: "r1", segments: "*", percentage: 100 }],
1177
+ }),
1178
+ { environments: undefined },
1179
+ );
1180
+ });
1181
+
1103
1182
  it("allows a subset of configured environments in rules, force, and expose", () => {
1104
1183
  expectParseSuccess(
1105
1184
  baseFeature({
@@ -1120,6 +1199,16 @@ describe("featureSchema.ts :: getFeatureZodSchema (variablesSchema and variable
1120
1199
  );
1121
1200
  });
1122
1201
 
1202
+ it("rejects direct rules when environments are configured", () => {
1203
+ const err = expectParseFailure(
1204
+ baseFeature({
1205
+ rules: [{ key: "r1", segments: "*", percentage: 100 }],
1206
+ }),
1207
+ );
1208
+
1209
+ expect(err.issues.some((issue) => issue.path.join(".").includes("rules"))).toBe(true);
1210
+ });
1211
+
1123
1212
  it("rejects unknown environment keys in rules", () => {
1124
1213
  const err = expectParseFailure(
1125
1214
  baseFeature({
@@ -14,8 +14,6 @@ import {
14
14
  } from "./schema";
15
15
  import { refineWithMessage } from "./zodHelpers";
16
16
 
17
- const tagRegex = /^[a-z0-9-]+$/;
18
-
19
17
  function isArrayOfStrings(value: unknown): value is string[] {
20
18
  return Array.isArray(value) && value.every((v) => typeof v === "string");
21
19
  }
@@ -1324,17 +1322,17 @@ export function getFeatureZodSchema(
1324
1322
  const andOrNotGroupSegment = z.union([
1325
1323
  z
1326
1324
  .object({
1327
- and: z.array(z.lazy(() => groupSegmentZodSchema)),
1325
+ and: z.array(z.lazy(() => groupSegmentZodSchema)).min(1),
1328
1326
  })
1329
1327
  .strict(),
1330
1328
  z
1331
1329
  .object({
1332
- or: z.array(z.lazy(() => groupSegmentZodSchema)),
1330
+ or: z.array(z.lazy(() => groupSegmentZodSchema)).min(1),
1333
1331
  })
1334
1332
  .strict(),
1335
1333
  z
1336
1334
  .object({
1337
- not: z.array(z.lazy(() => groupSegmentZodSchema)),
1335
+ not: z.array(z.lazy(() => groupSegmentZodSchema)).min(1),
1338
1336
  })
1339
1337
  .strict(),
1340
1338
  ]);
@@ -1353,6 +1351,7 @@ export function getFeatureZodSchema(
1353
1351
  .object({
1354
1352
  key: z.string(),
1355
1353
  description: z.string().optional(),
1354
+ promotable: z.boolean().optional(),
1356
1355
  segments: groupSegmentsZodSchema,
1357
1356
  percentage: z.number().min(0).max(100),
1358
1357
 
@@ -1456,21 +1455,23 @@ export function getFeatureZodSchema(
1456
1455
  (value) => `Unknown feature "${value}"`,
1457
1456
  );
1458
1457
 
1459
- const environmentKeys = projectConfig.environments || [];
1458
+ const environmentKeys = Array.isArray(projectConfig.environments)
1459
+ ? projectConfig.environments
1460
+ : [];
1460
1461
 
1461
1462
  const featureZodSchema = z
1462
1463
  .object({
1463
1464
  archived: z.boolean().optional(),
1464
1465
  deprecated: z.boolean().optional(),
1466
+ promotable: z.boolean().optional(),
1465
1467
  description: z.string(),
1466
1468
 
1467
1469
  tags: z
1468
1470
  .array(
1469
1471
  refineWithMessage(
1470
1472
  z.string(),
1471
- (value) => tagRegex.test(value),
1472
- (value) =>
1473
- `Tag "${value}" must be lower cased and alphanumeric, and may contain hyphens.`,
1473
+ (value) => projectConfig.tags.includes(value),
1474
+ (value) => `Unknown tag "${value}"`,
1474
1475
  ),
1475
1476
  )
1476
1477
  .superRefine((value, ctx) => {
@@ -1480,7 +1481,8 @@ export function getFeatureZodSchema(
1480
1481
  message: "Duplicate tags found: " + value.join(", "),
1481
1482
  });
1482
1483
  }
1483
- }),
1484
+ })
1485
+ .optional(),
1484
1486
 
1485
1487
  required: z
1486
1488
  .array(
@@ -1675,24 +1677,19 @@ export function getFeatureZodSchema(
1675
1677
  })
1676
1678
  .optional(),
1677
1679
 
1678
- expose:
1679
- projectConfig.environments === false
1680
- ? exposeSchema.optional()
1681
- : z
1682
- .partialRecord(z.enum(environmentKeys as [string, ...string[]]), exposeSchema)
1683
- .optional(),
1684
-
1685
- force:
1686
- projectConfig.environments === false
1687
- ? forceSchema
1688
- : z
1689
- .partialRecord(z.enum(environmentKeys as [string, ...string[]]), forceSchema)
1690
- .optional(),
1691
-
1692
- rules:
1693
- projectConfig.environments === false
1694
- ? rulesSchema
1695
- : z.partialRecord(z.enum(environmentKeys as [string, ...string[]]), rulesSchema),
1680
+ expose: !Array.isArray(projectConfig.environments)
1681
+ ? exposeSchema.optional()
1682
+ : z
1683
+ .partialRecord(z.enum(environmentKeys as [string, ...string[]]), exposeSchema)
1684
+ .optional(),
1685
+
1686
+ force: !Array.isArray(projectConfig.environments)
1687
+ ? forceSchema
1688
+ : z.partialRecord(z.enum(environmentKeys as [string, ...string[]]), forceSchema).optional(),
1689
+
1690
+ rules: !Array.isArray(projectConfig.environments)
1691
+ ? rulesSchema
1692
+ : z.partialRecord(z.enum(environmentKeys as [string, ...string[]]), rulesSchema),
1696
1693
  })
1697
1694
  .strict()
1698
1695
  .superRefine((value, ctx) => {
@@ -12,6 +12,7 @@ export function getGroupZodSchema(
12
12
  const groupZodSchema = z
13
13
  .object({
14
14
  description: z.string(),
15
+ promotable: z.boolean().optional(),
15
16
  slots: z
16
17
  .array(
17
18
  z
@@ -15,58 +15,10 @@ function createTempProjectFromExample1() {
15
15
  return tempRoot;
16
16
  }
17
17
 
18
- function createTempSplitProject() {
19
- const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "featurevisor-lint-split-"));
20
-
21
- fs.writeFileSync(
22
- path.join(tempRoot, "featurevisor.config.js"),
23
- `
24
- module.exports = {
25
- environments: ['staging', 'production'],
26
- splitByEnvironment: true,
27
- tags: ['all'],
28
- };
29
- `.trimStart(),
30
- "utf8",
31
- );
32
-
33
- fs.mkdirSync(path.join(tempRoot, "features"), { recursive: true });
34
- fs.mkdirSync(path.join(tempRoot, "environments", "staging"), { recursive: true });
35
- fs.mkdirSync(path.join(tempRoot, "environments", "production"), { recursive: true });
36
-
37
- fs.writeFileSync(
38
- path.join(tempRoot, "features", "foo.yml"),
39
- `
40
- key: foo
41
- description: Foo
42
- tags:
43
- - all
44
- bucketBy: userId
45
- `.trimStart(),
46
- "utf8",
47
- );
48
-
49
- fs.writeFileSync(
50
- path.join(tempRoot, "environments", "staging", "foo.yml"),
51
- `
52
- rules:
53
- - key: everyone
54
- segments: '*'
55
- percentage: 100
56
- `.trimStart(),
57
- "utf8",
58
- );
59
-
60
- fs.writeFileSync(
61
- path.join(tempRoot, "environments", "production", "foo.yml"),
62
- `
63
- rules:
64
- - key: everyone
65
- segments: '*'
66
- percentage: 0
67
- `.trimStart(),
68
- "utf8",
69
- );
18
+ function createTempProject(configBody = "module.exports = {};") {
19
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "featurevisor-lint-"));
20
+
21
+ fs.writeFileSync(path.join(tempRoot, "featurevisor.config.js"), configBody, "utf8");
70
22
 
71
23
  return tempRoot;
72
24
  }
@@ -109,6 +61,132 @@ describe("core: lintProject", function () {
109
61
  });
110
62
  });
111
63
 
64
+ it("rejects entity file names containing the namespace character", async () => {
65
+ const root = createTempProject();
66
+ tempProjectPath = root;
67
+
68
+ fs.mkdirSync(path.join(root, "segments"), { recursive: true });
69
+ fs.writeFileSync(
70
+ path.join(root, "segments", "version_5.5.yml"),
71
+ [
72
+ "description: Version 5.5",
73
+ "conditions:",
74
+ " - attribute: version",
75
+ " operator: semverEquals",
76
+ " value: 5.5.0",
77
+ "",
78
+ ].join("\n"),
79
+ );
80
+
81
+ const result = await lintProject(getDeps(root) as any, { json: true });
82
+
83
+ expect(result.hasError).toBe(true);
84
+ expect(result.errors).toEqual(
85
+ expect.arrayContaining([
86
+ expect.objectContaining({
87
+ entityType: "segment",
88
+ filePath: expect.stringContaining(path.join("segments", "version_5.5.yml")),
89
+ message: 'Invalid file or directory name: "version_5.5.yml"',
90
+ code: "invalid_name",
91
+ }),
92
+ ]),
93
+ );
94
+ });
95
+
96
+ it("allows established test file suffixes with the namespace character", async () => {
97
+ const root = createTempProject();
98
+ tempProjectPath = root;
99
+
100
+ fs.mkdirSync(path.join(root, "tests", "features"), { recursive: true });
101
+ fs.writeFileSync(
102
+ path.join(root, "tests", "features", "checkout.spec.yml"),
103
+ ["feature: checkout", "assertions: []", ""].join("\n"),
104
+ "utf8",
105
+ );
106
+
107
+ const result = await lintProject(getDeps(root) as any, { json: true, entityType: "test" });
108
+
109
+ expect(
110
+ result.errors.some(
111
+ (error) =>
112
+ error.code === "invalid_name" &&
113
+ error.filePath.includes(path.join("tests", "features", "checkout.spec.yml")),
114
+ ),
115
+ ).toBe(false);
116
+ });
117
+
118
+ it("accepts promotable flags on top-level authored entities", async () => {
119
+ const files = [
120
+ path.join(tempProjectPath, "attributes", "userId.yml"),
121
+ path.join(tempProjectPath, "segments", "everyone.yml"),
122
+ path.join(tempProjectPath, "features", "showHeader.yml"),
123
+ path.join(tempProjectPath, "groups", "myExclusionGroup.yml"),
124
+ path.join(tempProjectPath, "schemas", "money.yml"),
125
+ path.join(tempProjectPath, "tests", "features", "showHeader.spec.yml"),
126
+ ];
127
+
128
+ for (const file of files) {
129
+ fs.appendFileSync(file, "\npromotable: false\n");
130
+ }
131
+
132
+ const result = await lintProject(getDeps(tempProjectPath) as any, { json: true });
133
+
134
+ expect(result).toEqual({
135
+ hasError: false,
136
+ errors: [],
137
+ });
138
+ });
139
+
140
+ it("accepts promotable flags on feature rules", async () => {
141
+ replaceInFile(
142
+ path.join(tempProjectPath, "features", "showHeader.yml"),
143
+ ' - key: "1"',
144
+ ' - key: "1"\n promotable: false',
145
+ );
146
+
147
+ const result = await lintProject(getDeps(tempProjectPath) as any, { json: true });
148
+
149
+ expect(result).toEqual({
150
+ hasError: false,
151
+ errors: [],
152
+ });
153
+ });
154
+
155
+ it("rejects non-boolean promotable values", async () => {
156
+ fs.appendFileSync(
157
+ path.join(tempProjectPath, "attributes", "userId.yml"),
158
+ "\npromotable: nope\n",
159
+ );
160
+
161
+ const result = await lintProject(getDeps(tempProjectPath) as any, { json: true });
162
+
163
+ expect(result.hasError).toEqual(true);
164
+ expect(
165
+ result.errors.some(
166
+ (error) => error.path.join(".") === "promotable" && error.message.includes("boolean"),
167
+ ),
168
+ ).toEqual(true);
169
+ });
170
+
171
+ it("rejects non-boolean rule promotable values", async () => {
172
+ replaceInFile(
173
+ path.join(tempProjectPath, "features", "showHeader.yml"),
174
+ ' - key: "1"',
175
+ ' - key: "1"\n promotable: nope',
176
+ );
177
+
178
+ const result = await lintProject(getDeps(tempProjectPath) as any, { json: true });
179
+
180
+ expect(result.hasError).toEqual(true);
181
+ expect(
182
+ result.errors.some(
183
+ (error) =>
184
+ error.path.join(".") === "rules.staging.0.promotable" &&
185
+ error.message.includes("boolean"),
186
+ ),
187
+ ).toEqual(true);
188
+ });
189
+
112
190
  it("returns structured errors in JSON mode", async () => {
113
191
  fs.writeFileSync(
114
192
  path.join(tempProjectPath, "attributes", "invalid name.yml"),
@@ -206,7 +284,7 @@ conditions:
206
284
  expect.objectContaining({
207
285
  filePath: expect.stringContaining(path.join("tests", "features", "withSchema.spec.yml")),
208
286
  entityType: "test",
209
- key: "features/withSchema.spec",
287
+ key: "features.withSchema.spec",
210
288
  message: 'Unknown environment "qa"',
211
289
  path: ["assertions", 0, "environment"],
212
290
  code: "custom",
@@ -255,43 +333,4 @@ feature: foo
255
333
  expect(result.errors.length).toBeGreaterThan(0);
256
334
  expect(processExitSpy).not.toHaveBeenCalled();
257
335
  });
258
-
259
- it("reports missing split environment file with environment file path", async () => {
260
- const splitProjectPath = createTempSplitProject();
261
- fs.unlinkSync(path.join(splitProjectPath, "environments", "production", "foo.yml"));
262
-
263
- const result = await lintProject(getDeps(splitProjectPath) as any, {
264
- json: true,
265
- entityType: "feature",
266
- });
267
-
268
- expect(result.hasError).toBe(true);
269
- expect(result.errors[0].filePath).toContain(path.join("environments", "production", "foo.yml"));
270
- });
271
-
272
- it("reports split environment feature schema errors against environment file path", async () => {
273
- const splitProjectPath = createTempSplitProject();
274
- fs.writeFileSync(
275
- path.join(splitProjectPath, "environments", "staging", "foo.yml"),
276
- `
277
- rules:
278
- - key: everyone
279
- segments: '*'
280
- percentage: invalid
281
- `.trimStart(),
282
- "utf8",
283
- );
284
-
285
- const result = await lintProject(getDeps(splitProjectPath) as any, {
286
- json: true,
287
- entityType: "feature",
288
- });
289
-
290
- expect(result.hasError).toBe(true);
291
- expect(
292
- result.errors.some((error) =>
293
- error.filePath.includes(path.join("environments", "staging", "foo.yml")),
294
- ),
295
- ).toBe(true);
296
- });
297
336
  });