@pega/custom-dx-components
Version:
Utility for building custom UI components
563 lines (456 loc) • 17.5 kB
JavaScript
import chalk from 'chalk';
import fs from 'fs';
import path from 'path';
import fetch from 'node-fetch';
import { join } from 'path';
import { Listr } from 'listr2';
import {
addDebugLog,
getHttpsAgent,
getConfigDefaults,
getPegaServerConfig,
convertIntoPascalCase,
getComponents,
getComponentDirectoryPath,
getDirectoryFiles,
setConfigImportRelativePath,
forceDefaultsUpdate,
constructCompleteUrl } from '../../util.js';
import {
TASKS_CONFIG_JSON_FILENAME,
TEMP_PATH,
PACKAGE_JSON_FILENAME,
LIST_COMPONENT_LIBRARY_ARCHIVES_SERVICE_REST_ENDPOINT,
COMPONENT_LIBRARY_ARCHIVE_SERVICE_REST_ENDPOINT,
TOKEN_PATH} from '../../constants.js';
export const SOURCE_OF_COMPONENT_TYPES = {
SERVER: 'Server',
LOCAL: 'Local'
};
export const setTasksConfig = async (libraryName, version, devBuild, currDir) => {
addDebugLog("setTasksConfig", `libraryName: ${libraryName}, version: ${version}, devBuild: ${devBuild}`, "");
const pegaConfigJsonPath = join(currDir, TASKS_CONFIG_JSON_FILENAME);
const sPegaData = fs.readFileSync(pegaConfigJsonPath, { encoding: 'utf8' });
const configData = JSON.parse(sPegaData);
let serverConfig = configData['server-config'];
serverConfig.devBuild = devBuild;
let compConfig = configData['component'];
compConfig.library = libraryName;
compConfig.version = version;
// update file
// stringify "4", makes the json string look like JSON in the file, formatted instead of a single line
fs.writeFileSync(pegaConfigJsonPath, JSON.stringify(configData, null, 4), { encoding: 'utf8',flag:'w' });
}
export const setPackageOrg = async (organization, currDir) => {
addDebugLog("setPackageOrg", "", "");
const packageJsonPath = join(currDir, PACKAGE_JSON_FILENAME);
const sPackage = fs.readFileSync(packageJsonPath, { encoding: 'utf8'});
const packageData = JSON.parse(sPackage);
packageData.organization = organization;
// update file
// stringify "4", makes the json string look like JSON in the file, formatted instead of a single line
fs.writeFileSync(packageJsonPath, JSON.stringify(packageData, null, 4), { encoding: 'utf8',flag:'w' });
}
export const updateConfig = async (
{
oldComponentKey,
newComponentKey,
organization,
library,
version,
currentDirectory,
}
) => {
addDebugLog("updateConfig", `oldComponentKey: ${oldComponentKey}, newComponentKey: ${newComponentKey}`, "");
let configData = fs.readFileSync(join(currentDirectory, "/config.json"), { encoding: 'utf8' });
configData = configData && JSON.parse(configData);
configData.name = newComponentKey;
if (configData.componentKey) configData.componentKey = newComponentKey;
configData.organization = organization;
configData.library = library;
configData.version = version;
// stringify "4", makes the json string look like JSON in the file, formatted instead of a single line
fs.writeFileSync(join(currentDirectory, "/config.json"), JSON.stringify(configData, null, 4), { encoding: 'utf8',flag:'w' });
};
export const updateFile = async (
fileName,
oldComponentKeyPC,
newComponentKeyPC,
currentDirectory,
) => {
addDebugLog("updateFile", `fileName: ${fileName}, oldComponentKeyPC: ${oldComponentKeyPC}, newComponentKeyPC: ${newComponentKeyPC}, currentDirectory: ${currentDirectory}`, "");
let fileData = fs.readFileSync(join(currentDirectory, "/", fileName), { encoding: 'utf8' });
if (fileData.indexOf(oldComponentKeyPC) >= 0) {
fileData = fileData.replaceAll(oldComponentKeyPC, newComponentKeyPC);
fs.writeFileSync(join(currentDirectory, "/", fileName), fileData, { encoding: 'utf8',flag:'w' });
}
};
export const renameDirectory = async(currentDirectory, targetDirectory) => {
if (!fs.existsSync(targetDirectory)) {
fs.mkdirSync(targetDirectory, { recursive: true });
}
const targetFileList = await getDirectoryFiles(currentDirectory);
for (const targetFile of targetFileList) {
const currentFilePath = join(currentDirectory, targetFile);
const targetFilePath = join(targetDirectory, targetFile);
fs.copyFileSync(currentFilePath, targetFilePath)
}
// fs.rm(currentDirectory, { recursive: true }, err => {
// if (err) {
// throw err;
// }
// });
fs.rmSync(currentDirectory, { recursive: true, force: true, maxRetries: 2 });
}
// rename the component
export const renameComponent = async (
componentKey,
library,
version,
currDir
) => {
addDebugLog("renameComponent", `library: ${library}, componentKey: ${componentKey}`, "");
const componentName = componentKey.split("_")[2];
const configDef = getConfigDefaults();
const orgLibName = `${configDef.organization}_${library}`;
const organization = configDef.organization;
const newComponentKey = `${orgLibName}_${componentName}`;
const oldComponentKey = componentKey;
const newComponentKeyPC = convertIntoPascalCase(newComponentKey);
const oldComponentKeyPC = convertIntoPascalCase(oldComponentKey);
const currentDirectory = await getComponentDirectoryPath(componentKey, currDir);
const targetDirectory = await getComponentDirectoryPath(newComponentKey, currDir);
const targetFileList = await getDirectoryFiles(currentDirectory);
for (const fileIndex in targetFileList) {
const fileName = targetFileList[fileIndex];
if (fileName === "config.json") {
await updateConfig(
{
oldComponentKey,
newComponentKey,
componentName,
organization,
library,
version,
currentDirectory,
}
);
}
else {
await updateFile(
fileName,
oldComponentKeyPC,
newComponentKeyPC,
currentDirectory );
}
} // for
// rename the directory
await renameDirectory(currentDirectory, targetDirectory);
return newComponentKey;
};
export const renameComponents = async(compList, libraryName, version, compDir) => {
for (const componentKey of compList) {
await renameComponent(componentKey, libraryName, version, compDir);
}
}
export const updateTempArchive = async(libraryName, version, devBuild) => {
const currentDirectory = process.cwd();
const tempStoreDirectory = join(currentDirectory, TEMP_PATH);
const configDef = getConfigDefaults();
// remove -dev if exists
const justVersion = version.replace("-dev", "");
const tasks = new Listr(
[
{
title: 'Update tasks.config',
task: async () => {
await setTasksConfig(libraryName, justVersion, devBuild, tempStoreDirectory);
}
},
{
title: 'Update package.json',
task: async () => {
await setPackageOrg(configDef.organization, tempStoreDirectory);
}
},
{
title: `Renaming components`,
task: async () => {
const compDir = join(tempStoreDirectory, "src", "components");
const compList = await getComponents(compDir);
await renameComponents(compList, libraryName, version, compDir);
}
}
],
{
exitOnError: true
}
);
await tasks.run().catch(err => {
console.log(chalk.bold.red(err.toString()));
process.exit(1);
});
}
export const getServerOrDirectoryQuestion = async () => {
addDebugLog("getLibraryVersionQuestion", "", "");
return [
{
name: 'importFrom',
type: 'rawlist',
message: `Import from`,
choices: [{name: 'Local directory', value: 'Directory'}, {name: 'Server', value: 'Server'}],
default: 'Directory'
}
];
};
export const getFilePathQuestions = async () => {
addDebugLog("getFilePathQuestions", "", "");
console.log("\nFile path examples:");
if (path.sep === "/") {
console.log("\tMac OS/Unix:\t/Users/name/...\n");
}
else {
console.log("\tWindows:\tC:\\Users\\name\\...\n");
}
// console.log(chalk.yellow("[Press arrows to search, press <tab> to select, press <enter> to return (after selecting via tab).]"));
console.log(chalk.yellow("[Manual typing, give search time to find paths]\n"));
const configDef = getConfigDefaults();
const currentDirectory = process.cwd();
const arPathParts = currentDirectory.split(path.sep);
let defaultPath = join(arPathParts[0], arPathParts[1], arPathParts[2], configDef.importRelativePath);
let startDirectory = join(arPathParts[0], arPathParts[1], arPathParts[2]);
if (path.sep === "/") {
defaultPath = "/".concat(defaultPath);
startDirectory = "/".concat(startDirectory).concat("/");
}
return [
{
name: 'filePath',
type: 'fuzzypath',
message: 'Enter full file path of zip file location:',
default: defaultPath,
excludePath: nodePath => nodePath.startsWith('node_modules'),
// excludePath :: (String) -> Bool
// excludePath to exclude some paths from the file-system scan
excludeFilter: nodePath => nodePath.indexOf('/.') >= 0,
// excludeFilter :: (String) -> Bool
// excludeFilter to exclude some paths from the final list, e.g. '.'
itemType: 'directory',
// itemType :: 'any' | 'directory' | 'file'
// specify the type of nodes to display
// default value: 'any'
// example: itemType: 'file' - hides directories from the item list
rootPath: startDirectory,
// rootPath :: String
// Root search directory
suggestOnly: false,
// suggestOnly :: Bool
// Restrict prompt answer to available choices or use them as suggestions
depthLimit: 2,
// depthLimit :: integer >= 0
// Limit the depth of sub-folders to scan
// Defaults to infinite depth if undefined
}
];
};
export const getFileNameQuestions = async (zipFileList) => {
addDebugLog("getFileNameQuestions", "", "");
return [
{
name: 'fileName',
type: 'rawlist',
message: `Select zip file`,
choices: zipFileList
},
];
};
export const updateSavedFilePath = async(filePath) => {
const currentDirectory = process.cwd();
const arPathParts = currentDirectory.split(path.sep);
let defaultPath = join(path.sep, arPathParts[0], arPathParts[1], arPathParts[2]);
if (path.sep != "/") {
// windows
if (defaultPath.indexOf(path.sep) === 0) {
// if start with \\, then remove
defaultPath = defaultPath.substring(1);
}
if (filePath.indexOf(defaultPath) === 0) {
let relativePath = "";
if (filePath !== defaultPath) {
// same starting path, so we can save the relative
relativePath = filePath.replace(defaultPath.concat(path.sep), "");
}
await setConfigImportRelativePath(relativePath);
await forceDefaultsUpdate();
}
}
else {
// mac
if (filePath.indexOf(defaultPath) === 0) {
let relativePath = "";
if (filePath !== defaultPath) {
// same starting path, so we can save the relative
relativePath = filePath.replace(defaultPath.concat("/"), "");
}
await setConfigImportRelativePath(relativePath);
await forceDefaultsUpdate();
}
}
}
async function fetchComponentLibraryArchivesFromInfinity(apiUrl, headers) {
addDebugLog("fetchComponentLibraryArchivesFromInfinity", `apiUrl: ${apiUrl}`, "");
const serverConfig = await getPegaServerConfig();
return new Promise((resolve, reject) => {
let status = 500;
fetch(apiUrl, {
method: 'GET',
agent: getHttpsAgent(serverConfig),
headers
})
.then((response) => {
status = response.status;
if (status === 401) {
throw new Error('Error occurred in authentication. Please regenerate using authenticate');
}
else if (status === 404){
throw new Error('404: Server resource not found');
}
else if (status === 405){
throw new Error('405: Server method not allowed');
}
else if (status === 408){
throw new Error('408: Server timed out');
}
return response.json();
})
.then((resp) => {
// const respData = JSON.parse(resp);
if (status === 401) {
throw new Error('Error occurred in authentication. Please regenerate using authenticate');
// console.log(accessTokenUri, refreshToken);
/* TODO - Handle refresh_token */
} else if (status === 200) {
resolve(resp);
} else {
throw new Error(`${resp.message}`);
}
})
// eslint-disable-next-line prefer-promise-reject-errors
.catch((e) => reject(`${chalk.bold.red(e)}`));
});
}
export const getServerLibraryArchives = async() => {
addDebugLog("getServerLibraryArchives", '', "");
const defaultPegaServerConfig = await getPegaServerConfig();
let url = constructCompleteUrl(
defaultPegaServerConfig.server,
LIST_COMPONENT_LIBRARY_ARCHIVES_SERVICE_REST_ENDPOINT
);
return new Promise((resolve, reject) => {
try {
fs.readFile(TOKEN_PATH, 'utf8', (err, data) => {
if (err) {
// eslint-disable-next-line prefer-promise-reject-errors
reject(`\n${chalk.bold.red('Error occurred in authentication. Please regenerate using authenticate')}`);
}
if (data) {
const { access_token: accessToken, token_type: tokenType } = JSON.parse(data);
const headers = {};
headers.Authorization = `${tokenType} ${accessToken}`;
fetchComponentLibraryArchivesFromInfinity(url, headers)
.then((response) => resolve(response))
.catch((e) => reject(e));
}
});
} catch (error) {
console.log(`\n${chalk.bold.red(error)}`);
// eslint-disable-next-line prefer-promise-reject-errors
reject(`${chalk.bold.red(error)}`);
}
});
}
export const getLibraryArchiveQuestion = async (archiveVersions) => {
addDebugLog("getLibraryArchiveQuestion", `archiveVersions: ${archiveVersions}`, "");
return [
{
name: 'archiveData',
type: 'rawlist',
message: `Select an archive`,
choices: archiveVersions
}
];
};
async function fetchComponentLibraryArchiveFromInfinity(apiUrl, headers) {
addDebugLog("fetchComponentLibraryArchivesFromInfinity", `apiUrl: ${apiUrl}`, "");
const serverConfig = await getPegaServerConfig();
return new Promise((resolve, reject) => {
let status = 500;
fetch(apiUrl, {
method: 'GET',
agent: getHttpsAgent(serverConfig),
headers
})
.then((response) => {
status = response.status;
if (status === 401) {
throw new Error('Error occurred in authentication. Please regenerate using authenticate');
}
else if (status === 404){
throw new Error('404: Server resource not found');
}
else if (status === 405){
throw new Error('405: Server method not allowed');
}
else if (status === 408){
throw new Error('408: Server timed out');
}
return response.json();
})
.then((resp) => {
// const respData = JSON.parse(resp);
if (status === 401) {
throw new Error('Error occurred in authentication. Please regenerate using authenticate');
// console.log(accessTokenUri, refreshToken);
/* TODO - Handle refresh_token */
} else if (status === 200) {
resolve(resp);
} else {
throw new Error(`${resp.message}`);
}
})
// eslint-disable-next-line prefer-promise-reject-errors
.catch((e) => reject(`${chalk.bold.red(e)}`));
});
}
export const getServerLibraryArchive = async(appName, fileName, fileType, ruleSet, ruleSetVersion) => {
addDebugLog("getServerLibraryArchives", '', "");
const defaultPegaServerConfig = await getPegaServerConfig();
let url = constructCompleteUrl(
defaultPegaServerConfig.server,
COMPONENT_LIBRARY_ARCHIVE_SERVICE_REST_ENDPOINT
);
let urlParams = `/app-name/${appName}/file-name/${fileName}/file-type/${fileType}/rulesetname/${ruleSet}/rulesetversion/${ruleSetVersion}`;
url = url.concat(urlParams);
return new Promise((resolve, reject) => {
try {
fs.readFile(TOKEN_PATH, 'utf8', (err, data) => {
if (err) {
// eslint-disable-next-line prefer-promise-reject-errors
reject(`\n${chalk.bold.red('Error occurred in authentication. Please regenerate using authenticate')}`);
}
if (data) {
const { access_token: accessToken, token_type: tokenType } = JSON.parse(data);
const headers = {};
headers.Authorization = `${tokenType} ${accessToken}`;
fetchComponentLibraryArchiveFromInfinity(url, headers)
.then((response) => resolve(response))
.catch((e) => reject(e));
}
});
} catch (error) {
console.log(`\n${chalk.bold.red(error)}`);
// eslint-disable-next-line prefer-promise-reject-errors
reject(`${chalk.bold.red(error)}`);
}
});
}