UNPKG

@salesforce/packaging

Version:

Packaging library for the Salesforce packaging platform

564 lines 25.5 kB
"use strict"; /* * Copyright (c) 2020, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ Object.defineProperty(exports, "__esModule", { value: true }); exports.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