UNPKG

salesforce-alm

Version:

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

842 lines (840 loc) 49.7 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 */ // Node const path = require("path"); const util = require("util"); const os = require("os"); const crypto = require("crypto"); const BBPromise = require("bluebird"); const _ = require("lodash"); // npm const core_1 = require("@salesforce/core"); // Local // New messages (move to this) const core_2 = require("@salesforce/core"); const source_deploy_retrieve_1 = require("@salesforce/source-deploy-retrieve"); BBPromise.promisifyAll(require('fs-extra')); BBPromise.promisifyAll(require('xml2js')); core_2.Messages.importMessagesDirectory(__dirname); const cli_ux_1 = require("cli-ux"); const MessagesLocal = require("../messages"); const SettingsGenerator = require("../org/scratchOrgSettingsGenerator"); const ProfileApi = require("../package/profileApi"); const SourceConvertCommand = require("../source/sourceConvertCommand"); const srcDevUtil = require("../core/srcDevUtil"); const logApi = require("../core/logApi"); const almError = require("../core/almError"); const pkgUtils = require("./packageUtils"); const PackageVersionCreateRequestApi = require("./packageVersionCreateRequestApi"); const fs = BBPromise.promisifyAll(require('fs-extra')); const xml2js = BBPromise.promisifyAll(require('xml2js')); const consts = require("../core/constants"); const messages = MessagesLocal(); const DESCRIPTOR_FILE = 'package2-descriptor.json'; let logger; const POLL_INTERVAL_WITHOUT_VALIDATION_SECONDS = 5; class PackageVersionCreateCommand { constructor() { this.pollInterval = pkgUtils.POLL_INTERVAL_SECONDS; this.maxRetries = 0; logger = logApi.child('package:version:create'); } // convert source to mdapi format and copy to tmp dir packaging up _generateMDFolderForArtifact(options) { const convertCmd = new SourceConvertCommand(); const context = { flags: { rootdir: options.sourcedir, outputdir: options.deploydir, }, }; return BBPromise.resolve() .then(() => convertCmd.validate(context)) .then((fixedcontext) => convertCmd.execute(fixedcontext)); } _validateDependencyValues(dependency) { // If valid 04t package, just return it to be used straight away. if (dependency.subscriberPackageVersionId) { pkgUtils.validateId(pkgUtils.BY_LABEL.SUBSCRIBER_PACKAGE_VERSION_ID, dependency.subscriberPackageVersionId); return BBPromise.resolve(); } if (dependency.packageId && dependency.package) { throw new Error(messages.getMessage('errorPackageAndPackageIdCollision', [], 'package_version_create')); } const packageIdFromAlias = pkgUtils.getPackageIdFromAlias(dependency.packageId || dependency.package, this.force); // If valid 04t package, just return it to be used straight away. if (pkgUtils.validateIdNoThrow(pkgUtils.BY_LABEL.SUBSCRIBER_PACKAGE_VERSION_ID, packageIdFromAlias)) { dependency.subscriberPackageVersionId = packageIdFromAlias; return BBPromise.resolve(); } if (!packageIdFromAlias || !dependency.versionNumber) { throw new Error(messages.getMessage('errorDependencyPair', [JSON.stringify(dependency)], 'package_version_create')); } // Just override dependency.packageId value to the resolved alias. dependency.packageId = packageIdFromAlias; pkgUtils.validateId(pkgUtils.BY_LABEL.PACKAGE_ID, dependency.packageId); pkgUtils.validateVersionNumber(dependency.versionNumber, pkgUtils.LATEST_BUILD_NUMBER_TOKEN, pkgUtils.RELEASED_BUILD_NUMBER_TOKEN); // Validate that the Package2 id exists on the server const query = `SELECT Id FROM Package2 WHERE Id = '${dependency.packageId}'`; return this.force.toolingQuery(this.org, query).then((pkgQueryResult) => { const subRecords = pkgQueryResult.records; if (!subRecords || subRecords.length !== 1) { throw new Error(messages.getMessage('errorNoIdInHub', [dependency.packageId], 'package_version_create')); } }); } /** * A dependency in the workspace config file may be specified using either a subscriber package version id (04t) * or a package Id (0Ho) + a version number. Additionally, a build number may be the actual build number, or a * keyword: LATEST or RELEASED (meaning the latest or released build number for a given major.minor.patch). * * This method resolves a package Id + version number to a subscriber package version id (04t) * and adds it as a SubscriberPackageVersionId parameter in the dependency object. */ _retrieveSubscriberPackageVersionId(dependency, branchFromFlagOrDef) { return BBPromise.resolve().then(() => this._validateDependencyValues(dependency).then(() => { if (dependency.subscriberPackageVersionId) { delete dependency.package; // if an 04t id is specified just use it. return dependency; } const versionNumber = dependency.versionNumber.split(pkgUtils.VERSION_NUMBER_SEP); const buildNumber = versionNumber[3]; // use the dependency.branch if present otherwise use the branch of the version being created const branch = dependency.branch || dependency.branch === '' ? dependency.branch : branchFromFlagOrDef; const branchString = _.isNil(branch) || branch === '' ? 'null' : `'${branch}'`; // resolve a build number keyword to an actual number, if needed return this._resolveBuildNumber(versionNumber, dependency.packageId, branch).then((queryResult) => { const records = queryResult.records; if (!records || records.length === 0 || records[0].expr0 == null) { if (buildNumber === pkgUtils.RELEASED_BUILD_NUMBER_TOKEN) { throw new Error(`No released version was found in Dev Hub for package id ${dependency.packageId} and version number ${versionNumber.join(pkgUtils.VERSION_NUMBER_SEP)}`); } else { throw new Error(`No version number was found in Dev Hub for package id ${dependency.packageId} and branch ${branchString} and version number ${versionNumber.join(pkgUtils.VERSION_NUMBER_SEP)}`); } } // now that we have a full build number, query for the associated 04t. // because the build number may not be unique across versions, add in conditionals for // the branch or the RELEASED token (if used) const resolvedBuildNumber = records[0].expr0; const branchOrReleasedCondition = buildNumber === pkgUtils.RELEASED_BUILD_NUMBER_TOKEN ? 'AND IsReleased = true' : `AND Branch = ${branchString}`; const query = `SELECT SubscriberPackageVersionId FROM Package2Version WHERE Package2Id = '${dependency.packageId}' AND MajorVersion = ${versionNumber[0]} AND MinorVersion = ${versionNumber[1]} AND PatchVersion = ${versionNumber[2]} AND BuildNumber = ${resolvedBuildNumber} ${branchOrReleasedCondition}`; return this.force.toolingQuery(this.org, query).then((pkgVerQueryResult) => { const subRecords = pkgVerQueryResult.records; if (!subRecords || subRecords.length !== 1) { throw new Error(`No version number was found in Dev Hub for package id ${dependency.packageId} and branch ${branchString} and version number ${versionNumber.join(pkgUtils.VERSION_NUMBER_SEP)} that resolved to build number ${resolvedBuildNumber}`); } dependency.subscriberPackageVersionId = pkgVerQueryResult.records[0].SubscriberPackageVersionId; // warn user of the resolved build number when LATEST and RELEASED keywords are used if (Number.isNaN(parseInt(buildNumber))) { versionNumber[3] = resolvedBuildNumber; if (buildNumber === pkgUtils.LATEST_BUILD_NUMBER_TOKEN) { logger.log(messages.getMessage('buildNumberResolvedForLatest', [ dependency.package, versionNumber.join(pkgUtils.VERSION_NUMBER_SEP), branchString, dependency.subscriberPackageVersionId, ], 'package_version_create')); } else if (buildNumber === pkgUtils.RELEASED_BUILD_NUMBER_TOKEN) { logger.log(messages.getMessage('buildNumberResolvedForReleased', [ dependency.package, versionNumber.join(pkgUtils.VERSION_NUMBER_SEP), dependency.subscriberPackageVersionId, ], 'package_version_create')); } } delete dependency.packageId; delete dependency.package; delete dependency.versionNumber; delete dependency.branch; return dependency; }); }); })); } _resolveBuildNumber(versionNumber, packageId, branch) { return BBPromise.resolve().then(() => { if (!Number.isNaN(parseInt(versionNumber[3]))) { // The build number is already specified so just return it using the tooling query result obj structure return { records: [{ expr0: versionNumber[3] }] }; } // query for the LATEST or RELEASED build number let branchCondition = ''; let releasedCondition = ''; if (versionNumber[3] === pkgUtils.LATEST_BUILD_NUMBER_TOKEN) { // respect the branch when querying for LATEST const branchString = _.isNil(branch) || branch === '' ? 'null' : `'${branch}'`; branchCondition = `AND Branch = ${branchString}`; } else if (versionNumber[3] === pkgUtils.RELEASED_BUILD_NUMBER_TOKEN) { releasedCondition = 'AND IsReleased = true'; } const query = `SELECT MAX(BuildNumber) FROM Package2Version WHERE Package2Id = '${packageId}' AND MajorVersion = ${versionNumber[0]} AND MinorVersion = ${versionNumber[1]} AND PatchVersion = ${versionNumber[2]} ${branchCondition} ${releasedCondition}`; return this.force.toolingQuery(this.org, query); }); } _createRequestObject(packageId, context, preserveFiles, packageVersTmpRoot, packageVersBlobZipFile) { const zipFileBase64 = fs.readFileSync(packageVersBlobZipFile).toString('base64'); const requestObject = { Package2Id: packageId, VersionInfo: zipFileBase64, Tag: context.flags.tag, Branch: context.flags.branch, InstallKey: context.flags.installationkey, Instance: context.flags.buildinstance, SourceOrg: context.flags.sourceorg, CalculateCodeCoverage: context.flags.codecoverage, SkipValidation: context.flags.skipvalidation, }; if (preserveFiles) { logger.log(messages.getMessage('tempFileLocation', [packageVersTmpRoot], 'package_version_create')); return requestObject; } else { return fs.removeAsync(packageVersTmpRoot).then(() => requestObject); } } _getPackageDescriptorJsonFromPackageId(packageId, flags) { const artDir = flags.path; const packageDescriptorJson = this.packageDirs.find((packageDir) => { const packageDirPackageId = pkgUtils.getPackageIdFromAlias(packageDir.package, this.force); return !_.isNil(packageDirPackageId) && packageDirPackageId === packageId ? packageDir : null; }); if (!packageDescriptorJson) { throw new Error(`${consts.WORKSPACE_CONFIG_FILENAME} does not contain a packaging directory for ${artDir}`); } return packageDescriptorJson; } /** * Convert the list of command line options to a JSON object that can be used to create an Package2VersionCreateRequest entity. * * @param context * @param packageId * @returns {{Package2Id: (*|p|boolean), Package2VersionMetadata: *, Tag: *, Branch: number}} * @private */ _createPackageVersionCreateRequestFromOptions(context, packageId) { const artDir = context.flags.path; const preserveFiles = !util.isNullOrUndefined(context.flags.preserve || process.env.SFDX_PACKAGE2_VERSION_CREATE_PRESERVE); const uniqueHash = crypto.createHash('sha1').update(`${Date.now()}${Math.random()}`).digest('hex'); const packageVersTmpRoot = path.join(os.tmpdir(), `${packageId}-${uniqueHash}`); const packageVersMetadataFolder = path.join(packageVersTmpRoot, 'md-files'); const unpackagedMetadataFolder = path.join(packageVersTmpRoot, 'unpackaged-md-files'); const packageVersProfileFolder = path.join(packageVersMetadataFolder, 'profiles'); const packageVersBlobDirectory = path.join(packageVersTmpRoot, 'package-version-info'); const metadataZipFile = path.join(packageVersBlobDirectory, 'package.zip'); const unpackagedMetadataZipFile = path.join(packageVersBlobDirectory, 'unpackaged-metadata-package.zip'); const settingsZipFile = path.join(packageVersBlobDirectory, 'settings.zip'); const packageVersBlobZipFile = path.join(packageVersTmpRoot, 'package-version-info.zip'); const sourceBaseDir = path.join(context.org.force.getConfig().getProjectPath(), artDir); const mdOptions = { deploydir: packageVersMetadataFolder, sourcedir: sourceBaseDir, }; // Stores any additional client side info that might be needed later on in the process const clientSideInfo = new Map(); const settingsGenerator = new SettingsGenerator(); let hasUnpackagedMetadata = false; // Copy all of the metadata from the workspace to a tmp folder return (this._generateMDFolderForArtifact(mdOptions) .then(async () => { const packageDescriptorJson = this._getPackageDescriptorJsonFromPackageId(packageId, context.flags); if (Object.prototype.hasOwnProperty.call(packageDescriptorJson, 'package')) { delete packageDescriptorJson.package; packageDescriptorJson.id = packageId; } const definitionFile = context.flags.definitionfile ? context.flags.definitionfile : packageDescriptorJson['definitionFile']; if (definitionFile) { // package2-descriptor.json sent to the server should contain only the features, snapshot & orgPreferences // defined in the definition file. delete packageDescriptorJson.features; delete packageDescriptorJson.orgPreferences; delete packageDescriptorJson.definitionFile; delete packageDescriptorJson.snapshot; const definitionFilePayload = await fs.readFileAsync(definitionFile, 'utf8'); const definitionFileJson = JSON.parse(definitionFilePayload); const pkgProperties = [ 'country', 'edition', 'language', 'features', 'orgPreferences', 'snapshot', 'release', 'sourceOrg', ]; // Load any settings from the definition await settingsGenerator.extract(definitionFileJson); if (settingsGenerator.hasSettings() && definitionFileJson['orgPreferences']) { // this is not allowed, exit with an error return BBPromise.reject(almError('signupDuplicateSettingsSpecified')); } pkgProperties.forEach((prop) => { const propValue = definitionFileJson[prop]; if (propValue) { packageDescriptorJson[prop] = propValue; } }); } return [packageDescriptorJson]; }) .spread((packageDescriptorJson) => { let unpackagedPromise = null; // Add the Unpackaged Metadata, if any, to the output directory, only when code coverage is specified if (packageDescriptorJson.unpackagedMetadata && packageDescriptorJson.unpackagedMetadata.path && context.flags.codecoverage) { hasUnpackagedMetadata = true; const unpackagedPath = path.join(process.cwd(), packageDescriptorJson.unpackagedMetadata.path); try { fs.statSync(unpackagedPath); } catch (err) { throw new Error(`Unpackaged metadata directory '${packageDescriptorJson.unpackagedMetadata.path}' was specified but does not exist`); } srcDevUtil.ensureDirectoryExistsSync(unpackagedMetadataFolder); unpackagedPromise = this._generateMDFolderForArtifact({ deploydir: unpackagedMetadataFolder, sourcedir: unpackagedPath, }); // Set which package is the "unpackaged" package clientSideInfo.set('UnpackagedMetadataPath', packageDescriptorJson.unpackagedMetadata.path); } return [packageDescriptorJson, unpackagedPromise]; }) .spread((packageDescriptorJson) => { // Process permissionSet and permissionSetLicenses that should be enabled when running Apex tests // This only applies if code coverage is enabled if (context.flags.codecoverage) { // Assuming no permission sets are named 0, 0n, null, undefined, false, NaN, and the empty string if (packageDescriptorJson.apexTestAccess && packageDescriptorJson.apexTestAccess.permissionSets) { let permSets = packageDescriptorJson.apexTestAccess.permissionSets; if (!Array.isArray(permSets)) { permSets = permSets.split(','); } packageDescriptorJson.permissionSetNames = permSets.map((s) => s.trim()); } if (packageDescriptorJson.apexTestAccess && packageDescriptorJson.apexTestAccess.permissionSetLicenses) { let permissionSetLicenses = packageDescriptorJson.apexTestAccess.permissionSetLicenses; if (!Array.isArray(permissionSetLicenses)) { permissionSetLicenses = permissionSetLicenses.split(','); } packageDescriptorJson.permissionSetLicenseDeveloperNames = permissionSetLicenses.map((s) => s.trim()); } } delete packageDescriptorJson.apexTestAccess; return [packageDescriptorJson]; }) .spread((packageDescriptorJson) => { // All dependencies for the packaging dir should be resolved to an 04t id to be passed to the server. // (see _retrieveSubscriberPackageVersionId for details) const dependencies = packageDescriptorJson.dependencies; // branch can be set via flag or descriptor; flag takes precedence context.flags.branch = context.flags.branch ? context.flags.branch : packageDescriptorJson.branch; const operations = _.isNil(dependencies) ? [] : dependencies.map((dependency) => this._retrieveSubscriberPackageVersionId(dependency, context.flags.branch)); return [ BBPromise.all(operations), pkgUtils.getAncestorId(packageDescriptorJson, this.force, this.org), packageDescriptorJson, ]; }) // eslint-disable-next-line @typescript-eslint/require-await .spread(async (resultValues, ancestorId, packageDescriptorJson) => { // If dependencies exist, the resultValues array will contain the dependencies populated with a resolved // subscriber pkg version id. if (resultValues.length > 0) { packageDescriptorJson.dependencies = resultValues; } this._cleanPackageDescriptorJson(packageDescriptorJson); this._setPackageDescriptorJsonValues(packageDescriptorJson, context); srcDevUtil.ensureDirectoryExistsSync(packageVersTmpRoot); srcDevUtil.ensureDirectoryExistsSync(packageVersBlobDirectory); if (Object.prototype.hasOwnProperty.call(packageDescriptorJson, 'ancestorVersion')) { delete packageDescriptorJson.ancestorVersion; } packageDescriptorJson.ancestorId = ancestorId; return fs.writeJSONAsync(path.join(packageVersBlobDirectory, DESCRIPTOR_FILE), packageDescriptorJson); }) // As part of the source convert process, the package.xml has been written into the tmp metadata directory. // The package.xml may need to be manipulated due to processing profiles in the workspace or additional // metadata exclusions. If necessary, read the existing package.xml and then re-write it. .then(() => fs.readFileAsync(path.join(packageVersMetadataFolder, 'package.xml'), 'utf8')) .then((currentPackageXml) => // convert to json xml2js.parseStringAsync(currentPackageXml)) .then((packageJson) => { srcDevUtil.ensureDirectoryExistsSync(packageVersMetadataFolder); srcDevUtil.ensureDirectoryExistsSync(packageVersProfileFolder); // Apply any necessary exclusions to typesArr. let typesArr = packageJson.Package.types; // if we're using unpackaged metadata, don't package the profiles located there if (hasUnpackagedMetadata) { typesArr = this.profileApi.filterAndGenerateProfilesForManifest(typesArr, [ clientSideInfo.get('UnpackagedMetadataPath'), ]); } else { typesArr = this.profileApi.filterAndGenerateProfilesForManifest(typesArr); } // Next generate profiles and retrieve any profiles that were excluded because they had no matching nodes. const excludedProfiles = this.profileApi.generateProfiles(packageVersProfileFolder, { Package: { types: typesArr }, }, [clientSideInfo.get('UnpackagedMetadataPath')]); if (excludedProfiles.length > 0) { const profileIdx = typesArr.findIndex((e) => e.name[0] === 'Profile'); typesArr[profileIdx].members = typesArr[profileIdx].members.filter((e) => excludedProfiles.indexOf(e) === -1); } packageJson.Package.types = typesArr; // Re-write the package.xml in case profiles have been added or removed const xmlBuilder = new xml2js.Builder({ xmldec: { version: '1.0', encoding: 'UTF-8' }, }); const xml = xmlBuilder.buildObject(packageJson); // Log information about the profiles being packaged up const profiles = this.profileApi.getProfileInformation(); profiles.forEach((profile) => { if (logger.shouldLog(core_2.LoggerLevel.DEBUG)) { logger.log(profile.logDebug()); } else if (logger.shouldLog(core_2.LoggerLevel.INFO)) { logger.log(profile.logInfo()); } }); return fs.writeFileAsync(path.join(packageVersMetadataFolder, 'package.xml'), xml); }) .then(() => // Zip the packageVersMetadataFolder folder and put the zip in {packageVersBlobDirectory}/package.zip srcDevUtil.zipDir(packageVersMetadataFolder, metadataZipFile)) .then(() => { if (hasUnpackagedMetadata) { // Zip the unpackagedMetadataFolder folder and put the zip in {packageVersBlobDirectory}/{unpackagedMetadataZipFile} return srcDevUtil.zipDir(unpackagedMetadataFolder, unpackagedMetadataZipFile); } }) .then(() => { // Zip up the expanded settings (if present) if (settingsGenerator.hasSettings()) { return settingsGenerator .createDeployDir() .then((settingsRoot) => { // The SettingsGenerator now generates md files in source format, not mdapi format, // so we need to convert to mdapi format here. const compSet = source_deploy_retrieve_1.ComponentSet.fromSource(settingsRoot); const mdConverter = new source_deploy_retrieve_1.MetadataConverter(); return mdConverter.convert(compSet, 'metadata', { type: 'directory', outputDirectory: path.join(settingsRoot, 'pkgMdSettings'), genUniqueDir: false, }); }) .then((convertResult) => srcDevUtil.zipDir(convertResult.packagePath, settingsZipFile)); } return BBPromise.resolve(); }) // Zip the Version Info and package.zip files into another zip .then(() => srcDevUtil.zipDir(packageVersBlobDirectory, packageVersBlobZipFile)) .then(() => this._createRequestObject(packageId, context, preserveFiles, packageVersTmpRoot, packageVersBlobZipFile))); } _getPackagePropertyFromPackage(packageDirs, packageValue, context) { let foundByPackage = packageDirs.find((x) => x['package'] === packageValue); let foundById = packageDirs.find((x) => x['id'] === packageValue); if (foundByPackage && foundById) { throw new Error(messages.getMessage('errorPackageAndIdCollision', [], 'package_version_create')); } // didn't find anything? let's see if we can reverse look up if (!foundByPackage && !foundById) { // is it an alias? const pkgId = pkgUtils.getPackageIdFromAlias(packageValue, this.force); if (pkgId === packageValue) { // not an alias, or not a valid one, try to reverse lookup an alias in case this is an id const aliases = pkgUtils.getPackageAliasesFromId(packageValue, this.force); // if we found an alias, try to look that up in the config. foundByPackage = aliases.some((alias) => packageDirs.find((x) => x['package'] === alias)); } else { // it is an alias; try to lookup it's id in the config foundByPackage = packageDirs.find((x) => x['package'] === pkgId); foundById = packageDirs.find((x) => x['id'] === pkgId); if (!foundByPackage && !foundById) { // check if any configs use a different alias to that same id const aliases = pkgUtils.getPackageAliasesFromId(pkgId, this.force); foundByPackage = aliases.some((alias) => { const pd = packageDirs.find((x) => x['package'] === alias); if (pd) { // if so, set context.flags.package to be this alias instead of the alternate context.flags.package = alias; } return pd; }); } } // if we still didn't find anything, throw the error if (!foundByPackage && !foundById) { throw new Error(messages.getMessage('errorMissingPackage', [pkgId], 'package_version_create')); } } return foundByPackage ? 'package' : 'id'; } _getPackageValuePropertyFromDirectory(context, directoryFlag) { const packageValue = this._getConfigPackageDirectoriesValue(context, this.packageDirs, 'package', 'path', context.flags.path, directoryFlag); const packageIdValue = this._getConfigPackageDirectoriesValue(context, this.packageDirs, 'id', 'path', context.flags.path, directoryFlag); let packagePropVal = {}; if (!packageValue && !packageIdValue) { throw new Error(messages.getMessage('errorMissingPackage', [], 'package_version_create')); } else if (packageValue && packageIdValue) { throw new Error(messages.getMessage('errorPackageAndIdCollision', [], 'package_version_create')); } else if (packageValue) { packagePropVal = { packageProperty: 'package', packageValue, }; } else { packagePropVal = { packageProperty: 'id', packageValue: packageIdValue, }; } return packagePropVal; } /** * Returns the property value that corresponds to the propertyToLookup. This value found for a particular * package directory element that matches the knownProperty and knownValue. In other words, we locate a package * directory element whose knownProperty matches the knownValue, then we grab the value for the propertyToLookup * and return it. * * @param context * @param packageDirs The list of all the package directories from the sfdx-project.json * @param propertyToLookup The property ID whose value we want to find * @param knownProperty The JSON property in the packageDirectories that is already known * @param knownValue The value that corresponds to the knownProperty in the packageDirectories JSON * @param knownFlag The flag details e.g. short/long name, etc. Only used for the error message */ _getConfigPackageDirectoriesValue(context, packageDirs, propertyToLookup, knownProperty, knownValue, knownFlag) { let value; let packageDir = packageDirs.find((x) => x[knownProperty] === knownValue); if (!packageDir && knownFlag.name === 'path' && knownValue.endsWith(path.sep)) { // if this is the directory flag, try removing the trailing slash added by CLI auto-complete const dirWithoutTrailingSlash = knownValue.slice(0, -1); packageDir = packageDirs.find((x) => x[knownProperty] === dirWithoutTrailingSlash); if (packageDir) { context.flags.path = dirWithoutTrailingSlash; } } // didn't find it with the package property, try a reverse lookup with alias and id if (!packageDir && knownProperty === 'package') { const pkgId = pkgUtils.getPackageIdFromAlias(knownValue, this.force); if (pkgId !== knownValue) { packageDir = packageDirs.find((x) => x[knownProperty] === pkgId); } else { const aliases = pkgUtils.getPackageAliasesFromId(knownValue, this.force); aliases.some((alias) => { packageDir = packageDirs.find((x) => x[knownProperty] === alias); return packageDir; }); } } if (packageDir) { value = packageDir[propertyToLookup]; } else { throw new Error(messages.getMessage('errorNoMatchingPackageDirectory', [`--${knownFlag.name} (-${knownFlag.char})`, knownValue, knownProperty], 'package_version_create')); } return value; } execute(context) { return this._execute(context).catch((err) => { // TODO // until package2 is GA, wrap perm-based errors w/ 'contact sfdc' action (REMOVE once package2 is GA'd) err = pkgUtils.massageErrorMessage(err); throw pkgUtils.applyErrorAction(err); }); } // eslint-disable-next-line @typescript-eslint/require-await async _execute(context) { this.org = context.org; this.force = this.org.force; this.packageVersionCreateRequestApi = new PackageVersionCreateRequestApi(this.force, this.org); logger.setLevel(context.flags.loglevel); if (!context.flags.json && context.flags.skipvalidation === true) { cli_ux_1.default.warn(messages.getMessage('skipValidationWarning', [], 'package_version_create')); } if (context.flags.wait) { if (context.flags.skipvalidation === true) { this.pollInterval = POLL_INTERVAL_WITHOUT_VALIDATION_SECONDS; } this.maxRetries = (60 / this.pollInterval) * context.flags.wait; } // This command requires either the ID flag or path flag. The // other needed value can be looked up from sfdx-project.json. As // this concept is not supported by the framework, manually check if // we have at least one of the flags const pathFlag = context.command.flags.find((x) => x.name === 'path'); const packageFlag = context.command.flags.find((x) => x.name === 'package'); if (!context.flags.package && !context.flags.path) { const errorString = messages.getMessage('errorMissingFlags', [`--${packageFlag.name} (-${packageFlag.char})`, `--${pathFlag.name} (-${pathFlag.char})`], 'package_version_create'); const error = new Error(errorString); error['name'] = 'requiredFlagMissing'; return BBPromise.reject(error); } // This command does not allow --codecoverage and --skipvalidation at the same time if (context.flags.skipvalidation && context.flags.codecoverage) { const codeCovFlag = context.command.flags.find((x) => x.name === 'codecoverage'); const skipValFlag = context.command.flags.find((x) => x.name === 'skipvalidation'); const errorString = messages.getMessage('errorCannotSupplyCodeCoverageAndSkipValidation', [ `--${codeCovFlag.name} (-${codeCovFlag.char})`, `--${skipValFlag.name}`, `--${codeCovFlag.name} (-${codeCovFlag.char})`, `--${skipValFlag.name}`, ], 'package_version_create'); const error = new Error(errorString); error['name'] = 'requiredFlagMissing'; return BBPromise.reject(error); } // This command also requires either the installationkey flag or installationkeybypass flag if (!context.flags.installationkey && !context.flags.installationkeybypass) { const installationKeyFlag = context.command.flags.find((x) => x.name === 'installationkey'); const installationKeyBypassFlag = context.command.flags.find((x) => x.name === 'installationkeybypass'); const errorString = messages.getMessage('errorMissingFlagsInstallationKey', [ `--${installationKeyFlag.name} (-${installationKeyFlag.char})`, `--${installationKeyBypassFlag.name} (-${installationKeyBypassFlag.char})`, ], 'package_version_create'); const error = new Error(errorString); error['name'] = 'requiredFlagMissing'; return BBPromise.reject(error); } // Retrieve the content of the sfdx-project.json config file let sfdxProjectJson = new core_1.SfdxProjectJson(context.org.force.config); sfdxProjectJson.readSync(); let configContentPromise = Promise.resolve(sfdxProjectJson.getContents()); // For the first rollout of validating sfdx-project.json data against schema, make it optional and defaulted // to false. Validation only occurs if the hidden -j (--validateschema) flag has been specified. if (context.flags.validateschema) { sfdxProjectJson.schemaValidateSync(); } let canonicalPackageProperty; // Look up the missing value or confirm a match return configContentPromise.then(async (configContent) => { this.packageDirs = configContent.packageDirectories; // Check for empty packageDirectories if (!this.packageDirs) { throw new Error(messages.getMessage('errorEmptyPackageDirs', null, 'package_version_create')); } if (!context.flags.package) { const packageValProp = this._getPackageValuePropertyFromDirectory(context, pathFlag); context.flags.package = packageValProp.packageValue; canonicalPackageProperty = packageValProp.packageProperty; } else if (!context.flags.path) { canonicalPackageProperty = this._getPackagePropertyFromPackage(this.packageDirs, context.flags.package, context); context.flags.path = this._getConfigPackageDirectoriesValue(context, this.packageDirs, 'path', canonicalPackageProperty, context.flags.package, packageFlag); } else { canonicalPackageProperty = this._getPackagePropertyFromPackage(this.packageDirs, context.flags.package, context); this._getConfigPackageDirectoriesValue(context, this.packageDirs, canonicalPackageProperty, 'path', context.flags.path, pathFlag); const expectedPackageId = this._getConfigPackageDirectoriesValue(context, this.packageDirs, canonicalPackageProperty, 'path', context.flags.path, pathFlag); // This will thrown an error if the package id flag value doesn't match // any of the :id values in the package dirs. this._getConfigPackageDirectoriesValue(context, this.packageDirs, 'path', canonicalPackageProperty, context.flags.package, packageFlag); // This will thrown an error if the package id flag value doesn't match // the correct corresponding directory with that packageId. if (context.flags.package !== expectedPackageId) { throw new Error(messages.getMessage('errorDirectoryIdMismatch', [ `--${pathFlag.name} (-${pathFlag.char})`, context.flags.path, `--${packageFlag.name} (-${packageFlag.char})`, context.flags.package, ], 'package_version_create')); } } const resolvedPackageId = pkgUtils.getPackageIdFromAlias(context.flags.package, this.force); // At this point, the packageIdFromAlias should have been resolved to an Id. Now, we // need to validate that the Id is correct. pkgUtils.validateId(pkgUtils.BY_LABEL.PACKAGE_ID, resolvedPackageId); await this._validateFlagsForPackageType(resolvedPackageId, context.flags); // validate the versionNumber flag value if specified, otherwise the descriptor value const versionNumber = context.flags.versionnumber ? context.flags.versionnumber : this._getConfigPackageDirectoriesValue(context, this.packageDirs, 'versionNumber', canonicalPackageProperty, context.flags.package, packageFlag); pkgUtils.validateVersionNumber(versionNumber, pkgUtils.NEXT_BUILD_NUMBER_TOKEN, null); await pkgUtils.validatePatchVersion(this.force, this.org, versionNumber, resolvedPackageId); try { fs.statSync(path.join(process.cwd(), context.flags.path)); } catch (err) { throw new Error(`Directory '${context.flags.path}' does not exist`); } // Check for an includeProfileUserLiceneses flag in the packageDirectory const includeProfileUserLicenses = this._getConfigPackageDirectoriesValue(context, this.packageDirs, 'includeProfileUserLicenses', canonicalPackageProperty, context.flags.package, packageFlag); if (includeProfileUserLicenses !== undefined && includeProfileUserLicenses !== true && includeProfileUserLicenses !== false) { throw new Error(messages.getMessage('errorProfileUserLicensesInvalidValue', [includeProfileUserLicenses], 'package_version_create')); } const shouldGenerateProfileInformation = logger.shouldLog(core_2.LoggerLevel.INFO) || logger.shouldLog(core_2.LoggerLevel.DEBUG); this.profileApi = new ProfileApi(this.org, includeProfileUserLicenses, shouldGenerateProfileInformation); // If we are polling check to see if the package is Org-Dependent, if so, update the poll time if (context.flags.wait) { const query = `SELECT IsOrgDependent FROM Package2 WHERE Id = '${resolvedPackageId}'`; this.force.toolingQuery(this.org, query).then((pkgQueryResult) => { const subRecords = pkgQueryResult.records; if (subRecords && subRecords.length === 1 && subRecords[0].IsOrgDependent) { this.pollInterval = POLL_INTERVAL_WITHOUT_VALIDATION_SECONDS; this.maxRetries = (60 / this.pollInterval) * context.flags.wait; } }); } return Promise.resolve().then(() => this._createPackageVersionCreateRequestFromOptions(context, resolvedPackageId) .then((request) => this.force.toolingCreate(this.org, 'Package2VersionCreateRequest', request)) .then((createResult) => { if (createResult.success) { return createResult.id; } else { const errStr = createResult.errors && createResult.errors.length ? createResult.errors.join(', ') : createResult.errors; throw new Error(`Failed to create request${createResult.id ? ` [${createResult.id}]` : ''}: ${errStr}`); } }) .then((id) => { if (context.flags.wait) { if (this.pollInterval) { return pkgUtils.pollForStatusWithInterval(context, id, this.maxRetries, resolvedPackageId, logger, true, this.force, this.org, this.pollInterval); } else { return pkgUtils.pollForStatus(context, id, this.maxRetries, resolvedPackageId, logger, true, this.force, this.org); } } else { return this.packageVersionCreateRequestApi.byId(id); } }) .then((result) => (util.isArray(result) ? result[0] : result))); }); } rejectWithInstallKeyError(context) { // This command also requires either the installationkey flag or installationkeybypass flag const installationKeyFlag = context.command.flags.find((x) => x.name === 'installationkey'); const installationKeyBypassFlag = context.command.flags.find((x) => x.name === 'installationkeybypass'); const errorString = messages.getMessage('errorMissingFlagsInstallationKey', [ `--${installationKeyFlag.name} (-${installationKeyFlag.char})`, `--${installationKeyBypassFlag.name} (-${installationKeyBypassFlag.char})`, ], 'package_version_create'); const error = new Error(errorString); error['name'] = 'requiredFlagMissing'; return BBPromise.reject(error); } async _validateFlagsForPackageType(packageId, flags) { const packageType = await pkgUtils.getPackage2Type(packageId, this.force, this.org); if (packageType == 'Unlocked') { if (flags.postinstallscript || flags.uninstallscript) { throw core_2.SfdxError.create('salesforce-alm', 'packaging', 'version_create.errorScriptsNotApplicableToUnlockedPackage'); } // Don't allow ancestor in unlocked packages const packageDescriptorJson = this._getPackageDescriptorJsonFromPackageId(packageId, flags); const ancestorId = packageDescriptorJson.ancestorId; const ancestorVersion = packageDescriptorJson.ancestorVersion; if (ancestorId || ancestorVersion) { throw core_2.SfdxError.create('salesforce-alm', 'packaging', 'version_create.errorAncestorNotApplicableToUnlockedPackage'); } } if (packageType == 'Managed') { // W-10261008 this block is temporary and should be removed after 236 R2 (2022-02-13) cli_ux_1.default.warn(messages.getMessage('ancestorCheckWarning', [], 'package_version_create')); } } /** * * @param result - the data representing the Package Version, must include a 'Status' property * @returns {string} a human readable message for CLI output */ getHumanSuccessMessage(result) { switch (result.Status) { case 'Error': return result.Error.length > 0 ? result.Error.join('\n') : messages.getMessage('unknownError', [], 'package_version_create'); case 'Success': return messages.getMessage(result.Status, [result.Id, result.SubscriberPackageVersionId, pkgUtils.INSTALL_URL_BASE, result.SubscriberPackageVersionId], 'package_version_create'); default: return messages.getMessage('InProgress', [pkgUtils.convertCamelCaseStringToSentence(result.Status), result.Id], 'package_version_create'); } } /** * Cleans invalid attribute(s) from the packageDescriptorJSON */ _cleanPackageDescriptorJson(packageDescriptorJson) { if (typeof packageDescriptorJson.default !== 'undefined') { delete packageDescriptorJson.default; // for client-side use only, not needed } if (typeof packageDescriptorJson.includeProfileUserLicenses !== 'undefined') { delete packageDescriptorJson.includeProfileUserLicenses; // for client-side use only, not needed } if (typeof packageDescriptorJson.unpackagedMetadata !== 'undefined') { delete packageDescriptorJson.unpackagedMetadata; // for client-side use only, not needed } if (typeof packageDescriptorJson.branch !== 'undefined') { delete packageDescriptorJson.branch; // for client-side use only, not needed } } /** * Sets default or override values for packageDescriptorJSON attribs */ _setPackageDescriptorJsonValues(packageDescriptorJson, context) { if (context.flags.versionname) { packageDescriptorJson.versionName = context.flags.versionname; } if (context.flags.versiondescription) { packageDescriptorJson.versionDescription = context.flags.versiondescription; } if (context.flags.versionnumber) { packageDescriptorJson.versionNumber = context.flags.versionnumber; } // default versionName to versionNumber if unset, stripping .NEXT if present if (!packageDescriptorJson.versionName) { const versionNumber = packageDescriptorJson.versionNumber; packageDescriptorJson.versionName = versionNumber.split(pkgUtils.VERSION_NUMBER_SEP)[3] === pkgUtils.NEXT_BUILD_NUMBER_TOKEN ? versionNumber.substring(0, versionNumber.indexOf(pkgUtils.VERSION_NUMBER_SEP + pkgUtils.NEXT_BUILD_NUMBER_TOKEN)) : versionNumber; logger.warnUser(context, messages.getMessage('defaultVersionName', packageDescriptorJson.versionName, 'package_version_create')); } if (context.flags.releasenotesurl) { packageDescriptorJson.releaseNotesUrl = context.flags.releasenotesurl; } if (packageDescriptorJson.releaseNotesUrl && !pkgUtils.validUrl(packageDescriptorJson.releaseNotesUrl)) { throw new Error(messages.getMessage('malformedUrl', ['releaseNotesUrl', packageDescriptorJson.releaseNotesUrl], 'package_version_create')); } if (context.flags.postinstallurl) { packageDescriptorJson.postInstallUrl = context.flags.postinstallurl; } if (packageDescriptorJson.postInstallUrl && !pkgUtils.validUrl(packageDescriptorJson.postInstallUrl)) { throw new Error(messages.getMessage('malformedUrl', ['postInstallUrl', packageDescriptorJson.postInstallUrl], 'package_version_create')); } if (context.flags.postinstallscript) { packageDescriptorJson.postInstallScript = context.flags.postinstallscript; } if (context.flags.uninstallscript) { packageDescriptorJson.uninstallScript = context.flags.uninstallscript; } } } module.exports = PackageVersionCreateCommand; //# sourceMappingURL=packageVersionCreateCommand.js.map