55import re
66import time
77from collections import defaultdict
8+ from typing import Optional
89
910from cumulusci .core .config import BaseProjectConfig , ScratchOrgConfig , TaskConfig
1011from cumulusci .core .exceptions import ProjectConfigNotFound
@@ -223,9 +224,10 @@ def retrieve_components(
223224 extra_package_xml_opts : dict ,
224225 namespace_tokenize : str ,
225226 api_version : str ,
226- project_config : BaseProjectConfig = None ,
227+ project_config : Optional [ BaseProjectConfig ] = None ,
227228 retrieve_complete_profile : bool = False ,
228229 capture_output : bool = False ,
230+ output_dir : Optional [str ] = None ,
229231):
230232 """Retrieve specified components from an org into a target folder.
231233
@@ -272,12 +274,20 @@ def retrieve_components(
272274 json .dump (
273275 {"packageDirectories" : [{"path" : "force-app" , "default" : True }]}, f
274276 )
275- sfdx (
276- "project convert mdapi" ,
277- log_note = "Converting to DX format" ,
278- args = ["-r" , target , "-d" , "force-app" ],
279- check_return = True ,
280- )
277+ convert_output_dir = output_dir if output_dir else "force-app"
278+ try :
279+ sfdx (
280+ "project convert mdapi" ,
281+ log_note = "Converting to DX format" ,
282+ args = ["-r" , target , "-d" , convert_output_dir ],
283+ check_return = True ,
284+ )
285+ except Exception as e :
286+ if "No results to format" in str (e ):
287+ raise Exception (
288+ f"No metadata found to convert in '{ target } '. Please check the folder path or specify a different output directory using the output_dir option."
289+ )
290+ raise
281291
282292 # If retrieve_complete_profile is True, separate the profiles from
283293 # components to retrieve complete profile
@@ -323,13 +333,21 @@ def retrieve_components(
323333 cls_retrieve_profile ()
324334 if md_format :
325335 # Convert back to metadata format
326- sfdx (
327- "project convert source" ,
328- log_note = "Converting back to metadata format" ,
329- args = ["-r" , "force-app" , "-d" , target ],
330- capture_output = capture_output ,
331- check_return = True ,
332- )
336+ convert_output_dir = output_dir if output_dir else target
337+ try :
338+ sfdx (
339+ "project convert source" ,
340+ log_note = "Converting back to metadata format" ,
341+ args = ["-r" , "force-app" , "-d" , convert_output_dir ],
342+ capture_output = capture_output ,
343+ check_return = True ,
344+ )
345+ except Exception as e :
346+ if "No results to format" in str (e ):
347+ raise Exception (
348+ f"No DX source found to convert in 'force-app'. Please check the output directory or folder path."
349+ )
350+ raise
333351
334352 # Reinject namespace tokens
335353 if namespace_tokenize :
@@ -410,6 +428,7 @@ def _run_task(self):
410428 self .logger .info ("{MemberType}: {MemberName}" .format (** change ))
411429
412430 target = os .path .realpath (self .options ["path" ])
431+ output_dir = self .options .get ("output_dir" )
413432 package_xml_opts = {}
414433 if self .options ["path" ] == "src" :
415434 package_xml_opts .update (
@@ -430,6 +449,7 @@ def _run_task(self):
430449 extra_package_xml_opts = package_xml_opts ,
431450 project_config = self .project_config ,
432451 retrieve_complete_profile = self .options ["retrieve_complete_profile" ],
452+ output_dir = output_dir ,
433453 )
434454
435455 if self .options ["snapshot" ]:
0 commit comments