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/config.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Configuration and data models for Flutter Setup."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Literal
|
|
6
|
+
|
|
7
|
+
# Type aliases
|
|
8
|
+
FlutterChannel = Literal["stable", "beta"]
|
|
9
|
+
TemplateType = Literal["app", "plugin"]
|
|
10
|
+
IosLanguage = Literal["swift", "objc"]
|
|
11
|
+
AndroidLanguage = Literal["kotlin", "java"]
|
|
12
|
+
UpdateMode = Literal["reset", "reclone", "skip"]
|
|
13
|
+
Platform = Literal["ios", "android", "macos", "linux", "windows", "web"]
|
|
14
|
+
Architecture = Literal["basic", "clean"]
|
|
15
|
+
Database = Literal["none", "sqlite"]
|
|
16
|
+
Testing = Literal["standard", "mocktail"]
|
|
17
|
+
AuthProvider = Literal["none", "firebase"]
|
|
18
|
+
CloudDatabase = Literal["none", "firestore"]
|
|
19
|
+
NotificationsProvider = Literal["none", "firebase"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Config:
|
|
24
|
+
"""Configuration for Flutter setup."""
|
|
25
|
+
|
|
26
|
+
project_name: str
|
|
27
|
+
platforms: List[str]
|
|
28
|
+
org: str
|
|
29
|
+
channel: FlutterChannel
|
|
30
|
+
output_dir: Path
|
|
31
|
+
template: TemplateType
|
|
32
|
+
ios_language: IosLanguage
|
|
33
|
+
android_language: AndroidLanguage
|
|
34
|
+
flutter_update_mode: UpdateMode
|
|
35
|
+
dry_run: bool
|
|
36
|
+
verbose: bool
|
|
37
|
+
flutter_location: Path
|
|
38
|
+
architecture: Architecture = "basic"
|
|
39
|
+
database: Database = "none"
|
|
40
|
+
testing: Testing = "standard"
|
|
41
|
+
auth_provider: AuthProvider = "none"
|
|
42
|
+
cloud_database: CloudDatabase = "none"
|
|
43
|
+
notifications_provider: NotificationsProvider = "none"
|
|
44
|
+
flutter_version: str | None = None
|
|
45
|
+
|
|
46
|
+
def __post_init__(self) -> None:
|
|
47
|
+
"""Validate configuration after initialization."""
|
|
48
|
+
self._validate()
|
|
49
|
+
|
|
50
|
+
def _validate(self) -> None:
|
|
51
|
+
"""Validate configuration values."""
|
|
52
|
+
if not self.project_name:
|
|
53
|
+
raise ValueError("Project name cannot be empty")
|
|
54
|
+
|
|
55
|
+
if not self.platforms:
|
|
56
|
+
raise ValueError("At least one platform must be specified")
|
|
57
|
+
|
|
58
|
+
# Basic normalization
|
|
59
|
+
self.platforms = [p.strip().lower() for p in self.platforms if p.strip()]
|
|
60
|
+
|
|
61
|
+
# Validate platforms
|
|
62
|
+
valid_platforms = {"ios", "android", "macos", "linux", "windows", "web"}
|
|
63
|
+
for platform in self.platforms:
|
|
64
|
+
if platform not in valid_platforms:
|
|
65
|
+
raise ValueError(f"Invalid platform: {platform}")
|
|
66
|
+
|
|
67
|
+
# Validate template-specific options
|
|
68
|
+
if self.template == "plugin":
|
|
69
|
+
if self.ios_language not in ["swift", "objc"]:
|
|
70
|
+
raise ValueError(f"Invalid iOS language: {self.ios_language}")
|
|
71
|
+
if self.android_language not in ["kotlin", "java"]:
|
|
72
|
+
raise ValueError(f"Invalid Android language: {self.android_language}")
|
|
73
|
+
|
|
74
|
+
if self.architecture not in ["basic", "clean"]:
|
|
75
|
+
raise ValueError(f"Invalid architecture: {self.architecture}")
|
|
76
|
+
|
|
77
|
+
if self.database not in ["none", "sqlite"]:
|
|
78
|
+
raise ValueError(f"Invalid database: {self.database}")
|
|
79
|
+
|
|
80
|
+
if self.testing not in ["standard", "mocktail"]:
|
|
81
|
+
raise ValueError(f"Invalid testing framework: {self.testing}")
|
|
82
|
+
|
|
83
|
+
if self.auth_provider not in ["none", "firebase"]:
|
|
84
|
+
raise ValueError(f"Invalid auth provider: {self.auth_provider}")
|
|
85
|
+
|
|
86
|
+
if self.cloud_database not in ["none", "firestore"]:
|
|
87
|
+
raise ValueError(f"Invalid cloud database: {self.cloud_database}")
|
|
88
|
+
|
|
89
|
+
if self.notifications_provider not in ["none", "firebase"]:
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"Invalid notifications provider: {self.notifications_provider}"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if self.flutter_version is not None:
|
|
95
|
+
import re
|
|
96
|
+
|
|
97
|
+
if not re.fullmatch(r"\d+\.\d+\.\d+", self.flutter_version):
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"Invalid flutter_version '{self.flutter_version}': must be X.Y.Z (e.g. 3.24.0)"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def project_path(self) -> Path:
|
|
104
|
+
"""Get the full project path."""
|
|
105
|
+
return self.output_dir / self.project_name
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def package_name(self) -> str:
|
|
109
|
+
"""Get the sanitized package name."""
|
|
110
|
+
return self._sanitize_package_name(self.project_name)
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def platforms_csv(self) -> str:
|
|
114
|
+
"""Get platforms as comma-separated string."""
|
|
115
|
+
return ",".join(self.platforms)
|
|
116
|
+
|
|
117
|
+
def _sanitize_package_name(self, name: str) -> str:
|
|
118
|
+
"""Sanitize package name for Flutter."""
|
|
119
|
+
import re
|
|
120
|
+
|
|
121
|
+
# Convert to lowercase and replace non-alphanumeric with underscores
|
|
122
|
+
sanitized = re.sub(r"[^a-z0-9_]", "_", name.lower())
|
|
123
|
+
|
|
124
|
+
# Ensure it starts with a letter
|
|
125
|
+
if sanitized and not sanitized[0].isalpha():
|
|
126
|
+
sanitized = f"app_{sanitized}"
|
|
127
|
+
|
|
128
|
+
# Remove leading/trailing underscores
|
|
129
|
+
sanitized = sanitized.strip("_")
|
|
130
|
+
|
|
131
|
+
# Ensure it's not empty
|
|
132
|
+
if not sanitized:
|
|
133
|
+
sanitized = "app"
|
|
134
|
+
|
|
135
|
+
return sanitized
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Configuration file management with XDG Base Directory Specification support."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, Optional
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
|
|
11
|
+
console = Console()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConfigManager:
|
|
15
|
+
"""Manages configuration file using XDG Base Directory Specification."""
|
|
16
|
+
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
"""Initialize ConfigManager with XDG-compliant paths."""
|
|
19
|
+
self.config_dir = self._get_config_dir()
|
|
20
|
+
self.config_file = self.config_dir / "config.yaml"
|
|
21
|
+
|
|
22
|
+
def _get_config_dir(self) -> Path:
|
|
23
|
+
"""Get config directory following XDG Base Directory Specification."""
|
|
24
|
+
# XDG_CONFIG_HOME takes precedence, fallback to ~/.config
|
|
25
|
+
xdg_config_home = os.getenv("XDG_CONFIG_HOME")
|
|
26
|
+
if xdg_config_home:
|
|
27
|
+
config_dir = Path(xdg_config_home) / "flutter-setup"
|
|
28
|
+
else:
|
|
29
|
+
config_dir = Path.home() / ".config" / "flutter-setup"
|
|
30
|
+
|
|
31
|
+
return config_dir
|
|
32
|
+
|
|
33
|
+
def ensure_config_dir(self) -> None:
|
|
34
|
+
"""Ensure config directory exists, creating it if necessary."""
|
|
35
|
+
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
36
|
+
|
|
37
|
+
def get_default_config(self) -> Dict[str, Any]:
|
|
38
|
+
"""Get default configuration values."""
|
|
39
|
+
home = Path.home()
|
|
40
|
+
default_flutter_location = str(home / "development" / "flutter")
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
"flutter": {
|
|
44
|
+
"location": default_flutter_location,
|
|
45
|
+
"channel": "stable",
|
|
46
|
+
"update_mode": "skip",
|
|
47
|
+
},
|
|
48
|
+
"project": {
|
|
49
|
+
"org": "com.example",
|
|
50
|
+
"template": "app",
|
|
51
|
+
"architecture": "basic",
|
|
52
|
+
"database": "none",
|
|
53
|
+
"testing": "standard",
|
|
54
|
+
"auth_provider": "none",
|
|
55
|
+
"cloud_database": "none",
|
|
56
|
+
"notifications_provider": "none",
|
|
57
|
+
"ios_language": "swift",
|
|
58
|
+
"android_language": "kotlin",
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
def create_default_config(self) -> None:
|
|
63
|
+
"""Create default config file if it doesn't exist."""
|
|
64
|
+
if self.config_file.exists():
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
self.ensure_config_dir()
|
|
68
|
+
|
|
69
|
+
default_config = self.get_default_config()
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
with open(self.config_file, "w") as f:
|
|
73
|
+
yaml.dump(default_config, f, default_flow_style=False, sort_keys=False)
|
|
74
|
+
console.print(
|
|
75
|
+
f"[dim]Created default config file at: {self.config_file}[/dim]"
|
|
76
|
+
)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
console.print(
|
|
79
|
+
f"[yellow]Warning: Could not create config file: {e}[/yellow]"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def load_config(self) -> Dict[str, Any]:
|
|
83
|
+
"""Load configuration from file, creating default if it doesn't exist."""
|
|
84
|
+
# Create default config if it doesn't exist
|
|
85
|
+
if not self.config_file.exists():
|
|
86
|
+
self.create_default_config()
|
|
87
|
+
|
|
88
|
+
if not self.config_file.exists():
|
|
89
|
+
# If we still can't create it, return defaults
|
|
90
|
+
return self.get_default_config()
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
with open(self.config_file, "r") as f:
|
|
94
|
+
config = yaml.safe_load(f) or {}
|
|
95
|
+
return config
|
|
96
|
+
except Exception as e:
|
|
97
|
+
console.print(
|
|
98
|
+
f"[yellow]Warning: Could not load config file: {e}. Using defaults.[/yellow]"
|
|
99
|
+
)
|
|
100
|
+
return self.get_default_config()
|
|
101
|
+
|
|
102
|
+
def save_config(self, config: Dict[str, Any]) -> None:
|
|
103
|
+
"""Save configuration to file."""
|
|
104
|
+
self.ensure_config_dir()
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
with open(self.config_file, "w") as f:
|
|
108
|
+
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
|
|
109
|
+
except Exception as e:
|
|
110
|
+
console.print(f"[yellow]Warning: Could not save config file: {e}[/yellow]")
|
|
111
|
+
|
|
112
|
+
def get_flutter_location(self) -> Optional[Path]:
|
|
113
|
+
"""Get Flutter location from config file."""
|
|
114
|
+
config = self.load_config()
|
|
115
|
+
flutter_location = config.get("flutter", {}).get("location")
|
|
116
|
+
if flutter_location:
|
|
117
|
+
return Path(flutter_location)
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
def set_flutter_location(self, location: Path) -> None:
|
|
121
|
+
"""Set Flutter location in config file."""
|
|
122
|
+
config = self.load_config()
|
|
123
|
+
if "flutter" not in config:
|
|
124
|
+
config["flutter"] = {}
|
|
125
|
+
config["flutter"]["location"] = str(location)
|
|
126
|
+
self.save_config(config)
|
|
127
|
+
|
|
128
|
+
def detect_flutter_location(self) -> Optional[Path]:
|
|
129
|
+
"""Detect Flutter location from environment variables or PATH."""
|
|
130
|
+
# Check FLUTTER_ROOT environment variable
|
|
131
|
+
flutter_root = os.getenv("FLUTTER_ROOT")
|
|
132
|
+
if flutter_root:
|
|
133
|
+
path = Path(flutter_root)
|
|
134
|
+
if path.exists() and (path / "bin" / "flutter").exists():
|
|
135
|
+
return path
|
|
136
|
+
|
|
137
|
+
# Check if flutter is in PATH
|
|
138
|
+
flutter_bin = shutil.which("flutter")
|
|
139
|
+
if flutter_bin:
|
|
140
|
+
# flutter_bin is something like /path/to/flutter/bin/flutter
|
|
141
|
+
flutter_path = Path(flutter_bin).parent.parent
|
|
142
|
+
if flutter_path.exists() and (flutter_path / ".git").exists():
|
|
143
|
+
return flutter_path
|
|
144
|
+
|
|
145
|
+
# Check common installation locations
|
|
146
|
+
home = Path.home()
|
|
147
|
+
common_paths = [
|
|
148
|
+
home / "development" / "flutter",
|
|
149
|
+
home / "flutter",
|
|
150
|
+
home / ".flutter",
|
|
151
|
+
Path("/usr/local/flutter"),
|
|
152
|
+
Path("/opt/flutter"),
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
for path in common_paths:
|
|
156
|
+
if path.exists() and (path / "bin" / "flutter").exists():
|
|
157
|
+
return path
|
|
158
|
+
|
|
159
|
+
return None
|
flutter_setup/core.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""Core Flutter setup functionality."""
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
|
|
7
|
+
from .config import Config
|
|
8
|
+
from .exceptions import (
|
|
9
|
+
FlutterSetupError,
|
|
10
|
+
PrerequisitesError,
|
|
11
|
+
FlutterInstallationError,
|
|
12
|
+
ProjectCreationError,
|
|
13
|
+
)
|
|
14
|
+
from .prerequisites import PrerequisitesManager
|
|
15
|
+
from .flutter_manager import FlutterManager
|
|
16
|
+
from .project_creator import ProjectCreator
|
|
17
|
+
from .bootstrap import ProjectBootstrap
|
|
18
|
+
from .platform import detect_runtime_platform
|
|
19
|
+
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FlutterSetup:
|
|
24
|
+
"""Main class for orchestrating Flutter development environment setup."""
|
|
25
|
+
|
|
26
|
+
def __init__(self, config: Config):
|
|
27
|
+
"""Initialize FlutterSetup with configuration."""
|
|
28
|
+
self.config = config
|
|
29
|
+
self.platform = detect_runtime_platform()
|
|
30
|
+
self.prerequisites = PrerequisitesManager(config)
|
|
31
|
+
self.flutter_manager = FlutterManager(config)
|
|
32
|
+
self.project_creator = ProjectCreator(config)
|
|
33
|
+
self.bootstrap = ProjectBootstrap(config)
|
|
34
|
+
|
|
35
|
+
def run(self) -> None:
|
|
36
|
+
"""Run the complete Flutter setup process."""
|
|
37
|
+
console.print(
|
|
38
|
+
f"\n[bold blue]🚀 Starting Flutter setup for: {self.config.project_name}[/bold blue]"
|
|
39
|
+
)
|
|
40
|
+
console.print(
|
|
41
|
+
f"[dim]Template: {self.config.template} | Org: {self.config.org} | Channel: {self.config.channel}[/dim]"
|
|
42
|
+
)
|
|
43
|
+
console.print(
|
|
44
|
+
f"[dim]Architecture: {self.config.architecture} | Database: {self.config.database}[/dim]"
|
|
45
|
+
)
|
|
46
|
+
console.print(
|
|
47
|
+
f"[dim]Testing: {self.config.testing} | Auth: {self.config.auth_provider} | Cloud DB: {self.config.cloud_database} | Notifications: {self.config.notifications_provider}[/dim]"
|
|
48
|
+
)
|
|
49
|
+
console.print(
|
|
50
|
+
f"[dim]Platforms: {', '.join(self.config.platforms)} | Package: {self.config.package_name}[/dim]"
|
|
51
|
+
)
|
|
52
|
+
console.print(f"[dim]Host OS: {self.platform}[/dim]")
|
|
53
|
+
console.print(f"[dim]Output: {self.config.project_path}[/dim]")
|
|
54
|
+
|
|
55
|
+
if self.config.dry_run:
|
|
56
|
+
console.print(
|
|
57
|
+
"[yellow]⚠️ DRY RUN MODE - No actual changes will be made[/yellow]"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
# Step 1: Check and install prerequisites
|
|
62
|
+
self._run_prerequisites()
|
|
63
|
+
|
|
64
|
+
# Step 2: Install/update Flutter SDK
|
|
65
|
+
self._run_flutter_installation()
|
|
66
|
+
|
|
67
|
+
# Step 3: Create Flutter project
|
|
68
|
+
self._run_project_creation()
|
|
69
|
+
|
|
70
|
+
# Step 4: Bootstrap development environment
|
|
71
|
+
self._run_bootstrap()
|
|
72
|
+
|
|
73
|
+
# Step 5: Display next steps
|
|
74
|
+
self._show_next_steps()
|
|
75
|
+
|
|
76
|
+
except Exception as e:
|
|
77
|
+
console.print(f"\n[red]❌ Setup failed: {e}[/red]")
|
|
78
|
+
raise
|
|
79
|
+
|
|
80
|
+
def _run_prerequisites(self) -> None:
|
|
81
|
+
"""Run prerequisites check and installation."""
|
|
82
|
+
console.print("\n[bold]📋 Checking prerequisites...[/bold]")
|
|
83
|
+
|
|
84
|
+
with Progress(
|
|
85
|
+
SpinnerColumn(),
|
|
86
|
+
TextColumn("[progress.description]{task.description}"),
|
|
87
|
+
console=console,
|
|
88
|
+
) as progress:
|
|
89
|
+
task = progress.add_task("Checking system requirements...", total=None)
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
self.prerequisites.check_and_install()
|
|
93
|
+
progress.update(task, description="✅ Prerequisites satisfied")
|
|
94
|
+
except Exception as e:
|
|
95
|
+
progress.update(task, description="❌ Prerequisites failed")
|
|
96
|
+
raise PrerequisitesError(f"Failed to install prerequisites: {e}")
|
|
97
|
+
|
|
98
|
+
def _run_flutter_installation(self) -> None:
|
|
99
|
+
"""Run Flutter SDK installation/update."""
|
|
100
|
+
console.print("\n[bold]🦋 Installing/updating Flutter SDK...[/bold]")
|
|
101
|
+
|
|
102
|
+
with Progress(
|
|
103
|
+
SpinnerColumn(),
|
|
104
|
+
TextColumn("[progress.description]{task.description}"),
|
|
105
|
+
console=console,
|
|
106
|
+
) as progress:
|
|
107
|
+
task = progress.add_task("Setting up Flutter SDK...", total=None)
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
self.flutter_manager.ensure_flutter()
|
|
111
|
+
progress.update(task, description="✅ Flutter SDK ready")
|
|
112
|
+
except Exception as e:
|
|
113
|
+
progress.update(task, description="❌ Flutter installation failed")
|
|
114
|
+
raise FlutterInstallationError(f"Failed to install Flutter: {e}")
|
|
115
|
+
|
|
116
|
+
def _run_project_creation(self) -> None:
|
|
117
|
+
"""Run Flutter project creation."""
|
|
118
|
+
console.print("\n[bold]🏗️ Creating Flutter project...[/bold]")
|
|
119
|
+
|
|
120
|
+
with Progress(
|
|
121
|
+
SpinnerColumn(),
|
|
122
|
+
TextColumn("[progress.description]{task.description}"),
|
|
123
|
+
console=console,
|
|
124
|
+
) as progress:
|
|
125
|
+
task = progress.add_task("Creating project structure...", total=None)
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
self.project_creator.create_project()
|
|
129
|
+
progress.update(task, description="✅ Project created")
|
|
130
|
+
except Exception as e:
|
|
131
|
+
progress.update(task, description="❌ Project creation failed")
|
|
132
|
+
raise ProjectCreationError(f"Failed to create project: {e}")
|
|
133
|
+
|
|
134
|
+
def _run_bootstrap(self) -> None:
|
|
135
|
+
"""Run project bootstrapping."""
|
|
136
|
+
console.print("\n[bold]🔧 Bootstrapping development environment...[/bold]")
|
|
137
|
+
|
|
138
|
+
with Progress(
|
|
139
|
+
SpinnerColumn(),
|
|
140
|
+
TextColumn("[progress.description]{task.description}"),
|
|
141
|
+
console=console,
|
|
142
|
+
) as progress:
|
|
143
|
+
task = progress.add_task("Setting up development tools...", total=None)
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
self.bootstrap.bootstrap_project()
|
|
147
|
+
progress.update(task, description="✅ Development environment ready")
|
|
148
|
+
except Exception as e:
|
|
149
|
+
progress.update(task, description="❌ Bootstrap failed")
|
|
150
|
+
raise FlutterSetupError(f"Failed to bootstrap project: {e}")
|
|
151
|
+
|
|
152
|
+
def _show_next_steps(self) -> None:
|
|
153
|
+
"""Display next steps for the user."""
|
|
154
|
+
console.print("\n[bold green]🎉 Setup completed successfully![/bold green]")
|
|
155
|
+
|
|
156
|
+
if self.platform == "darwin":
|
|
157
|
+
shell_profile_steps = (
|
|
158
|
+
" [code]source ~/.zprofile[/code] # zsh login shells\n"
|
|
159
|
+
" [code]source ~/.zshrc[/code] # zsh interactive shells"
|
|
160
|
+
)
|
|
161
|
+
else:
|
|
162
|
+
shell_profile_steps = (
|
|
163
|
+
" [code]source ~/.bashrc[/code] # bash\n"
|
|
164
|
+
" [code]source ~/.zshrc[/code] # zsh"
|
|
165
|
+
)
|
|
166
|
+
platform_run_labels = {
|
|
167
|
+
"web": ("make run-chrome", "runs on Chrome"),
|
|
168
|
+
"ios": ("make run-ios", "runs on iOS simulator"),
|
|
169
|
+
"android": ("make run-android", "runs on Android emulator"),
|
|
170
|
+
}
|
|
171
|
+
run_steps = "\n".join(
|
|
172
|
+
f" [code]{cmd}[/code] # {label}"
|
|
173
|
+
for p in self.config.platforms
|
|
174
|
+
if (entry := platform_run_labels.get(p))
|
|
175
|
+
for cmd, label in [entry]
|
|
176
|
+
)
|
|
177
|
+
if not run_steps:
|
|
178
|
+
run_steps = " [code]flutter run[/code]"
|
|
179
|
+
|
|
180
|
+
next_steps = f"""
|
|
181
|
+
[bold]Next steps:[/bold]
|
|
182
|
+
|
|
183
|
+
1. [blue]Activate Flutter in your shell:[/blue]
|
|
184
|
+
{shell_profile_steps}
|
|
185
|
+
|
|
186
|
+
2. [blue]Navigate to your project:[/blue]
|
|
187
|
+
[code]cd "{self.config.project_path}"[/code]
|
|
188
|
+
|
|
189
|
+
3. [blue]Run your Flutter app:[/blue]
|
|
190
|
+
{run_steps}
|
|
191
|
+
|
|
192
|
+
4. [blue]Test your setup:[/blue]
|
|
193
|
+
[code]make test[/code] # run unit + widget tests
|
|
194
|
+
[code]make analyze[/code] # check lints
|
|
195
|
+
|
|
196
|
+
5. [blue]Open in Cursor/VS Code:[/blue]
|
|
197
|
+
Hit F5 ("Flutter Debug") to start debugging
|
|
198
|
+
|
|
199
|
+
[dim]Your project is located at: {self.config.project_path}[/dim]
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
console.print(
|
|
203
|
+
Panel(next_steps, title="🚀 Ready to Code!", border_style="green")
|
|
204
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Custom exceptions for Flutter Setup."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FlutterSetupError(Exception):
|
|
5
|
+
"""Base exception for Flutter setup errors."""
|
|
6
|
+
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PrerequisitesError(FlutterSetupError):
|
|
11
|
+
"""Raised when prerequisites are not met."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FlutterInstallationError(FlutterSetupError):
|
|
17
|
+
"""Raised when Flutter installation fails."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ProjectCreationError(FlutterSetupError):
|
|
23
|
+
"""Raised when project creation fails."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConfigurationError(FlutterSetupError):
|
|
29
|
+
"""Raised when configuration is invalid."""
|
|
30
|
+
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SystemError(FlutterSetupError):
|
|
35
|
+
"""Raised when system-level operations fail."""
|
|
36
|
+
|
|
37
|
+
pass
|