@featurevisor/core 1.35.3 → 2.0.0
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/README.md +0 -6
- package/coverage/clover.xml +321 -237
- package/coverage/coverage-final.json +8 -8
- package/coverage/lcov-report/index.html +77 -47
- package/coverage/lcov-report/lib/builder/allocator.js.html +14 -14
- package/coverage/lcov-report/lib/builder/index.html +16 -16
- package/coverage/lcov-report/lib/builder/revision.js.html +3 -3
- package/coverage/lcov-report/lib/builder/traffic.js.html +88 -64
- package/coverage/lcov-report/lib/list/index.html +116 -0
- package/coverage/lcov-report/lib/{tester → list}/matrix.js.html +90 -66
- package/coverage/lcov-report/lib/tester/helpers.js.html +295 -0
- package/coverage/lcov-report/lib/tester/index.html +20 -35
- package/coverage/lcov-report/src/builder/allocator.ts.html +2 -2
- package/coverage/lcov-report/src/builder/index.html +15 -15
- package/coverage/lcov-report/src/builder/revision.ts.html +1 -1
- package/coverage/lcov-report/src/builder/traffic.ts.html +96 -24
- package/coverage/lcov-report/src/list/index.html +116 -0
- package/coverage/lcov-report/src/{tester → list}/matrix.ts.html +87 -21
- package/coverage/lcov-report/src/tester/helpers.ts.html +313 -0
- package/coverage/lcov-report/src/tester/index.html +20 -35
- package/coverage/lcov.info +592 -436
- package/lib/assess-distribution/index.d.ts +1 -1
- package/lib/assess-distribution/index.js +102 -162
- package/lib/assess-distribution/index.js.map +1 -1
- package/lib/benchmark/index.js +87 -143
- package/lib/benchmark/index.js.map +1 -1
- package/lib/builder/allocator.d.ts +1 -1
- package/lib/builder/allocator.js +12 -12
- package/lib/builder/allocator.js.map +1 -1
- package/lib/builder/allocator.spec.js +22 -22
- package/lib/builder/allocator.spec.js.map +1 -1
- package/lib/builder/buildDatafile.d.ts +4 -3
- package/lib/builder/buildDatafile.js +311 -388
- package/lib/builder/buildDatafile.js.map +1 -1
- package/lib/builder/buildProject.d.ts +2 -1
- package/lib/builder/buildProject.js +96 -183
- package/lib/builder/buildProject.js.map +1 -1
- package/lib/builder/convertToV1.d.ts +10 -0
- package/lib/builder/convertToV1.js +119 -0
- package/lib/builder/convertToV1.js.map +1 -0
- package/lib/builder/getFeatureRanges.d.ts +1 -1
- package/lib/builder/getFeatureRanges.js +32 -105
- package/lib/builder/getFeatureRanges.js.map +1 -1
- package/lib/builder/hashes.d.ts +4 -0
- package/lib/builder/hashes.js +70 -0
- package/lib/builder/hashes.js.map +1 -0
- package/lib/builder/revision.js +2 -2
- package/lib/builder/revision.js.map +1 -1
- package/lib/builder/revision.spec.js +1 -1
- package/lib/builder/revision.spec.js.map +1 -1
- package/lib/builder/traffic.d.ts +1 -1
- package/lib/builder/traffic.js +57 -49
- package/lib/builder/traffic.js.map +1 -1
- package/lib/builder/traffic.spec.js +14 -14
- package/lib/builder/traffic.spec.js.map +1 -1
- package/lib/cli/cli.js +60 -129
- package/lib/cli/cli.js.map +1 -1
- package/lib/cli/plugins.js +14 -16
- package/lib/cli/plugins.js.map +1 -1
- package/lib/config/parsers.js +1 -1
- package/lib/config/parsers.js.map +1 -1
- package/lib/config/projectConfig.d.ts +8 -6
- package/lib/config/projectConfig.js +31 -72
- package/lib/config/projectConfig.js.map +1 -1
- package/lib/datasource/adapter.d.ts +1 -1
- package/lib/datasource/adapter.js +2 -5
- package/lib/datasource/adapter.js.map +1 -1
- package/lib/datasource/datasource.d.ts +2 -1
- package/lib/datasource/datasource.js +107 -148
- package/lib/datasource/datasource.js.map +1 -1
- package/lib/datasource/filesystemAdapter.d.ts +1 -1
- package/lib/datasource/filesystemAdapter.js +224 -360
- package/lib/datasource/filesystemAdapter.js.map +1 -1
- package/lib/evaluate/index.d.ts +1 -1
- package/lib/evaluate/index.js +120 -188
- package/lib/evaluate/index.js.map +1 -1
- package/lib/find-duplicate-segments/findDuplicateSegments.d.ts +1 -1
- package/lib/find-duplicate-segments/findDuplicateSegments.js +40 -128
- package/lib/find-duplicate-segments/findDuplicateSegments.js.map +1 -1
- package/lib/find-duplicate-segments/index.js +27 -82
- package/lib/find-duplicate-segments/index.js.map +1 -1
- package/lib/find-usage/index.d.ts +7 -5
- package/lib/find-usage/index.js +333 -507
- package/lib/find-usage/index.js.map +1 -1
- package/lib/generate-code/index.js +36 -91
- package/lib/generate-code/index.js.map +1 -1
- package/lib/generate-code/typescript.js +117 -157
- package/lib/generate-code/typescript.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -1
- package/lib/index.js.map +1 -1
- package/lib/info/index.js +45 -133
- package/lib/info/index.js.map +1 -1
- package/lib/init/index.d.ts +1 -1
- package/lib/init/index.js +16 -64
- package/lib/init/index.js.map +1 -1
- package/lib/linter/attributeSchema.d.ts +21 -6
- package/lib/linter/attributeSchema.js +18 -4
- package/lib/linter/attributeSchema.js.map +1 -1
- package/lib/linter/checkCircularDependency.d.ts +1 -1
- package/lib/linter/checkCircularDependency.js +22 -80
- package/lib/linter/checkCircularDependency.js.map +1 -1
- package/lib/linter/checkPercentageExceedingSlot.d.ts +1 -1
- package/lib/linter/checkPercentageExceedingSlot.js +36 -76
- package/lib/linter/checkPercentageExceedingSlot.js.map +1 -1
- package/lib/linter/conditionSchema.d.ts +1 -1
- package/lib/linter/conditionSchema.js +89 -41
- package/lib/linter/conditionSchema.js.map +1 -1
- package/lib/linter/featureSchema.d.ts +345 -197
- package/lib/linter/featureSchema.js +313 -172
- package/lib/linter/featureSchema.js.map +1 -1
- package/lib/linter/groupSchema.js +6 -6
- package/lib/linter/groupSchema.js.map +1 -1
- package/lib/linter/lintProject.js +306 -480
- package/lib/linter/lintProject.js.map +1 -1
- package/lib/linter/printError.js +7 -7
- package/lib/linter/printError.js.map +1 -1
- package/lib/linter/segmentSchema.js +2 -2
- package/lib/linter/segmentSchema.js.map +1 -1
- package/lib/linter/testSchema.d.ts +155 -3
- package/lib/linter/testSchema.js +47 -17
- package/lib/linter/testSchema.js.map +1 -1
- package/lib/list/index.d.ts +1 -0
- package/lib/list/index.js +349 -517
- package/lib/list/index.js.map +1 -1
- package/lib/{tester → list}/matrix.d.ts +1 -1
- package/lib/{tester → list}/matrix.js +50 -42
- package/lib/list/matrix.js.map +1 -0
- package/lib/{tester → list}/matrix.spec.js +7 -7
- package/lib/list/matrix.spec.js.map +1 -0
- package/lib/site/exportSite.js +25 -71
- package/lib/site/exportSite.js.map +1 -1
- package/lib/site/generateHistory.d.ts +1 -1
- package/lib/site/generateHistory.js +26 -82
- package/lib/site/generateHistory.js.map +1 -1
- package/lib/site/generateSiteSearchIndex.d.ts +1 -1
- package/lib/site/generateSiteSearchIndex.js +182 -259
- package/lib/site/generateSiteSearchIndex.js.map +1 -1
- package/lib/site/getLastModifiedFromHistory.d.ts +1 -1
- package/lib/site/getLastModifiedFromHistory.js +2 -2
- package/lib/site/getLastModifiedFromHistory.js.map +1 -1
- package/lib/site/getOwnerAndRepoFromUrl.js +6 -6
- package/lib/site/getOwnerAndRepoFromUrl.js.map +1 -1
- package/lib/site/getRelativePaths.js +7 -7
- package/lib/site/getRelativePaths.js.map +1 -1
- package/lib/site/getRepoDetails.js +20 -20
- package/lib/site/getRepoDetails.js.map +1 -1
- package/lib/site/index.js +25 -73
- package/lib/site/index.js.map +1 -1
- package/lib/site/serveSite.js +10 -10
- package/lib/site/serveSite.js.map +1 -1
- package/lib/tester/helpers.d.ts +2 -0
- package/lib/tester/helpers.js +71 -0
- package/lib/tester/helpers.js.map +1 -0
- package/lib/tester/helpers.spec.js +115 -0
- package/lib/tester/helpers.spec.js.map +1 -0
- package/lib/tester/index.d.ts +0 -1
- package/lib/tester/index.js +0 -1
- package/lib/tester/index.js.map +1 -1
- package/lib/tester/prettyDuration.js +11 -11
- package/lib/tester/prettyDuration.js.map +1 -1
- package/lib/tester/printTestResult.d.ts +1 -1
- package/lib/tester/printTestResult.js +35 -15
- package/lib/tester/printTestResult.js.map +1 -1
- package/lib/tester/testFeature.d.ts +4 -2
- package/lib/tester/testFeature.js +264 -226
- package/lib/tester/testFeature.js.map +1 -1
- package/lib/tester/testProject.d.ts +3 -7
- package/lib/tester/testProject.js +145 -246
- package/lib/tester/testProject.js.map +1 -1
- package/lib/tester/testSegment.d.ts +5 -2
- package/lib/tester/testSegment.js +65 -102
- package/lib/tester/testSegment.js.map +1 -1
- package/lib/utils/extractKeys.d.ts +2 -1
- package/lib/utils/extractKeys.js +57 -12
- package/lib/utils/extractKeys.js.map +1 -1
- package/lib/utils/git.d.ts +1 -1
- package/lib/utils/git.js +23 -23
- package/lib/utils/git.js.map +1 -1
- package/lib/utils/pretty.js +2 -4
- package/lib/utils/pretty.js.map +1 -1
- package/package.json +5 -6
- package/src/assess-distribution/index.ts +3 -2
- package/src/benchmark/index.ts +3 -3
- package/src/builder/allocator.spec.ts +1 -1
- package/src/builder/allocator.ts +1 -1
- package/src/builder/buildDatafile.ts +161 -124
- package/src/builder/buildProject.ts +6 -3
- package/src/builder/convertToV1.ts +166 -0
- package/src/builder/getFeatureRanges.ts +1 -1
- package/src/builder/hashes.ts +109 -0
- package/src/builder/traffic.ts +40 -16
- package/src/cli/cli.ts +1 -1
- package/src/cli/plugins.ts +0 -2
- package/src/config/projectConfig.ts +13 -10
- package/src/datasource/adapter.ts +1 -1
- package/src/datasource/datasource.ts +23 -2
- package/src/datasource/filesystemAdapter.ts +11 -12
- package/src/evaluate/index.ts +7 -6
- package/src/find-duplicate-segments/findDuplicateSegments.ts +1 -1
- package/src/find-usage/index.ts +111 -44
- package/src/generate-code/index.ts +1 -3
- package/src/generate-code/typescript.ts +7 -29
- package/src/index.ts +0 -1
- package/src/info/index.ts +2 -2
- package/src/init/index.ts +2 -2
- package/src/linter/attributeSchema.ts +18 -2
- package/src/linter/checkCircularDependency.ts +1 -1
- package/src/linter/checkPercentageExceedingSlot.ts +28 -8
- package/src/linter/conditionSchema.ts +66 -10
- package/src/linter/featureSchema.ts +312 -116
- package/src/linter/lintProject.ts +9 -4
- package/src/linter/testSchema.ts +42 -3
- package/src/list/index.ts +18 -30
- package/src/{tester → list}/matrix.ts +33 -11
- package/src/site/exportSite.ts +2 -4
- package/src/site/generateHistory.ts +1 -1
- package/src/site/generateSiteSearchIndex.ts +58 -50
- package/src/site/getLastModifiedFromHistory.ts +1 -1
- package/src/tester/helpers.spec.ts +149 -0
- package/src/tester/helpers.ts +76 -0
- package/src/tester/index.ts +0 -1
- package/src/tester/printTestResult.ts +25 -3
- package/src/tester/testFeature.ts +270 -124
- package/src/tester/testProject.ts +28 -49
- package/src/tester/testSegment.ts +48 -40
- package/src/utils/extractKeys.ts +58 -1
- package/src/utils/git.ts +1 -1
- package/tsconfig.cjs.json +1 -0
- package/coverage/lcov-report/lib/tester/checkIfObjectsAreEqual.js.html +0 -151
- package/coverage/lcov-report/src/tester/checkIfObjectsAreEqual.ts.html +0 -157
- package/lib/restore/index.d.ts +0 -4
- package/lib/restore/index.js +0 -91
- package/lib/restore/index.js.map +0 -1
- package/lib/tester/checkIfArraysAreEqual.d.ts +0 -1
- package/lib/tester/checkIfArraysAreEqual.js +0 -18
- package/lib/tester/checkIfArraysAreEqual.js.map +0 -1
- package/lib/tester/checkIfObjectsAreEqual.d.ts +0 -1
- package/lib/tester/checkIfObjectsAreEqual.js +0 -23
- package/lib/tester/checkIfObjectsAreEqual.js.map +0 -1
- package/lib/tester/checkIfObjectsAreEqual.spec.js +0 -26
- package/lib/tester/checkIfObjectsAreEqual.spec.js.map +0 -1
- package/lib/tester/matrix.js.map +0 -1
- package/lib/tester/matrix.spec.js.map +0 -1
- package/src/restore/index.ts +0 -42
- package/src/tester/checkIfArraysAreEqual.ts +0 -16
- package/src/tester/checkIfObjectsAreEqual.spec.ts +0 -31
- package/src/tester/checkIfObjectsAreEqual.ts +0 -24
- /package/lib/{tester → list}/matrix.spec.d.ts +0 -0
- /package/lib/tester/{checkIfObjectsAreEqual.spec.d.ts → helpers.spec.d.ts} +0 -0
- /package/src/{tester → list}/matrix.spec.ts +0 -0
package/src/find-usage/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Condition, FeatureKey, SegmentKey, AttributeKey } from "@featurevisor/types";
|
|
1
|
+
import type { Condition, FeatureKey, SegmentKey, AttributeKey } from "@featurevisor/types";
|
|
2
2
|
|
|
3
3
|
import { Dependencies } from "../dependencies";
|
|
4
4
|
import { Plugin } from "../cli";
|
|
@@ -54,10 +54,12 @@ export async function findAllUsageInFeatures(deps: Dependencies): Promise<UsageI
|
|
|
54
54
|
// variable overrides inside variations
|
|
55
55
|
if (feature.variations) {
|
|
56
56
|
feature.variations.forEach((variation) => {
|
|
57
|
-
if (variation.
|
|
58
|
-
variation.
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
if (variation.variableOverrides) {
|
|
58
|
+
Object.keys(variation.variableOverrides).forEach((variableKey) => {
|
|
59
|
+
const overrides = variation.variableOverrides?.[variableKey];
|
|
60
|
+
|
|
61
|
+
if (overrides) {
|
|
62
|
+
overrides.forEach((override) => {
|
|
61
63
|
if (override.segments) {
|
|
62
64
|
extractSegmentKeysFromGroupSegments(override.segments).forEach((segmentKey) =>
|
|
63
65
|
usageInFeatures[featureKey].segments.add(segmentKey),
|
|
@@ -79,13 +81,9 @@ export async function findAllUsageInFeatures(deps: Dependencies): Promise<UsageI
|
|
|
79
81
|
// with environments
|
|
80
82
|
if (Array.isArray(projectConfig.environments)) {
|
|
81
83
|
projectConfig.environments.forEach((environment) => {
|
|
82
|
-
if (!feature.environments) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
84
|
// force
|
|
87
|
-
if (feature.
|
|
88
|
-
feature.
|
|
85
|
+
if (feature.force && feature.force[environment]) {
|
|
86
|
+
feature.force[environment].forEach((force) => {
|
|
89
87
|
if (force.segments) {
|
|
90
88
|
extractSegmentKeysFromGroupSegments(force.segments).forEach((segmentKey) =>
|
|
91
89
|
usageInFeatures[featureKey].segments.add(segmentKey),
|
|
@@ -101,8 +99,8 @@ export async function findAllUsageInFeatures(deps: Dependencies): Promise<UsageI
|
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
// rules
|
|
104
|
-
if (feature.
|
|
105
|
-
feature.
|
|
102
|
+
if (feature.rules && feature.rules[environment]) {
|
|
103
|
+
feature.rules[environment].forEach((rule) => {
|
|
106
104
|
extractSegmentKeysFromGroupSegments(rule.segments).forEach((segmentKey) =>
|
|
107
105
|
usageInFeatures[featureKey].segments.add(segmentKey),
|
|
108
106
|
);
|
|
@@ -114,7 +112,7 @@ export async function findAllUsageInFeatures(deps: Dependencies): Promise<UsageI
|
|
|
114
112
|
// no environments
|
|
115
113
|
if (projectConfig.environments === false) {
|
|
116
114
|
// force
|
|
117
|
-
if (feature.force) {
|
|
115
|
+
if (Array.isArray(feature.force)) {
|
|
118
116
|
feature.force.forEach((force) => {
|
|
119
117
|
if (force.segments) {
|
|
120
118
|
extractSegmentKeysFromGroupSegments(force.segments).forEach((segmentKey) =>
|
|
@@ -131,7 +129,7 @@ export async function findAllUsageInFeatures(deps: Dependencies): Promise<UsageI
|
|
|
131
129
|
}
|
|
132
130
|
|
|
133
131
|
// rules
|
|
134
|
-
if (feature.rules) {
|
|
132
|
+
if (Array.isArray(feature.rules)) {
|
|
135
133
|
feature.rules.forEach((rule) => {
|
|
136
134
|
extractSegmentKeysFromGroupSegments(rule.segments).forEach((segmentKey) =>
|
|
137
135
|
usageInFeatures[featureKey].segments.add(segmentKey),
|
|
@@ -172,14 +170,27 @@ export async function findAllUsageInSegments(deps: Dependencies): Promise<UsageI
|
|
|
172
170
|
return usageInSegments;
|
|
173
171
|
}
|
|
174
172
|
|
|
173
|
+
export async function findFeatureUsage(
|
|
174
|
+
usageInFeatures: UsageInFeatures,
|
|
175
|
+
searchFeatureKey: FeatureKey,
|
|
176
|
+
): Promise<Set<FeatureKey>> {
|
|
177
|
+
const usedInFeatures = new Set<FeatureKey>();
|
|
178
|
+
|
|
179
|
+
for (const featureKey in usageInFeatures) {
|
|
180
|
+
if (usageInFeatures[featureKey].features.has(searchFeatureKey)) {
|
|
181
|
+
usedInFeatures.add(featureKey);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return usedInFeatures;
|
|
186
|
+
}
|
|
187
|
+
|
|
175
188
|
export async function findSegmentUsage(
|
|
176
|
-
|
|
189
|
+
usageInFeatures: UsageInFeatures,
|
|
177
190
|
segmentKey: SegmentKey,
|
|
178
191
|
): Promise<Set<FeatureKey>> {
|
|
179
192
|
const usedInFeatures = new Set<FeatureKey>();
|
|
180
193
|
|
|
181
|
-
const usageInFeatures = await findAllUsageInFeatures(deps);
|
|
182
|
-
|
|
183
194
|
for (const featureKey in usageInFeatures) {
|
|
184
195
|
if (usageInFeatures[featureKey].segments.has(segmentKey)) {
|
|
185
196
|
usedInFeatures.add(featureKey);
|
|
@@ -195,7 +206,8 @@ export interface AttributeUsage {
|
|
|
195
206
|
}
|
|
196
207
|
|
|
197
208
|
export async function findAttributeUsage(
|
|
198
|
-
|
|
209
|
+
usageInFeatures: UsageInFeatures,
|
|
210
|
+
usageInSegments: UsageInSegments,
|
|
199
211
|
attributeKey: AttributeKey,
|
|
200
212
|
): Promise<AttributeUsage> {
|
|
201
213
|
const usedIn: AttributeUsage = {
|
|
@@ -203,9 +215,6 @@ export async function findAttributeUsage(
|
|
|
203
215
|
segments: new Set<SegmentKey>(),
|
|
204
216
|
};
|
|
205
217
|
|
|
206
|
-
const usageInFeatures = await findAllUsageInFeatures(deps);
|
|
207
|
-
const usageInSegments = await findAllUsageInSegments(deps);
|
|
208
|
-
|
|
209
218
|
for (const featureKey in usageInFeatures) {
|
|
210
219
|
if (usageInFeatures[featureKey].attributes.has(attributeKey)) {
|
|
211
220
|
usedIn.features.add(featureKey);
|
|
@@ -221,12 +230,14 @@ export async function findAttributeUsage(
|
|
|
221
230
|
return usedIn;
|
|
222
231
|
}
|
|
223
232
|
|
|
224
|
-
export async function findUnusedSegments(
|
|
233
|
+
export async function findUnusedSegments(
|
|
234
|
+
deps: Dependencies,
|
|
235
|
+
usageInFeatures: UsageInFeatures,
|
|
236
|
+
): Promise<Set<SegmentKey>> {
|
|
225
237
|
const { datasource } = deps;
|
|
226
238
|
const unusedSegments = new Set<SegmentKey>();
|
|
227
239
|
|
|
228
240
|
const allSegmentKeys = await datasource.listSegments();
|
|
229
|
-
const usageInFeatures = await findAllUsageInFeatures(deps);
|
|
230
241
|
const usedSegmentKeys = new Set<SegmentKey>();
|
|
231
242
|
|
|
232
243
|
for (const featureKey in usageInFeatures) {
|
|
@@ -244,13 +255,15 @@ export async function findUnusedSegments(deps: Dependencies): Promise<Set<Segmen
|
|
|
244
255
|
return unusedSegments;
|
|
245
256
|
}
|
|
246
257
|
|
|
247
|
-
export async function findUnusedAttributes(
|
|
258
|
+
export async function findUnusedAttributes(
|
|
259
|
+
deps: Dependencies,
|
|
260
|
+
usageInFeatures: UsageInFeatures,
|
|
261
|
+
usageInSegments: UsageInSegments,
|
|
262
|
+
): Promise<Set<AttributeKey>> {
|
|
248
263
|
const { datasource } = deps;
|
|
249
264
|
const unusedAttributes = new Set<AttributeKey>();
|
|
250
265
|
|
|
251
266
|
const allAttributeKeys = await datasource.listAttributes();
|
|
252
|
-
const usageInFeatures = await findAllUsageInFeatures(deps);
|
|
253
|
-
const usageInSegments = await findAllUsageInSegments(deps);
|
|
254
267
|
const usedAttributeKeys = new Set<AttributeKey>();
|
|
255
268
|
|
|
256
269
|
for (const featureKey in usageInFeatures) {
|
|
@@ -275,6 +288,7 @@ export async function findUnusedAttributes(deps: Dependencies): Promise<Set<Attr
|
|
|
275
288
|
}
|
|
276
289
|
|
|
277
290
|
export interface FindUsageOptions {
|
|
291
|
+
feature?: string;
|
|
278
292
|
segment?: string;
|
|
279
293
|
attribute?: string;
|
|
280
294
|
|
|
@@ -289,9 +303,36 @@ export async function findUsageInProject(deps: Dependencies, options: FindUsageO
|
|
|
289
303
|
|
|
290
304
|
console.log("");
|
|
291
305
|
|
|
306
|
+
const usageInFeatures = await findAllUsageInFeatures(deps);
|
|
307
|
+
const usageInSegments = await findAllUsageInSegments(deps);
|
|
308
|
+
|
|
309
|
+
// feature
|
|
310
|
+
if (options.feature) {
|
|
311
|
+
const usedInFeatures = await findFeatureUsage(usageInFeatures, options.feature);
|
|
312
|
+
|
|
313
|
+
if (usedInFeatures.size === 0) {
|
|
314
|
+
console.log(`Feature "${options.feature}" is not used in any features.`);
|
|
315
|
+
} else {
|
|
316
|
+
console.log(`Feature "${options.feature}" is used in the following features:\n`);
|
|
317
|
+
|
|
318
|
+
for (const featureKey of Array.from(usedInFeatures)) {
|
|
319
|
+
if (options.authors) {
|
|
320
|
+
const entries = await datasource.listHistoryEntries("feature", featureKey);
|
|
321
|
+
const authors = Array.from(new Set(entries.map((entry) => entry.author)));
|
|
322
|
+
|
|
323
|
+
console.log(` - ${featureKey} (Authors: ${authors.join(", ")})`);
|
|
324
|
+
} else {
|
|
325
|
+
console.log(` - ${featureKey}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
292
333
|
// segment
|
|
293
334
|
if (options.segment) {
|
|
294
|
-
const usedInFeatures = await findSegmentUsage(
|
|
335
|
+
const usedInFeatures = await findSegmentUsage(usageInFeatures, options.segment);
|
|
295
336
|
|
|
296
337
|
if (usedInFeatures.size === 0) {
|
|
297
338
|
console.log(`Segment "${options.segment}" is not used in any features.`);
|
|
@@ -315,7 +356,7 @@ export async function findUsageInProject(deps: Dependencies, options: FindUsageO
|
|
|
315
356
|
|
|
316
357
|
// attribute
|
|
317
358
|
if (options.attribute) {
|
|
318
|
-
const usedIn = await findAttributeUsage(
|
|
359
|
+
const usedIn = await findAttributeUsage(usageInFeatures, usageInSegments, options.attribute);
|
|
319
360
|
|
|
320
361
|
if (usedIn.features.size === 0 && usedIn.segments.size === 0) {
|
|
321
362
|
console.log(`Attribute "${options.attribute}" is not used in any features or segments.`);
|
|
@@ -323,36 +364,61 @@ export async function findUsageInProject(deps: Dependencies, options: FindUsageO
|
|
|
323
364
|
return;
|
|
324
365
|
}
|
|
325
366
|
|
|
326
|
-
if (usedIn.
|
|
327
|
-
console.log(`Attribute "${options.attribute}" is used in the following
|
|
367
|
+
if (usedIn.segments.size > 0) {
|
|
368
|
+
console.log(`Attribute "${options.attribute}" is used in the following segments:\n`);
|
|
328
369
|
|
|
329
|
-
for (const
|
|
370
|
+
for (const segmentKey of Array.from(usedIn.segments)) {
|
|
330
371
|
if (options.authors) {
|
|
331
|
-
const entries = await datasource.listHistoryEntries("
|
|
372
|
+
const entries = await datasource.listHistoryEntries("segment", segmentKey);
|
|
332
373
|
const authors = Array.from(new Set(entries.map((entry) => entry.author)));
|
|
333
374
|
|
|
334
|
-
console.log(` - ${
|
|
375
|
+
console.log(` - ${segmentKey} (Authors: ${authors.join(", ")})`);
|
|
335
376
|
} else {
|
|
336
|
-
console.log(` - ${
|
|
377
|
+
console.log(` - ${segmentKey}`);
|
|
337
378
|
}
|
|
338
379
|
}
|
|
339
380
|
|
|
340
|
-
|
|
381
|
+
// features affected by above segments
|
|
382
|
+
const affectedFeatures = new Set<FeatureKey>();
|
|
383
|
+
|
|
384
|
+
for (const segmentKey of Array.from(usedIn.segments)) {
|
|
385
|
+
const featureKeys = await findSegmentUsage(usageInFeatures, segmentKey);
|
|
386
|
+
featureKeys.forEach((featureKey) => affectedFeatures.add(featureKey));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (affectedFeatures.size > 0) {
|
|
390
|
+
console.log(`\nSegments above are used in the following features:\n`);
|
|
391
|
+
|
|
392
|
+
for (const featureKey of Array.from(affectedFeatures)) {
|
|
393
|
+
if (options.authors) {
|
|
394
|
+
const entries = await datasource.listHistoryEntries("feature", featureKey);
|
|
395
|
+
const authors = Array.from(new Set(entries.map((entry) => entry.author)));
|
|
396
|
+
|
|
397
|
+
console.log(` - ${featureKey} (Authors: ${authors.join(", ")})`);
|
|
398
|
+
} else {
|
|
399
|
+
console.log(` - ${featureKey}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
console.log("");
|
|
404
|
+
}
|
|
341
405
|
}
|
|
342
406
|
|
|
343
|
-
if (usedIn.
|
|
344
|
-
console.log(`Attribute "${options.attribute}" is used in the following
|
|
407
|
+
if (usedIn.features.size > 0) {
|
|
408
|
+
console.log(`Attribute "${options.attribute}" is used directly in the following features:\n`);
|
|
345
409
|
|
|
346
|
-
for (const
|
|
410
|
+
for (const featureKey of Array.from(usedIn.features)) {
|
|
347
411
|
if (options.authors) {
|
|
348
|
-
const entries = await datasource.listHistoryEntries("
|
|
412
|
+
const entries = await datasource.listHistoryEntries("feature", featureKey);
|
|
349
413
|
const authors = Array.from(new Set(entries.map((entry) => entry.author)));
|
|
350
414
|
|
|
351
|
-
console.log(` - ${
|
|
415
|
+
console.log(` - ${featureKey} (Authors: ${authors.join(", ")})`);
|
|
352
416
|
} else {
|
|
353
|
-
console.log(` - ${
|
|
417
|
+
console.log(` - ${featureKey}`);
|
|
354
418
|
}
|
|
355
419
|
}
|
|
420
|
+
|
|
421
|
+
console.log("");
|
|
356
422
|
}
|
|
357
423
|
|
|
358
424
|
return;
|
|
@@ -360,7 +426,7 @@ export async function findUsageInProject(deps: Dependencies, options: FindUsageO
|
|
|
360
426
|
|
|
361
427
|
// unused segments
|
|
362
428
|
if (options.unusedSegments) {
|
|
363
|
-
const unusedSegments = await findUnusedSegments(deps);
|
|
429
|
+
const unusedSegments = await findUnusedSegments(deps, usageInFeatures);
|
|
364
430
|
|
|
365
431
|
if (unusedSegments.size === 0) {
|
|
366
432
|
console.log("No unused segments found.");
|
|
@@ -384,7 +450,7 @@ export async function findUsageInProject(deps: Dependencies, options: FindUsageO
|
|
|
384
450
|
|
|
385
451
|
// unused attributes
|
|
386
452
|
if (options.unusedAttributes) {
|
|
387
|
-
const unusedAttributes = await findUnusedAttributes(deps);
|
|
453
|
+
const unusedAttributes = await findUnusedAttributes(deps, usageInFeatures, usageInSegments);
|
|
388
454
|
|
|
389
455
|
if (unusedAttributes.size === 0) {
|
|
390
456
|
console.log("No unused attributes found.");
|
|
@@ -420,6 +486,7 @@ export const findUsagePlugin: Plugin = {
|
|
|
420
486
|
options: parsed,
|
|
421
487
|
},
|
|
422
488
|
{
|
|
489
|
+
feature: parsed.feature,
|
|
423
490
|
segment: parsed.segment,
|
|
424
491
|
attribute: parsed.attribute,
|
|
425
492
|
unusedSegments: parsed.unusedSegments,
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
|
|
4
|
-
import * as mkdirp from "mkdirp";
|
|
5
|
-
|
|
6
4
|
import { generateTypeScriptCodeForProject } from "./typescript";
|
|
7
5
|
import { Dependencies } from "../dependencies";
|
|
8
6
|
import { Plugin } from "../cli";
|
|
@@ -32,7 +30,7 @@ export async function generateCodeForProject(
|
|
|
32
30
|
|
|
33
31
|
if (!fs.existsSync(absolutePath)) {
|
|
34
32
|
console.log(`Creating output directory: ${absolutePath}`);
|
|
35
|
-
|
|
33
|
+
fs.mkdirSync(absolutePath, { recursive: true });
|
|
36
34
|
} else {
|
|
37
35
|
console.log(`Output directory already exists at: ${absolutePath}`);
|
|
38
36
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
|
|
4
|
-
import { Attribute } from "@featurevisor/types";
|
|
4
|
+
import type { Attribute } from "@featurevisor/types";
|
|
5
5
|
import { Dependencies } from "../dependencies";
|
|
6
6
|
|
|
7
7
|
function convertFeaturevisorTypeToTypeScriptType(featurevisorType: string) {
|
|
@@ -19,7 +19,7 @@ function convertFeaturevisorTypeToTypeScriptType(featurevisorType: string) {
|
|
|
19
19
|
case "array":
|
|
20
20
|
return "string[]";
|
|
21
21
|
case "object":
|
|
22
|
-
return "any"; // @
|
|
22
|
+
return "any"; // @NOTE: do a flat dictionary
|
|
23
23
|
case "json":
|
|
24
24
|
return "any";
|
|
25
25
|
default:
|
|
@@ -47,30 +47,6 @@ function getRelativePath(from, to) {
|
|
|
47
47
|
return relativePath;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
function getFeaturevisorTypeFromValue(value) {
|
|
51
|
-
if (typeof value === "boolean") {
|
|
52
|
-
return "boolean";
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (typeof value === "string") {
|
|
56
|
-
return "string";
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (typeof value === "number") {
|
|
60
|
-
if (Number.isInteger(value)) {
|
|
61
|
-
return "integer";
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return "double";
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (value instanceof Date) {
|
|
68
|
-
return "date";
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
throw new Error("Could not detect Featurevisor type from value");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
50
|
const instanceSnippet = `
|
|
75
51
|
import { FeaturevisorInstance } from "@featurevisor/sdk";
|
|
76
52
|
|
|
@@ -121,7 +97,7 @@ export async function generateTypeScriptCodeForProject(deps: Dependencies, outpu
|
|
|
121
97
|
})
|
|
122
98
|
.join("\n");
|
|
123
99
|
const contextContent = `
|
|
124
|
-
import { AttributeKey, AttributeValue } from "@featurevisor/types";
|
|
100
|
+
import type { AttributeKey, AttributeValue } from "@featurevisor/types";
|
|
125
101
|
|
|
126
102
|
export interface Context {
|
|
127
103
|
${attributeProperties}
|
|
@@ -152,8 +128,10 @@ ${attributeProperties}
|
|
|
152
128
|
let variableMethods = "";
|
|
153
129
|
|
|
154
130
|
if (parsedFeature.variablesSchema) {
|
|
155
|
-
|
|
156
|
-
|
|
131
|
+
const variableKeys = Object.keys(parsedFeature.variablesSchema);
|
|
132
|
+
|
|
133
|
+
for (const variableKey of variableKeys) {
|
|
134
|
+
const variableSchema = parsedFeature.variablesSchema[variableKey];
|
|
157
135
|
const variableType = variableSchema.type;
|
|
158
136
|
|
|
159
137
|
const internalMethodName = `getVariable${
|
package/src/index.ts
CHANGED
package/src/info/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Dependencies } from "../dependencies";
|
|
2
|
-
import { getMatrixCombinations } from "../
|
|
2
|
+
import { getMatrixCombinations } from "../list/matrix";
|
|
3
3
|
import { Plugin } from "../cli";
|
|
4
4
|
|
|
5
5
|
export async function showProjectInfo(deps: Dependencies) {
|
|
@@ -22,7 +22,7 @@ export async function showProjectInfo(deps: Dependencies) {
|
|
|
22
22
|
const feature = await datasource.readFeature(featureKey);
|
|
23
23
|
|
|
24
24
|
if (feature.variablesSchema) {
|
|
25
|
-
variablesCount += feature.variablesSchema.length;
|
|
25
|
+
variablesCount += Object.keys(feature.variablesSchema).length;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
package/src/init/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as tar from "tar";
|
|
|
3
3
|
|
|
4
4
|
import { Plugin } from "../cli";
|
|
5
5
|
|
|
6
|
-
export const DEFAULT_EXAMPLE = "
|
|
6
|
+
export const DEFAULT_EXAMPLE = "yml";
|
|
7
7
|
|
|
8
8
|
export const EXAMPLES_ORG_NAME = "fahad19";
|
|
9
9
|
export const EXAMPLES_REPO_NAME = "featurevisor";
|
|
@@ -12,7 +12,7 @@ export const EXAMPLES_BRANCH_NAME = "main";
|
|
|
12
12
|
export const EXAMPLES_TAR_URL = `https://codeload.github.com/${EXAMPLES_ORG_NAME}/${EXAMPLES_REPO_NAME}/tar.gz/${EXAMPLES_BRANCH_NAME}`;
|
|
13
13
|
|
|
14
14
|
function getExamplePath(exampleName: string) {
|
|
15
|
-
return `${EXAMPLES_REPO_NAME}-${EXAMPLES_BRANCH_NAME}/examples
|
|
15
|
+
return `${EXAMPLES_REPO_NAME}-${EXAMPLES_BRANCH_NAME}/examples/example-${exampleName}/`;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function initProject(
|
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
3
|
export function getAttributeZodSchema() {
|
|
4
|
+
const propertySchema = z.object({
|
|
5
|
+
type: z.enum([
|
|
6
|
+
"boolean",
|
|
7
|
+
"string",
|
|
8
|
+
"integer",
|
|
9
|
+
"double",
|
|
10
|
+
"date",
|
|
11
|
+
"semver",
|
|
12
|
+
"array",
|
|
13
|
+
|
|
14
|
+
// @NOTE: intentionally ignored for now to not allow nesting
|
|
15
|
+
// "object",
|
|
16
|
+
]),
|
|
17
|
+
description: z.string().optional(),
|
|
18
|
+
});
|
|
19
|
+
|
|
4
20
|
const attributeZodSchema = z
|
|
5
21
|
.object({
|
|
6
22
|
archived: z.boolean().optional(),
|
|
7
|
-
type: z.enum(["boolean", "string", "integer", "double", "date", "semver"]),
|
|
23
|
+
type: z.enum(["boolean", "string", "integer", "double", "date", "semver", "object", "array"]),
|
|
8
24
|
description: z.string(),
|
|
9
|
-
|
|
25
|
+
properties: z.record(z.string(), propertySchema).optional(),
|
|
10
26
|
})
|
|
11
27
|
.strict();
|
|
12
28
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Group } from "@featurevisor/types";
|
|
1
|
+
import type { Group, Rule } from "@featurevisor/types";
|
|
2
2
|
|
|
3
3
|
import { Datasource } from "../datasource";
|
|
4
4
|
|
|
5
|
-
// @
|
|
5
|
+
// @NOTE: ideally in future, this check should be done from Feature level,
|
|
6
6
|
// as well as Group level as done here
|
|
7
7
|
export async function checkForFeatureExceedingGroupSlotPercentage(
|
|
8
8
|
datasource: Datasource,
|
|
@@ -22,16 +22,36 @@ export async function checkForFeatureExceedingGroupSlotPercentage(
|
|
|
22
22
|
|
|
23
23
|
const parsedFeature = await datasource.readFeature(featureKey);
|
|
24
24
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
const hasEnvironments =
|
|
26
|
+
parsedFeature.rules &&
|
|
27
|
+
!Array.isArray(parsedFeature.rules) &&
|
|
28
|
+
Object.keys(parsedFeature.rules).length > 0;
|
|
29
|
+
|
|
30
|
+
if (hasEnvironments && parsedFeature.rules) {
|
|
31
|
+
// with environments
|
|
32
|
+
const environmentKeys = Object.keys(parsedFeature.rules);
|
|
33
|
+
|
|
34
|
+
for (const environmentKey of environmentKeys) {
|
|
35
|
+
const rules = parsedFeature.rules[environmentKey];
|
|
36
|
+
|
|
37
|
+
for (const rule of rules) {
|
|
38
|
+
if (rule.percentage > maxPercentageForRule) {
|
|
39
|
+
// @NOTE: this does not help with same feature belonging to multiple slots. fix that.
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Feature ${featureKey}'s rule ${rule.key} in ${environmentKey} has a percentage of ${rule.percentage} which is greater than the maximum percentage of ${maxPercentageForRule} for the slot`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} else if (parsedFeature.rules) {
|
|
47
|
+
// no environments
|
|
48
|
+
const rules = parsedFeature.rules as Rule[];
|
|
29
49
|
|
|
30
50
|
for (const rule of rules) {
|
|
31
51
|
if (rule.percentage > maxPercentageForRule) {
|
|
32
|
-
// @
|
|
52
|
+
// @NOTE: this does not help with same feature belonging to multiple slots. fix that.
|
|
33
53
|
throw new Error(
|
|
34
|
-
`Feature ${featureKey}'s rule ${rule.key}
|
|
54
|
+
`Feature ${featureKey}'s rule ${rule.key} has a percentage of ${rule.percentage} which is greater than the maximum percentage of ${maxPercentageForRule} for the slot`,
|
|
35
55
|
);
|
|
36
56
|
}
|
|
37
57
|
}
|
|
@@ -4,7 +4,14 @@ import { ProjectConfig } from "../config";
|
|
|
4
4
|
|
|
5
5
|
const commonOperators: [string, ...string[]] = ["equals", "notEquals"];
|
|
6
6
|
const numericOperators = ["greaterThan", "greaterThanOrEquals", "lessThan", "lessThanOrEquals"];
|
|
7
|
-
const stringOperators = [
|
|
7
|
+
const stringOperators = [
|
|
8
|
+
"contains",
|
|
9
|
+
"notContains",
|
|
10
|
+
"startsWith",
|
|
11
|
+
"endsWith",
|
|
12
|
+
"includes",
|
|
13
|
+
"notIncludes",
|
|
14
|
+
];
|
|
8
15
|
const semverOperators = [
|
|
9
16
|
"semverEquals",
|
|
10
17
|
"semverNotEquals",
|
|
@@ -15,6 +22,8 @@ const semverOperators = [
|
|
|
15
22
|
];
|
|
16
23
|
const dateOperators = ["before", "after"];
|
|
17
24
|
const arrayOperators = ["in", "notIn"];
|
|
25
|
+
const regexOperators = ["matches", "notMatches"];
|
|
26
|
+
const operatorsWithoutValue = ["exists", "notExists"];
|
|
18
27
|
|
|
19
28
|
export function getConditionsZodSchema(
|
|
20
29
|
projectConfig: ProjectConfig,
|
|
@@ -35,15 +44,27 @@ export function getConditionsZodSchema(
|
|
|
35
44
|
...semverOperators,
|
|
36
45
|
...dateOperators,
|
|
37
46
|
...arrayOperators,
|
|
47
|
+
...regexOperators,
|
|
48
|
+
...operatorsWithoutValue,
|
|
38
49
|
]),
|
|
39
|
-
value: z
|
|
40
|
-
z.string(),
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
value: z
|
|
51
|
+
.union([z.string(), z.array(z.string()), z.number(), z.boolean(), z.date(), z.null()])
|
|
52
|
+
.optional(),
|
|
53
|
+
regexFlags: z
|
|
54
|
+
.string()
|
|
55
|
+
.refine(
|
|
56
|
+
(value) => {
|
|
57
|
+
if (typeof value === "undefined") {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return /^[gimsuy]{1,}$/.test(value);
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
message: `regexFlags must of one or more of these characters: g, i, m, s, u, y`,
|
|
65
|
+
},
|
|
66
|
+
)
|
|
67
|
+
.optional(),
|
|
47
68
|
})
|
|
48
69
|
.superRefine((data, context) => {
|
|
49
70
|
// common
|
|
@@ -109,6 +130,35 @@ export function getConditionsZodSchema(
|
|
|
109
130
|
path: ["value"],
|
|
110
131
|
});
|
|
111
132
|
}
|
|
133
|
+
|
|
134
|
+
// regex
|
|
135
|
+
if (regexOperators.includes(data.operator)) {
|
|
136
|
+
if (typeof data.value !== "string") {
|
|
137
|
+
context.addIssue({
|
|
138
|
+
code: z.ZodIssueCode.custom,
|
|
139
|
+
message: `when operator is "${data.operator}", value must be a string`,
|
|
140
|
+
path: ["value"],
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
// regex flags are not needed
|
|
145
|
+
if (data.regexFlags) {
|
|
146
|
+
context.addIssue({
|
|
147
|
+
code: z.ZodIssueCode.custom,
|
|
148
|
+
message: `when operator is nether "matches" nor "notMatches", regexFlags are not needed`,
|
|
149
|
+
path: ["regexFlags"],
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// operators without value
|
|
155
|
+
if (operatorsWithoutValue.includes(data.operator) && data.value !== undefined) {
|
|
156
|
+
context.addIssue({
|
|
157
|
+
code: z.ZodIssueCode.custom,
|
|
158
|
+
message: `when operator is "${data.operator}", value is not needed`,
|
|
159
|
+
path: ["value"],
|
|
160
|
+
});
|
|
161
|
+
}
|
|
112
162
|
});
|
|
113
163
|
|
|
114
164
|
const andOrNotConditionZodSchema = z.union([
|
|
@@ -129,9 +179,15 @@ export function getConditionsZodSchema(
|
|
|
129
179
|
.strict(),
|
|
130
180
|
]);
|
|
131
181
|
|
|
182
|
+
const everyoneZodSchema = z.literal("*");
|
|
183
|
+
|
|
132
184
|
const conditionZodSchema = z.union([andOrNotConditionZodSchema, plainConditionZodSchema]);
|
|
133
185
|
|
|
134
|
-
const conditionsZodSchema = z.union([
|
|
186
|
+
const conditionsZodSchema = z.union([
|
|
187
|
+
conditionZodSchema,
|
|
188
|
+
z.array(conditionZodSchema),
|
|
189
|
+
everyoneZodSchema,
|
|
190
|
+
]);
|
|
135
191
|
|
|
136
192
|
return conditionsZodSchema;
|
|
137
193
|
}
|