flutter-dev 0.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.
managers/doctor.py ADDED
@@ -0,0 +1,286 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Doctor Module - Environment health check for Flutter development
4
+ Checks all required and optional tools, dependencies, and configurations
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import shutil
10
+ import subprocess
11
+ import platform
12
+ from pathlib import Path
13
+
14
+ from common_utils import RED, GREEN, YELLOW, BLUE, NC, CHECKMARK, CROSS, is_macos, is_windows
15
+
16
+
17
+ def _get_cmd_version(cmd_list, timeout=5):
18
+ """
19
+ Run a command and return its output (first line).
20
+ Returns None if the command is not found or fails.
21
+ """
22
+ try:
23
+ result = subprocess.run(
24
+ cmd_list,
25
+ capture_output=True,
26
+ text=True,
27
+ timeout=timeout,
28
+ encoding='utf-8',
29
+ errors='replace',
30
+ )
31
+ output = result.stdout.strip() or result.stderr.strip()
32
+ if result.returncode == 0 and output:
33
+ return output.splitlines()[0]
34
+ return None
35
+ except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
36
+ return None
37
+
38
+
39
+ def _extract_version(raw, prefix=None):
40
+ """Extract version number from a raw version string."""
41
+ if not raw:
42
+ return None
43
+ if prefix:
44
+ raw = raw.replace(prefix, '').strip()
45
+ # Take first token that looks like a version
46
+ import re
47
+ match = re.search(r'[\d]+(?:\.[\d]+)*', raw)
48
+ return match.group(0) if match else raw.strip()
49
+
50
+
51
+ def _check_tool(name, cmd_list, required=True, install_hint=None, version_prefix=None):
52
+ """
53
+ Check if a tool is installed and return (status, version_str, name).
54
+ status: True = found, False = not found
55
+ """
56
+ raw = _get_cmd_version(cmd_list)
57
+ if raw:
58
+ version = _extract_version(raw, version_prefix)
59
+ return True, version
60
+ else:
61
+ return False, install_hint
62
+
63
+
64
+ def _check_python_package(package_name):
65
+ """Check if a Python package is installed and return its version."""
66
+ try:
67
+ mod = __import__(package_name.replace('-', '_'))
68
+ version = getattr(mod, '__version__', None) or getattr(mod, 'VERSION', 'installed')
69
+ return True, str(version)
70
+ except ImportError:
71
+ return False, f"pip install {package_name}"
72
+
73
+
74
+ def _check_env_file():
75
+ """Check .env file existence and API key configuration."""
76
+ script_dir = Path(__file__).parent.parent
77
+ env_path = script_dir / '.env'
78
+
79
+ if not env_path.exists():
80
+ return False, ".env file not found"
81
+
82
+ # Read and check for configured API keys
83
+ configured_keys = []
84
+ default_service = None
85
+
86
+ with open(env_path, 'r', encoding='utf-8') as f:
87
+ for line in f:
88
+ line = line.strip()
89
+ if line.startswith('#') or '=' not in line:
90
+ continue
91
+ key, value = line.split('=', 1)
92
+ key = key.strip()
93
+ value = value.strip()
94
+
95
+ if key == 'DEFAULT_AI_SERVICE' and value:
96
+ default_service = value
97
+
98
+ # Check if API key is actually set (not placeholder)
99
+ if key.endswith('_API_KEY') and value and 'your_' not in value.lower() and 'here' not in value.lower():
100
+ service = key.replace('_API_KEY', '').lower()
101
+ configured_keys.append(service)
102
+
103
+ if configured_keys:
104
+ active = default_service or configured_keys[0]
105
+ return True, f"active: {active} ({len(configured_keys)} key(s) configured)"
106
+ else:
107
+ return False, "no API keys configured"
108
+
109
+
110
+ def _check_flutter_project():
111
+ """Check if current directory is a Flutter project."""
112
+ pubspec = Path('pubspec.yaml')
113
+ if pubspec.exists():
114
+ # Try to extract project name
115
+ try:
116
+ with open(pubspec, 'r', encoding='utf-8') as f:
117
+ for line in f:
118
+ if line.startswith('name:'):
119
+ name = line.split(':', 1)[1].strip()
120
+ return True, name
121
+ except Exception:
122
+ pass
123
+ return True, "detected"
124
+ return False, "not a Flutter project directory"
125
+
126
+
127
+ def run_doctor():
128
+ """Run full environment health check."""
129
+ print(f"\n{BLUE}╔══════════════════════════════════════════════════════╗{NC}")
130
+ print(f"{BLUE}║ fdev doctor - Environment Health Check ║{NC}")
131
+ print(f"{BLUE}╚══════════════════════════════════════════════════════╝{NC}")
132
+ print(f"{BLUE}Platform: {GREEN}{platform.system()} {platform.machine()}{NC}")
133
+ print(f"{BLUE}Python: {GREEN}{platform.python_version()} ({sys.executable}){NC}")
134
+ print()
135
+
136
+ passed = 0
137
+ failed = 0
138
+ warnings = 0
139
+
140
+ def print_result(name, status, detail, required=True, pad=22):
141
+ nonlocal passed, failed, warnings
142
+ label = name.ljust(pad)
143
+ if status:
144
+ print(f" {CHECKMARK} {label} {GREEN}{detail}{NC}")
145
+ passed += 1
146
+ elif required:
147
+ print(f" {CROSS} {label} {RED}{detail}{NC}")
148
+ failed += 1
149
+ else:
150
+ print(f" {YELLOW}!{NC} {label} {YELLOW}{detail}{NC}")
151
+ warnings += 1
152
+
153
+ # ── Core Tools (Required) ──────────────────────────────
154
+ print(f"{BLUE}Core Tools (required):{NC}")
155
+
156
+ status, detail = _check_tool("Flutter", ["flutter", "--version"], version_prefix="Flutter")
157
+ print_result("Flutter SDK", status, detail)
158
+
159
+ status, detail = _check_tool("Dart", ["dart", "--version"], version_prefix="Dart SDK version:")
160
+ print_result("Dart SDK", status, detail)
161
+
162
+ status, detail = _check_tool("Git", ["git", "--version"], version_prefix="git version")
163
+ print_result("Git", status, detail)
164
+
165
+ # Python packages
166
+ status, detail = _check_python_package("dotenv")
167
+ print_result("python-dotenv", status, detail)
168
+
169
+ status, detail = _check_python_package("requests")
170
+ print_result("requests", status, detail)
171
+
172
+ # ── Android Tools ──────────────────────────────────────
173
+ print(f"\n{BLUE}Android Tools:{NC}")
174
+
175
+ status, detail = _check_tool("ADB", ["adb", "--version"])
176
+ if status and detail:
177
+ detail = _extract_version(detail)
178
+ print_result("ADB", status, detail or "not found (install Android SDK platform-tools)", required=False)
179
+
180
+ # Check Java/JDK (needed for Android builds)
181
+ status, detail = _check_tool("Java", ["java", "-version"])
182
+ if not status:
183
+ detail = "not found (install JDK for Android builds)"
184
+ print_result("Java/JDK", status, detail, required=False)
185
+
186
+ # Check connected devices
187
+ if shutil.which("adb"):
188
+ try:
189
+ result = subprocess.run(
190
+ ["adb", "devices"], capture_output=True, text=True, timeout=5
191
+ )
192
+ lines = [l for l in result.stdout.strip().splitlines()[1:] if l.strip() and 'device' in l]
193
+ if lines:
194
+ print_result("Connected Devices", True, f"{len(lines)} device(s) connected", required=False)
195
+ else:
196
+ print_result("Connected Devices", False, "no devices connected", required=False)
197
+ except Exception:
198
+ print_result("Connected Devices", False, "could not check", required=False)
199
+
200
+ # ── iOS Tools (macOS only) ─────────────────────────────
201
+ if is_macos():
202
+ print(f"\n{BLUE}iOS Tools (macOS):{NC}")
203
+
204
+ status, detail = _check_tool("Xcode", ["xcodebuild", "-version"])
205
+ print_result("Xcode", status, detail or "not found (install from App Store)", required=False)
206
+
207
+ status, detail = _check_tool("CocoaPods", ["pod", "--version"])
208
+ print_result("CocoaPods", status, detail or "not found (gem install cocoapods)", required=False)
209
+
210
+ # xcrun check
211
+ status, detail = _check_tool("xcrun", ["xcrun", "--version"])
212
+ if status and detail:
213
+ detail = _extract_version(detail)
214
+ print_result("xcrun", status, detail or "not found (install Xcode CLI tools)", required=False)
215
+
216
+ # ── Optional Tools ─────────────────────────────────────
217
+ print(f"\n{BLUE}Optional Tools:{NC}")
218
+
219
+ status, detail = _check_tool("scrcpy", ["scrcpy", "--version"])
220
+ if not status:
221
+ if is_macos():
222
+ detail = "not found (brew install scrcpy)"
223
+ elif is_windows():
224
+ detail = "not found (scoop install scrcpy)"
225
+ else:
226
+ detail = "not found (apt install scrcpy)"
227
+ print_result("scrcpy", status, detail, required=False)
228
+
229
+ status, detail = _check_tool("Firebase CLI", ["firebase", "--version"])
230
+ if not status:
231
+ detail = "not found (npm install -g firebase-tools)"
232
+ print_result("Firebase CLI", status, detail, required=False)
233
+
234
+ # GitHub CLI (needed for `fdev git` account manager)
235
+ status, detail = _check_tool("gh", ["gh", "--version"])
236
+ if not status:
237
+ if is_macos():
238
+ detail = "not found (brew install gh)"
239
+ elif is_windows():
240
+ detail = "not found (scoop install gh)"
241
+ else:
242
+ detail = "not found (see https://cli.github.com)"
243
+ print_result("GitHub CLI", status, detail, required=False)
244
+
245
+ # VSCode CLI
246
+ status, detail = _check_tool("VSCode", ["code", "--version"])
247
+ if status and detail:
248
+ detail = detail.splitlines()[0]
249
+ if not status:
250
+ detail = "not found (install VSCode + shell command)"
251
+ print_result("VSCode CLI", status, detail, required=False)
252
+
253
+ # ── Configuration ──────────────────────────────────────
254
+ print(f"\n{BLUE}Configuration:{NC}")
255
+
256
+ status, detail = _check_env_file()
257
+ print_result(".env File", status, detail)
258
+
259
+ status, detail = _check_flutter_project()
260
+ print_result("Flutter Project", status, detail, required=False)
261
+
262
+ # Check if fdev is globally accessible
263
+ fdev_path = shutil.which("fdev")
264
+ if fdev_path:
265
+ print_result("fdev Global", True, fdev_path, required=False)
266
+ else:
267
+ print_result("fdev Global", False, "not in PATH (run setup.py)", required=False)
268
+
269
+ # ── Summary ────────────────────────────────────────────
270
+ print(f"\n{BLUE}{'─' * 54}{NC}")
271
+ total = passed + failed + warnings
272
+ summary_parts = []
273
+ if passed:
274
+ summary_parts.append(f"{GREEN}{passed} passed{NC}")
275
+ if warnings:
276
+ summary_parts.append(f"{YELLOW}{warnings} warning(s){NC}")
277
+ if failed:
278
+ summary_parts.append(f"{RED}{failed} issue(s){NC}")
279
+ print(f" {' | '.join(summary_parts)} (total: {total} checks)")
280
+
281
+ if failed == 0:
282
+ print(f"\n {GREEN}All required tools are installed! Happy coding!{NC}")
283
+ else:
284
+ print(f"\n {YELLOW}Fix the issues above to get full fdev functionality.{NC}")
285
+
286
+ print()