@featurevisor/core 0.0.3

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/lib/utils.js ADDED
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractAttributeKeysFromConditions = exports.extractSegmentKeysFromGroupSegments = exports.parseYaml = exports.getYAMLFiles = void 0;
4
+ var fs = require("fs");
5
+ var path = require("path");
6
+ var yaml = require("js-yaml");
7
+ function getYAMLFiles(directoryPath) {
8
+ var files = fs.readdirSync(directoryPath);
9
+ var yamlFiles = files.filter(function (file) { return file.endsWith(".yml"); });
10
+ return yamlFiles.map(function (file) { return path.join(directoryPath, file); });
11
+ }
12
+ exports.getYAMLFiles = getYAMLFiles;
13
+ function parseYaml(content) {
14
+ var parsed = yaml.load(content);
15
+ return parsed;
16
+ }
17
+ exports.parseYaml = parseYaml;
18
+ function extractSegmentKeysFromGroupSegments(segments) {
19
+ var result = new Set();
20
+ if (Array.isArray(segments)) {
21
+ segments.forEach(function (segment) {
22
+ extractSegmentKeysFromGroupSegments(segment).forEach(function (segmentKey) {
23
+ result.add(segmentKey);
24
+ });
25
+ });
26
+ }
27
+ if (typeof segments === "object") {
28
+ if ("and" in segments) {
29
+ extractSegmentKeysFromGroupSegments(segments.and).forEach(function (segmentKey) {
30
+ result.add(segmentKey);
31
+ });
32
+ }
33
+ if ("or" in segments) {
34
+ extractSegmentKeysFromGroupSegments(segments.or).forEach(function (segmentKey) {
35
+ result.add(segmentKey);
36
+ });
37
+ }
38
+ }
39
+ if (typeof segments === "string") {
40
+ result.add(segments);
41
+ }
42
+ return result;
43
+ }
44
+ exports.extractSegmentKeysFromGroupSegments = extractSegmentKeysFromGroupSegments;
45
+ function extractAttributeKeysFromConditions(conditions) {
46
+ var result = new Set();
47
+ if (Array.isArray(conditions)) {
48
+ conditions.forEach(function (condition) {
49
+ extractAttributeKeysFromConditions(condition).forEach(function (attributeKey) {
50
+ result.add(attributeKey);
51
+ });
52
+ });
53
+ }
54
+ if ("attribute" in conditions) {
55
+ result.add(conditions.attribute);
56
+ }
57
+ if ("and" in conditions) {
58
+ extractAttributeKeysFromConditions(conditions.and).forEach(function (attributeKey) {
59
+ result.add(attributeKey);
60
+ });
61
+ }
62
+ if ("or" in conditions) {
63
+ extractAttributeKeysFromConditions(conditions.or).forEach(function (attributeKey) {
64
+ result.add(attributeKey);
65
+ });
66
+ }
67
+ return result;
68
+ }
69
+ exports.extractAttributeKeysFromConditions = extractAttributeKeysFromConditions;
70
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@featurevisor/core",
3
+ "version": "0.0.3",
4
+ "description": "Core package of Featurevisor for Node.js usage",
5
+ "main": "lib/index.js",
6
+ "types": "lib/index.d.ts",
7
+ "scripts": {
8
+ "lint": "echo 'not linting in this package yet'",
9
+ "transpile": "rimraf lib && tsc --project tsconfig.cjs.json",
10
+ "dist": "echo 'Nothing to dist here'",
11
+ "build": "npm run transpile",
12
+ "test": "jest --config ../../jest.config.js --verbose"
13
+ },
14
+ "author": {
15
+ "name": "Fahad Heylaal",
16
+ "url": "https://fahad19.com"
17
+ },
18
+ "homepage": "https://featurevisor.com",
19
+ "keywords": [
20
+ "featurevisor",
21
+ "feature",
22
+ "features",
23
+ "flags",
24
+ "feature flags",
25
+ "feature toggles",
26
+ "feature management",
27
+ "experimentation",
28
+ "experiment",
29
+ "experiments"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/fahad19/featurevisor.git"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public",
37
+ "registry": "https://registry.npmjs.org/"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/fahad19/featurevisor/issues"
41
+ },
42
+ "license": "MIT",
43
+ "dependencies": {
44
+ "@featurevisor/sdk": "^0.0.3",
45
+ "@featurevisor/types": "^0.0.3",
46
+ "axios": "^1.3.4",
47
+ "joi": "^17.8.3",
48
+ "js-yaml": "^4.1.0",
49
+ "mkdirp": "^2.1.3",
50
+ "tar": "^6.1.13"
51
+ },
52
+ "devDependencies": {
53
+ "@types/js-yaml": "^4.0.5",
54
+ "@types/tar": "^6.1.4"
55
+ },
56
+ "gitHead": "303aca84f7c32163cb1079513d799296bd892e72"
57
+ }
package/src/builder.ts ADDED
@@ -0,0 +1,359 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+
4
+ import * as mkdirp from "mkdirp";
5
+
6
+ import {
7
+ Segment,
8
+ Feature,
9
+ DatafileContent,
10
+ Variation,
11
+ Variable,
12
+ VariableOverride,
13
+ Traffic,
14
+ SegmentKey,
15
+ AttributeKey,
16
+ Attribute,
17
+ GroupSegment,
18
+ Condition,
19
+ ExistingFeatures,
20
+ ExistingFeature,
21
+ ExistingState,
22
+ ParsedFeature,
23
+ FeatureKey,
24
+ Allocation,
25
+ EnvironmentKey,
26
+ } from "@featurevisor/types";
27
+ import { MAX_BUCKETED_NUMBER } from "@featurevisor/sdk";
28
+
29
+ import { ProjectConfig, SCHEMA_VERSION } from "./config";
30
+ import { getNewTraffic } from "./traffic";
31
+ import {
32
+ parseYaml,
33
+ extractAttributeKeysFromConditions,
34
+ extractSegmentKeysFromGroupSegments,
35
+ } from "./utils";
36
+
37
+ export interface BuildOptions {
38
+ schemaVersion: string;
39
+ revision: string;
40
+ environment: string;
41
+ tag: string;
42
+ }
43
+
44
+ export function getDatafilePath(
45
+ projectConfig: ProjectConfig,
46
+ environment: EnvironmentKey,
47
+ tag: string,
48
+ ): string {
49
+ const fileName = `datafile-tag-${tag}.json`;
50
+
51
+ return path.join(projectConfig.outputDirectoryPath, environment, fileName);
52
+ }
53
+
54
+ export function getExistingStateFilePath(
55
+ projectConfig: ProjectConfig,
56
+ environment: EnvironmentKey,
57
+ tag: string,
58
+ ): string {
59
+ return path.join(projectConfig.stateDirectoryPath, `existing-state-${environment}-${tag}.json`);
60
+ }
61
+
62
+ export function buildDatafile(
63
+ projectConfig: ProjectConfig,
64
+ options: BuildOptions,
65
+ ): DatafileContent {
66
+ const datafileContent: DatafileContent = {
67
+ schemaVersion: options.schemaVersion,
68
+ revision: options.revision,
69
+ attributes: [],
70
+ segments: [],
71
+ features: [],
72
+ };
73
+
74
+ const segmentKeysUsedByTag = new Set<SegmentKey>();
75
+ const attributeKeysUsedByTag = new Set<AttributeKey>();
76
+
77
+ // features
78
+ const features: Feature[] = [];
79
+ const featuresDirectory = projectConfig.featuresDirectoryPath;
80
+
81
+ let existingState = {} as ExistingState;
82
+ let existingFeatures = {} as ExistingFeatures;
83
+
84
+ const existingStateFilePath = getExistingStateFilePath(
85
+ projectConfig,
86
+ options.environment,
87
+ options.tag,
88
+ );
89
+
90
+ if (fs.existsSync(existingStateFilePath)) {
91
+ existingState = JSON.parse(fs.readFileSync(existingStateFilePath, "utf8")) as ExistingState;
92
+
93
+ if (existingState && existingState.features) {
94
+ existingFeatures = existingState.features;
95
+ }
96
+ }
97
+
98
+ if (fs.existsSync(featuresDirectory)) {
99
+ const featureFiles = fs.readdirSync(featuresDirectory).filter((f) => f.endsWith(".yml"));
100
+
101
+ for (const featureFile of featureFiles) {
102
+ const featureKey = path.basename(featureFile, ".yml") as FeatureKey;
103
+ const featureFilePath = path.join(featuresDirectory, featureFile);
104
+ const parsedFeature = parseYaml(fs.readFileSync(featureFilePath, "utf8")) as ParsedFeature;
105
+
106
+ if (parsedFeature.archived === true) {
107
+ continue;
108
+ }
109
+
110
+ if (parsedFeature.tags.indexOf(options.tag) === -1) {
111
+ continue;
112
+ }
113
+
114
+ if (parsedFeature.environments[options.environment].expose === false) {
115
+ continue;
116
+ }
117
+
118
+ const featureTraffic: Traffic[] = [];
119
+
120
+ for (const parsedRule of parsedFeature.environments[options.environment].rules) {
121
+ const extractedSegmentKeys = extractSegmentKeysFromGroupSegments(parsedRule.segments);
122
+ extractedSegmentKeys.forEach((segmentKey) => segmentKeysUsedByTag.add(segmentKey));
123
+
124
+ featureTraffic.push({
125
+ key: parsedRule.key,
126
+ segments:
127
+ typeof parsedRule.segments === "string"
128
+ ? parsedRule.segments
129
+ : JSON.stringify(parsedRule.segments),
130
+ percentage: parsedRule.percentage * (MAX_BUCKETED_NUMBER / 100),
131
+ allocation: [],
132
+ });
133
+ }
134
+
135
+ const feature: Feature = {
136
+ key: featureKey,
137
+ defaultVariation: parsedFeature.defaultVariation,
138
+ bucketBy: parsedFeature.bucketBy || projectConfig.defaultBucketBy,
139
+ variations: parsedFeature.variations.map((variation: Variation) => {
140
+ const mappedVariation: any = {
141
+ type: variation.type,
142
+ value: variation.value,
143
+ };
144
+
145
+ if (!variation.variables) {
146
+ return mappedVariation;
147
+ }
148
+
149
+ mappedVariation.variables = variation.variables.map((variable: Variable) => {
150
+ const mappedVariable: any = {
151
+ key: variable.key,
152
+ value: variable.value,
153
+ };
154
+
155
+ if (!variable.overrides) {
156
+ return mappedVariable;
157
+ }
158
+
159
+ mappedVariable.overrides = variable.overrides.map((override: VariableOverride) => {
160
+ if (typeof override.conditions !== "undefined") {
161
+ const extractedAttributeKeys = extractAttributeKeysFromConditions(
162
+ override.conditions,
163
+ );
164
+ extractedAttributeKeys.forEach((attributeKey) =>
165
+ attributeKeysUsedByTag.add(attributeKey),
166
+ );
167
+
168
+ return {
169
+ conditions: JSON.stringify(override.conditions),
170
+ value: override.value,
171
+ };
172
+ }
173
+
174
+ if (typeof override.segments !== "undefined") {
175
+ const extractedSegmentKeys = extractSegmentKeysFromGroupSegments(
176
+ override.segments as GroupSegment | GroupSegment[],
177
+ );
178
+ extractedSegmentKeys.forEach((segmentKey) => segmentKeysUsedByTag.add(segmentKey));
179
+
180
+ return {
181
+ segments: JSON.stringify(override.segments),
182
+ value: override.value,
183
+ };
184
+ }
185
+
186
+ return override;
187
+ });
188
+
189
+ return mappedVariable;
190
+ });
191
+
192
+ return mappedVariation;
193
+ }),
194
+ traffic: getNewTraffic(
195
+ parsedFeature.variations,
196
+ parsedFeature.environments[options.environment].rules,
197
+ existingFeatures && existingFeatures[featureKey],
198
+ ),
199
+ };
200
+
201
+ if (parsedFeature.variablesSchema) {
202
+ feature.variablesSchema = parsedFeature.variablesSchema;
203
+ }
204
+
205
+ if (parsedFeature.environments[options.environment].force) {
206
+ feature.force = parsedFeature.environments[options.environment].force;
207
+ }
208
+
209
+ features.push(feature);
210
+ }
211
+ }
212
+
213
+ // segments
214
+ const segments: Segment[] = [];
215
+ const segmentsDirectory = projectConfig.segmentsDirectoryPath;
216
+
217
+ if (fs.existsSync(segmentsDirectory)) {
218
+ const segmentFiles = fs.readdirSync(segmentsDirectory).filter((f) => f.endsWith(".yml"));
219
+
220
+ for (const segmentFile of segmentFiles) {
221
+ const segmentKey = path.basename(segmentFile, ".yml");
222
+ const segmentFilePath = path.join(segmentsDirectory, segmentFile);
223
+ const parsedSegment = parseYaml(fs.readFileSync(segmentFilePath, "utf8")) as Segment;
224
+
225
+ if (parsedSegment.archived === true) {
226
+ continue;
227
+ }
228
+
229
+ if (segmentKeysUsedByTag.has(segmentKey) === false) {
230
+ continue;
231
+ }
232
+
233
+ const extractedAttributeKeys = extractAttributeKeysFromConditions(
234
+ parsedSegment.conditions as Condition | Condition[],
235
+ );
236
+ extractedAttributeKeys.forEach((attributeKey) => attributeKeysUsedByTag.add(attributeKey));
237
+
238
+ const segment: Segment = {
239
+ key: segmentKey,
240
+ conditions:
241
+ typeof parsedSegment.conditions !== "string"
242
+ ? JSON.stringify(parsedSegment.conditions)
243
+ : parsedSegment.conditions,
244
+ };
245
+
246
+ segments.push(segment);
247
+ }
248
+ }
249
+
250
+ // attributes
251
+ const attributes: Attribute[] = [];
252
+ const attributesDirectory = projectConfig.attributesDirectoryPath;
253
+
254
+ if (fs.existsSync(attributesDirectory)) {
255
+ const attributeFiles = fs.readdirSync(attributesDirectory).filter((f) => f.endsWith(".yml"));
256
+
257
+ for (const attributeFile of attributeFiles) {
258
+ const attributeKey = path.basename(attributeFile, ".yml");
259
+ const attributeFilePath = path.join(attributesDirectory, attributeFile);
260
+ const parsedAttribute = parseYaml(fs.readFileSync(attributeFilePath, "utf8")) as Attribute;
261
+
262
+ if (parsedAttribute.archived === true) {
263
+ continue;
264
+ }
265
+
266
+ if (attributeKeysUsedByTag.has(attributeKey) === false && !parsedAttribute.capture) {
267
+ continue;
268
+ }
269
+
270
+ const attribute: Attribute = {
271
+ key: attributeKey,
272
+ type: parsedAttribute.type,
273
+ };
274
+
275
+ if (parsedAttribute.capture === true) {
276
+ attribute.capture = true;
277
+ }
278
+
279
+ attributes.push(attribute);
280
+ }
281
+ }
282
+
283
+ datafileContent.attributes = attributes;
284
+ datafileContent.segments = segments;
285
+ datafileContent.features = features;
286
+
287
+ // write
288
+ const outputEnvironmentDirPath = path.join(
289
+ projectConfig.outputDirectoryPath,
290
+ options.environment,
291
+ );
292
+ mkdirp.sync(outputEnvironmentDirPath);
293
+
294
+ const outputFilePath = getDatafilePath(projectConfig, options.environment, options.tag);
295
+ fs.writeFileSync(outputFilePath, JSON.stringify(datafileContent));
296
+
297
+ // write to state directory
298
+ if (!fs.existsSync(projectConfig.stateDirectoryPath)) {
299
+ mkdirp.sync(projectConfig.stateDirectoryPath);
300
+ }
301
+
302
+ const updatedExistingFeatures = datafileContent.features.reduce((acc, feature) => {
303
+ const item: ExistingFeature = {
304
+ variations: feature.variations.map((v: Variation) => {
305
+ return {
306
+ value: v.value,
307
+ weight: v.weight || 0,
308
+ };
309
+ }),
310
+ traffic: feature.traffic.map((t: Traffic) => {
311
+ return {
312
+ key: t.key,
313
+ percentage: t.percentage,
314
+ allocation: t.allocation.map((a: Allocation) => {
315
+ return {
316
+ variation: a.variation,
317
+ percentage: a.percentage,
318
+ };
319
+ }),
320
+ };
321
+ }),
322
+ };
323
+
324
+ acc[feature.key] = item;
325
+
326
+ return acc;
327
+ }, existingFeatures);
328
+
329
+ const updatedState: ExistingState = {
330
+ features: updatedExistingFeatures,
331
+ };
332
+
333
+ fs.writeFileSync(existingStateFilePath, JSON.stringify(updatedState));
334
+
335
+ console.log(` File generated: ${outputFilePath}`);
336
+
337
+ return datafileContent;
338
+ }
339
+
340
+ export function buildProject(rootDirectoryPath, projectConfig: ProjectConfig) {
341
+ const tags = projectConfig.tags;
342
+ const environments = projectConfig.environments;
343
+
344
+ const pkg = require(path.join(rootDirectoryPath, "package.json"));
345
+
346
+ for (const environment of environments) {
347
+ console.log(`\nBuilding datafiles for environment: ${environment}`);
348
+
349
+ for (const tag of tags) {
350
+ console.log(` => Tag: ${tag}`);
351
+ buildDatafile(projectConfig, {
352
+ schemaVersion: SCHEMA_VERSION,
353
+ revision: pkg.version,
354
+ environment: environment,
355
+ tag: tag,
356
+ });
357
+ }
358
+ }
359
+ }
package/src/config.ts ADDED
@@ -0,0 +1,62 @@
1
+ import * as path from "path";
2
+
3
+ import { BucketBy } from "@featurevisor/types";
4
+
5
+ export const FEATURES_DIRECTORY_NAME = "features";
6
+ export const SEGMENTS_DIRECTORY_NAME = "segments";
7
+ export const ATTRIBUTES_DIRECTORY_NAME = "attributes";
8
+ export const TESTS_DIRECTORY_NAME = "tests";
9
+ export const STATE_DIRECTORY_NAME = ".featurevisor";
10
+ export const OUTPUT_DIRECTORY_NAME = "dist";
11
+
12
+ export const CONFIG_MODULE_NAME = "featurevisor.config.js";
13
+ export const ROOT_DIR_PLACECOLDER = "<rootDir>";
14
+
15
+ export const DEFAULT_ENVIRONMENTS = ["staging", "production"];
16
+ export const DEFAULT_TAGS = ["all"];
17
+ export const DEFAULT_BUCKET_BY_ATTRIBUTE = "userId";
18
+
19
+ export const SCHEMA_VERSION = "1";
20
+
21
+ export interface ProjectConfig {
22
+ featuresDirectoryPath: string;
23
+ segmentsDirectoryPath: string;
24
+ attributesDirectoryPath: string;
25
+ testsDirectoryPath: string;
26
+ stateDirectoryPath: string;
27
+ outputDirectoryPath: string;
28
+ environments: string[];
29
+ tags: string[];
30
+ defaultBucketBy: BucketBy;
31
+ }
32
+
33
+ // rootDirectoryPath: path to the root directory of the project (without ending with a slash)
34
+ export function getProjectConfig(rootDirectoryPath: string): ProjectConfig {
35
+ const baseConfig = {
36
+ featuresDirectoryPath: path.join(rootDirectoryPath, FEATURES_DIRECTORY_NAME),
37
+ segmentsDirectoryPath: path.join(rootDirectoryPath, SEGMENTS_DIRECTORY_NAME),
38
+ attributesDirectoryPath: path.join(rootDirectoryPath, ATTRIBUTES_DIRECTORY_NAME),
39
+ testsDirectoryPath: path.join(rootDirectoryPath, TESTS_DIRECTORY_NAME),
40
+
41
+ stateDirectoryPath: path.join(rootDirectoryPath, STATE_DIRECTORY_NAME),
42
+ outputDirectoryPath: path.join(rootDirectoryPath, OUTPUT_DIRECTORY_NAME),
43
+
44
+ environments: DEFAULT_ENVIRONMENTS,
45
+ tags: DEFAULT_TAGS,
46
+ };
47
+
48
+ const configModulePath = path.join(rootDirectoryPath, CONFIG_MODULE_NAME);
49
+ const customConfig = require(configModulePath);
50
+
51
+ const finalConfig = {};
52
+
53
+ Object.keys(baseConfig).forEach((key) => {
54
+ finalConfig[key] = customConfig[key] || baseConfig[key];
55
+
56
+ if (key.endsWith("Path") && finalConfig[key].indexOf(ROOT_DIR_PLACECOLDER) !== -1) {
57
+ finalConfig[key] = finalConfig[key].replace(ROOT_DIR_PLACECOLDER, rootDirectoryPath);
58
+ }
59
+ });
60
+
61
+ return finalConfig as ProjectConfig;
62
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from "./config";
2
+ export * from "./linter";
3
+ export * from "./builder";
4
+ export * from "./tester";
5
+ export * from "./init";
package/src/init.ts ADDED
@@ -0,0 +1,44 @@
1
+ import axios from "axios";
2
+ import * as tar from "tar";
3
+
4
+ export const DEFAULT_EXAMPLE = "example-1";
5
+
6
+ export const EXAMPLES_ORG_NAME = "fahad19";
7
+ export const EXAMPLES_REPO_NAME = "featurevisor";
8
+ export const EXAMPLES_BRANCH_NAME = "main";
9
+
10
+ export const EXAMPLES_TAR_URL = `https://codeload.github.com/${EXAMPLES_ORG_NAME}/${EXAMPLES_REPO_NAME}/tar.gz/${EXAMPLES_BRANCH_NAME}`;
11
+
12
+ function getExamplePath(exampleName: string) {
13
+ return `${EXAMPLES_REPO_NAME}-${EXAMPLES_BRANCH_NAME}/examples/${exampleName}/`;
14
+ }
15
+
16
+ export function initProject(
17
+ directoryPath: string,
18
+ exampleName: string = DEFAULT_EXAMPLE,
19
+ ): Promise<boolean> {
20
+ return new Promise(function (resolve, reject) {
21
+ axios.get(EXAMPLES_TAR_URL, { responseType: "stream" }).then((response) => {
22
+ response.data
23
+ .pipe(
24
+ tar.x({
25
+ C: directoryPath,
26
+ filter: (path) => path.indexOf(getExamplePath(exampleName)) === 0,
27
+ strip: 3,
28
+ }),
29
+ )
30
+ .on("error", (e) => {
31
+ console.error(e);
32
+
33
+ resolve(false);
34
+ })
35
+ .on("finish", () => {
36
+ console.log(`Project scaffolded in ${directoryPath}`);
37
+ console.log(``);
38
+ console.log(`Please run "npm ci" in the directory above.`);
39
+
40
+ resolve(true);
41
+ });
42
+ });
43
+ });
44
+ }