UNPKG

vtex

Version:

The platform for e-commerce apps

286 lines (285 loc) 14.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const axios_1 = tslib_1.__importDefault(require("axios")); const chalk_1 = tslib_1.__importDefault(require("chalk")); const cli_table2_1 = tslib_1.__importDefault(require("cli-table2")); const enquirer_1 = tslib_1.__importDefault(require("enquirer")); const ramda_1 = tslib_1.__importStar(require("ramda")); const semver_diff_1 = tslib_1.__importDefault(require("semver-diff")); const errors_1 = require("../error/errors"); const Apps_1 = require("../clients/IOClients/infra/Apps"); const Registry_1 = require("../clients/IOClients/infra/Registry"); const Workspaces_1 = require("../clients/IOClients/infra/Workspaces"); const manifest_1 = require("../manifest"); const ManifestUtil_1 = require("../manifest/ManifestUtil"); const SessionManager_1 = require("../session/SessionManager"); const logger_1 = tslib_1.__importDefault(require("../logger")); const prompts_1 = require("./prompts"); const child_process_es6_promise_1 = require("child-process-es6-promise"); const diff_1 = require("diff"); const fs_extra_1 = require("fs-extra"); const path_1 = require("path"); const table_1 = require("../table"); const workspaceExampleName = process.env.USER || 'example'; const workspaceMasterAllowedOperations = ['install', 'uninstall']; // It is not allowed to link apps in a production workspace. const workspaceProductionAllowedOperatios = ['install', 'uninstall']; const builderHubMessagesLinkTimeout = 2000; // 2 seconds const builderHubMessagesPublishTimeout = 10000; // 10 seconds exports.workspaceMasterMessage = `This action is ${chalk_1.default.red('not allowed')} in workspace ${chalk_1.default.green('master')}, please use another workspace. You can run "${chalk_1.default.blue(`vtex use ${workspaceExampleName} -r`)}" to use a workspace named "${chalk_1.default.green(workspaceExampleName)}"`; exports.workspaceProductionMessage = workspace => `This action is ${chalk_1.default.red('not allowed')} in workspace ${chalk_1.default.green(workspace)} because it is a production workspace. You can create a ${chalk_1.default.yellowBright('dev')} workspace called ${chalk_1.default.green(workspaceExampleName)} by running ${chalk_1.default.blue(`vtex use ${workspaceExampleName} -r`)}`; exports.promptWorkspaceMaster = async (account) => { const confirm = await prompts_1.promptConfirm(`Are you sure you want to force this operation on the ${chalk_1.default.green('master')} workspace on the account ${chalk_1.default.blue(account)}?`, false); if (!confirm) { return false; } logger_1.default.warn(`Using ${chalk_1.default.green('master')} workspace. I hope you know what you're doing. 💥`); return true; }; exports.validateAppAction = async (operation, app) => { const { account, workspace } = SessionManager_1.SessionManager.getSingleton(); if (workspace === 'master') { if (!ramda_1.contains(operation, workspaceMasterAllowedOperations)) { throw new errors_1.CommandError(exports.workspaceMasterMessage); } else { const confirm = await exports.promptWorkspaceMaster(account); if (!confirm) { return false; } } } const workspaces = Workspaces_1.createWorkspacesClient(); const workspaceMeta = await workspaces.get(account, workspace); if (workspaceMeta.production && !ramda_1.contains(operation, workspaceProductionAllowedOperatios)) { throw new errors_1.CommandError(exports.workspaceProductionMessage(workspace)); } // No app arguments and no manifest file. const isReadable = await manifest_1.ManifestEditor.isManifestReadable(); if (!app && !isReadable) { throw new errors_1.CommandError(`No app was found, please fix your manifest.json${app ? ' or use <vendor>.<name>[@<version>]' : ''}`); } return true; }; exports.wildVersionByMajor = ramda_1.compose(ramda_1.concat(ramda_1.__, '.x'), ramda_1.head, ramda_1.split('.')); exports.extractVersionFromId = ramda_1.compose(ramda_1.last, ramda_1.split('@')); exports.pickLatestVersion = (versions) => { const start = ramda_1.head(versions); return ramda_1.reduce((acc, version) => { return semver_diff_1.default(acc, version) ? version : acc; }, start, ramda_1.tail(versions)); }; exports.handleError = ramda_1.curry((app, err) => { if (err.response && err.response.status === 404) { return Promise.reject(new errors_1.CommandError(`App ${chalk_1.default.green(app)} not found`)); } return Promise.reject(err); }); exports.appLatestVersion = (app, version = 'x') => { return Registry_1.createRegistryClient() .getAppManifest(app, version) .then(ramda_1.prop('id')) .then(exports.extractVersionFromId) .catch(exports.handleError(app)); }; exports.appLatestMajor = (app) => { return exports.appLatestVersion(app).then(exports.wildVersionByMajor); }; exports.appIdFromRegistry = (app, majorLocator) => { return Registry_1.createRegistryClient() .getAppManifest(app, majorLocator) .then(ramda_1.prop('id')) .catch(exports.handleError(app)); }; function optionsFormatter(billingOptions) { /** TODO: Eliminate the need for this stray, single `cli-table2` dependency */ const table = new cli_table2_1.default({ head: [{ content: chalk_1.default.cyan.bold('Billing Options'), colSpan: 2, hAlign: 'center' }], chars: { 'top-mid': '─', 'bottom-mid': '─', 'mid-mid': '─', middle: ' ' }, }); if (billingOptions.free) { table.push([{ content: chalk_1.default.green('This app is free'), colSpan: 2, hAlign: 'center' }]); } else { table.push([ { content: 'Plan', hAlign: 'center' }, { content: 'Values', hAlign: 'center' }, ]); billingOptions.policies.forEach(policy => { let rowCount = 0; const itemsArray = []; policy.billing.items.forEach(i => { if (i.fixed) { itemsArray.push([{ content: `${i.fixed} ${i.itemCurrency}`, hAlign: 'center', vAlign: 'center' }]); rowCount++; } else if (i.calculatedByMetricUnit) { if (i.calculatedByMetricUnit.minChargeValue) { itemsArray.push([`Minimum charge: ${i.calculatedByMetricUnit.minChargeValue} ${i.itemCurrency}`]); rowCount++; } let rangesStr = ''; i.calculatedByMetricUnit.ranges.forEach(r => { if (r.inclusiveTo) { rangesStr += `${r.multiplier} ${i.itemCurrency}/${i.calculatedByMetricUnit.metricName} (${r.exclusiveFrom} to ${r.inclusiveTo})`; rangesStr += '\nor\n'; } else { rangesStr += `${r.multiplier} ${i.itemCurrency}/${i.calculatedByMetricUnit.metricName} (over ${r.exclusiveFrom})`; } }); rowCount++; itemsArray.push([{ content: rangesStr, hAlign: 'center', vAlign: 'center' }]); } itemsArray.push([{ content: '+', hAlign: 'center' }]); rowCount++; }); itemsArray.pop(); rowCount--; table.push([ { content: `${chalk_1.default.yellow(policy.plan)}\n(Charged montlhy)`, rowSpan: rowCount, colSpan: 1, vAlign: 'center', hAlign: 'center', }, itemsArray[0][0], ], ...itemsArray.slice(1)); table.push([ { content: `The monthly amount will be charged in ${chalk_1.default.red(policy.currency)}`, colSpan: 2, hAlign: 'center', }, ]); }); } table.push([ { content: chalk_1.default.bold('Terms of use:'), hAlign: 'center' }, { content: billingOptions.termsURL, hAlign: 'center' }, ]); return table.toString(); } exports.optionsFormatter = optionsFormatter; async function checkBuilderHubMessage(cliRoute) { const http = axios_1.default.create({ baseURL: `https://vtex.myvtex.com`, timeout: cliRoute === 'link' ? builderHubMessagesLinkTimeout : builderHubMessagesPublishTimeout, }); try { const res = await http.get(`/_v/private/builder/0/getmessage/${cliRoute}`); return res.data; } catch (e) { return {}; } } exports.checkBuilderHubMessage = checkBuilderHubMessage; const promptConfirmName = (msg) => enquirer_1.default .prompt({ message: msg, name: 'appName', type: 'input', }) .then(ramda_1.prop('appName')); async function showBuilderHubMessage(message, showPrompt, manifest) { if (message) { if (showPrompt) { const confirmMsg = `Are you absolutely sure?\n${message || ''}\nPlease type in the name of the app to confirm (ex: vtex.getting-started):`; const appNameInput = await promptConfirmName(confirmMsg); const AppName = `${manifest.vendor}.${manifest.name}`; if (appNameInput !== AppName) { throw new errors_1.CommandError(`${appNameInput} doesn't match with the app name.`); } } else { logger_1.default.info(message); } } } exports.showBuilderHubMessage = showBuilderHubMessage; exports.resolveAppId = (appName, appVersion) => { const apps = Apps_1.createAppsClient(); return apps.getApp(`${appName}@${appVersion}`).then(ramda_1.prop('id')); }; exports.isLinked = ramda_1.propSatisfies(ramda_1.contains('+build'), 'version'); exports.yarnPath = `"${require.resolve('yarn/bin/yarn')}"`; exports.formatNano = (nanoseconds) => `${(nanoseconds / 1e9).toFixed(0)}s ${((nanoseconds / 1e6) % 1e3).toFixed(0)}ms`; exports.runYarn = (relativePath, force) => { logger_1.default.info(`Running yarn in ${chalk_1.default.green(relativePath)}`); const root = ManifestUtil_1.getAppRoot(); const command = force ? `${exports.yarnPath} --force --non-interactive --ignore-engines` : `${exports.yarnPath} --non-interactive --ignore-engines`; child_process_es6_promise_1.execSync(command, { stdio: 'inherit', cwd: path_1.resolve(root, relativePath) }); logger_1.default.info('Finished running yarn'); }; exports.runYarnIfPathExists = (relativePath) => { const root = ManifestUtil_1.getAppRoot(); const pathName = path_1.resolve(root, relativePath); if (fs_extra_1.existsSync(pathName)) { try { exports.runYarn(relativePath, false); } catch (e) { logger_1.default.error(`Failed to run yarn in ${chalk_1.default.green(relativePath)}`); throw e; } } }; const formatAppId = (appId) => { const [appVendor, appName] = ramda_1.default.split('.', appId); if (!appName) { // Then the app is an 'infra' app. const [infraAppVendor, infraAppName] = ramda_1.default.split(':', appId); if (!infraAppName) { return appId; } return `${chalk_1.default.blue(infraAppVendor)}:${infraAppName}`; } return `${chalk_1.default.blue(appVendor)}.${appName}`; }; const cleanVersion = (appId) => { return ramda_1.default.compose((version) => { const [pureVersion, build] = ramda_1.default.split('+build', version); return build ? `${pureVersion}(linked)` : pureVersion; }, ramda_1.default.last, ramda_1.default.split('@'))(appId); }; exports.matchedDepsDiffTable = (title1, title2, deps1, deps2) => { const depsDiff = diff_1.diffArrays(deps1, deps2); // Get deduplicated names (no version) of the changed deps. const depNames = [ ...new Set(ramda_1.default.compose(ramda_1.default.map(k => ramda_1.default.head(ramda_1.default.split('@', k))), ramda_1.default.flatten, ramda_1.default.pluck('value'), ramda_1.default.filter((k) => !!k.removed || !!k.added))(depsDiff)), ].sort((strA, strB) => strA.localeCompare(strB)); const produceStartValues = () => ramda_1.default.map(_ => [])(depNames); // Each of the following objects will start as a { `depName`: [] }, ... }-like. const addedDeps = ramda_1.default.zipObj(depNames, produceStartValues()); const removedDeps = ramda_1.default.zipObj(depNames, produceStartValues()); // Custom function to set the objects values. const setObjectValues = (obj, formatter, filterFunction) => { ramda_1.default.compose( // eslint-disable-next-line array-callback-return ramda_1.default.map(k => { const index = ramda_1.default.head(ramda_1.default.split('@', k)); obj[index].push(formatter(k)); }), ramda_1.default.flatten, ramda_1.default.pluck('value'), ramda_1.default.filter(filterFunction))(depsDiff); ramda_1.default.mapObjIndexed((_, index) => { obj[index] = obj[index].join(','); })(obj); }; // Setting the objects values. setObjectValues(removedDeps, k => chalk_1.default.red(`${cleanVersion(k)}`), (k) => !!k.removed); setObjectValues(addedDeps, k => chalk_1.default.green(`${cleanVersion(k)}`), (k) => !!k.added); const table = table_1.createTable(); // Set table headers. table.push(['', chalk_1.default.bold.yellow(title1), chalk_1.default.bold.yellow(title2)]); const formattedDepNames = ramda_1.default.map(formatAppId, depNames); // Push array of changed dependencies pairs to the table. Array.prototype.push.apply(table, ramda_1.default.map((k) => ramda_1.default.flatten(k))(ramda_1.default.zip( // zipping 3 arrays. ramda_1.default.zip(formattedDepNames, ramda_1.default.values(removedDeps)), ramda_1.default.values(addedDeps)))); return table; };