@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.
Files changed (53) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/coverage/clover.xml +2 -2
  3. package/coverage/lcov-report/builder/allocator.ts.html +1 -1
  4. package/coverage/lcov-report/builder/buildScopedConditions.ts.html +1 -1
  5. package/coverage/lcov-report/builder/buildScopedDatafile.ts.html +1 -1
  6. package/coverage/lcov-report/builder/buildScopedSegments.ts.html +1 -1
  7. package/coverage/lcov-report/builder/index.html +1 -1
  8. package/coverage/lcov-report/builder/mutateVariables.ts.html +1 -1
  9. package/coverage/lcov-report/builder/mutator.ts.html +1 -1
  10. package/coverage/lcov-report/builder/revision.ts.html +1 -1
  11. package/coverage/lcov-report/builder/traffic.ts.html +1 -1
  12. package/coverage/lcov-report/config/index.html +1 -1
  13. package/coverage/lcov-report/config/index.ts.html +1 -1
  14. package/coverage/lcov-report/config/projectConfig.ts.html +1 -1
  15. package/coverage/lcov-report/datasource/adapter.ts.html +1 -1
  16. package/coverage/lcov-report/datasource/datasource.ts.html +1 -1
  17. package/coverage/lcov-report/datasource/filesystemAdapter.ts.html +1 -1
  18. package/coverage/lcov-report/datasource/index.html +1 -1
  19. package/coverage/lcov-report/datasource/index.ts.html +1 -1
  20. package/coverage/lcov-report/index.html +1 -1
  21. package/coverage/lcov-report/linter/attributeSchema.ts.html +1 -1
  22. package/coverage/lcov-report/linter/checkCircularDependency.ts.html +1 -1
  23. package/coverage/lcov-report/linter/checkPercentageExceedingSlot.ts.html +1 -1
  24. package/coverage/lcov-report/linter/conditionSchema.ts.html +1 -1
  25. package/coverage/lcov-report/linter/featureSchema.ts.html +1 -1
  26. package/coverage/lcov-report/linter/groupSchema.ts.html +1 -1
  27. package/coverage/lcov-report/linter/index.html +1 -1
  28. package/coverage/lcov-report/linter/lintProject.ts.html +1 -1
  29. package/coverage/lcov-report/linter/mutationNotation.ts.html +1 -1
  30. package/coverage/lcov-report/linter/printError.ts.html +1 -1
  31. package/coverage/lcov-report/linter/schema.ts.html +1 -1
  32. package/coverage/lcov-report/linter/segmentSchema.ts.html +1 -1
  33. package/coverage/lcov-report/linter/testSchema.ts.html +1 -1
  34. package/coverage/lcov-report/list/index.html +1 -1
  35. package/coverage/lcov-report/list/matrix.ts.html +1 -1
  36. package/coverage/lcov-report/parsers/index.html +1 -1
  37. package/coverage/lcov-report/parsers/index.ts.html +1 -1
  38. package/coverage/lcov-report/parsers/json.ts.html +1 -1
  39. package/coverage/lcov-report/parsers/yml.ts.html +1 -1
  40. package/coverage/lcov-report/tester/cliFormat.ts.html +1 -1
  41. package/coverage/lcov-report/tester/helpers.ts.html +1 -1
  42. package/coverage/lcov-report/tester/index.html +1 -1
  43. package/coverage/lcov-report/utils/git.ts.html +1 -1
  44. package/coverage/lcov-report/utils/index.html +1 -1
  45. package/lib/generate-code/index.d.ts +3 -0
  46. package/lib/generate-code/index.js +16 -1
  47. package/lib/generate-code/index.js.map +1 -1
  48. package/lib/generate-code/typescript.d.ts +6 -1
  49. package/lib/generate-code/typescript.js +225 -49
  50. package/lib/generate-code/typescript.js.map +1 -1
  51. package/package.json +3 -3
  52. package/src/generate-code/index.ts +20 -1
  53. 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
- return `(${schemaToTypeScriptType(resolved.items, schemasByKey, schemaTypeNames)})[]`;
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(deps: Dependencies, outputPath: string) {
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/types";
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, "Context.ts");
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, "Schemas.ts");
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
- const namespaceValue = getPascalCase(featureKey) + "Feature";
514
- featureNamespaces.push(namespaceValue);
515
-
516
- let variableTypeDeclarations = "";
517
- let variableMethods = "";
518
- const featureSchemaTypesUsed = new Set<string>();
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
- const internalMethodName = `getVariable${
545
- variableType === "json" ? "JSON" : getPascalCase(variableType ?? "string")
546
- }`;
619
+ const namespaceValue = getPascalCase(featureKey) + "Feature";
620
+ parsedFeatures.push({ featureKey, parsedFeature, namespaceValue });
621
+ }
547
622
 
548
- const hasGeneric =
549
- variableType === "json" || variableType === "array" || variableType === "object";
550
- const literalAssertion = isLiteralType ? ` as ${returnTypeName} | null` : "";
551
- if (useGetVariable) {
552
- variableMethods += `
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
- } else if (variableType === "json") {
558
- variableMethods += `
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
- } else if (hasGeneric) {
564
- variableMethods += `
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
- } else {
570
- variableMethods += `
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
- if (allDeclarations.length > 0) {
579
- variableTypeDeclarations = "\n\n" + allDeclarations.join("\n\n");
690
+ if (allDeclarations.length > 0) {
691
+ variableTypeDeclarations = "\n\n" + allDeclarations.join("\n\n");
692
+ }
580
693
  }
581
- }
582
694
 
583
- const schemasImportLine =
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
- const featureContent = `
589
- import { Context } from "./Context";
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
- const featureNamespaceFilePath = path.join(outputPath, `${namespaceValue}.ts`);
605
- fs.writeFileSync(featureNamespaceFilePath, featureContent);
606
- console.log(
607
- `Feature ${featureKey} file written at: ${getRelativePath(
608
- rootDirectoryPath,
609
- featureNamespaceFilePath,
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 "./Context";`,
847
+ `export * from "./context";`,
618
848
  `export * from "./instance";`,
619
- ...(hasSchemasFile ? [`export * from "./Schemas";`] : []),
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
  }),