@featurevisor/core 0.47.7 → 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.
Files changed (48) hide show
  1. package/.eslintcache +1 -1
  2. package/CHANGELOG.md +11 -0
  3. package/coverage/clover.xml +2 -2
  4. package/coverage/lcov-report/index.html +1 -1
  5. package/coverage/lcov-report/lib/allocator.js.html +1 -1
  6. package/coverage/lcov-report/lib/index.html +1 -1
  7. package/coverage/lcov-report/lib/traffic.js.html +1 -1
  8. package/coverage/lcov-report/src/allocator.ts.html +1 -1
  9. package/coverage/lcov-report/src/index.html +1 -1
  10. package/coverage/lcov-report/src/traffic.ts.html +1 -1
  11. package/lib/builder.d.ts +3 -2
  12. package/lib/builder.js +20 -28
  13. package/lib/builder.js.map +1 -1
  14. package/lib/config.d.ts +3 -0
  15. package/lib/config.js +3 -1
  16. package/lib/config.js.map +1 -1
  17. package/lib/datasource/datasource.d.ts +32 -0
  18. package/lib/datasource/datasource.js +152 -0
  19. package/lib/datasource/datasource.js.map +1 -0
  20. package/lib/datasource/parsers.d.ts +15 -0
  21. package/lib/datasource/parsers.js +19 -0
  22. package/lib/datasource/parsers.js.map +1 -0
  23. package/lib/generate-code/index.js +3 -1
  24. package/lib/generate-code/index.js.map +1 -1
  25. package/lib/generate-code/typescript.d.ts +2 -1
  26. package/lib/generate-code/typescript.js +7 -10
  27. package/lib/generate-code/typescript.js.map +1 -1
  28. package/lib/init.d.ts +1 -1
  29. package/lib/init.js +1 -1
  30. package/lib/init.js.map +1 -1
  31. package/lib/linter.d.ts +2 -1
  32. package/lib/linter.js +44 -55
  33. package/lib/linter.js.map +1 -1
  34. package/lib/site.js +15 -28
  35. package/lib/site.js.map +1 -1
  36. package/lib/tester.js +9 -11
  37. package/lib/tester.js.map +1 -1
  38. package/package.json +5 -5
  39. package/src/builder.ts +22 -30
  40. package/src/config.ts +7 -0
  41. package/src/datasource/datasource.ts +185 -0
  42. package/src/datasource/parsers.ts +26 -0
  43. package/src/generate-code/index.ts +9 -1
  44. package/src/generate-code/typescript.ts +8 -12
  45. package/src/init.ts +1 -1
  46. package/src/linter.ts +46 -45
  47. package/src/site.ts +104 -127
  48. 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 { getYAMLFiles, parseYaml } from "./utils";
6
+ import { Datasource } from "./datasource/datasource";
8
7
 
9
8
  import { ProjectConfig } from "./config";
