@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.
- package/.eslintcache +1 -1
- package/CHANGELOG.md +16 -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 -2
- package/lib/builder/buildProject.js +95 -45
- 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 +23 -17
- package/lib/datasource/datasource.js +166 -69
- package/lib/datasource/datasource.js.map +1 -1
- package/lib/datasource/parsers.js +2 -2
- package/lib/datasource/parsers.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 +119 -103
- 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 +130 -60
- 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/lib/utils.d.ts +0 -2
- package/lib/utils.js +1 -15
- package/lib/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/builder/buildDatafile.ts +9 -9
- package/src/builder/buildProject.ts +4 -24
- package/src/builder/getFeatureRanges.ts +4 -4
- package/src/datasource/datasource.ts +66 -60
- package/src/datasource/parsers.ts +2 -2
- 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 +53 -60
- 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 +12 -13
- package/src/tester/testProject.ts +8 -5
- package/src/tester/testSegment.ts +3 -3
- 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
|
|
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
|
-
|
|
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
|
-
*
|
|
118
|
+
* State
|
|
105
119
|
*/
|
|
120
|
+
async readState(environment: EnvironmentKey): Promise<ExistingState> {
|
|
121
|
+
const filePath = getExistingStateFilePath(this.config, environment);
|
|
106
122
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
123
|
+
if (!fs.existsSync(filePath)) {
|
|
124
|
+
return {
|
|
125
|
+
features: {},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
110
128
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
129
|
+
return require(filePath);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async writeState(environment: EnvironmentKey, existingState: ExistingState) {
|
|
133
|
+
const filePath = getExistingStateFilePath(this.config, environment);
|
|
114
134
|
|
|
115
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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(
|
|
154
|
-
|
|
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(
|
|
171
|
-
|
|
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(
|
|
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;
|
|
209
|
+
async listGroups() {
|
|
210
|
+
return this.listEntities("group");
|
|
205
211
|
}
|
|
206
212
|
|
|
207
213
|
readGroup(groupKey: string) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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
|
|
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
|
|
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) {
|