UNPKG

@pega/custom-dx-components

Version:

Utility for building custom UI components

461 lines (346 loc) 14.4 kB
import fs from 'fs'; import { promisify } from 'util'; import { join } from 'path'; import inquirer from 'inquirer'; import ncp from 'ncp'; import chalk from 'chalk'; import { Listr } from 'listr2'; import { sanitize, checkPathAccess, showVersion, getDirectoryFiles, addDebugLog, checkLibraryAndArchives, getConfigDefaults, getLibraryBased, unZipFromArchive, zipVersionAndArchive, getLibraryArchiveDirectories, hasLibraryAndVersion, cleanUpTemp, hasArchives, restoreFromArchive, forceDefaultsUpdate, getUseInputConfig, getInputConfigForCommand } from '../../util.js'; import { TASKS_CONFIG_JSON_FILENAME, ARCHIVES_PATH, TEMP_PATH } from '../../constants.js'; import { getFilePathQuestions, getFileNameQuestions, updateTempArchive, updateSavedFilePath } from './helper.js'; import { showCurrentStatus } from '../show-status/index.js'; export const DXCB_CONFIG_INTERNAL_JSON_FILENAME = 'src/dxcb.config.json'; const currentDirectory = process.cwd(); const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME); import { convertIntoPascalCase, getComponentDirectoryPath } from '../../util.js'; const copy = promisify(ncp); export const updateConfig = async ( { oldComponentKey, newComponentKey, library, version, targetDirectory, }, options, onlyCompanion = false ) => { addDebugLog("updateConfig", `oldComponentKey: ${oldComponentKey}, newComponentKey: ${newComponentKey}`, ""); let configData = fs.readFileSync(join(targetDirectory, "/config.json"), { encoding: 'utf8' }); configData = configData && JSON.parse(configData); configData.name = newComponentKey; if (configData.componentKey) configData.componentKey = newComponentKey; configData.library = library; configData.version = version; // stringify "4", makes the json string look like JSON in the file, formated instead of a single line fs.writeFileSync(join(targetDirectory, "/config.json"), JSON.stringify(configData, null, 4), { encoding: 'utf8',flag:'w' }); }; export const updateFile = async ( fileName, oldComponentKeyPC, newComponentKeyPC, targetDirectory, ) => { addDebugLog("updateFile", `fileName: ${fileName}, oldComponentKeyPC: ${oldComponentKeyPC}, newComponentKeyPC: ${newComponentKeyPC}, targetDirectory: ${targetDirectory}`, ""); let fileData = fs.readFileSync(join(targetDirectory, "/", fileName), { encoding: 'utf8' }); if (fileData.indexOf(oldComponentKeyPC) >= 0) { fileData = fileData.replaceAll(oldComponentKeyPC, newComponentKeyPC); fs.writeFileSync(join(targetDirectory, "/", fileName), fileData, { encoding: 'utf8',flag:'w' }); } }; // rename the component export const renameComponent = async ( componentKey, library, version, options ) => { addDebugLog("renameComponent", `library: ${library}, componentKey: ${componentKey}`, ""); const componentName = componentKey.split("_")[2]; const configDef = getConfigDefaults(); const newComponentKey = `${library}_${componentName}`; const oldComponentKey = componentKey; const newComponentKeyPC = convertIntoPascalCase(newComponentKey); const oldComponentKeyPC = convertIntoPascalCase(oldComponentKey); const currentDirectory = await getComponentDirectoryPath(componentKey); const targetDirectory = await getComponentDirectoryPath(newComponentKey); // custom component try { fs.renameSync(currentDirectory, targetDirectory); } catch(err) { console.log(err) } const targetFileList = await getDirectoryFiles(targetDirectory); for (var fileIndex in targetFileList) { const fileName = targetFileList[fileIndex]; if (fileName === "config.json") { await updateConfig( { oldComponentKey, newComponentKey, componentName, library, version, targetDirectory, }, options ); } else { await updateFile( fileName, oldComponentKeyPC, newComponentKeyPC, targetDirectory ); } } // for return newComponentKey; }; export const getPackgeOrg = async(packagePath) => { const sPackData = fs.readFileSync(packagePath, { encoding: 'utf8' }); const packData = sPackData && JSON.parse(sPackData); return packData.organization; } export const getLocalConfigData = async(tasksPath) => { let configData = { library: "", version: "", buildVersion: "", devBuild: false}; const sTaskData = fs.readFileSync(tasksPath, { encoding: 'utf8' }); const taskData = sTaskData && JSON.parse(sTaskData); const devBuild = taskData["server-config"].devBuild; configData.library = taskData["component"].library; configData.version = taskData["component"].version; configData.devBuild = devBuild; configData.buildVersion = devBuild ? configData.version.concat("-dev") : configData.version; return configData; } export const moveComp = async(componentKey, orgLibName, version, copyShared, options) => { // put lib/version into temp directory const currentDirectory = process.cwd(); const tempDirectory = join(currentDirectory, TEMP_PATH); const archiveDirectory = join (currentDirectory, ARCHIVES_PATH, `${orgLibName}`, `${version}`); const fileName = `${orgLibName}_${version}.zip`; const archFileName = join(archiveDirectory, fileName); await unZipFromArchive(archFileName); // rename the component const newComponentKey = await renameComponent(componentKey, orgLibName, version); const currentCompPath = join(currentDirectory, "src", "components"); const tempCompPath = join(tempDirectory, "src", "components"); const currentComponentsPath = join(currentCompPath, `${newComponentKey}`); const tempDirectoryComponents = join(tempCompPath, `${newComponentKey}`); const currentSharedPath = join(currentCompPath, "shared"); const targetSharedPatah = join(tempCompPath, "shared"); // copy component into temp directory fs.cpSync(currentComponentsPath, tempDirectoryComponents, { recursive: true, force: true }); // remove from components fs.rmSync(currentComponentsPath, { recursive: true, force: true, maxRetries: 2 }); // copy shared if (copyShared) { fs.cpSync(currentSharedPath, targetSharedPatah, { recursive: true, force: true }); } console.log(`\nCopied ${chalk.green(`${componentKey}`)} to ${chalk.green(`${orgLibName}/${version}`)} as ${chalk.green(`${newComponentKey}`)}\n`); // rearchive temp directory await zipVersionAndArchive(orgLibName, version, tempDirectory); // delete temp directory fs.rmSync(tempDirectory, { recursive: true, force: true, maxRetries: 2 }); } export const importArch = async(filePath, fileName, hadArchive) => { addDebugLog("importArch", `filePath: ${filePath}, fileName: ${fileName}`, ""); const currentDirectory = process.cwd(); const tempDirectory = join(currentDirectory, TEMP_PATH); const zipFilePath = join(filePath, fileName); await unZipFromArchive(zipFilePath); // check to see if have a tasks.config.json file, if not, archive not good const tempTasksConfig = join(tempDirectory, "tasks.config.json"); const tempPackage = join(tempDirectory, "package.json"); if (!fs.existsSync(tempTasksConfig) || !fs.existsSync(tempPackage)) { console.log(chalk.red.bold(`Zip file: ${fileName} not a compatible DX Component archive.`)); console.log(chalk.red.bold("Cleaning up...")); fs.rmSync(tempDirectory, { recursive: true, force: true, maxRetries: 5 }); console.log(chalk.red.bold("Done.")); process.exit(1); } // need to get info from tasks.config and package.json // we won't rely on name of file, as owner can change it let organization = await getPackgeOrg(tempPackage); let localConfig = await getLocalConfigData(tempTasksConfig); let libName = localConfig.library; let orgLibName = `${organization}_${libName}`; const version = localConfig.buildVersion; const devBuild = localConfig.devBuild; const configDef = getConfigDefaults(); if (organization !== configDef.organization) { console.log(`\nImported ${chalk.yellow(`${orgLibName}/${version}`)} is not the same organization as your current organization: ${chalk.yellow(`${configDef.organization}`)}.`); console.log(`\nTo import, the organization of the components will be changed to match your organization.`); console.log(`\n\t>>> If you don't want this to happen, then end and create a new project with an organization`); console.log(`\t of ${chalk.yellow(`${organization}`)} and import there.\n`); const proceedAnswers = await inquirer.prompt([ { name: 'okToContinue', type: 'confirm', message: `Ok to proceed ?`, default: true } ]); if (proceedAnswers.okToContinue) { // check if new version matches existing // -dev is already on version, if present, so "false", for devBuild here const alreadyExists = await hasLibraryAndVersion(libName, version, false); if (alreadyExists) { console.log(`${chalk.yellow(`${configDef.organization}_${libName}/${version}`)} already exists.`); const archDirectories = await getLibraryArchiveDirectories(); const newLibNameAnswers = await inquirer.prompt([ { name: 'libraryName', message: 'Enter new libray name', validate: value => { /* value should not be empty It should not have spaces It should not start with a number Only case-insensitive alphanumeric values are allowed */ if (value && !/^\d/.test(value) && value === sanitize(value)) { if (archDirectories && archDirectories.length > 0) { const newOrgLib = `${configDef.organization}_${value}`; if (archDirectories.includes(newOrgLib)) { return `Library ${value} already exists.`; } else { return true; } } else { return true; } } else { return 'Only alphanumeric values are allowed, starting with alphabets, no spaces.'; } } } ]); libName = newLibNameAnswers.libraryName; } // rename orgLibName orgLibName = `${configDef.organization}_${libName}`; // update temp with new info await updateTempArchive(libName, version, devBuild); } else { process.exit(); } } // rearchive temp directory await zipVersionAndArchive(orgLibName, version, tempDirectory); // delete temp directory fs.rmSync(tempDirectory, { recursive: true, force: true, maxRetries: 2 }); if (hadArchive) { console.log(`\n${chalk.green(`${fileName}`)} has been imported!`); console.log(`You can switch to ${chalk.green(`${orgLibName}/${version}`)} via npm run switchLib.\n`); } else { const switchToFileName = `${orgLibName}_${version}.zip`; const tasks = new Listr( [ { title: `Restoring ${orgLibName}/${version}`, task: async () => { await restoreFromArchive(switchToFileName, orgLibName, version); } } ], { concurrent: false, exitOnError: true } ); console.log(`\n${chalk.green(`${fileName}`)} has been imported!`); await tasks.run().catch(err => { console.log(chalk.bold.red(err.toString())); process.exit(1); }); console.log(`${chalk.bold.green(`\nSwitched to ${orgLibName}/${version}!\n`)}`); await forceDefaultsUpdate(); await showCurrentStatus(); console.log('\n***************************************************************'); console.log(chalk.bold.yellow('Dependencies have changed between versions, recommend updating.')); console.log(`>>> PLEASE run ${chalk.bold.yellow("'npm update'")}.`); console.log('***************************************************************\n'); } } export const getZipFileList = async(filePath) => { if (fs.existsSync(filePath)) { return fs .readdirSync(filePath, { withFileTypes: true }) .filter(dirent => !dirent.isDirectory() && dirent.name.match(/.*\.(zip?)/ig)) .map(dirent => dirent.name); } else { console.log(chalk.red.bold(`File path: ${filePath} does not exist or no permissions.`)); process.exit(1); } } export default async options => { await showVersion(); // await checkLibraryAndArchives(); await checkPathAccess(pegaConfigJsonPath); const isLibraryBased = getLibraryBased(); const useInputConfig = getUseInputConfig(); if (!isLibraryBased) { console.log(`Command only supported for ${chalk.bold.green('library mode')} components.`) process.exit(); } const hasArch = await hasArchives(); await cleanUpTemp(); addDebugLog("importArchive", "", "+"); if (options.params.length >= 5) { const filePath = options.params[3]; const fileName = options.params[4]; await importArch( filePath, fileName, hasArch); } else { let filePath; let fileName; if (useInputConfig) { const inputConfig = await getInputConfigForCommand("importLibVersion"); filePath = inputConfig.fileLocation; fileName = inputConfig.fileName; } else { const filePathQuestions = await getFilePathQuestions(); const filePathAnswers = await inquirer.prompt(filePathQuestions); ({ filePath } = filePathAnswers); const zipFileList = await getZipFileList(filePath); if (!zipFileList || zipFileList.length === 0) { console.log(chalk.red.bold(`No zip files exist at path: ${filePath}`)); process.exit(); } await updateSavedFilePath(filePath); const fileNameQuestions = await getFileNameQuestions(zipFileList); const fileNameAnswers = await inquirer.prompt(fileNameQuestions); ({fileName} = fileNameAnswers); } await importArch( filePath, fileName, hasArch); // reapply to save if changed, because imported task.config may have overriden and we want // to keep what was entered as the latest await updateSavedFilePath(filePath); } addDebugLog("importArchive", "END", "-"); };