From 72e25f83c1c3f64e609576f0c0e966e0c8d3b413 Mon Sep 17 00:00:00 2001 From: Bao Tran Date: Tue, 9 Dec 2025 11:28:01 -0500 Subject: [PATCH 01/11] feat: add bundle uninstall command Made-with: Cursor --- messages/bundle_uninstall.md | 46 ++++++ src/commands/package/bundle/uninstall.ts | 142 +++++++++++++++++++ test/commands/bundle/bundleUninstall.test.ts | 131 +++++++++++++++++ 3 files changed, 319 insertions(+) create mode 100644 messages/bundle_uninstall.md create mode 100644 src/commands/package/bundle/uninstall.ts create mode 100644 test/commands/bundle/bundleUninstall.test.ts diff --git a/messages/bundle_uninstall.md b/messages/bundle_uninstall.md new file mode 100644 index 00000000..4342005c --- /dev/null +++ b/messages/bundle_uninstall.md @@ -0,0 +1,46 @@ +# summary + +Uninstall a package bundle version from a target org. + +# description + +Provide the package bundle version ID or alias and the target org to start an uninstall request. Optionally wait for the uninstall to complete. + +# examples + +- Uninstall a bundle version by ID from a specified org: + + <%= config.bin %> <%= command.id %> --bundle 1Q8... --target-org me@example.com + +- Uninstall a bundle version by alias from your default org and wait up to 10 minutes for completion: + + <%= config.bin %> <%= command.id %> --bundle MyBundle@1.2 -w 10 + +# flags.bundle.summary + +ID (starts with 1Q8) or alias of the package bundle version to uninstall. + +# flags.wait.summary + +Number of minutes to wait for the uninstall request to complete. + +# flags.verbose.summary + +Show additional progress while waiting for uninstall to finish. + +# bundleUninstallWaitingStatus + +Waiting %s more minutes for bundle uninstall. Current status: %s + +# bundleUninstallError + +Encountered errors uninstalling the bundle! %s + +# bundleUninstallInProgress + +Bundle uninstall is currently %s. Request Id: %s. Target org: %s + +# bundleUninstallSuccess + +Successfully uninstalled bundle version %s from %s + diff --git a/src/commands/package/bundle/uninstall.ts b/src/commands/package/bundle/uninstall.ts new file mode 100644 index 00000000..7517ae4c --- /dev/null +++ b/src/commands/package/bundle/uninstall.ts @@ -0,0 +1,142 @@ +/* + * Copyright 2026, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + Flags, + loglevel, + orgApiVersionFlagWithDeprecations, + requiredOrgFlagWithDeprecations, + SfCommand, +} from '@salesforce/sf-plugins-core'; +import { Lifecycle, Messages } from '@salesforce/core'; +import { BundleSObjects, PackageBundleUninstall } from '@salesforce/packaging'; +import { camelCaseToTitleCase, Duration } from '@salesforce/kit'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-packaging', 'bundle_uninstall'); + +export type BundleUninstallResult = BundleSObjects.PkgBundleVerUninstallReqResult; + +export class PackageBundleUninstallCommand extends SfCommand { + public static readonly hidden = true; + public static state = 'beta'; + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + public static readonly requiresProject = true; + public static readonly flags = { + loglevel, + bundle: Flags.string({ + char: 'b', + summary: messages.getMessage('flags.bundle.summary'), + required: true, + }), + 'target-org': requiredOrgFlagWithDeprecations, + '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(PackageBundleUninstallCommand); + + const targetOrg = flags['target-org']; + const connection = targetOrg.getConnection(flags['api-version']); + + // Set up lifecycle events for progress tracking + Lifecycle.getInstance().on( + 'bundle-uninstall-progress', + // no async methods + // eslint-disable-next-line @typescript-eslint/require-await + async (data: BundleSObjects.PkgBundleVerUninstallReqResult & { remainingWaitTime: Duration }) => { + if ( + data.UninstallStatus !== BundleSObjects.PkgBundleVersionUninstallReqStatus.success && + data.UninstallStatus !== BundleSObjects.PkgBundleVersionUninstallReqStatus.error + ) { + const status = messages.getMessage('bundleUninstallWaitingStatus', [ + data.remainingWaitTime.minutes, + data.UninstallStatus, + ]); + if (flags.verbose) { + this.log(status); + } else { + this.spinner.status = status; + } + } + } + ); + + const pollingOptions = + flags.wait && flags.wait > 0 + ? { polling: { timeout: Duration.minutes(flags.wait), frequency: Duration.seconds(5) } } + : undefined; + + const isSpinnerRunning = flags.wait && flags.wait > 0 && !flags.verbose; + if (isSpinnerRunning) { + this.spinner.start('Uninstalling bundle...'); + } + + let result: BundleSObjects.PkgBundleVerUninstallReqResult; + try { + result = await PackageBundleUninstall.uninstallBundle(connection, this.project!, { + connection, + project: this.project!, + PackageBundleVersion: flags.bundle, + ...pollingOptions, + }); + } catch (error) { + if (isSpinnerRunning) { + this.spinner.stop(); + } + throw error; + } + + if (isSpinnerRunning) { + this.spinner.stop(); + } + + switch (result.UninstallStatus) { + case BundleSObjects.PkgBundleVersionUninstallReqStatus.error: { + const errorText = + result.ValidationError ?? + `Bundle uninstall failed. Request Id: ${result.Id} Target org: ${targetOrg.getUsername() ?? 'target org'}`; + throw messages.createError('bundleUninstallError', [errorText]); + } + case BundleSObjects.PkgBundleVersionUninstallReqStatus.success: { + const bundleVersionId = result.PackageBundleVersionId ?? flags.bundle; + this.log(messages.getMessage('bundleUninstallSuccess', [bundleVersionId, targetOrg.getUsername() ?? ''])); + break; + } + default: + this.log( + messages.getMessage('bundleUninstallInProgress', [ + camelCaseToTitleCase(result.UninstallStatus as string), + result.Id, + targetOrg.getUsername() ?? '', + ]) + ); + } + + return result; + } +} + diff --git a/test/commands/bundle/bundleUninstall.test.ts b/test/commands/bundle/bundleUninstall.test.ts new file mode 100644 index 00000000..01a068de --- /dev/null +++ b/test/commands/bundle/bundleUninstall.test.ts @@ -0,0 +1,131 @@ +/* + * Copyright 2026, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { MockTestOrgData, TestContext } from '@salesforce/core/testSetup'; +import { Config } from '@oclif/core'; +import { assert, expect } from 'chai'; +import { BundleSObjects, PackageBundleUninstall } from '@salesforce/packaging'; +import sinon from 'sinon'; +import { SfCommand } from '@salesforce/sf-plugins-core'; +import { PackageBundleUninstallCommand } from '../../../src/commands/package/bundle/uninstall.js'; + +describe('package:bundle:uninstall - tests', () => { + const $$ = new TestContext(); + const testOrg = new MockTestOrgData(); + const config = new Config({ root: import.meta.url }); + + let uninstallStub = $$.SANDBOX.stub(PackageBundleUninstall, 'uninstallBundle'); + let logStub: sinon.SinonStub; + let warnStub: sinon.SinonStub; + + const stubSpinner = (cmd: PackageBundleUninstallCommand) => { + $$.SANDBOX.stub(cmd.spinner, 'start'); + $$.SANDBOX.stub(cmd.spinner, 'stop'); + $$.SANDBOX.stub(cmd.spinner, 'status').value(''); + }; + + before(async () => { + await $$.stubAuths(testOrg); + await config.load(); + }); + + beforeEach(async () => { + logStub = $$.SANDBOX.stub(SfCommand.prototype, 'log'); + warnStub = $$.SANDBOX.stub(SfCommand.prototype, 'warn'); + }); + + afterEach(() => { + $$.restore(); + }); + + it('uninstalls a bundle version successfully', async () => { + const successResult: BundleSObjects.PkgBundleVerUninstallReqResult = { + Id: '1aF000000000001', + UninstallStatus: BundleSObjects.PkgBundleVersionUninstallReqStatus.success, + PackageBundleVersionId: '1Q8000000000001', + InstalledPkgBundleVersionId: '08c000000000001', + ValidationError: '', + CreatedDate: '2025-01-01T00:00:00Z', + CreatedById: '005000000000001', + }; + uninstallStub.resolves(successResult); + + const cmd = new PackageBundleUninstallCommand( + ['-b', 'MyBundle@1.0', '--target-org', testOrg.username], + config + ); + stubSpinner(cmd); + const res = await cmd.run(); + + expect(res).to.deep.equal(successResult); + expect(warnStub.callCount).to.equal(0); + expect(logStub.callCount).to.equal(1); + expect(logStub.args[0]).to.deep.equal([ + `Successfully uninstalled bundle version 1Q8000000000001 from ${testOrg.username}`, + ]); + }); + + it('logs in-progress uninstall status', async () => { + const queuedResult: BundleSObjects.PkgBundleVerUninstallReqResult = { + Id: '1aF000000000002', + UninstallStatus: BundleSObjects.PkgBundleVersionUninstallReqStatus.queued, + PackageBundleVersionId: '1Q8000000000002', + InstalledPkgBundleVersionId: '08c000000000002', + ValidationError: '', + CreatedDate: '2025-01-01T00:00:00Z', + CreatedById: '005000000000002', + }; + uninstallStub = $$.SANDBOX.stub(PackageBundleUninstall, 'uninstallBundle'); + uninstallStub.resolves(queuedResult); + + const cmd = new PackageBundleUninstallCommand(['-b', '1Q8000000000002', '--target-org', testOrg.username], config); + stubSpinner(cmd); + const res = await cmd.run(); + + expect(res).to.deep.equal(queuedResult); + expect(logStub.callCount).to.equal(1); + const msg = String(logStub.args[0][0]).replace(/\r\n/g, '\n'); + expect(msg).to.equal( + `Bundle uninstall is currently Queued. Request Id: 1aF000000000002. Target org: ${testOrg.username}` + ); + }); + + it('throws when uninstall status is error', async () => { + const errorResult: BundleSObjects.PkgBundleVerUninstallReqResult = { + Id: '1aF000000000003', + UninstallStatus: BundleSObjects.PkgBundleVersionUninstallReqStatus.error, + PackageBundleVersionId: '1Q8000000000003', + InstalledPkgBundleVersionId: '08c000000000003', + ValidationError: 'Failed uninstall', + CreatedDate: '2025-01-01T00:00:00Z', + CreatedById: '005000000000003', + }; + uninstallStub = $$.SANDBOX.stub(PackageBundleUninstall, 'uninstallBundle'); + uninstallStub.resolves(errorResult); + + try { + const cmd = new PackageBundleUninstallCommand( + ['-b', '1Q8000000000003', '--target-org', testOrg.username], + config + ); + stubSpinner(cmd); + await cmd.run(); + assert.fail('the above should throw an error'); + } catch (e) { + expect((e as Error).message).to.equal('Encountered errors uninstalling the bundle! Failed uninstall'); + } + }); +}); + From c9f2c01e34b4debfb56b0ab860abfac945df1398 Mon Sep 17 00:00:00 2001 From: Bao Tran Date: Mon, 1 Dec 2025 13:40:30 -0500 Subject: [PATCH 02/11] feat: add command to list installed package bundles Introduces the command to display all installed package bundles in the target org, including details about each bundle and its associated packages. Updates the command schema and adds relevant messages for user guidance. --- command-snapshot.json | 8 + messages/bundle_installed_list.md | 21 +++ schemas/package-bundle-installed-list.json | 91 ++++++++++++ src/commands/package/bundle/installed/list.ts | 139 ++++++++++++++++++ 4 files changed, 259 insertions(+) create mode 100644 messages/bundle_installed_list.md create mode 100644 schemas/package-bundle-installed-list.json create mode 100644 src/commands/package/bundle/installed/list.ts diff --git a/command-snapshot.json b/command-snapshot.json index d5a270c9..fecd1dd2 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -95,6 +95,14 @@ "flags": ["api-version", "flags-dir", "json", "loglevel", "package-install-request-id", "target-org", "verbose"], "plugin": "@salesforce/plugin-packaging" }, + { + "alias": [], + "command": "package:bundle:installed:list", + "flagAliases": ["apiversion", "targetusername", "u"], + "flagChars": ["o"], + "flags": ["api-version", "flags-dir", "json", "loglevel", "target-org"], + "plugin": "@salesforce/plugin-packaging" + }, { "alias": [], "command": "package:bundle:list", diff --git a/messages/bundle_installed_list.md b/messages/bundle_installed_list.md new file mode 100644 index 00000000..2ae6aed4 --- /dev/null +++ b/messages/bundle_installed_list.md @@ -0,0 +1,21 @@ +# summary + +List all installed package bundles in the target org. + +# description + +Displays information about all package bundles currently installed in the target org, including the bundle details and the associated packages with their expected and actual versions. + +# examples + +- List all installed package bundles in your default org: + + <%= config.bin %> <%= command.id %> + +- List all installed package bundles in a specific org: + + <%= config.bin %> <%= command.id %> --target-org myorg@example.com + +# flags.target-org.summary + +The org to list installed package bundles from. diff --git a/schemas/package-bundle-installed-list.json b/schemas/package-bundle-installed-list.json new file mode 100644 index 00000000..75b67364 --- /dev/null +++ b/schemas/package-bundle-installed-list.json @@ -0,0 +1,91 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/PackageBundleInstalledListResults", + "definitions": { + "PackageBundleInstalledListResults": { + "type": "array", + "items": { + "$ref": "#/definitions/BundleSObjects.InstalledPackageBundleVersion" + } + }, + "BundleSObjects.InstalledPackageBundleVersion": { + "type": "object", + "properties": { + "Id": { + "type": "string" + }, + "BundleName": { + "type": "string" + }, + "BundleId": { + "type": "string" + }, + "BundleVersionId": { + "type": "string" + }, + "BundleVersionName": { + "type": "string" + }, + "MajorVersion": { + "type": "number" + }, + "MinorVersion": { + "type": "number" + }, + "Description": { + "type": "string" + }, + "InstalledDate": { + "type": "string" + }, + "LastUpgradedDate": { + "type": "string" + }, + "Components": { + "type": "array", + "items": { + "$ref": "#/definitions/BundleSObjects.InstalledPackageBundleVersionComponent" + } + } + }, + "required": [ + "Id", + "BundleName", + "BundleId", + "BundleVersionId", + "BundleVersionName", + "MajorVersion", + "MinorVersion", + "Description", + "InstalledDate", + "LastUpgradedDate", + "Components" + ], + "additionalProperties": false + }, + "BundleSObjects.InstalledPackageBundleVersionComponent": { + "type": "object", + "properties": { + "ExpectedPackageName": { + "type": "string" + }, + "ExpectedPackageVersionNumber": { + "type": "string" + }, + "ActualPackageName": { + "type": "string" + }, + "ActualPackageVersionNumber": { + "type": "string" + } + }, + "required": [ + "ExpectedPackageName", + "ExpectedPackageVersionNumber", + "ActualPackageName", + "ActualPackageVersionNumber" + ], + "additionalProperties": false + } + } +} diff --git a/src/commands/package/bundle/installed/list.ts b/src/commands/package/bundle/installed/list.ts new file mode 100644 index 00000000..d27707e5 --- /dev/null +++ b/src/commands/package/bundle/installed/list.ts @@ -0,0 +1,139 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + loglevel, + orgApiVersionFlagWithDeprecations, + requiredOrgFlagWithDeprecations, + SfCommand, +} from '@salesforce/sf-plugins-core'; +import { Connection, Messages } from '@salesforce/core'; +import { BundleSObjects, PackageBundleInstalledList } from '@salesforce/packaging'; +import chalk from 'chalk'; + +Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); +const messages = Messages.loadMessages('@salesforce/plugin-packaging', 'bundle_installed_list'); + +export type PackageBundleInstalledListResults = BundleSObjects.InstalledPackageBundleVersion[]; + +export class PackageBundleInstalledListCommand extends SfCommand { + public static readonly hidden = true; + public static state = 'beta'; + public static readonly summary = messages.getMessage('summary'); + public static readonly description = messages.getMessage('description'); + public static readonly examples = messages.getMessages('examples'); + public static readonly flags = { + loglevel, + 'target-org': requiredOrgFlagWithDeprecations, + 'api-version': orgApiVersionFlagWithDeprecations, + }; + + private targetOrgConnection!: Connection; + + private static formatDate(dateString: string): string { + try { + const date = new Date(dateString); + return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }); + } catch { + return dateString; + } + } + + public async run(): Promise { + const { flags } = await this.parse(PackageBundleInstalledListCommand); + this.targetOrgConnection = flags['target-org'].getConnection(flags['api-version']); + + const results = await PackageBundleInstalledList.getInstalledBundles(this.targetOrgConnection); + + if (results.length === 0) { + this.warn('No installed package bundles found in the target org'); + return results; + } + + // Display each bundle with its own tables + for (const bundle of results) { + // Bundle info table + const bundleInfo = [ + { + Key: 'Bundle Name', + Value: bundle.BundleName, + }, + { + Key: 'Bundle ID', + Value: bundle.BundleId, + }, + { + Key: 'Bundle Version ID', + Value: bundle.BundleVersionId, + }, + { + Key: 'Bundle Version Name', + Value: bundle.BundleVersionName, + }, + { + Key: 'Major Version', + Value: bundle.MajorVersion.toString(), + }, + { + Key: 'Minor Version', + Value: bundle.MinorVersion.toString(), + }, + { + Key: 'Description', + Value: bundle.Description || 'N/A', + }, + { + Key: 'Installed Date', + Value: PackageBundleInstalledListCommand.formatDate(bundle.InstalledDate), + }, + { + Key: 'Last Upgraded Date', + Value: PackageBundleInstalledListCommand.formatDate(bundle.LastUpgradedDate), + }, + ]; + + this.table({ + data: bundleInfo, + overflow: 'wrap', + }); + + // Add spacing + this.log(''); + + // Associated packages table + if (bundle.Components && bundle.Components.length > 0) { + this.log(chalk.blue('Associated Packages')); + const packageData = bundle.Components.map((comp: BundleSObjects.InstalledPackageBundleVersionComponent) => ({ + 'Expected Package': comp.ExpectedPackageName, + 'Expected Package Version Number': comp.ExpectedPackageVersionNumber, + 'Actual Package': comp.ActualPackageName, + 'Actual Package Version Number': comp.ActualPackageVersionNumber, + })); + + this.table({ + data: packageData, + overflow: 'wrap', + }); + } + + // Add spacing between bundles + this.log(''); + this.log(''); + } + + return results; + } +} From 340c0adfafec23acfefc5be4e4d7c322bca6f05d Mon Sep 17 00:00:00 2001 From: Bao Tran Date: Mon, 1 Dec 2025 14:40:17 -0500 Subject: [PATCH 03/11] test: bundle installed list plugin test --- .../bundle/bundleInstalledList.test.ts | 263 ++++++++++++++++++ 1 file changed, 263 insertions(+) create mode 100644 test/commands/bundle/bundleInstalledList.test.ts diff --git a/test/commands/bundle/bundleInstalledList.test.ts b/test/commands/bundle/bundleInstalledList.test.ts new file mode 100644 index 00000000..75d0450b --- /dev/null +++ b/test/commands/bundle/bundleInstalledList.test.ts @@ -0,0 +1,263 @@ +/* + * Copyright 2025, Salesforce, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Config } from '@oclif/core'; +import { TestContext, MockTestOrgData } from '@salesforce/core/testSetup'; +import { expect } from 'chai'; +import { PackageBundleInstalledList } from '@salesforce/packaging'; +import { stubSfCommandUx } from '@salesforce/sf-plugins-core'; +import sinon from 'sinon'; +import { PackageBundleInstalledListCommand } from '../../../src/commands/package/bundle/installed/list.js'; + +describe('package:bundle:installed:list - tests', () => { + const $$ = new TestContext(); + const testOrg = new MockTestOrgData(); + let sfCommandStubs: ReturnType; + let getInstalledBundlesStub: sinon.SinonStub; + const config = new Config({ root: import.meta.url }); + + beforeEach(async () => { + await $$.stubAuths(testOrg); + await config.load(); + sfCommandStubs = stubSfCommandUx($$.SANDBOX); + + getInstalledBundlesStub = $$.SANDBOX.stub(PackageBundleInstalledList, 'getInstalledBundles'); + }); + + afterEach(() => { + $$.restore(); + }); + + it('should list installed bundles with components', async () => { + const mockInstalledBundles = [ + { + Id: '1aE000000000001', + BundleName: 'TestBundle', + BundleId: '0Kz000000000001', + BundleVersionId: '05i000000000001', + BundleVersionName: 'ver 1.0', + MajorVersion: 1, + MinorVersion: 0, + Description: 'Test Description', + InstalledDate: '2024-01-01T00:00:00.000+0000', + LastUpgradedDate: '2024-01-01T00:00:00.000+0000', + Components: [ + { + ExpectedPackageName: 'TestPackage', + ExpectedPackageVersionNumber: '1.0.0.1', + ActualPackageName: 'TestPackage', + ActualPackageVersionNumber: '1.0.0.1', + }, + ], + }, + ]; + + const cmd = new PackageBundleInstalledListCommand(['-o', testOrg.username], config); + + getInstalledBundlesStub.resolves(mockInstalledBundles); + + await cmd.run(); + + expect(getInstalledBundlesStub.calledOnce).to.be.true; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.table.called).to.be.true; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.table.callCount).to.equal(2); // One for bundle info, one for components + }); + + it('should list multiple installed bundles', async () => { + const mockInstalledBundles = [ + { + Id: '1aE000000000001', + BundleName: 'TestBundle1', + BundleId: '0Kz000000000001', + BundleVersionId: '05i000000000001', + BundleVersionName: 'ver 1.0', + MajorVersion: 1, + MinorVersion: 0, + Description: '', + InstalledDate: '2024-01-01T00:00:00.000+0000', + LastUpgradedDate: '2024-01-01T00:00:00.000+0000', + Components: [ + { + ExpectedPackageName: 'Package1', + ExpectedPackageVersionNumber: '1.0.0.1', + ActualPackageName: 'Package1', + ActualPackageVersionNumber: '1.0.0.1', + }, + ], + }, + { + Id: '1aE000000000002', + BundleName: 'TestBundle2', + BundleId: '0Kz000000000002', + BundleVersionId: '05i000000000002', + BundleVersionName: 'ver 2.0', + MajorVersion: 2, + MinorVersion: 0, + Description: '', + InstalledDate: '2024-01-02T00:00:00.000+0000', + LastUpgradedDate: '2024-01-02T00:00:00.000+0000', + Components: [ + { + ExpectedPackageName: 'Package2', + ExpectedPackageVersionNumber: '2.0.0.1', + ActualPackageName: 'Package2', + ActualPackageVersionNumber: '2.0.0.1', + }, + ], + }, + ]; + + const cmd = new PackageBundleInstalledListCommand(['-o', testOrg.username], config); + + getInstalledBundlesStub.resolves(mockInstalledBundles); + + await cmd.run(); + + expect(getInstalledBundlesStub.calledOnce).to.be.true; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.table.called).to.be.true; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.table.callCount).to.equal(4); // Two bundles × (info + components) + }); + + it('should handle bundles with version mismatches', async () => { + const mockInstalledBundles = [ + { + Id: '1aE000000000001', + BundleName: 'TestBundle', + BundleId: '0Kz000000000001', + BundleVersionId: '05i000000000001', + BundleVersionName: 'ver 1.0', + MajorVersion: 1, + MinorVersion: 0, + Description: '', + InstalledDate: '2024-01-01T00:00:00.000+0000', + LastUpgradedDate: '2024-01-01T00:00:00.000+0000', + Components: [ + { + ExpectedPackageName: 'TestPackage', + ExpectedPackageVersionNumber: '1.0.0.1', + ActualPackageName: 'TestPackage', + ActualPackageVersionNumber: '2.0.0.1', + }, + ], + }, + ]; + + const cmd = new PackageBundleInstalledListCommand(['-o', testOrg.username], config); + + getInstalledBundlesStub.resolves(mockInstalledBundles); + + await cmd.run(); + + expect(getInstalledBundlesStub.calledOnce).to.be.true; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.table.called).to.be.true; + }); + + it('should handle bundles with uninstalled packages', async () => { + const mockInstalledBundles = [ + { + Id: '1aE000000000001', + BundleName: 'TestBundle', + BundleId: '0Kz000000000001', + BundleVersionId: '05i000000000001', + BundleVersionName: 'ver 1.0', + MajorVersion: 1, + MinorVersion: 0, + Description: '', + InstalledDate: '2024-01-01T00:00:00.000+0000', + LastUpgradedDate: '2024-01-01T00:00:00.000+0000', + Components: [ + { + ExpectedPackageName: 'TestPackage', + ExpectedPackageVersionNumber: '1.0.0.1', + ActualPackageName: 'Uninstalled', + ActualPackageVersionNumber: 'N/A', + }, + ], + }, + ]; + + const cmd = new PackageBundleInstalledListCommand(['-o', testOrg.username], config); + + getInstalledBundlesStub.resolves(mockInstalledBundles); + + await cmd.run(); + + expect(getInstalledBundlesStub.calledOnce).to.be.true; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.table.called).to.be.true; + }); + + it('should show warning when no installed bundles found', async () => { + const cmd = new PackageBundleInstalledListCommand(['-o', testOrg.username], config); + + getInstalledBundlesStub.resolves([]); + + await cmd.run(); + + expect(getInstalledBundlesStub.calledOnce).to.be.true; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.warn.calledOnce).to.be.true; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.warn.firstCall.args[0]).to.include('No installed package bundles found'); + }); + + it('should handle bundles without components', async () => { + const mockInstalledBundles = [ + { + Id: '1aE000000000001', + BundleName: 'TestBundle', + BundleId: '0Kz000000000001', + BundleVersionId: '05i000000000001', + BundleVersionName: 'ver 1.0', + MajorVersion: 1, + MinorVersion: 0, + Description: '', + InstalledDate: '2024-01-01T00:00:00.000+0000', + LastUpgradedDate: '2024-01-01T00:00:00.000+0000', + Components: [], + }, + ]; + + const cmd = new PackageBundleInstalledListCommand(['-o', testOrg.username], config); + + getInstalledBundlesStub.resolves(mockInstalledBundles); + + await cmd.run(); + + expect(getInstalledBundlesStub.calledOnce).to.be.true; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.table.called).to.be.true; + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + expect(sfCommandStubs.table.callCount).to.equal(1); // Only bundle info, no components table + }); + + it('should throw error when target org flag is missing', async () => { + const cmd = new PackageBundleInstalledListCommand([], config); + + getInstalledBundlesStub.resolves([]); + + try { + await cmd.run(); + expect.fail('Expected error was not thrown'); + } catch (error) { + expect((error as Error).message).to.include('No default environment found'); + } + }); +}); From 0b9a2877454b32d5cc0ed03eb81154605e42974f Mon Sep 17 00:00:00 2001 From: Bao Tran Date: Mon, 15 Dec 2025 15:34:11 -0500 Subject: [PATCH 04/11] chore: remove old bundle install list command --- command-snapshot.json | 16 +-- messages/bundle_install_list.md | 69 ------------ src/commands/package/bundle/install/list.ts | 101 ----------------- .../commands/bundle/bundleInstallList.test.ts | 105 ------------------ 4 files changed, 8 insertions(+), 283 deletions(-) delete mode 100644 messages/bundle_install_list.md delete mode 100644 src/commands/package/bundle/install/list.ts delete mode 100644 test/commands/bundle/bundleInstallList.test.ts diff --git a/command-snapshot.json b/command-snapshot.json index fecd1dd2..36aa6c30 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -79,14 +79,6 @@ "flags": ["api-version", "bundle", "dev-hub-org", "flags-dir", "json", "loglevel", "target-org", "verbose", "wait"], "plugin": "@salesforce/plugin-packaging" }, - { - "alias": [], - "command": "package:bundle:install:list", - "flagAliases": ["apiversion", "targetusername", "u"], - "flagChars": ["c", "o", "s"], - "flags": ["api-version", "created-last-days", "flags-dir", "json", "loglevel", "status", "target-org", "verbose"], - "plugin": "@salesforce/plugin-packaging" - }, { "alias": [], "command": "package:bundle:install:report", @@ -111,6 +103,14 @@ "flags": ["api-version", "flags-dir", "json", "loglevel", "target-dev-hub", "verbose"], "plugin": "@salesforce/plugin-packaging" }, + { + "alias": [], + "command": "package:bundle:uninstall", + "flagAliases": ["apiversion", "targetusername", "u"], + "flagChars": ["b", "o", "w"], + "flags": ["api-version", "bundle", "flags-dir", "json", "loglevel", "target-org", "verbose", "wait"], + "plugin": "@salesforce/plugin-packaging" + }, { "alias": [], "command": "package:bundle:version:create", diff --git a/messages/bundle_install_list.md b/messages/bundle_install_list.md deleted file mode 100644 index b7d9144b..00000000 --- a/messages/bundle_install_list.md +++ /dev/null @@ -1,69 +0,0 @@ -# summary - -List package bundle installation requests. - -# description - -Shows the details of each request to install a package bundle version in the target 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 install report" and enter the request ID. - -# flags.status.summary - -Status of the installation request, used to filter the list. - -# flags.verbose.summary - -Display additional information, such as the validation text for each package bundle installation request. - -# flags.created-last-days.summary - -Number of days since the request was created, starting at 0. Use 0 for today. - -# examples - -- List all package bundle installation requests in your default Dev Hub org: - - <%= config.bin %> <%= command.id %> - -- List package bundle installation 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 devhub@example.com - -- List package bundle installation requests with the Error status: - - <%= config.bin %> <%= command.id %> --status Error - -- List package bundle installation requests with the Queued status: - - <%= config.bin %> <%= command.id %> --status Queued - -- List package bundle install requests from today with the Success status: - - <%= config.bin %> <%= command.id %> --created-last-days 0 --status Success - -# id - -ID - -# status - -Status - -# package-bundle-version-id - -Package Bundle Version ID - -# development-organization - -Development Organization - -# created-by - -Created By - -# validation-error - -Validation Error diff --git a/src/commands/package/bundle/install/list.ts b/src/commands/package/bundle/install/list.ts deleted file mode 100644 index 679d279d..00000000 --- a/src/commands/package/bundle/install/list.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2026, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - Flags, - loglevel, - orgApiVersionFlagWithDeprecations, - requiredOrgFlagWithDeprecations, - SfCommand, -} from '@salesforce/sf-plugins-core'; -import { Connection, Messages } from '@salesforce/core'; -import { BundleSObjects, PackageBundleInstall } from '@salesforce/packaging'; -import chalk from 'chalk'; - -Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); -const messages = Messages.loadMessages('@salesforce/plugin-packaging', 'bundle_install_list'); - -type Status = BundleSObjects.PkgBundleVersionInstallReqStatus; -export type PackageBundleInstallRequestResults = BundleSObjects.PkgBundleVersionInstallReqResult[]; - -export class PackageBundleInstallListCommand extends SfCommand { - public static readonly hidden = true; - public static state = 'beta'; - public static readonly summary = messages.getMessage('summary'); - public static readonly description = messages.getMessage('description'); - public static readonly examples = messages.getMessages('examples'); - public static readonly flags = { - loglevel, - 'target-org': requiredOrgFlagWithDeprecations, - 'api-version': orgApiVersionFlagWithDeprecations, - 'created-last-days': Flags.integer({ - char: 'c', - summary: messages.getMessage('flags.created-last-days.summary'), - }), - status: Flags.custom({ - options: [ - BundleSObjects.PkgBundleVersionInstallReqStatus.queued, - BundleSObjects.PkgBundleVersionInstallReqStatus.inProgress, - BundleSObjects.PkgBundleVersionInstallReqStatus.success, - BundleSObjects.PkgBundleVersionInstallReqStatus.error, - ], - })({ - char: 's', - summary: messages.getMessage('flags.status.summary'), - }), - verbose: Flags.boolean({ - summary: messages.getMessage('flags.verbose.summary'), - }), - }; - - private connection!: Connection; - - public async run(): Promise { - const { flags } = await this.parse(PackageBundleInstallListCommand); - this.connection = flags['target-org'].getConnection(flags['api-version']); - const results = await PackageBundleInstall.getInstallStatuses( - this.connection, - flags.status, - flags['created-last-days'] - ); - - if (results.length === 0) { - this.warn('No results found'); - } else { - const data = results.map((r) => ({ - Id: r.Id ?? 'N/A', - Status: r.InstallStatus ?? 'Unknown', - 'Package Bundle Version Id': r.PackageBundleVersionId ?? 'N/A', - 'Development Organization': r.DevelopmentOrganization ?? 'N/A', - 'Created Date': r.CreatedDate ?? 'N/A', - 'Created By': r.CreatedById ?? 'N/A', - ...(flags.verbose - ? { - 'Validation Error': r.ValidationError ?? 'N/A', - } - : {}), - })); - - this.table({ - data, - overflow: 'wrap', - title: chalk.blue(`Package Bundle Install Requests [${results.length}]`), - }); - } - - return results; - } -} diff --git a/test/commands/bundle/bundleInstallList.test.ts b/test/commands/bundle/bundleInstallList.test.ts deleted file mode 100644 index 11c759b1..00000000 --- a/test/commands/bundle/bundleInstallList.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2026, Salesforce, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { Config } from '@oclif/core'; -import { TestContext, MockTestOrgData } from '@salesforce/core/testSetup'; -import { expect } from 'chai'; -import { PackageBundleInstall, BundleSObjects } from '@salesforce/packaging'; -import { stubSfCommandUx } from '@salesforce/sf-plugins-core'; -import sinon from 'sinon'; -import { PackageBundleInstallListCommand } from '../../../src/commands/package/bundle/install/list.js'; - -describe('package:bundle:install:list - tests', () => { - const $$ = new TestContext(); - const testOrg = new MockTestOrgData(); - let sfCommandStubs: ReturnType; - let getInstallStatusesStub: sinon.SinonStub; - const config = new Config({ root: import.meta.url }); - - beforeEach(async () => { - await $$.stubAuths(testOrg); - await config.load(); - sfCommandStubs = stubSfCommandUx($$.SANDBOX); - - getInstallStatusesStub = $$.SANDBOX.stub(PackageBundleInstall, 'getInstallStatuses'); - }); - - afterEach(() => { - $$.restore(); - }); - - it('should list bundle install requests', async () => { - const cmd = new PackageBundleInstallListCommand(['--target-org', testOrg.username], config); - - const mockResults: BundleSObjects.PkgBundleVersionInstallReqResult[] = [ - { - Id: 'test-id-1', - InstallStatus: BundleSObjects.PkgBundleVersionInstallReqStatus.success, - PackageBundleVersionId: 'bundle-version-id-1', - DevelopmentOrganization: 'dev-org-1', - CreatedDate: '2023-01-01T00:00:00Z', - CreatedById: 'user-id-1', - ValidationError: '', - Error: [], - }, - ]; - - getInstallStatusesStub.resolves(mockResults); - - await cmd.run(); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - expect(sfCommandStubs.table.calledOnce).to.be.true; - expect(getInstallStatusesStub.calledOnce).to.be.true; - }); - - it('should show warning when no results found', async () => { - const cmd = new PackageBundleInstallListCommand(['--target-org', testOrg.username], config); - - getInstallStatusesStub.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'); - }); - - // This test does very little to test the verbose command except make sure that it is there. - it('should handle verbose flag', async () => { - const cmd = new PackageBundleInstallListCommand(['--target-org', testOrg.username, '--verbose'], config); - - const mockResults: BundleSObjects.PkgBundleVersionInstallReqResult[] = [ - { - Id: 'test-id-1', - InstallStatus: BundleSObjects.PkgBundleVersionInstallReqStatus.error, - PackageBundleVersionId: 'bundle-version-id-1', - DevelopmentOrganization: 'dev-org-1', - CreatedDate: '2023-01-01T00:00:00Z', - CreatedById: 'user-id-1', - ValidationError: 'Installation failed', - Error: ['Test error'], - }, - ]; - - getInstallStatusesStub.resolves(mockResults); - - await cmd.run(); - - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - expect(sfCommandStubs.table.calledOnce).to.be.true; - expect(getInstallStatusesStub.calledOnce).to.be.true; - }); -}); From f3108a2590227fcd4c02fcb2673ae67c816d4af5 Mon Sep 17 00:00:00 2001 From: "b.tran" Date: Tue, 24 Mar 2026 10:59:36 -0400 Subject: [PATCH 05/11] chore: fix copyright headers to 2026 Made-with: Cursor --- src/commands/package/bundle/installed/list.ts | 2 +- test/commands/bundle/bundleInstalledList.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/package/bundle/installed/list.ts b/src/commands/package/bundle/installed/list.ts index d27707e5..790fa86f 100644 --- a/src/commands/package/bundle/installed/list.ts +++ b/src/commands/package/bundle/installed/list.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025, Salesforce, Inc. + * Copyright 2026, Salesforce, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/test/commands/bundle/bundleInstalledList.test.ts b/test/commands/bundle/bundleInstalledList.test.ts index 75d0450b..ed23354e 100644 --- a/test/commands/bundle/bundleInstalledList.test.ts +++ b/test/commands/bundle/bundleInstalledList.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2025, Salesforce, Inc. + * Copyright 2026, Salesforce, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From f606f8933de54b97bcfab9d50b4634300e39f25f Mon Sep 17 00:00:00 2001 From: "b.tran" Date: Fri, 27 Mar 2026 13:35:27 -0400 Subject: [PATCH 06/11] chore: update dependencies and fix INSTALL_URL_BASE usage - Bumped @salesforce/core from ^8.27.0 to ^8.27.1. - Updated AWS SDK dependencies to their latest versions. - Changed INSTALL_URL_BASE usage from .toString() to .href in multiple files for consistency. --- package.json | 2 +- src/commands/package/convert.ts | 2 +- src/commands/package/version/create.ts | 4 ++-- src/commands/package/version/create/report.ts | 2 +- src/commands/package/version/list.ts | 2 +- yarn.lock | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 3736b79b..6611216b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "o11yUploadEndpoint": "https://794testsite.my.site.com/byolwr/webruntime/log/metrics", "dependencies": { "@oclif/core": "^4", - "@salesforce/core": "^8.27.0", + "@salesforce/core": "^8.27.1", "@salesforce/kit": "^3.2.6", "@salesforce/packaging": "^4.21.10", "@salesforce/sf-plugins-core": "^12.2.6", diff --git a/src/commands/package/convert.ts b/src/commands/package/convert.ts index 05093b8f..4a6eeb7b 100644 --- a/src/commands/package/convert.ts +++ b/src/commands/package/convert.ts @@ -159,7 +159,7 @@ export class PackageConvert extends SfCommand const successMessage = pvcMessages.getMessage(result.Status, [ result.Id, result.SubscriberPackageVersionId, - INSTALL_URL_BASE.toString(), + INSTALL_URL_BASE.href, result.SubscriberPackageVersionId, this.config.bin, ]); diff --git a/src/commands/package/version/create.ts b/src/commands/package/version/create.ts index 40b42376..93e802f6 100644 --- a/src/commands/package/version/create.ts +++ b/src/commands/package/version/create.ts @@ -284,7 +284,7 @@ export class PackageVersionCreateCommand extends SfCommand'}` + ? `${pkgUtils.INSTALL_URL_BASE.href}${record.SubscriberPackageVersionId ?? ''}` : ''; const data = [ diff --git a/src/commands/package/version/list.ts b/src/commands/package/version/list.ts index e5fddf71..19be78f0 100644 --- a/src/commands/package/version/list.ts +++ b/src/commands/package/version/list.ts @@ -208,7 +208,7 @@ export class PackageVersionListCommand extends SfCommand Date: Fri, 27 Mar 2026 13:41:12 -0400 Subject: [PATCH 07/11] fix: update schema --- schemas/package-bundle-uninstall.json | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 schemas/package-bundle-uninstall.json diff --git a/schemas/package-bundle-uninstall.json b/schemas/package-bundle-uninstall.json new file mode 100644 index 00000000..dda0599c --- /dev/null +++ b/schemas/package-bundle-uninstall.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/BundleUninstallResult", + "definitions": { + "BundleUninstallResult": { + "$ref": "#/definitions/BundleSObjects.PkgBundleVerUninstallReqResult" + }, + "BundleSObjects.PkgBundleVerUninstallReqResult": { + "type": "object", + "additionalProperties": false, + "properties": { + "Id": { + "type": "string" + }, + "UninstallStatus": { + "$ref": "#/definitions/BundleSObjects.PkgBundleVersionUninstallReqStatus" + }, + "CreatedDate": { + "type": "string" + }, + "CreatedById": { + "type": "string" + }, + "Error": { + "type": "array", + "items": { + "type": "string" + } + }, + "PackageBundleVersionId": { + "type": "string" + }, + "InstalledPkgBundleVersionId": { + "type": "string" + }, + "ValidationError": { + "type": "string" + } + }, + "required": ["CreatedById", "CreatedDate", "Id", "PackageBundleVersionId", "UninstallStatus"] + }, + "BundleSObjects.PkgBundleVersionUninstallReqStatus": { + "type": "string", + "enum": ["Queued", "InProgress", "Success", "Error"] + } + } +} From 943750447ff23f338b3c7ab6c5e10025b52585ff Mon Sep 17 00:00:00 2001 From: "b.tran" Date: Fri, 27 Mar 2026 13:45:28 -0400 Subject: [PATCH 08/11] fix: update schema --- schemas/package-bundle-install-list.json | 63 ------------------------ 1 file changed, 63 deletions(-) delete mode 100644 schemas/package-bundle-install-list.json diff --git a/schemas/package-bundle-install-list.json b/schemas/package-bundle-install-list.json deleted file mode 100644 index ebe2980c..00000000 --- a/schemas/package-bundle-install-list.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/PackageBundleInstallRequestResults", - "definitions": { - "PackageBundleInstallRequestResults": { - "type": "array", - "items": { - "$ref": "#/definitions/BundleSObjects.PkgBundleVersionInstallReqResult" - } - }, - "BundleSObjects.PkgBundleVersionInstallReqResult": { - "type": "object", - "additionalProperties": false, - "properties": { - "Id": { - "type": "string" - }, - "InstallStatus": { - "$ref": "#/definitions/BundleSObjects.PkgBundleVersionInstallReqStatus" - }, - "ValidationError": { - "type": "string" - }, - "CreatedDate": { - "type": "string" - }, - "CreatedById": { - "type": "string" - }, - "Error": { - "type": "array", - "items": { - "type": "string" - } - }, - "PackageBundleVersionId": { - "type": "string" - }, - "DevelopmentOrganization": { - "type": "string" - } - }, - "required": [ - "CreatedById", - "CreatedDate", - "DevelopmentOrganization", - "Id", - "InstallStatus", - "PackageBundleVersionId", - "ValidationError" - ] - }, - "BundleSObjects.PkgBundleVersionInstallReqStatus": { - "type": "string", - "enum": [ - "Queued", - "InProgress", - "Success", - "Error" - ] - } - } -} \ No newline at end of file From 4c6e1686a11066919811f78e637f8e89aaba8289 Mon Sep 17 00:00:00 2001 From: "b.tran" Date: Fri, 27 Mar 2026 13:50:37 -0400 Subject: [PATCH 09/11] fix: tf fix --- src/commands/package/install.ts | 12 ++++++++++-- test/commands/package/install.test.ts | 22 +++------------------- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/commands/package/install.ts b/src/commands/package/install.ts index 4badddc3..6c30d3ce 100644 --- a/src/commands/package/install.ts +++ b/src/commands/package/install.ts @@ -21,7 +21,7 @@ import { requiredOrgFlagWithDeprecations, SfCommand, } from '@salesforce/sf-plugins-core'; -import { Connection, Lifecycle, Messages, SfError } from '@salesforce/core'; +import { Connection, Lifecycle, Messages, SfError, SfProject } from '@salesforce/core'; import { Duration } from '@salesforce/kit'; import { PackageEvents, @@ -135,9 +135,17 @@ export class Install extends SfCommand { throw messages.createError('apiVersionTooLow'); } + let packageId = flags.package; + try { + const project = SfProject.getInstance(); + packageId = project.getPackageIdFromAlias(flags.package) ?? flags.package; + } catch { + // not in a project directory; use the value as-is + } + this.subscriberPackageVersion = new SubscriberPackageVersion({ connection: this.connection, - aliasOrId: flags.package, + aliasOrId: packageId, password: flags['installation-key'], }); diff --git a/test/commands/package/install.test.ts b/test/commands/package/install.test.ts index 4a299024..c69f72c6 100644 --- a/test/commands/package/install.test.ts +++ b/test/commands/package/install.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { EOL } from 'node:os'; -import { Connection, Lifecycle, SfProject, SfError, SfProjectJson } from '@salesforce/core'; +import { Connection, Lifecycle, SfProject, SfError } from '@salesforce/core'; import { MockTestOrgData, TestContext } from '@salesforce/core/testSetup'; import { Config } from '@oclif/core'; import { expect } from 'chai'; @@ -283,27 +283,11 @@ describe('package:install', () => { } }); - // TODO: It seems that while linking @salesforce/packaging into the plugin - // we cannot stub the library calls of `SfProject.getInstance` e.g. "SfProject, 'getInstance'" - // once the library has been published, the stubs resume to work and this test will pass it('should print SUCCESS status correctly for package alias', async () => { - // Stubs SfProject.getInstance, SfProject.getSfProjectJson, and SfProjectJson.getContents - // in a way that makes TS happy... all to test package aliases. - const getContentsStub = $$.SANDBOX.stub(SfProjectJson.prototype, 'getContents').returns({ - packageAliases: { ['my_package_alias']: myPackageVersion04t }, - packageDirectories: [], - }); - // @ts-expect-error stubbing only 1 method - const getSfProjectJsonStub = $$.SANDBOX.stub(SfProject.prototype, 'getSfProjectJson').callsFake(() => ({ - getContents: getContentsStub, - })); - const getPackageIdFromAliasStub = $$.SANDBOX.stub(SfProject.prototype, 'getPackageIdFromAlias').returns( - myPackageVersion04t - ); + $$.SANDBOX.stub(SfProject.prototype, 'getPackageIdFromAlias').returns(myPackageVersion04t); // @ts-expect-error stubbing only a subset of methods $$.SANDBOX.stub(SfProject, 'getInstance').callsFake(() => ({ - getSfProjectJson: getSfProjectJsonStub, - getPackageIdFromAlias: getPackageIdFromAliasStub, + getPackageIdFromAlias: $$.SANDBOX.stub().returns(myPackageVersion04t), })); const request = Object.assign({}, pkgInstallRequest, { Status: 'SUCCESS' }); From 57f351252caa3930f2737806bdf1da4d0e1d956c Mon Sep 17 00:00:00 2001 From: "b.tran" Date: Mon, 30 Mar 2026 12:48:09 -0400 Subject: [PATCH 10/11] chore: use local packaging for bundle installed list and uninstall types Made-with: Cursor --- package.json | 2 +- yarn.lock | 38 ++++++++++++++++++-------------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 6611216b..fc05d6d0 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "@oclif/core": "^4", "@salesforce/core": "^8.27.1", "@salesforce/kit": "^3.2.6", - "@salesforce/packaging": "^4.21.10", + "@salesforce/packaging": "file:../packaging", "@salesforce/sf-plugins-core": "^12.2.6", "chalk": "^5.6.2" }, diff --git a/yarn.lock b/yarn.lock index db2cb46f..ef396c45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1840,7 +1840,7 @@ strip-ansi "6.0.1" ts-retry-promise "^0.8.1" -"@salesforce/core@^8.18.7", "@salesforce/core@^8.23.1", "@salesforce/core@^8.23.3", "@salesforce/core@^8.27.1", "@salesforce/core@^8.5.1": +"@salesforce/core@^8.18.7", "@salesforce/core@^8.23.1", "@salesforce/core@^8.23.3", "@salesforce/core@^8.27.0", "@salesforce/core@^8.27.1", "@salesforce/core@^8.5.1": version "8.27.1" resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-8.27.1.tgz#26b08b0d68dd1fc210d1dd7a7e3770e3b9f27e74" integrity sha512-1WpVt9tQAEINGzsQsSiVRmcmYUpKeK4P54624f9HvLIv7o7jTjdARwirJpOqivIihbDE8OJnYOdsr0vV5Dz93A== @@ -1909,19 +1909,17 @@ dependencies: "@salesforce/ts-types" "^2.0.12" -"@salesforce/packaging@^4.21.10": - version "4.21.10" - resolved "https://registry.yarnpkg.com/@salesforce/packaging/-/packaging-4.21.10.tgz#ca97fd4e29f97446e2d7e122737b3c362828e74b" - integrity sha512-IKf/saSCSJdRPs4jJd6s2iRUyH7d8lBm/iR7EIrSTR/Jo+iZ+ve6vS1AnLiyOnPTPRNx7qc36MddZ+jtr1pA9A== +"@salesforce/packaging@file:../packaging": + version "4.22.0" dependencies: "@jsforce/jsforce-node" "^3.10.14" "@salesforce/core" "^8.27.1" "@salesforce/kit" "^3.2.4" "@salesforce/schemas" "^1.10.3" - "@salesforce/source-deploy-retrieve" "^12.31.25" + "@salesforce/source-deploy-retrieve" "12.31.24" "@salesforce/ts-types" "^2.0.12" "@salesforce/types" "^1.7.1" - fast-xml-parser "^5.5.9" + fast-xml-parser "^5.5.7" globby "^11" graphology "^0.26.0" graphology-traversal "^0.3.1" @@ -1987,25 +1985,25 @@ cli-progress "^3.12.0" terminal-link "^3.0.0" -"@salesforce/source-deploy-retrieve@^12.31.25": - version "12.31.30" - resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-12.31.30.tgz#2a1ac651601526be200e86c2d960a2188832755f" - integrity sha512-gTEkoFPypIWmmnC9e1TigP+tnAdUUXcU4ZGYwL/vmd5McCXKLN11EhLCngmrnafJmM8cMPMcdnVI0cFGNgoIeQ== +"@salesforce/source-deploy-retrieve@12.31.24": + version "12.31.24" + resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-12.31.24.tgz#06e74409bc95000703f1f158ac6b700fb975ee74" + integrity sha512-8folNyLjlzblAOAG7dxHuPJo4QDVU2nytxotHdHHhYk00djXr3CdchuGSVG/+2w0LO/zFZ68V17qpU2x1VqxXg== dependencies: - "@salesforce/core" "^8.27.1" + "@salesforce/core" "^8.27.0" "@salesforce/kit" "^3.2.4" "@salesforce/ts-types" "^2.0.12" "@salesforce/types" "^1.6.0" fast-levenshtein "^3.0.0" - fast-xml-parser "^5.5.7" + fast-xml-parser "^5.3.6" got "^11.8.6" graceful-fs "^4.2.11" ignore "^5.3.2" jszip "^3.10.1" mime "2.6.0" - minimatch "^9.0.7" - proxy-agent "^6.5.0" - yaml "^2.8.3" + minimatch "^9.0.5" + proxy-agent "^6.4.0" + yaml "^2.8.1" "@salesforce/ts-types@^2.0.11", "@salesforce/ts-types@^2.0.12": version "2.0.12" @@ -4828,7 +4826,7 @@ fast-xml-parser@5.5.8: path-expression-matcher "^1.2.0" strnum "^2.2.0" -fast-xml-parser@^5.5.7, fast-xml-parser@^5.5.9: +fast-xml-parser@^5.3.6, fast-xml-parser@^5.5.7: version "5.5.9" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.5.9.tgz#e59637abebec3dbfbb4053b532d787af6ea11527" integrity sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g== @@ -6737,7 +6735,7 @@ minimatch@^5.0.1, minimatch@^5.1.6: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.4, minimatch@^9.0.5, minimatch@^9.0.7: +minimatch@^9.0.4, minimatch@^9.0.5: version "9.0.9" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e" integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== @@ -7486,7 +7484,7 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== -proxy-agent@^6.5.0: +proxy-agent@^6.4.0: version "6.5.0" resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-6.5.0.tgz#9e49acba8e4ee234aacb539f89ed9c23d02f232d" integrity sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A== @@ -9152,7 +9150,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^2.5.1, yaml@^2.8.3: +yaml@^2.5.1, yaml@^2.8.1: version "2.8.3" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.3.tgz#a0d6bd2efb3dd03c59370223701834e60409bd7d" integrity sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg== From 4f65140b3e7d4d1895dd509fb87d0b2025006ad1 Mon Sep 17 00:00:00 2001 From: "b.tran" Date: Mon, 30 Mar 2026 12:52:07 -0400 Subject: [PATCH 11/11] chore: use published @salesforce/packaging@^4.22.0 Made-with: Cursor --- package.json | 2 +- yarn.lock | 38 ++++++++++++++++++++------------------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index fc05d6d0..ebb7d24c 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "@oclif/core": "^4", "@salesforce/core": "^8.27.1", "@salesforce/kit": "^3.2.6", - "@salesforce/packaging": "file:../packaging", + "@salesforce/packaging": "^4.22.0", "@salesforce/sf-plugins-core": "^12.2.6", "chalk": "^5.6.2" }, diff --git a/yarn.lock b/yarn.lock index ef396c45..9bfdda91 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1840,7 +1840,7 @@ strip-ansi "6.0.1" ts-retry-promise "^0.8.1" -"@salesforce/core@^8.18.7", "@salesforce/core@^8.23.1", "@salesforce/core@^8.23.3", "@salesforce/core@^8.27.0", "@salesforce/core@^8.27.1", "@salesforce/core@^8.5.1": +"@salesforce/core@^8.18.7", "@salesforce/core@^8.23.1", "@salesforce/core@^8.23.3", "@salesforce/core@^8.27.1", "@salesforce/core@^8.5.1": version "8.27.1" resolved "https://registry.yarnpkg.com/@salesforce/core/-/core-8.27.1.tgz#26b08b0d68dd1fc210d1dd7a7e3770e3b9f27e74" integrity sha512-1WpVt9tQAEINGzsQsSiVRmcmYUpKeK4P54624f9HvLIv7o7jTjdARwirJpOqivIihbDE8OJnYOdsr0vV5Dz93A== @@ -1909,17 +1909,19 @@ dependencies: "@salesforce/ts-types" "^2.0.12" -"@salesforce/packaging@file:../packaging": +"@salesforce/packaging@^4.22.0": version "4.22.0" + resolved "https://registry.yarnpkg.com/@salesforce/packaging/-/packaging-4.22.0.tgz#f5bc154f818a4750874aa635b54af4403bfdf0d3" + integrity sha512-ENPs/1bqTkbzt9rJxUwaAkOj+yadGRtiipYdzpU+chyvKM+9aMymbbHG5Im0/vCJ74Z4C7q/DhzA2FT2NnxObg== dependencies: "@jsforce/jsforce-node" "^3.10.14" "@salesforce/core" "^8.27.1" - "@salesforce/kit" "^3.2.4" + "@salesforce/kit" "^3.2.6" "@salesforce/schemas" "^1.10.3" - "@salesforce/source-deploy-retrieve" "12.31.24" + "@salesforce/source-deploy-retrieve" "^12.31.30" "@salesforce/ts-types" "^2.0.12" "@salesforce/types" "^1.7.1" - fast-xml-parser "^5.5.7" + fast-xml-parser "^5.5.9" globby "^11" graphology "^0.26.0" graphology-traversal "^0.3.1" @@ -1985,25 +1987,25 @@ cli-progress "^3.12.0" terminal-link "^3.0.0" -"@salesforce/source-deploy-retrieve@12.31.24": - version "12.31.24" - resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-12.31.24.tgz#06e74409bc95000703f1f158ac6b700fb975ee74" - integrity sha512-8folNyLjlzblAOAG7dxHuPJo4QDVU2nytxotHdHHhYk00djXr3CdchuGSVG/+2w0LO/zFZ68V17qpU2x1VqxXg== +"@salesforce/source-deploy-retrieve@^12.31.30": + version "12.31.31" + resolved "https://registry.yarnpkg.com/@salesforce/source-deploy-retrieve/-/source-deploy-retrieve-12.31.31.tgz#720f7779981aafdc9f178e0a36c00f6bf668e399" + integrity sha512-qq27Ru7xBkxO+3I5elpVD67V6wI0seiV/vj1dXPhzRPkz9ejPCYv7e0guoFFMxiQ92jgAa0tj0Jyjw84dof6iw== dependencies: - "@salesforce/core" "^8.27.0" + "@salesforce/core" "^8.27.1" "@salesforce/kit" "^3.2.4" "@salesforce/ts-types" "^2.0.12" "@salesforce/types" "^1.6.0" fast-levenshtein "^3.0.0" - fast-xml-parser "^5.3.6" + fast-xml-parser "^5.5.9" got "^11.8.6" graceful-fs "^4.2.11" ignore "^5.3.2" jszip "^3.10.1" mime "2.6.0" - minimatch "^9.0.5" - proxy-agent "^6.4.0" - yaml "^2.8.1" + minimatch "^9.0.7" + proxy-agent "^6.5.0" + yaml "^2.8.3" "@salesforce/ts-types@^2.0.11", "@salesforce/ts-types@^2.0.12": version "2.0.12" @@ -4826,7 +4828,7 @@ fast-xml-parser@5.5.8: path-expression-matcher "^1.2.0" strnum "^2.2.0" -fast-xml-parser@^5.3.6, fast-xml-parser@^5.5.7: +fast-xml-parser@^5.5.9: version "5.5.9" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-5.5.9.tgz#e59637abebec3dbfbb4053b532d787af6ea11527" integrity sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g== @@ -6735,7 +6737,7 @@ minimatch@^5.0.1, minimatch@^5.1.6: dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.4, minimatch@^9.0.5: +minimatch@^9.0.4, minimatch@^9.0.5, minimatch@^9.0.7: version "9.0.9" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e" integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== @@ -7484,7 +7486,7 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== -proxy-agent@^6.4.0: +proxy-agent@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-6.5.0.tgz#9e49acba8e4ee234aacb539f89ed9c23d02f232d" integrity sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A== @@ -9150,7 +9152,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^2.5.1, yaml@^2.8.1: +yaml@^2.5.1, yaml@^2.8.3: version "2.8.3" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.3.tgz#a0d6bd2efb3dd03c59370223701834e60409bd7d" integrity sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==