UNPKG

@pega/custom-dx-components

Version:

Utility for building custom UI components

500 lines (391 loc) 16.5 kB
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; };