@featurevisor/core 1.35.3 → 2.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/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
|
@@ -5,9 +5,7 @@ import {
|
|
|
5
5
|
Feature,
|
|
6
6
|
DatafileContent,
|
|
7
7
|
DatafileContentV1,
|
|
8
|
-
DatafileContentV2,
|
|
9
8
|
Variation,
|
|
10
|
-
Variable,
|
|
11
9
|
VariableOverride,
|
|
12
10
|
Traffic,
|
|
13
11
|
SegmentKey,
|
|
@@ -18,7 +16,6 @@ import {
|
|
|
18
16
|
ExistingState,
|
|
19
17
|
FeatureKey,
|
|
20
18
|
Allocation,
|
|
21
|
-
VariableSchema,
|
|
22
19
|
Expose,
|
|
23
20
|
Rule,
|
|
24
21
|
Force,
|
|
@@ -27,9 +24,11 @@ import {
|
|
|
27
24
|
import { ProjectConfig, SCHEMA_VERSION } from "../config";
|
|
28
25
|
import { Datasource } from "../datasource";
|
|
29
26
|
import { extractAttributeKeysFromConditions, extractSegmentKeysFromGroupSegments } from "../utils";
|
|
27
|
+
import { generateHashForDatafile, generateHashForFeature, getSegmentHashes } from "./hashes";
|
|
30
28
|
|
|
31
29
|
import { getTraffic } from "./traffic";
|
|
32
30
|
import { getFeatureRanges } from "./getFeatureRanges";
|
|
31
|
+
import { convertToV1 } from "./convertToV1";
|
|
33
32
|
|
|
34
33
|
export interface CustomDatafileOptions {
|
|
35
34
|
featureKey?: string;
|
|
@@ -41,7 +40,9 @@ export interface CustomDatafileOptions {
|
|
|
41
40
|
inflate?: number;
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
export async function getCustomDatafile(
|
|
43
|
+
export async function getCustomDatafile(
|
|
44
|
+
options: CustomDatafileOptions,
|
|
45
|
+
): Promise<DatafileContent | DatafileContentV1> {
|
|
45
46
|
let featuresToInclude;
|
|
46
47
|
|
|
47
48
|
if (options.featureKey) {
|
|
@@ -69,6 +70,7 @@ export async function getCustomDatafile(options: CustomDatafileOptions): Promise
|
|
|
69
70
|
export interface BuildOptions {
|
|
70
71
|
schemaVersion: string;
|
|
71
72
|
revision: string;
|
|
73
|
+
revisionFromHash?: boolean;
|
|
72
74
|
environment: string | false;
|
|
73
75
|
tag?: string;
|
|
74
76
|
features?: FeatureKey[];
|
|
@@ -80,15 +82,7 @@ export async function buildDatafile(
|
|
|
80
82
|
datasource: Datasource,
|
|
81
83
|
options: BuildOptions,
|
|
82
84
|
existingState: ExistingState,
|
|
83
|
-
): Promise<DatafileContent> {
|
|
84
|
-
const datafileContent: DatafileContentV1 = {
|
|
85
|
-
schemaVersion: options.schemaVersion,
|
|
86
|
-
revision: options.revision,
|
|
87
|
-
attributes: [],
|
|
88
|
-
segments: [],
|
|
89
|
-
features: [],
|
|
90
|
-
};
|
|
91
|
-
|
|
85
|
+
): Promise<DatafileContent | DatafileContentV1> {
|
|
92
86
|
const segmentKeysUsedByTag = new Set<SegmentKey>();
|
|
93
87
|
const attributeKeysUsedByTag = new Set<AttributeKey>();
|
|
94
88
|
const { featureRanges, featureIsInGroup } = await getFeatureRanges(projectConfig, datasource);
|
|
@@ -119,14 +113,14 @@ export async function buildDatafile(
|
|
|
119
113
|
let rules: Rule[];
|
|
120
114
|
let force: Force[] | undefined;
|
|
121
115
|
|
|
122
|
-
if (options.environment
|
|
123
|
-
expose = parsedFeature.
|
|
124
|
-
rules = parsedFeature.
|
|
125
|
-
force = parsedFeature.
|
|
116
|
+
if (options.environment) {
|
|
117
|
+
expose = parsedFeature.expose?.[options.environment] as Expose | undefined;
|
|
118
|
+
rules = parsedFeature.rules?.[options.environment] as Rule[];
|
|
119
|
+
force = parsedFeature.force?.[options.environment] as Force[] | undefined;
|
|
126
120
|
} else {
|
|
127
|
-
expose = parsedFeature.expose;
|
|
121
|
+
expose = parsedFeature.expose as Expose | undefined;
|
|
128
122
|
rules = parsedFeature.rules as Rule[];
|
|
129
|
-
force = parsedFeature.force;
|
|
123
|
+
force = parsedFeature.force as Force[] | undefined;
|
|
130
124
|
}
|
|
131
125
|
|
|
132
126
|
if (expose === false) {
|
|
@@ -151,66 +145,62 @@ export async function buildDatafile(
|
|
|
151
145
|
deprecated: parsedFeature.deprecated === true ? true : undefined,
|
|
152
146
|
bucketBy: parsedFeature.bucketBy || projectConfig.defaultBucketBy,
|
|
153
147
|
required: parsedFeature.required,
|
|
148
|
+
disabledVariationValue: parsedFeature.disabledVariationValue,
|
|
154
149
|
variations: Array.isArray(parsedFeature.variations)
|
|
155
150
|
? parsedFeature.variations.map((variation: Variation) => {
|
|
156
151
|
const mappedVariation: any = {
|
|
157
152
|
value: variation.value,
|
|
158
|
-
weight: variation.weight, // @
|
|
153
|
+
weight: variation.weight, // @NOTE: added so state files can maintain weight info, but datafiles don't need this. find a way to remove it from datafiles later
|
|
154
|
+
variables: variation.variables,
|
|
155
|
+
variableOverrides: variation.variableOverrides,
|
|
159
156
|
};
|
|
160
157
|
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
158
|
+
if (variation.variableOverrides) {
|
|
159
|
+
const variableOverrides = variation.variableOverrides;
|
|
160
|
+
const variableKeys = Object.keys(variableOverrides);
|
|
161
|
+
|
|
162
|
+
for (const variableKey of variableKeys) {
|
|
163
|
+
mappedVariation.variableOverrides[variableKey] = variableOverrides[
|
|
164
|
+
variableKey
|
|
165
|
+
].map((override: VariableOverride) => {
|
|
166
|
+
if (typeof override.conditions !== "undefined") {
|
|
167
|
+
const extractedAttributeKeys = extractAttributeKeysFromConditions(
|
|
168
|
+
override.conditions,
|
|
169
|
+
);
|
|
170
|
+
extractedAttributeKeys.forEach((attributeKey) =>
|
|
171
|
+
attributeKeysUsedByTag.add(attributeKey),
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
conditions:
|
|
176
|
+
projectConfig.stringify && typeof override.conditions !== "string"
|
|
177
|
+
? JSON.stringify(override.conditions)
|
|
178
|
+
: override.conditions,
|
|
179
|
+
value: override.value,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (typeof override.segments !== "undefined") {
|
|
184
|
+
const extractedSegmentKeys = extractSegmentKeysFromGroupSegments(
|
|
185
|
+
override.segments as GroupSegment | GroupSegment[],
|
|
186
|
+
);
|
|
187
|
+
extractedSegmentKeys.forEach((segmentKey) =>
|
|
188
|
+
segmentKeysUsedByTag.add(segmentKey),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
segments:
|
|
193
|
+
projectConfig.stringify && typeof override.segments !== "string"
|
|
194
|
+
? JSON.stringify(override.segments)
|
|
195
|
+
: override.segments,
|
|
196
|
+
value: override.value,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return override;
|
|
201
|
+
});
|
|
173
202
|
}
|
|
174
|
-
|
|
175
|
-
mappedVariable.overrides = variable.overrides.map((override: VariableOverride) => {
|
|
176
|
-
if (typeof override.conditions !== "undefined") {
|
|
177
|
-
const extractedAttributeKeys = extractAttributeKeysFromConditions(
|
|
178
|
-
override.conditions,
|
|
179
|
-
);
|
|
180
|
-
extractedAttributeKeys.forEach((attributeKey) =>
|
|
181
|
-
attributeKeysUsedByTag.add(attributeKey),
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
conditions: projectConfig.stringify
|
|
186
|
-
? JSON.stringify(override.conditions)
|
|
187
|
-
: override.conditions,
|
|
188
|
-
value: override.value,
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (typeof override.segments !== "undefined") {
|
|
193
|
-
const extractedSegmentKeys = extractSegmentKeysFromGroupSegments(
|
|
194
|
-
override.segments as GroupSegment | GroupSegment[],
|
|
195
|
-
);
|
|
196
|
-
extractedSegmentKeys.forEach((segmentKey) =>
|
|
197
|
-
segmentKeysUsedByTag.add(segmentKey),
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
return {
|
|
201
|
-
segments:
|
|
202
|
-
typeof override.segments !== "string" && projectConfig.stringify
|
|
203
|
-
? JSON.stringify(override.segments)
|
|
204
|
-
: override.segments,
|
|
205
|
-
value: override.value,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return override;
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
return mappedVariable;
|
|
213
|
-
});
|
|
203
|
+
}
|
|
214
204
|
|
|
215
205
|
return mappedVariation;
|
|
216
206
|
})
|
|
@@ -246,39 +236,60 @@ export async function buildDatafile(
|
|
|
246
236
|
return {
|
|
247
237
|
key: t.key,
|
|
248
238
|
percentage: t.percentage,
|
|
249
|
-
allocation:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
239
|
+
allocation:
|
|
240
|
+
t.allocation &&
|
|
241
|
+
t.allocation.map((a: Allocation) => {
|
|
242
|
+
return {
|
|
243
|
+
variation: a.variation,
|
|
244
|
+
range: a.range,
|
|
245
|
+
};
|
|
246
|
+
}),
|
|
255
247
|
};
|
|
256
248
|
}),
|
|
257
249
|
};
|
|
258
250
|
|
|
259
251
|
if (featureIsInGroup[featureKey] === true) {
|
|
260
|
-
feature.ranges = featureRanges.get(
|
|
252
|
+
feature.ranges = featureRanges.get(featureKey);
|
|
261
253
|
}
|
|
262
254
|
|
|
263
255
|
if (parsedFeature.variablesSchema) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
256
|
+
const variableKeys = Object.keys(parsedFeature.variablesSchema);
|
|
257
|
+
feature.variablesSchema = {};
|
|
258
|
+
|
|
259
|
+
for (const variableKey of variableKeys) {
|
|
260
|
+
const v = parsedFeature.variablesSchema[variableKey];
|
|
261
|
+
|
|
262
|
+
feature.variablesSchema[variableKey] = {
|
|
263
|
+
key: variableKey,
|
|
267
264
|
type: v.type,
|
|
268
265
|
defaultValue: v.defaultValue,
|
|
269
266
|
deprecated: v.deprecated === true ? true : undefined,
|
|
267
|
+
useDefaultWhenDisabled: v.useDefaultWhenDisabled === true ? true : undefined,
|
|
268
|
+
disabledValue: typeof v.disabledValue !== "undefined" ? v.disabledValue : undefined,
|
|
270
269
|
};
|
|
271
|
-
}
|
|
270
|
+
}
|
|
272
271
|
}
|
|
273
272
|
|
|
274
273
|
if (force) {
|
|
275
|
-
feature.force = force
|
|
276
|
-
|
|
277
|
-
feature.force?.forEach((f) => {
|
|
274
|
+
feature.force = force.map((f) => {
|
|
278
275
|
if (f.segments) {
|
|
279
276
|
const extractedSegmentKeys = extractSegmentKeysFromGroupSegments(f.segments);
|
|
280
277
|
extractedSegmentKeys.forEach((segmentKey) => segmentKeysUsedByTag.add(segmentKey));
|
|
278
|
+
|
|
279
|
+
f.segments =
|
|
280
|
+
typeof f.segments !== "string" && projectConfig.stringify
|
|
281
|
+
? JSON.stringify(f.segments)
|
|
282
|
+
: f.segments;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (f.conditions) {
|
|
286
|
+
f.conditions =
|
|
287
|
+
typeof f.conditions !== "string" && projectConfig.stringify
|
|
288
|
+
? JSON.stringify(f.conditions)
|
|
289
|
+
: f.conditions;
|
|
281
290
|
}
|
|
291
|
+
|
|
292
|
+
return f;
|
|
282
293
|
});
|
|
283
294
|
}
|
|
284
295
|
|
|
@@ -335,7 +346,7 @@ export async function buildDatafile(
|
|
|
335
346
|
continue;
|
|
336
347
|
}
|
|
337
348
|
|
|
338
|
-
if (attributeKeysUsedByTag.has(attributeKey) === false
|
|
349
|
+
if (attributeKeysUsedByTag.has(attributeKey) === false) {
|
|
339
350
|
continue;
|
|
340
351
|
}
|
|
341
352
|
|
|
@@ -344,21 +355,17 @@ export async function buildDatafile(
|
|
|
344
355
|
type: parsedAttribute.type,
|
|
345
356
|
};
|
|
346
357
|
|
|
347
|
-
if (parsedAttribute.capture === true) {
|
|
348
|
-
attribute.capture = true;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
358
|
attributes.push(attribute);
|
|
352
359
|
}
|
|
353
360
|
}
|
|
354
361
|
|
|
355
362
|
// inflate
|
|
356
|
-
if (options.inflate) {
|
|
363
|
+
if (options.inflate && options.inflate >= 2) {
|
|
357
364
|
const allFeatureKeys = features.map((f) => f.key);
|
|
358
365
|
const allSegmentKeys = segments.map((s) => s.key);
|
|
359
366
|
const allAttributeKeys = attributes.map((a) => a.key);
|
|
360
367
|
|
|
361
|
-
for (let i = 0; i < options.inflate; i++) {
|
|
368
|
+
for (let i = 0; i < options.inflate - 1; i++) {
|
|
362
369
|
// feature
|
|
363
370
|
for (const featureKey of allFeatureKeys) {
|
|
364
371
|
const originalFeature = features.find((f) => f.key === featureKey) as Feature;
|
|
@@ -391,47 +398,77 @@ export async function buildDatafile(
|
|
|
391
398
|
}
|
|
392
399
|
}
|
|
393
400
|
|
|
394
|
-
// schema
|
|
395
|
-
if (options.schemaVersion === "
|
|
396
|
-
|
|
397
|
-
const datafileContentV2: DatafileContentV2 = {
|
|
398
|
-
schemaVersion: "2",
|
|
401
|
+
// schema v1
|
|
402
|
+
if (options.schemaVersion === "1") {
|
|
403
|
+
return convertToV1({
|
|
399
404
|
revision: options.revision,
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
features
|
|
403
|
-
|
|
405
|
+
projectConfig,
|
|
406
|
+
attributes,
|
|
407
|
+
features,
|
|
408
|
+
segments,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
404
411
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
412
|
+
// schema v2
|
|
413
|
+
const datafileContentV2: DatafileContent = {
|
|
414
|
+
schemaVersion: "2",
|
|
415
|
+
revision: options.revision,
|
|
416
|
+
segments: {},
|
|
417
|
+
features: {},
|
|
418
|
+
};
|
|
409
419
|
|
|
410
|
-
|
|
420
|
+
datafileContentV2.segments = segments.reduce((acc, segment) => {
|
|
421
|
+
// key check needed for supporting v1 datafile generation
|
|
422
|
+
if (segment.key) {
|
|
411
423
|
acc[segment.key] = segment;
|
|
424
|
+
delete acc[segment.key].key; // remove key from segment, as it is not needed in v2 datafile
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return acc;
|
|
428
|
+
}, {});
|
|
429
|
+
|
|
430
|
+
datafileContentV2.features = features.reduce((acc, feature) => {
|
|
431
|
+
if (!feature.key) {
|
|
412
432
|
return acc;
|
|
413
|
-
}
|
|
433
|
+
}
|
|
414
434
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
feature.variablesSchema = feature.variablesSchema.reduce((vAcc, variableSchema) => {
|
|
418
|
-
vAcc[variableSchema.key] = variableSchema;
|
|
435
|
+
const featureKey = feature.key as FeatureKey;
|
|
436
|
+
const featureV2 = feature;
|
|
419
437
|
|
|
420
|
-
|
|
421
|
-
|
|
438
|
+
// remove key, as it is not needed in v2 datafile
|
|
439
|
+
delete featureV2.key;
|
|
440
|
+
|
|
441
|
+
// remove variablesSchema[key].key
|
|
442
|
+
if (featureV2.variablesSchema) {
|
|
443
|
+
for (const [variableKey, variable] of Object.entries(featureV2.variablesSchema)) {
|
|
444
|
+
if (variable.key) {
|
|
445
|
+
delete featureV2.variablesSchema[variableKey].key;
|
|
446
|
+
}
|
|
422
447
|
}
|
|
448
|
+
}
|
|
423
449
|
|
|
424
|
-
|
|
425
|
-
return acc;
|
|
426
|
-
}, {});
|
|
450
|
+
acc[featureKey] = featureV2;
|
|
427
451
|
|
|
428
|
-
return
|
|
429
|
-
}
|
|
452
|
+
return acc;
|
|
453
|
+
}, {});
|
|
430
454
|
|
|
431
|
-
//
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
455
|
+
// add feature hashes for change detection
|
|
456
|
+
const segmentHashes = getSegmentHashes(datafileContentV2.segments);
|
|
457
|
+
Object.keys(datafileContentV2.features).forEach((featureKey) => {
|
|
458
|
+
const hash = generateHashForFeature(featureKey, datafileContentV2.features, segmentHashes);
|
|
435
459
|
|
|
436
|
-
|
|
460
|
+
datafileContentV2.features[featureKey].hash = hash;
|
|
461
|
+
|
|
462
|
+
// check needed to support --inflate option
|
|
463
|
+
if (existingState.features[featureKey]) {
|
|
464
|
+
existingState.features[featureKey].hash = hash;
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
if (options.revisionFromHash) {
|
|
469
|
+
const datafileHash = generateHashForDatafile(datafileContentV2);
|
|
470
|
+
datafileContentV2.revision = `${datafileHash}`;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return datafileContentV2;
|
|
437
474
|
}
|
|
@@ -5,15 +5,17 @@ import { getNextRevision } from "./revision";
|
|
|
5
5
|
import { buildDatafile, getCustomDatafile } from "./buildDatafile";
|
|
6
6
|
import { Dependencies } from "../dependencies";
|
|
7
7
|
import { Plugin } from "../cli";
|
|
8
|
+
import type { DatafileContent } from "@featurevisor/types";
|
|
8
9
|
|
|
9
10
|
export interface BuildCLIOptions {
|
|
10
11
|
revision?: string;
|
|
12
|
+
revisionFromHash?: boolean;
|
|
11
13
|
schemaVersion?: string;
|
|
12
14
|
|
|
13
15
|
// all three together
|
|
14
16
|
environment?: string;
|
|
15
17
|
feature?: string;
|
|
16
|
-
|
|
18
|
+
json?: boolean;
|
|
17
19
|
pretty?: boolean;
|
|
18
20
|
stateFiles?: boolean; // --no-state-files in CLI
|
|
19
21
|
inflate?: number;
|
|
@@ -48,6 +50,7 @@ async function buildForEnvironment({
|
|
|
48
50
|
{
|
|
49
51
|
schemaVersion: cliOptions.schemaVersion || SCHEMA_VERSION,
|
|
50
52
|
revision: nextRevision,
|
|
53
|
+
revisionFromHash: cliOptions.revisionFromHash,
|
|
51
54
|
environment: environment,
|
|
52
55
|
tag: tag,
|
|
53
56
|
inflate: cliOptions.inflate,
|
|
@@ -56,7 +59,7 @@ async function buildForEnvironment({
|
|
|
56
59
|
);
|
|
57
60
|
|
|
58
61
|
// write datafile for environment/tag
|
|
59
|
-
await datasource.writeDatafile(datafileContent, {
|
|
62
|
+
await datasource.writeDatafile(datafileContent as DatafileContent, {
|
|
60
63
|
environment,
|
|
61
64
|
tag,
|
|
62
65
|
datafilesDir: cliOptions.datafilesDir,
|
|
@@ -85,7 +88,7 @@ export async function buildProject(deps: Dependencies, cliOptions: BuildCLIOptio
|
|
|
85
88
|
* This way we centralize the datafile generation in one place,
|
|
86
89
|
* while tests can be run anywhere else.
|
|
87
90
|
*/
|
|
88
|
-
if (cliOptions.environment && cliOptions.
|
|
91
|
+
if (cliOptions.environment && cliOptions.json) {
|
|
89
92
|
const datafileContent = await getCustomDatafile({
|
|
90
93
|
featureKey: cliOptions.feature,
|
|
91
94
|
environment: cliOptions.environment,
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DatafileContentV1,
|
|
3
|
+
FeatureV1,
|
|
4
|
+
VariableSchema,
|
|
5
|
+
VariationV1,
|
|
6
|
+
Variation,
|
|
7
|
+
VariableV1,
|
|
8
|
+
Feature,
|
|
9
|
+
Segment,
|
|
10
|
+
Attribute,
|
|
11
|
+
} from "@featurevisor/types";
|
|
12
|
+
|
|
13
|
+
import { ProjectConfig } from "../config";
|
|
14
|
+
|
|
15
|
+
export interface ConvertToV1Options {
|
|
16
|
+
revision: string;
|
|
17
|
+
projectConfig: ProjectConfig;
|
|
18
|
+
attributes: Attribute[];
|
|
19
|
+
features: Feature[];
|
|
20
|
+
segments: Segment[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function convertToV1(options: ConvertToV1Options): DatafileContentV1 {
|
|
24
|
+
const datafileContent: DatafileContentV1 = {
|
|
25
|
+
schemaVersion: "1",
|
|
26
|
+
revision: options.revision,
|
|
27
|
+
attributes: options.attributes,
|
|
28
|
+
segments: options.segments,
|
|
29
|
+
features: [],
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const { features, projectConfig } = options;
|
|
33
|
+
|
|
34
|
+
for (const feature of features) {
|
|
35
|
+
const featureV1: FeatureV1 = {
|
|
36
|
+
key: feature.key,
|
|
37
|
+
deprecated: feature.deprecated,
|
|
38
|
+
bucketBy: feature.bucketBy,
|
|
39
|
+
required: feature.required,
|
|
40
|
+
traffic: feature.traffic,
|
|
41
|
+
force: feature.force,
|
|
42
|
+
ranges: feature.ranges,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (feature.variablesSchema && !Array.isArray(feature.variablesSchema)) {
|
|
46
|
+
const variablesSchemaObject = feature.variablesSchema;
|
|
47
|
+
const variablesSchemaArray: VariableSchema[] = [];
|
|
48
|
+
const variableKeys = Object.keys(variablesSchemaObject);
|
|
49
|
+
|
|
50
|
+
for (const variableKey of variableKeys) {
|
|
51
|
+
const v = variablesSchemaObject[variableKey];
|
|
52
|
+
variablesSchemaArray.push({
|
|
53
|
+
key: variableKey,
|
|
54
|
+
type: v.type,
|
|
55
|
+
defaultValue: v.defaultValue,
|
|
56
|
+
deprecated: v.deprecated === true ? true : undefined,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
featureV1.variablesSchema = variablesSchemaArray;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (feature.variations) {
|
|
64
|
+
const variationsV1: VariationV1[] = feature.variations.map((variation: Variation) => {
|
|
65
|
+
const variationV1: VariationV1 = {
|
|
66
|
+
value: variation.value,
|
|
67
|
+
weight: variation.weight || 0, // weight is not used in v1 datafile, but needed for state files
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// variables
|
|
71
|
+
const variablesResult: { [key: string]: VariableV1 } = {};
|
|
72
|
+
|
|
73
|
+
if (variation.variables) {
|
|
74
|
+
const variableKeys = Object.keys(variation.variables);
|
|
75
|
+
|
|
76
|
+
for (const variableKey of variableKeys) {
|
|
77
|
+
const variableValue = variation.variables[variableKey];
|
|
78
|
+
|
|
79
|
+
variablesResult[variableKey] = {
|
|
80
|
+
key: variableKey,
|
|
81
|
+
value: variableValue,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (variation.variableOverrides) {
|
|
87
|
+
const variableKeys = Object.keys(variation.variableOverrides);
|
|
88
|
+
|
|
89
|
+
for (const variableKey of variableKeys) {
|
|
90
|
+
const overrides = variation.variableOverrides[variableKey];
|
|
91
|
+
|
|
92
|
+
if (typeof variablesResult[variableKey] === "undefined") {
|
|
93
|
+
const variableSchema = feature.variablesSchema?.[variableKey];
|
|
94
|
+
|
|
95
|
+
variablesResult[variableKey] = {
|
|
96
|
+
key: variableKey,
|
|
97
|
+
value: variableSchema?.defaultValue, // default value if no variable is defined
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
variablesResult[variableKey].overrides = overrides.map((override) => {
|
|
102
|
+
if (typeof override.conditions !== "undefined") {
|
|
103
|
+
return {
|
|
104
|
+
conditions: projectConfig.stringify
|
|
105
|
+
? JSON.stringify(override.conditions)
|
|
106
|
+
: override.conditions,
|
|
107
|
+
value: override.value,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (typeof override.segments !== "undefined") {
|
|
112
|
+
return {
|
|
113
|
+
segments:
|
|
114
|
+
typeof override.segments !== "string" && projectConfig.stringify
|
|
115
|
+
? JSON.stringify(override.segments)
|
|
116
|
+
: override.segments,
|
|
117
|
+
value: override.value,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
value: override.value,
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
variationV1.variables = Object.keys(variablesResult).map((variableKey) => {
|
|
129
|
+
const variable = variablesResult[variableKey];
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
key: variable.key,
|
|
133
|
+
value: variable.value,
|
|
134
|
+
overrides: variable.overrides,
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
return variationV1;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
featureV1.variations = variationsV1;
|
|
142
|
+
|
|
143
|
+
if (featureV1.force) {
|
|
144
|
+
featureV1.force = featureV1.force.map((force) => {
|
|
145
|
+
if (
|
|
146
|
+
force.conditions &&
|
|
147
|
+
typeof force.conditions === "string" &&
|
|
148
|
+
force.conditions !== "*"
|
|
149
|
+
) {
|
|
150
|
+
force.conditions = JSON.parse(force.conditions);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (force.segments && typeof force.segments === "string" && force.segments !== "*") {
|
|
154
|
+
force.segments = JSON.parse(force.segments);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return force;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
datafileContent.features.push(featureV1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return datafileContent;
|
|
166
|
+
}
|