Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions command-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,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", "target-hub-org", "targetdevhubusername", "targetusername", "u"],
"flagChars": ["b", "o", "v"],
"flags": ["api-version", "bundles", "flags-dir", "json", "loglevel", "target-dev-hub", "target-org", "verbose"],
"plugin": "@salesforce/plugin-packaging"
},
{
"alias": [],
"command": "package:bundle:list",
Expand Down
31 changes: 31 additions & 0 deletions messages/bundle_installed_list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# summary

List installed package bundles with expected vs actual component versions.

# description

Shows each installed bundle (by provided bundle version IDs) with its components, expected version from the Dev Hub bundle definition, and the actual installed version in the subscriber org. Highlights mismatches and missing packages.

# examples

- List specific installed bundles in your default org and dev hub:

<%= config.bin %> <%= command.id %> --target-org [email protected] --target-dev-hub [email protected] -b 1Q8xxxxxxxAAAAAA -b 1Q8yyyyyyyBBBBBB

# flags.bundles.summary

One or more bundle version IDs to inspect.

# flags.bundles.description

Provide bundle version IDs (e.g., 1Q8...) to evaluate expected vs actual component versions.

# flags.verbose.summary

Show additional details.

# noBundles

No package bundles found in this org.


103 changes: 103 additions & 0 deletions src/commands/package/bundle/installed/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright (c) 2025, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { Flags, loglevel, orgApiVersionFlagWithDeprecations, requiredOrgFlagWithDeprecations, SfCommand } from '@salesforce/sf-plugins-core';
import { Connection, Messages } from '@salesforce/core';
import { BundleSObjects, PackageBundleInstall, getInstalledBundleStatuses, InstalledBundleStatus } from '@salesforce/packaging';
import chalk from 'chalk';
import { requiredHubFlag } from '../../../../utils/hubFlag.js';

Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-packaging', 'bundle_installed_list');

export type BundleInstalledListResults = InstalledBundleStatus[];

export class PackageBundleInstalledListCommand extends SfCommand<BundleInstalledListResults> {
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,
'target-dev-hub': requiredHubFlag,
'api-version': orgApiVersionFlagWithDeprecations,
bundles: Flags.string({
char: 'b',
multiple: true,
summary: messages.getMessage('flags.bundles.summary'),
description: messages.getMessage('flags.bundles.description'),
}),
verbose: Flags.boolean({ summary: messages.getMessage('flags.verbose.summary') }),
} as const;

private subscriberConn!: Connection;
private devHubConn!: Connection;

public async run(): Promise<BundleInstalledListResults> {
const { flags } = await this.parse(PackageBundleInstalledListCommand);

this.subscriberConn = flags['target-org'].getConnection(flags['api-version']);
this.devHubConn = flags['target-dev-hub'].getConnection(flags['api-version']);

let bundleVersionIds = flags.bundles ?? [];
if (bundleVersionIds.length === 0) {
// Preferred: try InstalledPackageBundleVersion if available in subscriber org
try {
const query =
'SELECT Id, PackageBundleVersion.Id FROM InstalledPackageBundleVersion ORDER BY CreatedDate DESC';
const ipbv = await this.subscriberConn.tooling.query<{ Id: string; PackageBundleVersion?: { Id: string } }>(
query
);
const ids = new Set<string>();
for (const r of ipbv.records) {
const id = r.PackageBundleVersion?.Id;
if (id) ids.add(id);
}
bundleVersionIds = [...ids];
} catch {
// Fallback: derive from successful install requests
const successes = await PackageBundleInstall.getInstallStatuses(
this.subscriberConn,
BundleSObjects.PkgBundleVersionInstallReqStatus.success
);
const ids = new Set<string>();
for (const r of successes) {
if (r.PackageBundleVersionID) ids.add(r.PackageBundleVersionID);
}
bundleVersionIds = [...ids];
}
}
const results = await getInstalledBundleStatuses(this.subscriberConn, this.devHubConn, bundleVersionIds);

if (results.length === 0) {
this.log(messages.getMessage('noBundles'));
return [];
}

this.displayResults(results, flags.verbose);
return results;
}

private displayResults(results: BundleInstalledListResults, verbose = false): void {
for (const r of results) {
this.styledHeader(chalk.blue(`${r.bundleName} (${r.bundleVersionNumber}) [${r.status}]`));
const componentRows = r.components.map((c: InstalledBundleStatus['components'][number]) => ({
Package: c.packageName,
'Expected Version': c.expectedVersionNumber,
'Actual Version': c.actualVersionNumber ?? 'Not Installed',
Status: c.status,
}));
this.table({ data: componentRows, overflow: 'wrap', title: chalk.gray(`Bundle ID ${r.bundleId}`) });
if (verbose) this.log('');
}
}
}


Loading