flutter-setup 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flutter_setup/__init__.py +0 -0
- flutter_setup/bootstrap.py +904 -0
- flutter_setup/cicd_generator.py +814 -0
- flutter_setup/cli.py +662 -0
- flutter_setup/config.py +135 -0
- flutter_setup/config_manager.py +159 -0
- flutter_setup/core.py +204 -0
- flutter_setup/exceptions.py +37 -0
- flutter_setup/flutter_manager.py +579 -0
- flutter_setup/platform.py +15 -0
- flutter_setup/prerequisites.py +39 -0
- flutter_setup/prerequisites_linux.py +140 -0
- flutter_setup/prerequisites_macos.py +226 -0
- flutter_setup/project_creator.py +82 -0
- flutter_setup-2.1.0.dist-info/METADATA +365 -0
- flutter_setup-2.1.0.dist-info/RECORD +20 -0
- flutter_setup-2.1.0.dist-info/WHEEL +5 -0
- flutter_setup-2.1.0.dist-info/entry_points.txt +2 -0
- flutter_setup-2.1.0.dist-info/licenses/LICENSE +21 -0
- flutter_setup-2.1.0.dist-info/top_level.txt +1 -0
flutter_setup/cli.py
ADDED
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Flutter Setup CLI - Main entry point."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, cast
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
|
|
12
|
+
from .core import FlutterSetup
|
|
13
|
+
from .config import (
|
|
14
|
+
Config,
|
|
15
|
+
FlutterChannel,
|
|
16
|
+
TemplateType,
|
|
17
|
+
IosLanguage,
|
|
18
|
+
AndroidLanguage,
|
|
19
|
+
UpdateMode,
|
|
20
|
+
Architecture,
|
|
21
|
+
Database,
|
|
22
|
+
Testing,
|
|
23
|
+
AuthProvider,
|
|
24
|
+
CloudDatabase,
|
|
25
|
+
NotificationsProvider,
|
|
26
|
+
)
|
|
27
|
+
from .config_manager import ConfigManager
|
|
28
|
+
from .exceptions import FlutterSetupError
|
|
29
|
+
from .prerequisites import PrerequisitesManager
|
|
30
|
+
from .flutter_manager import FlutterManager
|
|
31
|
+
from .platform import detect_runtime_platform
|
|
32
|
+
|
|
33
|
+
console = Console()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _is_interactive() -> bool:
|
|
37
|
+
"""Return True when stdin is a TTY (interactive session)."""
|
|
38
|
+
return sys.stdin.isatty()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def print_banner() -> None:
|
|
42
|
+
"""Print the application banner."""
|
|
43
|
+
banner = """
|
|
44
|
+
[bold blue]Flutter Development Environment Setup[/bold blue]
|
|
45
|
+
[dim]Automated Flutter development environment setup for macOS and Linux[/dim]
|
|
46
|
+
"""
|
|
47
|
+
console.print(Panel(banner, border_style="blue"))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@click.group(invoke_without_command=True)
|
|
51
|
+
@click.pass_context
|
|
52
|
+
def cli(ctx: click.Context) -> None:
|
|
53
|
+
"""Flutter Development Environment Setup CLI."""
|
|
54
|
+
# If no subcommand provided, show banner and help
|
|
55
|
+
if ctx.invoked_subcommand is None:
|
|
56
|
+
print_banner()
|
|
57
|
+
click.echo(ctx.get_help())
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@cli.command("init")
|
|
61
|
+
@click.option(
|
|
62
|
+
"--force",
|
|
63
|
+
is_flag=True,
|
|
64
|
+
help="Overwrite existing config file if it exists",
|
|
65
|
+
)
|
|
66
|
+
def init_config(force: bool) -> None:
|
|
67
|
+
"""Initialize the configuration file interactively."""
|
|
68
|
+
config_manager = ConfigManager()
|
|
69
|
+
config_manager.ensure_config_dir()
|
|
70
|
+
|
|
71
|
+
# Load existing config if it exists
|
|
72
|
+
existing_config = None
|
|
73
|
+
if config_manager.config_file.exists():
|
|
74
|
+
if force:
|
|
75
|
+
config_manager.config_file.unlink()
|
|
76
|
+
console.print(
|
|
77
|
+
f"[yellow]Deleted existing config: {config_manager.config_file}[/yellow]\n"
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
console.print(
|
|
81
|
+
f"[yellow]⚠️ Config file already exists at: {config_manager.config_file}[/yellow]"
|
|
82
|
+
)
|
|
83
|
+
console.print("[dim]Loading existing configuration for editing...[/dim]\n")
|
|
84
|
+
existing_config = config_manager.load_config()
|
|
85
|
+
|
|
86
|
+
# Start interactive configuration
|
|
87
|
+
console.print("[bold blue]Flutter Setup Configuration[/bold blue]\n")
|
|
88
|
+
|
|
89
|
+
# 1. Flutter Location
|
|
90
|
+
console.print("[bold]1. Flutter SDK Location[/bold]")
|
|
91
|
+
if existing_config:
|
|
92
|
+
current_location = existing_config.get("flutter", {}).get("location", "")
|
|
93
|
+
console.print(f"[dim]Current: {current_location}[/dim]")
|
|
94
|
+
else:
|
|
95
|
+
# Try to detect Flutter location
|
|
96
|
+
detected = config_manager.detect_flutter_location()
|
|
97
|
+
if detected:
|
|
98
|
+
console.print(f"[green]✓ Detected Flutter at: {detected}[/green]")
|
|
99
|
+
current_location = str(detected)
|
|
100
|
+
else:
|
|
101
|
+
default_location = str(Path.home() / "development" / "flutter")
|
|
102
|
+
console.print(f"[dim]Default: {default_location}[/dim]")
|
|
103
|
+
current_location = default_location
|
|
104
|
+
|
|
105
|
+
flutter_location_input = click.prompt(
|
|
106
|
+
"Flutter location",
|
|
107
|
+
default=current_location,
|
|
108
|
+
type=str,
|
|
109
|
+
)
|
|
110
|
+
flutter_location = Path(flutter_location_input).expanduser().resolve()
|
|
111
|
+
|
|
112
|
+
# Validate Flutter location
|
|
113
|
+
if not flutter_location.exists():
|
|
114
|
+
console.print(
|
|
115
|
+
"[yellow]⚠️ Warning: Path does not exist. It will be created when Flutter is installed.[/yellow]"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# 2. Flutter Channel
|
|
119
|
+
console.print("\n[bold]2. Flutter Channel[/bold]")
|
|
120
|
+
if existing_config:
|
|
121
|
+
current_channel = existing_config.get("flutter", {}).get("channel", "stable")
|
|
122
|
+
console.print(f"[dim]Current: {current_channel}[/dim]")
|
|
123
|
+
else:
|
|
124
|
+
current_channel = "stable"
|
|
125
|
+
|
|
126
|
+
channel = click.prompt(
|
|
127
|
+
"Flutter channel",
|
|
128
|
+
default=current_channel,
|
|
129
|
+
type=click.Choice(["stable", "beta"], case_sensitive=False),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# 3. Organization ID
|
|
133
|
+
console.print("\n[bold]3. Organization ID[/bold]")
|
|
134
|
+
if existing_config:
|
|
135
|
+
current_org = existing_config.get("project", {}).get("org", "com.example")
|
|
136
|
+
console.print(f"[dim]Current: {current_org}[/dim]")
|
|
137
|
+
else:
|
|
138
|
+
current_org = "com.example"
|
|
139
|
+
|
|
140
|
+
org = click.prompt(
|
|
141
|
+
"Organization ID (e.g., com.example, com.mycompany)",
|
|
142
|
+
default=current_org,
|
|
143
|
+
type=str,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Build the config, preserving any existing project settings not covered by init prompts
|
|
147
|
+
existing_project = existing_config.get("project", {}) if existing_config else {}
|
|
148
|
+
config = {
|
|
149
|
+
"flutter": {
|
|
150
|
+
"location": str(flutter_location),
|
|
151
|
+
"channel": channel.lower(),
|
|
152
|
+
"update_mode": (
|
|
153
|
+
existing_config.get("flutter", {}).get("update_mode", "skip")
|
|
154
|
+
if existing_config
|
|
155
|
+
else "skip"
|
|
156
|
+
),
|
|
157
|
+
},
|
|
158
|
+
"project": {
|
|
159
|
+
"org": org,
|
|
160
|
+
"template": existing_project.get("template", "app"),
|
|
161
|
+
# setup-owned fields: only carry forward if already written by a prior setup run
|
|
162
|
+
**{
|
|
163
|
+
k: existing_project[k]
|
|
164
|
+
for k in [
|
|
165
|
+
"architecture",
|
|
166
|
+
"database",
|
|
167
|
+
"testing",
|
|
168
|
+
"auth_provider",
|
|
169
|
+
"cloud_database",
|
|
170
|
+
"notifications_provider",
|
|
171
|
+
"ios_language",
|
|
172
|
+
"android_language",
|
|
173
|
+
]
|
|
174
|
+
if k in existing_project
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# Save the config
|
|
180
|
+
config_manager.save_config(config)
|
|
181
|
+
|
|
182
|
+
# Show summary
|
|
183
|
+
console.print("\n[green]✅ Configuration saved![/green]")
|
|
184
|
+
console.print("\n[bold]Config file location:[/bold]")
|
|
185
|
+
console.print(f" {config_manager.config_file}")
|
|
186
|
+
console.print("\n[bold]Configuration summary:[/bold]")
|
|
187
|
+
console.print(f" Flutter location: {config['flutter']['location']}")
|
|
188
|
+
console.print(f" Flutter channel: {config['flutter']['channel']}")
|
|
189
|
+
console.print(f" Organization: {config['project']['org']}")
|
|
190
|
+
console.print(
|
|
191
|
+
"\n[dim]You can edit this file directly or run 'flutter-setup init' again to update it.[/dim]"
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@cli.command("check")
|
|
196
|
+
@click.option(
|
|
197
|
+
"--verbose",
|
|
198
|
+
"-v",
|
|
199
|
+
is_flag=True,
|
|
200
|
+
help="Enable verbose output",
|
|
201
|
+
)
|
|
202
|
+
def check_command(verbose: bool) -> None:
|
|
203
|
+
"""Check that prerequisites and Flutter SDK are ready and updated."""
|
|
204
|
+
try:
|
|
205
|
+
print_banner()
|
|
206
|
+
console.print("\n[bold]🔍 Checking Flutter development environment...[/bold]\n")
|
|
207
|
+
|
|
208
|
+
# Load configuration from file
|
|
209
|
+
config_manager = ConfigManager()
|
|
210
|
+
config_manager.ensure_config_dir()
|
|
211
|
+
file_config = config_manager.load_config()
|
|
212
|
+
|
|
213
|
+
# Get Flutter location from config file
|
|
214
|
+
flutter_location_str = file_config.get("flutter", {}).get("location")
|
|
215
|
+
if flutter_location_str:
|
|
216
|
+
flutter_location = Path(flutter_location_str)
|
|
217
|
+
else:
|
|
218
|
+
# Fallback to default if not in config
|
|
219
|
+
flutter_location = Path.home() / "development" / "flutter"
|
|
220
|
+
|
|
221
|
+
# Get channel from config
|
|
222
|
+
channel = file_config.get("flutter", {}).get("channel", "stable")
|
|
223
|
+
|
|
224
|
+
# Create minimal config for checking (we don't need project details)
|
|
225
|
+
host_platform = detect_runtime_platform()
|
|
226
|
+
default_platform = "ios" if host_platform == "darwin" else "linux"
|
|
227
|
+
|
|
228
|
+
# Use dummy values for required fields
|
|
229
|
+
config = Config(
|
|
230
|
+
project_name="dummy",
|
|
231
|
+
platforms=[default_platform],
|
|
232
|
+
org="com.example",
|
|
233
|
+
channel=channel,
|
|
234
|
+
output_dir=Path("."),
|
|
235
|
+
template="app",
|
|
236
|
+
ios_language="swift",
|
|
237
|
+
android_language="kotlin",
|
|
238
|
+
flutter_update_mode="skip", # Don't update during check
|
|
239
|
+
dry_run=False,
|
|
240
|
+
verbose=verbose,
|
|
241
|
+
flutter_location=flutter_location,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Check prerequisites
|
|
245
|
+
console.print("[bold]📋 Checking prerequisites...[/bold]")
|
|
246
|
+
prerequisites = PrerequisitesManager(config)
|
|
247
|
+
prerequisites_ok = prerequisites.check_only()
|
|
248
|
+
|
|
249
|
+
# Check Flutter SDK
|
|
250
|
+
console.print("\n[bold]🦋 Checking Flutter SDK...[/bold]")
|
|
251
|
+
flutter_manager = FlutterManager(config)
|
|
252
|
+
flutter_ok = flutter_manager.check_only()
|
|
253
|
+
|
|
254
|
+
# Summary
|
|
255
|
+
console.print("\n[bold]📊 Summary[/bold]")
|
|
256
|
+
if prerequisites_ok and flutter_ok:
|
|
257
|
+
console.print(
|
|
258
|
+
"[green]✅ All checks passed! Your Flutter development environment is ready.[/green]"
|
|
259
|
+
)
|
|
260
|
+
sys.exit(0)
|
|
261
|
+
else:
|
|
262
|
+
console.print(
|
|
263
|
+
"[yellow]⚠️ Some checks failed. Please review the issues above.[/yellow]"
|
|
264
|
+
)
|
|
265
|
+
if not prerequisites_ok:
|
|
266
|
+
console.print(
|
|
267
|
+
"[dim]Run 'flutter-setup setup <project> <platforms>' to install missing prerequisites.[/dim]"
|
|
268
|
+
)
|
|
269
|
+
if not flutter_ok:
|
|
270
|
+
console.print(
|
|
271
|
+
"[dim]Run 'flutter-setup setup <project> <platforms>' to install or update Flutter SDK.[/dim]"
|
|
272
|
+
)
|
|
273
|
+
sys.exit(1)
|
|
274
|
+
|
|
275
|
+
except Exception as e:
|
|
276
|
+
console.print(f"[red]❌ Check failed: {e}[/red]")
|
|
277
|
+
if verbose:
|
|
278
|
+
console.print_exception()
|
|
279
|
+
sys.exit(1)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@cli.command("setup")
|
|
283
|
+
@click.argument("project_name", required=False, default=None)
|
|
284
|
+
@click.argument("platforms", nargs=-1)
|
|
285
|
+
@click.option(
|
|
286
|
+
"--org",
|
|
287
|
+
default="com.example",
|
|
288
|
+
help="Organization identifier (default: com.example)",
|
|
289
|
+
)
|
|
290
|
+
@click.option(
|
|
291
|
+
"--channel",
|
|
292
|
+
type=click.Choice(["stable", "beta"]),
|
|
293
|
+
default="stable",
|
|
294
|
+
help="Flutter channel (default: stable)",
|
|
295
|
+
)
|
|
296
|
+
@click.option(
|
|
297
|
+
"--dir",
|
|
298
|
+
default=".",
|
|
299
|
+
type=click.Path(exists=True, file_okay=False, dir_okay=True),
|
|
300
|
+
help="Output directory (default: current directory)",
|
|
301
|
+
)
|
|
302
|
+
@click.option(
|
|
303
|
+
"--template",
|
|
304
|
+
type=click.Choice(["app", "plugin"]),
|
|
305
|
+
default="app",
|
|
306
|
+
help="Project template (default: app)",
|
|
307
|
+
)
|
|
308
|
+
@click.option(
|
|
309
|
+
"--architecture",
|
|
310
|
+
type=click.Choice(["basic", "clean"]),
|
|
311
|
+
default="basic",
|
|
312
|
+
help="Application architecture scaffold (default: basic)",
|
|
313
|
+
)
|
|
314
|
+
@click.option(
|
|
315
|
+
"--database",
|
|
316
|
+
type=click.Choice(["none", "sqlite"]),
|
|
317
|
+
default="none",
|
|
318
|
+
help="Local persistence scaffold (default: none)",
|
|
319
|
+
)
|
|
320
|
+
@click.option(
|
|
321
|
+
"--testing",
|
|
322
|
+
type=click.Choice(["standard", "mocktail"]),
|
|
323
|
+
default="standard",
|
|
324
|
+
help="Testing starter scaffold (default: standard)",
|
|
325
|
+
)
|
|
326
|
+
@click.option(
|
|
327
|
+
"--auth-provider",
|
|
328
|
+
type=click.Choice(["none", "firebase"]),
|
|
329
|
+
default="none",
|
|
330
|
+
help="Auth integration scaffold (default: none)",
|
|
331
|
+
)
|
|
332
|
+
@click.option(
|
|
333
|
+
"--cloud-database",
|
|
334
|
+
type=click.Choice(["none", "firestore"]),
|
|
335
|
+
default="none",
|
|
336
|
+
help="Cloud database integration scaffold (default: none)",
|
|
337
|
+
)
|
|
338
|
+
@click.option(
|
|
339
|
+
"--notifications-provider",
|
|
340
|
+
type=click.Choice(["none", "firebase"]),
|
|
341
|
+
default="none",
|
|
342
|
+
help="Push notifications scaffold (default: none)",
|
|
343
|
+
)
|
|
344
|
+
@click.option(
|
|
345
|
+
"--ios-language",
|
|
346
|
+
type=click.Choice(["swift", "objc"]),
|
|
347
|
+
default="swift",
|
|
348
|
+
help="iOS language for plugin templates (default: swift)",
|
|
349
|
+
)
|
|
350
|
+
@click.option(
|
|
351
|
+
"--android-language",
|
|
352
|
+
type=click.Choice(["kotlin", "java"]),
|
|
353
|
+
default="kotlin",
|
|
354
|
+
help="Android language for plugin templates (default: kotlin)",
|
|
355
|
+
)
|
|
356
|
+
@click.option(
|
|
357
|
+
"--flutter-update",
|
|
358
|
+
type=click.Choice(["reset", "reclone", "skip"]),
|
|
359
|
+
default="skip",
|
|
360
|
+
help="Flutter update mode (default: skip)",
|
|
361
|
+
)
|
|
362
|
+
@click.option(
|
|
363
|
+
"--flutter-version",
|
|
364
|
+
default=None,
|
|
365
|
+
help="Required Flutter SDK version (e.g. 3.24.0). Errors early if not installed.",
|
|
366
|
+
)
|
|
367
|
+
@click.option(
|
|
368
|
+
"--dry-run",
|
|
369
|
+
is_flag=True,
|
|
370
|
+
help="Preview actions without executing them",
|
|
371
|
+
)
|
|
372
|
+
@click.option(
|
|
373
|
+
"--verbose",
|
|
374
|
+
"-v",
|
|
375
|
+
is_flag=True,
|
|
376
|
+
help="Enable verbose output",
|
|
377
|
+
)
|
|
378
|
+
@click.pass_context
|
|
379
|
+
def setup_command(
|
|
380
|
+
ctx: click.Context,
|
|
381
|
+
project_name: str | None,
|
|
382
|
+
platforms: tuple[str, ...],
|
|
383
|
+
org: str,
|
|
384
|
+
channel: FlutterChannel,
|
|
385
|
+
dir: str,
|
|
386
|
+
template: TemplateType,
|
|
387
|
+
architecture: Architecture,
|
|
388
|
+
database: Database,
|
|
389
|
+
testing: Testing,
|
|
390
|
+
auth_provider: AuthProvider,
|
|
391
|
+
cloud_database: CloudDatabase,
|
|
392
|
+
notifications_provider: NotificationsProvider,
|
|
393
|
+
ios_language: IosLanguage,
|
|
394
|
+
android_language: AndroidLanguage,
|
|
395
|
+
flutter_update: UpdateMode,
|
|
396
|
+
flutter_version: str | None,
|
|
397
|
+
dry_run: bool,
|
|
398
|
+
verbose: bool,
|
|
399
|
+
) -> None:
|
|
400
|
+
"""Set up a complete Flutter development environment."""
|
|
401
|
+
try:
|
|
402
|
+
print_banner()
|
|
403
|
+
|
|
404
|
+
# Load configuration from file
|
|
405
|
+
config_manager = ConfigManager()
|
|
406
|
+
config_manager.ensure_config_dir()
|
|
407
|
+
file_config = config_manager.load_config()
|
|
408
|
+
file_project = file_config.get("project", {})
|
|
409
|
+
file_flutter = file_config.get("flutter", {})
|
|
410
|
+
|
|
411
|
+
# Get Flutter location from config file
|
|
412
|
+
flutter_location_str = file_config.get("flutter", {}).get("location")
|
|
413
|
+
if flutter_location_str:
|
|
414
|
+
flutter_location = Path(flutter_location_str)
|
|
415
|
+
else:
|
|
416
|
+
flutter_location = Path.home() / "development" / "flutter"
|
|
417
|
+
config_manager.set_flutter_location(flutter_location)
|
|
418
|
+
|
|
419
|
+
def get_merged_or_prompt(
|
|
420
|
+
param_name: str,
|
|
421
|
+
cli_value: Any,
|
|
422
|
+
config_dict: Dict[str, Any],
|
|
423
|
+
prompt_text: str,
|
|
424
|
+
choices: list[str] | None = None,
|
|
425
|
+
config_key: str | None = None,
|
|
426
|
+
) -> Any:
|
|
427
|
+
"""Use CLI value if explicit, config file value if present and valid, else prompt."""
|
|
428
|
+
key = config_key or param_name
|
|
429
|
+
if (
|
|
430
|
+
ctx.get_parameter_source(param_name)
|
|
431
|
+
== click.core.ParameterSource.COMMANDLINE
|
|
432
|
+
):
|
|
433
|
+
return cli_value
|
|
434
|
+
if key in config_dict:
|
|
435
|
+
value = config_dict[key]
|
|
436
|
+
# Normalize string values to lowercase for case-insensitive matching
|
|
437
|
+
normalized = value.lower() if isinstance(value, str) else value
|
|
438
|
+
if choices is None or normalized in choices:
|
|
439
|
+
return normalized if isinstance(value, str) else value
|
|
440
|
+
# Config value is invalid — fall through to prompt
|
|
441
|
+
if not _is_interactive():
|
|
442
|
+
# Non-interactive environment: use CLI default silently
|
|
443
|
+
return cli_value
|
|
444
|
+
if choices:
|
|
445
|
+
return click.prompt(
|
|
446
|
+
prompt_text,
|
|
447
|
+
default=cli_value,
|
|
448
|
+
type=click.Choice(choices, case_sensitive=False),
|
|
449
|
+
)
|
|
450
|
+
return click.prompt(prompt_text, default=cli_value)
|
|
451
|
+
|
|
452
|
+
def get_merged(
|
|
453
|
+
param_name: str,
|
|
454
|
+
cli_value: Any,
|
|
455
|
+
config_dict: Dict[str, Any],
|
|
456
|
+
config_key: str | None = None,
|
|
457
|
+
) -> Any:
|
|
458
|
+
"""Use CLI value if explicit, config file value if present, else CLI default (no prompt)."""
|
|
459
|
+
key = config_key or param_name
|
|
460
|
+
if (
|
|
461
|
+
ctx.get_parameter_source(param_name)
|
|
462
|
+
== click.core.ParameterSource.COMMANDLINE
|
|
463
|
+
):
|
|
464
|
+
return cli_value
|
|
465
|
+
return config_dict.get(key, cli_value)
|
|
466
|
+
|
|
467
|
+
# Prompt for required arguments if not provided on the command line
|
|
468
|
+
if project_name is None:
|
|
469
|
+
project_name = click.prompt("Project name", type=str)
|
|
470
|
+
|
|
471
|
+
if not platforms:
|
|
472
|
+
console.print(
|
|
473
|
+
"[dim]Available platforms: ios, android, macos, linux, windows, web[/dim]"
|
|
474
|
+
)
|
|
475
|
+
platforms_str = click.prompt(
|
|
476
|
+
"Platforms (space-separated)",
|
|
477
|
+
default="ios android",
|
|
478
|
+
)
|
|
479
|
+
platforms = tuple(p.strip() for p in platforms_str.split() if p.strip())
|
|
480
|
+
|
|
481
|
+
if not platforms:
|
|
482
|
+
console.print("[red]Error: At least one platform is required[/red]")
|
|
483
|
+
sys.exit(1)
|
|
484
|
+
|
|
485
|
+
# Merge CLI options with config file, prompting for values not in either
|
|
486
|
+
merged_org = get_merged_or_prompt(
|
|
487
|
+
"org", org, file_project, "Organization ID (e.g., com.mycompany)"
|
|
488
|
+
)
|
|
489
|
+
merged_channel = cast(
|
|
490
|
+
FlutterChannel,
|
|
491
|
+
get_merged_or_prompt(
|
|
492
|
+
"channel",
|
|
493
|
+
channel,
|
|
494
|
+
file_flutter,
|
|
495
|
+
"Flutter channel",
|
|
496
|
+
choices=["stable", "beta"],
|
|
497
|
+
),
|
|
498
|
+
)
|
|
499
|
+
merged_template = cast(
|
|
500
|
+
TemplateType,
|
|
501
|
+
get_merged_or_prompt(
|
|
502
|
+
"template",
|
|
503
|
+
template,
|
|
504
|
+
file_project,
|
|
505
|
+
"Project template",
|
|
506
|
+
choices=["app", "plugin"],
|
|
507
|
+
),
|
|
508
|
+
)
|
|
509
|
+
merged_architecture = cast(
|
|
510
|
+
Architecture,
|
|
511
|
+
get_merged_or_prompt(
|
|
512
|
+
"architecture",
|
|
513
|
+
architecture,
|
|
514
|
+
file_project,
|
|
515
|
+
"Architecture",
|
|
516
|
+
choices=["basic", "clean"],
|
|
517
|
+
),
|
|
518
|
+
)
|
|
519
|
+
merged_database = cast(
|
|
520
|
+
Database,
|
|
521
|
+
get_merged_or_prompt(
|
|
522
|
+
"database",
|
|
523
|
+
database,
|
|
524
|
+
file_project,
|
|
525
|
+
"Local database",
|
|
526
|
+
choices=["none", "sqlite"],
|
|
527
|
+
),
|
|
528
|
+
)
|
|
529
|
+
merged_testing = cast(
|
|
530
|
+
Testing,
|
|
531
|
+
get_merged_or_prompt(
|
|
532
|
+
"testing",
|
|
533
|
+
testing,
|
|
534
|
+
file_project,
|
|
535
|
+
"Testing framework",
|
|
536
|
+
choices=["standard", "mocktail"],
|
|
537
|
+
),
|
|
538
|
+
)
|
|
539
|
+
merged_auth_provider = cast(
|
|
540
|
+
AuthProvider,
|
|
541
|
+
get_merged_or_prompt(
|
|
542
|
+
"auth_provider",
|
|
543
|
+
auth_provider,
|
|
544
|
+
file_project,
|
|
545
|
+
"Auth provider",
|
|
546
|
+
choices=["none", "firebase"],
|
|
547
|
+
),
|
|
548
|
+
)
|
|
549
|
+
merged_cloud_database = cast(
|
|
550
|
+
CloudDatabase,
|
|
551
|
+
get_merged_or_prompt(
|
|
552
|
+
"cloud_database",
|
|
553
|
+
cloud_database,
|
|
554
|
+
file_project,
|
|
555
|
+
"Cloud database",
|
|
556
|
+
choices=["none", "firestore"],
|
|
557
|
+
),
|
|
558
|
+
)
|
|
559
|
+
merged_notifications_provider = cast(
|
|
560
|
+
NotificationsProvider,
|
|
561
|
+
get_merged_or_prompt(
|
|
562
|
+
"notifications_provider",
|
|
563
|
+
notifications_provider,
|
|
564
|
+
file_project,
|
|
565
|
+
"Notifications provider",
|
|
566
|
+
choices=["none", "firebase"],
|
|
567
|
+
),
|
|
568
|
+
)
|
|
569
|
+
# Language options only apply to plugin templates; never prompt for app projects
|
|
570
|
+
if merged_template == "plugin":
|
|
571
|
+
merged_ios_language = cast(
|
|
572
|
+
IosLanguage,
|
|
573
|
+
get_merged_or_prompt(
|
|
574
|
+
"ios_language",
|
|
575
|
+
ios_language,
|
|
576
|
+
file_project,
|
|
577
|
+
"iOS language",
|
|
578
|
+
choices=["swift", "objc"],
|
|
579
|
+
),
|
|
580
|
+
)
|
|
581
|
+
merged_android_language = cast(
|
|
582
|
+
AndroidLanguage,
|
|
583
|
+
get_merged_or_prompt(
|
|
584
|
+
"android_language",
|
|
585
|
+
android_language,
|
|
586
|
+
file_project,
|
|
587
|
+
"Android language",
|
|
588
|
+
choices=["kotlin", "java"],
|
|
589
|
+
),
|
|
590
|
+
)
|
|
591
|
+
else:
|
|
592
|
+
merged_ios_language = cast(
|
|
593
|
+
IosLanguage, get_merged("ios_language", ios_language, file_project)
|
|
594
|
+
)
|
|
595
|
+
merged_android_language = cast(
|
|
596
|
+
AndroidLanguage,
|
|
597
|
+
get_merged("android_language", android_language, file_project),
|
|
598
|
+
)
|
|
599
|
+
merged_flutter_update = cast(
|
|
600
|
+
UpdateMode,
|
|
601
|
+
get_merged_or_prompt(
|
|
602
|
+
"flutter_update",
|
|
603
|
+
flutter_update,
|
|
604
|
+
file_flutter,
|
|
605
|
+
"Flutter update mode",
|
|
606
|
+
choices=["reset", "reclone", "skip"],
|
|
607
|
+
config_key="update_mode",
|
|
608
|
+
),
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
# Create configuration
|
|
612
|
+
config = Config(
|
|
613
|
+
project_name=project_name,
|
|
614
|
+
platforms=list(platforms),
|
|
615
|
+
org=merged_org,
|
|
616
|
+
channel=merged_channel,
|
|
617
|
+
output_dir=Path(dir),
|
|
618
|
+
template=merged_template,
|
|
619
|
+
ios_language=merged_ios_language,
|
|
620
|
+
android_language=merged_android_language,
|
|
621
|
+
flutter_update_mode=merged_flutter_update,
|
|
622
|
+
dry_run=dry_run,
|
|
623
|
+
verbose=verbose,
|
|
624
|
+
flutter_location=flutter_location,
|
|
625
|
+
architecture=merged_architecture,
|
|
626
|
+
database=merged_database,
|
|
627
|
+
testing=merged_testing,
|
|
628
|
+
auth_provider=merged_auth_provider,
|
|
629
|
+
cloud_database=merged_cloud_database,
|
|
630
|
+
notifications_provider=merged_notifications_provider,
|
|
631
|
+
flutter_version=flutter_version,
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
# Create and run setup
|
|
635
|
+
setup = FlutterSetup(config)
|
|
636
|
+
setup.run()
|
|
637
|
+
|
|
638
|
+
console.print("\n[green]✅ Flutter setup completed successfully![/green]")
|
|
639
|
+
|
|
640
|
+
except FlutterSetupError as e:
|
|
641
|
+
console.print(f"[red]❌ Setup failed: {e}[/red]")
|
|
642
|
+
if verbose:
|
|
643
|
+
console.print_exception()
|
|
644
|
+
sys.exit(1)
|
|
645
|
+
except KeyboardInterrupt:
|
|
646
|
+
console.print("\n[yellow]⚠️ Setup interrupted by user[/yellow]")
|
|
647
|
+
sys.exit(1)
|
|
648
|
+
except Exception as e:
|
|
649
|
+
console.print(f"[red]❌ Unexpected error: {e}[/red]")
|
|
650
|
+
if verbose:
|
|
651
|
+
console.print_exception()
|
|
652
|
+
sys.exit(1)
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def main() -> None:
|
|
656
|
+
"""Main entry point that routes to appropriate command."""
|
|
657
|
+
cli()
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
if __name__ == "__main__":
|
|
661
|
+
# Click handles argument parsing
|
|
662
|
+
main()
|