flet-cli 1.0.1__tar.gz → 1.0.3__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.
Files changed (82) hide show
  1. {flet_cli-1.0.1 → flet_cli-1.0.3}/PKG-INFO +3 -3
  2. {flet_cli-1.0.1 → flet_cli-1.0.3}/pyproject.toml +3 -3
  3. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/cli.py +66 -12
  4. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/build.py +3 -5
  5. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/build_base.py +145 -74
  6. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/debug.py +2 -0
  7. flet_cli-1.0.3/src/flet_cli/commands/options.py +78 -0
  8. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/pack.py +8 -2
  9. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/test.py +5 -3
  10. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/android_sdk.py +26 -37
  11. flet_cli-1.0.3/src/flet_cli/utils/app_excludes.py +117 -0
  12. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/processes.py +19 -1
  13. flet_cli-1.0.3/src/flet_cli/version.py +1 -0
  14. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli.egg-info/PKG-INFO +3 -3
  15. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli.egg-info/SOURCES.txt +7 -0
  16. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli.egg-info/requires.txt +2 -2
  17. flet_cli-1.0.3/tests/test_app_excludes.py +197 -0
  18. flet_cli-1.0.3/tests/test_build_app_path.py +119 -0
  19. flet_cli-1.0.3/tests/test_build_list_options.py +45 -0
  20. flet_cli-1.0.3/tests/test_build_platform_settings.py +77 -0
  21. flet_cli-1.0.3/tests/test_cli_pass_through_args.py +235 -0
  22. flet_cli-1.0.3/tests/test_cli_positionals.py +141 -0
  23. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_run_script_args.py +3 -3
  24. flet_cli-1.0.1/src/flet_cli/commands/options.py +0 -42
  25. flet_cli-1.0.1/src/flet_cli/version.py +0 -1
  26. {flet_cli-1.0.1 → flet_cli-1.0.3}/README.md +0 -0
  27. {flet_cli-1.0.1 → flet_cli-1.0.3}/setup.cfg +0 -0
  28. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/__pyinstaller/__init__.py +0 -0
  29. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/__pyinstaller/config.py +0 -0
  30. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/__pyinstaller/hook-flet.py +0 -0
  31. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/__pyinstaller/macos_utils.py +0 -0
  32. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/__pyinstaller/rthooks/pyi_rth_localhost_fletd.py +0 -0
  33. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/__pyinstaller/rthooks.dat +0 -0
  34. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/__pyinstaller/utils.py +0 -0
  35. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/__pyinstaller/win_utils.py +0 -0
  36. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/base.py +0 -0
  37. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/clean.py +0 -0
  38. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/create.py +0 -0
  39. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/devices.py +0 -0
  40. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/doctor.py +0 -0
  41. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/emulators.py +0 -0
  42. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/flutter_base.py +0 -0
  43. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/mcp.py +0 -0
  44. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/publish.py +0 -0
  45. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/run.py +0 -0
  46. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/serve.py +0 -0
  47. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/commands/test_host.py +0 -0
  48. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/android.py +0 -0
  49. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/cli.py +0 -0
  50. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/distros.py +0 -0
  51. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/flutter.py +0 -0
  52. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/hash_stamp.py +0 -0
  53. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/ios_sign.py +0 -0
  54. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/jdk.py +0 -0
  55. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/linux_deps.py +0 -0
  56. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/macos_sign.py +0 -0
  57. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/merge.py +0 -0
  58. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/plist.py +0 -0
  59. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/project_dependencies.py +0 -0
  60. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/pyodide.py +0 -0
  61. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/pyproject_toml.py +0 -0
  62. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/python_versions.py +0 -0
  63. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli/utils/template_cache.py +0 -0
  64. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli.egg-info/dependency_links.txt +0 -0
  65. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli.egg-info/entry_points.txt +0 -0
  66. {flet_cli-1.0.1 → flet_cli-1.0.3}/src/flet_cli.egg-info/top_level.txt +0 -0
  67. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_build_icons.py +0 -0
  68. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_build_ios.py +0 -0
  69. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_build_linux.py +0 -0
  70. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_build_macos_signing.py +0 -0
  71. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_build_splash.py +0 -0
  72. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_build_template.py +0 -0
  73. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_clean.py +0 -0
  74. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_create.py +0 -0
  75. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_find_platform_image.py +0 -0
  76. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_flutter.py +0 -0
  77. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_macos_sign.py +0 -0
  78. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_plist.py +0 -0
  79. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_project_dependencies.py +0 -0
  80. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_python_versions.py +0 -0
  81. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_run_assets_dir.py +0 -0
  82. {flet_cli-1.0.1 → flet_cli-1.0.3}/tests/test_run_desktop_dependency.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flet-cli
