Skip to content

Commit a4fe3f0

Browse files
fix: finish pvl, add NUT
1 parent f987af0 commit a4fe3f0

File tree

4 files changed

+233
-64
lines changed

4 files changed

+233
-64
lines changed

messages/package_version_list.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,11 @@ Build Duration in Seconds
134134
# hasMetadataRemoved
135135

136136
Managed Metadata Removed
137+
138+
# isOrgDependent
139+
140+
Org-Dependent Unlocked Package
141+
142+
# createdBy
143+
144+
Created By

src/commands/force/package/beta/version/list.ts

Lines changed: 55 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import * as os from 'os';
99
import { flags, FlagsConfig, SfdxCommand } from '@salesforce/command';
10-
import { Messages } from '@salesforce/core';
10+
import { Messages, SfProject } from '@salesforce/core';
1111
import { CliUx } from '@oclif/core';
1212
import {
1313
getContainerOptions,
@@ -18,18 +18,37 @@ import {
1818
PackageVersion,
1919
} from '@salesforce/packaging';
2020
import { PackageVersionListResult } from '@salesforce/packaging/src/interfaces';
21+
import { Optional } from '@salesforce/ts-types';
2122

2223
Messages.importMessagesDirectory(__dirname);
2324
const messages = Messages.loadMessages('@salesforce/plugin-packaging', 'package_version_list');
2425
const packaging = Messages.loadMessages('@salesforce/plugin-packaging', 'packaging');
2526

2627
type PackageVersionListCommandResult = Omit<
2728
PackageVersionListResult,
28-
'HasMetadataRemoved' | 'IsReleased' | 'IsPasswordProtected'
29+
| 'HasMetadataRemoved'
30+
| 'IsReleased'
31+
| 'IsPasswordProtected'
32+
| 'HasPassedCodeCoverageCheck'
33+
| 'CreatedById'
34+
| 'BuildDurationInSeconds'
35+
| 'CodeCoverage'
36+
| 'Package2'
2937
> & {
3038
HasMetadataRemoved: string;
3139
IsPasswordProtected: string | boolean;
3240
IsReleased: string | boolean;
41+
HasPassedCodeCoverageCheck: string | boolean;
42+
BuildDurationInSeconds: string;
43+
CodeCoverage: string;
44+
NamespacePrefix: string;
45+
Package2Name: string;
46+
Version: string;
47+
InstallUrl: string;
48+
AncestorVersion: string;
49+
Alias: string;
50+
IsOrgDependent: 'N/A' | 'Yes' | 'No';
51+
CreatedBy: string;
3352
};
3453

3554
export class PackageVersionListCommand extends SfdxCommand {
@@ -75,12 +94,12 @@ export class PackageVersionListCommand extends SfdxCommand {
7594
};
7695

7796
public async run(): Promise<PackageVersionListCommandResult[]> {
97+
const project = SfProject.getInstance();
7898
const pv = new PackageVersion({ connection: this.hubOrg.getConnection(), project: undefined });
7999
const packageIdOrAliases = (this.flags.packages as string[]) || [];
80100
// resolve any passed in values from aliases or IDs
81-
const packages = packageIdOrAliases.map((p) => getPackageIdFromAlias(p, this.project) ?? p);
101+
const packages = packageIdOrAliases.map((p) => getPackageIdFromAlias(p, project) ?? p);
82102
const records = await pv.list({
83-
connection: this.hubOrg.getConnection(),
84103
createdLastDays: this.flags.createdlastdays as number,
85104
concise: this.flags.concise as boolean,
86105
modifiedLastDays: this.flags.modifiedlastdays as number,
@@ -93,8 +112,7 @@ export class PackageVersionListCommand extends SfdxCommand {
93112
const results: PackageVersionListCommandResult[] = [];
94113

95114
if (records?.length > 0) {
96-
let ancestorVersionsMap;
97-
let containerOptionsMap;
115+
let ancestorVersionsMap: Optional<Map<string, string>>;
98116
// lookup ancestorVersions if ancestorIds are present
99117
const ancestorIds = records.filter((record) => record.AncestorId).map((record) => record.AncestorId);
100118
if (ancestorIds?.length > 0) {
@@ -103,21 +121,21 @@ export class PackageVersionListCommand extends SfdxCommand {
103121

104122
// Get the container options for each package version. We need this for determining if the version is OrgDependent
105123
const recordIds = [...new Set(records.map((record) => record.Package2Id))];
106-
containerOptionsMap = await getContainerOptions(recordIds, this.hubOrg.getConnection());
124+
const containerOptionsMap = await getContainerOptions(recordIds, this.hubOrg.getConnection());
107125

108126
records.forEach((record) => {
109127
const ids = [record.Id, record.SubscriberPackageVersionId];
110128
const aliases = [];
111129
ids.forEach((id) => {
112-
const matches = getPackageAliasesFromId(id, this.project);
130+
const matches = getPackageAliasesFromId(id, project);
113131
if (matches.length > 0) {
114132
aliases.push(matches);
115133
}
116134
});
117135
const AliasStr = aliases.length > 0 ? aliases.join() : '';
118136

119137
// set Ancestor display values
120-
let ancestorVersion = null;
138+
let ancestorVersion: Optional<string> = null;
121139
if (record.AncestorId) {
122140
ancestorVersion = ancestorVersionsMap.get(record.AncestorId);
123141
} else if (containerOptionsMap.get(record.Package2Id) !== 'Managed') {
@@ -129,7 +147,7 @@ export class PackageVersionListCommand extends SfdxCommand {
129147
const codeCoverage =
130148
record.CodeCoverage != null
131149
? `${record.CodeCoverage.ApexCodeCoveragePercentage}%`
132-
: record.Package2.IsOrgDependent === true || record.ValidationSkipped === true
150+
: record.Package2.IsOrgDependent || record.ValidationSkipped
133151
? 'N/A'
134152
: '';
135153

@@ -141,16 +159,12 @@ export class PackageVersionListCommand extends SfdxCommand {
141159
const isOrgDependent =
142160
containerOptionsMap.get(record.Package2Id) === 'Managed'
143161
? 'N/A'
144-
: record.Package2.IsOrgDependent === true
162+
: record.Package2.IsOrgDependent
145163
? 'Yes'
146164
: 'No';
147165

148166
const hasMetadataRemoved =
149-
containerOptionsMap.get(record.Package2Id) !== 'Managed'
150-
? 'N/A'
151-
: record.HasMetadataRemoved === true
152-
? 'Yes'
153-
: 'No';
167+
containerOptionsMap.get(record.Package2Id) !== 'Managed' ? 'N/A' : record.HasMetadataRemoved ? 'Yes' : 'No';
154168

155169
results.push({
156170
Package2Id: record.Package2Id,
@@ -175,7 +189,7 @@ export class PackageVersionListCommand extends SfdxCommand {
175189
LastModifiedDate: record.LastModifiedDate, // moment(record.LastModifiedDate).format('YYYY-MM-DD HH:mm'),
176190
// CreatedDate: moment(record.CreatedDate).format('YYYY-MM-DD HH:mm'),
177191
// LastModifiedDate: moment(record.LastModifiedDate).format('YYYY-MM-DD HH:mm'),
178-
InstallUrl: INSTALL_URL_BASE + record.SubscriberPackageVersionId,
192+
InstallUrl: INSTALL_URL_BASE.toString() + record.SubscriberPackageVersionId,
179193
CodeCoverage: codeCoverage,
180194
HasPassedCodeCoverageCheck: hasPassedCodeCoverageCheck,
181195
ValidationSkipped: record.ValidationSkipped,
@@ -184,16 +198,17 @@ export class PackageVersionListCommand extends SfdxCommand {
184198
Alias: AliasStr,
185199
IsOrgDependent: isOrgDependent,
186200
ReleaseVersion: record.ReleaseVersion == null ? '' : Number.parseFloat(record.ReleaseVersion).toFixed(1),
187-
BuildDurationInSeconds: record.BuildDurationInSeconds == null ? '' : record.BuildDurationInSeconds,
201+
BuildDurationInSeconds: record.BuildDurationInSeconds == null ? '' : record.BuildDurationInSeconds.toString(),
188202
HasMetadataRemoved: hasMetadataRemoved,
189203
CreatedBy: record.CreatedById,
190204
});
191205
});
206+
this.ux.styledHeader(`Package Versions [${results.length}]`);
207+
this.ux.table(results, this.getColumnData(), { 'no-truncate': true });
208+
} else {
209+
this.ux.log('No results found');
192210
}
193211

194-
this.ux.styledHeader(`Package Versions [${results.length}]`);
195-
this.ux.table(results, this.getColumnData());
196-
197212
return results;
198213
}
199214

@@ -208,26 +223,29 @@ export class PackageVersionListCommand extends SfdxCommand {
208223
IsReleased: { header: 'Released' },
209224
};
210225
}
226+
const defaultCols = {
227+
Package2Name: { header: 'Package Name' },
228+
NamespacePrefix: { header: 'Namespace' },
229+
Name: { header: 'Version Name' },
230+
Version: { header: messages.getMessage('version') },
231+
SubscriberPackageVersionId: {
232+
header: messages.getMessage('subscriberPackageVersionId'),
233+
},
234+
Alias: { header: messages.getMessage('alias') },
235+
IsPasswordProtected: { header: messages.getMessage('installKey') },
236+
IsReleased: { header: 'Released' },
237+
ValidationSkipped: { header: messages.getMessage('validationSkipped') },
238+
AncestorId: { header: 'Ancestor' },
239+
AncestorVersion: { header: 'Ancestor Version' },
240+
Branch: { header: messages.getMessage('packageBranch') },
241+
};
211242

212243
if (!this.flags.verbose) {
213-
return {
214-
Package2Name: { header: 'Package Name' },
215-
NamespacePrefix: { header: 'Namespace' },
216-
Name: { header: 'Version Name' },
217-
Version: { header: messages.getMessage('version') },
218-
SubscriberPackageVersionId: {
219-
header: messages.getMessage('subscriberPackageVersionId'),
220-
},
221-
Alias: { header: messages.getMessage('alias') },
222-
IsPasswordProtected: { header: messages.getMessage('installKey') },
223-
IsReleased: { header: 'Released' },
224-
ValidationSkipped: { header: messages.getMessage('validationSkipped') },
225-
AncestorId: { header: 'Ancestor' },
226-
AncestorVersion: { header: 'Ancestor Version' },
227-
Branch: { header: messages.getMessage('packageBranch') },
228-
};
244+
return defaultCols;
229245
} else {
246+
// add additional columns for verbose output
230247
return {
248+
...defaultCols,
231249
Package2Id: { header: messages.getMessage('packageId') },
232250
InstallUrl: { header: messages.getMessage('installUrl') },
233251
Id: { header: messages.getMessage('id') },
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright (c) 2022, 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 * as path from 'path';
9+
import { execCmd, TestSession } from '@salesforce/cli-plugins-testkit';
10+
import { OrgConfigProperties } from '@salesforce/core';
11+
import { expect } from 'chai';
12+
13+
describe('package:version:list', () => {
14+
let session: TestSession;
15+
let usernameOrAlias: string;
16+
before(async () => {
17+
const executablePath = path.join(process.cwd(), 'bin', 'dev');
18+
session = await TestSession.create({
19+
setupCommands: [`${executablePath} config:get ${OrgConfigProperties.TARGET_DEV_HUB} --json`],
20+
project: { name: 'packageVersionList' },
21+
});
22+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
23+
usernameOrAlias = (session.setup[0] as { result: [{ value: string }] }).result[0].value;
24+
25+
if (!usernameOrAlias) throw Error('no default username set');
26+
});
27+
28+
after(async () => {
29+
await session?.clean();
30+
});
31+
it('should list package versions in dev hub - human readable results', () => {
32+
const command = `force:package:beta:version:list -v ${usernameOrAlias}`;
33+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
34+
const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout as string;
35+
expect(output).to.contain('=== Package Versions [');
36+
expect(output).to.match(
37+
/Package Name\s+Namespace\s+Version Name\s+Version\s+Subscriber Package Version Id\sAlias\s+Installation Key\s+Released\s+Validation Skipped\s+Ancestor\s+Ancestor Version\s+Branch/
38+
);
39+
});
40+
41+
it('should list package versions in dev hub - concise output', () => {
42+
const command = `force:package:beta:version:list -v ${usernameOrAlias} --concise`;
43+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
44+
const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout as string;
45+
expect(output).to.contain('=== Package Versions [');
46+
expect(output).to.match(/Package Id\s+Version\s+Subscriber Package Version Id\s+Released/);
47+
});
48+
49+
it('should list package versions modified in the last 5 days', () => {
50+
const command = `force:package:beta:version:list -v ${usernameOrAlias} --modifiedlastdays 5`;
51+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
52+
const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout as string;
53+
expect(output).to.contain('=== Package Versions [');
54+
expect(output).to.match(
55+
/Package Name\s+Namespace\s+Version Name\s+Version\s+Subscriber Package Version Id\sAlias\s+Installation Key\s+Released\s+Validation Skipped\s+Ancestor\s+Ancestor Version\s+Branch/
56+
);
57+
});
58+
it('should list package versions created in the last 5 days', () => {
59+
const command = `force:package:beta:version:list -v ${usernameOrAlias} --createdlastdays 5`;
60+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
61+
const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout as string;
62+
expect(output).to.contain('=== Package Versions [');
63+
expect(output).to.match(
64+
/Package Name\s+Namespace\s+Version Name\s+Version\s+Subscriber Package Version Id\sAlias\s+Installation Key\s+Released\s+Validation Skipped\s+Ancestor\s+Ancestor Version\s+Branch/
65+
);
66+
});
67+
it('should list installed packages in dev hub - verbose human readable results', () => {
68+
const command = `force:package:beta:version:list -v ${usernameOrAlias} --verbose`;
69+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
70+
const output = execCmd(command, { ensureExitCode: 0 }).shellOutput.stdout as string;
71+
expect(output).to.contain('=== Package Versions [');
72+
expect(output).to.match(
73+
/Package Name\s+Namespace\s+Version Name\s+Version\s+Subscriber Package Version Id\sAlias\s+Installation Key\s+Released\s+Validation Skipped\s+Ancestor\s+Ancestor Version\s+Branch\s+Package Id\s+Installation URL\s+Package Version Id\s+Created Date\s+Last Modified Date\s+Tag\s+Description\s+Code Coverage\s+Code Coverage Met\s+Converted From Version Id\s+Org-Dependent\s+Unlocked Package\s+Release\s+Version\s+Build Duration in Seconds\s+Managed Metadata Removed\s+Created By/
74+
);
75+
});
76+
it('should list package versions in dev hub - json results', () => {
77+
const command = `force:package:beta:version:list -v ${usernameOrAlias} --json`;
78+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
79+
const output = execCmd(command, { ensureExitCode: 0 }).jsonOutput as {
80+
status: number;
81+
result: { [key: string]: unknown };
82+
};
83+
const keys = [
84+
'Package2Id',
85+
'Branch',
86+
'Tag',
87+
'MajorVersion',
88+
'MinorVersion',
89+
'PatchVersion',
90+
'BuildNumber',
91+
'Id',
92+
'SubscriberPackageVersionId',
93+
'Name',
94+
'NamespacePrefix',
95+
'Package2Name',
96+
'Description',
97+
'Version',
98+
'IsPasswordProtected',
99+
'IsReleased',
100+
'CreatedDate',
101+
'LastModifiedDate',
102+
'InstallUrl',
103+
'CodeCoverage',
104+
'ValidationSkipped',
105+
'AncestorId',
106+
'AncestorVersion',
107+
'Alias',
108+
'IsOrgDependent',
109+
'ReleaseVersion',
110+
'BuildDurationInSeconds',
111+
'HasMetadataRemoved',
112+
'CreatedBy',
113+
];
114+
expect(output).to.be.ok;
115+
expect(output.status).to.equal(0);
116+
expect(output.result).to.have.length.greaterThan(0);
117+
expect(output.result[0]).to.have.keys(keys);
118+
});
119+
});

0 commit comments

Comments
 (0)