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.

273 lines (246 loc) 12.3 kB
const child_process = require('child_process'); const path = require('path'); const fs = require('fs'); const ora = require('ora-classic'); const CONSTANTS = require('../constant'); const chalk = require('chalk'); const helper = require('../helper'); const replace = require('replace-in-file'); const { AppStaticService } = require('../../services/appstatic.service'); /* For dev, process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';*/ /** * This function Publish the library to appstatic service 1) zip the build folder. 2) encode the .zip file to base64 and send it in request body. 3) post call to /v100/componentlib endpoint to publish the component * @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 publishLib = async (libraryName, libraryVersion, tokenParam, appStaticSVCUrl) => { const { buildFolderName, packageVersion } = helper.getBuildFolderName(libraryName, libraryVersion); let componentRequestData; const zipFolderPath = path.join(process.cwd(), buildFolderName).replace(/\\/g, '/'); const folderName = path.basename(zipFolderPath).replace(/\\/g, '/'); const srcToZip = path.join(folderName, packageVersion).replace(/\\/g, '/'); const token = helper.getB2SToken(tokenParam); const appStaticServerUrl = helper.getAppStaticServerUrl(appStaticSVCUrl); const spinner = ora(chalk.magenta(`Verifying the Cosmos versions in use.\n`)); /* Step1: zip the file */ try{ /* Not needed now multiple version visible in build forlder, console.log(chalk.magenta('Running cleanup on build folder before zipping...')); helper.cleanUpBuildFolder(parentFolder, buildFolderName, packageVersion); */ if(!token){ console.log(chalk.red('B2STOKEN, Auth token is empty, exiting publish operation.')); process.exit(1); } if(!appStaticServerUrl){ console.log(chalk.red('App static service url not available, exiting publish operation.')); process.exit(1); } spinner.color = 'magenta'; spinner.start(); try { const npmListOutput = child_process.execSync('npm list --depth=0', { encoding: 'utf-8' }); if(npmListOutput && npmListOutput.indexOf(CONSTANTS.COSMOS_PKG_PATTERN) !== -1 && token){ const cosmosVersion = helper.getVersionFromListOutput(npmListOutput, CONSTANTS.COSMOS_PKG_PATTERN); const pegaStaticURL = helper.getPayloadFromTkn(token, 'pega_staticurl'); if(pegaStaticURL){ const buildInfoResponse = await fetch(pegaStaticURL.endsWith('/')?pegaStaticURL+CONSTANTS.STATIC_BUILDINFO : pegaStaticURL+'/'+CONSTANTS.STATIC_BUILDINFO, { method: 'GET', agent: helper.getHttpsAgent() }); if(buildInfoResponse.status === 200){ const buildInfoData = await buildInfoResponse.json(); const diffInCosmos = helper.compareObjects(cosmosVersion, buildInfoData[CONSTANTS.C11N_KEY_PEGA_DEP]); if(diffInCosmos && !(Object.keys(diffInCosmos).length === 0 && diffInCosmos.constructor === Object)){ spinner.stop(); console.log(chalk.red('Error: Please ensure that your package version aligns with the targeted Constellation runtime portal.')); console.log(chalk.yellow.bold('Prior to publishing, kindly address any version inconsistencies among the Cosmos packages referenced in your library. After resolving these issues, rebuild your project')); console.log(diffInCosmos); process.exit(1); } } } } } catch (error) { console.error(`Error executing npm list: ${error}`); } //console.log(chalk.magenta(`Zipping content from: ${path.join(process.cwd(), buildFolderName).replace(/\\/g, '/')}`)); spinner.text = chalk.magenta(`Zipping content from: ${path.join(process.cwd(), buildFolderName).replace(/\\/g, '/')}.`); spinner.color = 'magenta'; spinner.stop(); spinner.start(); if(token){ const orgId = helper.getPayloadFromTkn(token, 'customer_org'); if(orgId && appStaticServerUrl){ let appStaticURL = appStaticServerUrl; if(appStaticURL.endsWith('/')) appStaticURL = appStaticServerUrl.substring(0, appStaticServerUrl.lastIndexOf('/')); const options = { files: srcToZip+'/**', from: [/{ORG_ID}/g, /{APP_URL}/g], to: [orgId, appStaticURL], }; await replace(options); } } const { status } = await helper.zipContent(folderName, srcToZip); if(status === 'success'){ console.log('\n'+chalk.green('=== Zipped successfully ===')) } else{ spinner.stop(); return; } } catch(err){ spinner.stop(); console.log(chalk.red(`=== Error occurred while zipping build folder. exiting publish === : ${err}`)); process.exit(1); } /* Step2: encode to 'base64' string */ try{ //console.log(chalk.magenta(`encoding zip to base64 ....`)); spinner.text = chalk.magenta(`Encoding zip to base64.\n`); spinner.color = 'magenta'; spinner.stop(); spinner.start(); componentRequestData = fs.readFileSync(`${zipFolderPath}.zip`, {encoding:'base64'}); if(!componentRequestData){ spinner.stop(); console.log(chalk.red(`zip file encoded to empty string... exiting publish`)); process.exit(1); } console.log(chalk.green('=== Encoded successfully ===')) } catch(err){ spinner.stop(); console.log(chalk.red("=== Error occurred while encoding zip to base64. exiting publish ===")); console.error(err); process.exit(1); } // Check for lib and version already exists in server. const initialLibraryList = await AppStaticService.fetchLibraryNameOrVersions(buildFolderName, tokenParam, appStaticSVCUrl); // Assuming this is an async function if(Array.isArray(initialLibraryList) && initialLibraryList.some(version => version === packageVersion)){ spinner.stop(); console.log(chalk.red(`Library already exists ${chalk.green.underline.bold(buildFolderName+'/'+packageVersion)} in server, Increment the library version, please build and publish new version of library.`)); process.exit(1); } //const spinner = ora(chalk.magenta(`Initiating call to appstatic for publishing Library`)); spinner.text = chalk.magenta(`Initiating call to appstatic for publishing Library.\n`); spinner.color = 'magenta'; spinner.stop(); spinner.start(); /* Preparing endpoint url, headers, requestbody */ //console.log(chalk.magenta(`initiating call to appstatic for publishing component`)); const URL = appStaticServerUrl && appStaticServerUrl.endsWith('/') ? appStaticServerUrl+CONSTANTS.ENDPOINTURL : appStaticServerUrl+'/'+CONSTANTS.ENDPOINTURL; let status = 500; // defaulting to 500(internal server error) const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); const maxRetries = 4; let retries = 0; const retryInterval = 60000; // 60 seconds in milliseconds const headers = { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }; const reqbody = { zip: componentRequestData } try { const response = await fetchWithTimeout (URL, { method: 'POST', agent: helper.getHttpsAgent(), headers, body: JSON.stringify(reqbody), }); spinner.text = chalk.magenta(`We are processing your request. This may take a few minutes.\n`); spinner.color = "magenta"; spinner.stop() spinner.start(); status = response.status; data = await response.text(); if (status === 401) { spinner.stopAndPersist(); throw new Error('Error occurred in authentication. Please regenerate using authenticate'); } else if (status === 200 || status === 201) { spinner.stopAndPersist(); console.log(chalk.green(`========= Publish Success ✅ : ${chalk.green.underline.bold(buildFolderName+'/'+packageVersion)} =========`)); } else if(status === 504){ const timeout = setTimeout(() => { spinner.text = chalk.yellow('Still working on your request. Thank you for your patience!\n'); spinner.color = 'yellow'; spinner.stop(); spinner.start(); }, retryInterval*1.5); // polling to check lib and version availability while (retries < maxRetries) { retries += 1; //console.log(chalk.yellow(`Gateway timeout, attempt ${retries}. Retrying in ${retryInterval/1000} seconds...`)); await delay(retryInterval); const libraryList = await AppStaticService.fetchLibraryNameOrVersions(buildFolderName, tokenParam, appStaticSVCUrl); // Assuming this is an async function if(Array.isArray(libraryList) && libraryList.some(version => version === packageVersion)){ clearTimeout(timeout); spinner.stop(); console.log(chalk.green(`========= Publish Success ✅ : ${chalk.green.underline.bold(buildFolderName+'/'+packageVersion)} =========`)); break; } else if(retries == maxRetries){ clearTimeout(timeout); spinner.stop(); console.log(chalk.yellow(`The request has timed out. Please once verify the server's library availability using the list-library.`)); } } } else if(status === 400){ spinner.stopAndPersist(); console.log(chalk.redBright(`Bad Request: ${data}. \nIncrement the library version in package.json to publish new version of library.`)); process.exit(1); } else { spinner.stopAndPersist(); console.log(chalk.red(`Error status: ${status}`)); throw new Error(`${response}`); } } catch (e) { spinner.stopAndPersist(); if (e && e.code === 'ECONNREFUSED') { console.log(chalk.red("Error: Connection refused")); process.exit(1); } else if (status === 403) { console.log(chalk.red("Error: 403 Forbidden")); process.exit(1); } else { if(e){ console.log(chalk.red(e)); process.exit(1); } else{ console.log(chalk.yellow(`Possible timeout, please check library in server via list-library.`)); process.exit(1); } } } } async function fetchWithTimeout(url, options = {}, timeout = 300000) { // Set timeout to 2 minutes (120,000 milliseconds) const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { ...options, signal: controller.signal }); clearTimeout(timeoutId); return response; } catch (error) { if (error.name === 'AbortError') { throw new Error(`Request timed out after ${timeout} ms`); } throw error; } } module.exports = { publishLib }