3
- Version: 1.0.1
3
+ Version: 1.0.3
4
4
  Summary: Flet CLI
5
5
  Author-email: "Appveyor Systems Inc." <hello@flet.dev>
6
6
  License-Expression: Apache-2.0
@@ -9,8 +9,8 @@ Project-URL: Repository, https://github.com/flet-dev/flet
9
9
  Project-URL: Documentation, https://flet.dev/docs
10
10
  Requires-Python: >=3.10
11
11
  Description-Content-Type: text/markdown
12
- Requires-Dist: flet==1.0.1
13
- Requires-Dist: flet-platform-assets==1.0.1
12
+ Requires-Dist: flet==1.0.3
13
+ Requires-Dist: flet-platform-assets==1.0.3
14
14
  Requires-Dist: watchdog>=4.0.0
15
15
  Requires-Dist: packaging>=25.0
16
16
  Requires-Dist: qrcode>=7.4.2
@@ -1,14 +1,14 @@
1
1
  [project]
2
2
  name = "flet-cli"
3
- version = "1.0.1"
3
+ version = "1.0.3"
4
4
  description = "Flet CLI"
5
5
  authors = [{ name = "Appveyor Systems Inc.", email = "hello@flet.dev" }]
6
6
  license = "Apache-2.0"
7
7
  readme = "README.md"
8
8
  requires-python = ">=3.10"
