88import * as os from 'os' ;
99import { flags , FlagsConfig } from '@salesforce/command' ;
1010import { Messages } from '@salesforce/core' ;
11- import { DeployResult , MetadataApiDeploy } from '@salesforce/source-deploy-retrieve' ;
11+ import { AsyncResult , DeployResult , MetadataApiDeploy } from '@salesforce/source-deploy-retrieve' ;
1212import { Duration } from '@salesforce/kit' ;
1313import { getString , isString } from '@salesforce/ts-types' ;
14- import { env } from '@salesforce/kit' ;
14+ import { env , once } from '@salesforce/kit' ;
1515import { RequestStatus } from '@salesforce/source-deploy-retrieve/lib/src/client/types' ;
1616import { DeployCommand } from '../../../deployCommand' ;
1717import { ComponentSetBuilder } from '../../../componentSetBuilder' ;
18- import {
19- DeployResultFormatter ,
20- DeployCommandResult ,
21- DeployCommandAsyncResult ,
22- } from '../../../formatters/deployResultFormatter' ;
18+ import { DeployResultFormatter , DeployCommandResult } from '../../../formatters/deployResultFormatter' ;
19+ import { DeployAsyncResultFormatter , DeployCommandAsyncResult } from '../../../formatters/deployAsyncResultFormatter' ;
2320
2421Messages . importMessagesDirectory ( __dirname ) ;
2522const messages = Messages . loadMessages ( '@salesforce/plugin-source' , 'deploy' ) ;
@@ -102,6 +99,12 @@ export class Deploy extends DeployCommand {
10299
103100 private isAsync = false ;
104101 private isRest = false ;
102+ private asyncDeployResult : AsyncResult ;
103+
104+ private updateDeployId = once ( ( id ) => {
105+ this . displayDeployId ( id ) ;
106+ this . setStash ( id ) ;
107+ } ) ;
105108
106109 public async run ( ) : Promise < DeployCommandResult | DeployCommandAsyncResult > {
107110 await this . deploy ( ) ;
@@ -114,15 +117,15 @@ export class Deploy extends DeployCommand {
114117 // 2. asynchronous - deploy metadata and immediately return.
115118 // 3. recent validation - deploy metadata that's already been validated by the org
116119 protected async deploy ( ) : Promise < void > {
117- this . isAsync = this . getFlag < Duration > ( 'wait' ) . quantity === 0 ;
120+ const waitDuration = this . getFlag < Duration > ( 'wait' ) ;
121+ this . isAsync = waitDuration . quantity === 0 ;
118122 this . isRest = await this . isRestDeploy ( ) ;
119123 this . ux . log ( `*** Deploying with ${ this . isRest ? 'REST' : 'SOAP' } API ***` ) ;
120124
121125 if ( this . flags . validateddeployrequestid ) {
122126 this . deployResult = await this . deployRecentValidation ( ) ;
123127 } else {
124- // the deployment involves a component set
125- const cs = await ComponentSetBuilder . build ( {
128+ this . componentSet = await ComponentSetBuilder . build ( {
126129 apiversion : this . getFlag < string > ( 'apiversion' ) ,
127130 sourcepath : this . getFlag < string [ ] > ( 'sourcepath' ) ,
128131 manifest : this . flags . manifest && {
@@ -135,57 +138,63 @@ export class Deploy extends DeployCommand {
135138 } ,
136139 } ) ;
137140 // fire predeploy event for sync and async deploys
138- await this . lifecycle . emit ( 'predeploy' , cs . toArray ( ) ) ;
139- if ( this . isAsync ) {
140- // This is an async deploy. We just kick off the request.
141- throw Error ( 'ASYNC DEPLOYS NOT IMPLEMENTED YET' ) ;
142- } else {
143- const deploy = cs . deploy ( {
144- usernameOrConnection : this . org . getUsername ( ) ,
145- apiOptions : {
146- ignoreWarnings : this . getFlag < boolean > ( 'ignorewarnings' , false ) ,
147- rollbackOnError : ! this . getFlag < boolean > ( 'ignoreerrors' , false ) ,
148- checkOnly : this . getFlag < boolean > ( 'checkonly' , false ) ,
149- runTests : this . getFlag < string [ ] > ( 'runtests' ) ,
150- testLevel : this . getFlag < TestLevel > ( 'testlevel' , 'NoTestRun' ) ,
151- } ,
152- } ) ;
141+ await this . lifecycle . emit ( 'predeploy' , this . componentSet . toArray ( ) ) ;
153142
143+ const deploy = await this . componentSet . deploy ( {
144+ usernameOrConnection : this . org . getUsername ( ) ,
145+ apiOptions : {
146+ ignoreWarnings : this . getFlag < boolean > ( 'ignorewarnings' , false ) ,
147+ rollbackOnError : ! this . getFlag < boolean > ( 'ignoreerrors' , false ) ,
148+ checkOnly : this . getFlag < boolean > ( 'checkonly' , false ) ,
149+ runTests : this . getFlag < string [ ] > ( 'runtests' ) ,
150+ testLevel : this . getFlag < TestLevel > ( 'testlevel' ) ,
151+ rest : this . isRest ,
152+ } ,
153+ } ) ;
154+ this . asyncDeployResult = { id : deploy . id } ;
155+ this . updateDeployId ( deploy . id ) ;
156+
157+ if ( ! this . isAsync ) {
154158 // if SFDX_USE_PROGRESS_BAR is unset or true (default true) AND we're not print JSON output
155159 if ( env . getBoolean ( 'SFDX_USE_PROGRESS_BAR' , true ) && ! this . isJsonOutput ( ) ) {
156160 this . initProgressBar ( ) ;
157161 this . progress ( deploy ) ;
158162 }
159-
160- this . deployResult = await deploy . start ( ) ;
163+ this . deployResult = await deploy . pollStatus ( 500 , waitDuration . seconds ) ;
161164 }
162165 }
163166
164- await this . lifecycle . emit ( 'postdeploy' , this . deployResult ) ;
165-
166- const deployId = getString ( this . deployResult , 'response.id' ) ;
167- if ( deployId ) {
168- this . displayDeployId ( deployId ) ;
169- const file = this . getStash ( ) ;
170- // TODO: I think we should stash the ID as soon as we know it.
171- this . logger . debug ( `Stashing deploy ID: ${ deployId } ` ) ;
172- await file . write ( { [ DeployCommand . STASH_KEY ] : { jobid : deployId } } ) ;
167+ if ( this . deployResult ) {
168+ // Only fire the postdeploy event when we have results. I.e., not async.
169+ await this . lifecycle . emit ( 'postdeploy' , this . deployResult ) ;
173170 }
174171 }
175172
173+ /**
174+ * Checks the response status to determine whether the deploy was successful.
175+ * Async deploys are successful unless an error is thrown, which resolves as
176+ * unsuccessful in oclif.
177+ */
176178 protected resolveSuccess ( ) : void {
177- const status = getString ( this . deployResult , 'response.status' ) ;
178- if ( status !== RequestStatus . Succeeded ) {
179- this . setExitCode ( 1 ) ;
179+ if ( ! this . isAsync ) {
180+ const status = getString ( this . deployResult , 'response.status' ) ;
181+ if ( status !== RequestStatus . Succeeded ) {
182+ this . setExitCode ( 1 ) ;
183+ }
180184 }
181185 }
182186
183187 protected formatResult ( ) : DeployCommandResult | DeployCommandAsyncResult {
184188 const formatterOptions = {
185189 verbose : this . getFlag < boolean > ( 'verbose' , false ) ,
186- async : this . isAsync ,
187190 } ;
188- const formatter = new DeployResultFormatter ( this . logger , this . ux , formatterOptions , this . deployResult ) ;
191+
192+ let formatter : DeployAsyncResultFormatter | DeployResultFormatter ;
193+ if ( this . isAsync ) {
194+ formatter = new DeployAsyncResultFormatter ( this . logger , this . ux , formatterOptions , this . asyncDeployResult ) ;
195+ } else {
196+ formatter = new DeployResultFormatter ( this . logger , this . ux , formatterOptions , this . deployResult ) ;
197+ }
189198
190199 // Only display results to console when JSON flag is unset.
191200 if ( ! this . isJsonOutput ( ) ) {
@@ -199,15 +208,8 @@ export class Deploy extends DeployCommand {
199208 const conn = this . org . getConnection ( ) ;
200209 const id = this . getFlag < string > ( 'validateddeployrequestid' ) ;
201210
202- // TODO: This is an async call so we need to poll unless `--wait 0`
203- // See mdapiCheckStatusApi.ts for the toolbelt polling impl.
204211 const response = await conn . deployRecentValidation ( { id, rest : this . isRest } ) ;
205212
206- if ( ! this . isAsync ) {
207- // Remove this and add polling if we need to poll in the plugin.
208- throw Error ( 'deployRecentValidation polling not yet implemented' ) ;
209- }
210-
211213 // This is the deploy ID of the deployRecentValidation response, not
212214 // the already validated deploy ID (i.e., validateddeployrequestid).
213215 let validatedDeployId : string ;
@@ -218,26 +220,27 @@ export class Deploy extends DeployCommand {
218220 // REST API
219221 validatedDeployId = ( response as { id : string } ) . id ;
220222 }
223+ this . updateDeployId ( validatedDeployId ) ;
221224
222- return this . report ( validatedDeployId ) ;
225+ return this . isAsync ? this . report ( validatedDeployId ) : this . poll ( validatedDeployId ) ;
223226 }
224227
225228 private progress ( deploy : MetadataApiDeploy ) : void {
226- let started = false ;
229+ const startProgressBar = once ( ( componentTotal : number ) => {
230+ this . progressBar . start ( componentTotal ) ;
231+ } ) ;
232+
227233 deploy . onUpdate ( ( data ) => {
228234 // the numCompTot. isn't computed right away, wait to start until we know how many we have
229- if ( data . numberComponentsTotal && ! started ) {
230- this . displayDeployId ( data . id ) ;
231- this . progressBar . start ( data . numberComponentsTotal + data . numberTestsTotal ) ;
232- started = true ;
235+ if ( data . numberComponentsTotal ) {
236+ startProgressBar ( data . numberComponentsTotal + data . numberTestsTotal ) ;
237+ this . progressBar . update ( data . numberComponentsDeployed + data . numberTestsCompleted ) ;
233238 }
234239
235240 // the numTestsTot. isn't computed until validated as tests by the server, update the PB once we know
236- if ( data . numberTestsTotal ) {
241+ if ( data . numberTestsTotal && data . numberComponentsTotal ) {
237242 this . progressBar . setTotal ( data . numberComponentsTotal + data . numberTestsTotal ) ;
238243 }
239-
240- this . progressBar . update ( data . numberComponentsDeployed + data . numberTestsCompleted ) ;
241244 } ) ;
242245
243246 // any thing else should stop the progress bar
0 commit comments