UNPKG

@pega/custom-dx-components

Version:

Utility for building custom UI components

489 lines (408 loc) 17.2 kB
import inquirer from 'inquirer'; import { Listr } from 'listr2'; import chalk from 'chalk'; 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, getFetchAgent, getPegaServerConfig, showVersion, getC11NB2STokenAndStaticServer, getLocalLibraryVersions, getLibraryBased, getLibraryBasedCL, deletePublishExtras, addDebugLog, checkLibraryAndArchives, checkJWTExpiration, getConfigDefaults, checkForBranch, collectAllComponentSvgFiles } 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 manifestJson = path.join(libraryDirectory, 'manifest.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) }); } // Add manifest file if (fs.existsSync(manifestJson)) { archive.file(manifestJson, { name: path.basename(manifestJson) }); } 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(); } let manifestContent = ''; if (fs.existsSync(manifestJson)) { manifestContent = Buffer.from(fs.readFileSync(manifestJson)).toString(); } // Collect SVG files from all components in the source directory const srcComponentsDir = path.join(currentDirectory, 'src', 'components'); const svgContent = await collectAllComponentSvgFiles(srcComponentsDir); return { zipContent, configContent, localizationsContent, svgContent, manifestContent }; }; export const publishComponentLibraryToServer = async (data, doFetch) => { addDebugLog('publishComponentToServer', `data: ${data}`, ''); const { configContent, zipContent, localizationsContent, svgContent, rulesetName, rulesetVersion, libraryName, version, manifestContent } = 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, svgContent: svgContent || [], manifestContent }; } 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', dispatcher: getFetchAgent(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; };