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