Skip to content

Commit 89057b6

Browse files
committed
fix: adds target-org and wait support to deploy report command
1 parent 5bfaf8d commit 89057b6

File tree

4 files changed

+105
-36
lines changed

4 files changed

+105
-36
lines changed

command-snapshot.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,9 @@
101101
{
102102
"command": "project:deploy:report",
103103
"plugin": "@salesforce/plugin-deploy-retrieve",
104-
"flags": ["coverage-formatters", "job-id", "json", "junit", "results-dir", "use-most-recent"],
104+
"flags": ["coverage-formatters", "job-id", "json", "junit", "results-dir", "target-org", "use-most-recent", "wait"],
105105
"alias": ["deploy:metadata:report"],
106-
"flagChars": ["i", "r"],
106+
"flagChars": ["i", "o", "r", "w"],
107107
"flagAliases": []
108108
},
109109
{

messages/deploy.metadata.quick.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Overrides your default org.
7878

7979
# error.CannotQuickDeploy
8080

81-
Job ID can't be used for quick deployment. Possible reasons include the deployment hasn't been validated or the validation expired because you ran it more than 10 days ago.
81+
Job ID can't be used for quick deployment. Possible reasons include the deployment hasn't been validated, has already been deployed, or the validation expired because you ran it more than 10 days ago.
8282

8383
# error.QuickDeployFailure
8484

src/commands/project/deploy/report.ts

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
77

8-
import { Messages, Org } from '@salesforce/core';
9-
import { Duration } from '@salesforce/kit';
8+
import { Messages, Org, SfProject } from '@salesforce/core';
109
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
11-
import { DeployResult, MetadataApiDeployStatus } from '@salesforce/source-deploy-retrieve';
12-
import { buildComponentSet } from '../../../utils/deploy';
10+
import { ComponentSet, DeployResult, MetadataApiDeploy } from '@salesforce/source-deploy-retrieve';
11+
import { buildComponentSet, DeployOptions } from '../../../utils/deploy';
12+
import { DeployProgress } from '../../../utils/progressBar';
1313
import { DeployCache } from '../../../utils/deployCache';
1414
import { DeployReportResultFormatter } from '../../../formatters/deployReportResultFormatter';
1515
import { DeployResultJson } from '../../../utils/types';
1616
import { coverageFormattersFlag } from '../../../utils/flags';
1717

1818
Messages.importMessagesDirectory(__dirname);
1919
const messages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'deploy.metadata.report');
20+
const deployMessages = Messages.loadMessages('@salesforce/plugin-deploy-retrieve', 'deploy.metadata');
2021
const testFlags = 'Test';
2122

