@featurevisor/core
Version:
Core package of Featurevisor for Node.js usage
380 lines • 17.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.lintPlugin = void 0;
exports.lintProject = lintProject;
// for use in node only
const path = require("path");
const attributeSchema_1 = require("./attributeSchema");
const conditionSchema_1 = require("./conditionSchema");
const segmentSchema_1 = require("./segmentSchema");
const groupSchema_1 = require("./groupSchema");
const featureSchema_1 = require("./featureSchema");
const testSchema_1 = require("./testSchema");
const checkCircularDependency_1 = require("./checkCircularDependency");
const checkPercentageExceedingSlot_1 = require("./checkPercentageExceedingSlot");
const printError_1 = require("./printError");
const cliFormat_1 = require("../tester/cliFormat");
const ENTITY_NAME_REGEX = /^[a-zA-Z0-9_\-./]+$/;
const ENTITY_NAME_REGEX_ERROR = "Names must be alphanumeric and can contain _, -, and .";
const ATTRIBUTE_NAME_REGEX = /^[a-zA-Z0-9_\-/]+$/;
const ATTRIBUTE_NAME_REGEX_ERROR = "Names must be alphanumeric and can contain _, and -";
async function getAuthorsOfEntity(datasource, entityType, entityKey) {
const entries = await datasource.listHistoryEntries(entityType, entityKey);
const authors = Array.from(new Set(entries.map((entry) => entry.author)));
return authors;
}
async function lintProject(deps, options = {}) {
const { projectConfig, datasource } = deps;
let hasError = false;
function getFullPathFromKey(type, key, relative = false) {
const fileName = `${key}.${datasource.getExtension()}`;
let fullPath = "";
if (type === "attribute") {
fullPath = path.join(projectConfig.attributesDirectoryPath, fileName);
}
else if (type === "segment") {
fullPath = path.join(projectConfig.segmentsDirectoryPath, fileName);
}
else if (type === "feature") {
fullPath = path.join(projectConfig.featuresDirectoryPath, fileName);
}
else if (type === "group") {
fullPath = path.join(projectConfig.groupsDirectoryPath, fileName);
}
else if (type === "test") {
fullPath = path.join(projectConfig.testsDirectoryPath, fileName);
}
else {
throw new Error(`Unknown type: ${type}`);
}
if (relative) {
fullPath = path.relative(process.cwd(), fullPath);
}
return fullPath;
}
const keyPattern = options.keyPattern ? new RegExp(options.keyPattern) : null;
if (keyPattern) {
console.log("");
console.log(`Linting only keys matching pattern: ${keyPattern}`);
console.log("");
}
// lint attributes
const attributes = await datasource.listAttributes();
const attributeZodSchema = (0, attributeSchema_1.getAttributeZodSchema)();
if (!options.entityType || options.entityType === "attribute") {
const filteredKeys = !keyPattern
? attributes
: attributes.filter((key) => keyPattern.test(key));
if (filteredKeys.length > 0) {
console.log(`Linting ${filteredKeys.length} attributes...\n`);
}
for (const key of filteredKeys) {
const fullPath = getFullPathFromKey("attribute", key);
if (!ATTRIBUTE_NAME_REGEX.test(key)) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "attribute", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
console.log(cliFormat_1.CLI_FORMAT_RED, ` => Error: Invalid name: "${key}"`);
console.log(cliFormat_1.CLI_FORMAT_RED, ` ${ATTRIBUTE_NAME_REGEX_ERROR}`);
console.log("");
hasError = true;
}
try {
const parsed = await datasource.readAttribute(key);
const result = attributeZodSchema.safeParse(parsed);
if (!result.success) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "attribute", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
if ("error" in result) {
(0, printError_1.printZodError)(result.error);
}
hasError = true;
}
}
catch (e) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "attribute", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
console.log("");
console.log(e);
hasError = true;
}
}
}
const flattenedAttributes = await datasource.listFlattenedAttributes();
// lint segments
const segments = await datasource.listSegments();
const conditionsZodSchema = (0, conditionSchema_1.getConditionsZodSchema)(projectConfig, flattenedAttributes);
const segmentZodSchema = (0, segmentSchema_1.getSegmentZodSchema)(projectConfig, conditionsZodSchema);
if (!options.entityType || options.entityType === "segment") {
const filteredKeys = !keyPattern ? segments : segments.filter((key) => keyPattern.test(key));
if (filteredKeys.length > 0) {
console.log(`Linting ${filteredKeys.length} segments...\n`);
}
for (const key of filteredKeys) {
const fullPath = getFullPathFromKey("segment", key);
if (!ENTITY_NAME_REGEX.test(key)) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "segment", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
console.log(cliFormat_1.CLI_FORMAT_RED, ` => Error: Invalid name: "${key}"`);
console.log(cliFormat_1.CLI_FORMAT_RED, ` ${ENTITY_NAME_REGEX_ERROR}`);
console.log("");
hasError = true;
}
try {
const parsed = await datasource.readSegment(key);
const result = segmentZodSchema.safeParse(parsed);
if (!result.success) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "segment", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
if ("error" in result) {
(0, printError_1.printZodError)(result.error);
}
hasError = true;
}
}
catch (e) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "segment", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
console.log("");
console.log(e);
hasError = true;
}
}
}
// lint features
const features = await datasource.listFeatures();
const featureZodSchema = (0, featureSchema_1.getFeatureZodSchema)(projectConfig, conditionsZodSchema, flattenedAttributes, segments, features);
if (!options.entityType || options.entityType === "feature") {
const filteredKeys = !keyPattern ? features : features.filter((key) => keyPattern.test(key));
if (filteredKeys.length > 0) {
console.log(`Linting ${filteredKeys.length} features...\n`);
}
for (const key of filteredKeys) {
const fullPath = getFullPathFromKey("feature", key);
if (!ENTITY_NAME_REGEX.test(key)) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "feature", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
console.log(cliFormat_1.CLI_FORMAT_RED, ` => Error: Invalid name: "${key}"`);
console.log(cliFormat_1.CLI_FORMAT_RED, ` ${ENTITY_NAME_REGEX_ERROR}`);
console.log("");
hasError = true;
}
let parsed;
try {
parsed = await datasource.readFeature(key);
const result = featureZodSchema.safeParse(parsed);
if (!result.success) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "feature", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
if ("error" in result) {
(0, printError_1.printZodError)(result.error);
}
hasError = true;
}
}
catch (e) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "feature", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
console.log("");
console.log(e);
hasError = true;
}
if (parsed && parsed.required) {
try {
await (0, checkCircularDependency_1.checkForCircularDependencyInRequired)(datasource, key, parsed.required);
}
catch (e) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "feature", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
console.log(cliFormat_1.CLI_FORMAT_RED, ` => Error: ${e.message}`);
hasError = true;
}
}
}
}
// lint groups
const groups = await datasource.listGroups();
const groupZodSchema = (0, groupSchema_1.getGroupZodSchema)(projectConfig, datasource, features);
if (!options.entityType || options.entityType === "group") {
const filteredKeys = !keyPattern ? groups : groups.filter((key) => keyPattern.test(key));
if (filteredKeys.length > 0) {
console.log(`Linting ${filteredKeys.length} groups...\n`);
}
for (const key of filteredKeys) {
const fullPath = getFullPathFromKey("group", key);
if (!ENTITY_NAME_REGEX.test(key)) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
console.log(cliFormat_1.CLI_FORMAT_RED, ` => Error: Invalid name: "${key}"`);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "group", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
console.log(cliFormat_1.CLI_FORMAT_RED, ` ${ENTITY_NAME_REGEX_ERROR}`);
console.log("");
hasError = true;
}
let parsed;
try {
parsed = await datasource.readGroup(key);
const result = groupZodSchema.safeParse(parsed);
if (!result.success) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "group", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
if ("error" in result) {
(0, printError_1.printZodError)(result.error);
}
hasError = true;
}
}
catch (e) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "group", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
console.log("");
console.log(e);
hasError = true;
}
if (parsed) {
try {
await (0, checkPercentageExceedingSlot_1.checkForFeatureExceedingGroupSlotPercentage)(datasource, parsed, features);
}
catch (e) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
console.log(cliFormat_1.CLI_FORMAT_RED, ` => Error: ${e.message}`);
hasError = true;
}
}
}
}
// @TODO: feature cannot exist in multiple groups
// lint tests
const tests = await datasource.listTests();
const testsZodSchema = (0, testSchema_1.getTestsZodSchema)(projectConfig, features, segments);
if (!options.entityType || options.entityType === "test") {
const filteredKeys = !keyPattern ? tests : tests.filter((key) => keyPattern.test(key));
if (filteredKeys.length > 0) {
console.log(`Linting ${filteredKeys.length} tests...\n`);
}
for (const key of filteredKeys) {
const fullPath = getFullPathFromKey("test", key);
if (!ENTITY_NAME_REGEX.test(key)) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "test", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
console.log(cliFormat_1.CLI_FORMAT_RED, ` => Error: Invalid name: "${key}"`);
console.log(cliFormat_1.CLI_FORMAT_RED, ` ${ENTITY_NAME_REGEX_ERROR}`);
console.log("");
hasError = true;
}
try {
const parsed = await datasource.readTest(key);
const result = testsZodSchema.safeParse(parsed);
if (!result.success) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "test", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
if ("error" in result) {
(0, printError_1.printZodError)(result.error);
process.exit(1);
}
hasError = true;
}
}
catch (e) {
console.log(cliFormat_1.CLI_FORMAT_BOLD_UNDERLINE, fullPath);
if (options.authors) {
const authors = await getAuthorsOfEntity(datasource, "test", key);
console.log(` Authors: ${authors.join(", ")}\n`);
}
console.log("");
console.log(e);
hasError = true;
}
}
}
return hasError;
}
exports.lintPlugin = {
command: "lint",
handler: async function (options) {
const { rootDirectoryPath, projectConfig, datasource, parsed } = options;
const hasError = await lintProject({
rootDirectoryPath,
projectConfig,
datasource,
options: parsed,
}, {
keyPattern: parsed.keyPattern,
entityType: parsed.entityType,
authors: parsed.authors,
});
if (hasError) {
return false;
}
},
examples: [
{
command: "lint",
description: "lint all entities",
},
{
command: "lint --entityType=feature",
description: "lint only features",
},
{
command: "lint --entityType=segment",
description: "lint only segments",
},
{
command: "lint --entityType=group",
description: "lint only groups",
},
{
command: "lint --entityType=test",
description: "lint only tests",
},
{
command: 'lint --keyPattern="abc"',
description: `lint only entities with keys containing "abc"`,
},
],
};
//# sourceMappingURL=lintProject.js.map