@salesforce/packaging
Version:
Packaging library for the Salesforce packaging platform
564 lines • 25.5 kB
JavaScript
"use strict";
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PackageVersion = exports.Package2VersionFields = void 0;
const core_1 = require("@salesforce/core");
const kit_1 = require("@salesforce/kit");
const interfaces_1 = require("../interfaces");
const packageUtils_1 = require("../utils/packageUtils");
const packageVersionCreate_1 = require("./packageVersionCreate");
const packageVersionReport_1 = require("./packageVersionReport");
const packageVersionCreateRequestReport_1 = require("./packageVersionCreateRequestReport");
const packageVersionCreateRequest_1 = require("./packageVersionCreateRequest");
var Package2VersionStatus = interfaces_1.PackagingSObjects.Package2VersionStatus;
core_1.Messages.importMessagesDirectory(__dirname);
const messages = core_1.Messages.loadMessages('@salesforce/packaging', 'package_version');
exports.Package2VersionFields = [
'Id',
'IsDeleted',
'CreatedDate',
'CreatedById',
'LastModifiedDate',
'LastModifiedById',
'SystemModstamp',
'Package2Id',
'SubscriberPackageVersionId',
'Tag',
'Branch',
'AncestorId',
'ValidationSkipped',
'ValidatedAsync',
'Name',
'Description',
'MajorVersion',
'MinorVersion',
'PatchVersion',
'BuildNumber',
'IsDeprecated',
'IsPasswordProtected',
'CodeCoverage',
'CodeCoveragePercentages',
'HasPassedCodeCoverageCheck',
'InstallKey',
'IsReleased',
'ConvertedFromVersionId',
'ReleaseVersion',
'BuildDurationInSeconds',
'HasMetadataRemoved',
'EndToEndBuildDurationInSeconds',
'DeveloperUsePkgZip',
];
/**
* Provides the ability to create, update, delete, and promote 2nd
* generation package versions.
*
* **Examples**
*
* Create a new instance and get the ID (05i):
*
* `const id = new PackageVersion({connection, project, idOrAlias}).getId();`
*
* Create a new package version in the org:
*
* `const myPkgVersion = await PackageVersion.create(options, pollingOptions);`
*
* Promote a package version:
*
* `new PackageVersion({connection, project, idOrAlias}).promote();`
*/
class PackageVersion {
options;
project;
connection;
data;
packageType;
id;
constructor(options) {
this.options = options;
this.connection = this.options.connection;
this.project = this.options.project;
this.id = this.resolveId();
}
/**
* Sends a request to create a new package version and optionally polls for
* the status of the request until the package version is created or the
* polling timeout is reached.
*
* @param options PackageVersionCreateOptions
* @param polling frequency and timeout Durations to be used in polling
* @returns PackageVersionCreateRequestResult
*/
static async create(options, polling = {
frequency: kit_1.Duration.seconds(0),
timeout: kit_1.Duration.seconds(0),
}) {
const pvc = new packageVersionCreate_1.PackageVersionCreate({ ...options });
const createResult = await pvc.createPackageVersion();
if (createResult.Id) {
return PackageVersion.pollCreateStatus(createResult.Id, options.connection, options.project, polling).catch((err) => {
if (err.name === 'PollingClientTimeout') {
err.setData({ VersionCreateRequestId: createResult.Id });
err.message += ` Run 'sf package version create report -i ${createResult.Id}' to check the status.`;
}
// TODO
// until package2 is GA, wrap perm-based errors w/ 'contact sfdc' action (REMOVE once package2 is GA'd)
throw (0, packageUtils_1.applyErrorAction)((0, packageUtils_1.massageErrorMessage)(err));
});
}
else {
throw new Error(messages.getMessage('createResultIdCannotBeEmpty'));
}
}
/**
* Gets current state of a package version create request.
*
* @param createPackageRequestId
* @param connection
*/
static async getCreateStatus(createPackageRequestId, connection) {
return (0, packageVersionCreateRequestReport_1.getCreatePackageVersionCreateRequestReport)({
createPackageVersionRequestId: createPackageRequestId,
connection,
}).catch((err) => {
// TODO
// until package2 is GA, wrap perm-based errors w/ 'contact sfdc' action (REMOVE once package2 is GA'd)
throw (0, packageUtils_1.applyErrorAction)((0, packageUtils_1.massageErrorMessage)(err));
});
}
/**
* Fetch a list of package version create requests based on the given options.
*
* @param connection connection to an org
* @param options PackageVersionCreateRequestQueryOptions
* @returns the list of package version create requests.
*/
static async getPackageVersionCreateRequests(connection, options) {
return (0, packageVersionCreateRequest_1.list)(connection, options);
}
/**
* Convenience function that will wait for a package version to be created.
*
* This function emits LifeCycle events, "enqueued", "in-progress", "success", "error" and "timed-out" to
* progress and current status. Events also carry a payload of type PackageVersionCreateRequestResult.
*
* @param createPackageVersionRequestId
* @param connection Connection to the org
* @param project SfProject to read/write aliases from
* @param polling frequency and timeout Durations to be used in polling
* */
static async pollCreateStatus(createPackageVersionRequestId, connection, project, polling) {
if (polling.timeout?.milliseconds <= 0) {
return this.getCreateStatus(createPackageVersionRequestId, connection);
}
let remainingWaitTime = polling.timeout;
const pollingClient = await core_1.PollingClient.create({
poll: async () => {
const report = await this.getCreateStatus(createPackageVersionRequestId, connection);
switch (report.Status) {
case Package2VersionStatus.queued:
await core_1.Lifecycle.getInstance().emit(interfaces_1.PackageVersionEvents.create.enqueued, { ...report, remainingWaitTime });
remainingWaitTime = kit_1.Duration.seconds(remainingWaitTime.seconds - polling.frequency.seconds);
return {
completed: false,
payload: report,
};
case Package2VersionStatus.inProgress:
case Package2VersionStatus.initializing:
case Package2VersionStatus.verifyingFeaturesAndSettings:
case Package2VersionStatus.verifyingDependencies:
case Package2VersionStatus.verifyingMetadata:
case Package2VersionStatus.finalizingPackageVersion:
await core_1.Lifecycle.getInstance().emit(interfaces_1.PackageVersionEvents.create.progress, {
...report,
remainingWaitTime,
});
remainingWaitTime = kit_1.Duration.seconds(remainingWaitTime.seconds - polling.frequency.seconds);
return {
completed: false,
payload: report,
};
case Package2VersionStatus.performingValidations:
case Package2VersionStatus.success: {
await core_1.Lifecycle.getInstance().emit(interfaces_1.PackageVersionEvents.create.success, report);
const packageVersion = new PackageVersion({
connection,
project,
idOrAlias: report.Package2VersionId,
});
await packageVersion.updateProjectWithPackageVersion(report);
return { completed: true, payload: report };
}
case Package2VersionStatus.error:
await core_1.Lifecycle.getInstance().emit(interfaces_1.PackageVersionEvents.create.error, report);
return { completed: true, payload: report };
}
},
frequency: polling.frequency,
timeout: polling.timeout,
});
try {
return await pollingClient.subscribe();
}
catch (err) {
const report = await this.getCreateStatus(createPackageVersionRequestId, connection);
await core_1.Lifecycle.getInstance().emit(interfaces_1.PackageVersionEvents.create['timed-out'], report);
if (err instanceof Error) {
throw (0, packageUtils_1.applyErrorAction)(err);
}
throw err;
}
}
/**
* Gets current state of a package version create request.
*
* @param createPackageRequestId
* @param connection
*/
static async getCreateVersionReport(createPackageRequestId, connection) {
return (0, packageVersionCreateRequestReport_1.getCreatePackageVersionCreateRequestReport)({
createPackageVersionRequestId: createPackageRequestId,
connection,
}).catch((err) => {
// TODO
// until package2 is GA, wrap perm-based errors w/ 'contact sfdc' action (REMOVE once package2 is GA'd)
throw (0, packageUtils_1.applyErrorAction)((0, packageUtils_1.massageErrorMessage)(err));
});
}
/**
* Convenience function that will wait for a package version to be created.
*
* This function emits LifeCycle events, "enqueued", "in-progress", "success", "error" and "timed-out" to
* progress and current status. Events also carry a payload of type PackageVersionCreateRequestResult.
*
* @param createPackageVersionRequestId
* @param project
* @param connection
* @param polling frequency and timeout Durations to be used in polling
* */
static async waitForCreateVersion(createPackageVersionRequestId, project, connection, polling) {
if (polling.timeout?.milliseconds <= 0) {
return PackageVersion.getCreateVersionReport(createPackageVersionRequestId, connection);
}
let remainingWaitTime = polling.timeout;
const pollingClient = await core_1.PollingClient.create({
poll: async () => {
const report = await this.getCreateVersionReport(createPackageVersionRequestId, connection);
switch (report.Status) {
case Package2VersionStatus.queued:
await core_1.Lifecycle.getInstance().emit(interfaces_1.PackageVersionEvents.create.enqueued, { ...report, remainingWaitTime });
remainingWaitTime = kit_1.Duration.seconds(remainingWaitTime.seconds - polling.frequency.seconds);
return {
completed: false,
payload: report,
};
case Package2VersionStatus.inProgress:
case Package2VersionStatus.initializing:
case Package2VersionStatus.verifyingFeaturesAndSettings:
case Package2VersionStatus.verifyingDependencies:
case Package2VersionStatus.verifyingMetadata:
case Package2VersionStatus.finalizingPackageVersion:
await core_1.Lifecycle.getInstance().emit(interfaces_1.PackageVersionEvents.create.progress, {
...report,
remainingWaitTime,
});
remainingWaitTime = kit_1.Duration.seconds(remainingWaitTime.seconds - polling.frequency.seconds);
return {
completed: false,
payload: report,
};
case Package2VersionStatus.performingValidations:
case Package2VersionStatus.success:
await core_1.Lifecycle.getInstance().emit(interfaces_1.PackageVersionEvents.create.success, report);
await new PackageVersion({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
idOrAlias: report.SubscriberPackageVersionId,
project,
connection,
}).updateProjectWithPackageVersion(report);
return { completed: true, payload: report };
case Package2VersionStatus.error:
await core_1.Lifecycle.getInstance().emit(interfaces_1.PackageVersionEvents.create.error, report);
return { completed: true, payload: report };
}
},
frequency: polling.frequency,
timeout: polling.timeout,
});
try {
return await pollingClient.subscribe();
}
catch (err) {
const report = await this.getCreateVersionReport(createPackageVersionRequestId, connection);
await core_1.Lifecycle.getInstance().emit(interfaces_1.PackageVersionEvents.create['timed-out'], report);
throw (0, packageUtils_1.applyErrorAction)(err);
}
}
/**
* Query the Package2Version SObject and return data with the provided type.
*
* NOTE: There is a limit of 2000 records that can be returned, otherwise
* a GACK might be thrown. If more than 2000 records are desired you should
* filter the query by date and aggregate all results.
*
* @param connection jsForce Connection to the org.
* @param options Package2Version query options
* @returns Results from querying the Package2Version SObject.
*/
static async queryPackage2Version(connection, options = {}) {
const fields = options.fields ?? PackageVersion.getPackage2VersionFields(connection);
const { whereClause, whereClauseItems } = options;
const orderBy = options.orderBy ?? 'ORDER BY LastModifiedDate DESC';
let query = `SELECT ${fields.toString()} FROM Package2Version`;
if (whereClause) {
query += ` ${whereClause} ${orderBy}`;
if (whereClauseItems) {
query += ' LIMIT 2000';
return (0, packageUtils_1.queryWithInConditionChunking)(query, whereClauseItems, '%IDS%', connection);
}
}
query += ' LIMIT 2000';
const result = await connection.tooling.query(query);
if (result?.totalSize === 2000) {
const warningMsg = messages.getMessage('maxPackage2VersionRecords');
await core_1.Lifecycle.getInstance().emitWarning(warningMsg);
}
return result.records ?? [];
}
static getPackage2VersionFields(connection) {
return parseInt(connection.getApiVersion(), 10) > 60
? exports.Package2VersionFields
: exports.Package2VersionFields.filter((field) => field !== 'ValidatedAsync');
}
/**
* Get the package version ID for this PackageVersion.
*
* @returns The PackageVersionId (05i).
*/
async getId() {
if (!this.data?.Id) {
await this.getData();
}
return this.data?.Id;
}
/**
* Get the subscriber package version ID for this PackageVersion.
*
* @returns The SubscriberPackageVersionId (04t).
*/
async getSubscriberId() {
if (!this.data?.SubscriberPackageVersionId) {
await this.getData();
}
return this.data?.SubscriberPackageVersionId;
}
/**
* Get the package Id for this PackageVersion.
*
* @returns The PackageId (0Ho).
*/
async getPackageId() {
if (!this.data?.Package2Id) {
await this.getData();
}
return this.data?.Package2Id;
}
/**
* Get the package type for this PackageVersion.
*
* @returns The PackageType (Managed, Unlocked).
*/
async getPackageType() {
if (!this.packageType) {
this.packageType = (await this.connection.singleRecordQuery(`select ContainerOptions from Package2 where Id = '${(await this.getPackageId()) ?? ''}' limit 1`, { tooling: true })).ContainerOptions;
}
return this.packageType;
}
/**
* Get the Package2Version SObject data for this PackageVersion.
*
* @param force force a refresh of the package version data.
* @returns Package2Version
*/
async getData(force = false) {
if (!this.data || force) {
// validate ID
if (this.id.startsWith('04t')) {
(0, packageUtils_1.validateId)(packageUtils_1.BY_LABEL.SUBSCRIBER_PACKAGE_VERSION_ID, this.id);
}
else if (this.id.startsWith('05i')) {
(0, packageUtils_1.validateId)(packageUtils_1.BY_LABEL.PACKAGE_VERSION_ID, this.id);
}
else {
throw messages.createError('errorInvalidPackageVersionId', [this.options.idOrAlias]);
}
const queryConfig = this.id.startsWith('05i')
? {
id: this.id,
clause: `Id = '${this.id}'`,
label1: packageUtils_1.BY_LABEL.PACKAGE_VERSION_ID.label,
label2: packageUtils_1.BY_LABEL.SUBSCRIBER_PACKAGE_VERSION_ID.label,
}
: {
id: this.id,
clause: `SubscriberPackageVersionId = '${this.id}'`,
label1: packageUtils_1.BY_LABEL.SUBSCRIBER_PACKAGE_VERSION_ID.label,
label2: packageUtils_1.BY_LABEL.PACKAGE_VERSION_ID.label,
};
let allFieldsArr = PackageVersion.getPackage2VersionFields(this.connection);
// Remove DeveloperUsePkgZip because the user may not have the DownloadPackageVersionZips user permission
allFieldsArr = allFieldsArr.filter((field) => field !== 'DeveloperUsePkgZip');
const allFields = allFieldsArr.toString();
const query = `SELECT ${allFields} FROM Package2Version WHERE ${queryConfig.clause} LIMIT 1`;
try {
this.data = await this.connection.singleRecordQuery(query, { tooling: true });
}
catch (err) {
throw messages.createError('errorInvalidIdNoMatchingVersionId', [queryConfig.label1, queryConfig.id, queryConfig.label2], undefined, err instanceof Error ? err : new Error(err));
}
}
return this.data;
}
/**
* Deletes this PackageVersion.
*/
async delete() {
return this.updateDeprecation(true);
}
/**
* Undeletes this PackageVersion.
*/
async undelete() {
return this.updateDeprecation(false);
}
/**
* Reports details about this PackageVersion.
*
* @param verbose Whether to get a detailed version of the report, at the expense of performance.
*/
async report(verbose = false) {
const packageVersionId = await this.getId();
if (!packageVersionId) {
throw messages.createError('errorInvalidPackageVersionId', [this.options.idOrAlias]);
}
const results = await (0, packageVersionReport_1.getPackageVersionReport)({
packageVersionId,
connection: this.connection,
project: this.project,
verbose,
}).catch((err) => {
// TODO
// until package2 is GA, wrap perm-based errors w/ 'contact sfdc' action (REMOVE once package2 is GA'd)
throw (0, packageUtils_1.applyErrorAction)((0, packageUtils_1.massageErrorMessage)(err));
});
return results[0];
}
/**
* Promotes this PackageVersion to released state.
*/
async promote() {
const id = await this.getId();
if (!id) {
throw messages.createError('errorInvalidPackageVersionId', [this.options.idOrAlias]);
}
return this.options.connection.tooling.update('Package2Version', { IsReleased: true, Id: id });
}
async update(options) {
const id = await this.getId();
if (!id) {
throw messages.createError('errorInvalidPackageVersionId', [this.options.idOrAlias]);
}
const request = Object.fromEntries(Object.entries({
Id: id,
InstallKey: options.InstallKey,
Name: options.VersionName,
Description: options.VersionDescription,
Branch: options.Branch,
Tag: options.Tag,
}).filter(([, value]) => value !== undefined));
const result = await this.connection.tooling.update('Package2Version', request);
if (!result.success) {
throw new Error(result.errors.join(', '));
}
// Use the 04t ID for the success message
const subscriberPackageVersionId = await this.getSubscriberId();
if (!subscriberPackageVersionId) {
throw messages.createError('errorNoSubscriberPackageVersionId');
}
result.id = subscriberPackageVersionId;
return result;
}
async updateDeprecation(isDeprecated) {
const id = await this.getId();
if (!id) {
throw messages.createError('errorInvalidPackageVersionId', [this.options.idOrAlias]);
}
// setup the request
const request = {
Id: id,
IsDeprecated: isDeprecated,
};
const updateResult = await this.connection.tooling.update('Package2Version', request);
if (!updateResult.success) {
throw (0, packageUtils_1.combineSaveErrors)('Package2', 'update', updateResult.errors);
}
const subscriberPackageVersionId = await this.getSubscriberId();
if (!subscriberPackageVersionId) {
throw messages.createError('errorNoSubscriberPackageVersionId');
}
updateResult.id = subscriberPackageVersionId;
return updateResult;
}
async updateProjectWithPackageVersion(results) {
if (!this.project) {
throw new core_1.SfError('errors.RequiresProject');
}
if (!kit_1.env.getBoolean('SF_PROJECT_AUTOUPDATE_DISABLE_FOR_PACKAGE_VERSION_CREATE')) {
// get the newly created package version from the server
const versionResult = (await this.connection.tooling.query(`SELECT Branch, MajorVersion, MinorVersion, PatchVersion, BuildNumber FROM Package2Version WHERE SubscriberPackageVersionId='${results.SubscriberPackageVersionId ?? ''}'`)).records[0];
const aliases = this.project.getAliasesFromPackageId(results.Package2Id);
if (aliases.length === 0) {
throw messages.createError('packageAliasNotFound', [results.Package2Id]);
}
const version = `${aliases[0]}@${versionResult.MajorVersion ?? 0}.${versionResult.MinorVersion ?? 0}.${versionResult.PatchVersion ?? 0}`;
const build = versionResult.BuildNumber ? `-${versionResult.BuildNumber}` : '';
const branch = versionResult.Branch ? `-${versionResult.Branch}` : '';
const originalPackageAliases = this.project.getSfProjectJson().get('packageAliases') ?? {};
const updatedPackageAliases = {
...originalPackageAliases,
...(results.SubscriberPackageVersionId
? // set packageAliases entry '<package>@<major>.<minor>.<patch>-<build>-<branch>: <result.subscriberPackageVersionId>'
{ [`${version}${build}${branch}`]: results.SubscriberPackageVersionId }
: {}),
};
this.project.getSfProjectJson().set('packageAliases', updatedPackageAliases);
await this.project.getSfProjectJson().write();
}
}
resolveId() {
let packageId = this.options.idOrAlias;
if (packageId.startsWith('04t') || packageId.startsWith('05i')) {
return packageId;
}
if (!this.options.project) {
throw messages.createError('errorInvalidPackageVersionIdNoProject', [this.options.idOrAlias]);
}
packageId = this.options.project.getPackageIdFromAlias(this.options.idOrAlias) ?? this.options.idOrAlias;
if (packageId === this.options.idOrAlias) {
throw messages.createError('packageAliasNotFound', [this.options.idOrAlias]);
}
// validate the resolved alias value from sfdx-project is a valid ID
if (packageId.startsWith('04t') || packageId.startsWith('05i')) {
return packageId;
}
else {
throw messages.createError('errorInvalidPackageVersionId', [this.options.idOrAlias]);
}
}
}
exports.PackageVersion = PackageVersion;
//# sourceMappingURL=packageVersion.js.map