@featurevisor/core 2.9.0 → 2.11.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 +22 -0
- package/coverage/clover.xml +2 -2
- package/coverage/lcov-report/builder/allocator.ts.html +1 -1
- package/coverage/lcov-report/builder/buildScopedConditions.ts.html +1 -1
- package/coverage/lcov-report/builder/buildScopedDatafile.ts.html +1 -1
- package/coverage/lcov-report/builder/buildScopedSegments.ts.html +1 -1
- package/coverage/lcov-report/builder/index.html +1 -1
- package/coverage/lcov-report/builder/revision.ts.html +1 -1
- package/coverage/lcov-report/builder/traffic.ts.html +1 -1
- package/coverage/lcov-report/index.html +1 -1
- package/coverage/lcov-report/list/index.html +1 -1
- package/coverage/lcov-report/list/matrix.ts.html +1 -1
- package/coverage/lcov-report/parsers/index.html +1 -1
- package/coverage/lcov-report/parsers/json.ts.html +1 -1
- package/coverage/lcov-report/parsers/yml.ts.html +1 -1
- package/coverage/lcov-report/tester/helpers.ts.html +1 -1
- package/coverage/lcov-report/tester/index.html +1 -1
- package/lib/generate-code/typescript.js +150 -16
- package/lib/generate-code/typescript.js.map +1 -1
- package/lib/linter/featureSchema.d.ts +142 -101
- package/lib/linter/featureSchema.js +269 -81
- package/lib/linter/featureSchema.js.map +1 -1
- package/lib/linter/propertySchema.d.ts +5 -0
- package/lib/linter/propertySchema.js +43 -0
- package/lib/linter/propertySchema.js.map +1 -0
- package/package.json +5 -5
- package/src/generate-code/typescript.ts +168 -18
- package/src/linter/featureSchema.ts +358 -96
- package/src/linter/propertySchema.ts +47 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@featurevisor/core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.11.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",
|
|
@@ -40,14 +40,14 @@
|
|
|
40
40
|
},
|
|
41
41
|
"license": "MIT",
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@featurevisor/sdk": "2.
|
|
44
|
-
"@featurevisor/site": "2.
|
|
45
|
-
"@featurevisor/types": "2.
|
|
43
|
+
"@featurevisor/sdk": "2.10.0",
|
|
44
|
+
"@featurevisor/site": "2.10.0",
|
|
45
|
+
"@featurevisor/types": "2.10.0",
|
|
46
46
|
"axios": "^1.3.4",
|
|
47
47
|
"tar": "^7.5.7",
|
|
48
48
|
"yaml": "^1.10.2",
|
|
49
49
|
"yargs": "^17.7.2",
|
|
50
50
|
"zod": "^3.22.4"
|
|
51
51
|
},
|
|
52
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "28d3914f1ab4fd280ddb7c9f71ec64bf3783cc38"
|
|
53
53
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
|
|
4
|
-
import type { Attribute } from "@featurevisor/types";
|
|
4
|
+
import type { Attribute, PropertySchema, VariableSchema } from "@featurevisor/types";
|
|
5
5
|
import { Dependencies } from "../dependencies";
|
|
6
6
|
|
|
7
|
-
function convertFeaturevisorTypeToTypeScriptType(featurevisorType: string) {
|
|
7
|
+
function convertFeaturevisorTypeToTypeScriptType(featurevisorType: string): string {
|
|
8
8
|
switch (featurevisorType) {
|
|
9
9
|
case "boolean":
|
|
10
10
|
return "boolean";
|
|
@@ -19,14 +19,141 @@ function convertFeaturevisorTypeToTypeScriptType(featurevisorType: string) {
|
|
|
19
19
|
case "array":
|
|
20
20
|
return "string[]";
|
|
21
21
|
case "object":
|
|
22
|
-
return "
|
|
22
|
+
return "Record<string, unknown>";
|
|
23
23
|
case "json":
|
|
24
|
-
return "
|
|
24
|
+
return "unknown";
|
|
25
25
|
default:
|
|
26
26
|
throw new Error(`Unknown type: ${featurevisorType}`);
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Converts a PropertySchema (items or properties entry) to a TypeScript type string.
|
|
32
|
+
* Handles nested object/array with structured items and properties.
|
|
33
|
+
*/
|
|
34
|
+
function propertySchemaToTypeScriptType(schema: PropertySchema): string {
|
|
35
|
+
const type = schema.type;
|
|
36
|
+
if (!type) {
|
|
37
|
+
return "unknown";
|
|
38
|
+
}
|
|
39
|
+
switch (type) {
|
|
40
|
+
case "boolean":
|
|
41
|
+
return "boolean";
|
|
42
|
+
case "string":
|
|
43
|
+
return "string";
|
|
44
|
+
case "integer":
|
|
45
|
+
case "double":
|
|
46
|
+
return "number";
|
|
47
|
+
case "array":
|
|
48
|
+
if (schema.items) {
|
|
49
|
+
return `(${propertySchemaToTypeScriptType(schema.items)})[]`;
|
|
50
|
+
}
|
|
51
|
+
return "string[]";
|
|
52
|
+
case "object": {
|
|
53
|
+
const props = schema.properties;
|
|
54
|
+
if (props && typeof props === "object" && Object.keys(props).length > 0) {
|
|
55
|
+
const requiredSet = new Set(schema.required || []);
|
|
56
|
+
const entries = Object.entries(props)
|
|
57
|
+
.map(([k, v]) => {
|
|
58
|
+
const propType = propertySchemaToTypeScriptType(v);
|
|
59
|
+
const optional = !requiredSet.has(k);
|
|
60
|
+
return optional ? `${k}?: ${propType}` : `${k}: ${propType}`;
|
|
61
|
+
})
|
|
62
|
+
.join("; ");
|
|
63
|
+
return `{ ${entries} }`;
|
|
64
|
+
}
|
|
65
|
+
return "Record<string, unknown>";
|
|
66
|
+
}
|
|
67
|
+
default:
|
|
68
|
+
return "unknown";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Generates TypeScript type/interface declarations and metadata for a variable.
|
|
74
|
+
* Returns declarations to emit (interface or type alias) plus the type name and generic to use in the getter.
|
|
75
|
+
*/
|
|
76
|
+
function generateVariableTypeDeclarations(
|
|
77
|
+
variableKey: string,
|
|
78
|
+
variableSchema: VariableSchema,
|
|
79
|
+
): { declarations: string[]; returnTypeName: string; genericArg: string } {
|
|
80
|
+
const typeName = getPascalCase(variableKey) + "Variable";
|
|
81
|
+
const itemTypeName = getPascalCase(variableKey) + "VariableItem";
|
|
82
|
+
const type = variableSchema.type;
|
|
83
|
+
const declarations: string[] = [];
|
|
84
|
+
|
|
85
|
+
if (type === "json") {
|
|
86
|
+
return { declarations: [], returnTypeName: "T", genericArg: "T" };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (type === "object") {
|
|
90
|
+
const props = variableSchema.properties;
|
|
91
|
+
if (props && typeof props === "object" && Object.keys(props).length > 0) {
|
|
92
|
+
const requiredSet = new Set((variableSchema.required as string[]) || []);
|
|
93
|
+
const entries = Object.entries(props)
|
|
94
|
+
.map(([k, v]) => {
|
|
95
|
+
const propType = propertySchemaToTypeScriptType(v as PropertySchema);
|
|
96
|
+
const optional = !requiredSet.has(k);
|
|
97
|
+
return optional
|
|
98
|
+
? `${INDENT_NS_BODY}${k}?: ${propType};`
|
|
99
|
+
: `${INDENT_NS_BODY}${k}: ${propType};`;
|
|
100
|
+
})
|
|
101
|
+
.join("\n");
|
|
102
|
+
declarations.push(`${INDENT_NS}export interface ${typeName} {\n${entries}\n${INDENT_NS}}`);
|
|
103
|
+
return { declarations, returnTypeName: typeName, genericArg: typeName };
|
|
104
|
+
}
|
|
105
|
+
declarations.push(`${INDENT_NS}export type ${typeName} = Record<string, unknown>;`);
|
|
106
|
+
return { declarations, returnTypeName: typeName, genericArg: typeName };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (type === "array") {
|
|
110
|
+
if (variableSchema.items) {
|
|
111
|
+
const items = variableSchema.items as PropertySchema;
|
|
112
|
+
if (items.type === "object" && items.properties && Object.keys(items.properties).length > 0) {
|
|
113
|
+
const requiredSet = new Set(items.required || []);
|
|
114
|
+
const entries = Object.entries(items.properties)
|
|
115
|
+
.map(([k, v]) => {
|
|
116
|
+
const propType = propertySchemaToTypeScriptType(v);
|
|
117
|
+
const optional = !requiredSet.has(k);
|
|
118
|
+
return optional
|
|
119
|
+
? `${INDENT_NS_BODY}${k}?: ${propType};`
|
|
120
|
+
: `${INDENT_NS_BODY}${k}: ${propType};`;
|
|
121
|
+
})
|
|
122
|
+
.join("\n");
|
|
123
|
+
declarations.push(
|
|
124
|
+
`${INDENT_NS}export interface ${itemTypeName} {\n${entries}\n${INDENT_NS}}`,
|
|
125
|
+
);
|
|
126
|
+
// getVariableArray<T> returns T[] | null, so generic is item type
|
|
127
|
+
return {
|
|
128
|
+
declarations,
|
|
129
|
+
returnTypeName: `${itemTypeName}[]`,
|
|
130
|
+
genericArg: itemTypeName,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// array of primitive (e.g. string): emit item type only, use item[] in methods
|
|
134
|
+
const itemType = propertySchemaToTypeScriptType(items);
|
|
135
|
+
declarations.push(`${INDENT_NS}export type ${itemTypeName} = ${itemType};`);
|
|
136
|
+
return {
|
|
137
|
+
declarations,
|
|
138
|
+
returnTypeName: `${itemTypeName}[]`,
|
|
139
|
+
genericArg: itemTypeName,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// array without items (default string[]): emit item type only
|
|
143
|
+
declarations.push(`${INDENT_NS}export type ${itemTypeName} = string;`);
|
|
144
|
+
return {
|
|
145
|
+
declarations,
|
|
146
|
+
returnTypeName: `${itemTypeName}[]`,
|
|
147
|
+
genericArg: itemTypeName,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// primitive: boolean, string, integer, double
|
|
152
|
+
const primitiveType = convertFeaturevisorTypeToTypeScriptType(type);
|
|
153
|
+
declarations.push(`${INDENT_NS}export type ${typeName} = ${primitiveType};`);
|
|
154
|
+
return { declarations, returnTypeName: typeName, genericArg: typeName };
|
|
155
|
+
}
|
|
156
|
+
|
|
30
157
|
function getPascalCase(str) {
|
|
31
158
|
// Remove special characters and split the string into an array of words
|
|
32
159
|
const words = str.replace(/[^a-zA-Z0-9]/g, " ").split(" ");
|
|
@@ -47,6 +174,10 @@ function getRelativePath(from, to) {
|
|
|
47
174
|
return relativePath;
|
|
48
175
|
}
|
|
49
176
|
|
|
177
|
+
// Indentation for generated namespace content (2 spaces per level)
|
|
178
|
+
const INDENT_NS = " ";
|
|
179
|
+
const INDENT_NS_BODY = " ";
|
|
180
|
+
|
|
50
181
|
const instanceSnippet = `
|
|
51
182
|
import { FeaturevisorInstance } from "@featurevisor/sdk";
|
|
52
183
|
|
|
@@ -125,33 +256,52 @@ ${attributeProperties}
|
|
|
125
256
|
const namespaceValue = getPascalCase(featureKey) + "Feature";
|
|
126
257
|
featureNamespaces.push(namespaceValue);
|
|
127
258
|
|
|
259
|
+
let variableTypeDeclarations = "";
|
|
128
260
|
let variableMethods = "";
|
|
129
261
|
|
|
130
262
|
if (parsedFeature.variablesSchema) {
|
|
131
263
|
const variableKeys = Object.keys(parsedFeature.variablesSchema);
|
|
264
|
+
const allDeclarations: string[] = [];
|
|
132
265
|
|
|
133
266
|
for (const variableKey of variableKeys) {
|
|
134
267
|
const variableSchema = parsedFeature.variablesSchema[variableKey];
|
|
135
268
|
const variableType = variableSchema.type;
|
|
269
|
+
const { declarations, returnTypeName, genericArg } = generateVariableTypeDeclarations(
|
|
270
|
+
variableKey,
|
|
271
|
+
variableSchema,
|
|
272
|
+
);
|
|
273
|
+
allDeclarations.push(...declarations);
|
|
136
274
|
|
|
137
275
|
const internalMethodName = `getVariable${
|
|
138
276
|
variableType === "json" ? "JSON" : getPascalCase(variableType)
|
|
139
277
|
}`;
|
|
140
278
|
|
|
141
|
-
|
|
279
|
+
const hasGeneric =
|
|
280
|
+
variableType === "json" || variableType === "array" || variableType === "object";
|
|
281
|
+
if (variableType === "json") {
|
|
282
|
+
variableMethods += `
|
|
283
|
+
|
|
284
|
+
${INDENT_NS}export function get${getPascalCase(variableKey)}<T = unknown>(context: Context = {}): T | null {
|
|
285
|
+
${INDENT_NS_BODY}return getInstance().${internalMethodName}<T>(key, "${variableKey}", context);
|
|
286
|
+
${INDENT_NS}}`;
|
|
287
|
+
} else if (hasGeneric) {
|
|
142
288
|
variableMethods += `
|
|
143
289
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
290
|
+
${INDENT_NS}export function get${getPascalCase(variableKey)}(context: Context = {}): ${returnTypeName} | null {
|
|
291
|
+
${INDENT_NS_BODY}return getInstance().${internalMethodName}<${genericArg}>(key, "${variableKey}", context);
|
|
292
|
+
${INDENT_NS}}`;
|
|
147
293
|
} else {
|
|
148
294
|
variableMethods += `
|
|
149
295
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
296
|
+
${INDENT_NS}export function get${getPascalCase(variableKey)}(context: Context = {}): ${returnTypeName} | null {
|
|
297
|
+
${INDENT_NS_BODY}return getInstance().${internalMethodName}(key, "${variableKey}", context);
|
|
298
|
+
${INDENT_NS}}`;
|
|
153
299
|
}
|
|
154
300
|
}
|
|
301
|
+
|
|
302
|
+
if (allDeclarations.length > 0) {
|
|
303
|
+
variableTypeDeclarations = "\n\n" + allDeclarations.join("\n\n");
|
|
304
|
+
}
|
|
155
305
|
}
|
|
156
306
|
|
|
157
307
|
const featureContent = `
|
|
@@ -159,15 +309,15 @@ import { Context } from "./Context";
|
|
|
159
309
|
import { getInstance } from "./instance";
|
|
160
310
|
|
|
161
311
|
export namespace ${namespaceValue} {
|
|
162
|
-
|
|
312
|
+
${INDENT_NS}export const key = "${featureKey}";${variableTypeDeclarations}
|
|
163
313
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
314
|
+
${INDENT_NS}export function isEnabled(context: Context = {}) {
|
|
315
|
+
${INDENT_NS_BODY}return getInstance().isEnabled(key, context);
|
|
316
|
+
${INDENT_NS}}
|
|
167
317
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
318
|
+
${INDENT_NS}export function getVariation(context: Context = {}) {
|
|
319
|
+
${INDENT_NS_BODY}return getInstance().getVariation(key, context);
|
|
320
|
+
${INDENT_NS}}${variableMethods}
|
|
171
321
|
}
|
|
172
322
|
`.trimStart();
|
|
173
323
|
|