2223
export default class DeployMetadataReport extends SfCommand<DeployResultJson> {
@@ -27,6 +28,11 @@ export default class DeployMetadataReport extends SfCommand<DeployResultJson> {
2728
public static readonly deprecateAliases = true;
2829

2930
public static readonly flags = {
31+
'target-org': Flags.optionalOrg({
32+
char: 'o',
33+
description: deployMessages.getMessage('flags.target-org.description'),
34+
summary: deployMessages.getMessage('flags.target-org.summary'),
35+
}),
3036
'job-id': Flags.salesforceId({
3137
char: 'i',
3238
startsWith: '0Af',
@@ -51,23 +57,77 @@ export default class DeployMetadataReport extends SfCommand<DeployResultJson> {
5157
summary: messages.getMessage('flags.results-dir.summary'),
5258
helpGroup: testFlags,
5359
}),
60+
// we want to allow undefined for a simple check deploy status
61+
// eslint-disable-next-line sf-plugin/flag-min-max-default
62+
wait: Flags.duration({
63+
char: 'w',
64+
summary: deployMessages.getMessage('flags.wait.summary'),
65+
description: deployMessages.getMessage('flags.wait.description'),
66+
unit: 'minutes',
67+
helpValue: '<minutes>',
68+
min: 1,
69+
}),
5470
};
5571

5672
public async run(): Promise<DeployResultJson> {
5773
const [{ flags }, cache] = await Promise.all([this.parse(DeployMetadataReport), DeployCache.create()]);
58-
const jobId = cache.resolveLatest(flags['use-most-recent'], flags['job-id']);
74+
const jobId = cache.resolveLatest(flags['use-most-recent'], flags['job-id'], false);
75+
76+
const deployOpts = cache.get(jobId) ?? ({} as DeployOptions & { isMdapi: boolean });
77+
const waitDuration = flags['wait'];
78+
const org = flags['target-org'] ?? (await Org.create({ aliasOrUsername: deployOpts['target-org'] }));
5979

60-
const deployOpts = cache.get(jobId);
61-
const org = await Org.create({ aliasOrUsername: deployOpts['target-org'] });
62-
const [deployStatus, componentSet] = await Promise.all([
63-
// we'll use whatever the org supports since we can't specify the org
80+
// if we're using mdapi we won't have a component set
81+
let componentSet = new ComponentSet();
82+
if (!deployOpts.isMdapi) {
83+
if (!cache.get(jobId)) {
84+
// If the cache file isn't there, use the project package directories for the CompSet
85+
try {
86+
this.project = await SfProject.resolve();
87+
const sourcepath = this.project.getUniquePackageDirectories().map((pDir) => pDir.fullPath);
88+
componentSet = await buildComponentSet({ 'source-dir': sourcepath, wait: waitDuration });
89+
} catch (err) {
90+
// ignore the error. this was just to get improved command output.
91+
}
92+
} else {
93+
componentSet = await buildComponentSet({ ...deployOpts, wait: waitDuration });
94+
}
95+
}
96+
const mdapiDeploy = new MetadataApiDeploy({
97+
// setting an API version here won't matter since we're just checking deploy status
6498
// eslint-disable-next-line sf-plugin/get-connection-with-version
65-
org.getConnection().metadata.checkDeployStatus(jobId, true),
66-
// if we're using mdapi, we won't have a component set
67-
deployOpts.isMdapi ? undefined : buildComponentSet({ ...deployOpts, wait: Duration.minutes(deployOpts.wait) }),
68-
]);
99+
usernameOrConnection: org.getConnection(),
100+
id: jobId,
101+
components: componentSet,
102+
apiOptions: {
103+
rest: deployOpts.api === 'REST',
104+
},
105+
});
106+
107+
const getDeployResult = async (): Promise<DeployResult> => {
108+
const deployStatus = await mdapiDeploy.checkStatus();
109+
return new DeployResult(deployStatus, componentSet);
110+
};
69111

70-
const result = new DeployResult(deployStatus as MetadataApiDeployStatus, componentSet);
112+
let result: DeployResult;
113+
if (waitDuration) {
114+
// poll for deploy results
115+
try {
116+
new DeployProgress(mdapiDeploy, this.jsonEnabled()).start();
117+
result = await mdapiDeploy.pollStatus(500, waitDuration.seconds);
118+
} catch (error) {
119+
if (error instanceof Error && error.message.includes('The client has timed out')) {
120+
this.debug('[project deploy report] polling timed out. Requesting status...');
121+
} else {
122+
throw error;
123+
}
124+
} finally {
125+
result = await getDeployResult();
126+
}
127+
} else {
128+
// check the deploy status
129+
result = await getDeployResult();
130+
}
71131

72132
const formatter = new DeployReportResultFormatter(result, {
73133
...deployOpts,

test/commands/deploy/metadata/report.nut.ts

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ import { assert, isObject } from '@salesforce/ts-types';
1212
import { expect } from 'chai';
1313
import { DeployResultJson } from '../../../../src/utils/types';
1414

15-
describe('deploy metadata report NUTs with source-dir', () => {
15+
describe('[project deploy report] NUTs with source-dir', () => {
1616
let testkit: SourceTestkit;
1717

18+
const orgAlias = 'reportTestOrg2';
19+
1820
before(async () => {
1921
testkit = await SourceTestkit.create({
2022
repository: 'https://github.com/salesforcecli/sample-project-multiple-packages.git',
2123
nut: __filename,
24+
scratchOrgs: [{ duration: 1, alias: orgAlias, config: path.join('config', 'project-scratch-def.json') }],
2225
});
2326
});
2427

@@ -28,63 +31,69 @@ describe('deploy metadata report NUTs with source-dir', () => {
2831

2932
describe('--use-most-recent', () => {
3033
it('should report most recently started deployment', async () => {
31-
await testkit.execute<DeployResultJson>('deploy:metadata', {
34+
await testkit.execute<DeployResultJson>('project deploy start', {
3235
args: '--source-dir force-app --async',
3336
json: true,
3437
exitCode: 0,
3538
});
3639

37-
const deploy = await testkit.execute<DeployResultJson>('deploy:metadata:report', {
40+
const deploy = await testkit.execute<DeployResultJson>('project deploy report', {
3841
args: '--use-most-recent',
3942
json: true,
4043
exitCode: 0,
4144
});
4245
assert(isObject(deploy));
4346
await testkit.expect.filesToBeDeployedViaResult(['force-app/**/*'], ['force-app/test/**/*'], deploy.result.files);
4447
});
48+
});
4549

46-
it.skip('should report most recently started deployment without specifying the flag', async () => {
47-
await testkit.execute<DeployResultJson>('deploy:metadata', {
48-
args: '--source-dir force-app --async',
50+
describe('--job-id', () => {
51+
it('should report the provided job id', async () => {
52+
const first = await testkit.execute<DeployResultJson>('project deploy start', {
53+
args: '--source-dir force-app --async --ignore-conflicts',
4954
json: true,
5055
exitCode: 0,
5156
});
52-
53-
const deploy = await testkit.execute<DeployResultJson>('deploy:metadata:report', {
57+
const deploy = await testkit.execute<DeployResultJson>('project deploy report', {
58+
args: `--job-id ${first?.result.id}`,
5459
json: true,
5560
exitCode: 0,
5661
});
5762
assert(isObject(deploy));
5863
await testkit.expect.filesToBeDeployedViaResult(['force-app/**/*'], ['force-app/test/**/*'], deploy.result.files);
5964
});
60-
});
6165

62-
describe('--job-id', () => {
63-
it('should report the provided job id', async () => {
64-
const first = await testkit.execute<DeployResultJson>('deploy:metadata', {
65-
args: '--source-dir force-app --async --ignore-conflicts',
66+
it('should report from specified target-org and job-id without deploy cache', async () => {
67+
const first = await testkit.execute<DeployResultJson>('project deploy start', {
68+
args: `--source-dir force-app --async --target-org ${orgAlias}`,
6669
json: true,
6770
exitCode: 0,
6871
});
69-
const deploy = await testkit.execute<DeployResultJson>('deploy:metadata:report', {
70-
args: `--job-id ${first?.result.id}`,
72+
73+
// delete the cache file so we can verify that reporting just with job-id and org works
74+
const deployCacheFilePath = path.resolve(testkit.projectDir, path.join('..', '.sf', 'deploy-cache.json'));
75+
fs.unlinkSync(deployCacheFilePath);
76+
assert(!fs.existsSync(deployCacheFilePath));
77+
78+
const deploy = await testkit.execute<DeployResultJson>('project deploy report', {
79+
args: `--job-id ${first?.result.id} --target-org ${orgAlias} --wait 9`,
7180
json: true,
7281
exitCode: 0,
7382
});
7483
assert(isObject(deploy));
75-
await testkit.expect.filesToBeDeployedViaResult(['force-app/**/*'], ['force-app/test/**/*'], deploy.result.files);
84+
await testkit.expect.filesToBeDeployed(['force-app/**/*'], ['force-app/test/**/*']);
7685
});
7786
});
7887

7988
describe('test flags', () => {
8089
it('should override the --output-dir', async () => {
81-
const first = await testkit.execute<DeployResultJson>('deploy:metadata', {
90+
const first = await testkit.execute<DeployResultJson>('project deploy start', {
8291
args: '--source-dir force-app --async --ignore-conflicts --test-level RunAllTestsInOrg --coverage-formatters html --junit --results-dir test-output',
8392
json: true,
8493
exitCode: 0,
8594
});
86-
const deploy = await testkit.execute<DeployResultJson>('deploy:metadata:report', {
87-
args: `--job-id ${first?.result.id} --coverage-formatters html --coverage-formatters text --junit --results-dir test-output-override`,
95+
const deploy = await testkit.execute<DeployResultJson>('project deploy report', {
96+
args: `--job-id ${first?.result.id} --coverage-formatters html --coverage-formatters text --junit --results-dir test-output-override --wait 9`,
8897
json: true,
8998
exitCode: 0,
9099
});

0 commit comments

Comments
 (0)