flutter-dev 0.1.5__tar.gz → 0.1.7__tar.gz
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_dev-0.1.5 → flutter_dev-0.1.7}/CHANGELOG.md +22 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/PKG-INFO +34 -5
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/README.md +33 -4
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/fdev.py +14 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/flutter_dev.egg-info/SOURCES.txt +3 -1
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/gui.py +112 -26
- flutter_dev-0.1.7/managers/package.py +148 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/pyproject.toml +1 -1
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/tests/test_fdev_cli.py +22 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/tests/test_gui.py +102 -0
- flutter_dev-0.1.7/tests/test_package_commands.py +129 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/.env.example +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/LICENSE +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/MANIFEST.in +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/common_utils.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/core/__init__.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/core/constants.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/core/state.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/create_page.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/gemini_api.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/git_diff_output_editor.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/install_legacy.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/__init__.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/ai.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/app.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/brew.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/build.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/datetime.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/device.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/doctor.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/git.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/git_account.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/localization.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/merge.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/mirror.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/project.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/managers/web_deploy.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/requirements.txt +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/setup.cfg +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/setup.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/switch_ai.py +0 -0
- {flutter_dev-0.1.5 → flutter_dev-0.1.7}/tests/test_localization.py +0 -0
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.1.7 - 2026-06-25
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Reworked the Tkinter dashboard to use classic Tk widgets with explicit colors so `fdev` stays visible on macOS Xcode/system Python runtimes that can render `ttk` widgets as a blank popup.
|
|
10
|
+
- Silenced the macOS system Tk deprecation warning before launching the dashboard.
|
|
11
|
+
|
|
12
|
+
## 0.1.6 - 2026-06-25
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Added `fdev upgrade` to update the local `flutter-dev` package from PyPI after detecting the current OS.
|
|
17
|
+
- Added `fdev delete` to uninstall the local `flutter-dev` package after detecting the current OS and confirming the destructive action.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- `fdev upgrade` and `fdev delete` now print the exact Python executable they target before running `sys.executable -m pip`.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Reworked the Tkinter dashboard layout to avoid platform-sensitive paned-window sizing that could open a blank `fdev` popup after installing from PyPI.
|
|
26
|
+
|
|
5
27
|
## 0.1.5 - 2026-06-24
|
|
6
28
|
|
|
7
29
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flutter-dev
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
4
4
|
Summary: A cross-platform CLI toolkit for Flutter development workflows.
|
|
5
5
|
Author: Royal Court BD
|
|
6
6
|
License-Expression: MIT
|
|
@@ -39,10 +39,10 @@ Dynamic: license-file
|
|
|
39
39
|
The package is published on PyPI as `flutter-dev` and installs console commands for day-to-day Flutter work:
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
pip install flutter-dev
|
|
42
|
+
python -m pip install flutter-dev
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
Current package metadata declares version `0.1.
|
|
45
|
+
Current package metadata declares version `0.1.7`, Python `>=3.8`, MIT license, and support for macOS, Linux, and Windows.
|
|
46
46
|
|
|
47
47
|
## Key Features
|
|
48
48
|
|
|
@@ -55,7 +55,7 @@ Current package metadata declares version `0.1.5`, Python `>=3.8`, MIT license,
|
|
|
55
55
|
- Feature/page generator that creates a `lib/features/<name>` structure using the project's presenter/base-state pattern.
|
|
56
56
|
- Small Tkinter desktop dashboard opened by running `fdev` with no command, with live colored command logs.
|
|
57
57
|
- Environment diagnostics through `fdev doctor`.
|
|
58
|
-
- Small utilities for Homebrew cleanup, file merging from `paths.txt`, AI provider switching, and git diff prompt export.
|
|
58
|
+
- Small utilities for Homebrew cleanup, file merging from `paths.txt`, AI provider switching, PyPI self-upgrade/delete, and git diff prompt export.
|
|
59
59
|
|
|
60
60
|
## Installation
|
|
61
61
|
|
|
@@ -71,7 +71,7 @@ Verify the CLI is available:
|
|
|
71
71
|
fdev doctor
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
`pip install flutter-dev` installs the Python package, Python dependencies, and console scripts
|
|
74
|
+
`python -m pip install flutter-dev` installs the Python package, Python dependencies, and console scripts into the Python environment selected by `python`. It does not install Flutter, Dart, Git, Android SDK tools, CocoaPods, Firebase CLI, GitHub CLI, `scrcpy`, Java/JDK, or VS Code CLI.
|
|
75
75
|
|
|
76
76
|
The desktop dashboard uses Python Tkinter. Some Python distributions install this separately; for Homebrew Python, install the matching package such as `brew install python-tk@3.14`.
|
|
77
77
|
|
|
@@ -135,6 +135,9 @@ fdev
|
|
|
135
135
|
# Show command help
|
|
136
136
|
fdev help
|
|
137
137
|
|
|
138
|
+
# Upgrade the fdev CLI from PyPI
|
|
139
|
+
fdev upgrade
|
|
140
|
+
|
|
138
141
|
# Check environment and optional tools
|
|
139
142
|
fdev doctor
|
|
140
143
|
|
|
@@ -239,6 +242,8 @@ Switching GitHub accounts runs `gh auth switch` and `gh auth setup-git`, but it
|
|
|
239
242
|
| `fdev` | Opens a small Tkinter desktop dashboard for running `fdev` commands with live colored logs and command input. The terminal process stays running while the window is open. |
|
|
240
243
|
| `fdev help` | Shows command help without opening the dashboard. |
|
|
241
244
|
| `fdev doctor` | Checks platform, Python, Flutter/Dart/Git, Python packages, Android/iOS tools, optional CLIs, `.env`, Flutter project detection, and global `fdev` availability. |
|
|
245
|
+
| `fdev upgrade` | Detects the OS, then upgrades the active Python environment's `flutter-dev` package from PyPI with `python -m pip install --upgrade flutter-dev`. |
|
|
246
|
+
| `fdev delete` | Detects the OS, asks for confirmation, then uninstalls the active Python environment's `flutter-dev` package. Use `fdev delete --yes` to skip confirmation. |
|
|
242
247
|
| `fdev ai` | Shows the current AI provider and opens an interactive provider switch prompt. |
|
|
243
248
|
| `fdev ai <provider>` | Switches `DEFAULT_AI_SERVICE` to `groq`, `mistral`, `sambanova`, or `openrouter`. |
|
|
244
249
|
| `fdev brew` | Interactive Homebrew package manager for viewing packages, uninstalling with cleanup, clearing cache, and global cleanup. |
|
|
@@ -368,6 +373,7 @@ managers/mirror.py scrcpy install, mirror, wireless ADB setup
|
|
|
368
373
|
managers/git.py commit, tag, discard, branch sync, deployment merge
|
|
369
374
|
managers/git_account.py GitHub account manager around gh CLI
|
|
370
375
|
managers/doctor.py Environment diagnostics
|
|
376
|
+
managers/package.py PyPI self-upgrade and local uninstall commands
|
|
371
377
|
managers/ai.py AI provider status and switching
|
|
372
378
|
managers/brew.py Interactive Homebrew manager
|
|
373
379
|
managers/merge.py paths.txt to path_merge_files.txt merger
|
|
@@ -386,6 +392,8 @@ The installed Python layout is top-level modules plus the `core` and `managers`
|
|
|
386
392
|
- `fdev cleanup` applies `dart fix`, formats the project, and runs a major-version dependency upgrade.
|
|
387
393
|
- `fdev clear-data` clears app data on Android after confirmation.
|
|
388
394
|
- `fdev uninstall` may fall back to foreground app detection outside a Flutter project root.
|
|
395
|
+
- `fdev upgrade` and `fdev delete` manage the local `flutter-dev` Python package, not the Flutter app on a device.
|
|
396
|
+
- `fdev delete` removes the `fdev` command from the current Python environment after confirmation; reinstall with `python -m pip install flutter-dev`.
|
|
389
397
|
- `fdev` with no command opens the desktop dashboard. Use `fdev help` to print command usage.
|
|
390
398
|
- Most command subcommands create build output directories at startup in the current working directory.
|
|
391
399
|
- Some subcommands return failure internally without always mapping that failure to a non-zero process exit code, so automation should inspect command output carefully.
|
|
@@ -414,6 +422,27 @@ On Windows, use:
|
|
|
414
422
|
where fdev
|
|
415
423
|
```
|
|
416
424
|
|
|
425
|
+
### Upgrade Or Remove fdev
|
|
426
|
+
|
|
427
|
+
Use the built-in package management commands from the Python environment where `fdev` is installed:
|
|
428
|
+
|
|
429
|
+
```bash
|
|
430
|
+
fdev upgrade
|
|
431
|
+
fdev delete
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
Both commands print the detected OS and exact Python executable before running. They use that executable with `-m pip`, not a hardcoded `python`, `python3`, `pip`, or `pip3`, so the active venv/global Python that launched `fdev` is the environment that gets upgraded or deleted. On Windows, `fdev delete` and `fdev upgrade` schedule the same `sys.executable -m pip ...` operation in a delayed command process so the currently running console launcher can exit first.
|
|
435
|
+
|
|
436
|
+
### Blank Dashboard On macOS
|
|
437
|
+
|
|
438
|
+
If `fdev` prints an Xcode/system Tk warning or opens a blank dashboard, upgrade to `flutter-dev` 0.1.7 or newer:
|
|
439
|
+
|
|
440
|
+
```bash
|
|
441
|
+
fdev upgrade
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
`fdev` prints the Python executable it is using. If that path is `/Applications/Xcode.app/Contents/Developer/usr/bin/python3`, the CLI is running on Xcode's bundled Python and system Tk; 0.1.7 uses a more conservative Tk UI path for that runtime.
|
|
445
|
+
|
|
417
446
|
### Flutter Project Not Detected
|
|
418
447
|
|
|
419
448
|
Run project-specific commands from the directory that contains `pubspec.yaml`.
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
The package is published on PyPI as `flutter-dev` and installs console commands for day-to-day Flutter work:
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
pip install flutter-dev
|
|
8
|
+
python -m pip install flutter-dev
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Current package metadata declares version `0.1.
|
|
11
|
+
Current package metadata declares version `0.1.7`, Python `>=3.8`, MIT license, and support for macOS, Linux, and Windows.
|
|
12
12
|
|
|
13
13
|
## Key Features
|
|
14
14
|
|
|
@@ -21,7 +21,7 @@ Current package metadata declares version `0.1.5`, Python `>=3.8`, MIT license,
|
|
|
21
21
|
- Feature/page generator that creates a `lib/features/<name>` structure using the project's presenter/base-state pattern.
|
|
22
22
|
- Small Tkinter desktop dashboard opened by running `fdev` with no command, with live colored command logs.
|
|
23
23
|
- Environment diagnostics through `fdev doctor`.
|
|
24
|
-
- Small utilities for Homebrew cleanup, file merging from `paths.txt`, AI provider switching, and git diff prompt export.
|
|
24
|
+
- Small utilities for Homebrew cleanup, file merging from `paths.txt`, AI provider switching, PyPI self-upgrade/delete, and git diff prompt export.
|
|
25
25
|
|
|
26
26
|
## Installation
|
|
27
27
|
|
|
@@ -37,7 +37,7 @@ Verify the CLI is available:
|
|
|
37
37
|
fdev doctor
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
`pip install flutter-dev` installs the Python package, Python dependencies, and console scripts
|
|
40
|
+
`python -m pip install flutter-dev` installs the Python package, Python dependencies, and console scripts into the Python environment selected by `python`. It does not install Flutter, Dart, Git, Android SDK tools, CocoaPods, Firebase CLI, GitHub CLI, `scrcpy`, Java/JDK, or VS Code CLI.
|
|
41
41
|
|
|
42
42
|
The desktop dashboard uses Python Tkinter. Some Python distributions install this separately; for Homebrew Python, install the matching package such as `brew install python-tk@3.14`.
|
|
43
43
|
|
|
@@ -101,6 +101,9 @@ fdev
|
|
|
101
101
|
# Show command help
|
|
102
102
|
fdev help
|
|
103
103
|
|
|
104
|
+
# Upgrade the fdev CLI from PyPI
|
|
105
|
+
fdev upgrade
|
|
106
|
+
|
|
104
107
|
# Check environment and optional tools
|
|
105
108
|
fdev doctor
|
|
106
109
|
|
|
@@ -205,6 +208,8 @@ Switching GitHub accounts runs `gh auth switch` and `gh auth setup-git`, but it
|
|
|
205
208
|
| `fdev` | Opens a small Tkinter desktop dashboard for running `fdev` commands with live colored logs and command input. The terminal process stays running while the window is open. |
|
|
206
209
|
| `fdev help` | Shows command help without opening the dashboard. |
|
|
207
210
|
| `fdev doctor` | Checks platform, Python, Flutter/Dart/Git, Python packages, Android/iOS tools, optional CLIs, `.env`, Flutter project detection, and global `fdev` availability. |
|
|
211
|
+
| `fdev upgrade` | Detects the OS, then upgrades the active Python environment's `flutter-dev` package from PyPI with `python -m pip install --upgrade flutter-dev`. |
|
|
212
|
+
| `fdev delete` | Detects the OS, asks for confirmation, then uninstalls the active Python environment's `flutter-dev` package. Use `fdev delete --yes` to skip confirmation. |
|
|
208
213
|
| `fdev ai` | Shows the current AI provider and opens an interactive provider switch prompt. |
|
|
209
214
|
| `fdev ai <provider>` | Switches `DEFAULT_AI_SERVICE` to `groq`, `mistral`, `sambanova`, or `openrouter`. |
|
|
210
215
|
| `fdev brew` | Interactive Homebrew package manager for viewing packages, uninstalling with cleanup, clearing cache, and global cleanup. |
|
|
@@ -334,6 +339,7 @@ managers/mirror.py scrcpy install, mirror, wireless ADB setup
|
|
|
334
339
|
managers/git.py commit, tag, discard, branch sync, deployment merge
|
|
335
340
|
managers/git_account.py GitHub account manager around gh CLI
|
|
336
341
|
managers/doctor.py Environment diagnostics
|
|
342
|
+
managers/package.py PyPI self-upgrade and local uninstall commands
|
|
337
343
|
managers/ai.py AI provider status and switching
|
|
338
344
|
managers/brew.py Interactive Homebrew manager
|
|
339
345
|
managers/merge.py paths.txt to path_merge_files.txt merger
|
|
@@ -352,6 +358,8 @@ The installed Python layout is top-level modules plus the `core` and `managers`
|
|
|
352
358
|
- `fdev cleanup` applies `dart fix`, formats the project, and runs a major-version dependency upgrade.
|
|
353
359
|
- `fdev clear-data` clears app data on Android after confirmation.
|
|
354
360
|
- `fdev uninstall` may fall back to foreground app detection outside a Flutter project root.
|
|
361
|
+
- `fdev upgrade` and `fdev delete` manage the local `flutter-dev` Python package, not the Flutter app on a device.
|
|
362
|
+
- `fdev delete` removes the `fdev` command from the current Python environment after confirmation; reinstall with `python -m pip install flutter-dev`.
|
|
355
363
|
- `fdev` with no command opens the desktop dashboard. Use `fdev help` to print command usage.
|
|
356
364
|
- Most command subcommands create build output directories at startup in the current working directory.
|
|
357
365
|
- Some subcommands return failure internally without always mapping that failure to a non-zero process exit code, so automation should inspect command output carefully.
|
|
@@ -380,6 +388,27 @@ On Windows, use:
|
|
|
380
388
|
where fdev
|
|
381
389
|
```
|
|
382
390
|
|
|
391
|
+
### Upgrade Or Remove fdev
|
|
392
|
+
|
|
393
|
+
Use the built-in package management commands from the Python environment where `fdev` is installed:
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
fdev upgrade
|
|
397
|
+
fdev delete
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
Both commands print the detected OS and exact Python executable before running. They use that executable with `-m pip`, not a hardcoded `python`, `python3`, `pip`, or `pip3`, so the active venv/global Python that launched `fdev` is the environment that gets upgraded or deleted. On Windows, `fdev delete` and `fdev upgrade` schedule the same `sys.executable -m pip ...` operation in a delayed command process so the currently running console launcher can exit first.
|
|
401
|
+
|
|
402
|
+
### Blank Dashboard On macOS
|
|
403
|
+
|
|
404
|
+
If `fdev` prints an Xcode/system Tk warning or opens a blank dashboard, upgrade to `flutter-dev` 0.1.7 or newer:
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
fdev upgrade
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
`fdev` prints the Python executable it is using. If that path is `/Applications/Xcode.app/Contents/Developer/usr/bin/python3`, the CLI is running on Xcode's bundled Python and system Tk; 0.1.7 uses a more conservative Tk UI path for that runtime.
|
|
411
|
+
|
|
383
412
|
### Flutter Project Not Detected
|
|
384
413
|
|
|
385
414
|
Run project-specific commands from the directory that contains `pubspec.yaml`.
|
|
@@ -73,6 +73,10 @@ from managers.git_account import (
|
|
|
73
73
|
from managers.gui import (
|
|
74
74
|
launch_gui,
|
|
75
75
|
)
|
|
76
|
+
from managers.package import (
|
|
77
|
+
delete_fdev_command,
|
|
78
|
+
upgrade_fdev_command,
|
|
79
|
+
)
|
|
76
80
|
|
|
77
81
|
|
|
78
82
|
def show_usage(exit_code=1):
|
|
@@ -118,6 +122,8 @@ def show_usage(exit_code=1):
|
|
|
118
122
|
print(f"\n{BLUE}Utility Commands:{NC}")
|
|
119
123
|
print(" doctor Check environment health (tools, deps, config)")
|
|
120
124
|
print(" brew Interactive Homebrew package manager")
|
|
125
|
+
print(" upgrade Upgrade fdev from PyPI")
|
|
126
|
+
print(" delete Uninstall fdev from this computer")
|
|
121
127
|
print(" help Show this help")
|
|
122
128
|
print(" merge Merge files from paths.txt into single output file")
|
|
123
129
|
print(" ai Show current AI service or switch to another")
|
|
@@ -142,6 +148,8 @@ def show_usage(exit_code=1):
|
|
|
142
148
|
print(f" {GREEN}fdev git config{NC} # Update git user.name / user.email")
|
|
143
149
|
print(f" {GREEN}fdev arb-to-csv{NC} # Export lib/l10n ARB files to translations.csv")
|
|
144
150
|
print(f" {GREEN}fdev csv-to-arb{NC} # Generate lib/l10n ARB files from translations.csv")
|
|
151
|
+
print(f" {GREEN}fdev upgrade{NC} # Upgrade flutter-dev from PyPI")
|
|
152
|
+
print(f" {GREEN}fdev delete{NC} # Remove flutter-dev/fdev from this computer")
|
|
145
153
|
print(f" {GREEN}fdev{NC} # Open small desktop dashboard")
|
|
146
154
|
|
|
147
155
|
print(f"\n{BLUE}Note:{NC}")
|
|
@@ -259,6 +267,12 @@ def main():
|
|
|
259
267
|
elif command == "brew":
|
|
260
268
|
brew_manager()
|
|
261
269
|
|
|
270
|
+
# Package self-management commands
|
|
271
|
+
elif command == "upgrade":
|
|
272
|
+
sys.exit(upgrade_fdev_command(sys.argv[2:]))
|
|
273
|
+
elif command == "delete":
|
|
274
|
+
sys.exit(delete_fdev_command(sys.argv[2:]))
|
|
275
|
+
|
|
262
276
|
# AI command
|
|
263
277
|
elif command == "ai":
|
|
264
278
|
if len(sys.argv) < 3:
|
|
@@ -30,8 +30,10 @@ managers/gui.py
|
|
|
30
30
|
managers/localization.py
|
|
31
31
|
managers/merge.py
|
|
32
32
|
managers/mirror.py
|
|
33
|
+
managers/package.py
|
|
33
34
|
managers/project.py
|
|
34
35
|
managers/web_deploy.py
|
|
35
36
|
tests/test_fdev_cli.py
|
|
36
37
|
tests/test_gui.py
|
|
37
|
-
tests/test_localization.py
|
|
38
|
+
tests/test_localization.py
|
|
39
|
+
tests/test_package_commands.py
|
|
@@ -5,6 +5,7 @@ Small Tkinter GUI for running fdev commands with live logs.
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import os
|
|
8
9
|
import shlex
|
|
9
10
|
import subprocess
|
|
10
11
|
import sys
|
|
@@ -37,6 +38,21 @@ ANSI_COLORS = {
|
|
|
37
38
|
97: "bright_white",
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
UI_BG = "#f8fafc"
|
|
42
|
+
SIDEBAR_BG = "#e5e7eb"
|
|
43
|
+
PANEL_BG = "#ffffff"
|
|
44
|
+
TEXT_FG = "#111827"
|
|
45
|
+
MUTED_FG = "#475569"
|
|
46
|
+
BUTTON_BG = "#e2e8f0"
|
|
47
|
+
BUTTON_ACTIVE_BG = "#cbd5e1"
|
|
48
|
+
LOG_BG = "#111827"
|
|
49
|
+
LOG_FG = "#e5e7eb"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def prepare_tk_environment():
|
|
53
|
+
"""Prepare Tk for older macOS system Tk runtimes before importing tkinter."""
|
|
54
|
+
os.environ.setdefault("TK_SILENCE_DEPRECATION", "1")
|
|
55
|
+
|
|
40
56
|
|
|
41
57
|
def _apply_ansi_codes(codes: List[int], current_tag: Optional[str]) -> Optional[str]:
|
|
42
58
|
if not codes:
|
|
@@ -194,6 +210,8 @@ def get_gui_sections():
|
|
|
194
210
|
"Tools",
|
|
195
211
|
[
|
|
196
212
|
GuiCommand("Brew", ["brew"], "Open Homebrew package manager."),
|
|
213
|
+
GuiCommand("Upgrade fdev", ["upgrade"], "Upgrade flutter-dev from PyPI."),
|
|
214
|
+
GuiCommand("Delete fdev", ["delete"], "Uninstall flutter-dev from this computer.", confirm=True),
|
|
197
215
|
GuiCommand("Merge Files", ["merge"], "Merge paths listed in paths.txt."),
|
|
198
216
|
GuiCommand(
|
|
199
217
|
"AI Switch",
|
|
@@ -301,83 +319,127 @@ class FdevGui:
|
|
|
301
319
|
|
|
302
320
|
def _build_widgets(self):
|
|
303
321
|
import tkinter as tk
|
|
304
|
-
|
|
322
|
+
|
|
323
|
+
self.root.configure(bg=UI_BG)
|
|
305
324
|
|
|
306
325
|
self.root.columnconfigure(1, weight=1)
|
|
307
326
|
self.root.rowconfigure(0, weight=1)
|
|
308
327
|
|
|
309
|
-
sidebar =
|
|
328
|
+
sidebar = tk.Frame(self.root, bg=SIDEBAR_BG, padx=10, pady=10)
|
|
310
329
|
sidebar.grid(row=0, column=0, sticky="ns")
|
|
311
330
|
|
|
312
|
-
title =
|
|
331
|
+
title = tk.Label(
|
|
332
|
+
sidebar,
|
|
333
|
+
text="fdev",
|
|
334
|
+
font=("TkDefaultFont", 16, "bold"),
|
|
335
|
+
bg=SIDEBAR_BG,
|
|
336
|
+
fg=TEXT_FG,
|
|
337
|
+
)
|
|
313
338
|
title.grid(row=0, column=0, sticky="w", pady=(0, 10))
|
|
314
339
|
|
|
315
340
|
for index, section_name in enumerate(self.sections, start=1):
|
|
316
|
-
button =
|
|
341
|
+
button = tk.Button(
|
|
317
342
|
sidebar,
|
|
318
343
|
text=section_name,
|
|
319
344
|
command=lambda name=section_name: self._show_section(name),
|
|
345
|
+
bg=BUTTON_BG,
|
|
346
|
+
fg=TEXT_FG,
|
|
347
|
+
activebackground=BUTTON_ACTIVE_BG,
|
|
348
|
+
activeforeground=TEXT_FG,
|
|
349
|
+
relief="raised",
|
|
350
|
+
bd=1,
|
|
351
|
+
padx=10,
|
|
352
|
+
pady=4,
|
|
320
353
|
)
|
|
321
354
|
button.grid(row=index, column=0, sticky="ew", pady=3)
|
|
322
355
|
|
|
323
|
-
content =
|
|
356
|
+
content = tk.Frame(self.root, bg=UI_BG, padx=8, pady=10)
|
|
324
357
|
content.grid(row=0, column=1, sticky="nsew")
|
|
325
358
|
content.columnconfigure(0, weight=1)
|
|
326
359
|
content.rowconfigure(1, weight=1)
|
|
327
360
|
|
|
328
|
-
header =
|
|
361
|
+
header = tk.Frame(content, bg=UI_BG)
|
|
329
362
|
header.grid(row=0, column=0, sticky="ew", pady=(0, 8))
|
|
330
363
|
header.columnconfigure(0, weight=1)
|
|
331
364
|
|
|
332
|
-
self.section_label =
|
|
365
|
+
self.section_label = tk.Label(
|
|
366
|
+
header,
|
|
367
|
+
text="",
|
|
368
|
+
font=("TkDefaultFont", 14, "bold"),
|
|
369
|
+
bg=UI_BG,
|
|
370
|
+
fg=TEXT_FG,
|
|
371
|
+
)
|
|
333
372
|
self.section_label.grid(row=0, column=0, sticky="w")
|
|
334
373
|
|
|
335
|
-
self.status_label =
|
|
374
|
+
self.status_label = tk.Label(header, text="Ready", bg=UI_BG, fg=MUTED_FG)
|
|
336
375
|
self.status_label.grid(row=0, column=1, sticky="e")
|
|
337
376
|
|
|
338
|
-
body =
|
|
377
|
+
body = tk.Frame(content, bg=UI_BG)
|
|
339
378
|
body.grid(row=1, column=0, sticky="nsew")
|
|
379
|
+
body.columnconfigure(0, weight=1)
|
|
380
|
+
body.rowconfigure(0, weight=0)
|
|
381
|
+
body.rowconfigure(1, weight=1)
|
|
340
382
|
|
|
341
|
-
self.command_frame =
|
|
383
|
+
self.command_frame = tk.Frame(body, bg=UI_BG, pady=6)
|
|
384
|
+
self.command_frame.grid(row=0, column=0, sticky="ew")
|
|
342
385
|
self.command_frame.columnconfigure(0, weight=1)
|
|
343
386
|
self.command_frame.columnconfigure(1, weight=1)
|
|
344
|
-
body.add(self.command_frame, weight=2)
|
|
345
387
|
|
|
346
|
-
log_frame =
|
|
388
|
+
log_frame = tk.Frame(body, bg=UI_BG)
|
|
389
|
+
log_frame.grid(row=1, column=0, sticky="nsew", pady=(6, 0))
|
|
347
390
|
log_frame.columnconfigure(0, weight=1)
|
|
348
391
|
log_frame.rowconfigure(0, weight=1)
|
|
349
|
-
body.add(log_frame, weight=3)
|
|
350
392
|
|
|
351
393
|
self.log_text = tk.Text(
|
|
352
394
|
log_frame,
|
|
353
395
|
height=12,
|
|
354
396
|
wrap="word",
|
|
355
397
|
state="disabled",
|
|
356
|
-
bg=
|
|
357
|
-
fg=
|
|
358
|
-
insertbackground=
|
|
398
|
+
bg=LOG_BG,
|
|
399
|
+
fg=LOG_FG,
|
|
400
|
+
insertbackground=LOG_FG,
|
|
401
|
+
relief="sunken",
|
|
402
|
+
bd=1,
|
|
359
403
|
)
|
|
360
404
|
self.log_text.grid(row=0, column=0, sticky="nsew")
|
|
361
405
|
|
|
362
|
-
scrollbar =
|
|
406
|
+
scrollbar = tk.Scrollbar(log_frame, orient="vertical", command=self.log_text.yview)
|
|
363
407
|
scrollbar.grid(row=0, column=1, sticky="ns")
|
|
364
408
|
self.log_text.configure(yscrollcommand=scrollbar.set)
|
|
365
409
|
self._configure_log_tags()
|
|
366
410
|
|
|
367
|
-
input_bar =
|
|
411
|
+
input_bar = tk.Frame(content, bg=UI_BG)
|
|
368
412
|
input_bar.grid(row=2, column=0, sticky="ew", pady=(8, 0))
|
|
369
413
|
input_bar.columnconfigure(0, weight=1)
|
|
370
414
|
|
|
371
|
-
self.input_entry =
|
|
415
|
+
self.input_entry = tk.Entry(input_bar, bg=PANEL_BG, fg=TEXT_FG, insertbackground=TEXT_FG)
|
|
372
416
|
self.input_entry.grid(row=0, column=0, sticky="ew", padx=(0, 6))
|
|
373
417
|
self.input_entry.bind("<Return>", lambda _event: self._send_input())
|
|
374
418
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
419
|
+
tk.Button(
|
|
420
|
+
input_bar,
|
|
421
|
+
text="Send Input",
|
|
422
|
+
command=self._send_input,
|
|
423
|
+
bg=BUTTON_BG,
|
|
424
|
+
activebackground=BUTTON_ACTIVE_BG,
|
|
425
|
+
).grid(row=0, column=1, padx=(0, 6))
|
|
426
|
+
tk.Button(
|
|
427
|
+
input_bar,
|
|
428
|
+
text="Stop",
|
|
429
|
+
command=self._stop_process,
|
|
430
|
+
bg=BUTTON_BG,
|
|
431
|
+
activebackground=BUTTON_ACTIVE_BG,
|
|
432
|
+
).grid(row=0, column=2, padx=(0, 6))
|
|
433
|
+
tk.Button(
|
|
434
|
+
input_bar,
|
|
435
|
+
text="Clear",
|
|
436
|
+
command=self._clear_log,
|
|
437
|
+
bg=BUTTON_BG,
|
|
438
|
+
activebackground=BUTTON_ACTIVE_BG,
|
|
439
|
+
).grid(row=0, column=3)
|
|
378
440
|
|
|
379
441
|
def _show_section(self, section_name: str):
|
|
380
|
-
|
|
442
|
+
import tkinter as tk
|
|
381
443
|
|
|
382
444
|
self.current_section = section_name
|
|
383
445
|
self.section_label.configure(text=section_name)
|
|
@@ -389,18 +451,41 @@ class FdevGui:
|
|
|
389
451
|
row = index // 2
|
|
390
452
|
column = index % 2
|
|
391
453
|
|
|
392
|
-
container =
|
|
454
|
+
container = tk.Frame(
|
|
455
|
+
self.command_frame,
|
|
456
|
+
bg=PANEL_BG,
|
|
457
|
+
bd=1,
|
|
458
|
+
relief="solid",
|
|
459
|
+
padx=6,
|
|
460
|
+
pady=6,
|
|
461
|
+
)
|
|
393
462
|
container.grid(row=row, column=column, sticky="nsew", padx=4, pady=4)
|
|
394
463
|
container.columnconfigure(0, weight=1)
|
|
395
464
|
|
|
396
|
-
button =
|
|
465
|
+
button = tk.Button(
|
|
397
466
|
container,
|
|
398
467
|
text=command.label,
|
|
399
468
|
command=lambda item=command: self._run_command(item),
|
|
469
|
+
bg=BUTTON_BG,
|
|
470
|
+
fg=TEXT_FG,
|
|
471
|
+
activebackground=BUTTON_ACTIVE_BG,
|
|
472
|
+
activeforeground=TEXT_FG,
|
|
473
|
+
relief="raised",
|
|
474
|
+
bd=1,
|
|
475
|
+
padx=8,
|
|
476
|
+
pady=4,
|
|
400
477
|
)
|
|
401
478
|
button.grid(row=0, column=0, sticky="ew")
|
|
402
479
|
|
|
403
|
-
description =
|
|
480
|
+
description = tk.Label(
|
|
481
|
+
container,
|
|
482
|
+
text=command.description,
|
|
483
|
+
wraplength=260,
|
|
484
|
+
bg=PANEL_BG,
|
|
485
|
+
fg=MUTED_FG,
|
|
486
|
+
justify="left",
|
|
487
|
+
anchor="w",
|
|
488
|
+
)
|
|
404
489
|
description.grid(row=1, column=0, sticky="w", pady=(4, 0))
|
|
405
490
|
|
|
406
491
|
def _run_command(self, command: GuiCommand):
|
|
@@ -518,6 +603,7 @@ class FdevGui:
|
|
|
518
603
|
|
|
519
604
|
def launch_gui() -> int:
|
|
520
605
|
"""Launch the fdev Tkinter dashboard."""
|
|
606
|
+
prepare_tk_environment()
|
|
521
607
|
try:
|
|
522
608
|
import tkinter as tk
|
|
523
609
|
except ImportError:
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Package Manager - self-management commands for the installed fdev package.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import platform
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
from common_utils import BLUE, GREEN, RED, YELLOW, NC
|
|
12
|
+
|
|
13
|
+
PACKAGE_NAME = "flutter-dev"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_os_info():
|
|
17
|
+
"""
|
|
18
|
+
Detect the operating system before package self-management actions.
|
|
19
|
+
Returns a tuple of (key, display_name).
|
|
20
|
+
"""
|
|
21
|
+
system = platform.system()
|
|
22
|
+
|
|
23
|
+
if system == "Darwin":
|
|
24
|
+
return "macos", "macOS"
|
|
25
|
+
if system == "Linux":
|
|
26
|
+
return "linux", "Linux"
|
|
27
|
+
if system == "Windows":
|
|
28
|
+
return "windows", "Windows"
|
|
29
|
+
|
|
30
|
+
return "unknown", system or "Unknown"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _print_os_info(os_name):
|
|
34
|
+
print(f"{BLUE}Detected OS: {GREEN}{os_name}{NC}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _print_python_environment():
|
|
38
|
+
print(f"{BLUE}Python environment: {GREEN}{sys.executable}{NC}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _build_pip_command(pip_args):
|
|
42
|
+
return [sys.executable, "-m", "pip", *pip_args]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _run_pip_direct(pip_args):
|
|
46
|
+
result = subprocess.run(_build_pip_command(pip_args))
|
|
47
|
+
return result.returncode
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _build_windows_detached_command(pip_args):
|
|
51
|
+
pip_command = subprocess.list2cmdline(_build_pip_command(pip_args))
|
|
52
|
+
delayed_command = f"timeout /t 2 /nobreak > nul & {pip_command}"
|
|
53
|
+
return [
|
|
54
|
+
"cmd",
|
|
55
|
+
"/c",
|
|
56
|
+
"start",
|
|
57
|
+
"fdev package command",
|
|
58
|
+
"cmd",
|
|
59
|
+
"/c",
|
|
60
|
+
delayed_command,
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _run_windows_detached(pip_args, action_label):
|
|
65
|
+
command = _build_windows_detached_command(pip_args)
|
|
66
|
+
creationflags = (
|
|
67
|
+
getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
|
|
68
|
+
| getattr(subprocess, "DETACHED_PROCESS", 0)
|
|
69
|
+
)
|
|
70
|
+
subprocess.Popen(command, creationflags=creationflags)
|
|
71
|
+
print(f"{YELLOW}{action_label} has been scheduled in a new Windows command process.{NC}")
|
|
72
|
+
print(f"{YELLOW}Close this terminal after the command starts, then open a new one.{NC}")
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _confirm_delete():
|
|
77
|
+
print(f"{RED}This will uninstall the local {PACKAGE_NAME} package and remove the fdev command.{NC}")
|
|
78
|
+
answer = input(f"Delete {GREEN}{PACKAGE_NAME}{NC} from this Python environment? (y/N): ")
|
|
79
|
+
return answer.strip().lower() == "y"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _delete_parser():
|
|
83
|
+
parser = argparse.ArgumentParser(
|
|
84
|
+
prog="fdev delete",
|
|
85
|
+
description="Uninstall the local flutter-dev package that provides fdev.",
|
|
86
|
+
)
|
|
87
|
+
parser.add_argument(
|
|
88
|
+
"-y",
|
|
89
|
+
"--yes",
|
|
90
|
+
action="store_true",
|
|
91
|
+
help="Skip confirmation and uninstall flutter-dev.",
|
|
92
|
+
)
|
|
93
|
+
return parser
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _upgrade_parser():
|
|
97
|
+
return argparse.ArgumentParser(
|
|
98
|
+
prog="fdev upgrade",
|
|
99
|
+
description="Upgrade the local flutter-dev package from PyPI.",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def delete_fdev_command(argv=None):
|
|
104
|
+
"""Uninstall the local flutter-dev package from the active Python environment."""
|
|
105
|
+
args = _delete_parser().parse_args(argv)
|
|
106
|
+
os_key, os_name = get_os_info()
|
|
107
|
+
_print_os_info(os_name)
|
|
108
|
+
_print_python_environment()
|
|
109
|
+
|
|
110
|
+
if not args.yes and not _confirm_delete():
|
|
111
|
+
print(f"{YELLOW}Operation cancelled.{NC}")
|
|
112
|
+
return 1
|
|
113
|
+
|
|
114
|
+
pip_args = ["uninstall", "-y", PACKAGE_NAME]
|
|
115
|
+
print(f"{YELLOW}Uninstalling {PACKAGE_NAME} from the current Python environment...{NC}")
|
|
116
|
+
|
|
117
|
+
if os_key == "windows":
|
|
118
|
+
return _run_windows_detached(pip_args, "Uninstall")
|
|
119
|
+
|
|
120
|
+
result = _run_pip_direct(pip_args)
|
|
121
|
+
if result == 0:
|
|
122
|
+
print(f"{GREEN}✓ {PACKAGE_NAME} uninstalled successfully.{NC}")
|
|
123
|
+
print(f"{BLUE}Install again with: {GREEN}python -m pip install {PACKAGE_NAME}{NC}")
|
|
124
|
+
else:
|
|
125
|
+
print(f"{RED}✗ Failed to uninstall {PACKAGE_NAME}.{NC}")
|
|
126
|
+
return result
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def upgrade_fdev_command(argv=None):
|
|
130
|
+
"""Upgrade the local flutter-dev package from PyPI in the active environment."""
|
|
131
|
+
_upgrade_parser().parse_args(argv)
|
|
132
|
+
os_key, os_name = get_os_info()
|
|
133
|
+
_print_os_info(os_name)
|
|
134
|
+
_print_python_environment()
|
|
135
|
+
|
|
136
|
+
pip_args = ["install", "--upgrade", PACKAGE_NAME]
|
|
137
|
+
print(f"{YELLOW}Upgrading {PACKAGE_NAME} from PyPI...{NC}")
|
|
138
|
+
|
|
139
|
+
if os_key == "windows":
|
|
140
|
+
return _run_windows_detached(pip_args, "Upgrade")
|
|
141
|
+
|
|
142
|
+
result = _run_pip_direct(pip_args)
|
|
143
|
+
if result == 0:
|
|
144
|
+
print(f"{GREEN}✓ {PACKAGE_NAME} upgraded successfully.{NC}")
|
|
145
|
+
print(f"{BLUE}Restart your terminal if the fdev command still uses the old version.{NC}")
|
|
146
|
+
else:
|
|
147
|
+
print(f"{RED}✗ Failed to upgrade {PACKAGE_NAME}.{NC}")
|
|
148
|
+
return result
|
|
@@ -38,6 +38,28 @@ class FdevCliTests(unittest.TestCase):
|
|
|
38
38
|
show_usage.assert_called_once_with()
|
|
39
39
|
launch_gui.assert_not_called()
|
|
40
40
|
|
|
41
|
+
def test_delete_command_routes_to_package_delete(self):
|
|
42
|
+
with (
|
|
43
|
+
patch.object(sys, "argv", ["fdev", "delete", "--yes"]),
|
|
44
|
+
patch.object(fdev, "delete_fdev_command", return_value=0) as delete_command,
|
|
45
|
+
):
|
|
46
|
+
with self.assertRaises(SystemExit) as raised:
|
|
47
|
+
fdev.main()
|
|
48
|
+
|
|
49
|
+
self.assertEqual(raised.exception.code, 0)
|
|
50
|
+
delete_command.assert_called_once_with(["--yes"])
|
|
51
|
+
|
|
52
|
+
def test_upgrade_command_routes_to_package_upgrade(self):
|
|
53
|
+
with (
|
|
54
|
+
patch.object(sys, "argv", ["fdev", "upgrade"]),
|
|
55
|
+
patch.object(fdev, "upgrade_fdev_command", return_value=0) as upgrade_command,
|
|
56
|
+
):
|
|
57
|
+
with self.assertRaises(SystemExit) as raised:
|
|
58
|
+
fdev.main()
|
|
59
|
+
|
|
60
|
+
self.assertEqual(raised.exception.code, 0)
|
|
61
|
+
upgrade_command.assert_called_once_with([])
|
|
62
|
+
|
|
41
63
|
|
|
42
64
|
if __name__ == "__main__":
|
|
43
65
|
unittest.main()
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
import unittest
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from tkinter import TclError
|
|
5
|
+
from unittest.mock import patch
|
|
4
6
|
|
|
5
7
|
from managers.gui import (
|
|
8
|
+
FdevGui,
|
|
6
9
|
GuiCommand,
|
|
10
|
+
prepare_tk_environment,
|
|
7
11
|
parse_ansi_segments,
|
|
8
12
|
build_fdev_command,
|
|
9
13
|
get_gui_sections,
|
|
@@ -86,6 +90,8 @@ class GuiCatalogTests(unittest.TestCase):
|
|
|
86
90
|
("deploy",),
|
|
87
91
|
("doctor",),
|
|
88
92
|
("brew",),
|
|
93
|
+
("upgrade",),
|
|
94
|
+
("delete",),
|
|
89
95
|
("merge",),
|
|
90
96
|
("ai",),
|
|
91
97
|
("ai", "groq"),
|
|
@@ -140,6 +146,102 @@ class GuiCatalogTests(unittest.TestCase):
|
|
|
140
146
|
["sync", "dev", "main"],
|
|
141
147
|
)
|
|
142
148
|
|
|
149
|
+
def test_prepare_tk_environment_silences_macos_system_tk_warning(self):
|
|
150
|
+
with patch.dict("os.environ", {}, clear=True):
|
|
151
|
+
prepare_tk_environment()
|
|
152
|
+
|
|
153
|
+
import os
|
|
154
|
+
|
|
155
|
+
self.assertEqual(os.environ["TK_SILENCE_DEPRECATION"], "1")
|
|
156
|
+
|
|
157
|
+
def test_dashboard_uses_stable_grid_layout_without_paned_window(self):
|
|
158
|
+
try:
|
|
159
|
+
import tkinter as tk
|
|
160
|
+
except ImportError:
|
|
161
|
+
self.skipTest("Tkinter is not available")
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
root = tk.Tk()
|
|
165
|
+
except TclError as error:
|
|
166
|
+
self.skipTest(f"Tk display is not available: {error}")
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
app = FdevGui(root)
|
|
170
|
+
root.update_idletasks()
|
|
171
|
+
|
|
172
|
+
widget_classes = {
|
|
173
|
+
widget.winfo_class()
|
|
174
|
+
for widget in _walk_widgets(root)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
self.assertNotIn("TPanedwindow", widget_classes)
|
|
178
|
+
self.assertEqual(app.section_label.cget("text"), "Dashboard")
|
|
179
|
+
self.assertGreaterEqual(len(app.command_frame.winfo_children()), 3)
|
|
180
|
+
self.assertGreater(app.command_frame.winfo_width(), 100)
|
|
181
|
+
self.assertGreater(app.log_text.winfo_width(), 100)
|
|
182
|
+
finally:
|
|
183
|
+
root.destroy()
|
|
184
|
+
|
|
185
|
+
def test_dashboard_maps_initial_widgets_after_mainloop_starts(self):
|
|
186
|
+
try:
|
|
187
|
+
import tkinter as tk
|
|
188
|
+
except ImportError:
|
|
189
|
+
self.skipTest("Tkinter is not available")
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
root = tk.Tk()
|
|
193
|
+
except TclError as error:
|
|
194
|
+
self.skipTest(f"Tk display is not available: {error}")
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
app = FdevGui(root)
|
|
198
|
+
root.after(10, root.quit)
|
|
199
|
+
app.run()
|
|
200
|
+
|
|
201
|
+
self.assertTrue(app.command_frame.winfo_ismapped())
|
|
202
|
+
self.assertTrue(app.log_text.winfo_ismapped())
|
|
203
|
+
self.assertGreater(app.command_frame.winfo_width(), 100)
|
|
204
|
+
self.assertGreater(app.log_text.winfo_width(), 100)
|
|
205
|
+
finally:
|
|
206
|
+
root.destroy()
|
|
207
|
+
|
|
208
|
+
def test_dashboard_uses_classic_tk_widgets_for_system_tk_compatibility(self):
|
|
209
|
+
try:
|
|
210
|
+
import tkinter as tk
|
|
211
|
+
except ImportError:
|
|
212
|
+
self.skipTest("Tkinter is not available")
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
root = tk.Tk()
|
|
216
|
+
except TclError as error:
|
|
217
|
+
self.skipTest(f"Tk display is not available: {error}")
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
app = FdevGui(root)
|
|
221
|
+
root.update_idletasks()
|
|
222
|
+
|
|
223
|
+
widget_classes = {
|
|
224
|
+
widget.winfo_class()
|
|
225
|
+
for widget in _walk_widgets(root)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
self.assertFalse(
|
|
229
|
+
{"TButton", "TEntry", "TFrame", "TLabel", "TScrollbar"} & widget_classes
|
|
230
|
+
)
|
|
231
|
+
self.assertIn("Button", widget_classes)
|
|
232
|
+
self.assertIn("Label", widget_classes)
|
|
233
|
+
self.assertIn("Entry", widget_classes)
|
|
234
|
+
self.assertIn("Scrollbar", widget_classes)
|
|
235
|
+
self.assertGreater(app.command_frame.winfo_width(), 100)
|
|
236
|
+
finally:
|
|
237
|
+
root.destroy()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _walk_widgets(widget):
|
|
241
|
+
yield widget
|
|
242
|
+
for child in widget.winfo_children():
|
|
243
|
+
yield from _walk_widgets(child)
|
|
244
|
+
|
|
143
245
|
|
|
144
246
|
if __name__ == "__main__":
|
|
145
247
|
unittest.main()
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import subprocess
|
|
3
|
+
import unittest
|
|
4
|
+
from contextlib import redirect_stdout
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
from managers import package
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PackageCommandTests(unittest.TestCase):
|
|
11
|
+
def test_delete_yes_runs_pip_uninstall_in_current_python_environment(self):
|
|
12
|
+
completed = subprocess.CompletedProcess(
|
|
13
|
+
["/venv/bin/python", "-m", "pip", "uninstall", "-y", "flutter-dev"],
|
|
14
|
+
0,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
with (
|
|
18
|
+
patch.object(package.platform, "system", return_value="Darwin"),
|
|
19
|
+
patch.object(package.sys, "executable", "/venv/bin/python"),
|
|
20
|
+
patch.object(package.subprocess, "run", return_value=completed) as run,
|
|
21
|
+
):
|
|
22
|
+
result = package.delete_fdev_command(["--yes"])
|
|
23
|
+
|
|
24
|
+
self.assertEqual(result, 0)
|
|
25
|
+
run.assert_called_once_with(
|
|
26
|
+
["/venv/bin/python", "-m", "pip", "uninstall", "-y", "flutter-dev"]
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def test_delete_cancelled_by_user_does_not_run_pip(self):
|
|
30
|
+
with (
|
|
31
|
+
patch.object(package.platform, "system", return_value="Linux"),
|
|
32
|
+
patch("builtins.input", return_value="n"),
|
|
33
|
+
patch.object(package.subprocess, "run") as run,
|
|
34
|
+
):
|
|
35
|
+
result = package.delete_fdev_command([])
|
|
36
|
+
|
|
37
|
+
self.assertEqual(result, 1)
|
|
38
|
+
run.assert_not_called()
|
|
39
|
+
|
|
40
|
+
def test_upgrade_runs_pip_upgrade_in_current_python_environment(self):
|
|
41
|
+
completed = subprocess.CompletedProcess(
|
|
42
|
+
["/venv/bin/python", "-m", "pip", "install", "--upgrade", "flutter-dev"],
|
|
43
|
+
0,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
with (
|
|
47
|
+
patch.object(package.platform, "system", return_value="Linux"),
|
|
48
|
+
patch.object(package.sys, "executable", "/venv/bin/python"),
|
|
49
|
+
patch.object(package.subprocess, "run", return_value=completed) as run,
|
|
50
|
+
):
|
|
51
|
+
result = package.upgrade_fdev_command([])
|
|
52
|
+
|
|
53
|
+
self.assertEqual(result, 0)
|
|
54
|
+
run.assert_called_once_with(
|
|
55
|
+
["/venv/bin/python", "-m", "pip", "install", "--upgrade", "flutter-dev"]
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def test_windows_delete_schedules_detached_uninstall(self):
|
|
59
|
+
with (
|
|
60
|
+
patch.object(package.platform, "system", return_value="Windows"),
|
|
61
|
+
patch.object(package.sys, "executable", r"C:\Python\python.exe"),
|
|
62
|
+
patch.object(package.subprocess, "Popen") as popen,
|
|
63
|
+
):
|
|
64
|
+
result = package.delete_fdev_command(["--yes"])
|
|
65
|
+
|
|
66
|
+
self.assertEqual(result, 0)
|
|
67
|
+
popen.assert_called_once()
|
|
68
|
+
command = popen.call_args.args[0]
|
|
69
|
+
self.assertEqual(command[0], "cmd")
|
|
70
|
+
joined_command = " ".join(command)
|
|
71
|
+
self.assertIn("uninstall", joined_command)
|
|
72
|
+
self.assertIn("flutter-dev", joined_command)
|
|
73
|
+
|
|
74
|
+
def test_windows_detached_command_quotes_python_paths_with_spaces(self):
|
|
75
|
+
with patch.object(package.sys, "executable", r"C:\Program Files\Python312\python.exe"):
|
|
76
|
+
command = package._build_windows_detached_command(["install", "--upgrade", "flutter-dev"])
|
|
77
|
+
|
|
78
|
+
self.assertEqual(command[:3], ["cmd", "/c", "start"])
|
|
79
|
+
self.assertEqual(command[3], "fdev package command")
|
|
80
|
+
self.assertEqual(command[4:6], ["cmd", "/c"])
|
|
81
|
+
self.assertIn('"C:\\Program Files\\Python312\\python.exe"', command[6])
|
|
82
|
+
self.assertIn("-m pip install --upgrade flutter-dev", command[6])
|
|
83
|
+
|
|
84
|
+
def test_linux_delete_uses_running_python_not_python3_or_pip3(self):
|
|
85
|
+
completed = subprocess.CompletedProcess(
|
|
86
|
+
["/home/user/project/.venv/bin/python", "-m", "pip", "uninstall", "-y", "flutter-dev"],
|
|
87
|
+
0,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
with (
|
|
91
|
+
patch.object(package.platform, "system", return_value="Linux"),
|
|
92
|
+
patch.object(package.sys, "executable", "/home/user/project/.venv/bin/python"),
|
|
93
|
+
patch.object(package.subprocess, "run", return_value=completed) as run,
|
|
94
|
+
):
|
|
95
|
+
result = package.delete_fdev_command(["--yes"])
|
|
96
|
+
|
|
97
|
+
self.assertEqual(result, 0)
|
|
98
|
+
run.assert_called_once_with(
|
|
99
|
+
["/home/user/project/.venv/bin/python", "-m", "pip", "uninstall", "-y", "flutter-dev"]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def test_upgrade_prints_target_python_environment(self):
|
|
103
|
+
completed = subprocess.CompletedProcess(
|
|
104
|
+
["/home/user/project/.venv/bin/python", "-m", "pip", "install", "--upgrade", "flutter-dev"],
|
|
105
|
+
0,
|
|
106
|
+
)
|
|
107
|
+
output = io.StringIO()
|
|
108
|
+
|
|
109
|
+
with (
|
|
110
|
+
patch.object(package.platform, "system", return_value="Linux"),
|
|
111
|
+
patch.object(package.sys, "executable", "/home/user/project/.venv/bin/python"),
|
|
112
|
+
patch.object(package.subprocess, "run", return_value=completed),
|
|
113
|
+
redirect_stdout(output),
|
|
114
|
+
):
|
|
115
|
+
result = package.upgrade_fdev_command([])
|
|
116
|
+
|
|
117
|
+
self.assertEqual(result, 0)
|
|
118
|
+
self.assertIn("Python environment:", output.getvalue())
|
|
119
|
+
self.assertIn("/home/user/project/.venv/bin/python", output.getvalue())
|
|
120
|
+
|
|
121
|
+
def test_command_help_returns_zero(self):
|
|
122
|
+
with self.assertRaises(SystemExit) as raised:
|
|
123
|
+
package.delete_fdev_command(["--help"])
|
|
124
|
+
|
|
125
|
+
self.assertEqual(raised.exception.code, 0)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
if __name__ == "__main__":
|
|
129
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|