@featurevisor/core 0.53.2 → 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.
- package/.eslintcache +1 -1
- package/CHANGELOG.md +8 -0
- package/coverage/clover.xml +2 -2
- package/coverage/lcov-report/index.html +1 -1
- package/coverage/lcov-report/lib/builder/allocator.js.html +1 -1
- package/coverage/lcov-report/lib/builder/index.html +1 -1
- package/coverage/lcov-report/lib/builder/traffic.js.html +1 -1
- package/coverage/lcov-report/src/builder/allocator.ts.html +1 -1
- package/coverage/lcov-report/src/builder/index.html +1 -1
- package/coverage/lcov-report/src/builder/traffic.ts.html +1 -1
- package/lib/builder/buildDatafile.d.ts +1 -1
- package/lib/builder/buildDatafile.js +241 -168
- package/lib/builder/buildDatafile.js.map +1 -1
- package/lib/builder/buildProject.d.ts +1 -1
- package/lib/builder/buildProject.js +99 -40
- package/lib/builder/buildProject.js.map +1 -1
- package/lib/builder/getFeatureRanges.d.ts +1 -1
- package/lib/builder/getFeatureRanges.js +92 -31
- package/lib/builder/getFeatureRanges.js.map +1 -1
- package/lib/datasource/datasource.d.ts +16 -16
- package/lib/datasource/datasource.js +123 -67
- package/lib/datasource/datasource.js.map +1 -1
- package/lib/find-duplicate-segments/findDuplicateSegments.d.ts +1 -1
- package/lib/find-duplicate-segments/findDuplicateSegments.js +75 -18
- package/lib/find-duplicate-segments/findDuplicateSegments.js.map +1 -1
- package/lib/find-duplicate-segments/index.d.ts +1 -1
- package/lib/find-duplicate-segments/index.js +56 -9
- package/lib/find-duplicate-segments/index.js.map +1 -1
- package/lib/generate-code/index.d.ts +1 -1
- package/lib/generate-code/index.js +67 -23
- package/lib/generate-code/index.js.map +1 -1
- package/lib/generate-code/typescript.d.ts +1 -1
- package/lib/generate-code/typescript.js +139 -72
- package/lib/generate-code/typescript.js.map +1 -1
- package/lib/linter/checkCircularDependency.d.ts +1 -1
- package/lib/linter/checkCircularDependency.js +78 -22
- package/lib/linter/checkCircularDependency.js.map +1 -1
- package/lib/linter/groupSchema.js +79 -28
- package/lib/linter/groupSchema.js.map +1 -1
- package/lib/linter/lintProject.js +122 -101
- package/lib/linter/lintProject.js.map +1 -1
- package/lib/restore.d.ts +1 -1
- package/lib/restore.js +53 -11
- package/lib/restore.js.map +1 -1
- package/lib/site/exportSite.d.ts +1 -1
- package/lib/site/exportSite.js +64 -21
- package/lib/site/exportSite.js.map +1 -1
- package/lib/site/generateSiteSearchIndex.d.ts +1 -1
- package/lib/site/generateSiteSearchIndex.js +203 -111
- package/lib/site/generateSiteSearchIndex.js.map +1 -1
- package/lib/tester/testFeature.d.ts +1 -1
- package/lib/tester/testFeature.js +127 -59
- package/lib/tester/testFeature.js.map +1 -1
- package/lib/tester/testProject.d.ts +1 -1
- package/lib/tester/testProject.js +105 -48
- package/lib/tester/testProject.js.map +1 -1
- package/lib/tester/testSegment.d.ts +1 -1
- package/lib/tester/testSegment.js +69 -21
- package/lib/tester/testSegment.js.map +1 -1
- package/package.json +2 -2
- package/src/builder/buildDatafile.ts +9 -9
- package/src/builder/buildProject.ts +2 -2
- package/src/builder/getFeatureRanges.ts +4 -4
- package/src/datasource/datasource.ts +22 -69
- package/src/find-duplicate-segments/findDuplicateSegments.ts +9 -6
- package/src/find-duplicate-segments/index.ts +5 -2
- package/src/generate-code/index.ts +2 -2
- package/src/generate-code/typescript.ts +42 -25
- package/src/linter/checkCircularDependency.ts +4 -4
- package/src/linter/groupSchema.ts +3 -3
- package/src/linter/lintProject.ts +39 -39
- package/src/restore.ts +1 -1
- package/src/site/exportSite.ts +2 -2
- package/src/site/generateSiteSearchIndex.ts +14 -14
- package/src/tester/testFeature.ts +8 -6
- package/src/tester/testProject.ts +8 -5
- package/src/tester/testSegment.ts +3 -3
|
@@ -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(
|
|
109
|
-
|
|
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(
|
|
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
|
|
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(
|
|
154
|
-
|
|
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(
|
|
171
|
-
|
|
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(
|
|
188
|
-
|
|
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
|
|
9
|
-
|
|
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
|
-
|
|
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(
|
|
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("
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
package/src/site/exportSite.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|