88import { rm } from 'fs/promises' ;
99import { join , resolve } from 'path' ;
1010
11- import { EnvironmentVariable , Messages , OrgConfigProperties , SfError } from '@salesforce/core' ;
12- import { RetrieveResult , ComponentSetBuilder , RetrieveSetOptions } from '@salesforce/source-deploy-retrieve' ;
13-
11+ import { EnvironmentVariable , Messages , OrgConfigProperties , SfError , SfProject } from '@salesforce/core' ;
12+ import {
13+ RetrieveResult ,
14+ ComponentSetBuilder ,
15+ RetrieveSetOptions ,
16+ ComponentSet ,
17+ FileResponse ,
18+ } from '@salesforce/source-deploy-retrieve' ;
1419import { SfCommand , toHelpSection , Flags } from '@salesforce/sf-plugins-core' ;
1520import { getString } from '@salesforce/ts-types' ;
1621import { SourceTracking , SourceConflictError } from '@salesforce/source-tracking' ;
1722import { Duration } from '@salesforce/kit' ;
23+ import { Interfaces } from '@oclif/core' ;
24+
1825import { DEFAULT_ZIP_FILE_NAME , ensuredDirFlag , zipFileFlag } from '../../../utils/flags' ;
1926import { RetrieveResultFormatter } from '../../../formatters/retrieveResultFormatter' ;
2027import { MetadataRetrieveResultFormatter } from '../../../formatters/metadataRetrieveResultFormatter' ;
@@ -26,11 +33,11 @@ Messages.importMessagesDirectory(__dirname);
2633const messages = Messages . loadMessages ( '@salesforce/plugin-deploy-retrieve' , 'retrieve.metadata' ) ;
2734const mdTransferMessages = Messages . loadMessages ( '@salesforce/plugin-deploy-retrieve' , 'metadata.transfer' ) ;
2835
36+ type Format = 'source' | 'metadata' ;
2937export default class RetrieveMetadata extends SfCommand < RetrieveResultJson > {
3038 public static readonly summary = messages . getMessage ( 'summary' ) ;
3139 public static readonly description = messages . getMessage ( 'description' ) ;
3240 public static readonly examples = messages . getMessages ( 'examples' ) ;
33- public static readonly requiresProject = true ;
3441 public static readonly aliases = [ 'retrieve:metadata' ] ;
3542 public static readonly deprecateAliases = true ;
3643
@@ -127,79 +134,33 @@ export default class RetrieveMetadata extends SfCommand<RetrieveResultJson> {
127134
128135 protected retrieveResult ! : RetrieveResult ;
129136
130- // eslint-disable-next-line complexity
131137 public async run ( ) : Promise < RetrieveResultJson > {
132138 const { flags } = await this . parse ( RetrieveMetadata ) ;
139+ const format : Format = flags [ 'target-metadata-dir' ] ? 'metadata' : 'source' ;
140+ const zipFileName = flags [ 'zip-file-name' ] ?? DEFAULT_ZIP_FILE_NAME ;
133141
134142 this . spinner . start ( messages . getMessage ( 'spinner.start' ) ) ;
135- const format = flags [ 'target-metadata-dir' ] ? 'metadata' : 'source' ;
136- const stl = await SourceTracking . create ( {
137- org : flags [ 'target-org' ] ,
138- project : this . project ,
139- subscribeSDREvents : true ,
140- ignoreConflicts : format === 'metadata' || flags [ 'ignore-conflicts' ] ,
141- } ) ;
142- const isChanges = ! flags [ 'source-dir' ] && ! flags [ 'manifest' ] && ! flags [ 'metadata' ] ;
143- const { componentSetFromNonDeletes, fileResponsesFromDelete } = isChanges
144- ? await stl . maybeApplyRemoteDeletesToLocal ( true )
145- : {
146- componentSetFromNonDeletes : await ComponentSetBuilder . build ( {
147- apiversion : flags [ 'api-version' ] ,
148- sourcepath : flags [ 'source-dir' ] ,
149- packagenames : flags [ 'package-name' ] ,
150- ...( flags . manifest
151- ? {
152- manifest : {
153- manifestPath : flags . manifest ,
154- directoryPaths : await getPackageDirs ( ) ,
155- } ,
156- }
157- : { } ) ,
158- ...( flags . metadata
159- ? { metadata : { metadataEntries : flags . metadata , directoryPaths : await getPackageDirs ( ) } }
160- : { } ) ,
161- } ) ,
162- fileResponsesFromDelete : [ ] ,
163- } ;
164- // stl sets version based on config/files--if the command overrides it, we need to update
165- if ( isChanges && flags [ 'api-version' ] ) {
166- componentSetFromNonDeletes . apiVersion = flags [ 'api-version' ] ;
167- }
143+
144+ const { componentSetFromNonDeletes, fileResponsesFromDelete = [ ] } = await buildRetrieveAndDeleteTargets (
145+ flags ,
146+ format
147+ ) ;
148+ const retrieveOpts = await buildRetrieveOptions ( flags , format , zipFileName ) ;
149+
168150 this . spinner . status = messages . getMessage ( 'spinner.sending' , [
169151 componentSetFromNonDeletes . sourceApiVersion ?? componentSetFromNonDeletes . apiVersion ,
170152 ] ) ;
171153
172- const zipFileName = flags [ 'zip-file-name' ] ?? DEFAULT_ZIP_FILE_NAME ;
173- const retrieveOpts : RetrieveSetOptions = {
174- usernameOrConnection :
175- flags [ 'target-org' ] . getUsername ( ) ?? flags [ 'target-org' ] . getConnection ( flags [ 'api-version' ] ) ,
176- merge : true ,
177- output : this . project . getDefaultPackage ( ) . fullPath ,
178- packageOptions : flags [ 'package-name' ] ,
179- format,
180- ...( format === 'metadata'
181- ? {
182- singlePackage : flags [ 'single-package' ] ,
183- unzip : flags . unzip ,
184- zipFileName,
185- output : flags [ 'target-metadata-dir' ] ,
186- }
187- : { } ) ,
188- } ;
189-
190154 const retrieve = await componentSetFromNonDeletes . retrieve ( retrieveOpts ) ;
191155
192156 this . spinner . status = messages . getMessage ( 'spinner.polling' ) ;
193157
194158 retrieve . onUpdate ( ( data ) => {
195159 this . spinner . status = mdTransferMessages . getMessage ( data . status ) ;
196160 } ) ;
197-
198161 // any thing else should stop the progress bar
199162 retrieve . onFinish ( ( data ) => this . spinner . stop ( mdTransferMessages . getMessage ( data . response . status ) ) ) ;
200-
201163 retrieve . onCancel ( ( data ) => this . spinner . stop ( mdTransferMessages . getMessage ( data ?. status ?? 'Canceled' ) ) ) ;
202-
203164 retrieve . onError ( ( error : Error ) => {
204165 this . spinner . stop ( error . name ) ;
205166 throw error ;
@@ -242,17 +203,102 @@ export default class RetrieveMetadata extends SfCommand<RetrieveResultJson> {
242203 }
243204
244205 protected catch ( error : Error | SfError ) : Promise < SfCommand . Error > {
245- if ( error instanceof SourceConflictError ) {
246- if ( ! this . jsonEnabled ( ) ) {
247- writeConflictTable ( error . data ) ;
248- // set the message and add plugin-specific actions
249- return super . catch ( {
250- ...error ,
251- message : messages . getMessage ( 'error.Conflicts' ) ,
252- actions : messages . getMessages ( 'error.Conflicts.Actions' , [ this . config . bin ] ) ,
253- } ) ;
254- }
206+ if ( ! this . jsonEnabled ( ) && error instanceof SourceConflictError ) {
207+ writeConflictTable ( error . data ) ;
208+ // set the message and add plugin-specific actions
209+ return super . catch ( {
210+ ...error ,
211+ message : messages . getMessage ( 'error.Conflicts' ) ,
212+ actions : messages . getMessages ( 'error.Conflicts.Actions' , [ this . config . bin ] ) ,
213+ } ) ;
255214 }
215+
256216 return super . catch ( error ) ;
257217 }
258218}
219+
220+ type RetrieveAndDeleteTargets = {
221+ /** componentSet that can be used to retrieve known changes */
222+ componentSetFromNonDeletes : ComponentSet ;
223+ /** optional Array of artificially constructed FileResponses from the deletion of local files */
224+ fileResponsesFromDelete ?: FileResponse [ ] ;
225+ } ;
226+
227+ const buildRetrieveAndDeleteTargets = async (
228+ flags : Interfaces . InferredFlags < typeof RetrieveMetadata . flags > ,
229+ format : Format
230+ ) : Promise < RetrieveAndDeleteTargets > => {
231+ const isChanges = ! flags [ 'source-dir' ] && ! flags [ 'manifest' ] && ! flags [ 'metadata' ] && ! flags [ 'target-metadata-dir' ] ;
232+
233+ if ( isChanges ) {
234+ const stl = await SourceTracking . create ( {
235+ org : flags [ 'target-org' ] ,
236+ project : await SfProject . resolve ( ) ,
237+ subscribeSDREvents : true ,
238+ ignoreConflicts : format === 'metadata' || flags [ 'ignore-conflicts' ] ,
239+ } ) ;
240+ const result = await stl . maybeApplyRemoteDeletesToLocal ( true ) ;
241+ // STL returns a componentSet that gets these from the project/config.
242+ // if the command has a flag, we'll override
243+ if ( flags [ 'api-version' ] ) {
244+ result . componentSetFromNonDeletes . apiVersion = flags [ 'api-version' ] ;
245+ }
246+ return result ;
247+ } else {
248+ return {
249+ componentSetFromNonDeletes : await ComponentSetBuilder . build ( {
250+ apiversion : flags [ 'api-version' ] ,
251+ sourcepath : flags [ 'source-dir' ] ,
252+ packagenames : flags [ 'package-name' ] ,
253+ ...( flags . manifest
254+ ? {
255+ manifest : {
256+ manifestPath : flags . manifest ,
257+ // if mdapi format, there might not be a project
258+ directoryPaths : format === 'metadata' ? [ ] : await getPackageDirs ( ) ,
259+ } ,
260+ }
261+ : { } ) ,
262+ ...( flags . metadata
263+ ? {
264+ metadata : {
265+ metadataEntries : flags . metadata ,
266+ // if mdapi format, there might not be a project
267+ directoryPaths : format === 'metadata' ? [ ] : await getPackageDirs ( ) ,
268+ } ,
269+ }
270+ : { } ) ,
271+ } ) ,
272+ } ;
273+ }
274+ } ;
275+
276+ /**
277+ *
278+ *
279+ * @param flags
280+ * @param project
281+ * @param format 'metadata' or 'source'
282+ * @returns RetrieveSetOptions (an object that can be passed as the options for a ComponentSet retrieve)
283+ */
284+ const buildRetrieveOptions = async (
285+ flags : Interfaces . InferredFlags < typeof RetrieveMetadata . flags > ,
286+ format : Format ,
287+ zipFileName : string
288+ ) : Promise < RetrieveSetOptions > => ( {
289+ usernameOrConnection : flags [ 'target-org' ] . getUsername ( ) ?? flags [ 'target-org' ] . getConnection ( flags [ 'api-version' ] ) ,
290+ merge : true ,
291+ packageOptions : flags [ 'package-name' ] ,
292+ format,
293+ ...( format === 'metadata'
294+ ? {
295+ singlePackage : flags [ 'single-package' ] ,
296+ unzip : flags . unzip ,
297+ zipFileName,
298+ // known to exist because that's how `format` becomes 'metadata'
299+ output : flags [ 'target-metadata-dir' ] as string ,
300+ }
301+ : {
302+ output : ( await SfProject . resolve ( ) ) . getDefaultPackage ( ) . fullPath ,
303+ } ) ,
304+ } ) ;
0 commit comments