@featurevisor/core 0.53.1 → 0.53.3

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 (81) 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 -1
  15. package/lib/builder/buildProject.js +99 -40
  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 +16 -16
  21. package/lib/datasource/datasource.js +123 -67
  22. package/lib/datasource/datasource.js.map +1 -1
  23. package/lib/find-duplicate-segments/findDuplicateSegments.d.ts +1 -1
  24. package/lib/find-duplicate-segments/findDuplicateSegments.js +75 -18
  25. package/lib/find-duplicate-segments/findDuplicateSegments.js.map +1 -1
  26. package/lib/find-duplicate-segments/index.d.ts +1 -1
  27. package/lib/find-duplicate-segments/index.js +56 -9
  28. package/lib/find-duplicate-segments/index.js.map +1 -1
  29. package/lib/generate-code/index.d.ts +1 -1
  30. package/lib/generate-code/index.js +67 -23
  31. package/lib/generate-code/index.js.map +1 -1
  32. package/lib/generate-code/typescript.d.ts +1 -1
  33. package/lib/generate-code/typescript.js +139 -72
  34. package/lib/generate-code/typescript.js.map +1 -1
  35. package/lib/linter/checkCircularDependency.d.ts +1 -1
  36. package/lib/linter/checkCircularDependency.js +78 -22
  37. package/lib/linter/checkCircularDependency.js.map +1 -1
  38. package/lib/linter/groupSchema.js +79 -28
  39. package/lib/linter/groupSchema.js.map +1 -1
  40. package/lib/linter/lintProject.js +122 -101
  41. package/lib/linter/lintProject.js.map +1 -1
  42. package/lib/restore.d.ts +1 -1
  43. package/lib/restore.js +53 -11
  44. package/lib/restore.js.map +1 -1
  45. package/lib/site/exportSite.d.ts +1 -1
  46. package/lib/site/exportSite.js +64 -21
  47. package/lib/site/exportSite.js.map +1 -1
  48. package/lib/site/generateSiteSearchIndex.d.ts +1 -1
  49. package/lib/site/generateSiteSearchIndex.js +203 -111
  50. package/lib/site/generateSiteSearchIndex.js.map +1 -1
  51. package/lib/tester/cliFormat.d.ts +3 -0
  52. package/lib/tester/cliFormat.js +7 -0
  53. package/lib/tester/cliFormat.js.map +1 -0
  54. package/lib/tester/testFeature.d.ts +1 -1
  55. package/lib/tester/testFeature.js +128 -59
  56. package/lib/tester/testFeature.js.map +1 -1
  57. package/lib/tester/testProject.d.ts +1 -1
  58. package/lib/tester/testProject.js +106 -39
  59. package/lib/tester/testProject.js.map +1 -1
  60. package/lib/tester/testSegment.d.ts +1 -1
  61. package/lib/tester/testSegment.js +70 -21
  62. package/lib/tester/testSegment.js.map +1 -1
  63. package/package.json +2 -2
  64. package/src/builder/buildDatafile.ts +9 -9
  65. package/src/builder/buildProject.ts +2 -2
  66. package/src/builder/getFeatureRanges.ts +4 -4
  67. package/src/datasource/datasource.ts +22 -69
  68. package/src/find-duplicate-segments/findDuplicateSegments.ts +9 -6
  69. package/src/find-duplicate-segments/index.ts +5 -2
  70. package/src/generate-code/index.ts +2 -2
  71. package/src/generate-code/typescript.ts +42 -25
  72. package/src/linter/checkCircularDependency.ts +4 -4
  73. package/src/linter/groupSchema.ts +3 -3
  74. package/src/linter/lintProject.ts +39 -39
  75. package/src/restore.ts +1 -1
  76. package/src/site/exportSite.ts +2 -2
  77. package/src/site/generateSiteSearchIndex.ts +14 -14
  78. package/src/tester/cliFormat.ts +4 -0
  79. package/src/tester/testFeature.ts +17 -11
  80. package/src/tester/testProject.ts +20 -7
  81. package/src/tester/testSegment.ts +9 -7
