@pega/custom-dx-components
Version:
Utility for building custom UI components
461 lines (346 loc) • 14.4 kB
JavaScript
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", "-");
};