@featurevisor/core 0.47.6 → 0.48.0
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 +19 -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/builder.d.ts +3 -2
- package/lib/builder.js +20 -28
- package/lib/builder.js.map +1 -1
- package/lib/config.d.ts +3 -0
- package/lib/config.js +3 -1
- package/lib/config.js.map +1 -1
- package/lib/datasource/datasource.d.ts +32 -0
- package/lib/datasource/datasource.js +152 -0
- package/lib/datasource/datasource.js.map +1 -0
- package/lib/datasource/parsers.d.ts +15 -0
- package/lib/datasource/parsers.js +19 -0
- package/lib/datasource/parsers.js.map +1 -0
- package/lib/generate-code/index.js +3 -1
- package/lib/generate-code/index.js.map +1 -1
- package/lib/generate-code/typescript.d.ts +2 -1
- package/lib/generate-code/typescript.js +7 -10
- package/lib/generate-code/typescript.js.map +1 -1
- package/lib/init.d.ts +1 -1
- package/lib/init.js +1 -1
- package/lib/init.js.map +1 -1
- package/lib/linter.d.ts +2 -1
- package/lib/linter.js +44 -55
- package/lib/linter.js.map +1 -1
- package/lib/site.js +15 -28
- package/lib/site.js.map +1 -1
- package/lib/tester.js +9 -11
- package/lib/tester.js.map +1 -1
- package/package.json +5 -5
- package/src/builder.ts +22 -30
- package/src/config.ts +7 -0
- package/src/datasource/datasource.ts +185 -0
- package/src/datasource/parsers.ts +26 -0
- package/src/generate-code/index.ts +9 -1
- package/src/generate-code/typescript.ts +8 -12
- package/src/init.ts +1 -1
- package/src/linter.ts +46 -45
- package/src/site.ts +104 -127
- package/src/tester.ts +10 -12
package/src/linter.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
// for use in node only
|
|
2
2
|
import * as fs from "fs";
|
|
3
|
-
import * as path from "path";
|
|
4
3
|
|
|
5
4
|
import * as Joi from "joi";
|
|
6
5
|
|
|
7
|
-
import {
|
|
6
|
+
import { Datasource } from "./datasource/datasource";
|
|
8
7
|
|
|
9
8
|
import { ProjectConfig } from "./config";
|
|
10
|
-
import {
|
|
9
|
+
import { FeatureKey, Required } from "@featurevisor/types";
|
|
11
10
|
|
|
12
11
|
export function getAttributeJoiSchema() {
|
|
13
12
|
const attributeJoiSchema = Joi.object({
|
|
@@ -112,7 +111,11 @@ export function getSegmentJoiSchema(projectConfig: ProjectConfig, conditionsJoiS
|
|
|
112
111
|
return segmentJoiSchema;
|
|
113
112
|
}
|
|
114
113
|
|
|
115
|
-
export function getGroupJoiSchema(
|
|
114
|
+
export function getGroupJoiSchema(
|
|
115
|
+
projectConfig: ProjectConfig,
|
|
116
|
+
datasource: Datasource,
|
|
117
|
+
availableFeatureKeys: string[],
|
|
118
|
+
) {
|
|
116
119
|
const groupJoiSchema = Joi.object({
|
|
117
120
|
description: Joi.string().required(),
|
|
118
121
|
slots: Joi.array()
|
|
@@ -134,13 +137,14 @@ export function getGroupJoiSchema(projectConfig: ProjectConfig, availableFeature
|
|
|
134
137
|
|
|
135
138
|
if (slot.feature) {
|
|
136
139
|
const featureKey = slot.feature;
|
|
137
|
-
const
|
|
138
|
-
const parsedFeature = parseYaml(fs.readFileSync(featurePath, "utf8")) as ParsedFeature;
|
|
140
|
+
const featureExists = datasource.entityExists("feature", featureKey);
|
|
139
141
|
|
|
140
|
-
if (!
|
|
142
|
+
if (!featureExists) {
|
|
141
143
|
throw new Error(`feature ${featureKey} not found`);
|
|
142
144
|
}
|
|
143
145
|
|
|
146
|
+
const parsedFeature = datasource.readFeature(featureKey);
|
|
147
|
+
|
|
144
148
|
const environmentKeys = Object.keys(parsedFeature.environments);
|
|
145
149
|
for (const environmentKey of environmentKeys) {
|
|
146
150
|
const environment = parsedFeature.environments[environmentKey];
|
|
@@ -439,7 +443,7 @@ export function printJoiError(e: Joi.ValidationError) {
|
|
|
439
443
|
}
|
|
440
444
|
|
|
441
445
|
function checkForCircularDependencyInRequired(
|
|
442
|
-
|
|
446
|
+
datasource: Datasource,
|
|
443
447
|
featureKey: FeatureKey,
|
|
444
448
|
required?: Required[],
|
|
445
449
|
chain: FeatureKey[] = [],
|
|
@@ -461,19 +465,17 @@ function checkForCircularDependencyInRequired(
|
|
|
461
465
|
throw new Error(`circular dependency found: ${chain.join(" -> ")}`);
|
|
462
466
|
}
|
|
463
467
|
|
|
464
|
-
const
|
|
468
|
+
const requiredFeatureExists = datasource.entityExists("feature", requiredKey);
|
|
465
469
|
|
|
466
|
-
if (!
|
|
470
|
+
if (!requiredFeatureExists) {
|
|
467
471
|
throw new Error(`required feature "${requiredKey}" not found`);
|
|
468
472
|
}
|
|
469
473
|
|
|
470
|
-
const requiredParsedFeature =
|
|
471
|
-
fs.readFileSync(requiredFeaturePath, "utf8"),
|
|
472
|
-
) as ParsedFeature;
|
|
474
|
+
const requiredParsedFeature = datasource.readFeature(requiredKey);
|
|
473
475
|
|
|
474
476
|
if (requiredParsedFeature.required) {
|
|
475
477
|
checkForCircularDependencyInRequired(
|
|
476
|
-
|
|
478
|
+
datasource,
|
|
477
479
|
featureKey,
|
|
478
480
|
requiredParsedFeature.required,
|
|
479
481
|
chain,
|
|
@@ -484,19 +486,20 @@ function checkForCircularDependencyInRequired(
|
|
|
484
486
|
|
|
485
487
|
export async function lintProject(projectConfig: ProjectConfig): Promise<boolean> {
|
|
486
488
|
let hasError = false;
|
|
489
|
+
const datasource = new Datasource(projectConfig);
|
|
487
490
|
|
|
488
491
|
const availableAttributeKeys: string[] = [];
|
|
489
492
|
const availableSegmentKeys: string[] = [];
|
|
490
493
|
const availableFeatureKeys: string[] = [];
|
|
491
494
|
|
|
492
495
|
// lint attributes
|
|
493
|
-
|
|
494
|
-
|
|
496
|
+
const attributes = datasource.listAttributes();
|
|
497
|
+
console.log(`Linting ${attributes.length} attributes...\n`);
|
|
498
|
+
|
|
495
499
|
const attributeJoiSchema = getAttributeJoiSchema();
|
|
496
500
|
|
|
497
|
-
for (const
|
|
498
|
-
const
|
|
499
|
-
const parsed = parseYaml(fs.readFileSync(filePath, "utf8")) as any;
|
|
501
|
+
for (const key of attributes) {
|
|
502
|
+
const parsed = datasource.readAttribute(key);
|
|
500
503
|
availableAttributeKeys.push(key);
|
|
501
504
|
|
|
502
505
|
try {
|
|
@@ -515,14 +518,14 @@ export async function lintProject(projectConfig: ProjectConfig): Promise<boolean
|
|
|
515
518
|
}
|
|
516
519
|
|
|
517
520
|
// lint segments
|
|
518
|
-
|
|
519
|
-
|
|
521
|
+
const segments = datasource.listSegments();
|
|
522
|
+
console.log(`\nLinting ${segments.length} segments...\n`);
|
|
523
|
+
|
|
520
524
|
const conditionsJoiSchema = getConditionsJoiSchema(projectConfig, availableAttributeKeys);
|
|
521
525
|
const segmentJoiSchema = getSegmentJoiSchema(projectConfig, conditionsJoiSchema);
|
|
522
526
|
|
|
523
|
-
for (const
|
|
524
|
-
const
|
|
525
|
-
const parsed = parseYaml(fs.readFileSync(filePath, "utf8")) as any;
|
|
527
|
+
for (const key of segments) {
|
|
528
|
+
const parsed = datasource.readSegment(key);
|
|
526
529
|
availableSegmentKeys.push(key);
|
|
527
530
|
|
|
528
531
|
try {
|
|
@@ -541,14 +544,16 @@ export async function lintProject(projectConfig: ProjectConfig): Promise<boolean
|
|
|
541
544
|
}
|
|
542
545
|
|
|
543
546
|
// lint groups
|
|
544
|
-
|
|
547
|
+
|
|
545
548
|
if (fs.existsSync(projectConfig.groupsDirectoryPath)) {
|
|
546
|
-
const
|
|
547
|
-
|
|
549
|
+
const groups = datasource.listGroups();
|
|
550
|
+
console.log(`\nLinting ${groups.length} groups...\n`);
|
|
551
|
+
|
|
552
|
+
// @TODO: feature it slots can be from availableFeatureKeys only
|
|
553
|
+
const groupJoiSchema = getGroupJoiSchema(projectConfig, datasource, availableFeatureKeys);
|
|
548
554
|
|
|
549
|
-
for (const
|
|
550
|
-
const
|
|
551
|
-
const parsed = parseYaml(fs.readFileSync(filePath, "utf8")) as any;
|
|
555
|
+
for (const key of groups) {
|
|
556
|
+
const parsed = datasource.readGroup(key);
|
|
552
557
|
|
|
553
558
|
try {
|
|
554
559
|
await groupJoiSchema.validateAsync(parsed);
|
|
@@ -569,8 +574,9 @@ export async function lintProject(projectConfig: ProjectConfig): Promise<boolean
|
|
|
569
574
|
// @TODO: feature cannot exist in multiple groups
|
|
570
575
|
|
|
571
576
|
// lint features
|
|
572
|
-
|
|
573
|
-
|
|
577
|
+
const features = datasource.listFeatures();
|
|
578
|
+
console.log(`\nLinting ${features.length} features...\n`);
|
|
579
|
+
|
|
574
580
|
const featureJoiSchema = getFeatureJoiSchema(
|
|
575
581
|
projectConfig,
|
|
576
582
|
conditionsJoiSchema,
|
|
@@ -578,9 +584,8 @@ export async function lintProject(projectConfig: ProjectConfig): Promise<boolean
|
|
|
578
584
|
availableFeatureKeys,
|
|
579
585
|
);
|
|
580
586
|
|
|
581
|
-
for (const
|
|
582
|
-
const
|
|
583
|
-
const parsed = parseYaml(fs.readFileSync(filePath, "utf8")) as any;
|
|
587
|
+
for (const key of features) {
|
|
588
|
+
const parsed = datasource.readFeature(key);
|
|
584
589
|
availableFeatureKeys.push(key);
|
|
585
590
|
|
|
586
591
|
try {
|
|
@@ -599,11 +604,7 @@ export async function lintProject(projectConfig: ProjectConfig): Promise<boolean
|
|
|
599
604
|
|
|
600
605
|
if (parsed.required) {
|
|
601
606
|
try {
|
|
602
|
-
checkForCircularDependencyInRequired(
|
|
603
|
-
projectConfig.featuresDirectoryPath,
|
|
604
|
-
key,
|
|
605
|
-
parsed.required,
|
|
606
|
-
);
|
|
607
|
+
checkForCircularDependencyInRequired(datasource, key, parsed.required);
|
|
607
608
|
} catch (e) {
|
|
608
609
|
console.log(" =>", key);
|
|
609
610
|
console.log(" => Error:", e.message);
|
|
@@ -613,18 +614,18 @@ export async function lintProject(projectConfig: ProjectConfig): Promise<boolean
|
|
|
613
614
|
}
|
|
614
615
|
|
|
615
616
|
// lint tests
|
|
616
|
-
console.log("\nLinting tests...\n");
|
|
617
617
|
if (fs.existsSync(projectConfig.testsDirectoryPath)) {
|
|
618
|
-
const
|
|
618
|
+
const tests = datasource.listTests();
|
|
619
|
+
console.log(`\nLinting ${tests.length} tests...\n`);
|
|
620
|
+
|
|
619
621
|
const testsJoiSchema = getTestsJoiSchema(
|
|
620
622
|
projectConfig,
|
|
621
623
|
availableFeatureKeys,
|
|
622
624
|
availableSegmentKeys,
|
|
623
625
|
);
|
|
624
626
|
|
|
625
|
-
for (const
|
|
626
|
-
const
|
|
627
|
-
const parsed = parseYaml(fs.readFileSync(filePath, "utf8")) as any;
|
|
627
|
+
for (const key of tests) {
|
|
628
|
+
const parsed = datasource.readTest(key);
|
|
628
629
|
|
|
629
630
|
try {
|
|
630
631
|
await testsJoiSchema.validateAsync(parsed);
|
package/src/site.ts
CHANGED
|
@@ -6,9 +6,6 @@ import { execSync } from "child_process";
|
|
|
6
6
|
import * as mkdirp from "mkdirp";
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
Attribute,
|
|
10
|
-
ParsedFeature,
|
|
11
|
-
Segment,
|
|
12
9
|
HistoryEntry,
|
|
13
10
|
LastModified,
|
|
14
11
|
SearchIndex,
|
|
@@ -19,11 +16,8 @@ import {
|
|
|
19
16
|
} from "@featurevisor/types";
|
|
20
17
|
|
|
21
18
|
import { ProjectConfig } from "./config";
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
extractAttributeKeysFromConditions,
|
|
25
|
-
extractSegmentKeysFromGroupSegments,
|
|
26
|
-
} from "./utils";
|
|
19
|
+
import { Datasource } from "./datasource/datasource";
|
|
20
|
+
import { extractAttributeKeysFromConditions, extractSegmentKeysFromGroupSegments } from "./utils";
|
|
27
21
|
|
|
28
22
|
function getRelativePaths(rootDirectoryPath, projectConfig: ProjectConfig) {
|
|
29
23
|
const relativeFeaturesPath = path.relative(
|
|
@@ -104,7 +98,7 @@ export function generateHistory(rootDirectoryPath, projectConfig: ProjectConfig)
|
|
|
104
98
|
const fileName = lineSplit.pop() as string;
|
|
105
99
|
const relativeDir = lineSplit.join(path.sep);
|
|
106
100
|
|
|
107
|
-
const key = fileName.replace(".
|
|
101
|
+
const key = fileName.replace("." + projectConfig.parser, "");
|
|
108
102
|
|
|
109
103
|
let type = "feature" as "attribute" | "segment" | "feature";
|
|
110
104
|
|
|
@@ -249,6 +243,7 @@ export function generateSiteSearchIndex(
|
|
|
249
243
|
features: [],
|
|
250
244
|
},
|
|
251
245
|
};
|
|
246
|
+
const datasource = new Datasource(projectConfig);
|
|
252
247
|
|
|
253
248
|
/**
|
|
254
249
|
* Links
|
|
@@ -267,15 +262,15 @@ export function generateSiteSearchIndex(
|
|
|
267
262
|
result.links = {
|
|
268
263
|
attribute: repoDetails.blobUrl.replace(
|
|
269
264
|
"{{blobPath}}",
|
|
270
|
-
prefix + relativeAttributesPath + "/{{key}}.
|
|
265
|
+
prefix + relativeAttributesPath + "/{{key}}." + datasource.getExtension(),
|
|
271
266
|
),
|
|
272
267
|
segment: repoDetails.blobUrl.replace(
|
|
273
268
|
"{{blobPath}}",
|
|
274
|
-
prefix + relativeSegmentsPath + "/{{key}}.
|
|
269
|
+
prefix + relativeSegmentsPath + "/{{key}}." + datasource.getExtension(),
|
|
275
270
|
),
|
|
276
271
|
feature: repoDetails.blobUrl.replace(
|
|
277
272
|
"{{blobPath}}",
|
|
278
|
-
prefix + relativeFeaturesPath + "/{{key}}.
|
|
273
|
+
prefix + relativeFeaturesPath + "/{{key}}." + datasource.getExtension(),
|
|
279
274
|
),
|
|
280
275
|
commit: repoDetails.commitUrl,
|
|
281
276
|
};
|
|
@@ -296,145 +291,127 @@ export function generateSiteSearchIndex(
|
|
|
296
291
|
} = {};
|
|
297
292
|
|
|
298
293
|
// features
|
|
299
|
-
const featureFiles =
|
|
300
|
-
featureFiles
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
if (Array.isArray(parsed.variations)) {
|
|
310
|
-
parsed.variations.forEach((variation) => {
|
|
311
|
-
if (!variation.variables) {
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
294
|
+
const featureFiles = datasource.listFeatures();
|
|
295
|
+
featureFiles.forEach((entityName) => {
|
|
296
|
+
const parsed = datasource.readFeature(entityName);
|
|
297
|
+
|
|
298
|
+
if (Array.isArray(parsed.variations)) {
|
|
299
|
+
parsed.variations.forEach((variation) => {
|
|
300
|
+
if (!variation.variables) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
314
303
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
if (o.segments && o.segments !== "*") {
|
|
329
|
-
extractSegmentKeysFromGroupSegments(o.segments).forEach((segmentKey) => {
|
|
330
|
-
if (!segmentsUsedInFeatures[segmentKey]) {
|
|
331
|
-
segmentsUsedInFeatures[segmentKey] = new Set();
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
segmentsUsedInFeatures[segmentKey].add(entityName);
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
}
|
|
304
|
+
variation.variables.forEach((v) => {
|
|
305
|
+
if (v.overrides) {
|
|
306
|
+
v.overrides.forEach((o) => {
|
|
307
|
+
if (o.conditions) {
|
|
308
|
+
extractAttributeKeysFromConditions(o.conditions).forEach((attributeKey) => {
|
|
309
|
+
if (!attributesUsedInFeatures[attributeKey]) {
|
|
310
|
+
attributesUsedInFeatures[attributeKey] = new Set();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
attributesUsedInFeatures[attributeKey].add(entityName);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
342
316
|
|
|
343
|
-
|
|
344
|
-
|
|
317
|
+
if (o.segments && o.segments !== "*") {
|
|
318
|
+
extractSegmentKeysFromGroupSegments(o.segments).forEach((segmentKey) => {
|
|
319
|
+
if (!segmentsUsedInFeatures[segmentKey]) {
|
|
320
|
+
segmentsUsedInFeatures[segmentKey] = new Set();
|
|
321
|
+
}
|
|
345
322
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
extractSegmentKeysFromGroupSegments(rule.segments).forEach((segmentKey) => {
|
|
349
|
-
if (!segmentsUsedInFeatures[segmentKey]) {
|
|
350
|
-
segmentsUsedInFeatures[segmentKey] = new Set();
|
|
323
|
+
segmentsUsedInFeatures[segmentKey].add(entityName);
|
|
324
|
+
});
|
|
351
325
|
}
|
|
352
|
-
|
|
353
|
-
segmentsUsedInFeatures[segmentKey].add(entityName);
|
|
354
326
|
});
|
|
355
327
|
}
|
|
356
328
|
});
|
|
329
|
+
});
|
|
330
|
+
}
|
|
357
331
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (force.segments && force.segments !== "*") {
|
|
361
|
-
extractSegmentKeysFromGroupSegments(force.segments).forEach((segmentKey) => {
|
|
362
|
-
if (!segmentsUsedInFeatures[segmentKey]) {
|
|
363
|
-
segmentsUsedInFeatures[segmentKey] = new Set();
|
|
364
|
-
}
|
|
332
|
+
Object.keys(parsed.environments).forEach((environmentKey) => {
|
|
333
|
+
const env = parsed.environments[environmentKey];
|
|
365
334
|
|
|
366
|
-
|
|
367
|
-
|
|
335
|
+
env.rules.forEach((rule) => {
|
|
336
|
+
if (rule.segments && rule.segments !== "*") {
|
|
337
|
+
extractSegmentKeysFromGroupSegments(rule.segments).forEach((segmentKey) => {
|
|
338
|
+
if (!segmentsUsedInFeatures[segmentKey]) {
|
|
339
|
+
segmentsUsedInFeatures[segmentKey] = new Set();
|
|
368
340
|
}
|
|
369
341
|
|
|
370
|
-
|
|
371
|
-
extractAttributeKeysFromConditions(force.conditions).forEach((attributeKey) => {
|
|
372
|
-
if (!attributesUsedInFeatures[attributeKey]) {
|
|
373
|
-
attributesUsedInFeatures[attributeKey] = new Set();
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
attributesUsedInFeatures[attributeKey].add(entityName);
|
|
377
|
-
});
|
|
378
|
-
}
|
|
342
|
+
segmentsUsedInFeatures[segmentKey].add(entityName);
|
|
379
343
|
});
|
|
380
344
|
}
|
|
381
345
|
});
|
|
382
346
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
347
|
+
if (env.force) {
|
|
348
|
+
env.force.forEach((force) => {
|
|
349
|
+
if (force.segments && force.segments !== "*") {
|
|
350
|
+
extractSegmentKeysFromGroupSegments(force.segments).forEach((segmentKey) => {
|
|
351
|
+
if (!segmentsUsedInFeatures[segmentKey]) {
|
|
352
|
+
segmentsUsedInFeatures[segmentKey] = new Set();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
segmentsUsedInFeatures[segmentKey].add(entityName);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (force.conditions) {
|
|
360
|
+
extractAttributeKeysFromConditions(force.conditions).forEach((attributeKey) => {
|
|
361
|
+
if (!attributesUsedInFeatures[attributeKey]) {
|
|
362
|
+
attributesUsedInFeatures[attributeKey] = new Set();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
attributesUsedInFeatures[attributeKey].add(entityName);
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
}
|
|
388
370
|
});
|
|
389
371
|
|
|
372
|
+
result.entities.features.push({
|
|
373
|
+
...parsed,
|
|
374
|
+
key: entityName,
|
|
375
|
+
lastModified: getLastModifiedFromHistory(fullHistory, "feature", entityName),
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
390
379
|
// segments
|
|
391
|
-
const segmentFiles =
|
|
392
|
-
segmentFiles
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
extractAttributeKeysFromConditions(parsed.conditions as Condition | Condition[]).forEach(
|
|
402
|
-
(attributeKey) => {
|
|
403
|
-
if (!attributesUsedInSegments[attributeKey]) {
|
|
404
|
-
attributesUsedInSegments[attributeKey] = new Set();
|
|
405
|
-
}
|
|
380
|
+
const segmentFiles = datasource.listSegments();
|
|
381
|
+
segmentFiles.forEach((entityName) => {
|
|
382
|
+
const parsed = datasource.readSegment(entityName);
|
|
383
|
+
|
|
384
|
+
extractAttributeKeysFromConditions(parsed.conditions as Condition | Condition[]).forEach(
|
|
385
|
+
(attributeKey) => {
|
|
386
|
+
if (!attributesUsedInSegments[attributeKey]) {
|
|
387
|
+
attributesUsedInSegments[attributeKey] = new Set();
|
|
388
|
+
}
|
|
406
389
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
390
|
+
attributesUsedInSegments[attributeKey].add(entityName);
|
|
391
|
+
},
|
|
392
|
+
);
|
|
410
393
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
});
|
|
394
|
+
result.entities.segments.push({
|
|
395
|
+
...parsed,
|
|
396
|
+
key: entityName,
|
|
397
|
+
lastModified: getLastModifiedFromHistory(fullHistory, "segment", entityName),
|
|
398
|
+
usedInFeatures: Array.from(segmentsUsedInFeatures[entityName] || []),
|
|
417
399
|
});
|
|
400
|
+
});
|
|
418
401
|
|
|
419
402
|
// attributes
|
|
420
|
-
const attributeFiles =
|
|
421
|
-
attributeFiles
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
result.entities.attributes.push({
|
|
431
|
-
...parsed,
|
|
432
|
-
key: entityName,
|
|
433
|
-
lastModified: getLastModifiedFromHistory(fullHistory, "attribute", entityName),
|
|
434
|
-
usedInFeatures: Array.from(attributesUsedInFeatures[entityName] || []),
|
|
435
|
-
usedInSegments: Array.from(attributesUsedInSegments[entityName] || []),
|
|
436
|
-
});
|
|
403
|
+
const attributeFiles = datasource.listAttributes();
|
|
404
|
+
attributeFiles.forEach((entityName) => {
|
|
405
|
+
const parsed = datasource.readAttribute(entityName);
|
|
406
|
+
|
|
407
|
+
result.entities.attributes.push({
|
|
408
|
+
...parsed,
|
|
409
|
+
key: entityName,
|
|
410
|
+
lastModified: getLastModifiedFromHistory(fullHistory, "attribute", entityName),
|
|
411
|
+
usedInFeatures: Array.from(attributesUsedInFeatures[entityName] || []),
|
|
412
|
+
usedInSegments: Array.from(attributesUsedInSegments[entityName] || []),
|
|
437
413
|
});
|
|
414
|
+
});
|
|
438
415
|
|
|
439
416
|
return result;
|
|
440
417
|
}
|
package/src/tester.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
2
|
|
|
4
|
-
import { DatafileContent,
|
|
3
|
+
import { DatafileContent, Condition } from "@featurevisor/types";
|
|
5
4
|
import { createInstance, allConditionsAreMatched, MAX_BUCKETED_NUMBER } from "@featurevisor/sdk";
|
|
6
5
|
|
|
7
6
|
import { ProjectConfig } from "./config";
|
|
8
|
-
import { parseYaml } from "./utils";
|
|
9
7
|
import { getDatafilePath } from "./builder";
|
|
8
|
+
import { Datasource } from "./datasource/datasource";
|
|
10
9
|
|
|
11
10
|
// @TODO: make it better
|
|
12
11
|
export function checkIfArraysAreEqual(a, b) {
|
|
@@ -49,6 +48,7 @@ export function checkIfObjectsAreEqual(a, b) {
|
|
|
49
48
|
|
|
50
49
|
export function testProject(rootDirectoryPath: string, projectConfig: ProjectConfig): boolean {
|
|
51
50
|
let hasError = false;
|
|
51
|
+
const datasource = new Datasource(projectConfig);
|
|
52
52
|
|
|
53
53
|
if (!fs.existsSync(projectConfig.testsDirectoryPath)) {
|
|
54
54
|
console.error(`Tests directory does not exist: ${projectConfig.testsDirectoryPath}`);
|
|
@@ -57,9 +57,7 @@ export function testProject(rootDirectoryPath: string, projectConfig: ProjectCon
|
|
|
57
57
|
return hasError;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const testFiles =
|
|
61
|
-
.readdirSync(projectConfig.testsDirectoryPath)
|
|
62
|
-
.filter((f) => f.endsWith(".yml"));
|
|
60
|
+
const testFiles = datasource.listTests();
|
|
63
61
|
|
|
64
62
|
if (testFiles.length === 0) {
|
|
65
63
|
console.error(`No tests found in: ${projectConfig.testsDirectoryPath}`);
|
|
@@ -69,11 +67,11 @@ export function testProject(rootDirectoryPath: string, projectConfig: ProjectCon
|
|
|
69
67
|
}
|
|
70
68
|
|
|
71
69
|
for (const testFile of testFiles) {
|
|
72
|
-
const testFilePath =
|
|
70
|
+
const testFilePath = datasource.getEntityPath("test", testFile);
|
|
73
71
|
|
|
74
72
|
console.log(` => Testing: ${testFilePath.replace(rootDirectoryPath, "")}`);
|
|
75
73
|
|
|
76
|
-
const parsed =
|
|
74
|
+
const parsed = datasource.readTest(testFile);
|
|
77
75
|
|
|
78
76
|
parsed.tests.forEach(function (test) {
|
|
79
77
|
if (test.segments) {
|
|
@@ -83,16 +81,16 @@ export function testProject(rootDirectoryPath: string, projectConfig: ProjectCon
|
|
|
83
81
|
|
|
84
82
|
console.log(` => Segment "${segmentKey}":`);
|
|
85
83
|
|
|
86
|
-
const
|
|
84
|
+
const segmentExists = datasource.entityExists("segment", segmentKey);
|
|
87
85
|
|
|
88
|
-
if (!
|
|
89
|
-
console.error(` => Segment does not exist: ${
|
|
86
|
+
if (!segmentExists) {
|
|
87
|
+
console.error(` => Segment does not exist: ${segmentKey}`);
|
|
90
88
|
hasError = true;
|
|
91
89
|
|
|
92
90
|
return;
|
|
93
91
|
}
|
|
94
92
|
|
|
95
|
-
const parsedSegment =
|
|
93
|
+
const parsedSegment = datasource.readSegment(segmentKey);
|
|
96
94
|
const conditions = parsedSegment.conditions as Condition | Condition[];
|
|
97
95
|
|
|
98
96
|
segment.assertions.forEach(function (assertion, aIndex) {
|