diff --git a/command-snapshot.json b/command-snapshot.json index d5a270c9..36aa6c30 100644 --- a/command-snapshot.json +++ b/command-snapshot.json @@ -81,18 +81,18 @@ }, { "alias": [], - "command": "package:bundle:install:list", + "command": "package:bundle:install:report", "flagAliases": ["apiversion", "targetusername", "u"], - "flagChars": ["c", "o", "s"], - "flags": ["api-version", "created-last-days", "flags-dir", "json", "loglevel", "status", "target-org", "verbose"], + "flagChars": ["i", "o"], + "flags": ["api-version", "flags-dir", "json", "loglevel", "package-install-request-id", "target-org", "verbose"], "plugin": "@salesforce/plugin-packaging" }, { "alias": [], - "command": "package:bundle:install:report", + "command": "package:bundle:installed:list", "flagAliases": ["apiversion", "targetusername", "u"], - "flagChars": ["i", "o"], - "flags": ["api-version", "flags-dir", "json", "loglevel", "package-install-request-id", "target-org", "verbose"], + "flagChars": ["o"], + "flags": ["api-version", "flags-dir", "json", "loglevel", "target-org"], "plugin": "@salesforce/plugin-packaging" }, { @@ -103,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/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/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/package.json b/package.json index 3736b79b..ebb7d24c 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,9 @@ "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/packaging": "^4.22.0", "@salesforce/sf-plugins-core": "^12.2.6", "chalk": "^5.6.2" }, 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/schemas/package-bundle-install-list.json b/schemas/package-bundle-uninstall.json similarity index 51% rename from schemas/package-bundle-install-list.json rename to schemas/package-bundle-uninstall.json index ebe2980c..dda0599c 100644 --- a/schemas/package-bundle-install-list.json +++ b/schemas/package-bundle-uninstall.json @@ -1,25 +1,19 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/PackageBundleInstallRequestResults", + "$ref": "#/definitions/BundleUninstallResult", "definitions": { - "PackageBundleInstallRequestResults": { - "type": "array", - "items": { - "$ref": "#/definitions/BundleSObjects.PkgBundleVersionInstallReqResult" - } + "BundleUninstallResult": { + "$ref": "#/definitions/BundleSObjects.PkgBundleVerUninstallReqResult" }, - "BundleSObjects.PkgBundleVersionInstallReqResult": { + "BundleSObjects.PkgBundleVerUninstallReqResult": { "type": "object", "additionalProperties": false, "properties": { "Id": { "type": "string" }, - "InstallStatus": { - "$ref": "#/definitions/BundleSObjects.PkgBundleVersionInstallReqStatus" - }, - "ValidationError": { - "type": "string" + "UninstallStatus": { + "$ref": "#/definitions/BundleSObjects.PkgBundleVersionUninstallReqStatus" }, "CreatedDate": { "type": "string" @@ -36,28 +30,18 @@ "PackageBundleVersionId": { "type": "string" }, - "DevelopmentOrganization": { + "InstalledPkgBundleVersionId": { + "type": "string" + }, + "ValidationError": { "type": "string" } }, - "required": [ - "CreatedById", - "CreatedDate", - "DevelopmentOrganization", - "Id", - "InstallStatus", - "PackageBundleVersionId", - "ValidationError" - ] + "required": ["CreatedById", "CreatedDate", "Id", "PackageBundleVersionId", "UninstallStatus"] }, - "BundleSObjects.PkgBundleVersionInstallReqStatus": { + "BundleSObjects.PkgBundleVersionUninstallReqStatus": { "type": "string", - "enum": [ - "Queued", - "InProgress", - "Success", - "Error" - ] + "enum": ["Queued", "InProgress", "Success", "Error"] } } -} \ No newline at end of file +} 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/src/commands/package/bundle/installed/list.ts b/src/commands/package/bundle/installed/list.ts new file mode 100644 index 00000000..790fa86f --- /dev/null +++ b/src/commands/package/bundle/installed/list.ts @@ -0,0 +1,139 @@ +/* + * 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 { + 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; + } +} 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/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/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/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 { - 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; - }); -}); diff --git a/test/commands/bundle/bundleInstalledList.test.ts b/test/commands/bundle/bundleInstalledList.test.ts new file mode 100644 index 00000000..ed23354e --- /dev/null +++ b/test/commands/bundle/bundleInstalledList.test.ts @@ -0,0 +1,263 @@ +/* + * 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 { 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'); + } + }); +}); 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'); + } + }); +}); + 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' }); diff --git a/yarn.lock b/yarn.lock index 84b6982e..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,16 +1909,16 @@ 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@^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.25" + "@salesforce/source-deploy-retrieve" "^12.31.30" "@salesforce/ts-types" "^2.0.12" "@salesforce/types" "^1.7.1" fast-xml-parser "^5.5.9" @@ -1987,17 +1987,17 @@ 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.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.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.5.7" + fast-xml-parser "^5.5.9" got "^11.8.6" graceful-fs "^4.2.11" ignore "^5.3.2" @@ -4828,7 +4828,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.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==