10
- import { ParsedFeature, FeatureKey, Required } from "@featurevisor/types";
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(projectConfig: ProjectConfig, availableFeatureKeys: string[]) {
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 featurePath = path.join(projectConfig.featuresDirectoryPath, `${featureKey}.yml`);
138
- const parsedFeature = parseYaml(fs.readFileSync(featurePath, "utf8")) as ParsedFeature;
140
+ const featureExists = datasource.entityExists("feature", featureKey);
139
141
 
140
- if (!parsedFeature) {
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
- featuresDirectoryPath: string,
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 requiredFeaturePath = path.join(featuresDirectoryPath, `${requiredKey}.yml`);
468
+ const requiredFeatureExists = datasource.entityExists("feature", requiredKey);
465
469
 
466
- if (!fs.existsSync(requiredFeaturePath)) {
470
+ if (!requiredFeatureExists) {
467
471
  throw new Error(`required feature "${requiredKey}" not found`);
468
472
  }
469
473
 
470
- const requiredParsedFeature = parseYaml(
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
- featuresDirectoryPath,
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
- console.log("Linting attributes...\n");
494
- const attributeFilePaths = getYAMLFiles(path.join(projectConfig.attributesDirectoryPath));
496
+ const attributes = datasource.listAttributes();
497
+ console.log(`Linting ${attributes.length} attributes...\n`);
498
+
495
499
  const attributeJoiSchema = getAttributeJoiSchema();
496
500
 
497
- for (const filePath of attributeFilePaths) {
498
- const key = path.basename(filePath, ".yml");
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
- console.log("\nLinting segments...\n");
519
- const segmentFilePaths = getYAMLFiles(path.join(projectConfig.segmentsDirectoryPath));
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 filePath of segmentFilePaths) {
524
- const key = path.basename(filePath, ".yml");
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
- console.log("\nLinting groups...\n");
547
+
545
548
  if (fs.existsSync(projectConfig.groupsDirectoryPath)) {
546
- const groupFilePaths = getYAMLFiles(path.join(projectConfig.groupsDirectoryPath));
547
- const groupJoiSchema = getGroupJoiSchema(projectConfig, availableFeatureKeys);
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 filePath of groupFilePaths) {
550
- const key = path.basename(filePath, ".yml");
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
- console.log("\nLinting features...\n");
573
- const featureFilePaths = getYAMLFiles(path.join(projectConfig.featuresDirectoryPath));
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 filePath of featureFilePaths) {
582
- const key = path.basename(filePath, ".yml");
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 testFilePaths = getYAMLFiles(path.join(projectConfig.testsDirectoryPath));
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 filePath of testFilePaths) {
626
- const key = path.basename(filePath, ".yml");
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
- parseYaml,
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(".yml", "");
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}}.yml",
265
+ prefix + relativeAttributesPath + "/{{key}}." + datasource.getExtension(),
271
266
  ),
272
267
  segment: repoDetails.blobUrl.replace(
273
268
  "{{blobPath}}",
274
- prefix + relativeSegmentsPath + "/{{key}}.yml",
269
+ prefix + relativeSegmentsPath + "/{{key}}." + datasource.getExtension(),
275
270
  ),
276
271
  feature: repoDetails.blobUrl.replace(
277
272
  "{{blobPath}}",
278
- prefix + relativeFeaturesPath + "/{{key}}.yml",
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 = fs.readdirSync(projectConfig.featuresDirectoryPath);
300
- featureFiles
301
- .filter((fileName) => fileName.endsWith(".yml"))
302
- .forEach((fileName) => {
303
- const filePath = path.join(projectConfig.featuresDirectoryPath, fileName);
304
- const entityName = fileName.replace(".yml", "");
305
-
306
- const fileContent = fs.readFileSync(filePath, "utf8");
307
- const parsed = parseYaml(fileContent) as ParsedFeature;
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
- variation.variables.forEach((v) => {
316
- if (v.overrides) {
317
- v.overrides.forEach((o) => {
318
- if (o.conditions) {
319
- extractAttributeKeysFromConditions(o.conditions).forEach((attributeKey) => {
320
- if (!attributesUsedInFeatures[attributeKey]) {
321
- attributesUsedInFeatures[attributeKey] = new Set();
322
- }
323
-
324
- attributesUsedInFeatures[attributeKey].add(entityName);
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
- Object.keys(parsed.environments).forEach((environmentKey) => {
344
- const env = parsed.environments[environmentKey];
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
- env.rules.forEach((rule) => {
347
- if (rule.segments && rule.segments !== "*") {
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
- if (env.force) {
359
- env.force.forEach((force) => {
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
- segmentsUsedInFeatures[segmentKey].add(entityName);
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
- if (force.conditions) {
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
- result.entities.features.push({
384
- ...parsed,
385
- key: entityName,
386
- lastModified: getLastModifiedFromHistory(fullHistory, "feature", entityName),
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 = fs.readdirSync(projectConfig.segmentsDirectoryPath);
392
- segmentFiles
393
- .filter((fileName) => fileName.endsWith(".yml"))
394
- .forEach((fileName) => {
395
- const filePath = path.join(projectConfig.segmentsDirectoryPath, fileName);
396
- const entityName = fileName.replace(".yml", "");
397
-
398
- const fileContent = fs.readFileSync(filePath, "utf8");
399
- const parsed = parseYaml(fileContent) as Segment;
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
- attributesUsedInSegments[attributeKey].add(entityName);
408
- },
409
- );
390
+ attributesUsedInSegments[attributeKey].add(entityName);
391
+ },
392
+ );
410
393
 
411
- result.entities.segments.push({
412
- ...parsed,
413
- key: entityName,
414
- lastModified: getLastModifiedFromHistory(fullHistory, "segment", entityName),
415
- usedInFeatures: Array.from(segmentsUsedInFeatures[entityName] || []),
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 = fs.readdirSync(projectConfig.attributesDirectoryPath);
421
- attributeFiles
422
- .filter((fileName) => fileName.endsWith(".yml"))
423
- .forEach((fileName) => {
424
- const filePath = path.join(projectConfig.attributesDirectoryPath, fileName);
425
- const entityName = fileName.replace(".yml", "");
426
-
427
- const fileContent = fs.readFileSync(filePath, "utf8");
428
- const parsed = parseYaml(fileContent) as Attribute;
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, Spec, Segment, Condition } from "@featurevisor/types";
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 = fs
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 = path.join(projectConfig.testsDirectoryPath, testFile);
70
+ const testFilePath = datasource.getEntityPath("test", testFile);
73
71
 
74
72
  console.log(` => Testing: ${testFilePath.replace(rootDirectoryPath, "")}`);
75
73
 
76
- const parsed = parseYaml(fs.readFileSync(testFilePath, "utf8")) as Spec;
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 segmentPath = path.join(projectConfig.segmentsDirectoryPath, `${segmentKey}.yml`);
84
+ const segmentExists = datasource.entityExists("segment", segmentKey);
87
85
 
88
- if (!fs.existsSync(segmentPath)) {
89
- console.error(` => Segment does not exist: ${segmentPath}`);
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 = parseYaml(fs.readFileSync(segmentPath, "utf8")) as Segment;
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) {