UNPKG

@featurevisor/core

Version:

Core package of Featurevisor for Node.js usage

380 lines 17.3 kB
"use strict"; 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