UNPKG

@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
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 }