@featurevisor/core 0.53.2 → 0.53.4

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 (84) hide show
  1. package/.eslintcache +1 -1
  2. package/CHANGELOG.md +16 -0
  3. package/coverage/clover.xml +2 -2
  4. package/coverage/lcov-report/index.html +1 -1
  5. package/coverage/lcov-report/lib/builder/allocator.js.html +1 -1
  6. package/coverage/lcov-report/lib/builder/index.html +1 -1
  7. package/coverage/lcov-report/lib/builder/traffic.js.html +1 -1
  8. package/coverage/lcov-report/src/builder/allocator.ts.html +1 -1
  9. package/coverage/lcov-report/src/builder/index.html +1 -1
  10. package/coverage/lcov-report/src/builder/traffic.ts.html +1 -1
  11. package/lib/builder/buildDatafile.d.ts +1 -1
  12. package/lib/builder/buildDatafile.js +241 -168
  13. package/lib/builder/buildDatafile.js.map +1 -1
  14. package/lib/builder/buildProject.d.ts +1 -2
  15. package/lib/builder/buildProject.js +95 -45
  16. package/lib/builder/buildProject.js.map +1 -1
  17. package/lib/builder/getFeatureRanges.d.ts +1 -1
  18. package/lib/builder/getFeatureRanges.js +92 -31
  19. package/lib/builder/getFeatureRanges.js.map +1 -1
  20. package/lib/datasource/datasource.d.ts +23 -17
  21. package/lib/datasource/datasource.js +166 -69
  22. package/lib/datasource/datasource.js.map +1 -1
  23. package/lib/datasource/parsers.js +2 -2
  24. package/lib/datasource/parsers.js.map +1 -1
  25. package/lib/find-duplicate-segments/findDuplicateSegments.d.ts +1 -1
  26. package/lib/find-duplicate-segments/findDuplicateSegments.js +75 -18
  27. package/lib/find-duplicate-segments/findDuplicateSegments.js.map +1 -1
  28. package/lib/find-duplicate-segments/index.d.ts +1 -1
  29. package/lib/find-duplicate-segments/index.js +56 -9
  30. package/lib/find-duplicate-segments/index.js.map +1 -1
  31. package/lib/generate-code/index.d.ts +1 -1
  32. package/lib/generate-code/index.js +67 -23
  33. package/lib/generate-code/index.js.map +1 -1
  34. package/lib/generate-code/typescript.d.ts +1 -1
  35. package/lib/generate-code/typescript.js +139 -72
  36. package/lib/generate-code/typescript.js.map +1 -1
  37. package/lib/linter/checkCircularDependency.d.ts +1 -1
  38. package/lib/linter/checkCircularDependency.js +78 -22
  39. package/lib/linter/checkCircularDependency.js.map +1 -1
  40. package/lib/linter/groupSchema.js +79 -28
  41. package/lib/linter/groupSchema.js.map +1 -1
  42. package/lib/linter/lintProject.js +119 -103
  43. package/lib/linter/lintProject.js.map +1 -1
  44. package/lib/restore.d.ts +1 -1
  45. package/lib/restore.js +53 -11
  46. package/lib/restore.js.map +1 -1
  47. package/lib/site/exportSite.d.ts +1 -1
  48. package/lib/site/exportSite.js +64 -21
  49. package/lib/site/exportSite.js.map +1 -1
  50. package/lib/site/generateSiteSearchIndex.d.ts +1 -1
  51. package/lib/site/generateSiteSearchIndex.js +203 -111
  52. package/lib/site/generateSiteSearchIndex.js.map +1 -1
  53. package/lib/tester/testFeature.d.ts +1 -1
  54. package/lib/tester/testFeature.js +130 -60
  55. package/lib/tester/testFeature.js.map +1 -1
  56. package/lib/tester/testProject.d.ts +1 -1
  57. package/lib/tester/testProject.js +105 -48
  58. package/lib/tester/testProject.js.map +1 -1
  59. package/lib/tester/testSegment.d.ts +1 -1
  60. package/lib/tester/testSegment.js +69 -21
  61. package/lib/tester/testSegment.js.map +1 -1
  62. package/lib/utils.d.ts +0 -2
  63. package/lib/utils.js +1 -15
  64. package/lib/utils.js.map +1 -1
  65. package/package.json +2 -2
  66. package/src/builder/buildDatafile.ts +9 -9
  67. package/src/builder/buildProject.ts +4 -24
  68. package/src/builder/getFeatureRanges.ts +4 -4
  69. package/src/datasource/datasource.ts +66 -60
  70. package/src/datasource/parsers.ts +2 -2
  71. package/src/find-duplicate-segments/findDuplicateSegments.ts +9 -6
  72. package/src/find-duplicate-segments/index.ts +5 -2
  73. package/src/generate-code/index.ts +2 -2
  74. package/src/generate-code/typescript.ts +42 -25
  75. package/src/linter/checkCircularDependency.ts +4 -4
  76. package/src/linter/groupSchema.ts +3 -3
  77. package/src/linter/lintProject.ts +53 -60
  78. package/src/restore.ts +1 -1
  79. package/src/site/exportSite.ts +2 -2
  80. package/src/site/generateSiteSearchIndex.ts +14 -14
  81. package/src/tester/testFeature.ts +12 -13
  82. package/src/tester/testProject.ts +8 -5
  83. package/src/tester/testSegment.ts +3 -3
  84. package/src/utils.ts +0 -18
