@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.
316 lines (277 loc) • 14 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) => {
if (!notClean)
helper.cleanUpBuildFolder(CONSTANTS.CURRENT_WORKING_DIR, buildFolderName, packageVersion);
console.log(chalk.magenta("== Aggregating components to build - Components List - Started =="));
const projectPath = path.join(CONSTANTS.CURRENT_WORKING_DIR, CONSTANTS.PROJECT_ROOT).replace(/\\/g, '/');
const componentsDir = path.join(projectPath, CONSTANTS.COMPONENTS_DIR).replace(/\\/g, '/');
const componentsLists = fs.readdirSync(componentsDir).filter(function (file) {
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 (!cmpIndexPathObj[component] && fs.existsSync(path.join(componentsDir, component, indexFile))) {
let indexPath = path.join(CONSTANTS.COMPONENTS_DIR, 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) => {
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 projectPath = path.join(CONSTANTS.CURRENT_WORKING_DIR, CONSTANTS.PROJECT_ROOT).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 overrideQueryParam = component.split(":")[1];
const componentPath = path.join(projectPath, 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, '/')
});
if (tempList) {
cmpntDefs.push(...tempList);
const componentsConfigPath = path.join(projectPath, "../", buildFolderFullPath, 'componentsconfig.json').replace(/\\/g, '/');
ensureDirSync(componentsConfigPath);
fs.writeFileSync(componentsConfigPath, JSON.stringify(cmpntDefs), { encoding: 'utf8' });
}
const tempCompLocalizationList = getAggregateCompLocalizationForProject({
dirToLookUp: path.join(projectPath).replace(/\\/g, '/')
});
if (tempCompLocalizationList) {
cmpntLocalizations.push(...tempCompLocalizationList);
const componentsLocalizationPath = path.join(projectPath, "../", 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) => {
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 projectPath = path.join(CONSTANTS.CURRENT_WORKING_DIR, CONSTANTS.PROJECT_ROOT).replace(/\\/g, '/');
const webpackPath = path.join(CONSTANTS.BUILD_TOOL_ROOT_PATH).replace(/\\/g, '/');
child_process.execSync(`npx webpack --mode=production --env buildFolderFullPath=${buildFolderFullPath} buildFolderName=${buildFolderName} packageVersion=${packageVersion} appStaticUrl=${appStaticUrl} orgId=${orgId} v3=${v3} --config ${webpackPath}/webpack.config.js`, {
stdio: [0, 1, 2],
cwd: 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;
console.log(CONSTANTS.COMPONENT_LIST_FILE_NAME_OUTPUT + ' was deleted');
});
fs.unlink(path.join(projectPath, CONSTANTS.COMPONENT_LIST_FILE_NAME).replace(/\\/g, '/'), (err) => {
if (err) throw err;
console.log(CONSTANTS.COMPONENT_LIST_FILE_NAME + ' was deleted');
});
fs.unlink(path.join(projectPath, CONSTANTS.ENTRY_POINT_FILE).replace(/\\/g, '/'), (err) => {
if (err) throw err;
console.log(CONSTANTS.ENTRY_POINT_FILE + ' was deleted');
});
};
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) => {
console.log(chalk.magenta("== Creating manifest for the library version - Started =="));
const projectPath = path.join(CONSTANTS.CURRENT_WORKING_DIR, CONSTANTS.PROJECT_ROOT).replace(/\\/g, '/');
const data = {};
try {
// read stats.json file
const stats = fs.readFileSync(path.join(projectPath, '../', buildFolderFullPath, CONSTANTS.STATS).replace(/\\/g, '/'), '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;
console.log(CONSTANTS.STATS + ' was deleted');
});
}
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) => {
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);
// build component map
buildComponentMapJsFile(buildFolderFullPath);
// then build env
webpackBuild(buildFolderFullPath, buildFolderName, packageVersion, tokenParam, appStaticSVCUrl, v3);
// generate manifest file
buildManifestFile(buildFolderFullPath, v3);
};
const buildLibV3 = (libraryName, libraryVersion, tokenParam, appStaticSVCUrl) => {
buildLib(libraryName, libraryVersion, tokenParam, appStaticSVCUrl, true);
};
const getAggregateCompListForProject = (options) => {
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'));
return;
}
try {
const compMap = fs.readFileSync(componentList[0], 'utf-8');
const compMapObj = JSON.parse(compMap);
const definitions = glob.sync(`${dirToLookUp}/components/**/config.json`.replace(/\\/g, '/'));
if (!definitions) {
console.info(chalk.magenta(`No components found`));
return;
}
definitions.forEach((config) => {
config = config.replace(/\\/g, '/');
let cmpntNamePath = config.replace(`${dirToLookUp}/components/`, '').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);
}
return cmpntDefs;
};
const getAggregateCompLocalizationForProject = (options) => {
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);
const definitions = glob.sync(`${dirToLookUp}/components/**/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, '/'), '');
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;
}
module.exports = {
buildLib,
buildLibV3,
buildComponentsListFile,
buildComponentMapJsFile
}