@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.
- package/CHANGELOG.md +8 -0
- package/coverage/clover.xml +4991 -3130
- package/coverage/coverage-final.json +63 -39
- package/coverage/lcov-report/block-navigation.js +1 -1
- package/coverage/lcov-report/index.html +248 -98
- package/coverage/lcov-report/sorter.js +21 -7
- package/coverage/lcov-report/src/assess-distribution/index.html +116 -0
- package/coverage/lcov-report/src/assess-distribution/index.ts.html +754 -0
- package/coverage/lcov-report/{list → src/benchmark}/index.html +30 -30
- package/coverage/lcov-report/src/benchmark/index.ts.html +793 -0
- package/coverage/lcov-report/{builder → src/builder}/allocator.ts.html +38 -38
- package/coverage/lcov-report/{builder/buildScopedConditions.ts.html → src/builder/applyContextToConditions.ts.html} +80 -65
- package/coverage/lcov-report/{builder/buildScopedDatafile.ts.html → src/builder/applyContextToDatafile.ts.html} +92 -92
- package/coverage/lcov-report/{builder/buildScopedSegments.ts.html → src/builder/applyContextToSegments.ts.html} +103 -67
- package/coverage/lcov-report/{builder → src/builder}/buildDatafile.ts.html +336 -306
- package/coverage/lcov-report/src/builder/buildProject.ts.html +1117 -0
- package/coverage/lcov-report/src/builder/buildRuntimeDatafiles.ts.html +256 -0
- package/coverage/lcov-report/{builder → src/builder}/getFeatureRanges.ts.html +22 -22
- package/coverage/lcov-report/{builder → src/builder}/hashes.ts.html +50 -50
- package/coverage/lcov-report/{builder → src/builder}/index.html +114 -84
- package/coverage/lcov-report/{tester/cliFormat.ts.html → src/builder/index.ts.html} +17 -29
- package/coverage/lcov-report/{builder → src/builder}/mutateVariables.ts.html +55 -55
- package/coverage/lcov-report/{builder → src/builder}/mutator.ts.html +118 -118
- package/coverage/lcov-report/{builder → src/builder}/revision.ts.html +15 -15
- package/coverage/lcov-report/{builder → src/builder}/traffic.ts.html +86 -86
- package/coverage/lcov-report/src/cli/cli.ts.html +400 -0
- package/coverage/lcov-report/{tester → src/cli}/index.html +41 -41
- package/coverage/lcov-report/src/cli/plugins.ts.html +214 -0
- package/coverage/lcov-report/{config → src/config}/index.html +31 -31
- package/coverage/lcov-report/{config → src/config}/index.ts.html +12 -12
- package/coverage/lcov-report/{config → src/config}/projectConfig.ts.html +312 -96
- package/coverage/lcov-report/{datasource → src/datasource}/adapter.ts.html +28 -73
- package/coverage/lcov-report/{datasource → src/datasource}/datasource.ts.html +191 -104
- package/coverage/lcov-report/{datasource → src/datasource}/filesystemAdapter.ts.html +233 -635
- package/coverage/lcov-report/{datasource → src/datasource}/index.html +48 -48
- package/coverage/lcov-report/{datasource → src/datasource}/index.ts.html +14 -14
- package/coverage/lcov-report/src/evaluate/index.html +116 -0
- package/coverage/lcov-report/src/evaluate/index.ts.html +814 -0
- package/coverage/lcov-report/src/find-duplicate-segments/findDuplicateSegments.ts.html +313 -0
- package/coverage/lcov-report/src/find-duplicate-segments/index.html +131 -0
- package/coverage/lcov-report/src/find-duplicate-segments/index.ts.html +307 -0
- package/coverage/lcov-report/src/find-usage/index.html +116 -0
- package/coverage/lcov-report/src/find-usage/index.ts.html +1780 -0
- package/coverage/lcov-report/src/generate-code/index.html +131 -0
- package/coverage/lcov-report/src/generate-code/index.ts.html +403 -0
- package/coverage/lcov-report/{generate-code → src/generate-code}/typescript.ts.html +266 -1361
- package/coverage/lcov-report/{generate-code → src}/index.html +38 -23
- package/coverage/lcov-report/src/info/index.html +116 -0
- package/coverage/lcov-report/src/info/index.ts.html +472 -0
- package/coverage/lcov-report/src/init/index.html +116 -0
- package/coverage/lcov-report/src/init/index.ts.html +280 -0
- package/coverage/lcov-report/{linter → src/linter}/attributeSchema.ts.html +160 -157
- package/coverage/lcov-report/{linter → src/linter}/checkCircularDependency.ts.html +25 -25
- package/coverage/lcov-report/{linter → src/linter}/checkPercentageExceedingSlot.ts.html +42 -42
- package/coverage/lcov-report/{linter → src/linter}/conditionSchema.ts.html +207 -207
- package/coverage/lcov-report/{linter → src/linter}/featureSchema.ts.html +462 -471
- package/coverage/lcov-report/{linter → src/linter}/groupSchema.ts.html +25 -22
- package/coverage/lcov-report/{linter → src/linter}/index.html +129 -99
- package/coverage/lcov-report/src/linter/index.ts.html +88 -0
- package/coverage/lcov-report/{linter → src/linter}/lintProject.ts.html +740 -332
- package/coverage/lcov-report/{linter → src/linter}/mutationNotation.ts.html +130 -130
- package/coverage/lcov-report/{linter → src/linter}/printError.ts.html +27 -27
- package/coverage/lcov-report/{linter → src/linter}/schema.ts.html +153 -150
- package/coverage/lcov-report/{linter → src/linter}/segmentSchema.ts.html +18 -15
- package/coverage/lcov-report/src/linter/targetSchema.ts.html +223 -0
- package/coverage/lcov-report/{linter → src/linter}/testSchema.ts.html +54 -48
- package/coverage/lcov-report/{linter → src/linter}/zodHelpers.ts.html +17 -17
- package/coverage/lcov-report/src/list/index.html +131 -0
- package/coverage/lcov-report/src/list/index.ts.html +2065 -0
- package/coverage/lcov-report/{list → src/list}/matrix.ts.html +43 -58
- package/coverage/lcov-report/src/promoter/index.html +116 -0
- package/coverage/lcov-report/src/promoter/index.ts.html +3883 -0
- package/coverage/lcov-report/src/sets.ts.html +313 -0
- package/coverage/lcov-report/src/targeting.ts.html +403 -0
- package/coverage/lcov-report/src/tester/cliFormat.ts.html +133 -0
- package/coverage/lcov-report/{tester → src/tester}/helpers.ts.html +15 -15
- package/coverage/lcov-report/src/tester/index.html +206 -0
- package/coverage/lcov-report/src/tester/prettyDuration.ts.html +187 -0
- package/coverage/lcov-report/src/tester/printTestResult.ts.html +337 -0
- package/coverage/lcov-report/src/tester/testFeature.ts.html +1186 -0
- package/coverage/lcov-report/src/tester/testProject.ts.html +1018 -0
- package/coverage/lcov-report/src/tester/testSegment.ts.html +316 -0
- package/coverage/lcov-report/{utils → src/utils}/extractKeys.ts.html +68 -68
- package/coverage/lcov-report/{utils → src/utils}/git.ts.html +44 -68
- package/coverage/lcov-report/{utils → src/utils}/index.html +44 -44
- package/coverage/lcov-report/{utils → src/utils}/index.ts.html +14 -14
- package/coverage/lcov-report/{utils → src/utils}/pretty.ts.html +20 -20
- package/coverage/lcov.info +10121 -5723
- package/jest.config.js +13 -4
- package/lib/assess-distribution/index.d.ts +1 -1
- package/lib/assess-distribution/index.js +59 -40
- package/lib/assess-distribution/index.js.map +1 -1
- package/lib/benchmark/index.d.ts +8 -5
- package/lib/benchmark/index.js +82 -65
- package/lib/benchmark/index.js.map +1 -1
- package/lib/builder/applyContextToConditions.d.ts +5 -0
- package/lib/builder/{buildScopedConditions.js → applyContextToConditions.js} +19 -14
- package/lib/builder/applyContextToConditions.js.map +1 -0
- package/lib/builder/applyContextToDatafile.d.ts +2 -0
- package/lib/builder/{buildScopedDatafile.js → applyContextToDatafile.js} +26 -26
- package/lib/builder/applyContextToDatafile.js.map +1 -0
- package/lib/builder/applyContextToSegments.d.ts +5 -0
- package/lib/builder/{buildScopedSegments.js → applyContextToSegments.js} +19 -14
- package/lib/builder/applyContextToSegments.js.map +1 -0
- package/lib/builder/buildDatafile.d.ts +9 -6
- package/lib/builder/buildDatafile.js +86 -51
- package/lib/builder/buildDatafile.js.map +1 -1
- package/lib/builder/buildProject.d.ts +17 -3
- package/lib/builder/buildProject.js +151 -59
- package/lib/builder/buildProject.js.map +1 -1
- package/lib/builder/buildRuntimeDatafiles.d.ts +12 -0
- package/lib/builder/buildRuntimeDatafiles.js +35 -0
- package/lib/builder/buildRuntimeDatafiles.js.map +1 -0
- package/lib/builder/getFeatureRanges.js +34 -1
- package/lib/builder/getFeatureRanges.js.map +1 -1
- package/lib/builder/hashes.js +34 -1
- package/lib/builder/hashes.js.map +1 -1
- package/lib/builder/index.d.ts +0 -1
- package/lib/builder/index.js +0 -1
- package/lib/builder/index.js.map +1 -1
- package/lib/cli/cli.js +0 -4
- package/lib/cli/cli.js.map +1 -1
- package/lib/cli/plugins.js +7 -2
- package/lib/cli/plugins.js.map +1 -1
- package/lib/config/projectConfig.d.ts +17 -17
- package/lib/config/projectConfig.js +101 -15
- package/lib/config/projectConfig.js.map +1 -1
- package/lib/datasource/adapter.d.ts +7 -9
- package/lib/datasource/adapter.js +0 -7
- package/lib/datasource/adapter.js.map +1 -1
- package/lib/datasource/datasource.d.ts +15 -5
- package/lib/datasource/datasource.js +37 -9
- package/lib/datasource/datasource.js.map +1 -1
- package/lib/datasource/filesystemAdapter.d.ts +4 -10
- package/lib/datasource/filesystemAdapter.js +111 -149
- package/lib/datasource/filesystemAdapter.js.map +1 -1
- package/lib/evaluate/index.d.ts +1 -7
- package/lib/evaluate/index.js +81 -67
- package/lib/evaluate/index.js.map +1 -1
- package/lib/find-duplicate-segments/findDuplicateSegments.js +34 -1
- package/lib/find-duplicate-segments/findDuplicateSegments.js.map +1 -1
- package/lib/find-duplicate-segments/index.js +21 -13
- package/lib/find-duplicate-segments/index.js.map +1 -1
- package/lib/find-usage/index.js +57 -41
- package/lib/find-usage/index.js.map +1 -1
- package/lib/generate-code/index.d.ts +1 -1
- package/lib/generate-code/index.js +64 -21
- package/lib/generate-code/index.js.map +1 -1
- package/lib/generate-code/typescript.d.ts +1 -1
- package/lib/generate-code/typescript.js +59 -333
- package/lib/generate-code/typescript.js.map +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +2 -1
- package/lib/index.js.map +1 -1
- package/lib/info/index.js +57 -16
- package/lib/info/index.js.map +1 -1
- package/lib/init/index.d.ts +2 -2
- package/lib/init/index.js +39 -3
- package/lib/init/index.js.map +1 -1
- package/lib/linter/attributeSchema.d.ts +1 -0
- package/lib/linter/attributeSchema.js +1 -0
- package/lib/linter/attributeSchema.js.map +1 -1
- package/lib/linter/conditionSchema.js +5 -5
- package/lib/linter/conditionSchema.js.map +1 -1
- package/lib/linter/featureSchema.d.ts +4 -1
- package/lib/linter/featureSchema.js +15 -13
- package/lib/linter/featureSchema.js.map +1 -1
- package/lib/linter/groupSchema.d.ts +1 -0
- package/lib/linter/groupSchema.js +1 -0
- package/lib/linter/groupSchema.js.map +1 -1
- package/lib/linter/lintProject.d.ts +1 -3
- package/lib/linter/lintProject.js +198 -68
- package/lib/linter/lintProject.js.map +1 -1
- package/lib/linter/schema.js +1 -0
- package/lib/linter/schema.js.map +1 -1
- package/lib/linter/segmentSchema.d.ts +1 -0
- package/lib/linter/segmentSchema.js +1 -0
- package/lib/linter/segmentSchema.js.map +1 -1
- package/lib/linter/targetSchema.d.ts +16 -0
- package/lib/linter/targetSchema.js +40 -0
- package/lib/linter/targetSchema.js.map +1 -0
- package/lib/linter/testSchema.d.ts +4 -3
- package/lib/linter/testSchema.js +9 -4
- package/lib/linter/testSchema.js.map +1 -1
- package/lib/list/index.d.ts +1 -0
- package/lib/list/index.js +131 -12
- package/lib/list/index.js.map +1 -1
- package/lib/list/matrix.js +3 -7
- package/lib/list/matrix.js.map +1 -1
- package/lib/promoter/index.d.ts +50 -0
- package/lib/promoter/index.js +880 -0
- package/lib/promoter/index.js.map +1 -0
- package/lib/sets.d.ts +11 -0
- package/lib/sets.js +81 -0
- package/lib/sets.js.map +1 -0
- package/lib/targeting.d.ts +14 -0
- package/lib/targeting.js +82 -0
- package/lib/targeting.js.map +1 -0
- package/lib/tester/cliFormat.d.ts +7 -1
- package/lib/tester/cliFormat.js +11 -2
- package/lib/tester/cliFormat.js.map +1 -1
- package/lib/tester/printTestResult.js +2 -2
- package/lib/tester/printTestResult.js.map +1 -1
- package/lib/tester/testFeature.d.ts +0 -1
- package/lib/tester/testFeature.js +8 -25
- package/lib/tester/testFeature.js.map +1 -1
- package/lib/tester/testProject.d.ts +2 -3
- package/lib/tester/testProject.js +93 -86
- package/lib/tester/testProject.js.map +1 -1
- package/lib/tester/testSegment.d.ts +1 -1
- package/lib/tester/testSegment.js +3 -21
- package/lib/tester/testSegment.js.map +1 -1
- package/lib/utils/git.js +45 -19
- package/lib/utils/git.js.map +1 -1
- package/package.json +12 -12
- package/src/assess-distribution/index.ts +72 -52
- package/src/benchmark/index.ts +124 -91
- package/src/builder/{buildScopedConditions.spec.ts → applyContextToConditions.spec.ts} +160 -142
- package/src/builder/{buildScopedConditions.ts → applyContextToConditions.ts} +19 -14
- package/src/builder/{buildScopedDatafile.spec.ts → applyContextToDatafile.spec.ts} +141 -81
- package/src/builder/{buildScopedDatafile.ts → applyContextToDatafile.ts} +33 -33
- package/src/builder/{buildScopedSegments.spec.ts → applyContextToSegments.spec.ts} +171 -151
- package/src/builder/{buildScopedSegments.ts → applyContextToSegments.ts} +26 -14
- package/src/builder/buildDatafile.spec.ts +192 -14
- package/src/builder/buildDatafile.ts +74 -64
- package/src/builder/buildProject.ts +171 -86
- package/src/builder/buildRuntimeDatafiles.ts +57 -0
- package/src/builder/index.ts +0 -1
- package/src/cli/cli.ts +0 -5
- package/src/cli/plugins.ts +7 -2
- package/src/config/projectConfig.spec.ts +171 -8
- package/src/config/projectConfig.ts +100 -28
- package/src/datasource/adapter.ts +8 -23
- package/src/datasource/datasource.ts +50 -21
- package/src/datasource/filesystemAdapter.spec.ts +133 -74
- package/src/datasource/filesystemAdapter.ts +93 -227
- package/src/evaluate/index.ts +109 -97
- package/src/find-duplicate-segments/index.ts +34 -16
- package/src/find-usage/index.ts +89 -44
- package/src/generate-code/index.ts +36 -24
- package/src/generate-code/typescript.spec.ts +106 -0
- package/src/generate-code/typescript.ts +33 -398
- package/src/index.ts +2 -1
- package/src/info/index.ts +72 -16
- package/src/init/index.ts +1 -1
- package/src/linter/attributeSchema.ts +1 -0
- package/src/linter/conditionSchema.spec.ts +23 -3
- package/src/linter/conditionSchema.ts +5 -5
- package/src/linter/featureSchema.spec.ts +107 -18
- package/src/linter/featureSchema.ts +25 -28
- package/src/linter/groupSchema.ts +1 -0
- package/src/linter/lintProject.spec.ts +131 -92
- package/src/linter/lintProject.ts +233 -97
- package/src/linter/schema.ts +1 -0
- package/src/linter/segmentSchema.spec.ts +5 -3
- package/src/linter/segmentSchema.ts +1 -0
- package/src/linter/targetSchema.spec.ts +159 -0
- package/src/linter/targetSchema.ts +46 -0
- package/src/linter/testSchema.spec.ts +70 -5
- package/src/linter/testSchema.ts +12 -10
- package/src/list/index.spec.ts +135 -0
- package/src/list/index.ts +188 -12
- package/src/list/matrix.ts +3 -8
- package/src/promoter/index.spec.ts +617 -0
- package/src/promoter/index.ts +1266 -0
- package/src/sets.ts +76 -0
- package/src/targeting.spec.ts +74 -0
- package/src/targeting.ts +106 -0
- package/src/tester/cliFormat.ts +11 -1
- package/src/tester/printTestResult.ts +12 -3
- package/src/tester/testFeature.ts +10 -36
- package/src/tester/testProject.spec.ts +50 -0
- package/src/tester/testProject.ts +89 -140
- package/src/tester/testSegment.ts +5 -22
- package/src/utils/git.ts +13 -21
- package/tsconfig.cjs.json +5 -1
- package/tsconfig.typecheck.json +9 -0
- package/coverage/lcov-report/builder/convertToV1.ts.html +0 -583
- package/lib/builder/allocator.spec.d.ts +0 -1
- package/lib/builder/allocator.spec.js +0 -85
- package/lib/builder/allocator.spec.js.map +0 -1
- package/lib/builder/buildDatafile.spec.d.ts +0 -1
- package/lib/builder/buildDatafile.spec.js +0 -233
- package/lib/builder/buildDatafile.spec.js.map +0 -1
- package/lib/builder/buildScopedConditions.d.ts +0 -5
- package/lib/builder/buildScopedConditions.js.map +0 -1
- package/lib/builder/buildScopedConditions.spec.d.ts +0 -1
- package/lib/builder/buildScopedConditions.spec.js +0 -2167
- package/lib/builder/buildScopedConditions.spec.js.map +0 -1
- package/lib/builder/buildScopedDatafile.d.ts +0 -2
- package/lib/builder/buildScopedDatafile.js.map +0 -1
- package/lib/builder/buildScopedDatafile.spec.d.ts +0 -1
- package/lib/builder/buildScopedDatafile.spec.js +0 -2277
- package/lib/builder/buildScopedDatafile.spec.js.map +0 -1
- package/lib/builder/buildScopedSegments.d.ts +0 -5
- package/lib/builder/buildScopedSegments.js.map +0 -1
- package/lib/builder/buildScopedSegments.spec.d.ts +0 -1
- package/lib/builder/buildScopedSegments.spec.js +0 -1062
- package/lib/builder/buildScopedSegments.spec.js.map +0 -1
- package/lib/builder/convertToV1.d.ts +0 -10
- package/lib/builder/convertToV1.js +0 -119
- package/lib/builder/convertToV1.js.map +0 -1
- package/lib/builder/mutateVariables.spec.d.ts +0 -1
- package/lib/builder/mutateVariables.spec.js +0 -1074
- package/lib/builder/mutateVariables.spec.js.map +0 -1
- package/lib/builder/mutator.spec.d.ts +0 -1
- package/lib/builder/mutator.spec.js +0 -368
- package/lib/builder/mutator.spec.js.map +0 -1
- package/lib/builder/revision.spec.d.ts +0 -1
- package/lib/builder/revision.spec.js +0 -21
- package/lib/builder/revision.spec.js.map +0 -1
- package/lib/builder/traffic.spec.d.ts +0 -1
- package/lib/builder/traffic.spec.js +0 -872
- package/lib/builder/traffic.spec.js.map +0 -1
- package/lib/config/projectConfig.spec.d.ts +0 -1
- package/lib/config/projectConfig.spec.js +0 -24
- package/lib/config/projectConfig.spec.js.map +0 -1
- package/lib/datasource/filesystemAdapter.spec.d.ts +0 -1
- package/lib/datasource/filesystemAdapter.spec.js +0 -88
- package/lib/datasource/filesystemAdapter.spec.js.map +0 -1
- package/lib/generate-code/typescript.spec.d.ts +0 -1
- package/lib/generate-code/typescript.spec.js +0 -78
- package/lib/generate-code/typescript.spec.js.map +0 -1
- package/lib/linter/attributeSchema.spec.d.ts +0 -1
- package/lib/linter/attributeSchema.spec.js +0 -266
- package/lib/linter/attributeSchema.spec.js.map +0 -1
- package/lib/linter/conditionSchema.spec.d.ts +0 -1
- package/lib/linter/conditionSchema.spec.js +0 -380
- package/lib/linter/conditionSchema.spec.js.map +0 -1
- package/lib/linter/featureSchema.spec.d.ts +0 -1
- package/lib/linter/featureSchema.spec.js +0 -1552
- package/lib/linter/featureSchema.spec.js.map +0 -1
- package/lib/linter/lintProject.spec.d.ts +0 -1
- package/lib/linter/lintProject.spec.js +0 -211
- package/lib/linter/lintProject.spec.js.map +0 -1
- package/lib/linter/mutationNotation.spec.d.ts +0 -1
- package/lib/linter/mutationNotation.spec.js +0 -598
- package/lib/linter/mutationNotation.spec.js.map +0 -1
- package/lib/linter/schema.spec.d.ts +0 -1
- package/lib/linter/schema.spec.js +0 -535
- package/lib/linter/schema.spec.js.map +0 -1
- package/lib/linter/segmentSchema.spec.d.ts +0 -1
- package/lib/linter/segmentSchema.spec.js +0 -303
- package/lib/linter/segmentSchema.spec.js.map +0 -1
- package/lib/linter/testSchema.spec.d.ts +0 -1
- package/lib/linter/testSchema.spec.js +0 -162
- package/lib/linter/testSchema.spec.js.map +0 -1
- package/lib/list/matrix.spec.d.ts +0 -1
- package/lib/list/matrix.spec.js +0 -42
- package/lib/list/matrix.spec.js.map +0 -1
- package/lib/site/exportSite.d.ts +0 -2
- package/lib/site/exportSite.js +0 -30
- package/lib/site/exportSite.js.map +0 -1
- package/lib/site/generateHistory.d.ts +0 -3
- package/lib/site/generateHistory.js +0 -31
- package/lib/site/generateHistory.js.map +0 -1
- package/lib/site/generateSiteSearchIndex.d.ts +0 -4
- package/lib/site/generateSiteSearchIndex.js +0 -195
- package/lib/site/generateSiteSearchIndex.js.map +0 -1
- package/lib/site/getLastModifiedFromHistory.d.ts +0 -2
- package/lib/site/getLastModifiedFromHistory.js +0 -18
- package/lib/site/getLastModifiedFromHistory.js.map +0 -1
- package/lib/site/getOwnerAndRepoFromUrl.d.ts +0 -4
- package/lib/site/getOwnerAndRepoFromUrl.js +0 -20
- package/lib/site/getOwnerAndRepoFromUrl.js.map +0 -1
- package/lib/site/getRelativePaths.d.ts +0 -6
- package/lib/site/getRelativePaths.js +0 -15
- package/lib/site/getRelativePaths.js.map +0 -1
- package/lib/site/getRepoDetails.d.ts +0 -8
- package/lib/site/getRepoDetails.js +0 -48
- package/lib/site/getRepoDetails.js.map +0 -1
- package/lib/site/index.d.ts +0 -2
- package/lib/site/index.js +0 -43
- package/lib/site/index.js.map +0 -1
- package/lib/site/serveSite.d.ts +0 -2
- package/lib/site/serveSite.js +0 -54
- package/lib/site/serveSite.js.map +0 -1
- package/lib/tester/helpers.spec.d.ts +0 -1
- package/lib/tester/helpers.spec.js +0 -115
- package/lib/tester/helpers.spec.js.map +0 -1
- package/src/builder/convertToV1.ts +0 -166
- package/src/site/exportSite.ts +0 -42
- package/src/site/generateHistory.ts +0 -40
- package/src/site/generateSiteSearchIndex.ts +0 -258
- package/src/site/getLastModifiedFromHistory.ts +0 -21
- package/src/site/getOwnerAndRepoFromUrl.ts +0 -17
- package/src/site/getRelativePaths.ts +0 -24
- package/src/site/getRepoDetails.ts +0 -62
- package/src/site/index.ts +0 -46
- 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
|
-
|
|
25
|
-
|
|
25
|
+
catalogDirectoryPath: "",
|
|
26
|
+
setsDirectoryPath: "",
|
|
26
27
|
environments: ["staging", "production"],
|
|
27
|
-
|
|
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]
|
|
581
|
+
return /^[gimsuy]+$/.test(value) && new Set(value).size === value.length;
|
|
582
582
|
},
|
|
583
583
|
{
|
|
584
|
-
message: `regexFlags must
|
|
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
|
-
|
|
28
|
-
|
|
28
|
+
catalogDirectoryPath: "",
|
|
29
|
+
setsDirectoryPath: "",
|
|
29
30
|
environments: ["staging", "production"],
|
|
30
|
-
|
|
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
|
|
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(
|
|
139
|
-
|
|
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(
|
|
143
|
-
|
|
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(
|
|
153
|
-
|
|
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
|
|
663
|
-
{ segments: "countries
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
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) => {
|
|
@@ -15,58 +15,10 @@ function createTempProjectFromExample1() {
|
|
|
15
15
|
return tempRoot;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function
|
|
19
|
-
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "featurevisor-lint-
|
|
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
|
|
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
|
});
|