@@ -33,12 +33,12 @@ export interface BuildOptions {
33
33
  features?: FeatureKey[];
34
34
  }
35
35
 
36
- export function buildDatafile(
36
+ export async function buildDatafile(
37
37
  projectConfig: ProjectConfig,
38
38
  datasource: Datasource,
39
39
  options: BuildOptions,
40
40
  existingState: ExistingState,
41
- ): DatafileContent {
41
+ ): Promise<DatafileContent> {
42
42
  const datafileContent: DatafileContent = {
43
43
  schemaVersion: options.schemaVersion,
44
44
  revision: options.revision,
@@ -49,17 +49,17 @@ export function buildDatafile(
49
49
 
50
50
  const segmentKeysUsedByTag = new Set<SegmentKey>();
51
51
  const attributeKeysUsedByTag = new Set<AttributeKey>();
52
- const { featureRanges, featureIsInGroup } = getFeatureRanges(projectConfig, datasource);
52
+ const { featureRanges, featureIsInGroup } = await getFeatureRanges(projectConfig, datasource);
53
53
 
54
54
  // features
55
55
  const features: Feature[] = [];
56
56
  const featuresDirectory = projectConfig.featuresDirectoryPath;
57
57
 
58
58
  if (fs.existsSync(featuresDirectory)) {
59
- const featureFiles = datasource.listFeatures();
59
+ const featureFiles = await datasource.listFeatures();
60
60
 
61
61
  for (const featureKey of featureFiles) {
62
- const parsedFeature = datasource.readFeature(featureKey);
62
+ const parsedFeature = await datasource.readFeature(featureKey);
63
63
 
64
64
  if (parsedFeature.archived === true) {
65
65
  continue;
@@ -200,10 +200,10 @@ export function buildDatafile(
200
200
  const segmentsDirectory = projectConfig.segmentsDirectoryPath;
201
201
 
202
202
  if (fs.existsSync(segmentsDirectory)) {
203
- const segmentFiles = datasource.listSegments();
203
+ const segmentFiles = await datasource.listSegments();
204
204
 
205
205
  for (const segmentKey of segmentFiles) {
206
- const parsedSegment = datasource.readSegment(segmentKey);
206
+ const parsedSegment = await datasource.readSegment(segmentKey);
207
207
 
208
208
  if (parsedSegment.archived === true) {
209
209
  continue;
@@ -235,10 +235,10 @@ export function buildDatafile(
235
235
  const attributesDirectory = projectConfig.attributesDirectoryPath;
236
236
 
237
237
  if (fs.existsSync(attributesDirectory)) {
238
- const attributeFiles = datasource.listAttributes();
238
+ const attributeFiles = await datasource.listAttributes();
239
239
 
240
240
  for (const attributeKey of attributeFiles) {
241
- const parsedAttribute = datasource.readAttribute(attributeKey);
241
+ const parsedAttribute = await datasource.readAttribute(attributeKey);
242
242
 
243
243
  if (parsedAttribute.archived === true) {
244
244
  continue;
@@ -20,18 +20,11 @@ export function getDatafilePath(
20
20
  return path.join(projectConfig.outputDirectoryPath, environment, fileName);
21
21
  }
22
22
 
23
- export function getExistingStateFilePath(
24
- projectConfig: ProjectConfig,
25
- environment: EnvironmentKey,
26
- ): string {
27
- return path.join(projectConfig.stateDirectoryPath, `existing-state-${environment}.json`);
28
- }
29
-
30
23
  export interface BuildCLIOptions {
31
24
  revision?: string;
32
25
  }
33
26
 
34
- export function buildProject(
27
+ export async function buildProject(
35
28
  rootDirectoryPath,
36
29
  projectConfig: ProjectConfig,
37
30
  cliOptions: BuildCLIOptions = {},
@@ -45,16 +38,11 @@ export function buildProject(
45
38
  for (const environment of environments) {
46
39
  console.log(`\nBuilding datafiles for environment: ${environment}`);
47
40
 
48
- const existingStateFilePath = getExistingStateFilePath(projectConfig, environment);
49
- const existingState: ExistingState = fs.existsSync(existingStateFilePath)
50
- ? require(existingStateFilePath)
51
- : {
52
- features: {},
53
- };
41
+ const existingState: ExistingState = await datasource.readState(environment);
54
42
 
55
43
  for (const tag of tags) {
56
44
  console.log(`\n => Tag: ${tag}`);
57
- const datafileContent = buildDatafile(
45
+ const datafileContent = await buildDatafile(
58
46
  projectConfig,
59
47
  datasource,
60
48
  {
@@ -82,14 +70,6 @@ export function buildProject(
82
70
  }
83
71
 
84
72
  // write state for environment
85
- if (!fs.existsSync(projectConfig.stateDirectoryPath)) {
86
- mkdirp.sync(projectConfig.stateDirectoryPath);
87
- }
88
- fs.writeFileSync(
89
- existingStateFilePath,
90
- projectConfig.prettyState
91
- ? JSON.stringify(existingState, null, 2)
92
- : JSON.stringify(existingState),
93
- );
73
+ await datasource.writeState(environment, existingState);
94
74
  }
95
75
  }
@@ -12,19 +12,19 @@ export interface FeatureRangesResult {
12
12
  featureIsInGroup: { [key: string]: boolean };
13
13
  }
14
14
 
15
- export function getFeatureRanges(
15
+ export async function getFeatureRanges(
16
16
  projectConfig: ProjectConfig,
17
17
  datasource: Datasource,
18
- ): FeatureRangesResult {
18
+ ): Promise<FeatureRangesResult> {
19
19
  const featureRanges = new Map<FeatureKey, Range[]>();
20
20
  const featureIsInGroup = {}; // featureKey => boolean
21
21
 
22
22
  const groups: Group[] = [];
23
23
  if (fs.existsSync(projectConfig.groupsDirectoryPath)) {
24
- const groupFiles = datasource.listGroups();
24
+ const groupFiles = await datasource.listGroups();
25
25
 
26
26
  for (const groupKey of groupFiles) {
27
- const parsedGroup = datasource.readGroup(groupKey);
27
+ const parsedGroup = await datasource.readGroup(groupKey);
28
28
 
29
29
  groups.push({
30
30
  ...parsedGroup,
@@ -1,14 +1,17 @@
1
1
  import * as path from "path";
2
2
  import * as fs from "fs";
3
3
 
4
+ import * as mkdirp from "mkdirp";
5
+
4
6
  import {
5
- Tag,
6
7
  ParsedFeature,
7
8
  Segment,
8
9
  Attribute,
9
10
  Group,
10
11
  FeatureKey,
11
12
  Test,
13
+ EnvironmentKey,
14
+ ExistingState,
12
15
  } from "@featurevisor/types";
13
16
 
14
17
  import { ProjectConfig } from "../config";
@@ -16,6 +19,13 @@ import { parsers } from "./parsers";
16
19
 
17
20
  export type EntityType = "feature" | "group" | "segment" | "attribute" | "test";
18
21
 
22
+ export function getExistingStateFilePath(
23
+ projectConfig: ProjectConfig,
24
+ environment: EnvironmentKey,
25
+ ): string {
26
+ return path.join(projectConfig.stateDirectoryPath, `existing-state-${environment}.json`);
27
+ }
28
+
19
29
  export class Datasource {
20
30
  private extension;
21
31
  private parse;
@@ -51,11 +61,15 @@ export class Datasource {
51
61
  }
52
62
 
53
63
  /**
54
- * Common methods
64
+ * Common methods for entities
55
65
  */
56
- listEntities(entityType: EntityType): string[] {
66
+ async listEntities(entityType: EntityType): Promise<string[]> {
57
67
  const directoryPath = this.getEntityDirectoryPath(entityType);
58
68
 
69
+ if (!fs.existsSync(directoryPath)) {
70
+ return [];
71
+ }
72
+
59
73
  return fs
60
74
  .readdirSync(directoryPath)
61
75
  .filter((fileName) => fileName.endsWith(`.${this.extension}`))
@@ -82,84 +96,100 @@ export class Datasource {
82
96
  return path.join(basePath, `${entityKey}.${this.extension}`);
83
97
  }
84
98
 
85
- entityExists(entityType: EntityType, entityKey: string): boolean {
99
+ async entityExists(entityType: EntityType, entityKey: string): Promise<boolean> {
86
100
  const entityPath = this.getEntityPath(entityType, entityKey);
87
101
 
88
102
  return fs.existsSync(entityPath);
89
103
  }
90
104
 
91
- readEntity(entityType: EntityType, entityKey: string): string {
105
+ async readEntity(entityType: EntityType, entityKey: string): Promise<string> {
92
106
  const filePath = this.getEntityPath(entityType, entityKey);
93
107
 
94
108
  return fs.readFileSync(filePath, "utf8");
95
109
  }
96
110
 
97
- parseEntity<T>(entityType: EntityType, entityKey: string): T {
98
- const entityContent = this.readEntity(entityType, entityKey);
111
+ async parseEntity<T>(entityType: EntityType, entityKey: string): Promise<T> {
112
+ const entityContent = await this.readEntity(entityType, entityKey);
99
113
 
100
114
  return this.parse(entityContent) as T;
101
115
  }
102
116
 
103
117
  /**
104
- * Entity specific methods
118
+ * State
105
119
  */
120
+ async readState(environment: EnvironmentKey): Promise<ExistingState> {
121
+ const filePath = getExistingStateFilePath(this.config, environment);
106
122
 
107
- // features
108
- listFeatures(tag?: Tag) {
109
- const features = this.listEntities("feature");
123
+ if (!fs.existsSync(filePath)) {
124
+ return {
125
+ features: {},
126
+ };
127
+ }
110
128
 
111
- if (tag) {
112
- return features.filter((feature) => {
113
- const featureContent = this.parseEntity<ParsedFeature>("feature", feature);
129
+ return require(filePath);
130
+ }
131
+
132
+ async writeState(environment: EnvironmentKey, existingState: ExistingState) {
133
+ const filePath = getExistingStateFilePath(this.config, environment);
114
134
 
115
- return featureContent.tags.indexOf(tag) !== -1;
116
- });
135
+ if (!fs.existsSync(this.config.stateDirectoryPath)) {
136
+ mkdirp.sync(this.config.stateDirectoryPath);
117
137
  }
138
+ fs.writeFileSync(
139
+ filePath,
140
+ this.config.prettyState
141
+ ? JSON.stringify(existingState, null, 2)
142
+ : JSON.stringify(existingState),
143
+ );
118
144
 
119
- return features;
145
+ fs.writeFileSync(filePath, JSON.stringify(existingState, null, 2));
146
+ }
147
+
148
+ /**
149
+ * Entity specific methods
150
+ */
151
+
152
+ // features
153
+ async listFeatures() {
154
+ return await this.listEntities("feature");
120
155
  }
121
156
 
122
157
  readFeature(featureKey: string) {
123
158
  return this.parseEntity<ParsedFeature>("feature", featureKey);
124
159
  }
125
160
 
126
- getRequiredFeaturesChain(featureKey: FeatureKey, chain = new Set<FeatureKey>()): Set<FeatureKey> {
161
+ async getRequiredFeaturesChain(
162
+ featureKey: FeatureKey,
163
+ chain = new Set<FeatureKey>(),
164
+ ): Promise<Set<FeatureKey>> {
127
165
  chain.add(featureKey);
128
166
 
129
167
  if (!this.entityExists("feature", featureKey)) {
130
168
  throw new Error(`Feature not found: ${featureKey}`);
131
169
  }
132
170
 
133
- const feature = this.readFeature(featureKey);
171
+ const feature = await this.readFeature(featureKey);
134
172
 
135
173
  if (!feature.required) {
136
174
  return chain;
137
175
  }
138
176
 
139
- feature.required.forEach((r) => {
177
+ for (const r of feature.required) {
140
178
  const requiredKey = typeof r === "string" ? r : r.key;
141
179
 
142
180
  if (chain.has(requiredKey)) {
143
181
  throw new Error(`Circular dependency detected: ${chain.toString()}`);
144
182
  }
145
183
 
146
- this.getRequiredFeaturesChain(requiredKey, chain);
147
- });
184
+ await this.getRequiredFeaturesChain(requiredKey, chain);
185
+ }
148
186
 
149
187
  return chain;
150
188
  }
151
189
 
152
190
  // segments
153
- listSegments(segmentsList?: string[]) {
154
- const segments = this.listEntities("segment");
155
-
156
- if (segmentsList) {
157
- return segments.filter((segment) => {
158
- return segmentsList.indexOf(segment) !== -1;
159
- });
160
- }
161
-
162
- return segments;
191
+ listSegments() {
192
+ return this.listEntities("segment");
163
193
  }
164
194
 
165
195
  readSegment(segmentKey: string) {
@@ -167,16 +197,8 @@ export class Datasource {
167
197
  }
168
198
 
169
199
  // attributes
170
- listAttributes(attributesList?: string[]) {
171
- const attributes = this.listEntities("attribute");
172
-
173
- if (attributesList) {
174
- return attributes.filter((segment) => {
175
- return attributesList.indexOf(segment) !== -1;
176
- });
177
- }
178
-
179
- return attributes;
200
+ listAttributes() {
201
+ return this.listEntities("attribute");
180
202
  }
181
203
 
182
204
  readAttribute(attributeKey: string) {
@@ -184,24 +206,8 @@ export class Datasource {
184
206
  }
185
207
 
186
208
  // groups
187
- listGroups(featuresList?: string[]) {
188
- const groups = this.listEntities("group");
189
-
190
- if (featuresList) {
191
- return groups.filter((group) => {
192
- const groupContent = this.parseEntity<Group>("group", group);
193
-
194
- return groupContent.slots.some((slot) => {
195
- if (!slot.feature) {
196
- return false;
197
- }
198
-
199
- return featuresList.indexOf(slot.feature) !== -1;
200
- });
201
- });
202
- }
203
-
204
- return groups;
209
+ async listGroups() {
210
+ return this.listEntities("group");
205
211
  }
206
212
 
207
213
  readGroup(groupKey: string) {
@@ -1,4 +1,4 @@
1
- import { parseYaml } from "../utils";
1
+ import * as YAML from "js-yaml";
2
2
 
3
3
  /**
4
4
  * If we want to add more built-in parsers,
@@ -8,7 +8,7 @@ import { parseYaml } from "../utils";
8
8
  export const parsers = {
9
9
  // extension => function
10
10
  yml(content: string) {
11
- return parseYaml(content);
11
+ return YAML.load(content);
12
12
  },
13
13
 
14
14
  json(content: string) {
@@ -4,17 +4,20 @@ import { SegmentKey } from "@featurevisor/types";
4
4
 
5
5
  import { Datasource } from "../datasource";
6
6
 
7
- export function findDuplicateSegments(datasource: Datasource): SegmentKey[][] {
8
- const segmentsWithHash = datasource.listSegments().map((segmentKey) => {
9
- const segment = datasource.readSegment(segmentKey);
7
+ export async function findDuplicateSegments(datasource: Datasource): Promise<SegmentKey[][]> {
8
+ const segments = await datasource.listSegments();
9
+
10
+ const segmentsWithHash: { segmentKey: SegmentKey; hash: string }[] = [];
11
+ for (const segmentKey of segments) {
12
+ const segment = await datasource.readSegment(segmentKey);
10
13
  const conditions = JSON.stringify(segment.conditions);
11
14
  const hash = crypto.createHash("sha256").update(conditions).digest("hex");
12
15
 
13
- return {
16
+ segmentsWithHash.push({
14
17
  segmentKey,
15
18
  hash,
16
- };
17
- });
19
+ });
20
+ }
18
21
 
19
22
  const groupedSegments: { [hash: string]: SegmentKey[] } = segmentsWithHash.reduce(
20
23
  (acc, { segmentKey, hash }) => {
@@ -3,10 +3,13 @@ import { ProjectConfig } from "../config";
3
3
 
4
4
  import { findDuplicateSegments } from "./findDuplicateSegments";
5
5
 
6
- export function findDuplicateSegmentsInProject(rootDirectoryPath, projectConfig: ProjectConfig) {
6
+ export async function findDuplicateSegmentsInProject(
7
+ rootDirectoryPath,
8
+ projectConfig: ProjectConfig,
9
+ ) {
7
10
  const datasource = new Datasource(projectConfig);
8
11
 
9
- const duplicates = findDuplicateSegments(datasource);
12
+ const duplicates = await findDuplicateSegments(datasource);
10
13
 
11
14
  if (duplicates.length === 0) {
12
15
  console.log("No duplicate segments found");
@@ -14,7 +14,7 @@ export interface GenerateCodeCLIOptions {
14
14
  outDir: string;
15
15
  }
16
16
 
17
- export function generateCodeForProject(
17
+ export async function generateCodeForProject(
18
18
  rootDirectoryPath,
19
19
  projectConfig: ProjectConfig,
20
20
  cliOptions: GenerateCodeCLIOptions,
@@ -47,7 +47,7 @@ export function generateCodeForProject(
47
47
  }
48
48
 
49
49
  if (cliOptions.language === "typescript") {
50
- return generateTypeScriptCodeForProject(
50
+ return await generateTypeScriptCodeForProject(
51
51
  rootDirectoryPath,
52
52
  projectConfig,
53
53
  datasource,
@@ -3,6 +3,7 @@ import * as path from "path";
3
3
 
4
4
  import { ProjectConfig } from "../config";
5
5
  import { Datasource } from "../datasource";
6
+ import { Attribute } from "@featurevisor/types";
6
7
 
7
8
  function convertFeaturevisorTypeToTypeScriptType(featurevisorType: string) {
8
9
  switch (featurevisorType) {
@@ -37,6 +38,16 @@ function getPascalCase(str) {
37
38
  return pascalCased;
38
39
  }
39
40
 
41
+ function getRelativePath(from, to) {
42
+ const relativePath = path.relative(from, to);
43
+
44
+ if (relativePath.startsWith("..")) {
45
+ return path.join(".", relativePath);
46
+ }
47
+
48
+ return relativePath;
49
+ }
50
+
40
51
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
41
52
  function getFeaturevisorTypeFromValue(value) {
42
53
  if (typeof value === "boolean") {
@@ -76,39 +87,37 @@ export function getInstance(): FeaturevisorInstance {
76
87
  }
77
88
  `.trimStart();
78
89
 
79
- export function generateTypeScriptCodeForProject(
90
+ export async function generateTypeScriptCodeForProject(
80
91
  rootDirectoryPath: string,
81
92
  projectConfig: ProjectConfig,
82
93
  datasource: Datasource,
83
94
  outputPath: string,
84
95
  ) {
85
- console.log("Generating TypeScript code...");
96
+ console.log("\nGenerating TypeScript code...\n");
86
97
 
87
98
  // instance
88
99
  const instanceFilePath = path.join(outputPath, "instance.ts");
89
100
  fs.writeFileSync(instanceFilePath, instanceSnippet);
90
- console.log(`Instance file written at: ${instanceFilePath}`);
101
+ console.log(`Instance file written at: ${getRelativePath(rootDirectoryPath, instanceFilePath)}`);
91
102
 
92
103
  // attributes
93
- const attributeFiles = datasource.listAttributes();
94
- const attributes = attributeFiles
95
- .map((attributeKey) => {
96
- const parsedAttribute = datasource.readAttribute(attributeKey);
97
-
98
- return {
99
- archived: parsedAttribute.archived,
100
- key: attributeKey,
101
- type: parsedAttribute.type,
102
- typescriptType: convertFeaturevisorTypeToTypeScriptType(parsedAttribute.type),
103
- };
104
- })
105
- .filter((attribute) => {
106
- if (typeof attribute.archived === "undefined") {
107
- return true;
108
- }
104
+ const attributeFiles = await datasource.listAttributes();
105
+ const attributes: (Attribute & { typescriptType })[] = [];
109
106
 
110
- return !attribute.archived;
107
+ for (const attributeKey of attributeFiles) {
108
+ const parsedAttribute = await datasource.readAttribute(attributeKey);
109
+
110
+ if (typeof parsedAttribute.archived === "undefined") {
111
+ continue;
112
+ }
113
+
114
+ attributes.push({
115
+ archived: parsedAttribute.archived,
116
+ key: attributeKey,
117
+ type: parsedAttribute.type,
118
+ typescriptType: convertFeaturevisorTypeToTypeScriptType(parsedAttribute.type),
111
119
  });
120
+ }
112
121
 
113
122
  // context
114
123
  const attributeProperties = attributes
@@ -127,13 +136,16 @@ ${attributeProperties}
127
136
 
128
137
  const contextTypeFilePath = path.join(outputPath, "Context.ts");
129
138
  fs.writeFileSync(contextTypeFilePath, contextContent);
130
- console.log(`Context type file written at: ${contextTypeFilePath}`);
139
+ console.log(
140
+ `Context type file written at: ${getRelativePath(rootDirectoryPath, contextTypeFilePath)}`,
141
+ );
131
142
 
132
143
  // features
133
144
  const featureNamespaces: string[] = [];
134
- const featureFiles = datasource.listFeatures();
145
+ const featureFiles = await datasource.listFeatures();
146
+
135
147
  for (const featureKey of featureFiles) {
136
- const parsedFeature = datasource.readFeature(featureKey);
148
+ const parsedFeature = await datasource.readFeature(featureKey);
137
149
 
138
150
  if (typeof parsedFeature.archived !== "undefined" && parsedFeature.archived) {
139
151
  continue;
@@ -188,7 +200,12 @@ export namespace ${namespaceValue} {
188
200
 
189
201
  const featureNamespaceFilePath = path.join(outputPath, `${namespaceValue}.ts`);
190
202
  fs.writeFileSync(featureNamespaceFilePath, featureContent);
191
- console.log(`Feature ${featureKey} file written at: ${featureNamespaceFilePath}`);
203
+ console.log(
204
+ `Feature ${featureKey} file written at: ${getRelativePath(
205
+ rootDirectoryPath,
206
+ featureNamespaceFilePath,
207
+ )}`,
208
+ );
192
209
  }
193
210
 
194
211
  // index
@@ -202,5 +219,5 @@ export namespace ${namespaceValue} {
202
219
  .join("\n") + "\n";
203
220
  const indexFilePath = path.join(outputPath, "index.ts");
204
221
  fs.writeFileSync(indexFilePath, indexContent);
205
- console.log(`Index file written at: ${indexFilePath}`);
222
+ console.log(`Index file written at: ${getRelativePath(rootDirectoryPath, indexFilePath)}`);
206
223
  }
@@ -2,7 +2,7 @@ import { FeatureKey, Required } from "@featurevisor/types";
2
2
 
3
3
  import { Datasource } from "../datasource";
4
4
 
5
- export function checkForCircularDependencyInRequired(
5
+ export async function checkForCircularDependencyInRequired(
6
6
  datasource: Datasource,
7
7
  featureKey: FeatureKey,
8
8
  required?: Required[],
@@ -25,16 +25,16 @@ export function checkForCircularDependencyInRequired(
25
25
  throw new Error(`circular dependency found: ${chain.join(" -> ")}`);
26
26
  }
27
27
 
28
- const requiredFeatureExists = datasource.entityExists("feature", requiredKey);
28
+ const requiredFeatureExists = await datasource.entityExists("feature", requiredKey);
29
29
 
30
30
  if (!requiredFeatureExists) {
31
31
  throw new Error(`required feature "${requiredKey}" not found`);
32
32
  }
33
33
 
34
- const requiredParsedFeature = datasource.readFeature(requiredKey);
34
+ const requiredParsedFeature = await datasource.readFeature(requiredKey);
35
35
 
36
36
  if (requiredParsedFeature.required) {
37
- checkForCircularDependencyInRequired(
37
+ await checkForCircularDependencyInRequired(
38
38
  datasource,
39
39
  featureKey,
40
40
  requiredParsedFeature.required,
@@ -17,7 +17,7 @@ export function getGroupJoiSchema(
17
17
  percentage: Joi.number().precision(3).min(0).max(100),
18
18
  }),
19
19
  )
20
- .custom(function (value) {
20
+ .custom(async function (value) {
21
21
  const totalPercentage = value.reduce((acc, slot) => acc + slot.percentage, 0);
22
22
 
23
23
  if (totalPercentage !== 100) {
@@ -29,13 +29,13 @@ export function getGroupJoiSchema(
29
29
 
30
30
  if (slot.feature) {
31
31
  const featureKey = slot.feature;
32
- const featureExists = datasource.entityExists("feature", featureKey);
32
+ const featureExists = availableFeatureKeys.indexOf(featureKey) > -1;
33
33
 
34
34
  if (!featureExists) {
35
35
  throw new Error(`feature ${featureKey} not found`);
36
36
  }
37
37
 
38
- const parsedFeature = datasource.readFeature(featureKey);
38
+ const parsedFeature = await datasource.readFeature(featureKey);
39
39
 
40
40
  const environmentKeys = Object.keys(parsedFeature.environments);
41
41
  for (const environmentKey of environmentKeys) {