Skip to content

Commit 6284f4d

Browse files
committed
feat: add installed list sub org bundles
1 parent cd5ada7 commit 6284f4d

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-0
lines changed

command-snapshot.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@
105105
"flags": ["api-version", "flags-dir", "json", "loglevel", "package-install-request-id", "target-org", "verbose"],
106106
"plugin": "@salesforce/plugin-packaging"
107107
},
108+
{
109+
"alias": [],
110+
"command": "package:bundle:installed:list",
111+
"flagAliases": ["apiversion", "target-hub-org", "targetdevhubusername", "targetusername", "u"],
112+
"flagChars": ["b", "o", "v"],
113+
"flags": ["api-version", "bundles", "flags-dir", "json", "loglevel", "target-dev-hub", "target-org", "verbose"],
114+
"plugin": "@salesforce/plugin-packaging"
115+
},
108116
{
109117
"alias": [],
110118
"command": "package:bundle:list",

messages/bundle_installed_list.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# summary
2+
3+
List installed package bundles with expected vs actual component versions.
4+
5+
# description
6+
7+
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.
8+
9+
# examples
10+
11+
- List specific installed bundles in your default org and dev hub:
12+
13+
<%= config.bin %> <%= command.id %> --target-org me@example.com --target-dev-hub devhub@example.com -b 1Q8xxxxxxxAAAAAA -b 1Q8yyyyyyyBBBBBB
14+
15+
# flags.bundles.summary
16+
17+
One or more bundle version IDs to inspect.
18+
19+
# flags.bundles.description
20+
21+
Provide bundle version IDs (e.g., 1Q8...) to evaluate expected vs actual component versions.
22+
23+
# flags.verbose.summary
24+
25+
Show additional details.
26+
27+
# noBundles
28+
29+
No package bundles found in this org.
30+
31+
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright (c) 2025, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
import { Flags, loglevel, orgApiVersionFlagWithDeprecations, requiredOrgFlagWithDeprecations, SfCommand } from '@salesforce/sf-plugins-core';
9+
import { Connection, Messages } from '@salesforce/core';
10+
import { BundleSObjects, PackageBundleInstall, getInstalledBundleStatuses, InstalledBundleStatus } from '@salesforce/packaging';
11+
import chalk from 'chalk';
12+
import { requiredHubFlag } from '../../../../utils/hubFlag.js';
13+
14+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
15+
const messages = Messages.loadMessages('@salesforce/plugin-packaging', 'bundle_installed_list');
16+
17+
export type BundleInstalledListResults = InstalledBundleStatus[];
18+
19+
export class PackageBundleInstalledListCommand extends SfCommand<BundleInstalledListResults> {
20+
public static readonly summary = messages.getMessage('summary');
21+
public static readonly description = messages.getMessage('description');
22+
public static readonly examples = messages.getMessages('examples');
23+
24+
public static readonly flags = {
25+
loglevel,
26+
'target-org': requiredOrgFlagWithDeprecations,
27+
'target-dev-hub': requiredHubFlag,
28+
'api-version': orgApiVersionFlagWithDeprecations,
29+
bundles: Flags.string({
30+
char: 'b',
31+
multiple: true,
32+
summary: messages.getMessage('flags.bundles.summary'),
33+
description: messages.getMessage('flags.bundles.description'),
34+
}),
35+
verbose: Flags.boolean({ summary: messages.getMessage('flags.verbose.summary') }),
36+
} as const;
37+
38+
private subscriberConn!: Connection;
39+
private devHubConn!: Connection;
40+
41+
public async run(): Promise<BundleInstalledListResults> {
42+
const { flags } = await this.parse(PackageBundleInstalledListCommand);
43+
44+
this.subscriberConn = flags['target-org'].getConnection(flags['api-version']);
45+
this.devHubConn = flags['target-dev-hub'].getConnection(flags['api-version']);
46+
47+
let bundleVersionIds = flags.bundles ?? [];
48+
if (bundleVersionIds.length === 0) {
49+
// Preferred: try InstalledPackageBundleVersion if available in subscriber org
50+
try {
51+
const query =
52+
'SELECT Id, PackageBundleVersion.Id FROM InstalledPackageBundleVersion ORDER BY CreatedDate DESC';
53+
const ipbv = await this.subscriberConn.tooling.query<{ Id: string; PackageBundleVersion?: { Id: string } }>(
54+
query
55+
);
56+
const ids = new Set<string>();
57+
for (const r of ipbv.records) {
58+
const id = r.PackageBundleVersion?.Id;
59+
if (id) ids.add(id);
60+
}
61+
bundleVersionIds = [...ids];
62+
} catch {
63+
// Fallback: derive from successful install requests
64+
const successes = await PackageBundleInstall.getInstallStatuses(
65+
this.subscriberConn,
66+
BundleSObjects.PkgBundleVersionInstallReqStatus.success
67+
);
68+
const ids = new Set<string>();
69+
for (const r of successes) {
70+
if (r.PackageBundleVersionID) ids.add(r.PackageBundleVersionID);
71+
}
72+
bundleVersionIds = [...ids];
73+
}
74+
}
75+
const results = await getInstalledBundleStatuses(this.subscriberConn, this.devHubConn, bundleVersionIds);
76+
77+
if (results.length === 0) {
78+
this.log(messages.getMessage('noBundles'));
79+
return [];
80+
}
81+
82+
this.displayResults(results, flags.verbose);
83+
return results;
84+
}
85+
86+
private displayResults(results: BundleInstalledListResults, verbose = false): void {
87+
for (const r of results) {
88+
this.styledHeader(chalk.blue(`${r.bundleName} (${r.bundleVersionNumber}) [${r.status}]`));
89+
const componentRows = r.components.map((c: InstalledBundleStatus['components'][number]) => ({
90+
Package: c.packageName,
91+
'Expected Version': c.expectedVersionNumber,
92+
'Actual Version': c.actualVersionNumber ?? 'Not Installed',
93+
Status: c.status,
94+
}));
95+
this.table({ data: componentRows, overflow: 'wrap', title: chalk.gray(`Bundle ID ${r.bundleId}`) });
96+
if (verbose) this.log('');
97+
}
98+
}
99+
}
100+
101+

0 commit comments

Comments
 (0)