@featurevisor/core 2.15.0 → 2.17.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/mutateVariables.ts.html +1 -1
- package/coverage/lcov-report/builder/mutator.ts.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/config/index.html +1 -1
- package/coverage/lcov-report/config/index.ts.html +1 -1
- package/coverage/lcov-report/config/projectConfig.ts.html +1 -1
- package/coverage/lcov-report/datasource/adapter.ts.html +1 -1
- package/coverage/lcov-report/datasource/datasource.ts.html +1 -1
- package/coverage/lcov-report/datasource/filesystemAdapter.ts.html +1 -1
- package/coverage/lcov-report/datasource/index.html +1 -1
- package/coverage/lcov-report/datasource/index.ts.html +1 -1
- package/coverage/lcov-report/index.html +1 -1
- package/coverage/lcov-report/linter/attributeSchema.ts.html +1 -1
- package/coverage/lcov-report/linter/checkCircularDependency.ts.html +1 -1
- package/coverage/lcov-report/linter/checkPercentageExceedingSlot.ts.html +1 -1
- package/coverage/lcov-report/linter/conditionSchema.ts.html +1 -1
- package/coverage/lcov-report/linter/featureSchema.ts.html +1 -1
- package/coverage/lcov-report/linter/groupSchema.ts.html +1 -1
- package/coverage/lcov-report/linter/index.html +1 -1
- package/coverage/lcov-report/linter/lintProject.ts.html +1 -1
- package/coverage/lcov-report/linter/mutationNotation.ts.html +1 -1
- package/coverage/lcov-report/linter/printError.ts.html +1 -1
- package/coverage/lcov-report/linter/schema.ts.html +1 -1
- package/coverage/lcov-report/linter/segmentSchema.ts.html +1 -1
- package/coverage/lcov-report/linter/testSchema.ts.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/index.ts.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/cliFormat.ts.html +1 -1
- package/coverage/lcov-report/tester/helpers.ts.html +1 -1
- package/coverage/lcov-report/tester/index.html +1 -1
- package/coverage/lcov-report/utils/git.ts.html +1 -1
- package/coverage/lcov-report/utils/index.html +1 -1
- package/lib/generate-code/index.d.ts +3 -0
- package/lib/generate-code/index.js +16 -1
- package/lib/generate-code/index.js.map +1 -1
- package/lib/generate-code/typescript.d.ts +6 -1
- package/lib/generate-code/typescript.js +225 -49
- package/lib/generate-code/typescript.js.map +1 -1
- package/package.json +3 -3
- package/src/generate-code/index.ts +20 -1
- package/src/generate-code/typescript.ts +304 -71
|
@@ -3,12 +3,19 @@ import * as path from "path";
|
|
|
3
3
|
|
|
4
4
|
import type {
|
|
5
5
|
Attribute,
|
|
6
|
+
ParsedFeature,
|
|
6
7
|
Schema,
|
|
7
8
|
VariableSchema,
|
|
8
9
|
VariableSchemaWithInline,
|
|
9
10
|
} from "@featurevisor/types";
|
|
10
11
|
import { Dependencies } from "../dependencies";
|
|
11
12
|
|
|
13
|
+
export interface TypeScriptGenerationOptions {
|
|
14
|
+
tag?: string | string[];
|
|
15
|
+
react?: boolean;
|
|
16
|
+
individualFeatures?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
function convertFeaturevisorTypeToTypeScriptType(featurevisorType: string): string {
|
|
13
20
|
switch (featurevisorType) {
|
|
14
21
|
case "boolean":
|
|
@@ -32,6 +39,23 @@ function convertFeaturevisorTypeToTypeScriptType(featurevisorType: string): stri
|
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
41
|
|
|
42
|
+
function shouldWrapArrayItemType(typeName: string): boolean {
|
|
43
|
+
const trimmed = typeName.trim();
|
|
44
|
+
return trimmed.includes("|") || trimmed.includes("&");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function formatObjectKey(key: string): string {
|
|
48
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function formatTypeImport(typeNames: string[], fromPath: string): string {
|
|
52
|
+
if (typeNames.length === 0) {
|
|
53
|
+
return "";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return `import type {\n${typeNames.map((name) => ` ${name},`).join("\n")}\n} from "${fromPath}";\n\n`;
|
|
57
|
+
}
|
|
58
|
+
|
|
35
59
|
/**
|
|
36
60
|
* Resolve a schema to its full definition, following schema references (schema: key).
|
|
37
61
|
*/
|
|
@@ -103,7 +127,8 @@ function schemaToTypeScriptType(
|
|
|
103
127
|
return literalFromConst ?? unionFromEnum ?? "number";
|
|
104
128
|
case "array":
|
|
105
129
|
if (resolved.items) {
|
|
106
|
-
|
|
130
|
+
const itemType = schemaToTypeScriptType(resolved.items, schemasByKey, schemaTypeNames);
|
|
131
|
+
return shouldWrapArrayItemType(itemType) ? `(${itemType})[]` : `${itemType}[]`;
|
|
107
132
|
}
|
|
108
133
|
return "string[]";
|
|
109
134
|
case "object": {
|
|
@@ -383,6 +408,63 @@ function getRelativePath(from, to) {
|
|
|
383
408
|
return relativePath;
|
|
384
409
|
}
|
|
385
410
|
|
|
411
|
+
function getVariationUnionFromFeature(parsedFeature: ParsedFeature): string | null {
|
|
412
|
+
if (!parsedFeature.variations || parsedFeature.variations.length === 0) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const values = Array.from(
|
|
417
|
+
new Set(parsedFeature.variations.map((variation) => JSON.stringify(variation.value))),
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
return values.length > 0 ? values.join(" | ") : null;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function getVariableTypeForFeaturesMap(
|
|
424
|
+
variableSchema: VariableSchema,
|
|
425
|
+
schemasByKey: Record<string, Schema>,
|
|
426
|
+
schemaTypeNames?: Record<string, string>,
|
|
427
|
+
): { typeName: string; schemaTypesUsed: string[] } {
|
|
428
|
+
const schemaTypesUsed: string[] = [];
|
|
429
|
+
const addSchemaUsed = (name: string) => {
|
|
430
|
+
if (name && !schemaTypesUsed.includes(name)) {
|
|
431
|
+
schemaTypesUsed.push(name);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
if (
|
|
436
|
+
schemaTypeNames &&
|
|
437
|
+
"schema" in variableSchema &&
|
|
438
|
+
variableSchema.schema &&
|
|
439
|
+
schemasByKey[variableSchema.schema]
|
|
440
|
+
) {
|
|
441
|
+
const schemaTypeName = schemaTypeNames[variableSchema.schema];
|
|
442
|
+
addSchemaUsed(schemaTypeName);
|
|
443
|
+
return { typeName: schemaTypeName, schemaTypesUsed };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const effective = getEffectiveVariableSchema(variableSchema, schemasByKey);
|
|
447
|
+
if (!effective) {
|
|
448
|
+
return { typeName: "unknown", schemaTypesUsed };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if ("type" in effective && effective.type === "json") {
|
|
452
|
+
return { typeName: "unknown", schemaTypesUsed };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const typeName = schemaToTypeScriptType(effective as Schema, schemasByKey, schemaTypeNames);
|
|
456
|
+
|
|
457
|
+
if (schemaTypeNames) {
|
|
458
|
+
Object.values(schemaTypeNames).forEach((name) => {
|
|
459
|
+
if (typeName.includes(name)) {
|
|
460
|
+
addSchemaUsed(name);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return { typeName, schemaTypesUsed };
|
|
466
|
+
}
|
|
467
|
+
|
|
386
468
|
/**
|
|
387
469
|
* Generates the content of Schemas.ts: one exported type per schema key, using schema refs between schemas.
|
|
388
470
|
*/
|
|
@@ -423,8 +505,19 @@ export function getInstance(): FeaturevisorInstance {
|
|
|
423
505
|
}
|
|
424
506
|
`.trimStart();
|
|
425
507
|
|
|
426
|
-
export async function generateTypeScriptCodeForProject(
|
|
508
|
+
export async function generateTypeScriptCodeForProject(
|
|
509
|
+
deps: Dependencies,
|
|
510
|
+
outputPath: string,
|
|
511
|
+
options: TypeScriptGenerationOptions = {},
|
|
512
|
+
) {
|
|
427
513
|
const { rootDirectoryPath, datasource } = deps;
|
|
514
|
+
const selectedTags = options.tag
|
|
515
|
+
? Array.isArray(options.tag)
|
|
516
|
+
? options.tag
|
|
517
|
+
: [options.tag]
|
|
518
|
+
: [];
|
|
519
|
+
const shouldGenerateReact = Boolean(options.react);
|
|
520
|
+
const shouldGenerateIndividualFeatures = options.individualFeatures !== false;
|
|
428
521
|
|
|
429
522
|
console.log("\nGenerating TypeScript code...\n");
|
|
430
523
|
|
|
@@ -459,7 +552,7 @@ export async function generateTypeScriptCodeForProject(deps: Dependencies, outpu
|
|
|
459
552
|
})
|
|
460
553
|
.join("\n");
|
|
461
554
|
const contextContent = `
|
|
462
|
-
import type { AttributeKey, AttributeValue } from "@featurevisor/
|
|
555
|
+
import type { AttributeKey, AttributeValue } from "@featurevisor/sdk";
|
|
463
556
|
|
|
464
557
|
export interface Context {
|
|
465
558
|
${attributeProperties}
|
|
@@ -467,14 +560,13 @@ ${attributeProperties}
|
|
|
467
560
|
}
|
|
468
561
|
`.trimStart();
|
|
469
562
|
|
|
470
|
-
const contextTypeFilePath = path.join(outputPath, "
|
|
563
|
+
const contextTypeFilePath = path.join(outputPath, "context.ts");
|
|
471
564
|
fs.writeFileSync(contextTypeFilePath, contextContent);
|
|
472
565
|
console.log(
|
|
473
566
|
`Context type file written at: ${getRelativePath(rootDirectoryPath, contextTypeFilePath)}`,
|
|
474
567
|
);
|
|
475
568
|
|
|
476
569
|
// features
|
|
477
|
-
const featureNamespaces: string[] = [];
|
|
478
570
|
const featureFiles = await datasource.listFeatures();
|
|
479
571
|
|
|
480
572
|
// Load schemas for resolving variable schema references
|
|
@@ -491,7 +583,7 @@ ${attributeProperties}
|
|
|
491
583
|
const hasSchemasFile = schemaKeys.length > 0;
|
|
492
584
|
if (hasSchemasFile) {
|
|
493
585
|
const schemasContent = generateSchemasFileContent(schemaKeys, schemasByKey);
|
|
494
|
-
const schemasFilePath = path.join(outputPath, "
|
|
586
|
+
const schemasFilePath = path.join(outputPath, "schemas.ts");
|
|
495
587
|
fs.writeFileSync(schemasFilePath, schemasContent);
|
|
496
588
|
console.log(
|
|
497
589
|
`Schemas type file written at: ${getRelativePath(rootDirectoryPath, schemasFilePath)}`,
|
|
@@ -503,90 +595,107 @@ ${attributeProperties}
|
|
|
503
595
|
schemaTypeNames[k] = getPascalCase(k) + "Schema";
|
|
504
596
|
}
|
|
505
597
|
|
|
598
|
+
const parsedFeatures: {
|
|
599
|
+
featureKey: string;
|
|
600
|
+
parsedFeature: ParsedFeature;
|
|
601
|
+
namespaceValue: string;
|
|
602
|
+
}[] = [];
|
|
603
|
+
|
|
506
604
|
for (const featureKey of featureFiles) {
|
|
507
|
-
const parsedFeature = await datasource.readFeature(featureKey);
|
|
605
|
+
const parsedFeature = (await datasource.readFeature(featureKey)) as ParsedFeature;
|
|
508
606
|
|
|
509
607
|
if (typeof parsedFeature.archived !== "undefined" && parsedFeature.archived) {
|
|
510
608
|
continue;
|
|
511
609
|
}
|
|
512
610
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
if (parsedFeature.variablesSchema) {
|
|
521
|
-
const variableKeys = Object.keys(parsedFeature.variablesSchema);
|
|
522
|
-
const allDeclarations: string[] = [];
|
|
523
|
-
|
|
524
|
-
for (const variableKey of variableKeys) {
|
|
525
|
-
const variableSchema = parsedFeature.variablesSchema[variableKey];
|
|
526
|
-
const effective = getEffectiveVariableSchema(variableSchema, schemasByKey);
|
|
527
|
-
const variableType = effective?.type;
|
|
528
|
-
const {
|
|
529
|
-
declarations,
|
|
530
|
-
returnTypeName,
|
|
531
|
-
genericArg,
|
|
532
|
-
isLiteralType,
|
|
533
|
-
useGetVariable,
|
|
534
|
-
schemaTypesUsed,
|
|
535
|
-
} = generateVariableTypeDeclarations(
|
|
536
|
-
variableKey,
|
|
537
|
-
variableSchema,
|
|
538
|
-
schemasByKey,
|
|
539
|
-
hasSchemasFile ? schemaTypeNames : undefined,
|
|
540
|
-
);
|
|
541
|
-
schemaTypesUsed.forEach((t) => featureSchemaTypesUsed.add(t));
|
|
542
|
-
allDeclarations.push(...declarations);
|
|
611
|
+
if (selectedTags.length > 0) {
|
|
612
|
+
const featureTags = Array.isArray(parsedFeature.tags) ? parsedFeature.tags : [];
|
|
613
|
+
const hasTags = selectedTags.every((tag) => featureTags.includes(tag));
|
|
614
|
+
if (!hasTags) {
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
543
618
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
619
|
+
const namespaceValue = getPascalCase(featureKey) + "Feature";
|
|
620
|
+
parsedFeatures.push({ featureKey, parsedFeature, namespaceValue });
|
|
621
|
+
}
|
|
547
622
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
623
|
+
const featureNamespaces: string[] = [];
|
|
624
|
+
if (shouldGenerateIndividualFeatures) {
|
|
625
|
+
for (const { featureKey, parsedFeature, namespaceValue } of parsedFeatures) {
|
|
626
|
+
featureNamespaces.push(namespaceValue);
|
|
627
|
+
|
|
628
|
+
let variableTypeDeclarations = "";
|
|
629
|
+
let variableMethods = "";
|
|
630
|
+
const featureSchemaTypesUsed = new Set<string>();
|
|
631
|
+
|
|
632
|
+
if (parsedFeature.variablesSchema) {
|
|
633
|
+
const variableKeys = Object.keys(parsedFeature.variablesSchema);
|
|
634
|
+
const allDeclarations: string[] = [];
|
|
635
|
+
|
|
636
|
+
for (const variableKey of variableKeys) {
|
|
637
|
+
const variableSchema = parsedFeature.variablesSchema[variableKey];
|
|
638
|
+
const effective = getEffectiveVariableSchema(variableSchema, schemasByKey);
|
|
639
|
+
const variableType = effective?.type;
|
|
640
|
+
const {
|
|
641
|
+
declarations,
|
|
642
|
+
returnTypeName,
|
|
643
|
+
genericArg,
|
|
644
|
+
isLiteralType,
|
|
645
|
+
useGetVariable,
|
|
646
|
+
schemaTypesUsed,
|
|
647
|
+
} = generateVariableTypeDeclarations(
|
|
648
|
+
variableKey,
|
|
649
|
+
variableSchema,
|
|
650
|
+
schemasByKey,
|
|
651
|
+
hasSchemasFile ? schemaTypeNames : undefined,
|
|
652
|
+
);
|
|
653
|
+
schemaTypesUsed.forEach((t) => featureSchemaTypesUsed.add(t));
|
|
654
|
+
allDeclarations.push(...declarations);
|
|
655
|
+
|
|
656
|
+
const internalMethodName = `getVariable${
|
|
657
|
+
variableType === "json" ? "JSON" : getPascalCase(variableType ?? "string")
|
|
658
|
+
}`;
|
|
659
|
+
|
|
660
|
+
const hasGeneric =
|
|
661
|
+
variableType === "json" || variableType === "array" || variableType === "object";
|
|
662
|
+
const literalAssertion = isLiteralType ? ` as ${returnTypeName} | null` : "";
|
|
663
|
+
if (useGetVariable) {
|
|
664
|
+
variableMethods += `
|
|
553
665
|
|
|
554
666
|
${INDENT_NS}export function get${getPascalCase(variableKey)}(context: Context = {}): ${returnTypeName} | null {
|
|
555
667
|
${INDENT_NS_BODY}return getInstance().getVariable(key, "${variableKey}", context)${literalAssertion};
|
|
556
668
|
${INDENT_NS}}`;
|
|
557
|
-
|
|
558
|
-
|
|
669
|
+
} else if (variableType === "json") {
|
|
670
|
+
variableMethods += `
|
|
559
671
|
|
|
560
672
|
${INDENT_NS}export function get${getPascalCase(variableKey)}<T = unknown>(context: Context = {}): T | null {
|
|
561
673
|
${INDENT_NS_BODY}return getInstance().${internalMethodName}<T>(key, "${variableKey}", context);
|
|
562
674
|
${INDENT_NS}}`;
|
|
563
|
-
|
|
564
|
-
|
|
675
|
+
} else if (hasGeneric) {
|
|
676
|
+
variableMethods += `
|
|
565
677
|
|
|
566
678
|
${INDENT_NS}export function get${getPascalCase(variableKey)}(context: Context = {}): ${returnTypeName} | null {
|
|
567
679
|
${INDENT_NS_BODY}return getInstance().${internalMethodName}<${genericArg}>(key, "${variableKey}", context);
|
|
568
680
|
${INDENT_NS}}`;
|
|
569
|
-
|
|
570
|
-
|
|
681
|
+
} else {
|
|
682
|
+
variableMethods += `
|
|
571
683
|
|
|
572
684
|
${INDENT_NS}export function get${getPascalCase(variableKey)}(context: Context = {}): ${returnTypeName} | null {
|
|
573
685
|
${INDENT_NS_BODY}return getInstance().${internalMethodName}(key, "${variableKey}", context)${literalAssertion};
|
|
574
686
|
${INDENT_NS}}`;
|
|
687
|
+
}
|
|
575
688
|
}
|
|
576
|
-
}
|
|
577
689
|
|
|
578
|
-
|
|
579
|
-
|
|
690
|
+
if (allDeclarations.length > 0) {
|
|
691
|
+
variableTypeDeclarations = "\n\n" + allDeclarations.join("\n\n");
|
|
692
|
+
}
|
|
580
693
|
}
|
|
581
|
-
}
|
|
582
694
|
|
|
583
|
-
|
|
584
|
-
featureSchemaTypesUsed.size > 0
|
|
585
|
-
? `import type { ${[...featureSchemaTypesUsed].sort().join(", ")} } from "./Schemas";\n\n`
|
|
586
|
-
: "";
|
|
695
|
+
const schemasImportLine = formatTypeImport([...featureSchemaTypesUsed].sort(), "./schemas");
|
|
587
696
|
|
|
588
|
-
|
|
589
|
-
import { Context } from "./
|
|
697
|
+
const featureContent = `
|
|
698
|
+
import { Context } from "./context";
|
|
590
699
|
import { getInstance } from "./instance";
|
|
591
700
|
${schemasImportLine}export namespace ${namespaceValue} {
|
|
592
701
|
${INDENT_NS}export const key = "${featureKey}";${variableTypeDeclarations}
|
|
@@ -601,22 +710,146 @@ ${INDENT_NS}}${variableMethods}
|
|
|
601
710
|
}
|
|
602
711
|
`.trimStart();
|
|
603
712
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
713
|
+
const featureNamespaceFilePath = path.join(outputPath, `${namespaceValue}.ts`);
|
|
714
|
+
fs.writeFileSync(featureNamespaceFilePath, featureContent);
|
|
715
|
+
console.log(
|
|
716
|
+
`Feature ${featureKey} file written at: ${getRelativePath(
|
|
717
|
+
rootDirectoryPath,
|
|
718
|
+
featureNamespaceFilePath,
|
|
719
|
+
)}`,
|
|
720
|
+
);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
const featuresTypeSchemasUsed = new Set<string>();
|
|
725
|
+
const featureTypeEntries = parsedFeatures
|
|
726
|
+
.map(({ featureKey, parsedFeature }) => {
|
|
727
|
+
const featureLines: string[] = [];
|
|
728
|
+
const variationUnion = getVariationUnionFromFeature(parsedFeature);
|
|
729
|
+
|
|
730
|
+
if (variationUnion) {
|
|
731
|
+
featureLines.push(`${INDENT_NS_BODY}variation: ${variationUnion};`);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
if (parsedFeature.variablesSchema) {
|
|
735
|
+
for (const [variableKey, variableSchema] of Object.entries(parsedFeature.variablesSchema)) {
|
|
736
|
+
const { typeName, schemaTypesUsed } = getVariableTypeForFeaturesMap(
|
|
737
|
+
variableSchema,
|
|
738
|
+
schemasByKey,
|
|
739
|
+
hasSchemasFile ? schemaTypeNames : undefined,
|
|
740
|
+
);
|
|
741
|
+
schemaTypesUsed.forEach((name) => featuresTypeSchemasUsed.add(name));
|
|
742
|
+
featureLines.push(`${INDENT_NS_BODY}${formatObjectKey(variableKey)}: ${typeName};`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (featureLines.length === 0) {
|
|
747
|
+
return `${INDENT_NS}${formatObjectKey(featureKey)}: null;`;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return `${INDENT_NS}${formatObjectKey(featureKey)}: {\n${featureLines.join("\n")}\n${INDENT_NS}};`;
|
|
751
|
+
})
|
|
752
|
+
.join("\n");
|
|
753
|
+
|
|
754
|
+
const featuresSchemasImportLine = formatTypeImport(
|
|
755
|
+
[...featuresTypeSchemasUsed].sort(),
|
|
756
|
+
"./schemas",
|
|
757
|
+
);
|
|
758
|
+
|
|
759
|
+
const featuresFileContent = `
|
|
760
|
+
${featuresSchemasImportLine}export type Features = {
|
|
761
|
+
${featureTypeEntries}
|
|
762
|
+
};
|
|
763
|
+
|
|
764
|
+
export type FeatureKey = keyof Features;
|
|
765
|
+
export type VariableKey<F extends FeatureKey> = Features[F] extends Record<string, unknown>
|
|
766
|
+
? Extract<Exclude<keyof Features[F], "variation">, string>
|
|
767
|
+
: never;
|
|
768
|
+
export type VariableType<F extends FeatureKey, V extends VariableKey<F>> = Features[F] extends Record<string, unknown>
|
|
769
|
+
? Features[F][V]
|
|
770
|
+
: never;
|
|
771
|
+
export type Variation<F extends FeatureKey> = Features[F] extends { variation: infer V }
|
|
772
|
+
? V
|
|
773
|
+
: never;
|
|
774
|
+
`.trimStart();
|
|
775
|
+
const featuresFilePath = path.join(outputPath, "features.ts");
|
|
776
|
+
fs.writeFileSync(featuresFilePath, featuresFileContent);
|
|
777
|
+
console.log(`Features file written at: ${getRelativePath(rootDirectoryPath, featuresFilePath)}`);
|
|
778
|
+
|
|
779
|
+
const functionsFileContent = `
|
|
780
|
+
import { FeatureKey, Variation, VariableKey, VariableType } from "./features";
|
|
781
|
+
import { Context } from "./context";
|
|
782
|
+
import { getInstance } from "./instance";
|
|
783
|
+
|
|
784
|
+
export function isEnabled(featureKey: FeatureKey, context: Context = {}): boolean {
|
|
785
|
+
return getInstance().isEnabled(featureKey, context);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
export function getVariation<F extends FeatureKey>(
|
|
789
|
+
featureKey: F,
|
|
790
|
+
context: Context = {},
|
|
791
|
+
): Variation<F> | null {
|
|
792
|
+
return getInstance().getVariation(featureKey, context) as Variation<F> | null;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
export function getVariable<F extends FeatureKey, V extends VariableKey<F>>(
|
|
796
|
+
featureKey: F,
|
|
797
|
+
variableKey: V,
|
|
798
|
+
context: Context = {},
|
|
799
|
+
): VariableType<F, V> | null {
|
|
800
|
+
return getInstance().getVariable(featureKey, variableKey, context) as VariableType<F, V> | null;
|
|
801
|
+
}
|
|
802
|
+
`.trimStart();
|
|
803
|
+
const functionsFilePath = path.join(outputPath, "functions.ts");
|
|
804
|
+
fs.writeFileSync(functionsFilePath, functionsFileContent);
|
|
805
|
+
console.log(
|
|
806
|
+
`Functions file written at: ${getRelativePath(rootDirectoryPath, functionsFilePath)}`,
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
if (shouldGenerateReact) {
|
|
810
|
+
const reactFileContent = `
|
|
811
|
+
import {
|
|
812
|
+
useFlag as useFlagOriginal,
|
|
813
|
+
useVariation as useVariationOriginal,
|
|
814
|
+
useVariable as useVariableOriginal,
|
|
815
|
+
} from "@featurevisor/react";
|
|
816
|
+
|
|
817
|
+
import { FeatureKey, Variation, VariableKey, VariableType } from "./features";
|
|
818
|
+
import { Context } from "./context";
|
|
819
|
+
|
|
820
|
+
export function useFlag(featureKey: FeatureKey, context: Context = {}): boolean {
|
|
821
|
+
return useFlagOriginal(featureKey, context);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
export function useVariation<F extends FeatureKey>(
|
|
825
|
+
featureKey: F,
|
|
826
|
+
context: Context = {},
|
|
827
|
+
): Variation<F> | null {
|
|
828
|
+
return useVariationOriginal(featureKey, context) as Variation<F> | null;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
export function useVariable<F extends FeatureKey, V extends VariableKey<F>>(
|
|
832
|
+
featureKey: F,
|
|
833
|
+
variableKey: V,
|
|
834
|
+
context: Context = {},
|
|
835
|
+
): VariableType<F, V> | null {
|
|
836
|
+
return useVariableOriginal(featureKey, variableKey, context) as VariableType<F, V> | null;
|
|
837
|
+
}
|
|
838
|
+
`.trimStart();
|
|
839
|
+
const reactFilePath = path.join(outputPath, "react.ts");
|
|
840
|
+
fs.writeFileSync(reactFilePath, reactFileContent);
|
|
841
|
+
console.log(`React file written at: ${getRelativePath(rootDirectoryPath, reactFilePath)}`);
|
|
612
842
|
}
|
|
613
843
|
|
|
614
844
|
// index
|
|
615
845
|
const indexContent =
|
|
616
846
|
[
|
|
617
|
-
`export * from "./
|
|
847
|
+
`export * from "./context";`,
|
|
618
848
|
`export * from "./instance";`,
|
|
619
|
-
...(hasSchemasFile ? [`export * from "./
|
|
849
|
+
...(hasSchemasFile ? [`export * from "./schemas";`] : []),
|
|
850
|
+
`export * from "./features";`,
|
|
851
|
+
`export * from "./functions";`,
|
|
852
|
+
...(shouldGenerateReact ? [`export * from "./react";`] : []),
|
|
620
853
|
...featureNamespaces.map((featureNamespace) => {
|
|
621
854
|
return `export * from "./${featureNamespace}";`;
|
|
622
855
|
}),
|