UNPKG

salesforce-alm

Version:

This package contains tools, and APIs, for an improved salesforce.com developer experience.

255 lines (253 loc) 11.3 kB
"use strict"; /* * Copyright (c) 2020, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ Object.defineProperty(exports, "__esModule", { value: true }); exports.MdRetrieveApi = void 0; const path = require("path"); // Local const core_1 = require("@salesforce/core"); const ManifestCreateApi = require("../source/manifestCreateApi"); const almError = require("../core/almError"); const consts = require("../core/constants"); const logger = require("../core/logApi"); const StashApi = require("../core/stash"); const RetrieveReportApi = require("./mdapiRetrieveReportApi"); const mdApiUtil_1 = require("./mdApiUtil"); const Parser = require('fast-xml-parser'); /** * API that wraps Metadata API to retrieve source defined by given or generated package.xml. * * @param force * @constructor */ class MdRetrieveApi { constructor(org) { this.org = org; this.force = org.force; this.logger = logger.child('md-retrieve'); this._fsStatAsync = core_1.fs.stat; } // retrieve source from org async retrieve(options) { options.wait = +(options.wait === undefined || options.wait === null ? consts.DEFAULT_MDAPI_RETRIEVE_WAIT_MINUTES : options.wait); // set target org, org other than workspace defined org const orgApi = this.org; let retrievePromise = Promise.resolve(); // set the json flag on this for use by this._log this.isJsonOutput = options.json; this.retrieveTargetPath = this._resolvePath(options.retrievetargetdir); // emit pre retrieve event await core_1.Lifecycle.getInstance().emit('preretrieve', { packageXmlPath: options.unpackaged }); if (!options.packagenames && !options.jobid) { retrievePromise = MdRetrieveApi._getPackageJson(this, options); } return retrievePromise .then((unpackagedJson) => this._formatRetrieveOptions(options, unpackagedJson)) .then((retrieveOptions) => { // call mdapi to retrieve source this._log('Retrieving source...'); return options.jobid ? { id: options.jobid, deprecatedStatusRequest: 'true' } : this.force.mdapiRetrieve(orgApi, retrieveOptions); }) .then((result) => this._setStashVars(result, options)) .then((result) => { options.jobid = result.id; options['deprecatedStatusRequest'] = result.deprecatedStatusRequest; options['result'] = result; // remap property state to status in the result object options['result'].status = result.state; return this._reportStatus(options); }) .catch((err) => { if (err.message.toLowerCase().includes('polling time out')) { const waitTime = options.wait ? options.wait : consts.DEFAULT_MDAPI_RETRIEVE_WAIT_MINUTES; throw almError('mdapiCliWaitTimeExceededError', ['retrieve', waitTime]); } else { throw err; } }); } // Only log a message if not using json output format _log(message) { if (!this.isJsonOutput) { this.logger.log(message); } } // Some default options used by source pull and source retrieve. static getDefaultOptions() { return { autoUpdatePackage: true, rollbackOnError: true, runTest: false, unzip: true, disableLogging: true, json: true, }; } // generate json format of package.xml either from given // file or gen'd from workspace. json is sent w/ retrieve request. static _getPackageJson(mdApi, options) { let packageXmlPath; let promise = Promise.resolve(); if (options.unpackaged) { // fully qualify path to package.xml packageXmlPath = path.resolve(options.unpackaged); } else { // let create api default sourcedir to defaultArtifact dir promise = new ManifestCreateApi(mdApi.org) .execute({ outputdir: mdApi.retrieveTargetPath, sourcedir: options.sourcedir, }) .then((fileInfo) => { packageXmlPath = fileInfo.file; }); } return promise .then(() => core_1.fs.readFile(packageXmlPath, 'utf8')) .then((unpackagedXml) => { try { // convert to json return Parser.parse(unpackagedXml, { explicitArray: false }); } catch (err) { // wrap in SfdxError throw core_1.SfdxError.create('salesforce-alm', 'source', 'IllFormattedManifest', [`; ${err.message}`]); } }) .then((unpackagedJson) => { const packageData = unpackagedJson.Package; delete packageData.$; return packageData; }); } _reportStatus(options) { return new RetrieveReportApi(this.org).report(options); } _formatRetrieveOptions(options, unpackagedJson) { const retrieveOptions = {}; retrieveOptions.apiVersion = Number(options.apiversion || this.force.config.getApiVersion()); if (unpackagedJson) { retrieveOptions.unpackaged = unpackagedJson; } if (options.packagenames) { // ensure proper formatting and // convert packagenames to an array. retrieveOptions.packageNames = this._parsePackageNames(options.packagenames); } // convert possible undefined to false. retrieveOptions.singlePackage = !!options.singlepackage; return retrieveOptions; } _parsePackageNames(packagenames) { return packagenames .trim() .replace(/\s*,\s*/g, ',') .split(','); } async _setStashVars(result, options) { await StashApi.setValues({ jobid: result.id, retrievetargetdir: options.retrievetargetdir, targetusername: options.targetusername, }, StashApi.Commands.MDAPI_RETRIEVE); return result; } validate(context) { const options = context.flags; const validationPromises = []; const willCreateManifestFromArtifact = options.sourcedir || (!options.packagenames && !options.unpackaged); let insideProjectWorkspace = true; try { this.org.config.getAppConfig(); } catch (e) { insideProjectWorkspace = false; } // If we're outside of the workspace and a manifest will be created from the default artifact, we should throw an error. if (!insideProjectWorkspace && willCreateManifestFromArtifact) { return Promise.reject(almError('mdRetrieveCommandCliInvalidProjectError')); } try { mdApiUtil_1.MetadataTransportInfo.validateExclusiveFlag(options, 'sourcedir', 'unpackaged'); mdApiUtil_1.MetadataTransportInfo.validateExclusiveFlag(options, 'packagenames', 'sourcedir'); mdApiUtil_1.MetadataTransportInfo.validateExclusiveFlag(options, 'jobid', 'packagenames'); mdApiUtil_1.MetadataTransportInfo.validateExclusiveFlag(options, 'packagenames', 'unpackaged'); mdApiUtil_1.MetadataTransportInfo.validateExclusiveFlag(options, 'jobid', 'sourcedir'); mdApiUtil_1.MetadataTransportInfo.validateExclusiveFlag(options, 'jobid', 'unpackaged'); mdApiUtil_1.MetadataTransportInfo.validateExclusiveFlag(options, 'jobid', 'singlepackage'); } catch (err) { return Promise.reject(err); } const currentApiVersion = this.force.config.getApiVersion(); if (options.apiversion && (isNaN(+options.apiversion) || +options.apiversion < 0 || +options.apiversion > currentApiVersion)) { return Promise.reject(almError('mdRetrieveCommandCliInvalidApiVersionError', currentApiVersion)); } // Wait must be a number that is greater than zero or equal to -1. const validWaitValue = !isNaN(+options.wait) && (+options.wait === -1 || +options.wait >= 0); if (options.wait && !validWaitValue) { return Promise.reject(almError('mdapiCliInvalidWaitError')); } if (options.packagenames) { const packageNamesArray = this._parsePackageNames(options.packagenames); if (options.singlepackage && packageNamesArray.length > 1) { return Promise.reject(almError('mdRetrieveCommandCliTooManyPackagesError', packageNamesArray.join(','))); } } if (options.sourcedir) { validationPromises.push(this._validatePath(options.sourcedir, (data) => data.isDirectory(), () => Promise.resolve(), almError('InvalidArgumentDirectoryPath', ['sourcedir', options.sourcedir]))); } else if (options.unpackaged) { validationPromises.push(this._validatePath(options.unpackaged, (data) => data.isFile(), () => Promise.resolve(), almError('InvalidArgumentFilePath', ['unpackaged', options.unpackaged]))); } const retrieveTargetPath = this._resolvePath(options.retrievetargetdir); validationPromises.push(this._validatePath(retrieveTargetPath, (data) => data.isDirectory(), () => Promise.resolve(), almError('InvalidArgumentDirectoryPath', ['retrievetargetdir', retrieveTargetPath])).catch((err) => { // ignore PathDoesNotExist, it will create a directory if it doesn't already exist. if (err.name !== 'PathDoesNotExist') { return Promise.reject(err); } return Promise.resolve(); })); return Promise.all(validationPromises).then(() => options); } // Accepts: // pathToValidate: a file path to validate // validationFunc: function that is called with the result of a fs.stat(), should return true or false // successFunc: function that returns a promise. // error: an Error object that will be thrown if the validationFunc returns false. // Returns: // Successfull Validation: The result of a call to successFunc. // Failed Validation: A rejected promise with the specified error, or a PathDoesNotExist // error if the file read fails. _validatePath(pathToValidate, validationFunc, successFunc, error) { return this._fsStatAsync(pathToValidate) .then((data) => { if (validationFunc(data)) { return successFunc(); } else { return Promise.reject(error); } }) .catch((err) => { err = err.code === 'ENOENT' ? almError('PathDoesNotExist', pathToValidate) : err; return Promise.reject(err); }); } _resolvePath(...args) { return path.resolve.apply(this, args); } } exports.MdRetrieveApi = MdRetrieveApi; //# sourceMappingURL=mdapiRetrieveApi.js.map