@featurevisor/core 0.33.0 → 0.34.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/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [0.34.0](https://github.com/fahad19/featurevisor/compare/v0.33.1...v0.34.0) (2023-07-08)
7
+
8
+
9
+ ### Features
10
+
11
+ * code generation ([#98](https://github.com/fahad19/featurevisor/issues/98)) ([7474443](https://github.com/fahad19/featurevisor/commit/7474443d26fd526f471ef5258cbe39b42c0fad60))
12
+
13
+
14
+
15
+
16
+
6
17
  # [0.33.0](https://github.com/fahad19/featurevisor/compare/v0.32.1...v0.33.0) (2023-07-06)
7
18
 
8
19
  **Note:** Version bump only for package @featurevisor/core
@@ -1,6 +1,6 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <coverage generated="1688672127151" clover="3.2.0">
3
- <project timestamp="1688672127151" name="All files">
2
+ <coverage generated="1688829810488" clover="3.2.0">
3
+ <project timestamp="1688829810488" name="All files">
4
4
  <metrics statements="169" coveredstatements="161" conditionals="75" coveredconditionals="62" methods="28" coveredmethods="28" elements="272" coveredelements="251" complexity="0" loc="169" ncloc="169" packages="2" files="4" classes="4"/>
5
5
  <package name="lib">
6
6
  <metrics statements="88" coveredstatements="84" conditionals="42" coveredconditionals="35" methods="14" coveredmethods="14"/>
@@ -116,7 +116,7 @@
116
116
  <div class='footer quiet pad2 space-top1 center small'>
117
117
  Code coverage generated by
118
118
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
119
- at 2023-07-06T19:35:27.123Z
119
+ at 2023-07-08T15:23:30.468Z
120
120
  </div>
121
121
  <script src="prettify.js"></script>
122
122
  <script>
@@ -175,7 +175,7 @@ exports.getUpdatedAvailableRangesAfterFilling = getUpdatedAvailableRangesAfterFi
175
175
  <div class='footer quiet pad2 space-top1 center small'>
176
176
  Code coverage generated by
177
177
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
178
- at 2023-07-06T19:35:27.123Z
178
+ at 2023-07-08T15:23:30.468Z
179
179
  </div>
180
180
  <script src="../prettify.js"></script>
181
181
  <script>
@@ -116,7 +116,7 @@
116
116
  <div class='footer quiet pad2 space-top1 center small'>
117
117
  Code coverage generated by
118
118
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
119
- at 2023-07-06T19:35:27.123Z
119
+ at 2023-07-08T15:23:30.468Z
120
120
  </div>
121
121
  <script src="../prettify.js"></script>
122
122
  <script>
@@ -427,7 +427,7 @@ exports.getTraffic = getTraffic;
427
427
  <div class='footer quiet pad2 space-top1 center small'>
428
428
  Code coverage generated by
429
429
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
430
- at 2023-07-06T19:35:27.123Z
430
+ at 2023-07-08T15:23:30.468Z
431
431
  </div>
432
432
  <script src="../prettify.js"></script>
433
433
  <script>
@@ -193,7 +193,7 @@ export function getUpdatedAvailableRangesAfterFilling(
193
193
  <div class='footer quiet pad2 space-top1 center small'>
194
194
  Code coverage generated by
195
195
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
196
- at 2023-07-06T19:35:27.123Z
196
+ at 2023-07-08T15:23:30.468Z
197
197
  </div>
198
198
  <script src="../prettify.js"></script>
199
199
  <script>
@@ -116,7 +116,7 @@
116
116
  <div class='footer quiet pad2 space-top1 center small'>
117
117
  Code coverage generated by
118
118
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
119
- at 2023-07-06T19:35:27.123Z
119
+ at 2023-07-08T15:23:30.468Z
120
120
  </div>
121
121
  <script src="../prettify.js"></script>
122
122
  <script>
@@ -535,7 +535,7 @@ export function getTraffic(
535
535
  <div class='footer quiet pad2 space-top1 center small'>
536
536
  Code coverage generated by
537
537
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
538
- at 2023-07-06T19:35:27.123Z
538
+ at 2023-07-08T15:23:30.468Z
539
539
  </div>
540
540
  <script src="../prettify.js"></script>
541
541
  <script>
@@ -0,0 +1,7 @@
1
+ import { ProjectConfig } from "../config";
2
+ export declare const ALLOWED_LANGUAGES_FOR_CODE_GENERATION: string[];
3
+ export interface GenerateCodeCLIOptions {
4
+ language: string;
5
+ outDir: string;
6
+ }
7
+ export declare function generateCodeForProject(rootDirectoryPath: any, projectConfig: ProjectConfig, cliOptions: GenerateCodeCLIOptions): void;
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateCodeForProject = exports.ALLOWED_LANGUAGES_FOR_CODE_GENERATION = void 0;
4
+ var fs = require("fs");
5
+ var path = require("path");
6
+ var mkdirp = require("mkdirp");
7
+ var typescript_1 = require("./typescript");
8
+ exports.ALLOWED_LANGUAGES_FOR_CODE_GENERATION = ["typescript"];
9
+ function generateCodeForProject(rootDirectoryPath, projectConfig, cliOptions) {
10
+ if (!cliOptions.language) {
11
+ throw new Error("Option `--language` is required");
12
+ }
13
+ if (!cliOptions.outDir) {
14
+ throw new Error("Option `--out-dir` is required");
15
+ }
16
+ var absolutePath = path.resolve(rootDirectoryPath, cliOptions.outDir);
17
+ if (!fs.existsSync(absolutePath)) {
18
+ console.log("Creating output directory: ".concat(absolutePath));
19
+ mkdirp.sync(absolutePath);
20
+ }
21
+ else {
22
+ console.log("Output directory already exists at: ".concat(absolutePath));
23
+ }
24
+ if (!exports.ALLOWED_LANGUAGES_FOR_CODE_GENERATION.includes(cliOptions.language)) {
25
+ console.log("Only these languages are supported: ".concat(exports.ALLOWED_LANGUAGES_FOR_CODE_GENERATION.join(", ")));
26
+ throw new Error("Language ".concat(cliOptions.language, " is not supported for code generation"));
27
+ }
28
+ if (cliOptions.language === "typescript") {
29
+ return (0, typescript_1.generateTypeScriptCodeForProject)(rootDirectoryPath, projectConfig, absolutePath);
30
+ }
31
+ throw new Error("Language ".concat(cliOptions.language, " is not supported"));
32
+ }
33
+ exports.generateCodeForProject = generateCodeForProject;
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/generate-code/index.ts"],"names":[],"mappings":";;;AAAA,uBAAyB;AACzB,2BAA6B;AAE7B,+BAAiC;AAGjC,2CAAgE;AAEnD,QAAA,qCAAqC,GAAG,CAAC,YAAY,CAAC,CAAC;AAOpE,SAAgB,sBAAsB,CACpC,iBAAiB,EACjB,aAA4B,EAC5B,UAAkC;IAElC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE;QACxB,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;KACpD;IAED,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE;QACtB,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;KACnD;IAED,IAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAExE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE;QAChC,OAAO,CAAC,GAAG,CAAC,qCAA8B,YAAY,CAAE,CAAC,CAAC;QAC1D,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;KAC3B;SAAM;QACL,OAAO,CAAC,GAAG,CAAC,8CAAuC,YAAY,CAAE,CAAC,CAAC;KACpE;IAED,IAAI,CAAC,6CAAqC,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;QACxE,OAAO,CAAC,GAAG,CACT,8CAAuC,6CAAqC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAE,CAC1F,CAAC;QAEF,MAAM,IAAI,KAAK,CAAC,mBAAY,UAAU,CAAC,QAAQ,0CAAuC,CAAC,CAAC;KACzF;IAED,IAAI,UAAU,CAAC,QAAQ,KAAK,YAAY,EAAE;QACxC,OAAO,IAAA,6CAAgC,EAAC,iBAAiB,EAAE,aAAa,EAAE,YAAY,CAAC,CAAC;KACzF;IAED,MAAM,IAAI,KAAK,CAAC,mBAAY,UAAU,CAAC,QAAQ,sBAAmB,CAAC,CAAC;AACtE,CAAC;AAnCD,wDAmCC"}
@@ -0,0 +1,2 @@
1
+ import { ProjectConfig } from "../config";
2
+ export declare function generateTypeScriptCodeForProject(rootDirectoryPath: string, projectConfig: ProjectConfig, outputPath: string): void;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateTypeScriptCodeForProject = void 0;
4
+ var fs = require("fs");
5
+ var path = require("path");
6
+ var utils_1 = require("../utils");
7
+ function convertFeaturevisorTypeToTypeScriptType(featurevisorType) {
8
+ switch (featurevisorType) {
9
+ case "boolean":
10
+ return "boolean";
11
+ case "string":
12
+ return "string";
13
+ case "integer":
14
+ return "number";
15
+ case "double":
16
+ return "number";
17
+ case "date":
18
+ return "Date | string";
19
+ case "array":
20
+ return "string[]";
21
+ case "object":
22
+ return "any"; // @TODO: do a flat dictionary
23
+ case "json":
24
+ return "any";
25
+ default:
26
+ throw new Error("Unknown type: ".concat(featurevisorType));
27
+ }
28
+ }
29
+ function getPascalCase(str) {
30
+ // Remove special characters and split the string into an array of words
31
+ var words = str.replace(/[^a-zA-Z0-9]/g, " ").split(" ");
32
+ // Capitalize the first letter of each word and join them together
33
+ var pascalCased = words.map(function (word) { return word.charAt(0).toUpperCase() + word.slice(1); }).join("");
34
+ return pascalCased;
35
+ }
36
+ function getFeaturevisorTypeFromValue(value) {
37
+ if (typeof value === "boolean") {
38
+ return "boolean";
39
+ }
40
+ if (typeof value === "string") {
41
+ return "string";
42
+ }
43
+ if (typeof value === "number") {
44
+ if (Number.isInteger(value)) {
45
+ return "integer";
46
+ }
47
+ return "double";
48
+ }
49
+ if (value instanceof Date) {
50
+ return "date";
51
+ }
52
+ throw new Error("Could not detect Featurevisor type from value");
53
+ }
54
+ var instanceSnippet = "\nimport { FeaturevisorInstance } from \"@featurevisor/sdk\";\n\nlet _instance: FeaturevisorInstance;\n\nexport function setInstance(instance: FeaturevisorInstance) {\n _instance = instance;\n}\n\nexport function getInstance(): FeaturevisorInstance {\n return _instance as FeaturevisorInstance;\n}\n".trimStart();
55
+ function generateTypeScriptCodeForProject(rootDirectoryPath, projectConfig, outputPath) {
56
+ console.log("Generating TypeScript code...");
57
+ // instance
58
+ var instanceFilePath = path.join(outputPath, "instance.ts");
59
+ fs.writeFileSync(instanceFilePath, instanceSnippet);
60
+ console.log("Instance file written at: ".concat(instanceFilePath));
61
+ // attributes
62
+ var attributeFiles = (0, utils_1.getYAMLFiles)(projectConfig.attributesDirectoryPath);
63
+ var attributes = attributeFiles
64
+ .map(function (attributeFile) {
65
+ var parsedAttribute = (0, utils_1.parseYaml)(fs.readFileSync(attributeFile, "utf8"));
66
+ var attributeKey = path.basename(attributeFile, ".yml");
67
+ return {
68
+ archived: parsedAttribute.archived,
69
+ key: attributeKey,
70
+ type: parsedAttribute.type,
71
+ typescriptType: convertFeaturevisorTypeToTypeScriptType(parsedAttribute.type),
72
+ };
73
+ })
74
+ .filter(function (attribute) {
75
+ if (typeof attribute.archived === "undefined") {
76
+ return true;
77
+ }
78
+ return !attribute.archived;
79
+ });
80
+ var attributeProperties = attributes
81
+ .map(function (attribute) {
82
+ return " ".concat(attribute.key, "?: ").concat(attribute.typescriptType, ";");
83
+ })
84
+ .join("\n");
85
+ var attributesContent = "\nimport { AttributeKey, AttributeValue } from \"@featurevisor/types\";\n\nexport interface Attributes {\n".concat(attributeProperties, "\n [key: AttributeKey]: AttributeValue;\n}\n").trimStart();
86
+ var attributesTypeFilePath = path.join(outputPath, "Attributes.ts");
87
+ fs.writeFileSync(attributesTypeFilePath, attributesContent);
88
+ console.log("Attributes type file written at: ".concat(attributesTypeFilePath));
89
+ // features
90
+ var featureNamespaces = [];
91
+ var featureFiles = (0, utils_1.getYAMLFiles)(projectConfig.featuresDirectoryPath);
92
+ for (var _i = 0, featureFiles_1 = featureFiles; _i < featureFiles_1.length; _i++) {
93
+ var featureFile = featureFiles_1[_i];
94
+ var featureKey = path.basename(featureFile, ".yml");
95
+ var parsedFeature = (0, utils_1.parseYaml)(fs.readFileSync(featureFile, "utf8"));
96
+ var variationType = getFeaturevisorTypeFromValue(parsedFeature.defaultVariation);
97
+ var variationTypeScriptType = convertFeaturevisorTypeToTypeScriptType(variationType);
98
+ if (typeof parsedFeature.archived !== "undefined" && parsedFeature.archived) {
99
+ continue;
100
+ }
101
+ var namespaceValue = getPascalCase(featureKey) + "Feature";
102
+ featureNamespaces.push(namespaceValue);
103
+ var variableMethods = "";
104
+ if (parsedFeature.variablesSchema) {
105
+ for (var _a = 0, _b = parsedFeature.variablesSchema; _a < _b.length; _a++) {
106
+ var variableSchema = _b[_a];
107
+ var variableKey = variableSchema.key;
108
+ var variableType = variableSchema.type;
109
+ var internalMethodName = "getVariable".concat(variableType === "json" ? "JSON" : getPascalCase(variableType));
110
+ if (variableType === "json" || variableType === "object") {
111
+ variableMethods += "\n\n export function get".concat(getPascalCase(variableKey), "<T>(attributes: Attributes = {}) {\n return getInstance().").concat(internalMethodName, "<T>(key, \"").concat(variableKey, "\", attributes);\n }");
112
+ }
113
+ else {
114
+ variableMethods += "\n\n export function get".concat(getPascalCase(variableKey), "(attributes: Attributes = {}) {\n return getInstance().").concat(internalMethodName, "(key, \"").concat(variableKey, "\", attributes);\n }");
115
+ }
116
+ }
117
+ }
118
+ var featureContent = "\nimport { Attributes } from \"./Attributes\";\nimport { getInstance } from \"./instance\";\n\nexport namespace ".concat(namespaceValue, " {\n export const key = \"").concat(featureKey, "\";\n\n export function getVariation(attributes: Attributes = {}) {\n return getInstance().getVariation").concat(getPascalCase(variationType), "(key, attributes);\n }").concat(variableMethods, "\n}\n").trimStart();
119
+ var featureNamespaceFilePath = path.join(outputPath, "".concat(namespaceValue, ".ts"));
120
+ fs.writeFileSync(featureNamespaceFilePath, featureContent);
121
+ console.log("Feature ".concat(featureKey, " file written at: ").concat(featureNamespaceFilePath));
122
+ }
123
+ // index
124
+ var indexContent = ["export * from \"./Attributes\";", "export * from \"./instance\";"]
125
+ .concat(featureNamespaces.map(function (featureNamespace) {
126
+ return "export * from \"./".concat(featureNamespace, "\";");
127
+ }))
128
+ .join("\n") + "\n";
129
+ var indexFilePath = path.join(outputPath, "index.ts");
130
+ fs.writeFileSync(indexFilePath, indexContent);
131
+ console.log("Index file written at: ".concat(indexFilePath));
132
+ }
133
+ exports.generateTypeScriptCodeForProject = generateTypeScriptCodeForProject;
134
+ //# sourceMappingURL=typescript.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typescript.js","sourceRoot":"","sources":["../../src/generate-code/typescript.ts"],"names":[],"mappings":";;;AAAA,uBAAyB;AACzB,2BAA6B;AAK7B,kCAAmD;AAEnD,SAAS,uCAAuC,CAAC,gBAAwB;IACvE,QAAQ,gBAAgB,EAAE;QACxB,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAC;QAClB,KAAK,SAAS;YACZ,OAAO,QAAQ,CAAC;QAClB,KAAK,QAAQ;YACX,OAAO,QAAQ,CAAC;QAClB,KAAK,MAAM;YACT,OAAO,eAAe,CAAC;QACzB,KAAK,OAAO;YACV,OAAO,UAAU,CAAC;QACpB,KAAK,QAAQ;YACX,OAAO,KAAK,CAAC,CAAC,8BAA8B;QAC9C,KAAK,MAAM;YACT,OAAO,KAAK,CAAC;QACf;YACE,MAAM,IAAI,KAAK,CAAC,wBAAiB,gBAAgB,CAAE,CAAC,CAAC;KACxD;AACH,CAAC;AAED,SAAS,aAAa,CAAC,GAAG;IACxB,wEAAwE;IACxE,IAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAE3D,kEAAkE;IAClE,IAAM,WAAW,GAAG,KAAK,CAAC,GAAG,CAAC,UAAC,IAAI,IAAK,OAAA,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAA5C,CAA4C,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAE/F,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,SAAS,4BAA4B,CAAC,KAAK;IACzC,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE;QAC9B,OAAO,SAAS,CAAC;KAClB;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QAC7B,OAAO,QAAQ,CAAC;KACjB;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QAC7B,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE;YAC3B,OAAO,SAAS,CAAC;SAClB;QAED,OAAO,QAAQ,CAAC;KACjB;IAED,IAAI,KAAK,YAAY,IAAI,EAAE;QACzB,OAAO,MAAM,CAAC;KACf;IAED,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;AACnE,CAAC;AAED,IAAM,eAAe,GAAG,+SAYvB,CAAC,SAAS,EAAE,CAAC;AAEd,SAAgB,gCAAgC,CAC9C,iBAAyB,EACzB,aAA4B,EAC5B,UAAkB;IAElB,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;IAE7C,WAAW;IACX,IAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;IAC9D,EAAE,CAAC,aAAa,CAAC,gBAAgB,EAAE,eAAe,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,oCAA6B,gBAAgB,CAAE,CAAC,CAAC;IAE7D,aAAa;IACb,IAAM,cAAc,GAAG,IAAA,oBAAY,EAAC,aAAa,CAAC,uBAAuB,CAAC,CAAC;IAC3E,IAAM,UAAU,GAAG,cAAc;SAC9B,GAAG,CAAC,UAAC,aAAa;QACjB,IAAM,eAAe,GAAG,IAAA,iBAAS,EAAC,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAc,CAAC;QAEvF,IAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QAE1D,OAAO;YACL,QAAQ,EAAE,eAAe,CAAC,QAAQ;YAClC,GAAG,EAAE,YAAY;YACjB,IAAI,EAAE,eAAe,CAAC,IAAI;YAC1B,cAAc,EAAE,uCAAuC,CAAC,eAAe,CAAC,IAAI,CAAC;SAC9E,CAAC;IACJ,CAAC,CAAC;SACD,MAAM,CAAC,UAAC,SAAS;QAChB,IAAI,OAAO,SAAS,CAAC,QAAQ,KAAK,WAAW,EAAE;YAC7C,OAAO,IAAI,CAAC;SACb;QAED,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEL,IAAM,mBAAmB,GAAG,UAAU;SACnC,GAAG,CAAC,UAAC,SAAS;QACb,OAAO,YAAK,SAAS,CAAC,GAAG,gBAAM,SAAS,CAAC,cAAc,MAAG,CAAC;IAC7D,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,IAAM,iBAAiB,GAAG,oHAI1B,mBAAmB,kDAGpB,CAAC,SAAS,EAAE,CAAC;IAEZ,IAAM,sBAAsB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;IACtE,EAAE,CAAC,aAAa,CAAC,sBAAsB,EAAE,iBAAiB,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,2CAAoC,sBAAsB,CAAE,CAAC,CAAC;IAE1E,WAAW;IACX,IAAM,iBAAiB,GAAa,EAAE,CAAC;IACvC,IAAM,YAAY,GAAG,IAAA,oBAAY,EAAC,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACvE,KAA0B,UAAY,EAAZ,6BAAY,EAAZ,0BAAY,EAAZ,IAAY,EAAE;QAAnC,IAAM,WAAW,qBAAA;QACpB,IAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACtD,IAAM,aAAa,GAAG,IAAA,iBAAS,EAAC,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAkB,CAAC;QAEvF,IAAM,aAAa,GAAG,4BAA4B,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;QACnF,IAAM,uBAAuB,GAAG,uCAAuC,CAAC,aAAa,CAAC,CAAC;QAEvF,IAAI,OAAO,aAAa,CAAC,QAAQ,KAAK,WAAW,IAAI,aAAa,CAAC,QAAQ,EAAE;YAC3E,SAAS;SACV;QAED,IAAM,cAAc,GAAG,aAAa,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC;QAC7D,iBAAiB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAEvC,IAAI,eAAe,GAAG,EAAE,CAAC;QAEzB,IAAI,aAAa,CAAC,eAAe,EAAE;YACjC,KAA6B,UAA6B,EAA7B,KAAA,aAAa,CAAC,eAAe,EAA7B,cAA6B,EAA7B,IAA6B,EAAE;gBAAvD,IAAM,cAAc,SAAA;gBACvB,IAAM,WAAW,GAAG,cAAc,CAAC,GAAG,CAAC;gBACvC,IAAM,YAAY,GAAG,cAAc,CAAC,IAAI,CAAC;gBAEzC,IAAM,kBAAkB,GAAG,qBACzB,YAAY,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,YAAY,CAAC,CAC9D,CAAC;gBAEH,IAAI,YAAY,KAAK,MAAM,IAAI,YAAY,KAAK,QAAQ,EAAE;oBACxD,eAAe,IAAI,mCAEN,aAAa,CAAC,WAAW,CAAC,0EACtB,kBAAkB,wBAAa,WAAW,0BACjE,CAAC;iBACI;qBAAM;oBACL,eAAe,IAAI,mCAEN,aAAa,CAAC,WAAW,CAAC,uEACtB,kBAAkB,qBAAU,WAAW,0BAC9D,CAAC;iBACI;aACF;SACF;QAED,IAAM,cAAc,GAAG,0HAIR,cAAc,wCACT,UAAU,wHAGK,aAAa,CAAC,aAAa,CAAC,oCAC9D,eAAe,UAEnB,CAAC,SAAS,EAAE,CAAC;QAEV,IAAM,wBAAwB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAG,cAAc,QAAK,CAAC,CAAC;QAC/E,EAAE,CAAC,aAAa,CAAC,wBAAwB,EAAE,cAAc,CAAC,CAAC;QAC3D,OAAO,CAAC,GAAG,CAAC,kBAAW,UAAU,+BAAqB,wBAAwB,CAAE,CAAC,CAAC;KACnF;IAED,QAAQ;IACR,IAAM,YAAY,GAChB,CAAC,iCAA+B,EAAE,+BAA6B,CAAC;SAC7D,MAAM,CACL,iBAAiB,CAAC,GAAG,CAAC,UAAC,gBAAgB;QACrC,OAAO,4BAAoB,gBAAgB,QAAI,CAAC;IAClD,CAAC,CAAC,CACH;SACA,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACvB,IAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IACxD,EAAE,CAAC,aAAa,CAAC,aAAa,EAAE,YAAY,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,iCAA0B,aAAa,CAAE,CAAC,CAAC;AACzD,CAAC;AA/HD,4EA+HC"}
package/lib/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export * from "./builder";
4
4
  export * from "./tester";
5
5
  export * from "./init";
6
6
  export * from "./site";
7
+ export * from "./generate-code";
package/lib/index.js CHANGED
@@ -20,4 +20,5 @@ __exportStar(require("./builder"), exports);
20
20
  __exportStar(require("./tester"), exports);
21
21
  __exportStar(require("./init"), exports);
22
22
  __exportStar(require("./site"), exports);
23
+ __exportStar(require("./generate-code"), exports);
23
24
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,2CAAyB;AACzB,4CAA0B;AAC1B,2CAAyB;AACzB,yCAAuB;AACvB,yCAAuB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,2CAAyB;AACzB,4CAA0B;AAC1B,2CAAyB;AACzB,yCAAuB;AACvB,yCAAuB;AACvB,kDAAgC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@featurevisor/core",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "Core package of Featurevisor for Node.js usage",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -41,9 +41,9 @@
41
41
  },
42
42
  "license": "MIT",
43
43
  "dependencies": {
44
- "@featurevisor/sdk": "^0.33.0",
45
- "@featurevisor/site": "^0.33.0",
46
- "@featurevisor/types": "^0.33.0",
44
+ "@featurevisor/sdk": "^0.34.0",
45
+ "@featurevisor/site": "^0.34.0",
46
+ "@featurevisor/types": "^0.34.0",
47
47
  "axios": "^1.3.4",
48
48
  "joi": "^17.8.3",
49
49
  "js-yaml": "^4.1.0",
@@ -54,5 +54,5 @@
54
54
  "@types/js-yaml": "^4.0.5",
55
55
  "@types/tar": "^6.1.4"
56
56
  },
57
- "gitHead": "a03246983110e5e83432b0a90849aece2fae1512"
57
+ "gitHead": "202f481ab6230534d224192bd7d170779ac399f8"
58
58
  }
@@ -0,0 +1,51 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+
4
+ import * as mkdirp from "mkdirp";
5
+
6
+ import { ProjectConfig } from "../config";
7
+ import { generateTypeScriptCodeForProject } from "./typescript";
8
+
9
+ export const ALLOWED_LANGUAGES_FOR_CODE_GENERATION = ["typescript"];
10
+
11
+ export interface GenerateCodeCLIOptions {
12
+ language: string;
13
+ outDir: string;
14
+ }
15
+
16
+ export function generateCodeForProject(
17
+ rootDirectoryPath,
18
+ projectConfig: ProjectConfig,
19
+ cliOptions: GenerateCodeCLIOptions,
20
+ ) {
21
+ if (!cliOptions.language) {
22
+ throw new Error("Option `--language` is required");
23
+ }
24
+
25
+ if (!cliOptions.outDir) {
26
+ throw new Error("Option `--out-dir` is required");
27
+ }
28
+
29
+ const absolutePath = path.resolve(rootDirectoryPath, cliOptions.outDir);
30
+
31
+ if (!fs.existsSync(absolutePath)) {
32
+ console.log(`Creating output directory: ${absolutePath}`);
33
+ mkdirp.sync(absolutePath);
34
+ } else {
35
+ console.log(`Output directory already exists at: ${absolutePath}`);
36
+ }
37
+
38
+ if (!ALLOWED_LANGUAGES_FOR_CODE_GENERATION.includes(cliOptions.language)) {
39
+ console.log(
40
+ `Only these languages are supported: ${ALLOWED_LANGUAGES_FOR_CODE_GENERATION.join(", ")}`,
41
+ );
42
+
43
+ throw new Error(`Language ${cliOptions.language} is not supported for code generation`);
44
+ }
45
+
46
+ if (cliOptions.language === "typescript") {
47
+ return generateTypeScriptCodeForProject(rootDirectoryPath, projectConfig, absolutePath);
48
+ }
49
+
50
+ throw new Error(`Language ${cliOptions.language} is not supported`);
51
+ }
@@ -0,0 +1,207 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+
4
+ import { Attribute, ParsedFeature } from "@featurevisor/types";
5
+
6
+ import { ProjectConfig } from "../config";
7
+ import { getYAMLFiles, parseYaml } from "../utils";
8
+
9
+ function convertFeaturevisorTypeToTypeScriptType(featurevisorType: string) {
10
+ switch (featurevisorType) {
11
+ case "boolean":
12
+ return "boolean";
13
+ case "string":
14
+ return "string";
15
+ case "integer":
16
+ return "number";
17
+ case "double":
18
+ return "number";
19
+ case "date":
20
+ return "Date | string";
21
+ case "array":
22
+ return "string[]";
23
+ case "object":
24
+ return "any"; // @TODO: do a flat dictionary
25
+ case "json":
26
+ return "any";
27
+ default:
28
+ throw new Error(`Unknown type: ${featurevisorType}`);
29
+ }
30
+ }
31
+
32
+ function getPascalCase(str) {
33
+ // Remove special characters and split the string into an array of words
34
+ const words = str.replace(/[^a-zA-Z0-9]/g, " ").split(" ");
35
+
36
+ // Capitalize the first letter of each word and join them together
37
+ const pascalCased = words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
38
+
39
+ return pascalCased;
40
+ }
41
+
42
+ function getFeaturevisorTypeFromValue(value) {
43
+ if (typeof value === "boolean") {
44
+ return "boolean";
45
+ }
46
+
47
+ if (typeof value === "string") {
48
+ return "string";
49
+ }
50
+
51
+ if (typeof value === "number") {
52
+ if (Number.isInteger(value)) {
53
+ return "integer";
54
+ }
55
+
56
+ return "double";
57
+ }
58
+
59
+ if (value instanceof Date) {
60
+ return "date";
61
+ }
62
+
63
+ throw new Error("Could not detect Featurevisor type from value");
64
+ }
65
+
66
+ const instanceSnippet = `
67
+ import { FeaturevisorInstance } from "@featurevisor/sdk";
68
+
69
+ let _instance: FeaturevisorInstance;
70
+
71
+ export function setInstance(instance: FeaturevisorInstance) {
72
+ _instance = instance;
73
+ }
74
+
75
+ export function getInstance(): FeaturevisorInstance {
76
+ return _instance as FeaturevisorInstance;
77
+ }
78
+ `.trimStart();
79
+
80
+ export function generateTypeScriptCodeForProject(
81
+ rootDirectoryPath: string,
82
+ projectConfig: ProjectConfig,
83
+ outputPath: string,
84
+ ) {
85
+ console.log("Generating TypeScript code...");
86
+
87
+ // instance
88
+ const instanceFilePath = path.join(outputPath, "instance.ts");
89
+ fs.writeFileSync(instanceFilePath, instanceSnippet);
90
+ console.log(`Instance file written at: ${instanceFilePath}`);
91
+
92
+ // attributes
93
+ const attributeFiles = getYAMLFiles(projectConfig.attributesDirectoryPath);
94
+ const attributes = attributeFiles
95
+ .map((attributeFile) => {
96
+ const parsedAttribute = parseYaml(fs.readFileSync(attributeFile, "utf8")) as Attribute;
97
+
98
+ const attributeKey = path.basename(attributeFile, ".yml");
99
+
100
+ return {
101
+ archived: parsedAttribute.archived,
102
+ key: attributeKey,
103
+ type: parsedAttribute.type,
104
+ typescriptType: convertFeaturevisorTypeToTypeScriptType(parsedAttribute.type),
105
+ };
106
+ })
107
+ .filter((attribute) => {
108
+ if (typeof attribute.archived === "undefined") {
109
+ return true;
110
+ }
111
+
112
+ return !attribute.archived;
113
+ });
114
+
115
+ const attributeProperties = attributes
116
+ .map((attribute) => {
117
+ return ` ${attribute.key}?: ${attribute.typescriptType};`;
118
+ })
119
+ .join("\n");
120
+ const attributesContent = `
121
+ import { AttributeKey, AttributeValue } from "@featurevisor/types";
122
+
123
+ export interface Attributes {
124
+ ${attributeProperties}
125
+ [key: AttributeKey]: AttributeValue;
126
+ }
127
+ `.trimStart();
128
+
129
+ const attributesTypeFilePath = path.join(outputPath, "Attributes.ts");
130
+ fs.writeFileSync(attributesTypeFilePath, attributesContent);
131
+ console.log(`Attributes type file written at: ${attributesTypeFilePath}`);
132
+
133
+ // features
134
+ const featureNamespaces: string[] = [];
135
+ const featureFiles = getYAMLFiles(projectConfig.featuresDirectoryPath);
136
+ for (const featureFile of featureFiles) {
137
+ const featureKey = path.basename(featureFile, ".yml");
138
+ const parsedFeature = parseYaml(fs.readFileSync(featureFile, "utf8")) as ParsedFeature;
139
+
140
+ const variationType = getFeaturevisorTypeFromValue(parsedFeature.defaultVariation);
141
+ const variationTypeScriptType = convertFeaturevisorTypeToTypeScriptType(variationType);
142
+
143
+ if (typeof parsedFeature.archived !== "undefined" && parsedFeature.archived) {
144
+ continue;
145
+ }
146
+
147
+ const namespaceValue = getPascalCase(featureKey) + "Feature";
148
+ featureNamespaces.push(namespaceValue);
149
+
150
+ let variableMethods = "";
151
+
152
+ if (parsedFeature.variablesSchema) {
153
+ for (const variableSchema of parsedFeature.variablesSchema) {
154
+ const variableKey = variableSchema.key;
155
+ const variableType = variableSchema.type;
156
+
157
+ const internalMethodName = `getVariable${
158
+ variableType === "json" ? "JSON" : getPascalCase(variableType)
159
+ }`;
160
+
161
+ if (variableType === "json" || variableType === "object") {
162
+ variableMethods += `
163
+
164
+ export function get${getPascalCase(variableKey)}<T>(attributes: Attributes = {}) {
165
+ return getInstance().${internalMethodName}<T>(key, "${variableKey}", attributes);
166
+ }`;
167
+ } else {
168
+ variableMethods += `
169
+
170
+ export function get${getPascalCase(variableKey)}(attributes: Attributes = {}) {
171
+ return getInstance().${internalMethodName}(key, "${variableKey}", attributes);
172
+ }`;
173
+ }
174
+ }
175
+ }
176
+
177
+ const featureContent = `
178
+ import { Attributes } from "./Attributes";
179
+ import { getInstance } from "./instance";
180
+
181
+ export namespace ${namespaceValue} {
182
+ export const key = "${featureKey}";
183
+
184
+ export function getVariation(attributes: Attributes = {}) {
185
+ return getInstance().getVariation${getPascalCase(variationType)}(key, attributes);
186
+ }${variableMethods}
187
+ }
188
+ `.trimStart();
189
+
190
+ const featureNamespaceFilePath = path.join(outputPath, `${namespaceValue}.ts`);
191
+ fs.writeFileSync(featureNamespaceFilePath, featureContent);
192
+ console.log(`Feature ${featureKey} file written at: ${featureNamespaceFilePath}`);
193
+ }
194
+
195
+ // index
196
+ const indexContent =
197
+ [`export * from "./Attributes";`, `export * from "./instance";`]
198
+ .concat(
199
+ featureNamespaces.map((featureNamespace) => {
200
+ return `export * from "./${featureNamespace}";`;
201
+ }),
202
+ )
203
+ .join("\n") + "\n";
204
+ const indexFilePath = path.join(outputPath, "index.ts");
205
+ fs.writeFileSync(indexFilePath, indexContent);
206
+ console.log(`Index file written at: ${indexFilePath}`);
207
+ }
package/src/index.ts CHANGED
@@ -4,3 +4,4 @@ export * from "./builder";
4
4
  export * from "./tester";
5
5
  export * from "./init";
6
6
  export * from "./site";
7
+ export * from "./generate-code";