9
9
  dependencies = [
10
- "flet==1.0.1",
11
- "flet-platform-assets==1.0.1",
10
+ "flet==1.0.3",
11
+ "flet-platform-assets==1.0.3",
12
12
  "watchdog >=4.0.0",
13
13
  "packaging >=25.0",
14
14
  "qrcode >=7.4.2",
@@ -41,6 +41,34 @@ def _render_version(as_json: bool) -> str:
41
41
  return f"Flet: {flet.version.flet_version}\nFlutter: {flet.version.flutter_version}"
42
42
 
43
43
 
44
+ class _PositionalsFixArgumentParser(argparse.ArgumentParser):
45
+ """
46
+ An `argparse.ArgumentParser` with the fix for CPython gh-59317 backported.
47
+
48
+ Before Python 3.12.7 and 3.13.1, `argparse` gives an optional positional its
49
+ default as soon as the positionals typed before an option run out, so one
50
+ typed after the option is rejected. `get_parser()` uses this class only on
51
+ those interpreters, and subparsers inherit it through `add_subparsers()`.
52
+ """
53
+
54
+ def _match_arguments_partial(self, actions, arg_strings_pattern):
55
+ """
56
+ Match positionals as Python 3.12.7+ and 3.13.1+ do: when the typed
57
+ positionals end at an option, the trailing positionals that matched
58
+ nothing are left unfilled, so positionals after the option can fill them.
59
+
60
+ The matched counts add up to the length of the matched pattern, so the
61
+ result is unchanged on interpreters whose `argparse` already does this.
62
+ """
63
+
64
+ result = super()._match_arguments_partial(actions, arg_strings_pattern)
65
+ matched = sum(result)
66
+ if matched < len(arg_strings_pattern) and arg_strings_pattern[matched] == "O":
67
+ while result and not result[-1]:
68
+ result.pop()
69
+ return result
70
+
71
+
44
72
  # Source https://stackoverflow.com/a/26379693
45
73
  def set_default_subparser(
46
74
  parser: argparse.ArgumentParser, name: str, args: list
@@ -88,9 +116,11 @@ def set_default_subparser(
88
116
 
89
117
  def get_parser() -> argparse.ArgumentParser:
90
118
  """Construct and return the CLI argument parser."""
91
- parser = argparse.ArgumentParser(
92
- formatter_class=argparse.RawDescriptionHelpFormatter
93
- )
119
+ parser = (
120
+ _PositionalsFixArgumentParser
121
+ if sys.version_info < (3, 12, 7) or sys.version_info[:3] == (3, 13, 0)
122
+ else argparse.ArgumentParser
123
+ )(formatter_class=argparse.RawDescriptionHelpFormatter)
94
124
 
95
125
  # add version flags
96
126
  parser.add_argument(
@@ -138,9 +168,11 @@ def split_script_args(argv: list) -> tuple[list, Optional[list]]:
138
168
  """
139
169
  Split a command line at the first bare `--` separator.
140
170
 
141
- Everything after the separator is meant for the app script, not for Flet
171
+ Everything after the separator is meant for the program the command runs -
172
+ the app script of `flet run`, pytest for `flet test`, Flutter for
173
+ `flet build` and `flet debug`, or PyInstaller for `flet pack` - not for Flet
142
174
  itself, so it must be removed before the parser (and `set_default_subparser`)
143
- sees it - otherwise `flet run app.py -- --web` would be read as Flet's own
175
+ sees it. Otherwise `flet run app.py -- --web` would be read as Flet's own
144
176
  `--web` option, and `flet app.py -- build` would suppress the default `run`
145
177
  subcommand.
146
178
 
@@ -169,10 +201,14 @@ def parse_command_line(argv: list) -> argparse.Namespace:
169
201
 
170
202
  Returns:
171
203
  The parsed arguments. For `flet run`, `script_args` holds the arguments
172
- to forward to the app script.
204
+ to forward to the app script, for `flet test`, `pytest_args` holds the
205
+ arguments to pass to pytest, for `flet build` and `flet debug`,
206
+ `flutter_build_args` holds the arguments to pass to Flutter, and for
207
+ `flet pack`, `pyinstaller_build_args` holds the arguments to pass to
208
+ PyInstaller.
173
209
  """
174
210
 
175
- # pull off the arguments meant for the app script before the parser sees them
211
+ # pull off arguments meant for the program the command runs before parser sees them
176
212
  argv, script_args = split_script_args(argv)
177
213
 
178
214
  parser = get_parser()
@@ -195,14 +231,32 @@ def parse_command_line(argv: list) -> argparse.Namespace:
195
231
  )
196
232
  parser.error(message)
197
233
 
198
- # forward the arguments that followed `--` to the app script
234
+ # forward the arguments that followed `--` to the program the command runs
199
235
  if script_args is not None:
200
- if getattr(args, "command", None) != "run":
236
+ command = getattr(args, "command", None)
237
+ if command == "run":
238
+ args.script_args = list(args.script_args) + script_args
239
+ elif command == "test":
240
+ args.pytest_args = script_args
241
+ elif command in ("build", "debug"):
242
+ if script_args:
243
+ args.flutter_build_args = [
244
+ *(args.flutter_build_args or []),
245
+ script_args,
246
+ ]
247
+ elif command == "pack":
248
+ if script_args:
249
+ args.pyinstaller_build_args = [
250
+ *(args.pyinstaller_build_args or []),
251
+ script_args,
252
+ ]
253
+ else:
201
254
  parser.error(
202
- "the `--` separator is only supported by `flet run`, to pass "
203
- "the arguments that follow it to your app script"
255
+ "the `--` separator is only supported by commands that run "
256
+ "another program, which receives the arguments that follow it: "
257
+ "`flet run` (the app script), `flet test` (pytest), `flet build` "
258
+ "and `flet debug` (Flutter), and `flet pack` (PyInstaller)"
204
259
  )
205
- args.script_args = list(args.script_args) + script_args
206
260
 
207
261
  return args
208
262
 
@@ -43,6 +43,8 @@ class Command(BaseBuildCommand):
43
43
  Android (APK/AAB), and iOS (IPA and simulator .app), with a wide range of
44
44
  customization options for metadata, assets, splash screens, and signing.
45
45
 
46
+ Arguments after `--` are passed down to the underlying `flutter build`.
47
+
46
48
  Detailed usage guide: https://flet.dev/docs/publish
47
49
  """
48
50
 
@@ -206,11 +208,7 @@ class Command(BaseBuildCommand):
206
208
  if build_version:
207
209
  args.extend(["--build-name", build_version])
208
210
 
209
- for arg in (
210
- self.get_pyproject(f"tool.flet.{self.config_platform}.flutter.build_args")
211
- or self.get_pyproject("tool.flet.flutter.build_args")
212
- or []
213
- ):
211
+ for arg in self.get_platform_setting("flutter.build_args", []):
214
212
  args.append(arg)
215
213
 
216
214
  def run_flutter(self):
@@ -28,6 +28,7 @@ from flet_platform_assets import (
28
28
  write,
29
29
  )
30
30
  from packaging.requirements import Requirement
31
+ from rich.markup import escape
31
32
  from rich.panel import Panel
32
33
  from rich.table import Column, Table
33
34
 
@@ -42,10 +43,12 @@ from flet_cli.commands.flutter_base import (
42
43
  verbose2_style,
43
44
  warning_style,
44
45
  )
46
+ from flet_cli.commands.options import PassThroughArgsAction
45
47
  from flet_cli.utils.android import (
46
48
  ANDROID_ARCH_TO_FLUTTER_TARGET_PLATFORM,
47
49
  excluded_android_abis,
48
50
  )
51
+ from flet_cli.utils.app_excludes import find_default_excludes
49
52
  from flet_cli.utils.cli import parse_cli_bool_value
50
53
  from flet_cli.utils.hash_stamp import HashStamp
51
54
  from flet_cli.utils.merge import merge_dict
@@ -358,6 +361,23 @@ class BaseBuildCommand(BaseFlutterCommand):
358
361
  help="Files and/or directories to exclude from the package"
359
362
  "; can be used multiple times",
360
363
  )
364
+ parser.add_argument(
365
+ "--include",
366
+ dest="include",
367
+ action="extend",
368
+ nargs="+",
369
+ default=[],
370
+ help="Files and/or directories excluded by default to package anyway "
371
+ "(e.g. `.env`); can be used multiple times",
372
+ )
373
+ parser.add_argument(
374
+ "--no-default-excludes",
375
+ dest="default_excludes",
376
+ action="store_false",
377
+ default=None,
378
+ help="Package hidden top-level files and directories, virtual "
379
+ "environments and `__pycache__` directories instead of excluding them",
380
+ )
361
381
  parser.add_argument(
362
382
  "--project",
363
383
  dest="project_name",
@@ -619,9 +639,12 @@ class BaseBuildCommand(BaseFlutterCommand):
619
639
  parser.add_argument(
620
640
  "--flutter-build-args",
621
641
  dest="flutter_build_args",
622
- action="append",
642
+ action=PassThroughArgsAction,
643
+ example="--obfuscate",
623
644
  nargs="*",
624
- help="Additional arguments for flutter build command",
645
+ help="Additional arguments for flutter build command. Attach an "
646
+ "argument that starts with `-` using `=`, e.g. "
647
+ "`--flutter-build-args=--obfuscate`, or pass the arguments after `--`",
625
648
  )
626
649
  parser.add_argument(
627
650
  "--source-packages",
@@ -634,6 +657,7 @@ class BaseBuildCommand(BaseFlutterCommand):
634
657
  parser.add_argument(
635
658
  "--android-extract-packages",
636
659
  dest="android_extract_packages",
660
+ action="extend",
637
661
  nargs="+",
638
662
  default=[],
639
663
  help="Android only: Python packages (relative paths) to ship extracted "
@@ -860,19 +884,25 @@ class BaseBuildCommand(BaseFlutterCommand):
860
884
  self.config_platform = self.platforms[self.target_platform]["config_platform"]
861
885
  self.require_android_sdk = self.package_platform == "Android"
862
886
 
863
- super().initialize_command()
864
-
865
887
  self.python_app_path = Path(self.options.python_app_path).resolve()
866
888
 
867
- if not (
868
- os.path.exists(self.python_app_path) or os.path.isdir(self.python_app_path)
869
- ):
889
+ # validate that the app path exists and is a directory, not a file
890
+ if not os.path.isdir(self.python_app_path):
891
+ self.skip_flutter_doctor = True
892
+ if os.path.isfile(self.python_app_path):
893
+ self.cleanup(
894
+ 1,
895
+ f"Path to Flet app must be a directory, not a file: "
896
+ f"{escape(str(self.python_app_path))}",
897
+ )
870
898
  self.cleanup(
871
899
  1,
872
900
  f"Path to Flet app does not exist or is not a directory: "
873
- f"{self.python_app_path}",
901
+ f"{escape(str(self.python_app_path))}",
874
902
  )
875
903
 
904
+ super().initialize_command()
905
+
876
906
  self.rel_out_dir = self.options.output_dir or os.path.join(
877
907
  "build", self.platforms[self.target_platform]["dist"]
878
908
  )
@@ -1366,10 +1396,8 @@ class BaseBuildCommand(BaseFlutterCommand):
1366
1396
  deep_linking_scheme = self.options.deep_linking_scheme
1367
1397
  deep_linking_host = self.options.deep_linking_host
1368
1398
 
1369
- target_arch = (
1370
- self.options.target_arch
1371
- or self.get_pyproject(f"tool.flet.{self.config_platform}.target_arch")
1372
- or self.get_pyproject("tool.flet.target_arch")
1399
+ target_arch = self.options.target_arch or self.get_platform_setting(
1400
+ "target_arch"
1373
1401
  )
1374
1402
  target_arch = (
1375
1403
  target_arch
@@ -2532,11 +2560,7 @@ class BaseBuildCommand(BaseFlutterCommand):
2532
2560
 
2533
2561
  dev_packages_configured = False
2534
2562
  if len(toml_dependencies) > 0:
2535
- dev_packages = (
2536
- self.get_pyproject(f"tool.flet.{self.config_platform}.dev_packages")
2537
- or self.get_pyproject("tool.flet.dev_packages")
2538
- or []
2539
- )
2563
+ dev_packages = self.get_platform_setting("dev_packages", {})
2540
2564
  if len(dev_packages) > 0:
2541
2565
  for i in range(0, len(toml_dependencies)):
2542
2566
  package_name = Requirement(toml_dependencies[i]).name
@@ -2616,25 +2640,63 @@ class BaseBuildCommand(BaseFlutterCommand):
2616
2640
  )
2617
2641
 
2618
2642
  # exclude
2619
- exclude_list = ["build"]
2620
-
2621
- app_exclude = (
2622
- self.options.exclude
2623
- or self.get_pyproject(f"tool.flet.{self.config_platform}.app.exclude")
2624
- or self.get_pyproject("tool.flet.app.exclude")
2643
+ app_exclude = self.options.exclude or self.get_platform_setting(
2644
+ "app.exclude", []
2625
2645
  )
2626
- if app_exclude:
2627
- exclude_list.extend(app_exclude)
2646
+ explicit_excludes = [
2647
+ "build",
2648
+ *app_exclude,
2649
+ *(["assets"] if self.target_platform == "web" else []),
2650
+ ]
2628
2651
 
2629
- if self.target_platform == "web":
2630
- exclude_list.append("assets")
2631
- package_args.extend(["--exclude", ",".join(exclude_list)])
2652
+ default_excludes: list[str] = []
2653
+ if self.get_bool_setting(
2654
+ self.options.default_excludes, "app.default_excludes", True
2655
+ ):
2656
+ app_include = self.options.include or self.get_platform_setting(
2657
+ "app.include", []
2658
+ )
2659
+ default_excludes = find_default_excludes(
2660
+ self.package_app_path, app_include, explicit_excludes
2661
+ )
2662
+
2663
+ exclude_list = list(dict.fromkeys(explicit_excludes + default_excludes))
2664
+ # one flag per path: serious_python (>= 5.0.0) doesn't split values on
2665
+ # commas, so paths containing `,` survive
2666
+ for path in exclude_list:
2667
+ package_args.extend(["--exclude", path])
2668
+
2669
+ if default_excludes:
2670
+ console.log(
2671
+ "Excluded from app package by default: "
2672
+ f"{', '.join(default_excludes)} "
2673
+ "(use --include <path> or --no-default-excludes to package them)",
2674
+ markup=False,
2675
+ )
2676
+ env_files = [
2677
+ p for p in default_excludes if p == ".env" or p.startswith(".env.")
2678
+ ]
2679
+ if env_files:
2680
+ it = "it" if len(env_files) == 1 else "them"
2681
+ console.log(
2682
+ f"Warning: {', '.join(env_files)} not packaged. If the app "
2683
+ f"loads {it} at runtime (e.g. with python-dotenv), package "
2684
+ f"{it} with `--include {' '.join(env_files)}` or "
2685
+ f"`include = {json.dumps(env_files)}` under [tool.flet.app] "
2686
+ "in pyproject.toml.",
2687
+ style=warning_style,
2688
+ markup=False,
2689
+ )
2690
+ if self.verbose > 0:
2691
+ console.log(
2692
+ f"App package exclude list: {exclude_list}",
2693
+ style=verbose1_style,
2694
+ markup=False,
2695
+ )
2632
2696
 
2633
2697
  # source-packages
2634
- source_packages = (
2635
- self.options.source_packages
2636
- or self.get_pyproject(f"tool.flet.{self.config_platform}.source_packages")
2637
- or self.get_pyproject("tool.flet.source_packages")
2698
+ source_packages = self.options.source_packages or self.get_platform_setting(
2699
+ "source_packages"
2638
2700
  )
2639
2701
  if source_packages:
2640
2702
  package_env["SERIOUS_PYTHON_ALLOW_SOURCE_DISTRIBUTIONS"] = ",".join(
@@ -2651,11 +2713,7 @@ class BaseBuildCommand(BaseFlutterCommand):
2651
2713
  if self.package_platform == "Android":
2652
2714
  user_extract_packages = (
2653
2715
  self.options.android_extract_packages
2654
- or self.get_pyproject(
2655
- f"tool.flet.{self.config_platform}.extract_packages"
2656
- )
2657
- or self.get_pyproject("tool.flet.extract_packages")
2658
- or []
2716
+ or self.get_platform_setting("extract_packages", [])
2659
2717
  )
2660
2718
  self.android_extract_packages = list(
2661
2719
  dict.fromkeys(ANDROID_DEFAULT_EXTRACT_PACKAGES + user_extract_packages)
@@ -2678,42 +2736,34 @@ class BaseBuildCommand(BaseFlutterCommand):
2678
2736
 
2679
2737
  if cleanup_app_files := (
2680
2738
  self.options.cleanup_app_files
2681
- or self.get_pyproject(f"tool.flet.{self.config_platform}.cleanup.app_files")
2682
- or self.get_pyproject("tool.flet.cleanup.app_files")
2739
+ or self.get_platform_setting("cleanup.app_files")
2683
2740
  ):
2684
2741
  if isinstance(cleanup_app_files, str):
2685
2742
  cleanup_app_files = [
2686
2743
  value.strip() for value in cleanup_app_files.split(",")
2687
2744
  ]
2688
2745
  if isinstance(cleanup_app_files, list):
2689
- package_args.extend(
2690
- [
2691
- "--cleanup-app-files",
2692
- ",".join([v.strip() for v in cleanup_app_files if v.strip()]),
2693
- ]
2694
- )
2746
+ for glob_pattern in cleanup_app_files:
2747
+ if glob_pattern.strip():
2748
+ package_args.extend(
2749
+ ["--cleanup-app-files", glob_pattern.strip()]
2750
+ )
2695
2751
  cleanup_app = True
2696
2752
 
2697
2753
  if cleanup_package_files := (
2698
2754
  self.options.cleanup_package_files
2699
- or self.get_pyproject(
2700
- f"tool.flet.{self.config_platform}.cleanup.package_files"
2701
- )
2702
- or self.get_pyproject("tool.flet.cleanup.package_files")
2755
+ or self.get_platform_setting("cleanup.package_files")
2703
2756
  ):
2704
2757
  if isinstance(cleanup_package_files, str):
2705
2758
  cleanup_package_files = [
2706
2759
  value for value in cleanup_package_files.split(",")
2707
2760
  ]
2708
2761
  if isinstance(cleanup_package_files, list):
2709
- package_args.extend(
2710
- [
2711
- "--cleanup-package-files",
2712
- ",".join(
2713
- [v.strip() for v in cleanup_package_files if v.strip()]
2714
- ),
2715
- ]
2716
- )
2762
+ for glob_pattern in cleanup_package_files:
2763
+ if glob_pattern.strip():
2764
+ package_args.extend(
2765
+ ["--cleanup-package-files", glob_pattern.strip()]
2766
+ )
2717
2767
  cleanup_packages = True
2718
2768
 
2719
2769
  if cleanup_app:
@@ -2722,6 +2772,15 @@ class BaseBuildCommand(BaseFlutterCommand):
2722
2772
  if cleanup_packages:
2723
2773
  package_args.append("--cleanup-packages")
2724
2774
 
2775
+ if self.verbose > 0:
2776
+ console.log(
2777
+ f"Compile app: {'--compile-app' in package_args}, "
2778
+ f"cleanup app: {cleanup_app}, cleanup packages: {cleanup_packages} "
2779
+ "(cleanup removes serious_python's default junk globs plus any "
2780
+ "--cleanup-app-files/--cleanup-package-files)",
2781
+ style=verbose1_style,
2782
+ )
2783
+
2725
2784
  if self.verbose > 1:
2726
2785
  package_args.append("--verbose")
2727
2786
 
@@ -2840,24 +2899,39 @@ class BaseBuildCommand(BaseFlutterCommand):
2840
2899
  Resolved boolean-like setting value.
2841
2900
  """
2842
2901
 
2843
- assert self.get_pyproject
2844
2902
  return (
2845
2903
  cli_option
2846
2904
  if cli_option is not None
2847
- else (
2848
- self.get_pyproject(f"tool.flet.{self.config_platform}.{pyproj_setting}")
2849
- if self.get_pyproject(
2850
- f"tool.flet.{self.config_platform}.{pyproj_setting}"
2851
- )
2852
- is not None
2853
- else (
2854
- self.get_pyproject(f"tool.flet.{pyproj_setting}")
2855
- if self.get_pyproject(f"tool.flet.{pyproj_setting}") is not None
2856
- else default_value
2857
- )
2858
- )
2905
+ else self.get_platform_setting(pyproj_setting, default_value)
2859
2906
  )
2860
2907
 
2908
+ def get_platform_setting(self, pyproj_setting, default_value=None):
2909
+ """
2910
+ Resolve a pyproject setting with precedence: platform, global, default.
2911
+
2912
+ The platform value wins whenever it is set, even when it is empty, so
2913
+ `[]` or `{}` under `tool.flet.<platform>.` clears a global list or table
2914
+ for that platform.
2915
+
2916
+ Args:
2917
+ pyproj_setting: Relative key under `tool.flet.<platform>.` and
2918
+ `tool.flet.`.
2919
+ default_value: Fallback value when neither key is defined.
2920
+
2921
+ Returns:
2922
+ Resolved setting value.
2923
+ """
2924
+
2925
+ assert self.get_pyproject
2926
+ for key in (
2927
+ f"tool.flet.{self.config_platform}.{pyproj_setting}",
2928
+ f"tool.flet.{pyproj_setting}",
2929
+ ):
2930
+ value = self.get_pyproject(key)
2931
+ if value is not None:
2932
+ return value
2933
+ return default_value
2934
+
2861
2935
  def add_flutter_command_args(self, args: list[str]):
2862
2936
  """
2863
2937
  Hook for subclasses to append command-specific Flutter arguments.
@@ -3001,10 +3075,7 @@ class BaseBuildCommand(BaseFlutterCommand):
3001
3075
 
3002
3076
  flutter_build_args = (
3003
3077
  self.options.flutter_build_args
3004
- or self.get_pyproject(
3005
- f"tool.flet.{self.config_platform}.flutter.build_args"
3006
- )
3007
- or self.get_pyproject("tool.flet.flutter.build_args")
3078
+ or self.get_platform_setting("flutter.build_args")
3008
3079
  )
3009
3080
  if flutter_build_args:
3010
3081
  if isinstance(flutter_build_args, (list, tuple)):
@@ -13,6 +13,8 @@ class Command(BaseBuildCommand):
13
13
  """
14
14
  Run a Flet Python app in debug mode on a specified platform (desktop, web, mobile).
15
15
 
16
+ Arguments after `--` are passed down to the underlying `flutter run`.
17
+
16
18
  Detailed usage guide: https://flet.dev/blog/flet-debug-the-new-cli-for-testing-flet-apps-on-mobile-devices
17
19
  """
18
20
 
@@ -0,0 +1,78 @@
1
+ import argparse
2
+ from typing import Any
3
+
4
+
5
+ class Option:
6
+ """A reusable option object which delegates all arguments
7
+ to parser.add_argument().
8
+ """
9
+
10
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
11
+ self.args = args
12
+ self.kwargs = kwargs
13
+
14
+ def add_to_parser(self, parser: argparse._ActionsContainer) -> None:
15
+ """
16
+ Add this option definition to an argument parser or compatible container.
17
+
18
+ Args:
19
+ parser: Parser-like object exposing `add_argument()`.
20
+ """
21
+
22
+ parser.add_argument(*self.args, **self.kwargs)
23
+
24
+ def add_to_group(self, group: argparse._ArgumentGroup) -> None:
25
+ """
26
+ Add this option definition to an argument group.
27
+
28
+ Args:
29
+ group: Argument group that receives the option.
30
+ """
31
+
32
+ group.add_argument(*self.args, **self.kwargs)
33
+
34
+
35
+ class PassThroughArgsAction(argparse.Action):
36
+ """
37
+ Collect the values of each occurrence of an option that passes arguments on
38
+ to another program, as one list per occurrence.
39
+
40
+ A value that starts with `-` is read as an option of its own unless it is
41
+ attached with `=`, which leaves the occurrence without values and hands the
42
+ value to whichever Flet option it names. An occurrence without values is
43
+ therefore rejected, with a hint to attach the value or to pass it after
44
+ `--`.
45
+ """
46
+
47
+ def __init__(self, option_strings, dest, example: str, **kwargs):
48
+ """
49
+ Args:
50
+ option_strings: The option strings of the option.
51
+ dest: The attribute that receives the collected values.
52
+ example: A value that starts with `-`, shown in the hint.
53
+ **kwargs: The other `add_argument()` arguments.
54
+ """
55
+
56
+ super().__init__(option_strings, dest, **kwargs)
57
+ self.example = example
58
+
59
+ def __call__(self, parser, namespace, values, option_string=None):
60
+ if not values:
61
+ raise argparse.ArgumentError(
62
+ self,
63
+ "expected at least one argument - attach one that starts with "
64
+ f"`-` using `=`, e.g. `{self.option_strings[0]}={self.example}`, "
65
+ "or pass it after `--`",
66
+ )
67
+ collected = getattr(namespace, self.dest, None) or []
68
+ setattr(namespace, self.dest, [*collected, values])
69
+
70
+
71
+ verbose_option = Option(
72
+ "-v",
73
+ "--verbose",
74
+ action="count",
75
+ default=0,
76
+ help="Enable verbose output. "
77
+ "Use -v for standard verbose logging and -vv for more detailed output",
78
+ )
@@ -13,6 +13,7 @@ from pathlib import Path
13
13
  import flet_cli.__pyinstaller.config as hook_config
14
14
  from flet.utils import is_linux, is_macos, is_windows
15
15
  from flet_cli.commands.base import BaseCommand
16
+ from flet_cli.commands.options import PassThroughArgsAction
16
17
 
17
18
 
18
19
  class Command(BaseCommand):
@@ -20,6 +21,8 @@ class Command(BaseCommand):
20
21
  Package a Flet application into a standalone desktop executable or app bundle
21
22
  using PyInstaller.
22
23
 
24
+ Arguments after `--` are passed down to PyInstaller.
25
+
23
26
  Detailed usage guide: https://flet.dev/docs/publish/using-pyinstaller
24
27
  """
25
28
 
@@ -152,9 +155,12 @@ class Command(BaseCommand):
152
155
  parser.add_argument(
153
156
  "--pyinstaller-build-args",
154
157
  dest="pyinstaller_build_args",
155
- action="append",
158
+ action=PassThroughArgsAction,
159
+ example="--clean",
156
160
  nargs="*",
157
- help="Additional raw arguments to the underlying pyinstaller build command",
161
+ help="Additional raw arguments to the underlying pyinstaller build "
162
+ "command. Attach an argument that starts with `-` using `=`, e.g. "
163
+ "`--pyinstaller-build-args=--clean`, or pass the arguments after `--`",
158
164
  )
159
165
  parser.add_argument(
160
166
  "-y",