@pega/constellation-dx-components-build-utils
Version:
This tool uses a 'v3' approach to group components in a library, create a component map, employ webpack, and load the library like Pega-generated components, constellation app-static.
410 lines (356 loc) • 18.2 kB
JavaScript
const child_process = require("child_process");
const fs = require('fs');
const path = require('path');
const glob = require('glob');
const CONSTANTS = require('../constant');
const helper = require('../helper');
const chalk = require('chalk');
const ensureDirSync = (filePath) => {
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
};
const buildComponentsListFile = (buildFolderName, packageVersion, notClean, pathToBuild) => {
if (!notClean)
helper.cleanUpBuildFolder(CONSTANTS.CURRENT_WORKING_DIR, buildFolderName, packageVersion);
console.log(chalk.magenta("== Aggregating components to build - Components List - Started =="));
const rootForCustomCmptDir = pathToBuild ? path.dirname(pathToBuild) : undefined;
const customCmptDir = pathToBuild ? path.basename(pathToBuild) : undefined;
let projectPath = path.join(CONSTANTS.CURRENT_WORKING_DIR, CONSTANTS.PROJECT_ROOT).replace(/\\/g, '/');
if(pathToBuild && rootForCustomCmptDir){
projectPath = path.join(CONSTANTS.CURRENT_WORKING_DIR, rootForCustomCmptDir).replace(/\\/g, '/');
}
let componentsDir = path.join(projectPath, CONSTANTS.COMPONENTS_DIR).replace(/\\/g, '/');
if(pathToBuild && customCmptDir){
componentsDir = path.join(projectPath, customCmptDir).replace(/\\/g, '/');
}
let componentsLists;
if(pathToBuild){
componentsLists = getComponentsWithConfig(
componentsDir,
CONSTANTS.COMPONENT_CONFIG_FILE_NAME
);
} else{
componentsLists = fs.readdirSync(componentsDir).filter(function (file) {
if(fs.statSync(path.join(componentsDir, file).replace(/\\/g, '/')).isDirectory() && fs.existsSync(path.join(componentsDir, file, CONSTANTS.COMPONENT_CONFIG_FILE_NAME).replace(/\\/g, '/')))
return fs.statSync(path.join(componentsDir, file).replace(/\\/g, '/')).isDirectory() && fs.existsSync(path.join(componentsDir, file, CONSTANTS.COMPONENT_CONFIG_FILE_NAME).replace(/\\/g, '/'));
});
}
let cmpIndexPathObj ={};
componentsLists.forEach((component)=>{
CONSTANTS.ENTRY_POINT_FILES.forEach((indexFile) => {
if(pathToBuild){
if(!cmpIndexPathObj[component] && fs.existsSync(path.join(component, indexFile))){
let indexPath = path.join(component, indexFile).replace(/\\/g, '/');
cmpIndexPathObj[component] = indexPath;
}
} else{
if(!cmpIndexPathObj[component] && fs.existsSync(path.join(componentsDir, component, indexFile))){
let indexPath = path.join(CONSTANTS.COMPONENTS_DIR, component, indexFile).replace(/\\/g, '/');
if(pathToBuild && customCmptDir){
indexPath = path.join(customCmptDir, component, indexFile).replace(/\\/g, '/');
}
cmpIndexPathObj[component] = indexPath;
}
}
});
});
const componentListPath = path.join(projectPath, CONSTANTS.COMPONENT_LIST_FILE_NAME).replace(/\\/g, '/');
ensureDirSync(componentListPath);
fs.writeFileSync(componentListPath, JSON.stringify(cmpIndexPathObj), { encoding: 'utf8' });
const entryPointPath = path.join(projectPath, CONSTANTS.ENTRY_POINT_FILE).replace(/\\/g, '/');
ensureDirSync(entryPointPath);
fs.writeFileSync(entryPointPath, CONSTANTS.CONTENT_ENTRY_POINT_FILE, { encoding: 'utf8' });
console.log(chalk.green("== Aggregating components to build - Components List - Completed =="));
};
const buildComponentMapJsFile = (buildFolderFullPath, pathToBuild) => {
console.log(chalk.magenta("== Creating map from the list of components - Component Map - Started =="));
const cmpntDefs = [];
const cmpntLocalizations = [];
const data = fs.readFileSync(path.join(CONSTANTS.BUILD_TOOL_LIB_PATH, CONSTANTS.UTILS).replace(/\\/g, '/'), 'utf8');
const indexOfStartDelimeter = data.indexOf(CONSTANTS.START_DELIMETER);
const lenthOfStartDelimeter = CONSTANTS.START_DELIMETER.length;
const tillStartDelimeterData = data.substring(0, indexOfStartDelimeter + lenthOfStartDelimeter);
const indexOfEndDelimeter = data.indexOf(CONSTANTS.END_DELIMETER);
const fromEndDelimeterData = data.substring(indexOfEndDelimeter);
// component_map
let componentMapData = `\n const ComponentMap = {\n`;
const rootForCustomCmptDir = pathToBuild ? path.dirname(pathToBuild) : undefined;
const customCmptDir = pathToBuild ? path.basename(pathToBuild) : undefined;
let projectPath = path.join(CONSTANTS.CURRENT_WORKING_DIR, CONSTANTS.PROJECT_ROOT).replace(/\\/g, '/');
if(pathToBuild && rootForCustomCmptDir){
projectPath = path.join(CONSTANTS.CURRENT_WORKING_DIR, rootForCustomCmptDir).replace(/\\/g, '/');
}
const componentListJsonPath = path.join(projectPath, CONSTANTS.COMPONENT_LIST_FILE_NAME).replace(/\\/g, '/');
const componentListJSON = fs.readFileSync(componentListJsonPath, { encoding: 'utf8' });
const componentListJSONData = JSON.parse(componentListJSON);
Object.keys(componentListJSONData).forEach((component) => {
//const componentName = component.split(":")[0];
const componentName = (pathToBuild && rootForCustomCmptDir) ? component.substring(component.lastIndexOf("/") + 1) : component.split(":")[0];
const overrideQueryParam = component.split(":")[1];
let componentPath = path.join(projectPath, componentListJSONData[component]).replace(/\\/g, '/');
if(projectPath && rootForCustomCmptDir){
componentPath = path.join(componentListJSONData[component]).replace(/\\/g, '/');
}
const parameterForLoadable = overrideQueryParam ? `'${componentName}','${overrideQueryParam}'` : `'${componentName}'`;
componentMapData += `\t${componentName}: {\n\t modules: [\n\t\t loadable (() =>\n\t\t\t import(\n\t\t\t\t/* webpackChunkName: '${componentName}' */\n\t\t\t\t '${componentPath}'\n\t\t\t\t)\n\t\t\t, ${parameterForLoadable})\n\t\t]\n\t},\n`;
});
// remove the last ,
componentMapData = componentMapData.slice(0, -2);
componentMapData += `\n};\n`;
const modifiedComponentMapFile = tillStartDelimeterData + componentMapData + fromEndDelimeterData;
const componentListOutputPath = path.join(projectPath, CONSTANTS.COMPONENT_LIST_FILE_NAME_OUTPUT).replace(/\\/g, '/');
ensureDirSync(componentListOutputPath);
fs.writeFileSync(componentListOutputPath, modifiedComponentMapFile, { encoding: 'utf8' });
const tempList = getAggregateCompListForProject({
dirToLookUp: path.join(projectPath).replace(/\\/g, '/')
}, customCmptDir);
if (tempList) {
cmpntDefs.push(...tempList);
let componentsConfigPath = path.join(projectPath, "../", buildFolderFullPath, 'componentsconfig.json').replace(/\\/g, '/');
if(pathToBuild && rootForCustomCmptDir){
componentsConfigPath = path.join(CONSTANTS.CURRENT_WORKING_DIR, buildFolderFullPath, 'componentsconfig.json').replace(/\\/g, '/');
}
ensureDirSync(componentsConfigPath);
fs.writeFileSync(componentsConfigPath, JSON.stringify(cmpntDefs), { encoding: 'utf8' });
}
const tempCompLocalizationList = getAggregateCompLocalizationForProject({
dirToLookUp: path.join(projectPath).replace(/\\/g, '/')
}, customCmptDir);
if (tempCompLocalizationList) {
cmpntLocalizations.push(...tempCompLocalizationList);
let componentsLocalizationPath = path.join(projectPath, "../", buildFolderFullPath, 'componentslocalization.json').replace(/\\/g, '/');
if(pathToBuild && rootForCustomCmptDir){
componentsLocalizationPath = path.join(CONSTANTS.CURRENT_WORKING_DIR, buildFolderFullPath, 'componentslocalization.json').replace(/\\/g, '/');
}
ensureDirSync(componentsLocalizationPath);
fs.writeFileSync(componentsLocalizationPath, JSON.stringify(cmpntLocalizations), { encoding: 'utf8' });
}
console.log(chalk.green("== Creating map from the list of components - Component Map - Completed =="));
};
const webpackBuild = (buildFolderFullPath, buildFolderName, packageVersion, tokenParams, appStaticSVCUrl, v3, pathToBuild) => {
console.log(chalk.magenta("==== Webpack - Starting ===="));
const appStaticUrl = require('../helper').getAppstaticEnd(appStaticSVCUrl);
const token = helper.getB2SToken(tokenParams);
let orgId = '';
if (token) {
orgId = helper.getPayloadFromTkn(token, 'customer_org');
}
if (!orgId) {
orgId = '{ORG_ID}';
}
const rootForCustomCmptDir = pathToBuild ? path.dirname(pathToBuild) : undefined;
const customCmptDir = pathToBuild ? path.basename(pathToBuild) : undefined;
let projectPath = path.join(CONSTANTS.CURRENT_WORKING_DIR, CONSTANTS.PROJECT_ROOT).replace(/\\/g, '/');
if(pathToBuild && rootForCustomCmptDir){
projectPath = path.join(CONSTANTS.CURRENT_WORKING_DIR, rootForCustomCmptDir).replace(/\\/g, '/');
}
const webpackPath = path.join(CONSTANTS.BUILD_TOOL_ROOT_PATH).replace(/\\/g, '/');
child_process.execSync(`npx webpack --mode=production --env buildFolderFullPath=${buildFolderFullPath} customCmptDir=${customCmptDir} outputPath=${path.join(CONSTANTS.CURRENT_WORKING_DIR, buildFolderFullPath)} buildFolderName=${buildFolderName} packageVersion=${packageVersion} appStaticUrl=${appStaticUrl} orgId=${orgId} v3=${v3} --config ${webpackPath}/webpack.config.js`, {
stdio: [0, 1, 2],
cwd: (pathToBuild && rootForCustomCmptDir) ? path.join(projectPath).replace(/\\/g, '/') : path.join(CONSTANTS.CURRENT_WORKING_DIR).replace(/\\/g, '/'),
});
console.log(chalk.magenta("==== Cleanup ===="));
fs.unlink(path.join(projectPath, CONSTANTS.COMPONENT_LIST_FILE_NAME_OUTPUT).replace(/\\/g, '/'), (err) => {
if (err) throw err;
});
fs.unlink(path.join(projectPath, CONSTANTS.COMPONENT_LIST_FILE_NAME).replace(/\\/g, '/'), (err) => {
if (err) throw err;
});
fs.unlink(path.join(projectPath, CONSTANTS.ENTRY_POINT_FILE).replace(/\\/g, '/'), (err) => {
if (err) throw err;
});
};
const getComponentAssets = (componentAssets) => {
const assets = [];
componentAssets.forEach(ele => {
if (ele?.name && ele?.name.split('/').length == 2) {
const fileName = ele?.name.split('/')[1];
assets.push(fileName);
} else if (ele?.name && typeof ele?.name === "string") {
assets.push(ele?.name);
}
});
return assets;
}
const buildManifestFile = (buildFolderFullPath, v3, pathToBuild) => {
console.log(chalk.magenta("== Creating manifest for the library version - Started =="));
const rootForCustomCmptDir = pathToBuild ? path.dirname(pathToBuild) : undefined;
let projectPath = path.join(CONSTANTS.CURRENT_WORKING_DIR, CONSTANTS.PROJECT_ROOT).replace(/\\/g, '/');
const data = {};
try {
// read stats.json file
let statsPath = path.join(projectPath, '../', buildFolderFullPath, CONSTANTS.STATS).replace(/\\/g, '/');
if(pathToBuild && rootForCustomCmptDir){
statsPath = path.join(CONSTANTS.CURRENT_WORKING_DIR, buildFolderFullPath, CONSTANTS.STATS).replace(/\\/g, '/');
}
const stats = fs.readFileSync(statsPath, 'utf-8');
const statsObj = JSON.parse(stats);
const components = {};
Object.keys(statsObj?.namedChunkGroups).forEach(component => {
if (component !== 'main') {
const value = [];
if (statsObj?.namedChunkGroups[component]?.assets?.length) {
value.push(...getComponentAssets(statsObj.namedChunkGroups[component].assets));
}
if (statsObj?.namedChunkGroups[component]?.auxiliaryAssets?.length) {
value.push(...getComponentAssets(statsObj.namedChunkGroups[component].auxiliaryAssets));
}
components[component] = value;
}
});
data.components = components;
const { stdout } = child_process.spawnSync('npm', ['list', '--depth=0'], { encoding: 'utf-8' });
const npmListOutput = stdout;
if (npmListOutput && npmListOutput.indexOf(CONSTANTS.COSMOS_PKG_PATTERN) !== -1) {
const cosmosVersion = helper.getVersionFromListOutput(npmListOutput, CONSTANTS.COSMOS_PKG_PATTERN);
data.versions = cosmosVersion;
}
let manifestPath;
if (!v3) {
manifestPath = path.join(projectPath, '../', buildFolderFullPath, 'chunks', CONSTANTS.MANIFEST).replace(/\\/g, '/');
} else {
manifestPath = path.join(projectPath, '../', buildFolderFullPath, CONSTANTS.MANIFEST).replace(/\\/g, '/');
}
ensureDirSync(manifestPath);
fs.writeFileSync(manifestPath, JSON.stringify(data), { encoding: 'utf8' });
console.log(chalk.magenta("== Creating manifest for the library version - completed =="));
fs.unlink(path.join(projectPath, '../', buildFolderFullPath, CONSTANTS.STATS).replace(/\\/g, '/'), (err) => {
if (err) throw err;
});
}
catch (e) {
console.log('Warning in manifest file generation.');
console.log(e);
}
}
/**
* This function generate build for library
* @param {string} libraryName optional library name, else it will take value from package.json name
* @param {string} libraryVersion optional library version, else it will take value from package.json version
* @param {string} tokenParam optional B2S token for appstatic service, else it will take value from constant.js B2STOKEN
* @param {string} appStaticSVCUrl optional appstatic service url, else it will take value from constant.js APPSTATICURL
*/
const buildLib = (libraryName, libraryVersion, tokenParam, appStaticSVCUrl, v3, pathToBuild) => {
console.log(chalk.magenta("==== Library Build - Starting ===="));
const { buildFolderFullPath, buildFolderName, packageVersion } = helper.getBuildFolderName(libraryName, libraryVersion);
// build entry point and cmpt-list.json
buildComponentsListFile(buildFolderName, packageVersion, false, pathToBuild);
// build component map
buildComponentMapJsFile(buildFolderFullPath, pathToBuild);
// then build env
webpackBuild(buildFolderFullPath, buildFolderName, packageVersion, tokenParam, appStaticSVCUrl, v3, pathToBuild);
// generate manifest file
buildManifestFile(buildFolderFullPath, v3, pathToBuild);
};
const buildLibV3 = (libraryName, libraryVersion, tokenParam, appStaticSVCUrl, pathToBuild) => { //src/app/_components/custom-constellation
buildLib(libraryName, libraryVersion, tokenParam, appStaticSVCUrl, true, pathToBuild);
};
const getAggregateCompListForProject = (options, customCmptDir) => {
const { dirToLookUp } = options;
const cmpntDefs = [];
const componentList = glob.sync(path.join(dirToLookUp, CONSTANTS.COMPONENT_LIST_FILE_NAME).replace(/\\/g, '/'));
if (!componentList || componentList.length === 0) {
console.error(chalk.red('No component-list.json found'));
process.exit(1);
}
try {
const compMap = fs.readFileSync(componentList[0], 'utf-8');
const compMapObj = JSON.parse(compMap);
let definitions = glob.sync(`${dirToLookUp}/components/**/config.json`.replace(/\\/g, '/'));
if(customCmptDir){
definitions = glob.sync(`${dirToLookUp}/${customCmptDir}/**/config.json`.replace(/\\/g, '/'));
}
if (!definitions) {
console.info(chalk.magenta(`No components found`));
process.exit(1);
}
definitions.forEach((config) => {
config = config.replace(/\\/g, '/');
let cmpntNamePath = config.replace(`${dirToLookUp}/components/`, '').replace('/config.json', '');
if(customCmptDir){
cmpntNamePath = config.replace(`${dirToLookUp}/${customCmptDir}/`, '').replace('/config.json', '');
}
const cmpntName = Object.keys(compMapObj).find(key => compMapObj[key].includes(cmpntNamePath));
if (cmpntName) {
const data = fs.readFileSync(config, 'utf-8');
try {
const cfg = JSON.parse(data);
cmpntDefs.push(cfg);
} catch (ex) {
console.log(ex);
}
}
});
} catch (err) {
console.log(err);
process.exit(1);
}
return cmpntDefs;
};
const getAggregateCompLocalizationForProject = (options, customCmptDir) => {
const cmpntLocalizations = [];
const { dirToLookUp } = options;
const componentList = glob.sync(path.join(dirToLookUp, CONSTANTS.COMPONENT_LIST_FILE_NAME).replace(/\\/g, '/'));
if (!componentList || componentList.length === 0) {
console.error(chalk.red('No component-list.json found'));
return;
}
try {
const compMap = fs.readFileSync(componentList[0], 'utf-8');
const compMapObj = JSON.parse(compMap);
let definitions = glob.sync(`${dirToLookUp}/components/**/localizations.json`.replace(/\\/g, '/'));
if(customCmptDir){
definitions = glob.sync(`${dirToLookUp}/${customCmptDir}/**/localizations.json`.replace(/\\/g, '/'));
}
if (!definitions || definitions.length === 0) {
console.info(chalk.magenta(`No components localization found`));
return;
}
definitions.forEach((config) => {
// get cmpnt name
let cmpntNamePath = config.replace(`${dirToLookUp}/components/`.replace(/\\/g, '/'), '').replace('/localizations.json'.replace(/\\/g, '/'), '');
if(customCmptDir){
cmpntNamePath = config.replace(`${dirToLookUp}/${customCmptDir}/`.replace(/\\/g, '/'), '').replace('/localizations.json'.replace(/\\/g, '/'), '');
}
const cmpntName = Object.keys(compMapObj).find(key => compMapObj[key].includes(cmpntNamePath));
// read cmpnt localization file content
if (cmpntName) {
const data = fs.readFileSync(config, 'utf-8');
// key : value
const cmpntLocalization = { [cmpntName]: JSON.parse(data) };
// push to []
cmpntLocalizations.push(cmpntLocalization);
}
});
} catch (err) {
console.log(err);
}
// return
return cmpntLocalizations;
}
function getComponentsWithConfig(dir, configFileName) {
const results = [];
function recurse(currentDir) {
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const subDir = path.join(currentDir, entry.name);
const configPath = path.join(subDir, configFileName);
if (fs.existsSync(configPath)) {
results.push(subDir);
}
recurse(subDir); // Keep searching deeper
}
}
}
recurse(dir);
return results;
}
module.exports = {
buildLib,
buildLibV3,
buildComponentsListFile,
buildComponentMapJsFile
}