@pega/custom-dx-components
Version:
Utility for building custom UI components
1,566 lines (1,241 loc) • 93.2 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { join } from 'path';
import { promisify } from 'util';
import https from 'https';
import crypto from 'node:crypto';
import { createRequire } from 'module';
import {jwtDecode} from 'jwt-decode';
import archiver from 'archiver';
import {unarchive} from 'unarchive';
import ora from 'ora';
import chalk from 'chalk';
import mustache from 'mustache';
import pascalCase from 'pascalcase';
import fetch from 'node-fetch';
// import checkGit from 'check-git';
import * as currentPath from './currentPath.cjs';
import {
COMPONENTS_DIRECTORY_PATH,
COMPONENTS_CONFIG_FILE_NAME,
TASKS_CONFIG_JSON_FILENAME,
INPUT_CONFIG_JSON_FILENAME,
PACKAGE_JSON_FILENAME,
PACKAGE_LOCK_JSON_FILENAME,
TOKEN_PATH,
OOTB_COMPONENT_SERVICE_REST_ENDPOINT,
DELETE_COMPONENT_SERVICE_REST_ENDPOINT,
LP_DELETE_COMPONENT_SERVICE_REST_ENDPOINT,
DELETE_COMPONENT_LIBRARY_SERVICE_REST_ENDPOINT,
OOTB_COMPONENTS,
USE_PROMOTED_WEB_PACK,
BOOTSTRAP_CONFIG,
LIBRARY_BASED,
LIBRARY_BASED_CL,
USE_INPUT_CONFIG,
SHARED_DIRECTORY,
COMPONENT_SCHEMA,
ARCHIVES_PATH,
BIN_ARCHIVES_PATH,
TEMP_PATH,
JEST_TESTS_FUNCTIONAL_PATH,
IMPORT_RELATIVE_PATH,
EXPORT_RELATIVE_PATH,
MAX_RFT_CHARS
} from './constants.js';
const access = promisify(fs.access);
let showDebug = false;
let debugLevel = 1;
let haveDependencyDifference = false;
let addAboutData = true;
// updated upon start
let defaultConfigs = {
serverType: "",
isLibraryBased: false,
isLibraryBasedCL: false,
devBuild: true,
devServerPort: "",
library: "",
organization: "",
currentOrgLib: "",
version: "",
buildVersion: "",
displayLibVersion: "",
showDebug: false,
importRelativePath: "",
exportRelativePath: "",
useInputConfig: false
};
let pegaConfig = null;
let pegaData = null;
let sPegaData;
let sPegaServerConfig;
export const modAddAboutData = (addInAboutData) => {
// used by jest to not put in to match assets without aboutData
addAboutData = addInAboutData;
}
export const addDebugLog = (sFunctionName, sLogData, sIncDec) => {
let sLog;
if (showDebug) {
if (sIncDec === "-") {
debugLevel--;
}
sLog = `>> log ${debugLevel} `;
for (let i =0; i < debugLevel; i++) {
sLog = sLog.concat("-");
}
sLog = sLog.concat(sFunctionName).concat(": ").concat(sLogData);
console.log(`${chalk.hex('#A0522D')(sLog)}`);
if (sIncDec === "+") {
debugLevel++;
}
}
}
export const setUpDefaults = async () => {
// defaults, start up function will set these up
// so that won't have to continuously hit files to get data
// thus saving time.
let retrievedConfigData = {
serverType: "",
isLibraryBased: false,
isLibraryBasedCL: false,
devBuild: true,
devServerPort: "",
library: "",
organization: "",
currentOrgLib: "",
version: "",
buildVersion: "",
displayLibVersion: "",
showDebug: false,
importRelativePath: "",
exportRelativePath: "",
useInputConfig: false
};
// retrievedConfigData
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
try {
// force to throw error and not console log
await checkPathAccess(pegaConfigJsonPath, {}, true);
let data = fs.readFileSync(pegaConfigJsonPath, { encoding: 'utf8' });
data = JSON.parse(data);
if (data[IMPORT_RELATIVE_PATH]) {
retrievedConfigData.importRelativePath = data[IMPORT_RELATIVE_PATH];
}
if (data[EXPORT_RELATIVE_PATH]) {
retrievedConfigData.exportRelativePath = data[EXPORT_RELATIVE_PATH];
}
if (data[USE_INPUT_CONFIG]) {
retrievedConfigData.useInputConfig = data[USE_INPUT_CONFIG];
}
if (data[LIBRARY_BASED]) {
retrievedConfigData.isLibraryBased = data[LIBRARY_BASED];
}
retrievedConfigData.library = data.component.library;
retrievedConfigData.version = data.component.version;
if (data[LIBRARY_BASED_CL]) {
retrievedConfigData.isLibraryBasedCL = data[LIBRARY_BASED_CL];
// if this is true, then force library based to true, so it doesn't have to be set
if (retrievedConfigData.isLibraryBasedCL) {
retrievedConfigData.isLibraryBased = retrievedConfigData.isLibraryBasedCL;
}
}
const { organization } = JSON.parse(fs.readFileSync(path.resolve('package.json'), 'utf8'));
retrievedConfigData.organization = organization;
if (data["showDebug"]) {
retrievedConfigData.showDebug = data.showDebug;
showDebug = data.showDebug;
}
retrievedConfigData.serverType = data["server-config"].serverType;
retrievedConfigData.devBuild = data["server-config"].devBuild;
// override devBuild for libraryBasedCL, always false.
if (retrievedConfigData.isLibraryBasedCL) {
retrievedConfigData.devBuild = false;
}
retrievedConfigData.devServerPort = data["server-config"].devServerPort;
retrievedConfigData.buildVersion = retrievedConfigData.devBuild ? `${retrievedConfigData.version}-dev` : retrievedConfigData.version;
retrievedConfigData.currentOrgLib = `${retrievedConfigData.organization}_${retrievedConfigData.library}`;
retrievedConfigData.displayLibVersion = `${retrievedConfigData.currentOrgLib }/${retrievedConfigData.buildVersion}`;
}
catch (ex) {
// when can't find it, always be false, assume no library based.
// This is IMPORTANT for init (npx install)
return false;
}
return JSON.parse(JSON.stringify(retrievedConfigData));
}
export const convertYNToBoolean = (inputYN) => {
switch (inputYN) {
case 'Y':
case 'y':
case 'Yes':
case 'YES':
case 't':
case 'true':
case 'True':
case 'TRUE':
return true;
}
return false;
}
export const getInputConfigForCommand = async (commandName) => {
// retrievedConfigData
const currentDirectory = process.cwd();
const inputConfigJsonPath = join(currentDirectory, INPUT_CONFIG_JSON_FILENAME);
const isLibraryBased = getLibraryBased();
try {
// force to throw error and not console log
await checkPathAccess(inputConfigJsonPath, {}, true);
let data = fs.readFileSync(inputConfigJsonPath, { encoding: 'utf8' });
data = JSON.parse(data);
if (isLibraryBased) {
const libraryCommands = data["libraryMode"];
if (libraryCommands[commandName]) {
return libraryCommands[commandName];
}
}
else {
const standardCommands = data["standard"];
if (standardCommands[commandName]) {
return standardCommands[commandName];
}
}
}
catch (ex) {
// when can't find it, always be false, assume no library based.
// This is IMPORTANT for init (npx install)
return {};
}
return {};
}
// initialize
// await setUpDefaults();
export const checkPathAccess = async (path, options = {}, throwError = false) => {
const { errorMessage } = options;
try {
await access(path, fs.constants.R_OK);
} catch (err) {
if (throwError) {
if (showDebug) addDebugLog("checkPathAccess", "path: ".concat(path), "+");
if (showDebug) addDebugLog("checkPathAccess", "Error: ".concat(err), "");
if (showDebug) addDebugLog("checkPathAccess", "END", "-");
throw new Error(err);
}
else {
if (showDebug) addDebugLog("checkPathAccess", "path: ".concat(path), "+");
const message =
errorMessage || `${chalk.red.bold('ERROR')} Unable to access path - ${path}`;
console.error(message);
if (showDebug) addDebugLog("checkPathAccess", "END", "-");
process.exit(1);
}
}
};
export const getUseWebPackPromotion = async () => {
if (showDebug) addDebugLog("getUseWebPackPromotion", "", "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
try {
// force to throw error and not console log
await checkPathAccess(pegaConfigJsonPath, {}, true);
}
catch (ex) {
// when can't find it, always be false, assume no webpack override.
// This is IMPORTANT for init (npx install)
return false;
}
let data = fs.readFileSync(pegaConfigJsonPath, { encoding: 'utf8' });
data = JSON.parse(data);
if (!data[COMPONENTS_DIRECTORY_PATH]) {
console.error(
`${chalk.red.bold('ERROR')} Unable to find components directory path in config.json`
);
process.exit(1);
}
if (data[USE_PROMOTED_WEB_PACK] == null) {
// value doesn't exist in task config, so add it with default of false
data[USE_PROMOTED_WEB_PACK] = false;
await updateUseWebPackPromotion(false);
}
return data[USE_PROMOTED_WEB_PACK];
}
export const updateUseWebPackPromotion = async ( useValue = false ) => {
if (showDebug) addDebugLog("updateUseWebPackPromotion", "", "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
await checkPathAccess(pegaConfigJsonPath);
let data = fs.readFileSync(pegaConfigJsonPath, { encoding: 'utf8' });
data = JSON.parse(data);
if (!data[COMPONENTS_DIRECTORY_PATH]) {
console.error(
`${chalk.red.bold('ERROR')} Unable to find components directory path in config.json`
);
process.exit(1);
}
data["usePromotedWebPack"] = useValue;
fs.writeFileSync(pegaConfigJsonPath, JSON.stringify(data, null, 4), { encoding: 'utf8',flag:'w' });
}
export const getAboutData = async (currentOrgLib, buildVersion, dateStamp) => {
if (showDebug) addDebugLog("getAboutData", `currentOrgLib: ${currentOrgLib}, buildVersion: ${buildVersion}, dateStamp: ${dateStamp}`,"");
const tokenAndStaticServer = await getC11NB2STokenAndStaticServer();
if (tokenAndStaticServer.C11NB2S === undefined) {
// console.log(chalk.yellowBright("Need to authenticate, missing services token.\nLibrary requires authentication to acquire a token to build."));
return `${currentOrgLib} ${buildVersion}, ${dateStamp}`;
}
const jwt = jwtDecode(tokenAndStaticServer.C11NB2S);
let infinityVersion= jwt.infinity_version;
infinityVersion = infinityVersion.replace("8.", "");
return `${currentOrgLib} ${buildVersion}, ${dateStamp}, ${infinityVersion}`;
}
export const applyPegaData = async (configJson) => {
if (showDebug) addDebugLog("applyPegaData", "", "");
const tokenAndStaticServer = await getC11NB2STokenAndStaticServer();
const currentDirectory = process.cwd()
// const currentPackagePath = join(currentDirectory, PACKAGE_JSON_FILENAME);
const currentPackageLockPath = join(currentDirectory, PACKAGE_LOCK_JSON_FILENAME);
// let currentPackageData;
let currentPackageLockData;
try {
currentPackageLockData = fs.readFileSync(currentPackageLockPath);
}
catch (ex) {
console.log(chalk.red(`\nUnable to retrieve current package-lock.json.`));
process.exit(1);
}
// const cData = JSON.parse(currentPackageData);
// const cosmosVersion = cData.dependencies["@pega/cosmos-react-core"];
const cData = JSON.parse(currentPackageLockData);
const cosmosReact = cData.packages["node_modules/@pega/cosmos-react-core"];
const cosmosVersion = cosmosReact.version;
let infinityVersion = "";
if (tokenAndStaticServer.C11NB2S === undefined) {
}
else {
const jwt = jwtDecode(tokenAndStaticServer.C11NB2S);
infinityVersion= jwt.infinity_version;
// don't remove "8." from infinity version so can do a comparison
}
// apply info to config.json
configJson.infinityVersion = infinityVersion;
configJson.packageCosmosVersion = cosmosVersion;
}
export const checkJWTExpiration = async () => {
const tokenAndStaticServer = await getC11NB2STokenAndStaticServer();
const isLibraryBased = getLibraryBased();
const isLibraryBasedCL = getLibraryBasedCL();
if (!isLibraryBasedCL && isLibraryBased) {
if (tokenAndStaticServer.C11NB2S === undefined) {
console.log(chalk.redBright("Need to authenticate, missing services token.\nBuilding a library requires authentication to acquire a token to build."));
process.exit(1);
}
}
const jwt = jwtDecode(tokenAndStaticServer.C11NB2S);
const { exp, customer_org } = jwt;
if (exp < (new Date().getTime() + 1) / 1000) {
console.log(chalk.redBright("JWT token has expired, please reauthenticate."));
process.exit(1);
}
// only check if older isLibraryBased
if (!isLibraryBasedCL && isLibraryBased) {
// check that there is customer_org
if (!customer_org || customer_org === "") {
console.log(chalk.red(`PegaInfinity server DSS setting ${chalk.redBright.bold(`C11nCCv2CustomerOrg`)} is NOT set.`));
console.log(chalk.red("Please update DSS setting, save and then reauthenticate."));
process.exit(1);
}
}
}
export const checkAccessTokenExpiration = async() => {
const tokenAndStaticServer = await getC11NB2STokenAndStaticServer();
if (tokenAndStaticServer.access_token === undefined) {
console.log(chalk.redBright("Need to authenticate, missing token.\n"));
process.exit(1);
}
const jwt = jwtDecode(tokenAndStaticServer.access_token);
const { exp, customer_org, sub, app_name, app_version, operator_access } = jwt;
if (exp < (new Date().getTime() + 1) / 1000) {
console.log(chalk.redBright("Authentication token has expired, please reauthenticate."));
process.exit(1);
}
}
export const getApplicationAndVersionFromToken = async() => {
const tokenAndStaticServer = await getC11NB2STokenAndStaticServer();
if (tokenAndStaticServer.access_token === undefined) {
// console.log(chalk.redBright("Need to authenticate, missing token.\n"));
// process.exit(1);
const app_name = "**";
const app_version = "**";
const sub = "**";
const operator_access = "**";
return { app_name, app_version, sub, operator_access};
}
const jwt = jwtDecode(tokenAndStaticServer.access_token);
const { app_name, app_version, sub, operator_access } = jwt;
return { app_name, app_version, sub, operator_access};
}
export const getLibraryBased = () => {
if (showDebug) addDebugLog("getLibraryBased", "", "");
return defaultConfigs.isLibraryBased;
}
export const getLibraryBasedCL = () => {
if (showDebug) addDebugLog("getLibraryBasedCL", "", "");
return defaultConfigs.isLibraryBasedCL;
}
export const getUseInputConfig = () => {
if (showDebug) addDebugLog("getUseInputConfig", "", "");
return defaultConfigs.useInputConfig;
}
export const setLibraryBased = async (isLibraryBased) => {
if (showDebug) addDebugLog("setLibraryBased", "", "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
try {
// force to throw error and not console log
await checkPathAccess(pegaConfigJsonPath, {}, true);
}
catch (ex) {
// when can't find it, always be false, assume no library based.
// This is IMPORTANT for init (npx install)
return false;
}
let data = fs.readFileSync(pegaConfigJsonPath, { encoding: 'utf8' });
data = JSON.parse(data);
if (data[LIBRARY_BASED] != undefined) {
data[LIBRARY_BASED] = isLibraryBased;
// update file
// stringify "4", makes the json string look like JSON in the file, formatted instead of a single line
try {
fs.writeFileSync(pegaConfigJsonPath, JSON.stringify(data, null, 4), { encoding: 'utf8',flag:'w' });
}
catch (err) {
console.error(err);
}
//await forceDefaultsUpdate();
defaultConfigs.isLibraryBased = isLibraryBased;
// reset
pegaData = null;
pegaConfig = null;
}
}
export const setLibraryBasedCL = async (isLibraryBasedCL) => {
if (showDebug) addDebugLog("setLibraryBased", "", "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
try {
// force to throw error and not console log
await checkPathAccess(pegaConfigJsonPath, {}, true);
}
catch (ex) {
// when can't find it, always be false, assume no library based.
// This is IMPORTANT for init (npx install)
return false;
}
let data = fs.readFileSync(pegaConfigJsonPath, { encoding: 'utf8' });
data = JSON.parse(data);
if (data[LIBRARY_BASED_CL] != undefined) {
data[LIBRARY_BASED_CL] = isLibraryBasedCL;
// if not undefined, force this as well
if (data[LIBRARY_BASED] != undefined) {
data[LIBRARY_BASED] = isLibraryBasedCL;
}
// update file
// stringify "4", makes the json string look like JSON in the file, formatted instead of a single line
try {
fs.writeFileSync(pegaConfigJsonPath, JSON.stringify(data, null, 4), { encoding: 'utf8',flag:'w' });
}
catch (err) {
console.error(err);
}
//await forceDefaultsUpdate();
defaultConfigs.isLibraryBasedCL = isLibraryBasedCL;
// force libraryBased if isLibraryBasedCL is true
defaultConfigs.isLibraryBased = isLibraryBasedCL;
// reset
pegaData = null;
pegaConfig = null;
}
}
export const getShowDebug = () => {
return defaultConfigs.showDebug;
}
export const deleteLocalComponent = async componentKey => {
if (showDebug) addDebugLog("deleteLocalComponent", `componentKey: ${componentKey}`, "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
await checkPathAccess(pegaConfigJsonPath);
let data = fs.readFileSync(pegaConfigJsonPath, { encoding: 'utf8' });
data = JSON.parse(data);
if (!data[COMPONENTS_DIRECTORY_PATH]) {
console.error(
`${chalk.red.bold('ERROR')} Unable to find components directory path in config.json`
);
process.exit(1);
}
const directory = join(currentDirectory, data[COMPONENTS_DIRECTORY_PATH], componentKey);
try {
fs.rmSync(directory, { recursive: true });
console.log(`${chalk.red.bold(componentKey)} is deleted from Local`);
} catch (err) {
console.log('no file');
throw new Error(`No such file ${componentKey}`);
}
};
export const getHttpsAgent = serverConfig => {
const agentOptions = { rejectUnauthorized: false };
if (serverConfig.legacyTLS) {
agentOptions.secureOptions = crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT;
}
return new https.Agent(agentOptions);
};
export const deleteServerComponent = async componentKey => {
if (showDebug) addDebugLog("deleteServerComponent", `componentKey: ${componentKey}`, "");
const serverConfig = await getPegaServerConfig();
const { server, user, password } = serverConfig;
const url = constructCompleteUrl(server, DELETE_COMPONENT_SERVICE_REST_ENDPOINT);
const [componentName, rulesetName, rulesetVersion] = componentKey.split('~|~');
const deleteUrl = `${url}/${componentName}/rulesetname/${rulesetName}/rulesetversion/${rulesetVersion}`;
try {
const OauthData = fs.readFileSync(TOKEN_PATH, 'utf8');
if (OauthData) {
const {
access_token: accessToken,
token_type: tokenType,
refresh_token: refreshToken
} = JSON.parse(OauthData);
fetch(deleteUrl, {
method: 'DELETE',
agent: getHttpsAgent(serverConfig),
headers: {
Authorization: `${tokenType} ${accessToken}`
}
})
.then(response => response.text())
.then(resp => {
let respData;
try {
respData = JSON.parse(resp);
} catch (e) {
console.log(chalk.bold.redBright(`Failure : ${resp}`));
process.exit(1);
}
if (respData.status == 200) {
console.log(chalk.bold.green(`Success : ${respData.message}`));
} else {
throw new Error(`${respData.message}`);
}
})
.catch(e => Promise.reject(`${chalk.bold.red(e)}`));
}
} catch (error) {
console.log(`\n${chalk.bold.red(error)}`);
}
};
export const copyFileToShared = async ( fileName, fileContents) => {
if (showDebug) addDebugLog("copyFileToShared", `fileName: ${fileName}`, "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
await checkPathAccess(pegaConfigJsonPath);
let data = fs.readFileSync(pegaConfigJsonPath, { encoding: 'utf8' });
data = JSON.parse(data);
if (!data[COMPONENTS_DIRECTORY_PATH]) {
console.error(
`${chalk.red.bold('ERROR')} Could not find components directory path in config.json`
);
process.exit(1);
}
const directory = join(currentDirectory, data[COMPONENTS_DIRECTORY_PATH]);
const sharedDirectory = join(directory, SHARED_DIRECTORY);
// if shared doesn't exist, create it
if (!fs.existsSync(sharedDirectory)) {
fs.mkdirSync(sharedDirectory, { recursive: true });
}
const filePath = join(sharedDirectory, fileName);
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, fileContents);
}
};
export const getComponents = async (overrideDirectory = null) => {
if (showDebug) addDebugLog("getComponents", "", "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
await checkPathAccess(pegaConfigJsonPath);
let data = fs.readFileSync(pegaConfigJsonPath, { encoding: 'utf8' });
data = JSON.parse(data);
if (!data[COMPONENTS_DIRECTORY_PATH]) {
console.error(
`${chalk.red.bold('ERROR')} Could not find components directory path in config.json`
);
process.exit(1);
}
const directory = overrideDirectory || join(currentDirectory, data[COMPONENTS_DIRECTORY_PATH]);
return fs
.readdirSync(directory, { withFileTypes: true })
.filter(dirent => dirent.isDirectory() && dirent.name !== SHARED_DIRECTORY)
.map(dirent => dirent.name);
};
export const getTopLevelConfigFiles = async (directory) => {
if (showDebug) addDebugLog("getTopLevelConfigFiles", `directory: ${directory}`, "");
if (fs.existsSync(directory)) {
return fs
.readdirSync(directory, { withFileTypes: true })
.filter(dirent => !dirent.isDirectory())
.map(dirent => dirent.name);
}
else {
return [];
}
}
export const removeTopLevelConfigFiles = async (directory) => {
if (showDebug) addDebugLog("removeTopLevelConfigFiles", `directory: ${directory}`, "");
const configFiles = await getTopLevelConfigFiles(directory);
if (configFiles && configFiles.length > 0) {
for (const configF of configFiles) {
// don't remove these files, otherwise, remove it
if (configF !== "package-lock.json" &&
configF !== "package.json" &&
configF !== "tasks.config.json" &&
configF !== ".gitignore" &&
configF !== ".npmrc" &&
configF !== ".prettierignore") {
fs.rmSync( join(directory, configF), { recursive: true, force: true, maxRetries: 2 });
}
}
}
}
export const restoreTopLevelConfigFiles = async(directory) => {
if (showDebug) addDebugLog("restoreTopLevelConfigFiles", `directory: ${directory}`, "");
const currentDirectory = process.cwd();
const tempDirectory = join(currentDirectory, TEMP_PATH);
const configFiles = await getTopLevelConfigFiles(tempDirectory);
// we force to get the tasks.config.json and store it locally
// when we call updateComponentTaskConfig we will use this stored version to bring back server-config (except ruleset/version)
// and component objects
await getPegaConfig(true);
if (configFiles && configFiles.length > 0) {
for (const configF of configFiles) {
// don't want to restore package, package-lock or an archive file
if (configF !== "package-lock.json" && configF !== "package.json" && (configF.indexOf(".zip") < 0) ) {
fs.copyFileSync(join(tempDirectory, configF), join(directory, configF));
}
}
}
}
export const getTemplateConfigFiles = async () => {
if (showDebug) addDebugLog("getTemplateConfigFiles", ``, "");
const currentDirectory = process.cwd();
const initDir = join(currentDirectory, "node_modules", "@pega", "custom-dx-components", "src", "tasks", "init");
const templateDir = join(initDir, "template");
if (fs.existsSync(templateDir)) {
return fs
.readdirSync(templateDir, { withFileTypes: true })
.filter(dirent => !dirent.isDirectory())
.map(dirent => dirent.name);
}
else {
return [];
}
}
export const restoreTopLevelConfigFilesFromTemplate = async(clearDep = true) => {
if (showDebug) addDebugLog("restoreTopLevelConfigFilesFromTemplate", ``, "");
// we can't loose devDep that is part of install, so go get the template mustache and push
// those in, replacing any changes there, but allowing additional, an keeping it updated
// so won't loose new function if an old archive
const currentDirectory = process.cwd();
const initDir = join(currentDirectory, "node_modules", "@pega", "custom-dx-components", "src", "tasks", "init");
const templateDir = join(initDir, "template");
const packageTemplateJsonPath = path.join(templateDir, 'package.json.mustache');
const currentPackagePath = join(currentDirectory, PACKAGE_JSON_FILENAME);
let currentPackageData;
// reset
haveDependencyDifference = false;
try {
currentPackageData = fs.readFileSync(currentPackagePath);
}
catch (ex) {
console.log($chalk.red(`\nUnable to retrieve current package.json.`))
process.exit(1);
}
let cData = JSON.parse(currentPackageData);
let retrievedTemplateData;
try {
retrievedTemplateData = fs.readFileSync(packageTemplateJsonPath);
}
catch (ex) {
console.log($chalk.red(`\nUnable to retrieve package.json.mustache from code.`))
process.exit(1);
}
// copy template dependencies and devDependencies
const tData = JSON.parse(retrievedTemplateData);
// check to see if any dependency changes
const sDep = JSON.stringify(tData.dependencies);
const sDevDep = JSON.stringify(tData.devDependencies);
const sCurDep = JSON.stringify(cData.dependencies);
const sCurDevDep = JSON.stringify(cData.devDependencies);
// any changes, update haveDependencyDifference variable
if (sDep != sCurDep) {
haveDependencyDifference = true;
}
else if (sDevDep != sCurDevDep) {
haveDependencyDifference = true;
}
await removeTopLevelConfigFiles(currentDirectory);
// clear out
if (clearDep) {
cData.dependencies = {};
cData.devDependencies = {};
}
const tDep = tData.dependencies;
for (const [key, value] of Object.entries(tDep)) {
cData.dependencies[key] = value;
}
const tDevDep = tData.devDependencies;
for (const [key, value] of Object.entries(tDevDep)) {
cData.devDependencies[key] = value;
}
// update current package json file
fs.writeFileSync(currentPackagePath, JSON.stringify(cData, null, 4), { encoding: 'utf8',flag:'w' });
// restore from template
const templateFiles = await getTemplateConfigFiles();
if (templateFiles && templateFiles.length > 0) {
for (const tFile of templateFiles) {
if (tFile !== "package.json.mustache" && tFile !== "templateNPMRC.txt" && tFile !== "tasks.config.json") {
fs.copyFileSync(join(templateDir, tFile), join(currentDirectory, tFile));
}
}
}
}
export const getSubComponents = async (directory, type) => {
if (showDebug) addDebugLog("getDirectoryFiles", `directory: ${directory}, type: ${type}`, "");
const subDirectory = join(directory, type);
return fs
.readdirSync(subDirectory, { withFileTypes: true })
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
};
export const getDirectoryFiles = async directory => {
if (showDebug) addDebugLog("getDirectoryFiles", `directory: ${directory}`, "");
return fs
.readdirSync(directory, { withFileTypes: true })
.filter(dirent => !dirent.isDirectory())
.map(dirent => dirent.name);
};
export const getComponentsObj = async () => {
if (showDebug) addDebugLog("getComponentsObj", "", "+");
const compList = [];
// const componentList = await getDirectoryFiles(directory);
const componentList = await getComponents();
if (componentList.length > 0) {
compList.push(
...componentList.map(name => {
const container = {};
container.name = name;
container.value = name;
if (showDebug) addDebugLog("getComponentsObj", "container: " + JSON.stringify(container), "");
return container;
})
);
}
if (showDebug) addDebugLog("getComponentsObj", "END", "-");
return compList;
};
export const getPegaConfig = async (force = false) => {
if (showDebug) { addDebugLog("getPegaConfig", "", "+"); }
if (!pegaData || force) {
if (showDebug) { addDebugLog("getPegaConfig", "retrieveData", ""); }
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
await checkPathAccess(pegaConfigJsonPath);
sPegaData = fs.readFileSync(pegaConfigJsonPath, { encoding: 'utf8' });
pegaData = JSON.parse(sPegaData);
}
if (showDebug) { addDebugLog("getPegaConfig", "pegaData: " + sPegaData, ""); }
if (showDebug) { addDebugLog("getPegaConfig", "END", "-"); }
return pegaData;
};
export const getPegaServerConfig = async (force = false) => {
if (showDebug) addDebugLog("getPegaServerConfig", "", "+");
if (!pegaConfig || force) {
pegaConfig = await getPegaConfig(force);
if (getServerType() === "launchpad") {
// need to append isolation id for now
let OAuthData;
try {
OAuthData = fs.readFileSync(TOKEN_PATH, 'utf8');
}
catch (ex) {
return ({});
}
if (OAuthData) {
const oOAuthData = JSON.parse(OAuthData);
if (oOAuthData["isolation-ids"]) {
const isolationId = oOAuthData["isolation-ids"].write;
pegaConfig['server-config'].isolationId = isolationId[0];
}
}
}
sPegaServerConfig = JSON.stringify(pegaConfig['server-config']);
}
if (showDebug) addDebugLog("getPegaServerConfig", "server config:" + sPegaServerConfig, "");
if (showDebug) addDebugLog("getPegaServerConfig", "END", "-");
return pegaConfig['server-config'];
};
// contains all the config, task config, etc. created upon start
export const getConfigDefaults = () => {
if (showDebug) addDebugLog("getConfigDefaults", `defaults: ${JSON.stringify(defaultConfigs)}`, "");
return defaultConfigs;
}
export const forceDefaultsUpdate = async () => {
defaultConfigs = await setUpDefaults();
}
export const updateServerConfigRuleSetAndVersion = async (rulesetName, rulesetVersion) => {
if (showDebug) addDebugLog("updateServerConfigRuleSetAndVersion", `rulesetName: ${rulesetName}, rulesetVersion: ${rulesetVersion}`, "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
let configData = await getPegaConfig(true);
let serverConfig = configData['server-config'];
serverConfig.rulesetName = rulesetName;
serverConfig.rulesetVersion = rulesetVersion;
// 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 updateServerConfigArchiveRuleSetAndVersion = async (rulesetName, rulesetVersion) => {
if (showDebug) addDebugLog("updateServerConfigArchiveRuleSetAndVersion", `rulesetName: ${rulesetName}, rulesetVersion: ${rulesetVersion}`, "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
let configData = await getPegaConfig(true);
let serverConfig = configData['server-config'];
serverConfig.archiveRulesetName = rulesetName;
serverConfig.archiveRulesetVersion = rulesetVersion;
// 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 updateComponentDefaultLibrary = async (library) => {
if (showDebug) addDebugLog("updateComponentDefaultLibrary", `library: ${library}`, "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
await checkPathAccess(pegaConfigJsonPath);
let config = await getPegaConfig(true);
let componentsDefault = config.component;
componentsDefault.library = library;
fs.writeFileSync(pegaConfigJsonPath, JSON.stringify(config, null, 4), { encoding: 'utf8',flag:'w' });
}
export const updateComponentDefaultVersion = async (version) => {
if (showDebug) addDebugLog("updateComponentDefaultVersion", `version: ${version}`, "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
await checkPathAccess(pegaConfigJsonPath);
let config = await getPegaConfig(true);
let componentsDefault = config.component;
componentsDefault.version = version;
fs.writeFileSync(pegaConfigJsonPath, JSON.stringify(config, null, 4), { encoding: 'utf8',flag:'w' });
}
export const getConfigDevBuild = async() => {
if (showDebug) addDebugLog("getConfigDevBuild", "", "");
// const serverConfig = await getPegaServerConfig();
if (getLibraryBasedCL()) {
return false;
}
// return serverConfig.devBuild;
return defaultConfigs.devBuild;
}
export const setConfigDevBuild = async (devBuild) => {
if (showDebug) addDebugLog("setConfigDevBuild", `devBuild: ${devBuild}`, "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
let configData = await getPegaConfig(true);
let serverConfig = configData['server-config'];
serverConfig.devBuild = devBuild;
// 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 getConfigDevServerPort = async() => {
if (showDebug) addDebugLog("getConfigDevServerPort", "", "");
// const serverConfig = await getPegaServerConfig();
return defaultConfigs.devServerPort;
}
export const setConfigImportRelativePath = async (importRelativePath) => {
if (showDebug) addDebugLog("setConfigImportRelativePath", `importRelativePath: ${importRelativePath}`, "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
let configData = await getPegaConfig(true);
configData[IMPORT_RELATIVE_PATH] = importRelativePath;
// 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 setConfigExportRelativePath = async (exportRelativePath) => {
if (showDebug) addDebugLog("setConfigExportRelativePath", `exportRelativePath: ${exportRelativePath}`, "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
let configData = await getPegaConfig(true);
configData[EXPORT_RELATIVE_PATH] = exportRelativePath;
// 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 updateDefaultOrganization = async (organization) => {
if (showDebug) addDebugLog("updateDefaultOrganization", `organization: ${organization}`, "");
let packageData = JSON.parse(fs.readFileSync(path.resolve('package.json'), 'utf-8'));
packageData.organization = organization;
fs.writeFileSync(path.resolve('package.json'), JSON.stringify(packageData, null, 4), { encoding: 'utf8',flag:'w' });
}
export const updateConfigVersion = async (componentKey, buildVersion, currentOrgLib) => {
if (showDebug) addDebugLog("updateConfigVersion", `componentKey: ${componentKey}, buildVersion: ${buildVersion}, currentOrgLib: ${currentOrgLib}`, "");
const componentDirectory = await getComponentDirectoryPath(componentKey);
const 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: ${componentKey}`)}`);
return;
}
const currentDate = new Date(Date.now());
const dateStamp = currentDate.toDateString();
const aboutData = await getAboutData(currentOrgLib, buildVersion, dateStamp);
let data;
try {
data = JSON.parse(configData);
//find "About" and create or update
const properties = data.properties;
if (properties) {
let foundProp = null;
// check to see if PegaAboutData exists. If customer decides to put
// data under PegaAboutData, we won't change the order
// otherwise, will put at bottom
for (let i in properties) {
if (properties[i].name && properties[i].name === "PegaAboutData") {
foundProp = properties[i];
break;
}
}
if (addAboutData) {
if (foundProp) {
foundProp.label = aboutData;
}
else {
// don't have "About"
const aboutLabelJSON = {
"format": "LABEL",
"label": "About",
"name": "PegaAboutLabel",
"variant": "h3"
};
const aboutDataJSON = {
"format": "LABEL",
"label": aboutData,
"name": "PegaAboutData",
"variant": "primary"
}
properties.push(aboutLabelJSON);
properties.push(aboutDataJSON);
}
}
}
} catch (er) {
throw new Error(`Invalid schema json in config.json: ${er}`);
}
data.version = buildVersion;
if (addAboutData) {
data.buildDate = currentDate;
await applyPegaData(data);
}
// write back file
fs.writeFileSync(configFile, JSON.stringify(data, null, 4), { encoding: 'utf8',flag:'w' });
};
export const getServerType = () => {
if (showDebug) addDebugLog("getServerType", "", "+");
// const defaultPegaServerConfig = await getPegaServerConfig();
// if (showDebug) addDebugLog("getServerType", "serverType:" + defaultPegaServerConfig.serverType, "");
if (showDebug) addDebugLog("getServerType", "END", "-");
// return defaultPegaServerConfig.serverType;
return defaultConfigs.serverType;
}
export const setServerType = async(serverType) => {
if (showDebug) addDebugLog("setServerType", `serverType: ${serverType}`, "");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
let configData = await getPegaConfig(true);
let serverConfig = configData['server-config'];
serverConfig.serverType = serverType;
// 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' });
// reset
pegaData = null;
pegaConfig = null;
await forceDefaultsUpdate();
}
export const getSrcDirectoryPath = async () => {
const currentDirectory = process.cwd();
return join(currentDirectory, "src");
}
export const getComponentDirectoryPath = async (componentKey, overrideDir = null) => {
if (showDebug) addDebugLog("getComponentDirectoryPath", "", "+");
const currentDirectory = process.cwd();
const pegaConfigJsonPath = join(currentDirectory, TASKS_CONFIG_JSON_FILENAME);
await checkPathAccess(pegaConfigJsonPath);
let data = fs.readFileSync(pegaConfigJsonPath, { encoding: 'utf8' });
data = JSON.parse(data);
if (!data[COMPONENTS_DIRECTORY_PATH]) {
console.error(
`${chalk.red.bold('ERROR')} Unable to find components directory path in config.json`
);
process.exit(1);
}
if (showDebug) addDebugLog("getComponentDirectoryPath", "path: " + join(currentDirectory, data[COMPONENTS_DIRECTORY_PATH], componentKey), "");
if (showDebug) addDebugLog("getComponentDirectoryPath", "END", "-");
return overrideDir ? join(overrideDir, componentKey) : join(currentDirectory, data[COMPONENTS_DIRECTORY_PATH], componentKey);
};
export const compileMustacheTemplate = (file, data) => {
if (showDebug) addDebugLog("compileMustacheTemplate", `file: ${file}`, "");
const content = fs.readFileSync(file, 'utf8');
return mustache.render(content, data);
};
export const isPascalCase = string => {
return string === pascalCase(string);
};
export const convertIntoPascalCase = string => {
return pascalCase(string);
};
export const constructCompleteUrl = (baseServer, endPoint) => {
if (showDebug) addDebugLog("constructCompleteUrl", "url: " + baseServer.endsWith('/') ? `${baseServer}${endPoint}` : `${baseServer}/${endPoint}`, "");
return baseServer.endsWith('/') ? `${baseServer}${endPoint}` : `${baseServer}/${endPoint}`;
};
export const sanitize = str => {
/* Allow only numbers and case insensitive alphabets */
str = str.replace(/[^a-zA-Z0-9 ]/g, '');
/* spaces will be replaced by - */
str = str.replace(/\s+/g, '-');
return str;
};
export const validateSemver = str => {
/* basic semver version validation - 0.0.1-dev */
const regex = /^[0-9]\d*\.\d+\.\d+(?:-[a-zA-Z0-9]+)?$/g;
return regex.test(str);
};
export const validateRulesetVersion = str => {
/* Ruleset version range - 01-99 */
if (str.indexOf('00') !== -1) {
return false;
}
/* basic ruleset version validation - 01-01-01 */
const regex = /^\d\d-\d\d-\d\d$/g;
return regex.test(str);
};
export const standardizeStr = (str, length) => {
return str.padEnd(length);
};
export const showVersion = async () => {
// get package version, so can display at start
// const require = createRequire(import.meta.url);
const require = createRequire(currentPath.default);
const serverType = getServerType();
const sLibMode = getLibraryBased() ? "(library mode)" : serverType === "launchpad" ? "(LaunchPad)" : "";
const pData = require('@pega/custom-dx-components/package.json');
const serverConfig = await getPegaServerConfig();
console.log(chalk.green(`DX Component Builder${sLibMode} v${pData.version}`));
showDebug = getShowDebug();
if (getLibraryBased() && serverType === "launchpad") {
console.log(`\nLaunchpad commands only supported for ${chalk.bold.green('non library mode')} components.\n`);
process.exit();
}
// do a check of node version, it should be 24 or greater
const arNodeVersion = process.versions.node.split('.');
const nodeMajorVersion = parseInt(arNodeVersion[0]);
const nodeMinorVersion = parseInt(arNodeVersion[1]);
const nodePatchVersion = parseInt(arNodeVersion[2]);
if (nodeMajorVersion < 24) {
console.log(
chalk.redBright(
`DX Component Builder - requires node v24.4.1 or greater, current version: ${process.version}`
)
);
process.exit(1);
}
else if ( nodeMajorVersion == 24 && nodeMinorVersion < 4) {
console.log(
chalk.redBright(
`DX Component Builder - requires node v24.4.1 or greater, current version: ${process.version}`
)
);
process.exit(1);
}
else if (nodeMajorVersion == 24 && nodeMinorVersion == 4 && nodePatchVersion < 1 ) {
console.log(
chalk.redBright(
`DX Component Builder - requires node v24.4.1 or greater, current version: ${process.version}`
)
);
process.exit(1);
}
await checkForBranch(serverConfig.rulesetName);
};
export const checkForBranch = async(rulesetName) => {
if (getServerType() === "infinity") {
if (rulesetName === "") {
const serverConfig = await getPegaServerConfig();
rulesetName = serverConfig.rulesetName;
}
if (rulesetName.toLowerCase().indexOf("_branch") >= 0) {
console.log(
chalk.redBright(
`Ruleset ${chalk.bold(`${rulesetName}`)} is a branch ruleset.\nPublishing to branch rulesets are not supported.`
)
);
process.exit(1);
}
}
}
export const getOOTBComponents = async () => {
if (showDebug) addDebugLog("getOOTBComponents", "", "");
return new Promise(resolve => {
fs.readFile(OOTB_COMPONENTS, 'utf-8', async (error, ootbComponentsData) => {
if (error) {
try {
const serverConfig = await getPegaServerConfig();
const { server } = serverConfig;
const url = constructCompleteUrl(server, OOTB_COMPONENT_SERVICE_REST_ENDPOINT);
const OauthData = fs.readFileSync(TOKEN_PATH, 'utf8');
if (OauthData) {
const { access_token: accessToken, token_type: tokenType } = JSON.parse(OauthData);
fetch(url, {
method: 'GET',
agent: getHttpsAgent(serverConfig),
headers: {
Authorization: `${tokenType} ${accessToken}`
}
})
.then(response => response.text())
.then(data => {
if (data.charAt() === '[') {
data = data.slice(1, -1);
data = data.replace(/\s/g, '');
fs.writeFile(OOTB_COMPONENTS, data, err => {
if (err) {
console.error(err);
}
resolve(data.split(','));
});
} else {
data = JSON.parse(data);
if (data && data.errors) {
const errMessages = data.errors;
errMessages.forEach(msgArr => {
throw new Error(`Failed with error - ${JSON.stringify(msgArr.message)}`);
});
}
}
})
// eslint-disable-next-line prefer-promise-reject-errors
.catch(e => Promise.reject(`${chalk.bold.red(e)}`));
}
} catch (e) {
throw new Error('Error occurred in validation', e);
}
} else {
resolve(ootbComponentsData.split(','));
}
});
});
};
export const deleteInfinityServerComponent = async componentKey => {
if (showDebug) addDebugLog("deleteInfinityServerComponent", `componentKey: ${componentKey}`, "");
const serverConfig = await getPegaServerConfig();
const { server } = serverConfig;
const url = constructCompleteUrl(server, DELETE_COMPONENT_SERVICE_REST_ENDPOINT);
const [componentName, rulesetName, rulesetVersion] = componentKey.split('~|~');
const deleteUrl = `${url}/${componentName}/rulesetname/${rulesetName}/rulesetversion/${rulesetVersion}`;
try {
const OauthData = fs.readFileSync(TOKEN_PATH, 'utf8');
let status = 500;
if (OauthData) {
const { access_token: accessToken, token_type: tokenType } = JSON.parse(OauthData);
const response = await fetch(deleteUrl, {
method: 'DELETE',
agent: getHttpsAgent(serverConfig),
headers: {
Authorization: `${tokenType} ${accessToken}`
}
});
status = response.status;
if (!response.ok) {
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 === 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');
}
else if (response.status === 403) {
throw new Error(
'Error forbidden: User does not have privileges to Publish.'
);
}
}
try {
const respData = await response.json();
console.log(
chalk.bold.green(`Success : ${respData.message}`)
);
if (status === 500) {
status = 999;
throw new Error(respData.message);
}
}
catch (err) {
if (status === 500) {
throw new Error(
`Error occurred in authentication. Please regenerate using authenticate`
);
}
else if (status === 999) {
throw new Error (err);
}
}
}
} catch (error) {
throw new Error(error);
}
};
export const deleteInfinityServerComponentLibrary = async (libraryName, rulesetName, rulesetVersion) => {
if (showDebug) addDebugLog("deleteInfinityServerComponentLibrary", `componentKey: ${componentKey}, rulesetName: ${rulesetName}, rulesetVersion: ${rulesetVersion}`, "");
const serverConfig = await getPegaServerConfig();
const { server } = serverConfig;
const url = constructCompleteUrl(server, DELETE_COMPONENT_LIBRARY_SERVICE_REST_ENDPOINT);
const deleteUrl = `${url}/${libraryName}/rulesetname/${rulesetName}/rulesetversion/${rulesetVersion}`;
try {
const OauthData = fs.readFileSync(TOKEN_PATH, 'utf8');
let status = 500;
if (OauthData) {
const { access_token: accessToken, token_type: tokenType } = JSON.parse(OauthData);
let spinner;
console.log();
spinner = ora(chalk.green.bold('Deleting component library...')).start();
const response = await fetch(deleteUrl, {
method: 'DELETE',
agent: getHttpsAgent(serverConfig),
headers: {
Authorization: `${tokenType} ${accessToken}`
}
});
spinner.stop();
console.log();
status = response.status;
if (!response.ok) {
if (status === 401) {