diff --git a/command-snapshot.json b/command-snapshot.json index e20e55e2..285c5189 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -71,6 +71,50 @@ "flags": ["api-version", "flags-dir", "json", "loglevel", "target-dev-hub", "verbose"], "plugin": "@salesforce/plugin-packaging" }, + { + "alias": ["force:package:bundle:version:create"], + "command": "package:bundle:version:create", + "flagAliases": ["apiversion", "target-hub-org", "targetdevhubusername"], + "flagChars": ["b", "p", "v", "w"], + "flags": [ + "api-version", + "bundle", + "definition-file", + "flags-dir", + "json", + "loglevel", + "target-dev-hub", + "verbose", + "wait" + ], + "plugin": "@salesforce/plugin-packaging" + }, + { + "alias": ["force:package:bundle:version:create:list"], + "command": "package:bundle:version:create:list", + "flagAliases": ["apiversion", "createdlastdays", "target-hub-org", "targetdevhubusername"], + "flagChars": ["c", "s", "v"], + "flags": [ + "api-version", + "created-last-days", + "flags-dir", + "json", + "loglevel", + "show-conversions-only", + "status", + "target-dev-hub", + "verbose" + ], + "plugin": "@salesforce/plugin-packaging" + }, + { + "alias": ["force:package:bundle:version:create:report"], + "command": "package:bundle:version:create:report", + "flagAliases": ["apiversion", "packagecreaterequestid", "target-hub-org", "targetdevhubusername"], + "flagChars": ["i", "v"], + "flags": ["api-version", "flags-dir", "json", "loglevel", "package-create-request-id", "target-dev-hub"], + "plugin": "@salesforce/plugin-packaging" + }, { "alias": ["force:package:convert"], "command": "package:convert", diff --git a/messages/bundle_create.md b/messages/bundle_create.md index 3ffdb7a9..4eb03ee0 100644 --- a/messages/bundle_create.md +++ b/messages/bundle_create.md @@ -11,7 +11,7 @@ A bundle can be listed on AppExchange, installed, or upgraded as a single artifa Create a package bundle in the Dev Hub org; uses the Dev Hub org with the username devhub@example.com: -sf package bundle create --name “Your bundle name” --description "Your bundle description" --target-dev-hub devhub@example.com +sf package bundle create --name "Your bundle name" --description "Your bundle description" --target-dev-hub devhub@example.com # flags.name.summary @@ -20,3 +20,23 @@ Name of the package bundle. # flags.description.summary Description of the package bundle. + +# flags.wait.summary + +Number of minutes to wait for the bundle creation to complete. + +# flags.verbose.summary + +Display extended bundle creation detail. + +# requestInProgress + +Creating bundle. + +# bundleCreateWaitingStatus + +%d minutes remaining until timeout. Create bundle status: %s + +# bundleCreateFinalStatus + +Create bundle status: %s diff --git a/messages/bundle_version_create.md b/messages/bundle_version_create.md new file mode 100644 index 00000000..135cb7da --- /dev/null +++ b/messages/bundle_version_create.md @@ -0,0 +1,65 @@ +# summary + +Create a new package bundle version. + +# description + +Create a new version of a package bundle with the specified components. + +# examples + +Create a new version of a package bundle: + +sf package bundle version create -b MyBundle -p path/to/definition.json + +# flags.bundle.summary + +The name or ID of the package bundle to create a version for. + +# flags.definition-file.summary + +Path to the JSON file containing the list of components to include in the bundle version. + +# flags.wait.summary + +Number of minutes to wait for the command to complete before timing out. + +# flags.verbose.summary + +Show verbose output of the command execution. + +# flags.verbose.description + +Show detailed information about the bundle version creation process. + +# bundleVersionCreateWaitingStatus + +Waiting for bundle version creation to complete. %s minutes remaining. Current status: %s + +# bundleVersionCreateFinalStatus + +Package Bundle version creation completed with status: %s + +# multipleErrors + +The following errors occurred during bundle version creation:%s + +# InProgress + +Bundle version creation is %s. Use 'sf package bundle version create report -i %s' to check the status later. + +# requestInProgress + +Creating bundle version... + +# packageVersionCreateFinalStatus + +Package Bundle version creation completed with status: %s + +# packageVersionCreatePerformingValidations + +Performing validations on the package bundle version... + +# bundleVersionCreateSuccess + +Successfully created bundle version for bundle %s diff --git a/messages/bundle_version_create_list.md b/messages/bundle_version_create_list.md new file mode 100644 index 00000000..dd6f4a3e --- /dev/null +++ b/messages/bundle_version_create_list.md @@ -0,0 +1,73 @@ +# summary + +List package version creation requests. + +# description + +Shows the details of each request to create a package bundle version in the Dev Hub org. + +All filter parameters are applied using the AND logical operator (not OR). + +To get information about a specific request, run "<%= config.bin %> package bundle version create report" and supply the request ID. + +# flags.status.summary + +Status of the version creation request, used to filter the list. + +# flags.show-conversions-only.summary + +Filter the list output to display only converted package bundle version. + +# flags.verbose.summary + +Displays additional information at a slight performance cost, such as the version name and number for each package version create request. + +# flags.created-last-days.summary + +Number of days since the request was created, starting at 00:00:00 of first day to now. Use 0 for today. + +# examples + +- List all package bundle version creation requests in your default Dev Hub org: + + <%= config.bin %> <%= command.id %> + +- List package bundle version creation requests from the last 3 days in the Dev Hub org with username devhub@example.com: + + <%= config.bin %> <%= command.id %> --created-last-days 3 --target-dev-hub + +- List package bundle version creation requests with status Error: + + <%= config.bin %> <%= command.id %> --status Error + +- List package bundle version creation requests with status Queued: + + <%= config.bin %> <%= command.id %> --status Queued + +- List package bundle version creation requests with status Success that were created today: + + <%= config.bin %> <%= command.id %> --created-last-days 0 --status Success + +# id + +ID + +# status + +Status + +# package-id + +Package Bundle Id + +# packageVersionId + +Package Bundle Version Id + +# createdBy + +Created By + +# convertedFromVersionId + +Converted From Version Id diff --git a/messages/bundle_version_create_report.md b/messages/bundle_version_create_report.md new file mode 100644 index 00000000..bf8d68d0 --- /dev/null +++ b/messages/bundle_version_create_report.md @@ -0,0 +1,49 @@ +# summary + +Report on the status of a package bundle version creation request. + +# description + +Use this command to check the status of a package bundle version creation request. The command returns information about the request, including its current status and details about the package bundle version being created. + +# examples + +- Report on a package bundle version creation request: + + <%= config.bin %> <%= command.id %> --package-create-request-id 0Ho0x0000000000000 + +- Report on a package bundle version creation request using an alias: + + <%= config.bin %> force:package:bundle:version:create:report -i 0Ho0x0000000000000 + +# flags.package-create-request-id.summary + +The ID of the package bundle version creation request to report on. + +# id + +ID + +# status + +Status + +# package-bundle-id + +Package Bundle ID + +# package-bundle-version-id + +Package Bundle Version ID + +# version-name + +Version Name + +# created-date + +Created Date + +# created-by + +Created By diff --git a/schemas/package-bundle-create.json b/schemas/package-bundle-create.json deleted file mode 100644 index 2049f257..00000000 --- a/schemas/package-bundle-create.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/BundleCreate", - "definitions": { - "BundleCreate": { - "type": "object", - "properties": { - "Id": { - "type": "string" - } - }, - "required": ["Id"], - "additionalProperties": false - } - } -} diff --git a/schemas/package-bundle-version-create-list.json b/schemas/package-bundle-version-create-list.json new file mode 100644 index 00000000..1e26283c --- /dev/null +++ b/schemas/package-bundle-version-create-list.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/PackageBundleVersionCreateRequestResults", + "definitions": { + "PackageBundleVersionCreateRequestResults": { + "type": "array", + "items": { + "$ref": "#/definitions/BundleSObjects.PackageBundleVersionCreateRequestResult" + } + }, + "BundleSObjects.PackageBundleVersionCreateRequestResult": { + "type": "object", + "additionalProperties": false, + "properties": { + "Id": { + "type": "string" + }, + "PackageBundleVersionId": { + "type": "string" + }, + "RequestStatus": { + "$ref": "#/definitions/BundleSObjects.PkgBundleVersionCreateReqStatus" + }, + "CreatedDate": { + "type": "string" + }, + "CreatedById": { + "type": "string" + }, + "Error": { + "type": "array", + "items": { + "type": "string" + } + }, + "PackageBundleId": { + "type": "string" + }, + "VersionName": { + "type": "string" + }, + "MajorVersion": { + "type": "string" + }, + "MinorVersion": { + "type": "string" + }, + "BundleVersionComponents": { + "type": "string" + }, + "Ancestor": { + "type": ["string", "null"] + } + }, + "required": [ + "BundleVersionComponents", + "CreatedById", + "CreatedDate", + "Id", + "MajorVersion", + "MinorVersion", + "PackageBundleId", + "PackageBundleVersionId", + "RequestStatus", + "VersionName" + ] + }, + "BundleSObjects.PkgBundleVersionCreateReqStatus": { + "type": "string", + "enum": ["Queued", "Success", "Error"] + } + } +} diff --git a/schemas/package-bundle-version-create-report.json b/schemas/package-bundle-version-create-report.json new file mode 100644 index 00000000..59ac8e8c --- /dev/null +++ b/schemas/package-bundle-version-create-report.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/ReportCommandResult", + "definitions": { + "ReportCommandResult": { + "type": "array", + "items": { + "$ref": "#/definitions/BundleSObjects.PackageBundleVersionCreateRequestResult" + } + }, + "BundleSObjects.PackageBundleVersionCreateRequestResult": { + "type": "object", + "additionalProperties": false, + "properties": { + "Id": { + "type": "string" + }, + "PackageBundleVersionId": { + "type": "string" + }, + "RequestStatus": { + "$ref": "#/definitions/BundleSObjects.PkgBundleVersionCreateReqStatus" + }, + "CreatedDate": { + "type": "string" + }, + "CreatedById": { + "type": "string" + }, + "Error": { + "type": "array", + "items": { + "type": "string" + } + }, + "PackageBundleId": { + "type": "string" + }, + "VersionName": { + "type": "string" + }, + "MajorVersion": { + "type": "string" + }, + "MinorVersion": { + "type": "string" + }, + "BundleVersionComponents": { + "type": "string" + }, + "Ancestor": { + "type": ["string", "null"] + } + }, + "required": [ + "BundleVersionComponents", + "CreatedById", + "CreatedDate", + "Id", + "MajorVersion", + "MinorVersion", + "PackageBundleId", + "PackageBundleVersionId", + "RequestStatus", + "VersionName" + ] + }, + "BundleSObjects.PkgBundleVersionCreateReqStatus": { + "type": "string", + "enum": ["Queued", "Success", "Error"] + } + } +} diff --git a/schemas/package-bundle-version-create.json b/schemas/package-bundle-version-create.json new file mode 100644 index 00000000..9b047a8c --- /dev/null +++ b/schemas/package-bundle-version-create.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/BundleSObjects.PackageBundleVersionCreateRequestResult", + "definitions": { + "BundleSObjects.PackageBundleVersionCreateRequestResult": { + "type": "object", + "additionalProperties": false, + "properties": { + "Id": { + "type": "string" + }, + "PackageBundleVersionId": { + "type": "string" + }, + "RequestStatus": { + "$ref": "#/definitions/BundleSObjects.PkgBundleVersionCreateReqStatus" + }, + "CreatedDate": { + "type": "string" + }, + "CreatedById": { + "type": "string" + }, + "Error": { + "type": "array", + "items": { + "type": "string" + } + }, + "PackageBundleId": { + "type": "string" + }, + "VersionName": { + "type": "string" + }, + "MajorVersion": { + "type": "string" + }, + "MinorVersion": { + "type": "string" + }, + "BundleVersionComponents": { + "type": "string" + }, + "Ancestor": { + "type": ["string", "null"] + } + }, + "required": [ + "BundleVersionComponents", + "CreatedById", + "CreatedDate", + "Id", + "MajorVersion", + "MinorVersion", + "PackageBundleId", + "PackageBundleVersionId", + "RequestStatus", + "VersionName" + ] + }, + "BundleSObjects.PkgBundleVersionCreateReqStatus": { + "type": "string", + "enum": ["Queued", "Success", "Error"] + } + } +} diff --git a/src/commands/package/bundle/create.ts b/src/commands/package/bundle/create.ts index d7a837ae..63c45c3c 100644 --- a/src/commands/package/bundle/create.ts +++ b/src/commands/package/bundle/create.ts @@ -42,6 +42,7 @@ export class PackageBundlesCreate extends SfCommand { Description: flags.description ?? '', BundleName: flags.name, }; + this.spinner.start(`Creating Bundle with name ${options.BundleName}`); const result = await PackageBundle.create( flags['target-dev-hub'].getConnection(flags['api-version']), diff --git a/src/commands/package/bundle/version/create.ts b/src/commands/package/bundle/version/create.ts new file mode 100644 index 00000000..5a2b4105 --- /dev/null +++ b/src/commands/package/bundle/version/create.ts @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2025, 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 + */ + +import { Flags, loglevel, orgApiVersionFlagWithDeprecations, SfCommand } from '@salesforce/sf-plugins-core'; +import { + BundleVersionCreateOptions, + PackageVersionEvents, + BundleSObjects, + PackageBundleVersion, +} from '@salesforce/packaging'; +import { Messages, Lifecycle } from '@salesforce/core'; +import { camelCaseToTitleCase, Duration } from '@salesforce/kit'; +import { requiredHubFlag } from '../../../../utils/hubFlag.js'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +// TODO: Update messages +const messages = Messages.loadMessages('@salesforce/plugin-packaging', 'bundle_version_create'); +export type BundleVersionCreate = BundleSObjects.PackageBundleVersionCreateRequestResult; + +export class PackageBundlesCreate extends SfCommand { + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + public static readonly aliases = ['force:package:bundle:version:create']; + public static readonly requiresProject = true; + public static readonly flags = { + loglevel, + bundle: Flags.string({ + char: 'b', + summary: messages.getMessage('flags.bundle.summary'), + required: true, + }), + 'definition-file': Flags.string({ + char: 'p', + summary: messages.getMessage('flags.definition-file.summary'), + required: true, + }), + 'target-dev-hub': requiredHubFlag, + 'api-version': orgApiVersionFlagWithDeprecations, + wait: Flags.integer({ + char: 'w', + summary: messages.getMessage('flags.wait.summary'), + default: 0, + }), + verbose: Flags.boolean({ + summary: messages.getMessage('flags.verbose.summary'), + }), + }; + + public async run(): Promise { + const { flags } = await this.parse(PackageBundlesCreate); + const options: BundleVersionCreateOptions = { + connection: flags['target-dev-hub'].getConnection(flags['api-version']), + project: this.project!, + PackageBundle: flags.bundle, + BundleVersionComponentsPath: flags['definition-file'], + MajorVersion: '', + MinorVersion: '', + Ancestor: '', + }; + Lifecycle.getInstance().on( + PackageVersionEvents.create.progress, + // no async methods + // eslint-disable-next-line @typescript-eslint/require-await + async (data: BundleSObjects.PackageBundleVersionCreateRequestResult & { remainingWaitTime: Duration }) => { + if ( + data.RequestStatus !== BundleSObjects.PkgBundleVersionCreateReqStatus.success && + data.RequestStatus !== BundleSObjects.PkgBundleVersionCreateReqStatus.error + ) { + const status = messages.getMessage('bundleVersionCreateWaitingStatus', [ + data.remainingWaitTime.minutes, + data.RequestStatus, + ]); + if (flags.verbose) { + this.log(status); + } else { + this.spinner.status = status; + } + } + } + ); + + const result = await PackageBundleVersion.create({ + ...options, + polling: { + timeout: Duration.minutes(flags.wait), + frequency: Duration.seconds(5), + }, + }); + const finalStatusMsg = messages.getMessage('bundleVersionCreateFinalStatus', [result.RequestStatus]); + if (flags.verbose) { + this.log(finalStatusMsg); + } else { + this.spinner.stop(finalStatusMsg); + } + + switch (result.RequestStatus) { + case BundleSObjects.PkgBundleVersionCreateReqStatus.error: + throw messages.createError('multipleErrors', ['Unknown error']); + case BundleSObjects.PkgBundleVersionCreateReqStatus.success: + this.log(messages.getMessage('bundleVersionCreateSuccess', [result.Id])); + break; + default: + this.log(messages.getMessage('InProgress', [camelCaseToTitleCase(result.RequestStatus as string), result.Id])); + } + return result; + } +} diff --git a/src/commands/package/bundle/version/create/list.ts b/src/commands/package/bundle/version/create/list.ts new file mode 100644 index 00000000..b974d244 --- /dev/null +++ b/src/commands/package/bundle/version/create/list.ts @@ -0,0 +1,151 @@ +/* + * 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 + */ + +import { Flags, loglevel, orgApiVersionFlagWithDeprecations, SfCommand } from '@salesforce/sf-plugins-core'; +import { Connection, Messages } from '@salesforce/core'; +import { + PackageVersion, + getPackageVersionNumber, + BundleSObjects, + PackageBundleVersionCreate, +} from '@salesforce/packaging'; +import chalk from 'chalk'; +import { requiredHubFlag } from '../../../../../utils/hubFlag.js'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-packaging', 'bundle_version_create_list'); + +type Status = BundleSObjects.PkgBundleVersionCreateReqStatus; +export type PackageBundleVersionCreateRequestResults = BundleSObjects.PackageBundleVersionCreateRequestResult[]; + +export class PackageBundleVersionCreateListCommand extends SfCommand { + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + public static readonly deprecateAliases = true; + public static readonly aliases = ['force:package:bundle:version:create:list']; + public static readonly flags = { + loglevel, + 'target-dev-hub': requiredHubFlag, + 'api-version': orgApiVersionFlagWithDeprecations, + 'created-last-days': Flags.integer({ + char: 'c', + deprecateAliases: true, + aliases: ['createdlastdays'], + summary: messages.getMessage('flags.created-last-days.summary'), + }), + status: Flags.custom({ + options: [ + BundleSObjects.PkgBundleVersionCreateReqStatus.queued, + BundleSObjects.PkgBundleVersionCreateReqStatus.success, + BundleSObjects.PkgBundleVersionCreateReqStatus.error, + ], + })({ + char: 's', + summary: messages.getMessage('flags.status.summary'), + }), + 'show-conversions-only': Flags.boolean({ + summary: messages.getMessage('flags.show-conversions-only.summary'), + }), + verbose: Flags.boolean({ + summary: messages.getMessage('flags.verbose.summary'), + }), + }; + + private connection!: Connection; + + public async run(): Promise { + const { flags } = await this.parse(PackageBundleVersionCreateListCommand); + this.connection = flags['target-dev-hub'].getConnection(flags['api-version']); + let results = await PackageBundleVersionCreate.getCreateStatuses( + this.connection, + flags.status, + flags['created-last-days'] + ); + + if (results.length === 0) { + this.warn('No results found'); + } else { + if (flags.verbose) { + try { + results = await this.fetchVerboseData(results); + } catch (err) { + const errMsg = typeof err === 'string' ? err : err instanceof Error ? err.message : 'unknown error'; + this.warn(`error when retrieving verbose data (package name and version) due to: ${errMsg}`); + } + } + + const data = results.map((r) => ({ + Id: r.Id ?? 'N/A', + Status: r.RequestStatus ?? 'Unknown', + 'Package Bundle Id': r.PackageBundleId ?? 'N/A', + 'Package Bundle Version Id': r.PackageBundleVersionId ?? 'N/A', + 'Created Date': r.CreatedDate ?? 'N/A', + 'Created By': r.CreatedById ?? 'N/A', + ...(flags.verbose + ? { + 'Version Name': r.VersionName ?? 'N/A', + 'Major Version': r.MajorVersion ?? 'N/A', + 'Minor Version': r.MinorVersion ?? 'N/A', + } + : {}), + })); + + this.table({ + data, + overflow: 'wrap', + title: chalk.blue(`Package Bundle Version Create Requests [${results.length}]`), + }); + } + + return results; + } + + // Queries Package2Version for the name and version number of the packages and adds that data + // to the results. + private async fetchVerboseData( + results: PackageBundleVersionCreateRequestResults + ): Promise { + type VersionDataMap = { + [id: string]: { name: string; version: string }; + }; + + // Filter out any results without a valid PackageBundleVersionId + const validResults = results.filter((r) => r?.PackageBundleVersionId); + if (validResults.length === 0) { + return results; + } + + // Query for the version name and number data + const versionData = await PackageVersion.queryPackage2Version(this.connection, { + fields: ['Id', 'Name', 'MajorVersion', 'MinorVersion', 'PatchVersion', 'BuildNumber'], + whereClause: "WHERE Id IN ('%IDS%')", + whereClauseItems: validResults.map((pvcrr) => pvcrr.PackageBundleVersionId), + }); + + const vDataMap: VersionDataMap = {}; + versionData.forEach((vData) => { + if (vData?.Id) { + const version = getPackageVersionNumber(vData, true); + vDataMap[vData.Id] = { name: vData.Name, version }; + } + }); + + return results.map((pvcrr) => { + if (pvcrr?.PackageBundleVersionId && vDataMap[pvcrr.PackageBundleVersionId]) { + return { + ...pvcrr, + ...{ + VersionName: vDataMap[pvcrr.PackageBundleVersionId].name, + VersionNumber: vDataMap[pvcrr.PackageBundleVersionId].version, + }, + }; + } + return pvcrr; + }); + } +} diff --git a/src/commands/package/bundle/version/create/report.ts b/src/commands/package/bundle/version/create/report.ts new file mode 100644 index 00000000..a4a4c0f0 --- /dev/null +++ b/src/commands/package/bundle/version/create/report.ts @@ -0,0 +1,85 @@ +/* + * 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 + */ + +import { Flags, loglevel, orgApiVersionFlagWithDeprecations, SfCommand } from '@salesforce/sf-plugins-core'; +import { Messages } from '@salesforce/core'; +import { BundleSObjects, PackageBundleVersionCreate } from '@salesforce/packaging'; +import chalk from 'chalk'; +import { camelCaseToTitleCase } from '@salesforce/kit'; +import { requiredHubFlag } from '../../../../../utils/hubFlag.js'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-packaging', 'bundle_version_create_report'); + +export type ReportCommandResult = BundleSObjects.PackageBundleVersionCreateRequestResult[]; + +export class PackageBundleVersionCreateReportCommand extends SfCommand { + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + public static readonly deprecateAliases = true; + public static readonly aliases = ['force:package:bundle:version:create:report']; + public static readonly flags = { + loglevel, + 'target-dev-hub': requiredHubFlag, + 'api-version': orgApiVersionFlagWithDeprecations, + // eslint-disable-next-line sf-plugin/id-flag-suggestions + 'package-create-request-id': Flags.salesforceId({ + length: 'both', + deprecateAliases: true, + aliases: ['packagecreaterequestid'], + char: 'i', + summary: messages.getMessage('flags.package-create-request-id.summary'), + required: true, + }), + }; + + public async run(): Promise { + const { flags } = await this.parse(PackageBundleVersionCreateReportCommand); + const result = await PackageBundleVersionCreate.getCreateStatus( + flags['package-create-request-id'], + flags['target-dev-hub'].getConnection(flags['api-version']) + ); + this.display(result); + return [result]; + } + + private display(record: BundleSObjects.PackageBundleVersionCreateRequestResult): void { + const data = [ + { + name: messages.getMessage('id'), + value: record.Id, + }, + { + name: messages.getMessage('status'), + value: camelCaseToTitleCase(record.RequestStatus), + }, + { + name: messages.getMessage('package-bundle-id'), + value: record.PackageBundleId, + }, + { + name: messages.getMessage('package-bundle-version-id'), + value: record.PackageBundleVersionId, + }, + { + name: messages.getMessage('version-name'), + value: record.VersionName, + }, + { + name: messages.getMessage('created-date'), + value: record.CreatedDate, + }, + { + name: messages.getMessage('created-by'), + value: record.CreatedById, + }, + ]; + + this.table({ data, title: chalk.blue('Package Bundle Version Create Request') }); + } +} diff --git a/test/commands/bundle/bundleVersionCreateList.test.ts b/test/commands/bundle/bundleVersionCreateList.test.ts new file mode 100644 index 00000000..3e7ecd30 --- /dev/null +++ b/test/commands/bundle/bundleVersionCreateList.test.ts @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025, 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 + */ +import { Config } from '@oclif/core'; +import { TestContext, MockTestOrgData } from '@salesforce/core/testSetup'; +import { expect } from 'chai'; +import { PackageBundleVersionCreate } from '@salesforce/packaging'; +import { stubSfCommandUx } from '@salesforce/sf-plugins-core'; +import sinon from 'sinon'; +import { PackageBundleVersionCreateListCommand } from '../../../src/commands/package/bundle/version/create/list.js'; + +describe('bundle:version:create:list - tests', () => { + const $$ = new TestContext(); + const testOrg = new MockTestOrgData(); + let sfCommandStubs: ReturnType; + let getCreateStatusesStub: sinon.SinonStub; + const config = new Config({ root: import.meta.url }); + + beforeEach(async () => { + await $$.stubAuths(testOrg); + await config.load(); + sfCommandStubs = stubSfCommandUx($$.SANDBOX); + + getCreateStatusesStub = $$.SANDBOX.stub(PackageBundleVersionCreate, 'getCreateStatuses'); + }); + + afterEach(() => { + $$.restore(); + }); + + it('should list bundle version create requests', async () => { + const cmd = new PackageBundleVersionCreateListCommand(['--target-dev-hub', testOrg.username], config); + + const mockResults = [ + { + Id: 'test-id-1', + RequestStatus: 'Success', + PackageBundleId: 'bundle-id-1', + PackageBundleVersionId: 'version-id-1', + CreatedDate: '2023-01-01T00:00:00Z', + CreatedById: 'user-id-1', + }, + ]; + + getCreateStatusesStub.resolves(mockResults); + + await cmd.run(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.table.calledOnce).to.be.true; + expect(getCreateStatusesStub.calledOnce).to.be.true; + }); + + it('should show warning when no results found', async () => { + const cmd = new PackageBundleVersionCreateListCommand(['--target-dev-hub', testOrg.username], config); + + getCreateStatusesStub.resolves([]); + + await cmd.run(); + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.warn.calledOnce).to.be.true; + expect(sfCommandStubs.warn.firstCall.args[0]).to.equal('No results found'); + }); + + it('should throw error when target-dev-hub flag is missing', async () => { + const cmd = new PackageBundleVersionCreateListCommand([], config); + + try { + await cmd.run(); + expect.fail('Expected error was not thrown'); + } catch (error) { + expect((error as Error).message).to.include('No default dev hub found'); + } + }); +}); diff --git a/test/commands/bundle/bundleVersionCreateReport.test.ts b/test/commands/bundle/bundleVersionCreateReport.test.ts new file mode 100644 index 00000000..e7d1abfa --- /dev/null +++ b/test/commands/bundle/bundleVersionCreateReport.test.ts @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2025, 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 + */ +import { Config } from '@oclif/core'; +import { TestContext, MockTestOrgData } from '@salesforce/core/testSetup'; +import { expect } from 'chai'; +import { PackageBundleVersionCreate, BundleSObjects } from '@salesforce/packaging'; +import { stubSfCommandUx } from '@salesforce/sf-plugins-core'; +import sinon from 'sinon'; +import { PackageBundleVersionCreateReportCommand } from '../../../src/commands/package/bundle/version/create/report.js'; + +describe('force:package:bundle:version:create:report - tests', () => { + const $$ = new TestContext(); + const testOrg = new MockTestOrgData(); + let sfCommandStubs: ReturnType; + let getCreateStatusStub: sinon.SinonStub; + const config = new Config({ root: import.meta.url }); + + beforeEach(async () => { + await $$.stubAuths(testOrg); + await config.load(); + sfCommandStubs = stubSfCommandUx($$.SANDBOX); + + getCreateStatusStub = $$.SANDBOX.stub(PackageBundleVersionCreate, 'getCreateStatus'); + }); + + afterEach(() => { + $$.restore(); + }); + + it('should report on a package bundle version creation request', async () => { + const requestId = '0Ho0x0000000000000'; + const mockResult: BundleSObjects.PackageBundleVersionCreateRequestResult = { + Id: requestId, + RequestStatus: BundleSObjects.PkgBundleVersionCreateReqStatus.queued, + PackageBundleId: '0Ho0x0000000000001', + PackageBundleVersionId: '0Ho0x0000000000002', + VersionName: '1.0.0', + MajorVersion: '1', + MinorVersion: '0', + BundleVersionComponents: '[{"packageId": "0Ho0x0000000000001", "versionNumber": "1.0.0"}]', + Error: [], + CreatedDate: '2025-01-01T00:00:00.000+0000', + CreatedById: '0050x0000000000001', + Ancestor: null, + }; + + getCreateStatusStub.resolves(mockResult); + + const cmd = new PackageBundleVersionCreateReportCommand( + ['--package-create-request-id', requestId, '--target-dev-hub', testOrg.username], + config + ); + + await cmd.run(); + + expect(getCreateStatusStub.calledOnce).to.be.true; + expect(getCreateStatusStub.firstCall.args[0]).to.equal(requestId); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.table.calledOnce).to.be.true; + }); + + it('should report on a package bundle version creation request using alias flag', async () => { + const requestId = '0Ho0x0000000000000'; + const mockResult: BundleSObjects.PackageBundleVersionCreateRequestResult = { + Id: requestId, + RequestStatus: BundleSObjects.PkgBundleVersionCreateReqStatus.success, + PackageBundleId: '0Ho0x0000000000001', + PackageBundleVersionId: '0Ho0x0000000000002', + VersionName: '1.0.0', + MajorVersion: '1', + MinorVersion: '0', + BundleVersionComponents: '[{"packageId": "0Ho0x0000000000001", "versionNumber": "1.0.0"}]', + Error: [], + CreatedDate: '2025-01-01T00:00:00.000+0000', + CreatedById: '0050x0000000000001', + Ancestor: null, + }; + + getCreateStatusStub.resolves(mockResult); + + const cmd = new PackageBundleVersionCreateReportCommand( + ['-i', requestId, '--target-dev-hub', testOrg.username], + config + ); + + await cmd.run(); + + expect(getCreateStatusStub.calledOnce).to.be.true; + expect(getCreateStatusStub.firstCall.args[0]).to.equal(requestId); + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.table.calledOnce).to.be.true; + }); + + it('should throw error when package-create-request-id flag is missing', async () => { + const cmd = new PackageBundleVersionCreateReportCommand(['--target-dev-hub', testOrg.username], config); + + try { + await cmd.run(); + expect.fail('Expected error was not thrown'); + } catch (error) { + expect((error as Error).message).to.include('Missing required flag'); + } + }); + + it('should throw error when target-dev-hub flag is missing', async () => { + const requestId = '0Ho0x0000000000000'; + const cmd = new PackageBundleVersionCreateReportCommand(['--package-create-request-id', requestId], config); + + try { + await cmd.run(); + expect.fail('Expected error was not thrown'); + } catch (error) { + expect((error as Error).message).to.include('No default dev hub found'); + } + }); + + it('should handle API errors gracefully', async () => { + const requestId = '0Ho0x0000000000000'; + const errorMessage = 'Package bundle version creation request not found'; + + getCreateStatusStub.rejects(new Error(errorMessage)); + + const cmd = new PackageBundleVersionCreateReportCommand( + ['--package-create-request-id', requestId, '--target-dev-hub', testOrg.username], + config + ); + + try { + await cmd.run(); + expect.fail('Expected error was not thrown'); + } catch (error) { + expect((error as Error).message).to.include(errorMessage); + } + }); +}); diff --git a/test/commands/bundle/packageBundleVersionCreate.test.ts b/test/commands/bundle/packageBundleVersionCreate.test.ts new file mode 100644 index 00000000..4ac8d278 --- /dev/null +++ b/test/commands/bundle/packageBundleVersionCreate.test.ts @@ -0,0 +1,230 @@ +/* + * 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 + */ +import { MockTestOrgData, TestContext } from '@salesforce/core/testSetup'; +import { Config } from '@oclif/core'; +import { assert, expect } from 'chai'; +import { PackageBundleVersion, BundleSObjects } from '@salesforce/packaging'; +import sinon from 'sinon'; +import { SfCommand } from '@salesforce/sf-plugins-core'; +import { PackageBundlesCreate } from '../../../src/commands/package/bundle/version/create.js'; + +const pkgBundleVersionCreateErrorResult: BundleSObjects.PackageBundleVersionCreateRequestResult = { + Id: '08c3i000000fylXXXX', + RequestStatus: BundleSObjects.PkgBundleVersionCreateReqStatus.error, + PackageBundleId: '0Ho3i000000TNHXXXX', + PackageBundleVersionId: '', + VersionName: 'TestBundle@1.0', + MajorVersion: '1', + MinorVersion: '0', + BundleVersionComponents: '[]', + Error: [ + 'PropertyController: Invalid type: Schema.Property__c', + 'SampleDataController: Invalid type: Schema.Property__c', + 'SampleDataController: Invalid type: Schema.Broker__c', + ], + CreatedDate: '2022-11-03 09:21', + CreatedById: '0053i000001ZIyXXXX', + Ancestor: null, +}; + +const pkgBundleVersionCreateSuccessResult: BundleSObjects.PackageBundleVersionCreateRequestResult = { + Id: '08c3i000000fylgAAA', + RequestStatus: BundleSObjects.PkgBundleVersionCreateReqStatus.success, + PackageBundleId: '0Ho3i000000TNHYCA4', + PackageBundleVersionId: '05i3i000000fxw1AAA', + VersionName: 'TestBundle@1.0', + MajorVersion: '1', + MinorVersion: '0', + BundleVersionComponents: '[{"packageId": "0Ho3i000000TNHYCA4", "versionNumber": "1.0.0"}]', + Error: [], + CreatedDate: '2022-11-03 09:46', + CreatedById: '0053i000001ZIyGAAW', + Ancestor: null, +}; + +const pkgBundleVersionCreateQueuedResult: BundleSObjects.PackageBundleVersionCreateRequestResult = { + Id: '08c3i000000fylgBBB', + RequestStatus: BundleSObjects.PkgBundleVersionCreateReqStatus.queued, + PackageBundleId: '0Ho3i000000TNHYCA4', + PackageBundleVersionId: '', + VersionName: 'TestBundle@1.1', + MajorVersion: '1', + MinorVersion: '1', + BundleVersionComponents: '[{"packageId": "0Ho3i000000TNHYCA4", "versionNumber": "1.1.0"}]', + Error: [], + CreatedDate: '2022-11-03 10:00', + CreatedById: '0053i000001ZIyGAAW', + Ancestor: null, +}; + +describe('package:bundle:version:create - tests', () => { + const $$ = new TestContext(); + const testOrg = new MockTestOrgData(); + let createStub = $$.SANDBOX.stub(PackageBundleVersion, 'create'); + const config = new Config({ root: import.meta.url }); + + // stubs + let logStub: sinon.SinonStub; + let warnStub: sinon.SinonStub; + + const stubSpinner = (cmd: PackageBundlesCreate) => { + $$.SANDBOX.stub(cmd.spinner, 'start'); + $$.SANDBOX.stub(cmd.spinner, 'stop'); + }; + + before(async () => { + await $$.stubAuths(testOrg); + await config.load(); + }); + + beforeEach(async () => { + logStub = $$.SANDBOX.stub(SfCommand.prototype, 'log'); + warnStub = $$.SANDBOX.stub(SfCommand.prototype, 'warn'); + }); + + afterEach(() => { + $$.restore(); + }); + + describe('package:bundle:version:create', () => { + it('should create a new package bundle version', async () => { + createStub.resolves(pkgBundleVersionCreateSuccessResult); + + const cmd = new PackageBundlesCreate( + ['-b', 'TestBundle', '-p', 'path/to/definition.json', '--target-dev-hub', 'test@hub.org'], + config + ); + stubSpinner(cmd); + const res = await cmd.run(); + expect(res).to.deep.equal({ + Id: '08c3i000000fylgAAA', + RequestStatus: 'Success', + PackageBundleId: '0Ho3i000000TNHYCA4', + PackageBundleVersionId: '05i3i000000fxw1AAA', + VersionName: 'TestBundle@1.0', + MajorVersion: '1', + MinorVersion: '0', + BundleVersionComponents: '[{"packageId": "0Ho3i000000TNHYCA4", "versionNumber": "1.0.0"}]', + Error: [], + CreatedDate: '2022-11-03 09:46', + CreatedById: '0053i000001ZIyGAAW', + Ancestor: null, + }); + expect(warnStub.callCount).to.equal(0); + expect(logStub.callCount).to.equal(1); + expect(logStub.args[0]).to.deep.equal(['Successfully created bundle version for bundle 08c3i000000fylgAAA']); + }); + + it('should create a new package bundle version with wait option', async () => { + createStub = $$.SANDBOX.stub(PackageBundleVersion, 'create'); + createStub.resolves(pkgBundleVersionCreateSuccessResult); + + const cmd = new PackageBundlesCreate( + ['-b', 'TestBundle', '-p', 'path/to/definition.json', '-w', '10', '--target-dev-hub', 'test@hub.org'], + config + ); + stubSpinner(cmd); + const res = await cmd.run(); + expect(res).to.deep.equal({ + Id: '08c3i000000fylgAAA', + RequestStatus: 'Success', + PackageBundleId: '0Ho3i000000TNHYCA4', + PackageBundleVersionId: '05i3i000000fxw1AAA', + VersionName: 'TestBundle@1.0', + MajorVersion: '1', + MinorVersion: '0', + BundleVersionComponents: '[{"packageId": "0Ho3i000000TNHYCA4", "versionNumber": "1.0.0"}]', + Error: [], + CreatedDate: '2022-11-03 09:46', + CreatedById: '0053i000001ZIyGAAW', + Ancestor: null, + }); + expect(warnStub.callCount).to.equal(0); + expect(logStub.callCount).to.equal(1); + expect(logStub.args[0]).to.deep.equal(['Successfully created bundle version for bundle 08c3i000000fylgAAA']); + }); + + it('should create a new package bundle version with verbose option', async () => { + createStub = $$.SANDBOX.stub(PackageBundleVersion, 'create'); + createStub.resolves(pkgBundleVersionCreateSuccessResult); + + const cmd = new PackageBundlesCreate( + ['-b', 'TestBundle', '-p', 'path/to/definition.json', '--verbose', '--target-dev-hub', 'test@hub.org'], + config + ); + stubSpinner(cmd); + const res = await cmd.run(); + expect(res).to.deep.equal({ + Id: '08c3i000000fylgAAA', + RequestStatus: 'Success', + PackageBundleId: '0Ho3i000000TNHYCA4', + PackageBundleVersionId: '05i3i000000fxw1AAA', + VersionName: 'TestBundle@1.0', + MajorVersion: '1', + MinorVersion: '0', + BundleVersionComponents: '[{"packageId": "0Ho3i000000TNHYCA4", "versionNumber": "1.0.0"}]', + Error: [], + CreatedDate: '2022-11-03 09:46', + CreatedById: '0053i000001ZIyGAAW', + Ancestor: null, + }); + expect(warnStub.callCount).to.equal(0); + expect(logStub.callCount).to.equal(2); + expect(logStub.args[0]).to.deep.equal(['Package Bundle version creation completed with status: Success']); + expect(logStub.args[1]).to.deep.equal(['Successfully created bundle version for bundle 08c3i000000fylgAAA']); + }); + + it('should handle queued status', async () => { + createStub = $$.SANDBOX.stub(PackageBundleVersion, 'create'); + createStub.resolves(pkgBundleVersionCreateQueuedResult); + + const cmd = new PackageBundlesCreate( + ['-b', 'TestBundle', '-p', 'path/to/definition.json', '--target-dev-hub', 'test@hub.org'], + config + ); + stubSpinner(cmd); + const res = await cmd.run(); + expect(res).to.deep.equal({ + Id: '08c3i000000fylgBBB', + RequestStatus: 'Queued', + PackageBundleId: '0Ho3i000000TNHYCA4', + PackageBundleVersionId: '', + VersionName: 'TestBundle@1.1', + MajorVersion: '1', + MinorVersion: '1', + BundleVersionComponents: '[{"packageId": "0Ho3i000000TNHYCA4", "versionNumber": "1.1.0"}]', + Error: [], + CreatedDate: '2022-11-03 10:00', + CreatedById: '0053i000001ZIyGAAW', + Ancestor: null, + }); + expect(warnStub.callCount).to.equal(0); + expect(logStub.callCount).to.equal(1); + expect(logStub.args[0]).to.deep.equal([ + "Bundle version creation is Queued. Use 'sf package bundle version create report -i 08c3i000000fylgBBB' to check the status later.", + ]); + }); + + it('should report multiple errors', async () => { + createStub = $$.SANDBOX.stub(PackageBundleVersion, 'create'); + createStub.resolves(pkgBundleVersionCreateErrorResult); + try { + const cmd = new PackageBundlesCreate( + ['-b', 'TestBundle', '-p', 'path/to/definition.json', '--target-dev-hub', 'test@hub.org'], + config + ); + stubSpinner(cmd); + await cmd.run(); + assert.fail('the above should throw multiple errors'); + } catch (e) { + expect((e as Error).message).to.equal( + 'The following errors occurred during bundle version creation:Unknown error' + ); + } + }); + }); +});