UNPKG

@pega/custom-dx-components

Version:

Utility for building custom UI components

1,446 lines (1,201 loc) 93.8 kB
import fs from 'fs'; import path from 'path'; import { join } from 'path'; import { promisify } from 'util'; 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 undici from 'undici'; import chalk from 'chalk'; import mustache from 'mustache'; import pascalCase from 'pascalcase'; // 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 getFetchAgent = serverConfig => { const nodeUseEnvProxy = process.env.node_use_env_proxy || process.env.NODE_USE_ENV_PROXY; const agentOptions = { rejectUnauthorized: false }; if (serverConfig.legacyTLS) { agentOptions.secureOptions = crypto.constants.SSL_OP_LEGACY_SERVER_CONNECT; } if (nodeUseEnvProxy === '1') { return new undici.EnvHttpProxyAgent({ proxyTls: agentOptions, requestTls: agentOptions }); } return new undici.Agent({ connect: 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', dispatcher: getFetchAgent(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', dispatcher: getFetchAgent(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)}`); }); } } }) .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', dispatcher: getFetchAgent(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', dispatcher: getFetchAgent(serverConfig), headers: { Authorization: `${tokenType} ${accessToken}` } }); spinner.stop(); console.log(); 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 re