@pega/custom-dx-components
Version:
Utility for building custom UI components
500 lines (391 loc) • 16.5 kB
JavaScript
import inquirer from 'inquirer';
import { Listr } from 'listr2';
import chalk from 'chalk';
import fetch from 'node-fetch';
import archiver from 'archiver';
import path from 'path';
import fs from 'fs';
import ora from 'ora';
import validate from '../validator/index.js';
import { lintComponent } from '../linter/index.js';
import { constructCompleteUrl, getComponentDirectoryPath, getHttpsAgent, getPegaServerConfig, showVersion, getC11NB2STokenAndStaticServer, getLocalLibraryVersions, getLibraryBased, getLibraryBasedCL, deletePublishExtras, addDebugLog, checkLibraryAndArchives, checkJWTExpiration, getConfigDefaults, checkForBranch } from '../../util.js';
import { publishLib, listLibJSONResponse, deleteLibVersion } from '@pega/constellation-dx-components-build-utils/index.js';
import { getLibraryVersionQuestion } from './helper.js';
import {
LP_PUBLISH_COMPONENT_SERVICE_REST_ENDPOINT,
PUBLISH_COMPONENT_LIBRARY_SERVICE_REST_ENDPOINT,
TOKEN_PATH
} from '../../constants.js';
export function getCustomTasks(componentKey, sourceMap, devBuild, options) {
addDebugLog("getCustomTasks", `componentKey: ${componentKey}, sourceMap: ${sourceMap}, devBuild: ${devBuild}`, "");
const sDevBuild = devBuild ? '(dev build)' : '';
return new Listr(
[
{
title: 'Validate config schema',
task: async () => {
await validate(componentKey);
}
},
{
title: 'Lint component',
task: async () => {
const targetDirectory = await getComponentDirectoryPath(componentKey);
// console.log(`in buildComponent Lint component task: componentKey: ${componentKey} targetDirectory: ${targetDirectory}`);
await lintComponent(targetDirectory);
}
}
],
{ concurrent: false, exitOnError: true }
);
}
export const doesLibraryExist = async (libraryName, libraryVersion) => {
addDebugLog("doesLibraryExist", `libraryName: ${libraryName}, libraryVersion: ${libraryVersion}`, "");
const tokenAndStaticServer = await getC11NB2STokenAndStaticServer();
const libList = await listLibJSONResponse(tokenAndStaticServer.C11NB2S, tokenAndStaticServer.appStaticContentServer);
let libVersionExists = false;
if (libList && libList[libraryName] && libList[libraryName][libraryVersion]) {
libVersionExists = true;
}
return libVersionExists;
}
export const zipLibraryComponent = async (libraryName, version) => {
addDebugLog("zipLibraryComponent", `libraryName: ${libraryName}, libraryVersion: ${version}`, "");
const currentDirectory = process.cwd();
const libraryDirName = `${libraryName}/${version}`;
const libraryDirectory = path.join(currentDirectory, libraryDirName);
const componentsConfigJson = path.join(libraryDirectory, "componentsconfig.json");
// const componentsConfigJson = `${libraryDirectory}/componentsconfig.json`;
const localizationsJson = path.join(libraryDirectory, "componentslocalization.json");
const archive = archiver('zip', { zlib: { level: 9 } });
const zipChunks = [];
archive.on('data', chunk => zipChunks.push(chunk));
// No going to add full list of created by buildLib, instead
// below going just just *.js files and a few specific .json files
// // Add main directory as src directory in zip
// archive.directory(libraryDirectory, 'src');
// only zip *.js files, not *.js.br, *.js.map, license, text, json, etc.
archive.glob("*.js", { cwd: libraryDirectory });
// Add componentsconfig.json.json file (array of config.json)
if (fs.existsSync(componentsConfigJson)) {
archive.file(componentsConfigJson, { name: path.basename(componentsConfigJson) });
}
// Add componentslocalization.json (array of component localizations) file
if (fs.existsSync(localizationsJson)) {
archive.file(localizationsJson, { name: path.basename(localizationsJson) });
}
await archive.finalize();
const zipBuffer = Buffer.concat(zipChunks);
console.log(chalk.green(`component zipped with size: ${Math.ceil(zipBuffer.length / 1024)} KB`));
const zipContent = zipBuffer.toString('base64');
const configContent = Buffer.from(fs.readFileSync(componentsConfigJson)).toString();
let localizationsContent = "[]";
if (fs.existsSync(localizationsJson)) {
localizationsContent = Buffer.from(fs.readFileSync(localizationsJson)).toString();
}
return { zipContent, configContent, localizationsContent};
};
export const publishComponentLibraryToServer = async (data, doFetch) => {
addDebugLog("publishComponentToServer", `data: ${data}`, "");
const { configContent, zipContent, localizationsContent, rulesetName, rulesetVersion, libraryName, version } = data;
const defaultPegaServerConfig = await getPegaServerConfig();
const { serverType, isolationId } = defaultPegaServerConfig;
const isLaunchpad = serverType === 'launchpad';
const launchpadRestEndpoint = LP_PUBLISH_COMPONENT_SERVICE_REST_ENDPOINT.replace(
'{isolationId}',
isolationId || 'undefined'
);
let apiBody;
if (isLaunchpad) {
// currently no launchpad records to rule resolve a library
} else {
apiBody = {
configContent,
zipContent,
publishFor: 'constellation',
rulesetName,
rulesetVersion,
libraryName,
version,
category: '',
localizationsContent
};
}
const defaultPegaConfig = await getPegaServerConfig();
const url = constructCompleteUrl(
defaultPegaConfig.server,
isLaunchpad ? launchpadRestEndpoint : PUBLISH_COMPONENT_LIBRARY_SERVICE_REST_ENDPOINT
);
if (doFetch) {
try {
const OauthData = fs.readFileSync(TOKEN_PATH, 'utf8');
if (OauthData) {
const {
access_token: accessToken,
token_type: tokenType
// refresh_token: refreshToken
} = JSON.parse(OauthData);
let status = 500;
let headers = {};
if (isLaunchpad) {
headers.cookie = `Pega-AAT=${accessToken}`;
headers['Content-Type'] = 'application/json';
} else {
headers.Authorization = `${tokenType} ${accessToken}`;
}
let spinner;
console.log();
spinner = ora(chalk.green.bold('Publishing library...')).start();
const response = await fetch(url, {
method: 'POST',
agent: getHttpsAgent(defaultPegaConfig),
headers,
body: JSON.stringify(apiBody)
});
spinner.stop();
status = response.status;
if (!response.ok) {
if (status === 401) {
throw new Error(
'Error occurred in authentication. Please regenerate using authenticate'
);
// console.log(accessTokenUri, refreshToken);
/* TODO - Handle refresh_token */
} else if (status === 404) {
throw new Error('404: Server resource not found');
} else if (status === 405) {
throw new Error('405: Server method not allowed');
} else if (status === 408) {
throw new Error('408: Server timed out');
} else if (response.status === 403) {
throw new Error('Error forbidden: User does not have privileges to Publish.');
}
}
try {
// const respData = isLaunchpad ? await response.text() : await response.json();
const headerType = response.headers.get('content-type');
const respData = headerType.includes("json") ? await response.json() : await response.text();
let respMessage = headerType.includes("json") ? respData.message : respData;
if (status === 500) {
status = 999;
if (respMessage === '') {
respMessage = `Server error, please check server logs for error.`;
}
throw new Error(respMessage);
}
console.log(chalk.bold.green(`Success : ${respMessage }`));
} catch (err) {
if (status === 500) {
throw new Error(`Server error, please check server logs for error.`);
} else if (status === 999) {
throw new Error(err);
}
}
} else {
throw new Error(`Error occurred in authentication. Please regenerate using authenticate`);
}
} catch (err) {
// throw new Error(`Error occurred in authentication. Please regenerate using authenticate`);
throw new Error(err);
}
}
};
export const publishUILibraryComponentToInfinity = async (content) => {
addDebugLog("publishUILibraryComponentToInfinity", `content: ${content}`, "");
const defaultPegaServerConfig = await getPegaServerConfig();
console.log(chalk.green(`Publishing ${content.libraryName}/${content.version} to server ${chalk.green.bold(`${defaultPegaServerConfig.server}`)}.`));
const fromZip = await zipLibraryComponent(content.libraryName, content.version);
content = { ...fromZip, ...content };
try {
await publishComponentLibraryToServer(content, true );
// console.log(chalk.green.bold(`Success: Published ${content.libraryName}/${content.version} to server as a CL.`));
}
catch (err) {
let sErr = err.toString();
// strip all Error:
sErr = sErr.replaceAll("Error: ", "");
console.log(chalk.bold.red("Error: " + sErr));
process.exit(1);
}
}
export const publishNewLibraryAsync = async (libraryName, version, checkDeleted = false) => {
addDebugLog("publishNewLibraryAsync", `libraryName: ${libraryName}, version: ${version}, checkDeleted: ${checkDeleted} `, "");
const tokenAndStaticServer = await getC11NB2STokenAndStaticServer();
if (checkDeleted ) {
console.log(chalk.yellow(`Verifying library has been deleted..`));
let libExists = true;
let count = 0;
while (libExists) {
// sleep a second and try again
await new Promise(r => setTimeout(r, 1000));
// keep checking until no longer exists
libExists = await doesLibraryExist(libraryName, version);
count ++;
if (count > 100) {
console.log(chalk.red.bold(`Library hasn't been deleted, can't proceed.`));
process.exit(1);
}
}
console.log(chalk.green(`Deleted..`));
}
console.log("\nPublishing Library " + chalk.bold.green(`${libraryName}/${version}`) + " to " + chalk.bold.green(`${tokenAndStaticServer.appStaticContentServer}`));
addDebugLog("publishLib", "services call", "");
await publishLib(libraryName, version, tokenAndStaticServer.C11NB2S, tokenAndStaticServer.appStaticContentServer);
addDebugLog("deleteLib", "services end", "");
}
export default async (options) => {
const isLibraryBased = getLibraryBased();
const isLibraryBasedCL = getLibraryBasedCL();
if (!isLibraryBased) {
console.log(`Command only supported for ${chalk.bold.green('library mode')} components.`)
process.exit();
}
if (options.params.length >= 4) {
// internal so already called
await showVersion();
await checkLibraryAndArchives();
await checkJWTExpiration();
}
addDebugLog("publishLibrary", "", "+");
let organization;
let library;
let version;
let devBuild;
let internalPublish = false;
let rulesetName;
let rulesetVersion;
let libraryName;
let noRealPublish = false;
if (options.params.length === 7) {
libraryName = options.params[3];
version = options.params[4];
rulesetName = options.params[5];
rulesetVersion = options.params[6];
}
else if (options.params.length === 8) {
libraryName = options.params[3];
version = options.params[4];
rulesetName = options.params[5];
rulesetVersion = options.params[6];
noRealPublish = true;
}
else if (options.params.length === 6) {
rulesetName = options.params[3];
rulesetVersion = options.params[4];
// internal call from publish
internalPublish = true;
const orgLib = getConfigDefaults();
library = orgLib.library;
organization = orgLib.organization;
libraryName = orgLib.currentOrgLib;
version = orgLib.buildVersion;
}
await checkForBranch(rulesetName);
const tokenAndStaticServer = await getC11NB2STokenAndStaticServer();
if (tokenAndStaticServer.C11NB2S === undefined) {
console.log(chalk.redBright("Need to authenticate, missing services token.\nPublishing a library requires authentication to acquire a token to publish."));
process.exit(1);
}
let libVersions = [];
if (!libraryName) {
const compDef = getConfigDefaults();
organization = compDef.organization;
library = compDef.library;
libraryName = compDef.currentOrgLib;
try {
libVersions = await getLocalLibraryVersions(libraryName);
}
catch (ex) {}
}
console.log(`\nPreparing to publish ${chalk.bold.green(`${libraryName}/${version}`)} `)
if (libVersions.length > 0 || version) {
if (!version) {
const versionQuestions = await getLibraryVersionQuestion(libVersions);
const versionAnswers = await inquirer.prompt(versionQuestions);
({version} = versionAnswers);
}
let okToPublish = true;
// this check matches check below, but for JEST, we don't want to go any further, just verifying if will
// be caught
if (noRealPublish && !version.includes("-dev")) {
console.log("PRODUCTION, no publish");
return;
}
// This is for 24.2 version of libraryMode, libraryModeCL doesn't do "permanent"
if (!version.includes("-dev") && !internalPublish && !isLibraryBasedCL) {
okToPublish = false;
const publishAnswers = await inquirer.prompt([
{
name: 'confirmPublish',
type: 'confirm',
message: `You are about to publish a ${chalk.bold.yellow('PRODUCTION')} version which can ${chalk.bold.yellow('NOT')} be deleted, proceed ?`,
default: false
}
]);
if (publishAnswers.confirmPublish) {
okToPublish = true;
}
}
if (noRealPublish) {
// this is as far as JEST test can go since need server for rest and don't want to use server.
console.log("OK to publish.");
return;
}
if (okToPublish) {
if (isLibraryBasedCL) {
// for Library Mode CL (25.1 and greater), this is the default
let content = { libraryName, version, rulesetName, rulesetVersion };
await publishUILibraryComponentToInfinity(content);
}
else {
//
// This is 24.2 library mode with App Static (old)
// at some point need to deprecate and remove this code
//
let libVersionExists = await doesLibraryExist(libraryName, version);
let overrideExisting = false;
let checkDeleted = false;
await deletePublishExtras(libraryName, version);
if (libVersionExists) {
console.log("\nLibrary " + chalk.bold.yellow(`${libraryName}/${version}`) + " already exists.");
const questions = [
{
name: 'override',
type: 'confirm',
message: `Do you wish to overwrite it`,
default: false
}
];
await inquirer.prompt(questions).then(async answers => {
overrideExisting = answers.override;
});
}
if (libVersionExists) {
if (overrideExisting) {
console.log("\nDeleting existing library " + chalk.bold.yellow(`${libraryName}:${version}`) + " ...");
addDebugLog("deleteLibVersion", `services call: libraryName: ${libraryName}, version: ${version}`, "");
await deleteLibVersion(libraryName, version, tokenAndStaticServer.C11NB2S, tokenAndStaticServer.appStaticContentServer);
addDebugLog("deleteLibVersion", "services end", "");
checkDeleted = true;
}
else {
// don't override, so end
console.log(chalk.yellow(`You will need to increment your library version to publish`));
addDebugLog("publishLibrary", "END", "-");
return;
}
}
// need a timeout to give services time to delete the library
// setTimeout( function(){ await publishNewLibraryAsync(libraryName, version, checkDeleted)}, 700 );
await publishNewLibraryAsync(libraryName, version, checkDeleted);
}
}
}
else {
console.log(chalk.bold.redBright(`No library versions named ${libraryName}.`));
console.log(chalk.bold.redBright(`You need to build library ${libraryName} first.`));
process.exit(1);
}
addDebugLog("publishLibrary", "END", "-");
return true;
};