UNPKG

@pega/custom-dx-components

Version:

Utility for building custom UI components

451 lines (401 loc) 15.7 kB
import path from 'path'; import fs from 'fs'; import chalk from 'chalk'; import inquirer from 'inquirer'; import Ajv from 'ajv'; import { COMPONENT_SCHEMA } from '../../constants.js'; import { getComponentDirectoryPath, getComponents, isComponentValidLength, getConfigDefaults, getLibraryBasedCL } from '../../util.js'; const ajv = new Ajv({ allErrors: true }); const combinationTypes = ['PAGE', 'CASE']; const configDef = getConfigDefaults(); const isLibraryBasedCL = getLibraryBasedCL(); const allowedSubtypes = [ ...new Set([ ...COMPONENT_SCHEMA.subtype.field.map(({ value }) => value), ...COMPONENT_SCHEMA.subtype.template.map(({ value }) => value), ...COMPONENT_SCHEMA.subtype.widget.map(({ value }) => value) ]) ]; // additional subtypes are manually added below const allowedOverrideSubtypes = [ ...new Set([ ...COMPONENT_SCHEMA.subtype.field.map(({ value }) => value), ...COMPONENT_SCHEMA.subtype.template.map(({ value }) => value), ...COMPONENT_SCHEMA.subtype.widget.map(({ value }) => value) ]), "ACTION", "DATA_CAPTURE", "SUMMARY", "CASEVIEW", "DATAVIEW", "LIST" ]; const schema = { type: 'object', properties: { baseComponentName: { type: 'string' }, componentKey: { type: 'string', pattern: '^[A-Za-z0-9]+_[A-Za-z0-9]+_[A-Za-z0-9]+$' }, name: { type: 'string', pattern: '^[A-Za-z0-9]+_[A-Za-z0-9]+_[A-Za-z0-9]+$' }, label: { type: 'string' }, description: { type: 'string' }, organization: { type: 'string' }, version: { type: 'string' }, library: { type: 'string' }, type: { type: 'string', enum: COMPONENT_SCHEMA.type.map(({ value }) => value) }, subtype: { anyOf: [{ type: 'string' }, { type: 'array' }], enum: allowedSubtypes }, icon: { type: 'string' }, properties: { type: 'array', uniqueItems: true, items: { anyOf: [ { type: 'object', properties: { name: { type: 'string' }, label: { type: 'string' }, format: { type: 'string' } }, required: ['name', 'label', 'format'] }, { type: 'object', properties: { format: { type: 'string' }, source: { type: 'object', properties: { name: { type: 'string' }, label: { type: 'string' }, format: { type: 'string' } }, required: ['name', 'label', 'format'] }, cascadeElements: { type: 'array', uniqueItems: true, items: { anyOf: [ { type: 'object', properties: { key: { type: 'string'}, name: { type: 'string' }, label: { type: 'string' }, format: { type: 'string' } }, required: ['key','name', 'label', 'format'] } ] } }, }, required: ['format'] }, { type: 'object', properties: { label: { type: 'string' }, format: { type: 'string' }, properties: { type: 'array', uniqueItems: true, items: { anyOf: [ { type: 'object', properties: { name: { type: 'string' }, label: { type: 'string' }, format: { type: 'string' } }, required: ['name', 'label', 'format'] }, { type: 'object', properties: { variant: { type: 'string' }, label: { type: 'string' }, format: { type: 'string' } }, required: ['variant', 'label', 'format'] }, { type: 'object', properties: { format: { type: 'string' }, elements: { type: 'array', uniqueItems: true, items: { anyOf: [ { type: 'object', properties: { key: { type: 'string' }, name: { type: 'string' }, label: { type: 'string' }, format: { type: 'string' }, variant: { type: 'string' } }, required: ['key', 'format', 'label'] } ] } } }, required: ['format', 'elements'] }, ] } } }, required: ['label', 'format'], } ] } }, defaultConfig: { type: 'object' } }, required: ['componentKey', 'name', 'description', 'type', 'subtype', 'properties'], additionalProperties: true }; const overrideSchema = { type: 'object', properties: { baseComponentName: { type: 'string' }, componentKey: { type: 'string', pattern: '^[A-Za-z0-9]+$' }, name: { type: 'string', pattern: '^[A-Za-z0-9]+$' }, label: { type: 'string' }, description: { type: 'string' }, organization: { type: 'string' }, version: { type: 'string' }, library: { type: 'string' }, type: { type: 'string', enum: COMPONENT_SCHEMA.type.map(({ value }) => value) }, subtype: { anyOf: [{ type: 'string' }, { type: 'array' }], enum: allowedOverrideSubtypes }, icon: { type: 'string' }, properties: { type: 'array', uniqueItems: true, items: { anyOf: [ { type: 'object', properties: { name: { type: 'string' }, label: { type: 'string' }, format: { type: 'string' } }, required: ['name', 'label', 'format'] }, { type: 'object', properties: { format: { type: 'string' }, source: { type: 'object', properties: { name: { type: 'string' }, label: { type: 'string' }, format: { type: 'string' } }, required: ['name', 'label', 'format'] }, cascadeElements: { type: 'array', uniqueItems: true, items: { anyOf: [ { type: 'object', properties: { key: { type: 'string'}, name: { type: 'string' }, label: { type: 'string' }, format: { type: 'string' } }, required: ['key','name', 'label', 'format'] } ] } }, }, required: ['format'] }, { type: 'object', properties: { label: { type: 'string' }, format: { type: 'string' }, properties: { type: 'array', uniqueItems: true, items: { anyOf: [ { type: 'object', properties: { name: { type: 'string' }, label: { type: 'string' }, format: { type: 'string' } }, required: ['name', 'label', 'format'] }, { type: 'object', properties: { variant: { type: 'string' }, label: { type: 'string' }, format: { type: 'string' } }, required: ['variant', 'label', 'format'] }, { type: 'object', properties: { format: { type: 'string' }, elements: { type: 'array', uniqueItems: true, items: { anyOf: [ { type: 'object', properties: { key: { type: 'string' }, name: { type: 'string' }, label: { type: 'string' }, format: { type: 'string' }, variant: { type: 'string' } }, required: ['key', 'format', 'label'] } ] } } }, required: ['format', 'elements'] }, ] } } }, required: ['label', 'format'], } ] } }, defaultConfig: { type: 'object' } }, required: ['name', 'type', 'subtype', 'properties', 'componentKey'], additionalProperties: true }; const validateSchema = ajv.compile(schema); const validateOverrideSchema = ajv.compile(overrideSchema); export default async (option) => { // addDebugLog("validate","", ""); let componentToValidate = null; let throwTheError = false; if (option.params && option.params[3]) { componentToValidate = option.params[3]; throwTheError = true; } else if (option && option.task === 'validateSchema') { const localComponents = await getComponents(); const answer = await inquirer.prompt({ name: 'component', type: 'rawlist', message: 'Select component to validate', choices: localComponents }); componentToValidate = answer.component; } else if (option.name) { componentToValidate = option.value; throwTheError = true; } else { componentToValidate = option; throwTheError = true; } if (!isComponentValidLength(componentToValidate)) { throw new Error(`Invalid: componentName is too long.`); } if (isLibraryBasedCL) { if (componentToValidate.indexOf(configDef.currentOrgLib + "_") != 0) { const arPrefix = componentToValidate.split("_"); const sPrefix = `${arPrefix[0]}_${arPrefix[1]}`; throw new Error(`Component: ${componentToValidate} prefix invalid.\n --> '${sPrefix}' prefix does not match configuration '${configDef.currentOrgLib}'. \n --> Org_Lib prefix of component must match Organization and Library values \n --> in package.json and tasks.config.json respectively.`); } } const componentDirectory = await getComponentDirectoryPath(componentToValidate); let configFileName = "config.json"; const configFile = path.join(componentDirectory, configFileName); let configData; try { configData = fs.readFileSync(path.resolve(configFile), 'utf8'); } catch (err) { // for now, if now file just end console.log(`${chalk.bold.yellow('Config file not available for: ' + componentToValidate)}`); return; } //const data = JSON.parse(fs.readFileSync(path.resolve(configFile), 'utf8')); let data; try { data = JSON.parse(configData); } catch (er) { throw new Error(`Invalid schema json in config.json: ${er}`); } const valid = validateSchema(data); if (valid) { if (data.description === "") { throw new Error(`Invalid schema json in config.json: 'description' cannot be blank.`); process.exit(1); } if ( componentToValidate && !/^[A-Za-z0-9]+_[A-Za-z0-9]+_[A-Za-z0-9]+$/.test(componentToValidate) ) { if (throwTheError) { throw new Error (`${chalk.redBright(`Error: Invalid name of component '${componentToValidate}' .\n Component name (and component folder) MUST be of the form: 'Org_Lib_ComponentName'.`)}`); } console.log(`${chalk.redBright(`Error: Invalid name of component '${componentToValidate}' .\n Component name (and component folder) MUST be of the form: 'Org_Lib_ComponentName'.`)}`); process.exit(1); } if (isLibraryBasedCL) { if (data.library !== configDef.library) { throw new Error (`${chalk.redBright(`Error: Invalid library in config.json for component '${data.name}'.\n -->Library: '${data.library}' must match tasks.config library: '${configDef.library}'.`)}`); } if (data.organization !== configDef.organization) { throw new Error (`${chalk.redBright(`Error: Invalid organization in config.json for component '${data.name}'.\n -->Organization: '${data.organization}' must match package.json organization: '${configDef.organization}'.`)}`); } if (data.componentKey.indexOf(configDef.currentOrgLib + "_") !== 0) { const arPrefix = data.componentKey.split("_"); const sPrefix = `${arPrefix[0]}_${arPrefix[1]}` throw new Error (`${chalk.redBright(`Error: Invalid componentKey in config.json for component '${data.name}'.\n --> Component prefix Org_Lib: '${sPrefix}' must match '${configDef.currentOrgLib}'. \n --> These represent organization and library values found in package.json and tasks.config.json respectively. `)}`); } if (data.componentKey !== componentToValidate) { throw new Error (`${chalk.redBright(`Error: Invalid folder name or componentKey in config.json for component '${data.name}'.\n --> Component folder name: '${componentToValidate}' must match component key '${data.componentKey}'.\n --> NOTE: Values are case sensitve.`)}`); } if (data.name !== componentToValidate) { throw new Error (`${chalk.redBright(`Error: Invalid folder name or name in config.json for component '${data.name}'.\n --> Component folder name: '${componentToValidate}' must match name '${data.name}'.\n --> NOTE: Values are case sensitve.`)}`); } } else { if (data.componentKey !== componentToValidate) { throw new Error (`${chalk.redBright(`Error: Invalid folder name or componentKey in config.json for component '${data.name}'.\n --> Component folder name: '${componentToValidate}' must match component key '${data.componentKey}'.\n --> NOTE: Values are case sensitve.`)}`); } if (data.name !== componentToValidate) { throw new Error (`${chalk.redBright(`Error: Invalid folder name or name in config.json for component '${data.name}'.\n --> Component folder name: '${componentToValidate}' must match name '${data.name}'.\n --> NOTE: Values are case sensitve.`)}`); } } console.log(`${chalk.bold.green(componentToValidate)} schema is valid`); } else { throw new Error(`Invalid: ${ajv.errorsText(validateSchema.errors)}`); } };