UNPKG

salesforce-alm

Version:

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

410 lines (408 loc) 15.9 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 */ /* -------------------------------------------------------------------------------------------------------------------- * WARNING: This file has been deprecated and should now be considered locked against further changes. Its contents * have been partially or wholely superceded by functionality included in the @salesforce/core npm package, and exists * now to service prior uses in this repository only until they can be ported to use the new @salesforce/core library. * * If you need or want help deciding where to add new functionality or how to migrate to the new library, please * contact the CLI team at alm-cli@salesforce.com. * ----------------------------------------------------------------------------------------------------------------- */ // Node const path = require("path"); // Thirdparty const BBPromise = require("bluebird"); const optional = require("optional-js"); const _ = require("lodash"); const mkdirp = require("mkdirp"); // Local const core_1 = require("@salesforce/core"); const kit_1 = require("@salesforce/kit"); const core_2 = require("@salesforce/core"); const orgConfigAttributes = require("../org/orgConfigAttributes"); const MdapiDeployApi = require("../mdapi/mdapiDeployApi"); const Messages = require("../messages"); const configApi_1 = require("./configApi"); // import * as almError from './almError'; const configValidator = require("./configValidator"); const logger = require("./logApi"); const srcDevUtil = require("./srcDevUtil"); const defaultConnectedAppInfo = require('./defaultConnectedApp'); const messages = Messages(); const urls = require('../urls'); const fs = BBPromise.promisifyAll(require('fs')); const _buildNoOrgError = (org) => { let message = messages.getMessage('defaultOrgNotFound', org.type); if (!_.isNil(org.name)) { message = messages.getMessage('namedOrgNotFound', org.name); } const noConfigError = new Error(message); noConfigError.name = 'NoOrgFound'; if (org.type === core_1.Config.DEFAULT_USERNAME) { kit_1.set(noConfigError, 'action', messages.getMessage('defaultOrgNotFoundAction')); } else if (org.type === core_1.Config.DEFAULT_DEV_HUB_USERNAME) { kit_1.set(noConfigError, 'action', messages.getMessage('defaultOrgNotFoundDevHubAction')); } return noConfigError; }; /** * Represents a config json file in the state folder that consumers can interact with. * * TODO Extract out * TODO Make async. Has huge implications on source*.js files * TODO remove config with workspace.js in sfdx-core */ class StateFile { constructor(config, filePath, contents = {}) { this.path = path.join(config.getProjectPath(), srcDevUtil.getWorkspaceStateFolderName(), filePath); this.backupPath = `${this.path}.bak`; this.contents = contents; } _read(filePath) { // TODO use readJSON when async try { return JSON.parse(fs.readFileSync(filePath)); } catch (e) { if (e.code === 'ENOENT') { return {}; } else { throw e; } } } _write(filePath, contents) { mkdirp.sync(path.dirname(filePath)); fs.writeFileSync(filePath, JSON.stringify(contents, null, 4)); } _exist(filePath) { try { return fs.statSync(filePath); } catch (err) { return false; } } _delete(filePath) { if (this._exist(filePath)) { return fs.unlinkSync(filePath); } return false; } read() { this.contents = this._read(this.path); return this.contents; } write(newContents) { if (!_.isNil(newContents)) { this.contents = newContents; } this._write(this.path, this.contents); return this.contents; } exist() { return this._exist(this.path); } delete() { return this._delete(this.path); } backup() { if (this.exist()) { this._write(this.backupPath, this.read()); } } revert() { if (this._exist(this.backupPath)) { this.write(this._read(this.backupPath)); this._delete(this.backupPath); } return this.contents; } } /** * @deprecated The functionality is moving to sfdx-core */ class Org { /** * Construct a new org. No configuration is initialized at this point. To * get any auth data, getConfig must first be called which will try to get * the default org of the given type unless the setName method is called. * Any calls to org.force will call getConfig. * * @param {Force} force The force api * @param {string} type The type of org for the CLI. This is used to store * and find defaults for the project. * @constructor */ constructor(force, type = core_1.Config.DEFAULT_USERNAME) { // eslint-disable-next-line const Force = require('./force'); this.force = optional.ofNullable(force).orElse(new Force(new configApi_1.Config())); this.config = this.force.getConfig(); this.logger = logger.child('Org'); this.type = type; this.mdDeploy = new MdapiDeployApi(this); } retrieveMaxApiVersion() { // getting the max api version does not require auth. So if you think adding a call to refreshAuth here is the correct // thing to do. it's not! return this.force.getApiVersions(this).then((versions) => _.maxBy(versions, (_ver) => _ver.version)); } /** * Gets the name of this scratch org. */ getName() { return this.name; } async resolveDefaultName() { // If the name is set, we don't want to resolve the default if (this.getName()) { return; } const sfdxConfig = await this.resolvedAggregator(); const name = sfdxConfig.getPropertyValue(this.type); const orgName = (await core_1.Aliases.create({})).get(name); this.setName(orgName !== null && orgName !== void 0 ? orgName : name); } /** * Sets the name of this scratch org. After setting the name any call to getConfig will result in the org associated * with $HOME/.sfdx/[name].json being returned. * * @param name - the name of the org. */ setName(name) { this.name = name; this.logger.setConfig('username', name); this.force.logger.setConfig('username', name); } async resolvedAggregator() { if (!this.aggregator) { this.aggregator = core_1.ConfigAggregator.create(); } return this.aggregator; } async initializeConfig() { let config; try { config = await core_1.Config.create({ isGlobal: false }); } catch (err) { if (err.name === 'InvalidProjectWorkspace') { config = await core_1.Config.create({ isGlobal: true }); } else { throw err; } } return config; } getDataPath(filename) { const username = this.getName(); if (!username) { throw _buildNoOrgError(this); } // Create a path like <project>/.sfdx/orgs/<username>/<filename> return path.join(...['orgs', username, filename].filter((e) => !!e)); } /** * Clean all data files in the org's data path, then remove the data directory. * Usually <workspace>/.sfdx/orgs/<username> */ cleanData(orgDataPath) { let dataPath; try { dataPath = path.join(this.config.getProjectPath(), srcDevUtil.getWorkspaceStateFolderName(), orgDataPath || this.getDataPath()); } catch (err) { if (err.name === 'InvalidProjectWorkspace') { // If we aren't in a project dir, we can't clean up data files. // If the user deletes this org outside of the workspace they used it in, // data files will be left over. return; } throw err; } const removeDir = (dirPath) => { let stats; try { stats = fs .readdirSync(dirPath) .map((file) => path.join(dirPath, file)) .map((filePath) => ({ filePath, stat: fs.statSync(filePath) })); stats.filter(({ stat }) => stat.isDirectory()).forEach(({ filePath }) => removeDir(filePath)); stats.filter(({ stat }) => stat.isFile()).forEach(({ filePath }) => fs.unlinkSync(filePath)); fs.rmdirSync(dirPath); } catch (err) { this.logger.warn(`failed to read directory ${dirPath}`); } }; removeDir(dataPath); } /** * Get the full path to the file storing the maximum revision value from the last valid pull from workspace scratch org * * @param wsPath - The root path of the workspace * @returns {*} */ getMaxRevision() { return new StateFile(this.config, this.getDataPath('maxRevision.json')); } /** * Get the full path to the file storing the workspace source path information * * @param wsPath - The root path of the workspace * @returns {*} */ getSourcePathInfos() { return new StateFile(this.config, this.getDataPath('sourcePathInfos.json')); } /** * Returns a promise to retrieve the ScratchOrg configuration for this workspace. * * @returns {BBPromise} */ getConfig() { if (this.authConfig) { return BBPromise.resolve(this.authConfig); } return this.resolveDefaultName() .then(() => this.resolvedAggregator()) .then((sfdxConfig) => { const username = this.getName(); // The username of the org can be set by the username config var, env var, or command line. // If the username is not set, getName will resolve to the default username for the workspace. // If the username is an access token, use that instead of getting the username auth file. const accessTokenMatch = _.isString(username) && username.match(/^(00D\w{12,15})![\.\w]*$/); if (accessTokenMatch) { let instanceUrl; const orgId = accessTokenMatch[1]; this.usingAccessToken = true; // If it is an env var, use it instead of the local workspace sfdcLoginUrl property, // otherwise try to use the local sfdx-project property instead. if (sfdxConfig.getInfo('instanceUrl').isEnvVar()) { instanceUrl = sfdxConfig.getPropertyValue('instanceUrl'); } else { instanceUrl = sfdxConfig.getPropertyValue('instanceUrl') || urls.production; } // If the username isn't an email, is it as a accessToken return { accessToken: username, instanceUrl, orgId, }; } else { return srcDevUtil .getGlobalConfig(`${username}.json`) .then((config) => configValidator.getCleanObject(config, orgConfigAttributes, false)) .then((config) => { if (_.isNil(config.clientId)) { config.clientId = defaultConnectedAppInfo.legacyClientId; config.clientSecret = defaultConnectedAppInfo.legacyClientSecret; } return config; }) .catch((error) => { let returnError = error; if (error.code === 'ENOENT') { returnError = _buildNoOrgError(this); } return BBPromise.reject(returnError); }); } }) .then((config) => { this.authConfig = config; return config; }); } getFileName() { return `${this.name}.json`; } /** * Returns a promise to save a valid workspace scratch org configuration to disk. * * @param configObject - The object to save. If the object isn't valid an error will be thrown. * { orgId:, redirectUri:, accessToken:, refreshToken:, instanceUrl:, clientId: } * @param saveAsDefault {boolean} - whether to save this org as the default for this workspace. * @returns {BBPromise.<Object>} Not the access tokens will be encrypted. Call get config to get decrypted access tokens. */ saveConfig(configObject, saveAsDefault) { if (this.usingAccessToken) { return BBPromise.resolve(configObject); } this.name = configObject.username; let savedData; // For security reasons we don't want to arbitrarily write the configObject to disk. return configValidator .getCleanObject(configObject, orgConfigAttributes, true) .then((dataToSave) => { savedData = dataToSave; return srcDevUtil.saveGlobalConfig(this.getFileName(), savedData); }) .then(async () => { core_2.AuthInfo.clearCache(configObject.username); this.logger.info(`Saved org configuration: ${this.getFileName()}`); if (saveAsDefault) { const config = await this.initializeConfig(); config.set(this.type, this.alias || this.name); await config.write(); } this.authConfig = configObject; return BBPromise.resolve(savedData); }); } /** * @deprecated See Org.ts in sfdx-core */ static async create(username, defaultType) { // If orgType is undefined, Org will use the right default. const org = new Org(undefined, defaultType); if (_.isString(username) && !_.isEmpty(username)) { // Check if the user is an alias const alias = await core_1.Aliases.create({}); const actualUsername = alias.get(username); const verbose = srcDevUtil.isVerbose(); if (_.isString(actualUsername) && !_.isEmpty(actualUsername)) { if (verbose) { logger.log(`Using resolved username ${actualUsername} from alias ${username}${logger.getEOL()}`); } org.alias = username; org.setName(actualUsername); } else { if (verbose) { logger.log(`Using specified username ${username}${logger.getEOL()}`); } org.setName(username); } } // If the username isn't set or passed in, the default username // will be resolved on config. await org.getConfig(); return org; } } /** * Org types that can be set as a default for local and global configs. * All commands target USERNAME, except commands that specify a different * default, like org:create specifing DEVHUB has a default. */ Org.Defaults = { DEVHUB: core_1.Config.DEFAULT_DEV_HUB_USERNAME, USERNAME: core_1.Config.DEFAULT_USERNAME, list() { return [core_1.Config.DEFAULT_DEV_HUB_USERNAME, core_1.Config.DEFAULT_USERNAME]; }, }; module.exports = Org; //# sourceMappingURL=scratchOrgApi.js.map