@salesforce/packaging
Version:
Packaging library for the Salesforce packaging platform
879 lines • 54.5 kB
JavaScript
"use strict";
/*
* Copyright (c) 2022, 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
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateVersionNumber = exports.validateAncestorId = exports.packageXmlJsonToXmlString = exports.packageXmlStringToPackageXmlJson = exports.MetadataResolver = exports.PackageVersionCreate = void 0;
const node_path_1 = __importDefault(require("node:path"));
const node_os_1 = __importDefault(require("node:os"));
const node_fs_1 = __importDefault(require("node:fs"));
const core_1 = require("@salesforce/core");
const source_deploy_retrieve_1 = require("@salesforce/source-deploy-retrieve");
const project_1 = require("@salesforce/core/project");
const kit_1 = require("@salesforce/kit");
const fast_xml_parser_1 = require("fast-xml-parser");
const ts_types_1 = require("@salesforce/ts-types");
const pkgUtils = __importStar(require("../utils/packageUtils"));
const packageUtils_1 = require("../utils/packageUtils");
const interfaces_1 = require("../interfaces");
const packageProfileApi_1 = require("./packageProfileApi");
const packageVersionCreateRequest_1 = require("./packageVersionCreateRequest");
const package_1 = require("./package");
const versionNumber_1 = require("./versionNumber");
core_1.Messages.importMessagesDirectory(__dirname);
const messages = core_1.Messages.loadMessages('@salesforce/packaging', 'package_version_create');
const DESCRIPTOR_FILE = 'package2-descriptor.json';
class PackageVersionCreate {
options;
apiVersionFromPackageXml;
project;
connection;
packageObject;
packageId;
pkg;
logger;
metadataResolver;
constructor(options) {
this.options = options;
this.connection = this.options.connection;
this.project = this.options.project;
this.logger = core_1.Logger.childFromRoot('packageVersionCreate');
this.metadataResolver = new MetadataResolver();
}
createPackageVersion() {
try {
return this.packageVersionCreate();
}
catch (err) {
if (err instanceof Error) {
throw pkgUtils.applyErrorAction(pkgUtils.massageErrorMessage(err));
}
throw err;
}
}
/**
* A dependency in the project 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.
*/
async retrieveSubscriberPackageVersionId(dependency) {
if (!dependency.versionNumber) {
throw messages.createError('errorDependencyPair', [JSON.stringify(dependency)]);
}
if (!dependency.packageId) {
throw messages.createError('errorDependencyPair', [JSON.stringify(dependency)]);
}
const versionNumber = versionNumber_1.VersionNumber.from(dependency.versionNumber);
const buildNumber = versionNumber.build;
// use the dependency.branch if present otherwise use the branch of the version being created
const branch = dependency.branch ?? this.options.branch;
const branchString = !branch ? 'null' : `'${branch}'`;
// resolve a build number keyword to an actual number, if needed
const resolvedBuildNumber = await this.resolveBuildNumber(versionNumber, dependency.packageId, branch);
// 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 branchOrReleasedCondition = buildNumber === versionNumber_1.BuildNumberToken.RELEASED_BUILD_NUMBER_TOKEN
? 'AND IsReleased = true'
: `AND Branch = ${branchString}`;
const query = `SELECT SubscriberPackageVersionId FROM Package2Version WHERE Package2Id = '${dependency.packageId}' AND MajorVersion = ${versionNumber.major} AND MinorVersion = ${versionNumber.minor} AND PatchVersion = ${versionNumber.patch} AND BuildNumber = ${resolvedBuildNumber} ${branchOrReleasedCondition}`;
const pkgVerQueryResult = await this.connection.tooling.query(query);
const subRecords = pkgVerQueryResult.records;
if (!subRecords || subRecords.length !== 1) {
throw messages.createError('versionNumberNotFoundInDevHub', [
dependency.packageId,
branchString,
versionNumber.toString(),
resolvedBuildNumber,
]);
}
// warn user of the resolved build number when LATEST and RELEASED keywords are used
if (versionNumber.isBuildKeyword()) {
versionNumber.build = resolvedBuildNumber;
if (buildNumber === versionNumber_1.BuildNumberToken.LATEST_BUILD_NUMBER_TOKEN) {
this.logger.info(messages.getMessage('buildNumberResolvedForLatest', [
dependency.package,
versionNumber.toString(),
branchString,
dependency.subscriberPackageVersionId,
]));
}
else if (buildNumber === versionNumber_1.BuildNumberToken.RELEASED_BUILD_NUMBER_TOKEN) {
this.logger.info(messages.getMessage('buildNumberResolvedForReleased', [
dependency.package,
versionNumber.toString(),
dependency.subscriberPackageVersionId,
]));
}
}
return pkgVerQueryResult.records[0].SubscriberPackageVersionId;
}
/** side effect: removes properties from the passed in dependency! */
async resolveSubscriberPackageVersionId(dependency) {
await this.validateDependencyValues(dependency);
if (dependency.subscriberPackageVersionId) {
delete dependency.package;
// if a 04t id is specified just use it.
return dependency;
}
dependency.subscriberPackageVersionId = await this.retrieveSubscriberPackageVersionId(dependency);
delete dependency.packageId;
delete dependency.package;
delete dependency.versionNumber;
delete dependency.branch;
return dependency;
}
async 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;
}
if (dependency.packageId && dependency.package) {
throw messages.createError('errorPackageAndPackageIdCollision', []);
}
const idOrPackage = dependency.packageId ?? dependency.package;
if (!idOrPackage) {
throw messages.createError('errorPackageOrPackageIdMissing', []);
}
const packageIdFromAlias = this.project.getPackageIdFromAlias(idOrPackage) ?? idOrPackage;
// 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;
}
if (!packageIdFromAlias || !dependency.versionNumber) {
throw messages.createError('errorDependencyPair', [JSON.stringify(dependency)]);
}
// Just override dependency.packageId value to the resolved alias.
dependency.packageId = packageIdFromAlias;
pkgUtils.validateId(pkgUtils.BY_LABEL.PACKAGE_ID, dependency.packageId);
(0, exports.validateVersionNumber)(dependency.versionNumber, versionNumber_1.BuildNumberToken.LATEST_BUILD_NUMBER_TOKEN, versionNumber_1.BuildNumberToken.RELEASED_BUILD_NUMBER_TOKEN);
await this.validatePatchVersion(dependency.versionNumber, dependency.packageId);
// Validate that the Package2 id exists on the server
const query = `SELECT Id FROM Package2 WHERE Id = '${dependency.packageId}'`;
const result = await this.connection.tooling.query(query);
if (!result.records || result.records.length !== 1) {
throw messages.createError('errorNoIdInHub', [dependency.packageId]);
}
}
async resolveBuildNumber(versionNumber, packageId, branch) {
if (!versionNumber.isBuildKeyword()) {
// The build number is already specified so just return it using the tooling query result obj structure
return `${versionNumber.build}`;
}
// query for the LATEST or RELEASED build number (excluding deleted versions)
let branchCondition = '';
let releasedCondition = '';
if (versionNumber.build === versionNumber_1.BuildNumberToken.LATEST_BUILD_NUMBER_TOKEN) {
// respect the branch when querying for LATEST
const branchString = !branch || branch === '' ? 'null' : `'${branch}'`;
branchCondition = `AND Branch = ${branchString}`;
}
else if (versionNumber.build === versionNumber_1.BuildNumberToken.RELEASED_BUILD_NUMBER_TOKEN) {
releasedCondition = 'AND IsReleased = true';
}
const query = `SELECT MAX(BuildNumber) FROM Package2Version WHERE Package2Id = '${packageId}' AND IsDeprecated != true AND MajorVersion = ${versionNumber.major} AND MinorVersion = ${versionNumber.minor} AND PatchVersion = ${versionNumber.patch} ${branchCondition} ${releasedCondition}`;
const results = await this.connection.tooling.query(query);
if (results.records?.length === 0 || results.records[0].expr0 == null) {
if (versionNumber.build === versionNumber_1.BuildNumberToken.RELEASED_BUILD_NUMBER_TOKEN) {
throw messages.createError('noReleaseVersionFound', [packageId, versionNumber.toString()]);
}
else {
throw messages.createError('noReleaseVersionFoundForBranch', [packageId, branch, versionNumber.toString()]);
}
}
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
return `${results.records[0].expr0}`;
}
async createRequestObject(preserveFiles, packageVersTmpRoot, packageVersBlobZipFile) {
const zipFileBase64 = node_fs_1.default.readFileSync(packageVersBlobZipFile).toString('base64');
const requestObject = {
Package2Id: this.packageId,
VersionInfo: zipFileBase64,
Tag: this.options.tag,
Branch: this.options.branch,
InstallKey: this.options.installationkey,
Instance: this.options.buildinstance,
SourceOrg: this.options.sourceorg,
CalculateCodeCoverage: this.options.codecoverage ?? false,
SkipValidation: this.options.skipvalidation ?? false,
AsyncValidation: this.options.asyncvalidation ?? false,
Language: this.options.language, // note: the createRequest's Language corresponds to the AllPackageVersion's language
CalcTransitiveDependencies: this.packageObject.calculateTransitiveDependencies ?? false,
};
this.stripUnsupportedPropertiesBasedOnApiVersion(requestObject);
if (preserveFiles) {
const message = messages.getMessage('tempFileLocation', [packageVersTmpRoot]);
await core_1.Lifecycle.getInstance().emit(interfaces_1.PackageVersionEvents.create['preserve-files'], {
location: packageVersTmpRoot,
message,
});
this.logger.info(message);
return requestObject;
}
else {
return node_fs_1.default.promises.rm(packageVersTmpRoot, { recursive: true, force: true }).then(() => requestObject);
}
}
// TODO: W-18696754 - None of the logger.warn() calls work, nothing is printed to the console when they're hit.
stripUnsupportedPropertiesBasedOnApiVersion(requestObject) {
if (this.connection.getApiVersion() < '57.0') {
if (requestObject.Language) {
this.logger.warn(`The language option is only valid for API version 57.0 and higher. Ignoring ${requestObject.Language}`);
}
delete requestObject.Language;
}
if (this.connection.getApiVersion() < '60.0') {
if (requestObject.AsyncValidation) {
this.logger.warn(`The async validation option is only valid for API version 60.0 and higher. Ignoring ${requestObject.AsyncValidation}`);
}
delete requestObject.AsyncValidation;
}
if (this.connection.getApiVersion() < '65.0') {
if (requestObject.CalcTransitiveDependencies) {
this.logger.warn(`The CalculateTransitiveDependencies option is only valid for API version 65.0 and higher. Ignoring ${requestObject.CalcTransitiveDependencies}`);
}
delete requestObject.CalcTransitiveDependencies;
}
}
/**
* Convert the list of command line options to a JSON object that can be used to create an Package2VersionCreateRequest entity.
*
* @returns {{Package2Id: (*|p|boolean), Package2VersionMetadata: *, Tag: *, Branch: number}}
* @private
*/
async createPackageVersionCreateRequestFromOptions() {
const preserveFiles = !!(this.options.preserve ?? kit_1.env.getBoolean('SF_PACKAGE2_VERSION_CREATE_PRESERVE'));
const uniqueHash = (0, packageUtils_1.uniqid)({ template: `${this.packageId}-%s` });
const packageVersTmpRoot = node_path_1.default.join(node_os_1.default.tmpdir(), `${uniqueHash}`);
const packageVersMetadataFolder = node_path_1.default.join(packageVersTmpRoot, 'md-files');
const unpackagedMetadataFolder = node_path_1.default.join(packageVersTmpRoot, 'unpackaged-md-files');
const seedMetadataFolder = node_path_1.default.join(packageVersTmpRoot, 'seed-md-files');
const packageVersProfileFolder = node_path_1.default.join(packageVersMetadataFolder, 'profiles');
const packageVersBlobDirectory = node_path_1.default.join(packageVersTmpRoot, 'package-version-info');
const metadataZipFile = node_path_1.default.join(packageVersBlobDirectory, 'package.zip');
const unpackagedMetadataZipFile = node_path_1.default.join(packageVersBlobDirectory, 'unpackaged-metadata-package.zip');
const seedMetadataZipFile = node_path_1.default.join(packageVersBlobDirectory, 'seed-metadata-package.zip');
const settingsZipFile = node_path_1.default.join(packageVersBlobDirectory, 'settings.zip');
const packageVersBlobZipFile = node_path_1.default.join(packageVersTmpRoot, 'package-version-info.zip');
const sourceBaseDir = node_path_1.default.join(this.project.getPath(), this.packageObject?.path ?? '');
const mdOptions = {
deploydir: packageVersMetadataFolder,
sourceDir: sourceBaseDir,
sourceApiVersion: this.project?.getSfProjectJson()?.get('sourceApiVersion'),
};
await node_fs_1.default.promises.mkdir(packageVersBlobDirectory, { recursive: true });
const settingsGenerator = new core_1.ScratchOrgSettingsGenerator({ asDirectory: true });
let packageDescriptorJson = structuredClone(this.packageObject);
const apvLanguage = packageDescriptorJson.language;
// Copy all the metadata from the workspace to a tmp folder
const componentSet = await this.metadataResolver.generateMDFolderForArtifact(mdOptions);
this.verifyHasSource(componentSet);
if (packageDescriptorJson.package) {
delete packageDescriptorJson.package;
packageDescriptorJson.id = this.packageId;
}
if (packageDescriptorJson.language) {
// the structuredClone() call above added the packageDir's language to the descriptor;
// remove that from the descriptor here; it will be set correctly from the definitionFile values below
delete packageDescriptorJson.language;
}
const definitionFile = this.options.definitionfile
? this.options.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 node_fs_1.default.promises.readFile(definitionFile, 'utf8');
const definitionFileJson = JSON.parse(definitionFilePayload);
// Load any settings from the definition
await settingsGenerator.extract(definitionFileJson);
if (settingsGenerator.hasSettings() && definitionFileJson.orgPreferences) {
// this is not allowed, exit with an error
throw messages.createError('signupDuplicateSettingsSpecified');
}
packageDescriptorJson = (0, packageUtils_1.copyDescriptorProperties)(packageDescriptorJson, definitionFileJson);
}
this.resolveBuildUserPermissions(packageDescriptorJson);
// All dependencies for the packaging dir should be resolved to an 04t id to be passed to the server.
// (see resolveSubscriberPackageVersionId() for details)
const dependencies = packageDescriptorJson.dependencies;
// branch and APV language can be set via options or packageDirectory; option takes precedence
this.options.branch = this.options.branch ?? packageDescriptorJson.branch;
this.options.language = this.options.language ?? apvLanguage;
const resultValues = await Promise.all(!dependencies ? [] : dependencies.map((dependency) => this.resolveSubscriberPackageVersionId(dependency)));
const versionNumber = this.options.versionnumber ?? packageDescriptorJson.versionNumber;
if (!versionNumber) {
throw messages.createError('versionNumberRequired');
}
const ancestorId = await this.getAncestorId(packageDescriptorJson, this.options.project, versionNumber, !!this.options.skipancestorcheck);
// 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;
}
packageDescriptorJson = cleanPackageDescriptorJson(packageDescriptorJson);
packageDescriptorJson = setPackageDescriptorJsonValues(packageDescriptorJson, this.options, this.logger);
await node_fs_1.default.promises.mkdir(packageVersTmpRoot, { recursive: true });
await node_fs_1.default.promises.mkdir(packageVersBlobDirectory, { recursive: true });
if (Reflect.has(packageDescriptorJson, 'ancestorVersion')) {
delete packageDescriptorJson.ancestorVersion;
}
packageDescriptorJson.ancestorId = ancestorId;
await node_fs_1.default.promises.writeFile(node_path_1.default.join(packageVersBlobDirectory, DESCRIPTOR_FILE), JSON.stringify(packageDescriptorJson), 'utf-8');
await this.cleanGeneratedPackage({
packageVersMetadataFolder,
packageVersProfileFolder,
unpackagedMetadataFolder,
seedMetadataFolder,
metadataZipFile,
settingsZipFile,
packageVersBlobDirectory,
packageVersBlobZipFile,
unpackagedMetadataZipFile,
seedMetadataZipFile,
settingsGenerator,
});
return this.createRequestObject(preserveFiles, packageVersTmpRoot, packageVersBlobZipFile);
}
verifyHasSource(result) {
if (!result.converted || result.converted?.length === 0) {
throw messages.createError('noSourceInRootDirectory', [this.packageObject.path ?? '<unknown>']);
}
}
async cleanGeneratedPackage({ packageVersMetadataFolder, packageVersProfileFolder, unpackagedMetadataFolder, seedMetadataFolder, metadataZipFile, settingsZipFile, packageVersBlobDirectory, packageVersBlobZipFile, unpackagedMetadataZipFile, seedMetadataZipFile, settingsGenerator, }) {
// 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.
const currentPackageXml = await node_fs_1.default.promises.readFile(node_path_1.default.join(packageVersMetadataFolder, 'package.xml'), 'utf8');
// convert to json
const packageXmlAsJson = (0, exports.packageXmlStringToPackageXmlJson)(currentPackageXml);
if (!packageXmlAsJson) {
throw messages.createError('packageXmlDoesNotContainPackage');
}
if (!packageXmlAsJson?.types) {
throw messages.createError('packageXmlDoesNotContainPackageTypes');
}
node_fs_1.default.mkdirSync(packageVersMetadataFolder, { recursive: true });
node_fs_1.default.mkdirSync(packageVersProfileFolder, { recursive: true });
this.apiVersionFromPackageXml = packageXmlAsJson.version;
const sourceApiVersion = this.project?.getSfProjectJson()?.get('sourceApiVersion');
const hasSeedMetadata = await this.metadataResolver.resolveMetadata(this.packageObject.seedMetadata?.path, seedMetadataFolder, 'seedMDDirectoryDoesNotExist', sourceApiVersion);
let hasUnpackagedMetadata = false;
const unpackagedMetadataPath = this.packageObject.unpackagedMetadata?.path;
if (this.options.codecoverage) {
hasUnpackagedMetadata = await this.metadataResolver.resolveMetadata(unpackagedMetadataPath, unpackagedMetadataFolder, 'unpackagedMDDirectoryDoesNotExist', sourceApiVersion);
}
let profileExcludeDirs = [];
if (this.packageObject.scopeProfiles) {
this.logger.debug(`packageDirectory: ${this.packageObject.name} has 'scopeProfiles' set, so only including profiles from within this directory`);
// exclude all package dirs except the one being packaged
profileExcludeDirs = this.project
.getPackageDirectories()
.map((packageDir) => {
if (packageDir.path !== this.packageObject.path) {
return packageDir.path;
}
})
.filter((packageDirPath) => packageDirPath);
}
else {
// don't package the profiles from any un-packagedMetadata dir in the project
profileExcludeDirs = this.project
.getPackageDirectories()
.filter(project_1.isNamedPackagingDirectory)
.map((packageDir) => packageDir.unpackagedMetadata?.path)
.filter(ts_types_1.isString);
let debugMsg = 'Searching for profiles to include from all packageDirectories';
if (profileExcludeDirs?.length) {
debugMsg += ` excluding these unpackagedMetadata dirs: ${profileExcludeDirs.toString()}`;
}
this.logger.debug(debugMsg);
}
const typesArr = this.options?.profileApi?.filterAndGenerateProfilesForManifest(packageXmlAsJson.types, profileExcludeDirs) ??
packageXmlAsJson.types;
// Next generate profiles and retrieve any profiles that were excluded because they had no matching nodes.
const excludedProfiles = this.options?.profileApi?.generateProfiles(packageVersProfileFolder, typesArr, profileExcludeDirs);
packageXmlAsJson.types = typesArr.map((type) => {
if (type.name !== 'Profile')
return type;
return { ...type, members: type.members.filter((m) => !excludedProfiles?.includes(m)) };
});
const xml = (0, exports.packageXmlJsonToXmlString)(packageXmlAsJson);
await node_fs_1.default.promises.writeFile(node_path_1.default.join(packageVersMetadataFolder, 'package.xml'), xml, 'utf-8');
// Zip the packageVersMetadataFolder folder and put the zip in {packageVersBlobDirectory}/package.zip
await (0, packageUtils_1.zipDir)(packageVersMetadataFolder, metadataZipFile);
if (hasSeedMetadata) {
// Zip the seedMetadataFolder folder and put the zip in {packageVersBlobDirectory}/{seedMetadataZipFile}
await (0, packageUtils_1.zipDir)(seedMetadataFolder, seedMetadataZipFile);
}
if (hasUnpackagedMetadata) {
// Zip the unpackagedMetadataFolder folder and put the zip in {packageVersBlobDirectory}/{unpackagedMetadataZipFile}
await (0, packageUtils_1.zipDir)(unpackagedMetadataFolder, unpackagedMetadataZipFile);
}
// Zip up the expanded settings (if present)
if (settingsGenerator.hasSettings()) {
await settingsGenerator.createDeploy();
await settingsGenerator.createDeployPackageContents(this.apiVersionFromPackageXml);
await (0, packageUtils_1.zipDir)(`${settingsGenerator.getDestinationPath() ?? ''}${node_path_1.default.sep}${settingsGenerator.getShapeDirName()}`, settingsZipFile);
}
// Zip the Version Info and package.zip files into another zip
await (0, packageUtils_1.zipDir)(packageVersBlobDirectory, packageVersBlobZipFile);
}
/** side effect: modifies the passed in parameter! */
resolveBuildUserPermissions(packageDescriptorJson) {
// Process permissionSet and permissionSetLicenses that should be enabled when running Apex tests
// This only applies if code coverage is enabled
if (this.options.codecoverage) {
// Assuming no permission sets are named 0, 0n, null, undefined, false, NaN, and the empty string
if (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?.permissionSetLicenses) {
let permissionSetLicenses = packageDescriptorJson.apexTestAccess.permissionSetLicenses;
if (!Array.isArray(permissionSetLicenses)) {
permissionSetLicenses = permissionSetLicenses.split(',');
}
packageDescriptorJson.permissionSetLicenseDeveloperNames = permissionSetLicenses.map((s) => s.trim());
}
}
// Process permissionSet and permissionsetLicenses that should be enabled for the package metadata deploy
if (packageDescriptorJson.packageMetadataAccess?.permissionSets) {
let permSets = packageDescriptorJson.packageMetadataAccess.permissionSets;
if (!Array.isArray(permSets)) {
permSets = permSets.split(',');
}
packageDescriptorJson.packageMetadataPermissionSetNames = permSets.map((s) => s.trim());
}
if (packageDescriptorJson.packageMetadataAccess?.permissionSetLicenses) {
let permissionSetLicenses = packageDescriptorJson.packageMetadataAccess.permissionSetLicenses;
if (!Array.isArray(permissionSetLicenses)) {
permissionSetLicenses = permissionSetLicenses.split(',');
}
packageDescriptorJson.packageMetadataPermissionSetLicenseNames = permissionSetLicenses.map((s) => s.trim());
}
delete packageDescriptorJson.apexTestAccess;
delete packageDescriptorJson.packageMetadataAccess;
}
// eslint-disable-next-line complexity
async packageVersionCreate() {
// For the first rollout of validating sfdx-project.json data against schema, make it optional and defaulted
// to false. Validation only occurs if the optional validateschema option has been specified.
if (this.options.validateschema) {
await this.project.getSfProjectJson().schemaValidate();
}
// Check for empty packageDirectories
if (this.project.getPackageDirectories()?.length === 0) {
throw messages.createError('errorEmptyPackageDirs');
}
// either options.packageId or options.path is required
if (!this.options.packageId && !this.options.path) {
throw messages.createError('errorMissingPackageIdOrPath');
}
// establish the package Id (0ho) and load the package directory
let packageName;
let packageObject;
if (this.options.packageId) {
const pkg = this.options.packageId;
// for backward compatibility allow for a packageDirectory package property to be an id (0Ho) instead of an alias.
packageName = (await this.getPackageDirFromId(pkg))?.package;
if (!packageName) {
packageName = pkg.startsWith('0Ho') ? this.project.getAliasesFromPackageId(pkg).find((alias) => alias) : pkg;
if (!packageName)
throw messages.createError('errorMissingPackage', [this.options.packageId]);
}
packageObject = this.project.findPackage((namedPackageDir) => (0, project_1.isPackagingDirectory)(namedPackageDir) &&
(namedPackageDir.package === packageName || namedPackageDir.name === packageName));
}
else {
// We'll either have a package ID or alias, or a directory path
if (!this.options.path) {
throw messages.createError('errorMissingPackagePath', [JSON.stringify(this.options)]);
}
packageObject = this.project.getPackageFromPath(this.options.path);
if (!packageObject || !(0, project_1.isPackagingDirectory)(packageObject))
throw messages.createError('errorCouldNotFindPackageUsingPath', [this.options.path]);
packageName = packageObject?.package;
}
if (!packageObject) {
throw messages.createError('errorCouldNotFindPackageDir', [
this.options.packageId ? 'packageId or alias' : 'path',
this.options.packageId ?? this.options.path,
]);
}
else {
this.packageObject = packageObject;
}
this.packageId = this.project.getPackageIdFromAlias(packageName) ?? packageName;
this.options.profileApi = await packageProfileApi_1.PackageProfileApi.create({
project: this.project,
includeUserLicenses: !!this.packageObject.includeProfileUserLicenses,
});
// 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, this.packageId);
try {
await this.validateOptionsForPackageType();
}
catch (error) {
if (error instanceof Error) {
if (error.name === 'NOT_FOUND') {
// this means the 0Ho package was not found in the org. throw a better error.
throw messages.createError('errorNoIdInHub', [this.packageId]);
}
}
throw error;
}
const request = await this.createPackageVersionCreateRequestFromOptions();
const options = {
multipartFileFields: {
VersionInfo: {
contentType: 'application/zip',
filename: 'package-version-info.zip',
},
},
};
const createResult = await this.connection.tooling.create('Package2VersionCreateRequest', request, options);
if (!createResult.success) {
const errStr = createResult.errors?.join(', ') ?? createResult.errors;
const id = createResult.id ?? '';
throw messages.createError('failedToCreatePVCRequest', [id === '' ? '' : ` [${id}]`, errStr.toString()]);
}
return (await (0, packageVersionCreateRequest_1.byId)(createResult.id, this.connection))[0];
}
async getPackageDirFromId(pkg) {
if (pkg.startsWith('0Ho')) {
const dir = (await this.project.getSfProjectJson().getPackageDirectories())
.filter(project_1.isPackagingDirectory)
.filter((p) => p.package === pkg);
if (dir.length === 1) {
return dir[0];
}
}
}
async getPackageType() {
// this.packageId should be an 0Ho package Id at this point
if (!this.pkg) {
this.pkg = new package_1.Package({
packageAliasOrId: this.packageId,
project: this.project,
connection: this.connection,
});
}
return this.pkg.getType();
}
async validateOptionsForPackageType() {
if ((await this.getPackageType()) === 'Unlocked') {
// Don't allow scripts in unlocked packages
if (Boolean(this.options.postinstallscript) || this.options.uninstallscript) {
throw messages.createError('errorScriptsNotApplicableToUnlockedPackage');
}
// Don't allow ancestor in unlocked packages
if (Boolean(this.packageObject.ancestorId) || this.packageObject.ancestorVersion) {
throw messages.createError('errorAncestorNotApplicableToUnlockedPackage');
}
}
}
async validatePatchVersion(versionNumberString, packageId) {
const query = `SELECT ContainerOptions FROM Package2 WHERE id ='${packageId}'`;
const queryResult = await this.connection.tooling.query(query);
if (queryResult.records === null || queryResult.records.length === 0) {
throw messages.createError('errorInvalidPackageId', [packageId]);
}
// Enforce a patch version of zero (0) for Locked packages only
if (queryResult.records[0].ContainerOptions === 'Locked') {
const versionNumber = versionNumber_1.VersionNumber.from(versionNumberString);
if (versionNumber.patch !== '0') {
throw messages.createError('errorInvalidPatchNumber', [versionNumberString]);
}
}
}
// eslint-disable-next-line complexity
async getAncestorId(packageDescriptorJson, project, versionNumberString, skipAncestorCheck) {
// If an id property is present, use it. Otherwise, look up the package id from the package property.
const packageId = packageDescriptorJson.id ??
project.getPackageIdFromAlias(packageDescriptorJson.package ?? '') ??
packageDescriptorJson.package;
// No need to proceed if Unlocked
if ((await this.getPackageType()) === 'Unlocked') {
return '';
}
let ancestorId = '';
// ancestorID can be alias, 05i, or 04t;
// validate and convert to 05i, as needed
const versionNumber = versionNumber_1.VersionNumber.from(versionNumberString);
let origSpecifiedAncestor = packageDescriptorJson.ancestorId ?? '';
let highestReleasedVersion = null;
const explicitUseHighestRelease = packageDescriptorJson.ancestorId === versionNumber_1.BuildNumberToken.HIGHEST_VERSION_NUMBER_TOKEN ||
packageDescriptorJson.ancestorVersion === versionNumber_1.BuildNumberToken.HIGHEST_VERSION_NUMBER_TOKEN;
const explicitUseNoAncestor = packageDescriptorJson.ancestorId === versionNumber_1.BuildNumberToken.NONE_VERSION_NUMBER_TOKEN ||
packageDescriptorJson.ancestorVersion === versionNumber_1.BuildNumberToken.NONE_VERSION_NUMBER_TOKEN;
if ((explicitUseHighestRelease || explicitUseNoAncestor) &&
packageDescriptorJson.ancestorId &&
packageDescriptorJson.ancestorVersion) {
if (packageDescriptorJson.ancestorId !== packageDescriptorJson.ancestorVersion) {
// both ancestorId and ancestorVersion specified, HIGHEST and/or NONE are used, the values disagree
throw messages.createError('errorAncestorIdVersionHighestOrNoneMismatch', [
packageDescriptorJson.ancestorId,
packageDescriptorJson.ancestorVersion,
]);
}
}
if (explicitUseNoAncestor && skipAncestorCheck) {
return '';
}
else {
const result = await this.getAncestorIdHighestRelease(packageId, versionNumberString, explicitUseHighestRelease, skipAncestorCheck);
if (result.finalAncestorId) {
return result.finalAncestorId;
}
highestReleasedVersion = result.highestReleasedVersion;
}
// at this point if explicitUseHighestRelease=true, we have returned the ancestorId or thrown an error
// highestReleasedVersion should be null only if skipAncestorCheck or if there is no existing released package version
if (!explicitUseNoAncestor && packageDescriptorJson.ancestorId) {
ancestorId = project.getPackageIdFromAlias(packageDescriptorJson.ancestorId) ?? packageDescriptorJson.ancestorId;
(0, packageUtils_1.validateId)([packageUtils_1.BY_LABEL.SUBSCRIBER_PACKAGE_VERSION_ID, packageUtils_1.BY_LABEL.PACKAGE_VERSION_ID], ancestorId);
ancestorId = (await (0, packageUtils_1.getPackageVersionId)(ancestorId, this.connection)) ?? '';
}
if (!explicitUseNoAncestor && packageDescriptorJson.ancestorVersion) {
const regNumbers = new RegExp('^[0-9]+$');
const versionNumberSplit = packageDescriptorJson.ancestorVersion.split(packageUtils_1.VERSION_NUMBER_SEP);
if (versionNumberSplit.length < 3 ||
versionNumberSplit.length > 4 ||
!versionNumberSplit[0].match(regNumbers) ||
!versionNumberSplit[1].match(regNumbers) ||
!versionNumberSplit[2].match(regNumbers)) {
throw new Error(messages.getMessage('errorInvalidAncestorVersionFormat', [packageDescriptorJson.ancestorVersion]));
}
const query = 'SELECT Id, IsReleased FROM Package2Version ' +
`WHERE Package2Id = '${packageId ?? ''}' AND MajorVersion = ${versionNumberSplit[0]} AND MinorVersion = ${versionNumberSplit[1]} AND PatchVersion = ${versionNumberSplit[2]}`;
let queriedAncestorId;
const ancestorVersionResult = await this.connection.tooling.query(query);
if (!ancestorVersionResult.totalSize) {
throw messages.createError('errorNoMatchingAncestor', [packageDescriptorJson.ancestorVersion, packageId]);
}
else {
const releasedAncestor = ancestorVersionResult.records.find((rec) => rec.IsReleased === true);
if (!releasedAncestor) {
throw messages.createError('errorAncestorNotReleased', [packageDescriptorJson.ancestorVersion]);
}
else {
queriedAncestorId = releasedAncestor.Id;
}
}
// check for discrepancy between queried ancestorId and descriptor's ancestorId
if (packageDescriptorJson?.ancestorId && ancestorId !== queriedAncestorId) {
throw messages.createError('errorAncestorIdVersionMismatch', [
packageDescriptorJson.ancestorVersion,
packageDescriptorJson.ancestorId,
]);
}
ancestorId = queriedAncestorId;
origSpecifiedAncestor = packageDescriptorJson.ancestorVersion;
}
return (0, exports.validateAncestorId)(ancestorId, highestReleasedVersion, explicitUseNoAncestor, versionNumber.patch !== '0', skipAncestorCheck, origSpecifiedAncestor);
}
async getAncestorIdHighestRelease(packageId, versionNumberString, explicitUseHighestRelease, skipAncestorCheck) {
if (!packageId) {
throw messages.createError('packageIdCannotBeUndefined');
}
const versionNumber = versionNumberString.split(packageUtils_1.VERSION_NUMBER_SEP);
const isPatch = versionNumber[2] !== '0';
const result = {
finalAncestorId: null,
highestReleasedVersion: null,
};
if (isPatch && explicitUseHighestRelease) {
// based on server-side validation, whatever ancestor is specified for a patch is
// tightly controlled; therefore we only need concern ourselves if explicitUseHighestRelease == true;
// equally applies when skipAncestorCheck == true
// gather appropriate matching major.minor.0
const query = `SELECT Id FROM Package2Version WHERE Package2Id = '${packageId}' ` +
'AND IsReleased = True AND IsDeprecated = False AND PatchVersion = 0 ' +
`AND MajorVersion = ${versionNumber[0]} AND MinorVersion = ${versionNumber[1]} ` +
'ORDER BY MajorVersion Desc, MinorVersion Desc, PatchVersion Desc, BuildNumber Desc LIMIT 1';
const majorMinorVersionResult = await this.connection.tooling.query(query);
const majorMinorVersionRecords = majorMinorVersionResult.records;
if (majorMinorVersionRecords && majorMinorVersionRecords?.length === 1 && majorMinorVersionRecords[0]) {
result.finalAncestorId = majorMinorVersionRecords[0].Id ?? null;
}
else {
const majorMinorNotFound = `${versionNumber[0]}.${versionNumber[1]}.0`;
throw messages.createError('errorNoMatchingMajorMinorForPatch', [majorMinorNotFound]);
}
}
else if (!isPatch && (explicitUseHighestRelease || !skipAncestorCheck)) {
// ancestor must be set to latest released major.minor version
const query = 'SELECT Id, SubscriberPackageVersionId, MajorVersion, MinorVersion, PatchVersion FROM Package2Version ' +
`WHERE Package2Id = '${packageId}' AND IsReleased = True AND IsDeprecated = False AND PatchVersion = 0 ` +
'ORDER BY MajorVersion Desc, MinorVersion Desc, PatchVersion Desc, BuildNumber Desc LIMIT 1';
const highestVersionResult = await this.connection.tooling.query(query);
const highestVersionRecords = highestVersionResult.records;
if (highestVersionRecords?.[0]) {
result.highestReleasedVersion = highestVersionRecords[0];
if (explicitUseHighestRelease) {
result.finalAncestorId = result.highestReleasedVersion.Id;
}
}
else if (explicitUseHighestRelease) {
// there is no eligible ancestor version
throw messages.createError('errorNoMatchingAncestor', [versionNumberString, packageId]);
}
}
return result;
}
}
exports.PackageVersionCreate = PackageVersionCreate;
class MetadataResolver {
async resolveMetadata(metadataRelativePath, metadataOutputPath, errorMessageLabel, sourceApiVersion) {
if (metadataRelativePath) {
const metadataFullPath = node_path_1.default.join(process.cwd(), metadataRelativePath);
if (!node_fs_1.default.existsSync(metadataFullPath)) {
throw messages.createError(errorMessageLabel, [metadataRelativePath]);
}
node_fs_1.default.mkdirSync(metadataOutputPath, { recursive: true });
await this.generateMDFolderForArtifact({
deploydir: metadataOutputPath,
sourceDir: metadataFullPath,
sourceApiVersion,
});
return true;
}
return false;
}
// convert source to mdapi format and copy to tmp dir packaging up
async generateMDFolderForArtifact(options) {
const sourcePaths = (0, kit_1.ensureArray)(options.sourcePaths ?? options.sourceDir ? options.sourceDir : undefined).filter((srcPath) => srcPath);
const componentSetOptions = {
sourceapiversion: options.sourceApiVersion,
...(sourcePaths.length > 0 ? { sourcepath: sourcePaths } : {}),
};
if (!options.deploydir) {
throw messages.createError('deploydirCannotBeUndefined', [JSON.stringify(options)]);
}
const componentSet = await source_deploy_retrieve_1.ComponentSetBuilder.build(componentSetOptions);
const packageName = options.packageName;
const outputDirectory = node_path_1.default.resolve(options.deploydir);
const convertResult = await this.convertMetadata(componentSet, outputDirectory, packageName);
if (packageName) {
// SDR will build an output path like /output/directory/packageName/package.xml
// this was breaking from toolbelt, so to revert it we copy the directory up a level and delete the original
if (!convertResult.packagePath) {
throw messages.createError('packagePathCannotBeUndefined');
}
(0, packageUtils_1.copyDir)(convertResult.packagePath, outputDirectory);
try {
node_fs_1.default.rmSync(convertResult.packagePath, { recursive: true });
}
catch (e) {
// rmdirSync is being deprecated and emits a warning
// but rmSync is introduced in node 14 so fall back to rmdirSync
node_fs_1.default.rmdirSync(convertResult.packagePath, { recursive: true });
}
convertResult.packagePath = outputDirectory;
}
return convertResult;
}
/**
* Extracted into a method for UT purposes
*
* @param componentSet CS to convert
* @param outputDirectory where to place the converted MD
* @param packageName the packagename related to the CS
* @private
*/
// eslint-disable-next-line class-methods-use-this
async convertMetadata(componentSet, outputDirectory, packageName) {
const converter = new source_deploy_retrieve_1.MetadataConverter();
return converter.convert(componentSet, 'metadata', {
type: 'directory',
outputDirectory,
packageName,
genUniqueDir: false,
});
}
}
exports.MetadataResolver = MetadataResolver;
const packageXmlStringToPackageXmlJson = (rawXml) => {
const parser = new fast_xml_parser_1.XMLParser({
ignoreAttributes: true,
parseTagValue: false,
parseAttributeValue: false,
cdataPropName: '__cdata',
ignoreDeclaration: true,
numberParseOptions: { leadingZeros: false, hex: false },
// make sure types and members is always an array
isArray: (name) => ['types', 'members'].includes(name),
});
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
return parser.parse(rawXml).Package;
};
exports.packageXmlStringToPackageXmlJson = packageXmlStringToPackageXmlJson;
/**
* Converts PackageXmlJson to a string representing the Xml
* */
const packageXmlJsonToXmlString = (packageXmlJson) => {
const builder = new fast_xml_parser_1.XMLBuilder({
format: true,
indentBy: ' ',
ignoreAttributes: false,
cdataPropName: '__cdata',
processEntities: false,
attributeNamePrefix: '@@@',
});
return String(builder.build({
'?xml': {
'@@@version': '1.0',
'@@@encoding