@featurevisor/core 0.51.0 → 0.51.1
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/allocator.js.html +1 -1
- package/coverage/lcov-report/lib/index.html +1 -1
- package/coverage/lcov-report/lib/traffic.js.html +1 -1
- package/coverage/lcov-report/src/allocator.ts.html +1 -1
- package/coverage/lcov-report/src/index.html +1 -1
- package/coverage/lcov-report/src/traffic.ts.html +1 -1
- package/lib/linter/attributeSchema.d.ts +2 -0
- package/lib/linter/attributeSchema.js +15 -0
- package/lib/linter/attributeSchema.js.map +1 -0
- package/lib/linter/checkCircularDependency.d.ts +3 -0
- package/lib/linter/checkCircularDependency.js +30 -0
- package/lib/linter/checkCircularDependency.js.map +1 -0
- package/lib/linter/conditionSchema.d.ts +3 -0
- package/lib/linter/conditionSchema.js +44 -0
- package/lib/linter/conditionSchema.js.map +1 -0
- package/lib/linter/featureSchema.d.ts +3 -0
- package/lib/linter/featureSchema.js +147 -0
- package/lib/linter/featureSchema.js.map +1 -0
- package/lib/linter/groupSchema.d.ts +4 -0
- package/lib/linter/groupSchema.js +51 -0
- package/lib/linter/groupSchema.js.map +1 -0
- package/lib/linter/index.d.ts +2 -0
- package/lib/linter/index.js +241 -0
- package/lib/linter/index.js.map +1 -0
- package/lib/linter/printJoiError.d.ts +2 -0
- package/lib/linter/printJoiError.js +14 -0
- package/lib/linter/printJoiError.js.map +1 -0
- package/lib/linter/segmentSchema.d.ts +3 -0
- package/lib/linter/segmentSchema.js +14 -0
- package/lib/linter/segmentSchema.js.map +1 -0
- package/lib/linter/testSchema.d.ts +3 -0
- package/lib/linter/testSchema.js +33 -0
- package/lib/linter/testSchema.js.map +1 -0
- package/package.json +2 -2
- package/src/linter/attributeSchema.ts +12 -0
- package/src/linter/checkCircularDependency.ts +45 -0
- package/src/linter/conditionSchema.ts +85 -0
- package/src/linter/featureSchema.ts +205 -0
- package/src/linter/groupSchema.ts +63 -0
- package/src/linter/index.ts +179 -0
- package/src/linter/printJoiError.ts +11 -0
- package/src/linter/segmentSchema.ts +13 -0
- package/src/linter/testSchema.ts +43 -0
- package/lib/linter.d.ts +0 -11
- package/lib/linter.js +0 -542
- package/lib/linter.js.map +0 -1
- package/src/linter.ts +0 -627
package/src/linter.ts
DELETED
|
@@ -1,627 +0,0 @@
|
|
|
1
|
-
// for use in node only
|
|
2
|
-
import * as fs from "fs";
|
|
3
|
-
|
|
4
|
-
import * as Joi from "joi";
|
|
5
|
-
|
|
6
|
-
import { Datasource } from "./datasource/datasource";
|
|
7
|
-
|
|
8
|
-
import { ProjectConfig } from "./config";
|
|
9
|
-
import { FeatureKey, Required } from "@featurevisor/types";
|
|
10
|
-
|
|
11
|
-
export function getAttributeJoiSchema() {
|
|
12
|
-
const attributeJoiSchema = Joi.object({
|
|
13
|
-
archived: Joi.boolean(),
|
|
14
|
-
type: Joi.string().allow("boolean", "string", "integer", "double", "date").required(),
|
|
15
|
-
description: Joi.string().required(),
|
|
16
|
-
capture: Joi.boolean(),
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
return attributeJoiSchema;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function getConditionsJoiSchema(
|
|
23
|
-
projectConfig: ProjectConfig,
|
|
24
|
-
availableAttributeKeys: string[],
|
|
25
|
-
) {
|
|
26
|
-
const plainConditionJoiSchema = Joi.object({
|
|
27
|
-
attribute: Joi.string()
|
|
28
|
-
.valid(...availableAttributeKeys)
|
|
29
|
-
.required(),
|
|
30
|
-
operator: Joi.string()
|
|
31
|
-
.valid(
|
|
32
|
-
"equals",
|
|
33
|
-
"notEquals",
|
|
34
|
-
|
|
35
|
-
// numeric
|
|
36
|
-
"greaterThan",
|
|
37
|
-
"greaterThanOrEquals",
|
|
38
|
-
"lessThan",
|
|
39
|
-
"lessThanOrEquals",
|
|
40
|
-
|
|
41
|
-
// string
|
|
42
|
-
"contains",
|
|
43
|
-
"notContains",
|
|
44
|
-
"startsWith",
|
|
45
|
-
"endsWith",
|
|
46
|
-
|
|
47
|
-
// semver (string)
|
|
48
|
-
"semverEquals",
|
|
49
|
-
"semverNotEquals",
|
|
50
|
-
"semverGreaterThan",
|
|
51
|
-
"semverGreaterThanOrEquals",
|
|
52
|
-
"semverLessThan",
|
|
53
|
-
"semverLessThanOrEquals",
|
|
54
|
-
|
|
55
|
-
// date comparisons
|
|
56
|
-
"before",
|
|
57
|
-
"after",
|
|
58
|
-
|
|
59
|
-
// array of strings
|
|
60
|
-
"in",
|
|
61
|
-
"notIn",
|
|
62
|
-
)
|
|
63
|
-
.required(),
|
|
64
|
-
value: Joi.alternatives()
|
|
65
|
-
.try(
|
|
66
|
-
// @TODO: make them more specific
|
|
67
|
-
Joi.string(),
|
|
68
|
-
Joi.number(),
|
|
69
|
-
Joi.boolean(),
|
|
70
|
-
Joi.date(),
|
|
71
|
-
Joi.array().items(Joi.string()),
|
|
72
|
-
)
|
|
73
|
-
.required(),
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const andOrNotConditionJoiSchema = Joi.alternatives()
|
|
77
|
-
.try(
|
|
78
|
-
Joi.object({
|
|
79
|
-
and: Joi.array().items(Joi.link("#andOrNotCondition"), plainConditionJoiSchema),
|
|
80
|
-
}),
|
|
81
|
-
Joi.object({
|
|
82
|
-
or: Joi.array().items(Joi.link("#andOrNotCondition"), plainConditionJoiSchema),
|
|
83
|
-
}),
|
|
84
|
-
Joi.object({
|
|
85
|
-
// @TODO: allow plainConditionJoiSchema as well?
|
|
86
|
-
not: Joi.array().items(Joi.link("#andOrNotCondition"), plainConditionJoiSchema),
|
|
87
|
-
}),
|
|
88
|
-
)
|
|
89
|
-
.id("andOrNotCondition");
|
|
90
|
-
|
|
91
|
-
const conditionJoiSchema = Joi.alternatives().try(
|
|
92
|
-
andOrNotConditionJoiSchema,
|
|
93
|
-
plainConditionJoiSchema,
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
const conditionsJoiSchema = Joi.alternatives().try(
|
|
97
|
-
conditionJoiSchema,
|
|
98
|
-
Joi.array().items(conditionJoiSchema),
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
return conditionsJoiSchema;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function getSegmentJoiSchema(projectConfig: ProjectConfig, conditionsJoiSchema) {
|
|
105
|
-
const segmentJoiSchema = Joi.object({
|
|
106
|
-
archived: Joi.boolean().optional(),
|
|
107
|
-
description: Joi.string().required(),
|
|
108
|
-
conditions: conditionsJoiSchema.required(),
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
return segmentJoiSchema;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function getGroupJoiSchema(
|
|
115
|
-
projectConfig: ProjectConfig,
|
|
116
|
-
datasource: Datasource,
|
|
117
|
-
availableFeatureKeys: string[],
|
|
118
|
-
) {
|
|
119
|
-
const groupJoiSchema = Joi.object({
|
|
120
|
-
description: Joi.string().required(),
|
|
121
|
-
slots: Joi.array()
|
|
122
|
-
.items(
|
|
123
|
-
Joi.object({
|
|
124
|
-
feature: Joi.string().valid(...availableFeatureKeys),
|
|
125
|
-
percentage: Joi.number().precision(3).min(0).max(100),
|
|
126
|
-
}),
|
|
127
|
-
)
|
|
128
|
-
.custom(function (value) {
|
|
129
|
-
const totalPercentage = value.reduce((acc, slot) => acc + slot.percentage, 0);
|
|
130
|
-
|
|
131
|
-
if (totalPercentage !== 100) {
|
|
132
|
-
throw new Error("total percentage is not 100");
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
for (const slot of value) {
|
|
136
|
-
const maxPercentageForRule = slot.percentage;
|
|
137
|
-
|
|
138
|
-
if (slot.feature) {
|
|
139
|
-
const featureKey = slot.feature;
|
|
140
|
-
const featureExists = datasource.entityExists("feature", featureKey);
|
|
141
|
-
|
|
142
|
-
if (!featureExists) {
|
|
143
|
-
throw new Error(`feature ${featureKey} not found`);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const parsedFeature = datasource.readFeature(featureKey);
|
|
147
|
-
|
|
148
|
-
const environmentKeys = Object.keys(parsedFeature.environments);
|
|
149
|
-
for (const environmentKey of environmentKeys) {
|
|
150
|
-
const environment = parsedFeature.environments[environmentKey];
|
|
151
|
-
const rules = environment.rules;
|
|
152
|
-
|
|
153
|
-
for (const rule of rules) {
|
|
154
|
-
if (rule.percentage > maxPercentageForRule) {
|
|
155
|
-
// @TODO: this does not help with same feature belonging to multiple slots. fix that.
|
|
156
|
-
throw new Error(
|
|
157
|
-
`Feature ${featureKey}'s rule ${rule.key} in ${environmentKey} has a percentage of ${rule.percentage} which is greater than the maximum percentage of ${maxPercentageForRule} for the slot`,
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return value;
|
|
166
|
-
})
|
|
167
|
-
.required(),
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
return groupJoiSchema;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const tagRegex = /^[a-z0-9-]+$/;
|
|
174
|
-
|
|
175
|
-
export function getFeatureJoiSchema(
|
|
176
|
-
projectConfig: ProjectConfig,
|
|
177
|
-
conditionsJoiSchema,
|
|
178
|
-
availableSegmentKeys: string[],
|
|
179
|
-
availableFeatureKeys: string[],
|
|
180
|
-
) {
|
|
181
|
-
const variationValueJoiSchema = Joi.string().required();
|
|
182
|
-
const variableValueJoiSchema = Joi.alternatives()
|
|
183
|
-
.try(
|
|
184
|
-
// @TODO: make it stricter based on variableSchema.type
|
|
185
|
-
Joi.string(),
|
|
186
|
-
Joi.number(),
|
|
187
|
-
Joi.boolean(),
|
|
188
|
-
Joi.array().items(Joi.string()),
|
|
189
|
-
Joi.object().custom(function (value) {
|
|
190
|
-
let isFlat = true;
|
|
191
|
-
|
|
192
|
-
Object.keys(value).forEach((key) => {
|
|
193
|
-
if (typeof value[key] === "object") {
|
|
194
|
-
isFlat = false;
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
if (!isFlat) {
|
|
199
|
-
throw new Error("object is not flat");
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return value;
|
|
203
|
-
}),
|
|
204
|
-
)
|
|
205
|
-
.allow("");
|
|
206
|
-
|
|
207
|
-
const plainGroupSegment = Joi.string().valid("*", ...availableSegmentKeys);
|
|
208
|
-
|
|
209
|
-
const andOrNotGroupSegment = Joi.alternatives()
|
|
210
|
-
.try(
|
|
211
|
-
Joi.object({
|
|
212
|
-
and: Joi.array().items(Joi.link("#andOrNotGroupSegment"), plainGroupSegment),
|
|
213
|
-
}),
|
|
214
|
-
Joi.object({
|
|
215
|
-
or: Joi.array().items(Joi.link("#andOrNotGroupSegment"), plainGroupSegment),
|
|
216
|
-
}),
|
|
217
|
-
Joi.object({
|
|
218
|
-
// @TODO: allow plainGroupSegment as well?
|
|
219
|
-
not: Joi.array().items(Joi.link("#andOrNotGroupSegment"), plainGroupSegment),
|
|
220
|
-
}),
|
|
221
|
-
)
|
|
222
|
-
.id("andOrNotGroupSegment");
|
|
223
|
-
|
|
224
|
-
const groupSegment = Joi.alternatives().try(andOrNotGroupSegment, plainGroupSegment);
|
|
225
|
-
|
|
226
|
-
const groupSegmentsJoiSchema = Joi.alternatives().try(
|
|
227
|
-
Joi.array().items(groupSegment),
|
|
228
|
-
groupSegment,
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
const environmentJoiSchema = Joi.object({
|
|
232
|
-
expose: Joi.boolean(),
|
|
233
|
-
rules: Joi.array()
|
|
234
|
-
.items(
|
|
235
|
-
Joi.object({
|
|
236
|
-
key: Joi.string(),
|
|
237
|
-
segments: groupSegmentsJoiSchema,
|
|
238
|
-
percentage: Joi.number().precision(3).min(0).max(100),
|
|
239
|
-
|
|
240
|
-
enabled: Joi.boolean().optional(),
|
|
241
|
-
variation: variationValueJoiSchema.optional(), // @TODO: only allowed if feature.variations is present
|
|
242
|
-
variables: Joi.object().optional(), // @TODO: make it stricter
|
|
243
|
-
}),
|
|
244
|
-
)
|
|
245
|
-
.unique("key")
|
|
246
|
-
.required(),
|
|
247
|
-
force: Joi.array().items(
|
|
248
|
-
Joi.object({
|
|
249
|
-
// @TODO: either of the two below
|
|
250
|
-
segments: groupSegmentsJoiSchema.optional(),
|
|
251
|
-
conditions: conditionsJoiSchema.optional(),
|
|
252
|
-
|
|
253
|
-
enabled: Joi.boolean().optional(),
|
|
254
|
-
variation: variationValueJoiSchema.optional(),
|
|
255
|
-
variables: Joi.object().optional(), // @TODO: make it stricter
|
|
256
|
-
}),
|
|
257
|
-
),
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
const allEnvironmentsSchema = {};
|
|
261
|
-
projectConfig.environments.forEach((environmentKey) => {
|
|
262
|
-
allEnvironmentsSchema[environmentKey] = environmentJoiSchema.required();
|
|
263
|
-
});
|
|
264
|
-
const allEnvironmentsJoiSchema = Joi.object(allEnvironmentsSchema);
|
|
265
|
-
|
|
266
|
-
const featureJoiSchema = Joi.object({
|
|
267
|
-
archived: Joi.boolean().optional(),
|
|
268
|
-
deprecated: Joi.boolean().optional(),
|
|
269
|
-
description: Joi.string().required(),
|
|
270
|
-
tags: Joi.array()
|
|
271
|
-
.items(
|
|
272
|
-
Joi.string().custom((value) => {
|
|
273
|
-
if (!tagRegex.test(value)) {
|
|
274
|
-
throw new Error("tag must be lower cased and alphanumeric, and may contain hyphens.");
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return value;
|
|
278
|
-
}),
|
|
279
|
-
)
|
|
280
|
-
.required(),
|
|
281
|
-
|
|
282
|
-
required: Joi.array()
|
|
283
|
-
.items(
|
|
284
|
-
Joi.alternatives().try(
|
|
285
|
-
Joi.string()
|
|
286
|
-
.required()
|
|
287
|
-
.valid(...availableFeatureKeys),
|
|
288
|
-
Joi.object({
|
|
289
|
-
key: Joi.string()
|
|
290
|
-
.required()
|
|
291
|
-
.valid(...availableFeatureKeys),
|
|
292
|
-
variation: Joi.string().optional(), // @TODO: can be made stricter
|
|
293
|
-
}),
|
|
294
|
-
),
|
|
295
|
-
)
|
|
296
|
-
.optional(),
|
|
297
|
-
|
|
298
|
-
bucketBy: Joi.alternatives()
|
|
299
|
-
.try(
|
|
300
|
-
// plain
|
|
301
|
-
Joi.string(),
|
|
302
|
-
|
|
303
|
-
// and
|
|
304
|
-
Joi.array().items(Joi.string()),
|
|
305
|
-
|
|
306
|
-
// or
|
|
307
|
-
Joi.object({
|
|
308
|
-
or: Joi.array().items(Joi.string()),
|
|
309
|
-
}),
|
|
310
|
-
)
|
|
311
|
-
.required(),
|
|
312
|
-
|
|
313
|
-
variablesSchema: Joi.array()
|
|
314
|
-
.items(
|
|
315
|
-
Joi.object({
|
|
316
|
-
key: Joi.string().disallow("variation"),
|
|
317
|
-
type: Joi.string().valid(
|
|
318
|
-
"string",
|
|
319
|
-
"integer",
|
|
320
|
-
"boolean",
|
|
321
|
-
"double",
|
|
322
|
-
"array",
|
|
323
|
-
"object",
|
|
324
|
-
"json",
|
|
325
|
-
),
|
|
326
|
-
description: Joi.string().optional(),
|
|
327
|
-
defaultValue: variableValueJoiSchema, // @TODO: make it stricter based on `type`
|
|
328
|
-
}),
|
|
329
|
-
)
|
|
330
|
-
.unique("key"),
|
|
331
|
-
|
|
332
|
-
variations: Joi.array()
|
|
333
|
-
.items(
|
|
334
|
-
Joi.object({
|
|
335
|
-
description: Joi.string(),
|
|
336
|
-
value: variationValueJoiSchema.required(),
|
|
337
|
-
weight: Joi.number().precision(3).min(0).max(100).required(),
|
|
338
|
-
variables: Joi.array()
|
|
339
|
-
.items(
|
|
340
|
-
Joi.object({
|
|
341
|
-
key: Joi.string(),
|
|
342
|
-
value: variableValueJoiSchema,
|
|
343
|
-
overrides: Joi.array().items(
|
|
344
|
-
Joi.object({
|
|
345
|
-
// @TODO: either segments or conditions prsent at a time
|
|
346
|
-
segments: groupSegmentsJoiSchema,
|
|
347
|
-
conditions: conditionsJoiSchema,
|
|
348
|
-
|
|
349
|
-
// @TODO: make it stricter based on `type`
|
|
350
|
-
value: variableValueJoiSchema,
|
|
351
|
-
}),
|
|
352
|
-
),
|
|
353
|
-
}),
|
|
354
|
-
)
|
|
355
|
-
.unique("key"),
|
|
356
|
-
}),
|
|
357
|
-
)
|
|
358
|
-
.custom((value) => {
|
|
359
|
-
const total = value.reduce((acc, v) => acc + v.weight, 0);
|
|
360
|
-
|
|
361
|
-
if (total !== 100) {
|
|
362
|
-
throw new Error(`Sum of all variation weights must be 100, got ${total}`);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return value;
|
|
366
|
-
})
|
|
367
|
-
.optional(),
|
|
368
|
-
|
|
369
|
-
environments: allEnvironmentsJoiSchema.required(),
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
return featureJoiSchema;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
export function getTestsJoiSchema(
|
|
376
|
-
projectConfig: ProjectConfig,
|
|
377
|
-
availableFeatureKeys: string[],
|
|
378
|
-
availableSegmentKeys: string[],
|
|
379
|
-
) {
|
|
380
|
-
const segmentTestJoiSchema = Joi.object({
|
|
381
|
-
segment: Joi.string()
|
|
382
|
-
.valid(...availableSegmentKeys)
|
|
383
|
-
.required(),
|
|
384
|
-
assertions: Joi.array().items(
|
|
385
|
-
Joi.object({
|
|
386
|
-
description: Joi.string().optional(),
|
|
387
|
-
context: Joi.object(),
|
|
388
|
-
expectedToMatch: Joi.boolean(),
|
|
389
|
-
}),
|
|
390
|
-
),
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
const featureTestJoiSchema = Joi.object({
|
|
394
|
-
feature: Joi.string()
|
|
395
|
-
.valid(...availableFeatureKeys)
|
|
396
|
-
.required(),
|
|
397
|
-
assertions: Joi.array().items(
|
|
398
|
-
Joi.object({
|
|
399
|
-
description: Joi.string().optional(),
|
|
400
|
-
at: Joi.number().precision(3).min(0).max(100),
|
|
401
|
-
environment: Joi.string().valid(...projectConfig.environments),
|
|
402
|
-
context: Joi.object(),
|
|
403
|
-
|
|
404
|
-
// @TODO: one or all below
|
|
405
|
-
expectedToBeEnabled: Joi.boolean().required(),
|
|
406
|
-
expectedVariation: Joi.alternatives().try(Joi.string(), Joi.number(), Joi.boolean()),
|
|
407
|
-
expectedVariables: Joi.object(),
|
|
408
|
-
}),
|
|
409
|
-
),
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
return Joi.alternatives().try(segmentTestJoiSchema, featureTestJoiSchema);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
export function printJoiError(e: Joi.ValidationError) {
|
|
416
|
-
const { details } = e;
|
|
417
|
-
|
|
418
|
-
details.forEach((detail) => {
|
|
419
|
-
console.error(" => Error:", detail.message);
|
|
420
|
-
console.error(" => Path:", detail.path.join("."));
|
|
421
|
-
console.error(" => Value:", detail.context?.value);
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
function checkForCircularDependencyInRequired(
|
|
426
|
-
datasource: Datasource,
|
|
427
|
-
featureKey: FeatureKey,
|
|
428
|
-
required?: Required[],
|
|
429
|
-
chain: FeatureKey[] = [],
|
|
430
|
-
) {
|
|
431
|
-
if (!required) {
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
const requiredKeys = required.map((r) => (typeof r === "string" ? r : r.key));
|
|
436
|
-
|
|
437
|
-
if (requiredKeys.length === 0) {
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
for (const requiredKey of requiredKeys) {
|
|
442
|
-
chain.push(requiredKey);
|
|
443
|
-
|
|
444
|
-
if (chain.indexOf(featureKey) > -1) {
|
|
445
|
-
throw new Error(`circular dependency found: ${chain.join(" -> ")}`);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
const requiredFeatureExists = datasource.entityExists("feature", requiredKey);
|
|
449
|
-
|
|
450
|
-
if (!requiredFeatureExists) {
|
|
451
|
-
throw new Error(`required feature "${requiredKey}" not found`);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const requiredParsedFeature = datasource.readFeature(requiredKey);
|
|
455
|
-
|
|
456
|
-
if (requiredParsedFeature.required) {
|
|
457
|
-
checkForCircularDependencyInRequired(
|
|
458
|
-
datasource,
|
|
459
|
-
featureKey,
|
|
460
|
-
requiredParsedFeature.required,
|
|
461
|
-
chain,
|
|
462
|
-
);
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
export async function lintProject(projectConfig: ProjectConfig): Promise<boolean> {
|
|
468
|
-
let hasError = false;
|
|
469
|
-
const datasource = new Datasource(projectConfig);
|
|
470
|
-
|
|
471
|
-
const availableAttributeKeys: string[] = [];
|
|
472
|
-
const availableSegmentKeys: string[] = [];
|
|
473
|
-
const availableFeatureKeys: string[] = [];
|
|
474
|
-
|
|
475
|
-
// lint attributes
|
|
476
|
-
const attributes = datasource.listAttributes();
|
|
477
|
-
console.log(`Linting ${attributes.length} attributes...\n`);
|
|
478
|
-
|
|
479
|
-
const attributeJoiSchema = getAttributeJoiSchema();
|
|
480
|
-
|
|
481
|
-
for (const key of attributes) {
|
|
482
|
-
const parsed = datasource.readAttribute(key);
|
|
483
|
-
availableAttributeKeys.push(key);
|
|
484
|
-
|
|
485
|
-
try {
|
|
486
|
-
await attributeJoiSchema.validateAsync(parsed);
|
|
487
|
-
} catch (e) {
|
|
488
|
-
console.log(" =>", key);
|
|
489
|
-
|
|
490
|
-
if (e instanceof Joi.ValidationError) {
|
|
491
|
-
printJoiError(e);
|
|
492
|
-
} else {
|
|
493
|
-
console.log(e);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
hasError = true;
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// lint segments
|
|
501
|
-
const segments = datasource.listSegments();
|
|
502
|
-
console.log(`\nLinting ${segments.length} segments...\n`);
|
|
503
|
-
|
|
504
|
-
const conditionsJoiSchema = getConditionsJoiSchema(projectConfig, availableAttributeKeys);
|
|
505
|
-
const segmentJoiSchema = getSegmentJoiSchema(projectConfig, conditionsJoiSchema);
|
|
506
|
-
|
|
507
|
-
for (const key of segments) {
|
|
508
|
-
const parsed = datasource.readSegment(key);
|
|
509
|
-
availableSegmentKeys.push(key);
|
|
510
|
-
|
|
511
|
-
try {
|
|
512
|
-
await segmentJoiSchema.validateAsync(parsed);
|
|
513
|
-
} catch (e) {
|
|
514
|
-
console.log(" =>", key);
|
|
515
|
-
|
|
516
|
-
if (e instanceof Joi.ValidationError) {
|
|
517
|
-
printJoiError(e);
|
|
518
|
-
} else {
|
|
519
|
-
console.log(e);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
hasError = true;
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// lint groups
|
|
527
|
-
|
|
528
|
-
if (fs.existsSync(projectConfig.groupsDirectoryPath)) {
|
|
529
|
-
const groups = datasource.listGroups();
|
|
530
|
-
console.log(`\nLinting ${groups.length} groups...\n`);
|
|
531
|
-
|
|
532
|
-
// @TODO: feature it slots can be from availableFeatureKeys only
|
|
533
|
-
const groupJoiSchema = getGroupJoiSchema(projectConfig, datasource, availableFeatureKeys);
|
|
534
|
-
|
|
535
|
-
for (const key of groups) {
|
|
536
|
-
const parsed = datasource.readGroup(key);
|
|
537
|
-
|
|
538
|
-
try {
|
|
539
|
-
await groupJoiSchema.validateAsync(parsed);
|
|
540
|
-
} catch (e) {
|
|
541
|
-
console.log(" =>", key);
|
|
542
|
-
|
|
543
|
-
if (e instanceof Joi.ValidationError) {
|
|
544
|
-
printJoiError(e);
|
|
545
|
-
} else {
|
|
546
|
-
console.log(e);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
hasError = true;
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// @TODO: feature cannot exist in multiple groups
|
|
555
|
-
|
|
556
|
-
// lint features
|
|
557
|
-
const features = datasource.listFeatures();
|
|
558
|
-
console.log(`\nLinting ${features.length} features...\n`);
|
|
559
|
-
|
|
560
|
-
const featureJoiSchema = getFeatureJoiSchema(
|
|
561
|
-
projectConfig,
|
|
562
|
-
conditionsJoiSchema,
|
|
563
|
-
availableSegmentKeys,
|
|
564
|
-
availableFeatureKeys,
|
|
565
|
-
);
|
|
566
|
-
|
|
567
|
-
for (const key of features) {
|
|
568
|
-
const parsed = datasource.readFeature(key);
|
|
569
|
-
availableFeatureKeys.push(key);
|
|
570
|
-
|
|
571
|
-
try {
|
|
572
|
-
await featureJoiSchema.validateAsync(parsed);
|
|
573
|
-
} catch (e) {
|
|
574
|
-
console.log(" =>", key);
|
|
575
|
-
|
|
576
|
-
if (e instanceof Joi.ValidationError) {
|
|
577
|
-
printJoiError(e);
|
|
578
|
-
} else {
|
|
579
|
-
console.log(e);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
hasError = true;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
if (parsed.required) {
|
|
586
|
-
try {
|
|
587
|
-
checkForCircularDependencyInRequired(datasource, key, parsed.required);
|
|
588
|
-
} catch (e) {
|
|
589
|
-
console.log(" =>", key);
|
|
590
|
-
console.log(" => Error:", e.message);
|
|
591
|
-
hasError = true;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
// lint tests
|
|
597
|
-
if (fs.existsSync(projectConfig.testsDirectoryPath)) {
|
|
598
|
-
const tests = datasource.listTests();
|
|
599
|
-
console.log(`\nLinting ${tests.length} tests...\n`);
|
|
600
|
-
|
|
601
|
-
const testsJoiSchema = getTestsJoiSchema(
|
|
602
|
-
projectConfig,
|
|
603
|
-
availableFeatureKeys,
|
|
604
|
-
availableSegmentKeys,
|
|
605
|
-
);
|
|
606
|
-
|
|
607
|
-
for (const key of tests) {
|
|
608
|
-
const parsed = datasource.readTest(key);
|
|
609
|
-
|
|
610
|
-
try {
|
|
611
|
-
await testsJoiSchema.validateAsync(parsed);
|
|
612
|
-
} catch (e) {
|
|
613
|
-
console.log(" =>", key);
|
|
614
|
-
|
|
615
|
-
if (e instanceof Joi.ValidationError) {
|
|
616
|
-
printJoiError(e);
|
|
617
|
-
} else {
|
|
618
|
-
console.log(e);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
hasError = true;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
return hasError;
|
|
627
|
-
}
|