11import argparse
22import os
3+ import re
34from pathlib import Path
45
56from rich .console import Group
@@ -18,79 +19,65 @@ class Command(BaseFlutterCommand):
1819
1920 def __init__ (self , parser : argparse .ArgumentParser ) -> None :
2021 super ().__init__ (parser )
21- self .launch_target = None
22+ self .action = None
23+ self .emulator_target = None
2224 self .cold_boot = False
23- self .create_emulator = False
24- self .delete_emulator = None
2525 self .emulator_name = None
2626
2727 def add_arguments (self , parser : argparse .ArgumentParser ) -> None :
2828 parser .add_argument (
29- "--launch" ,
30- dest = "launch" ,
29+ "action" ,
30+ type = str .lower ,
31+ nargs = "?" ,
32+ choices = ["start" , "create" , "delete" ],
33+ help = "Action to perform: start an emulator, create a new one, "
34+ "or delete it." ,
35+ )
36+ parser .add_argument (
37+ "emulator" ,
3138 type = str ,
32- help = "Launch a specific emulator by ID or name." ,
39+ nargs = "?" ,
40+ help = "Emulator ID or name (required for start, create, and delete)." ,
3341 )
3442 parser .add_argument (
3543 "--cold" ,
3644 dest = "cold" ,
3745 action = "store_true" ,
3846 default = False ,
39- help = "Cold boot the emulator when launching." ,
40- )
41- parser .add_argument (
42- "--create" ,
43- dest = "create" ,
44- action = "store_true" ,
45- default = False ,
46- help = "Create a new emulator using Flutter defaults." ,
47- )
48- parser .add_argument (
49- "--delete" ,
50- dest = "delete" ,
51- type = str ,
52- help = "Delete an emulator by ID or name (Android only)." ,
53- )
54- parser .add_argument (
55- "--name" ,
56- dest = "name" ,
57- type = str ,
58- help = "Name for the new emulator (used with --create)." ,
47+ help = "Cold boot the emulator when starting." ,
5948 )
6049 super ().add_arguments (parser )
6150
6251 def handle (self , options : argparse .Namespace ) -> None :
6352 super ().handle (options )
6453 if self .options :
65- self .launch_target = self .options .launch
54+ self .action = self .options .action
55+ self .emulator_target = self .options .emulator
56+ self .emulator_name = self .options .emulator
6657 self .cold_boot = bool (self .options .cold )
67- self .create_emulator = bool (self .options .create )
68- self .delete_emulator = self .options .delete
69- self .emulator_name = self .options .name
70-
71- selected_actions = [
72- bool (self .create_emulator ),
73- bool (self .launch_target ),
74- bool (self .delete_emulator ),
75- ]
76- if sum (selected_actions ) > 1 :
77- msg = "Please choose only one action: --create, --launch, or --delete."
78- console .log (msg , style = verbose2_style )
79- self .cleanup (1 , msg )
8058
8159 self .status = console .status (
8260 "[bold blue]Initializing environment..." ,
8361 spinner = "bouncingBall" ,
8462 )
8563 with Live (Group (self .status , self .progress ), console = console ) as self .live :
8664 self .initialize_command ()
87- if self .delete_emulator :
65+ if self .action == "delete" :
66+ if not self .emulator_target :
67+ self .skip_flutter_doctor = True
68+ self .cleanup (1 , "Provide emulator ID or name to delete." )
8869 self ._delete_emulator ()
8970 return
90- if self .create_emulator :
71+ if self .action == "create" :
72+ if not self .emulator_name :
73+ self .skip_flutter_doctor = True
74+ self .cleanup (1 , "Provide emulator name to create." )
9175 self ._create_emulator ()
9276 return
93- if self .launch_target :
77+ if self .action == "start" :
78+ if not self .emulator_target :
79+ self .skip_flutter_doctor = True
80+ self .cleanup (1 , "Provide emulator ID or name to start." )
9481 self ._launch_emulator ()
9582 return
9683 self ._list_emulators ()
@@ -131,7 +118,7 @@ def _list_emulators(self):
131118 emulators = self ._parse_emulators_output (output )
132119 if not emulators :
133120 footer = (
134- '\n Run [green]"flet emulators -- create"[/green] '
121+ '\n Run [green]"flet emulators create <name> "[/green] '
135122 "to create a new emulator.\n "
136123 )
137124 self .cleanup (0 , Group (Panel ("No emulators found." ), footer ), no_border = True )
@@ -157,11 +144,11 @@ def _list_emulators(self):
157144 footer = (
158145 "\n "
159146 "Launch an emulator with "
160- '[green]"flet emulators --launch <emulator-id>"[/green].\n '
147+ '[green]"flet emulators start <emulator-id>"[/green].\n '
161148 "Create a new emulator with "
162- '[green]"flet emulators -- create [--name <name>] "[/green].\n '
149+ '[green]"flet emulators create <name>"[/green].\n '
163150 "Delete an Android emulator with "
164- '[green]"flet emulators -- delete <emulator-id>"[/green].\n '
151+ '[green]"flet emulators delete <emulator-id>"[/green].\n '
165152 "\n "
166153 "You can find more information on managing emulators at the links below:\n "
167154 " https://developer.android.com/studio/run/managing-avds\n "
@@ -171,15 +158,15 @@ def _list_emulators(self):
171158 self .cleanup (0 , message = Group (table , footer ), no_border = True )
172159
173160 def _launch_emulator (self ):
174- assert self .launch_target
161+ assert self .emulator_target
175162 self .update_status (
176- f"[bold blue]Launching emulator [cyan]{ self .launch_target } [/cyan]..."
163+ f"[bold blue]Starting emulator [cyan]{ self .emulator_target } [/cyan]..."
177164 )
178165 args = [
179166 self .flutter_exe ,
180167 "emulators" ,
181168 "--launch" ,
182- self .launch_target ,
169+ self .emulator_target ,
183170 "--no-version-check" ,
184171 "--suppress-analytics" ,
185172 ]
@@ -200,7 +187,7 @@ def _launch_emulator(self):
200187 (
201188 error_output
202189 if error_output
203- else f"Failed to launch emulator '{ self .launch_target } '."
190+ else f"Failed to start emulator '{ self .emulator_target } '."
204191 ),
205192 )
206193 return
@@ -209,12 +196,12 @@ def _launch_emulator(self):
209196 console .log (output , style = verbose2_style )
210197
211198 mode = " (cold boot)" if self .cold_boot else ""
212- self .cleanup (0 , f"Emulator [cyan]{ self .launch_target } [/cyan] launched { mode } ." )
199+ self .cleanup (0 , f"Emulator [cyan]{ self .emulator_target } [/cyan] started { mode } ." )
213200
214201 def _delete_emulator (self ):
215- assert self .delete_emulator
202+ assert self .emulator_target
216203 self .update_status (
217- f"[bold blue]Deleting emulator [cyan]{ self .delete_emulator } [/cyan]..."
204+ f"[bold blue]Deleting emulator [cyan]{ self .emulator_target } [/cyan]..."
218205 )
219206 home_dir = self .env .get ("ANDROID_HOME" ) or AndroidSDK .android_home_dir ()
220207 if not home_dir :
@@ -227,18 +214,25 @@ def _delete_emulator(self):
227214 )
228215
229216 try :
230- sdk .delete_avd (Path (home_dir ), self .delete_emulator )
217+ sdk .delete_avd (Path (home_dir ), self .emulator_target )
231218 except Exception as exc : # pragma: no cover - defensive
232219 self .skip_flutter_doctor = True
233220 self .cleanup (
234- 1 , f"Failed to delete emulator '{ self .delete_emulator } ': { exc } "
221+ 1 , f"Failed to delete emulator '{ self .emulator_target } ': { exc } "
235222 )
236223 return
237224
238- self .cleanup (0 , f"Deleted emulator [cyan]{ self .delete_emulator } [/cyan]." )
225+ self .cleanup (0 , f"Deleted emulator [cyan]{ self .emulator_target } [/cyan]." )
239226
240227 def _create_emulator (self ):
241228 self .update_status ("[bold blue]Creating emulator..." )
229+ if not self ._is_valid_emulator_name (self .emulator_name ):
230+ self .skip_flutter_doctor = True
231+ self .cleanup (
232+ 1 ,
233+ "Emulator name is invalid. Allowed characters: a-z A-Z 0-9 . _ -" ,
234+ )
235+
242236 args = [
243237 self .flutter_exe ,
244238 "emulators" ,
@@ -255,25 +249,33 @@ def _create_emulator(self):
255249 capture_output = True ,
256250 )
257251 output = create_result .stdout or ""
258- if create_result .returncode != 0 :
259- error_output = create_result .stderr or output
252+ error_output = create_result .stderr or output
253+ invalid_name = (
254+ "contains invalid characters" in (error_output or "" ).lower ()
255+ or "contains invalid characters" in output .lower ()
256+ )
257+ exit_code = create_result .returncode or (1 if invalid_name else 0 )
258+ if exit_code != 0 :
260259 self .cleanup (
261- create_result . returncode ,
260+ exit_code ,
262261 error_output or "Failed to create emulator." ,
263262 )
264263 return
265264
266265 if output and self .verbose >= 1 :
267266 console .log (output , style = verbose2_style )
268267
269- created_name = self .emulator_name or "emulator"
268+ created_name = self .emulator_name
270269 self .cleanup (
271270 0 ,
272271 f"Created emulator [cyan]{ created_name } [/cyan]. "
273272 "Use `flet emulators` to list it or "
274- f"`flet emulators --launch { created_name } ` to start it." ,
273+ f"`flet emulators start { created_name } ` to start it." ,
275274 )
276275
276+ def _is_valid_emulator_name (self , name : str ) -> bool :
277+ return bool (re .match (r"^[A-Za-z0-9._-]+$" , name or "" ))
278+
277279 def _parse_emulators_output (self , output : str ) -> list [dict ]:
278280 emulators = []
279281 for raw_line in output .splitlines ():
0 commit comments