@@ -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,15 +1,7 @@
1
1
  import * as path from "path";
2
2
  import * as fs from "fs";
3
3
 
4
- import {
5
- Tag,
6
- ParsedFeature,
7
- Segment,
8
- Attribute,
9
- Group,
10
- FeatureKey,
11
- Test,
12
- } from "@featurevisor/types";
4
+ import { ParsedFeature, Segment, Attribute, Group, FeatureKey, Test } from "@featurevisor/types";
13
5
 
14
6
  import { ProjectConfig } from "../config";
15
7
  import { parsers } from "./parsers";
@@ -53,7 +45,7 @@ export class Datasource {
53
45
  /**
54
46
  * Common methods
55
47
  */
56
- listEntities(entityType: EntityType): string[] {
48
+ async listEntities(entityType: EntityType): Promise<string[]> {
57
49
  const directoryPath = this.getEntityDirectoryPath(entityType);
58
50
 
59
51
  return fs
@@ -82,20 +74,20 @@ export class Datasource {
82
74
  return path.join(basePath, `${entityKey}.${this.extension}`);
83
75
  }
84
76
 
85
- entityExists(entityType: EntityType, entityKey: string): boolean {
77
+ async entityExists(entityType: EntityType, entityKey: string): Promise<boolean> {
86
78
  const entityPath = this.getEntityPath(entityType, entityKey);
87
79
 
88
80
  return fs.existsSync(entityPath);
89
81
  }
90
82
 
91
- readEntity(entityType: EntityType, entityKey: string): string {
83
+ async readEntity(entityType: EntityType, entityKey: string): Promise<string> {
92
84
  const filePath = this.getEntityPath(entityType, entityKey);
93
85
 
94
86
  return fs.readFileSync(filePath, "utf8");
95
87
  }
96
88
 
97
- parseEntity<T>(entityType: EntityType, entityKey: string): T {
98
- const entityContent = this.readEntity(entityType, entityKey);
89
+ async parseEntity<T>(entityType: EntityType, entityKey: string): Promise<T> {
90
+ const entityContent = await this.readEntity(entityType, entityKey);
99
91
 
100
92
  return this.parse(entityContent) as T;
101
93
  }
@@ -105,61 +97,46 @@ export class Datasource {
105
97
  */
106
98
 
107
99
  // features
108
- listFeatures(tag?: Tag) {
109
- const features = this.listEntities("feature");
110
-
111
- if (tag) {
112
- return features.filter((feature) => {
113
- const featureContent = this.parseEntity<ParsedFeature>("feature", feature);
114
-
115
- return featureContent.tags.indexOf(tag) !== -1;
116
- });
117
- }
118
-
119
- return features;
100
+ async listFeatures() {
101
+ return await this.listEntities("feature");
120
102
  }
121
103
 
122
104
  readFeature(featureKey: string) {
123
105
  return this.parseEntity<ParsedFeature>("feature", featureKey);
124
106
  }
125
107
 
126
- getRequiredFeaturesChain(featureKey: FeatureKey, chain = new Set<FeatureKey>()): Set<FeatureKey> {
108
+ async getRequiredFeaturesChain(
109
+ featureKey: FeatureKey,
110
+ chain = new Set<FeatureKey>(),
111
+ ): Promise<Set<FeatureKey>> {
127
112
  chain.add(featureKey);
128
113
 
129
114
  if (!this.entityExists("feature", featureKey)) {
130
115
  throw new Error(`Feature not found: ${featureKey}`);
131
116
  }
132
117
 
133
- const feature = this.readFeature(featureKey);
118
+ const feature = await this.readFeature(featureKey);
134
119
 
135
120
  if (!feature.required) {
136
121
  return chain;
137
122
  }
138
123
 
139
- feature.required.forEach((r) => {
124
+ for (const r of feature.required) {
140
125
  const requiredKey = typeof r === "string" ? r : r.key;
141
126
 
142
127
  if (chain.has(requiredKey)) {
143
128
  throw new Error(`Circular dependency detected: ${chain.toString()}`);
144
129
  }
145
130
 
146
- this.getRequiredFeaturesChain(requiredKey, chain);
147
- });
131
+ await this.getRequiredFeaturesChain(requiredKey, chain);
132
+ }
148
133
 
149
134
  return chain;
150
135
  }
151
136
 
152
137
  // 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;
138
+ listSegments() {
139
+ return this.listEntities("segment");
163
140
  }
164
141
 
165
142
  readSegment(segmentKey: string) {
@@ -167,16 +144,8 @@ export class Datasource {
167
144
  }
168
145
 
169
146
  // 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;
147
+ listAttributes() {
148
+ return this.listEntities("attribute");
180
149
  }
181
150
 
182
151
  readAttribute(attributeKey: string) {
@@ -184,24 +153,8 @@ export class Datasource {
184
153
  }
185
154
 
186
155
  // 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;
156
+ async listGroups() {
157
+ return this.listEntities("group");
205
158
  }
206
159
 
207
160
  readGroup(groupKey: 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) {
@@ -25,13 +25,13 @@ export async function lintProject(projectConfig: ProjectConfig): Promise<boolean
25
25
  const availableFeatureKeys: string[] = [];
26
26
 
27
27
  // lint attributes
28
- const attributes = datasource.listAttributes();
28
+ const attributes = await datasource.listAttributes();
29
29
  console.log(`Linting ${attributes.length} attributes...\n`);
30
30
 
31
31
  const attributeJoiSchema = getAttributeJoiSchema();
32
32
 
33
33
  for (const key of attributes) {
34
- const parsed = datasource.readAttribute(key);
34
+ const parsed = await datasource.readAttribute(key);
35
35
  availableAttributeKeys.push(key);
36
36
 
37
37
  try {
@@ -50,14 +50,14 @@ export async function lintProject(projectConfig: ProjectConfig): Promise<boolean
50
50
  }
51
51
 
52
52
  // lint segments
53
- const segments = datasource.listSegments();
53
+ const segments = await datasource.listSegments();
54
54
  console.log(`\nLinting ${segments.length} segments...\n`);
55
55
 
56
56
  const conditionsJoiSchema = getConditionsJoiSchema(projectConfig, availableAttributeKeys);
57
57
  const segmentJoiSchema = getSegmentJoiSchema(projectConfig, conditionsJoiSchema);
58
58
 
59
59
  for (const key of segments) {
60
- const parsed = datasource.readSegment(key);
60
+ const parsed = await datasource.readSegment(key);
61
61
  availableSegmentKeys.push(key);
62
62
 
63
63
  try {
@@ -75,38 +75,8 @@ export async function lintProject(projectConfig: ProjectConfig): Promise<boolean
75
75
  }
76
76
  }
77
77
 
78
- // lint groups
79
-
80
- if (fs.existsSync(projectConfig.groupsDirectoryPath)) {
81
- const groups = datasource.listGroups();
82
- console.log(`\nLinting ${groups.length} groups...\n`);
83
-
84
- // @TODO: feature it slots can be from availableFeatureKeys only
85
- const groupJoiSchema = getGroupJoiSchema(projectConfig, datasource, availableFeatureKeys);
86
-
87
- for (const key of groups) {
88
- const parsed = datasource.readGroup(key);
89
-
90
- try {
91
- await groupJoiSchema.validateAsync(parsed);
92
- } catch (e) {
93
- console.log(" =>", key);
94
-
95
- if (e instanceof Joi.ValidationError) {
96
- printJoiError(e);
97
- } else {
98
- console.log(e);
99
- }
100
-
101
- hasError = true;
102
- }
103
- }
104
- }
105
-
106
- // @TODO: feature cannot exist in multiple groups
107
-
108
78
  // lint features
109
- const features = datasource.listFeatures();
79
+ const features = await datasource.listFeatures();
110
80
  console.log(`\nLinting ${features.length} features...\n`);
111
81
 
112
82
  const featureJoiSchema = getFeatureJoiSchema(
@@ -117,7 +87,7 @@ export async function lintProject(projectConfig: ProjectConfig): Promise<boolean
117
87
  );
118
88
 
119
89
  for (const key of features) {
120
- const parsed = datasource.readFeature(key);
90
+ const parsed = await datasource.readFeature(key);
121
91
  availableFeatureKeys.push(key);
122
92
 
123
93
  try {
@@ -136,7 +106,7 @@ export async function lintProject(projectConfig: ProjectConfig): Promise<boolean
136
106
 
137
107
  if (parsed.required) {
138
108
  try {
139
- checkForCircularDependencyInRequired(datasource, key, parsed.required);
109
+ await checkForCircularDependencyInRequired(datasource, key, parsed.required);
140
110
  } catch (e) {
141
111
  console.log(" =>", key);
142
112
  console.log(" => Error:", e.message);
@@ -145,9 +115,39 @@ export async function lintProject(projectConfig: ProjectConfig): Promise<boolean
145
115
  }
146
116
  }
147
117
 
118
+ // lint groups
119
+
120
+ if (fs.existsSync(projectConfig.groupsDirectoryPath)) {
121
+ const groups = await datasource.listGroups();
122
+ console.log(`\nLinting ${groups.length} groups...\n`);
123
+
124
+ // @TODO: feature it slots can be from availableFeatureKeys only
125
+ const groupJoiSchema = getGroupJoiSchema(projectConfig, datasource, availableFeatureKeys);
126
+
127
+ for (const key of groups) {
128
+ const parsed = await datasource.readGroup(key);
129
+
130
+ try {
131
+ await groupJoiSchema.validateAsync(parsed);
132
+ } catch (e) {
133
+ console.log(" =>", key);
134
+
135
+ if (e instanceof Joi.ValidationError) {
136
+ printJoiError(e);
137
+ } else {
138
+ console.log(e);
139
+ }
140
+
141
+ hasError = true;
142
+ }
143
+ }
144
+ }
145
+
146
+ // @TODO: feature cannot exist in multiple groups
147
+
148
148
  // lint tests
149
149
  if (fs.existsSync(projectConfig.testsDirectoryPath)) {
150
- const tests = datasource.listTests();
150
+ const tests = await datasource.listTests();
151
151
  console.log(`\nLinting ${tests.length} tests...\n`);
152
152
 
153
153
  const testsJoiSchema = getTestsJoiSchema(
@@ -157,7 +157,7 @@ export async function lintProject(projectConfig: ProjectConfig): Promise<boolean
157
157
  );
158
158
 
159
159
  for (const key of tests) {
160
- const parsed = datasource.readTest(key);
160
+ const parsed = await datasource.readTest(key);
161
161
 
162
162
  try {
163
163
  await testsJoiSchema.validateAsync(parsed);
package/src/restore.ts CHANGED
@@ -3,7 +3,7 @@ import { execSync } from "child_process";
3
3
 
4
4
  import { ProjectConfig } from "./config";
5
5
 
6
- export function restoreProject(rootDirectoryPath, projectConfig: ProjectConfig) {
6
+ export async function restoreProject(rootDirectoryPath, projectConfig: ProjectConfig) {
7
7
  const relativeStateDirPath = path.relative(rootDirectoryPath, projectConfig.stateDirectoryPath);
8
8
  const cmd = `git checkout -- ${relativeStateDirPath}${path.sep}`;
9
9
 
@@ -9,7 +9,7 @@ import { generateHistory } from "./generateHistory";
9
9
  import { getRepoDetails } from "./getRepoDetails";
10
10
  import { generateSiteSearchIndex } from "./generateSiteSearchIndex";
11
11
 
12
- export function exportSite(rootDirectoryPath: string, projectConfig: ProjectConfig) {
12
+ export async function exportSite(rootDirectoryPath: string, projectConfig: ProjectConfig) {
13
13
  const hasError = false;
14
14
 
15
15
  mkdirp.sync(projectConfig.siteExportDirectoryPath);
@@ -30,7 +30,7 @@ export function exportSite(rootDirectoryPath: string, projectConfig: ProjectConf
30
30
 
31
31
  // site search index
32
32
  const repoDetails = getRepoDetails();
33
- const searchIndex = generateSiteSearchIndex(
33
+ const searchIndex = await generateSiteSearchIndex(
34
34
  rootDirectoryPath,
35
35
  projectConfig,
36
36
  fullHistory,
@@ -15,12 +15,12 @@ import { getRelativePaths } from "./getRelativePaths";
15
15
  import { getLastModifiedFromHistory } from "./getLastModifiedFromHistory";
16
16
  import { RepoDetails } from "./getRepoDetails";
17
17
 
18
- export function generateSiteSearchIndex(
18
+ export async function generateSiteSearchIndex(
19
19
  rootDirectoryPath: string,
20
20
  projectConfig: ProjectConfig,
21
21
  fullHistory: HistoryEntry[],
22
22
  repoDetails: RepoDetails | undefined,
23
- ): SearchIndex {
23
+ ): Promise<SearchIndex> {
24
24
  const result: SearchIndex = {
25
25
  links: undefined,
26
26
  entities: {
@@ -77,9 +77,9 @@ export function generateSiteSearchIndex(
77
77
  } = {};
78
78
 
79
79
  // features
80
- const featureFiles = datasource.listFeatures();
81
- featureFiles.forEach((entityName) => {
82
- const parsed = datasource.readFeature(entityName);
80
+ const featureFiles = await datasource.listFeatures();
81
+ for (const entityName of featureFiles) {
82
+ const parsed = await datasource.readFeature(entityName);
83
83
 
84
84
  if (Array.isArray(parsed.variations)) {
85
85
  parsed.variations.forEach((variation) => {
@@ -160,12 +160,12 @@ export function generateSiteSearchIndex(
160
160
  key: entityName,
161
161
  lastModified: getLastModifiedFromHistory(fullHistory, "feature", entityName),
162
162
  });
163
- });
163
+ }
164
164
 
165
165
  // segments
166
- const segmentFiles = datasource.listSegments();
167
- segmentFiles.forEach((entityName) => {
168
- const parsed = datasource.readSegment(entityName);
166
+ const segmentFiles = await datasource.listSegments();
167
+ for (const entityName of segmentFiles) {
168
+ const parsed = await datasource.readSegment(entityName);
169
169
 
170
170
  extractAttributeKeysFromConditions(parsed.conditions as Condition | Condition[]).forEach(
171
171
  (attributeKey) => {
@@ -183,12 +183,12 @@ export function generateSiteSearchIndex(
183
183
  lastModified: getLastModifiedFromHistory(fullHistory, "segment", entityName),
184
184
  usedInFeatures: Array.from(segmentsUsedInFeatures[entityName] || []),
185
185
  });
186
- });
186
+ }
187
187
 
188
188
  // attributes
189
- const attributeFiles = datasource.listAttributes();
190
- attributeFiles.forEach((entityName) => {
191
- const parsed = datasource.readAttribute(entityName);
189
+ const attributeFiles = await datasource.listAttributes();
190
+ for (const entityName of attributeFiles) {
191
+ const parsed = await datasource.readAttribute(entityName);
192
192
 
193
193
  result.entities.attributes.push({
194
194
  ...parsed,
@@ -197,7 +197,7 @@ export function generateSiteSearchIndex(
197
197
  usedInFeatures: Array.from(attributesUsedInFeatures[entityName] || []),
198
198
  usedInSegments: Array.from(attributesUsedInSegments[entityName] || []),
199
199
  });
200
- });
200
+ }
201
201
 
202
202
  return result;
203
203
  }
@@ -0,0 +1,4 @@
1
+ export const CLI_FORMAT_RED = "\x1b[31m%s\x1b[0m";
2
+ export const CLI_FORMAT_GREEN = "\x1b[32m%s\x1b[0m";
3
+
4
+ export const CLI_FORMAT_BOLD = "\x1b[1m%s\x1b[0m";