UNPKG

@pega/custom-dx-components

Version:

Utility for building custom UI components

1,566 lines (1,241 loc) 93.2 kB
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) {