flet-cli 1.0.0.dev2__py3-none-any.whl → 1.0.2__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.
flet_cli/cli.py CHANGED
@@ -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
 
@@ -32,6 +32,42 @@ class Option:
32
32
  group.add_argument(*self.args, **self.kwargs)
33
33
 
34
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
+
35
71
  verbose_option = Option(
36
72
  "-v",
37
73
  "--verbose",
flet_cli/commands/pack.py CHANGED
@@ -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",
@@ -370,6 +370,7 @@ class Command(BaseCommand):
370
370
  pyodide_pre=options.pre,
371
371
  pyodide_script_path=str(script_path),
372
372
  pyodide_version=python_release.pyodide,
373
+ app_package_url=app_tar_gz_filename,
373
374
  # "canvaskit" default for the same reason as `flet build web`:
374
375
  # "auto" puts Chromium on dart2wasm/skwasm, whose JS <-> Dart
375
376
  # typed-data boundary costs are a large per-frame tax for
flet_cli/commands/run.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import argparse
2
+ import logging
2
3
  import os
3
4
  import platform
4
5
  import signal
@@ -207,6 +208,11 @@ class Command(BaseCommand):
207
208
  options: Parsed command options produced by :meth:`add_arguments`.
208
209
  """
209
210
 
211
+ if options.verbose > 0:
212
+ logging.basicConfig(
213
+ level=logging.DEBUG if options.verbose > 1 else logging.INFO
214
+ )
215
+
210
216
  from flet.utils.pip import (
211
217
  ensure_flet_desktop_package_installed,
212
218
  ensure_flet_web_package_installed,
@@ -350,6 +356,7 @@ class Command(BaseCommand):
350
356
  flet_app_data_dir=str(flet_app_data_dir),
351
357
  flet_app_cache_dir=str(flet_app_cache_dir),
352
358
  flet_app_temp_dir=str(flet_app_temp_dir),
359
+ verbose=options.verbose,
353
360
  )
354
361
 
355
362
  my_observer = Observer()
@@ -401,6 +408,7 @@ class Handler(FileSystemEventHandler):
401
408
  flet_app_data_dir,
402
409
  flet_app_cache_dir,
403
410
  flet_app_temp_dir,
411
+ verbose,
404
412
  ) -> None:
405
413
  super().__init__()
406
414
  self.args = args
@@ -425,6 +433,7 @@ class Handler(FileSystemEventHandler):
425
433
  self.flet_app_data_dir = flet_app_data_dir
426
434
  self.flet_app_cache_dir = flet_app_cache_dir
427
435
  self.flet_app_temp_dir = flet_app_temp_dir
436
+ self.verbose = verbose
428
437
  self.terminate = threading.Event()
429
438
  self.start_process()
430
439
 
@@ -453,7 +462,16 @@ class Handler(FileSystemEventHandler):
453
462
  p_env["FLET_SERVER_UDS_PATH"] = self.uds_path
454
463
  if self.assets_dir is not None:
455
464
  p_env["FLET_ASSETS_DIR"] = self.assets_dir
465
+ # The app runs with its cwd set to the storage directory below, so a
466
+ # relative path the user typed in their shell has to be resolved here
467
+ # while that meaning still holds.
468
+ web_path = p_env.get("FLET_WEB_PATH")
469
+ if web_path and not Path(web_path).is_absolute():
470
+ p_env["FLET_WEB_PATH"] = str(Path(web_path).resolve())
471
+
456
472
  p_env["FLET_DISPLAY_URL_PREFIX"] = self.page_url_prefix
473
+ if self.verbose > 0:
474
+ p_env["FLET_LOG_LEVEL"] = "debug" if self.verbose > 1 else "info"
457
475
 
458
476
  p_env["FLET_APP_STORAGE_DATA"] = self.flet_app_data_dir
459
477
  p_env["FLET_APP_STORAGE_CACHE"] = self.flet_app_cache_dir
flet_cli/commands/test.py CHANGED
@@ -109,6 +109,7 @@ class Command(BaseBuildCommand):
109
109
  `flet build`, in test mode) so the app runs on-device with embedded Python,
110
110
  then runs pytest. Tests in the `tests/` directory drive the app through the
111
111
  `flet_app` fixture (find controls by key, tap, take/assert screenshots).
112
+ Arguments after `--` are passed to pytest, e.g. `flet test -- -x`.
112
113
 
113
114
  Detailed usage guide: https://flet.dev/docs/getting-started/integration-testing
114
115
  """
@@ -123,9 +124,8 @@ class Command(BaseBuildCommand):
123
124
  self.flutter_test_host: Optional[str] = None
124
125
 
125
126
  def add_arguments(self, parser: argparse.ArgumentParser) -> None:
126
- # `platform` is a positional, like `flet debug`. Register it first, then
127
- # the inherited build args (which add the `python_app_path` positional),
128
- # then our trailing `pytest_args` REMAINDER positional.
127
+ # register `platform` before the inherited build args, so that it precedes
128
+ # the `python_app_path` positional they add
129
129
  parser.add_argument(
130
130
  "platform",
131
131
  type=str.lower,
@@ -170,6 +170,7 @@ class Command(BaseBuildCommand):
170
170
  help="Only run tests matching the given pytest keyword expression "
171
171
  "(passed through to pytest -k).",
172
172
  )
173
+ parser.set_defaults(pytest_args=[])
173
174
 
174
175
  def handle(self, options: argparse.Namespace) -> None:
175
176
  super().handle(options)
@@ -191,6 +192,7 @@ class Command(BaseBuildCommand):
191
192
  self.pytest_args = []
192
193
  if options.pytest_keyword:
193
194
  self.pytest_args += ["-k", options.pytest_keyword]
195
+ self.pytest_args += options.pytest_args
194
196
 
195
197
  # Check for pytest availability before provisioning, since `flet test` runs
196
198
  # `sys.executable -m pytest` and pytest comes from the app’s `flet[test]`
@@ -13,12 +13,15 @@ ANDROID_CMDLINE_TOOLS_DOWNLOAD_VERSION = "11076708"
13
13
  ANDROID_CMDLINE_TOOLS_VERSION = "12.0"
14
14
 
15
15
  MINIMAL_PACKAGES = [
16
- "cmdline-tools;latest",
17
16
  "platform-tools",
18
17
  "platforms;android-35",
19
18
  "build-tools;34.0.0",
20
19
  ]
21
20
 
21
+ # Written to the standard input of `sdkmanager`, which reads one answer line per
22
+ # license prompt; `--licenses` also asks once before showing the licenses.
23
+ SDKMANAGER_ANSWERS = "y\n" * 50
24
+
22
25
 
23
26
  class AndroidSDK:
24
27
  """
@@ -103,6 +106,11 @@ class AndroidSDK:
103
106
  """
104
107
  Return the `cmdline-tools` binary directory in an SDK installation.
105
108
 
109
+ The version installed by this CLI is preferred over `latest`, because
110
+ from version 23.0, `sdkmanager` is a wrapper around the Android CLI,
111
+ which ignores `--licenses`, collects usage metrics by default and fails
112
+ on Windows.
113
+
106
114
  Args:
107
115
  home_dir: Android SDK home directory.
108
116
 
@@ -111,8 +119,8 @@ class AndroidSDK:
111
119
  """
112
120
 
113
121
  for d in [
114
- home_dir / "cmdline-tools" / "latest" / "bin",
115
122
  home_dir / "cmdline-tools" / ANDROID_CMDLINE_TOOLS_VERSION / "bin",
123
+ home_dir / "cmdline-tools" / "latest" / "bin",
116
124
  ]:
117
125
  if d.exists():
118
126
  return d
@@ -322,36 +330,23 @@ class AndroidSDK:
322
330
  RuntimeError: If package installation fails.
323
331
  """
324
332
 
325
- if home_dir.joinpath(*package_name.split(";")).exists():
333
+ package_dir = home_dir.joinpath(*package_name.split(";"))
334
+ if package_dir.exists():
326
335
  self.log(f'Android SDK package "{package_name}" is already installed')
327
336
  return 0
328
337
 
329
338
  self.log(f'Installing Android SDK package "{package_name}"')
330
339
 
331
340
  p = self.run(
332
- (
333
- [
334
- "sh",
335
- "-c",
336
- f'yes | "{self.sdkmanager_exe(home_dir)}" "{package_name}"',
337
- ]
338
- if platform.system() != "Windows"
339
- else [
340
- "cmd.exe",
341
- "/C",
342
- "echo",
343
- "y",
344
- "|",
345
- self.sdkmanager_exe(home_dir),
346
- package_name,
347
- ]
348
- ),
341
+ [self.sdkmanager_exe(home_dir), package_name],
349
342
  env={"ANDROID_HOME": str(home_dir)},
350
343
  capture_output=False,
344
+ input=SDKMANAGER_ANSWERS,
351
345
  )
352
- if p.returncode != 0:
346
+ # sdkmanager exits with 0 when it skips a package whose license is declined
347
+ if p.returncode != 0 or not package_dir.exists():
353
348
  self.log(p.stderr)
354
- raise RuntimeError("Error installing Android SDK tools")
349
+ raise RuntimeError(f'Error installing Android SDK package "{package_name}"')
355
350
  return 1
356
351
 
357
352
  def _accept_licenses(self, home_dir: Path):
@@ -368,23 +363,14 @@ class AndroidSDK:
368
363
  self.log("Accepting Android SDK licenses")
369
364
 
370
365
  p = self.run(
371
- (
372
- ["sh", "-c", f'yes | "{self.sdkmanager_exe(home_dir)}" --licenses']
373
- if platform.system() != "Windows"
374
- else [
375
- "cmd.exe",
376
- "/C",
377
- "echo",
378
- "y",
379
- "|",
380
- self.sdkmanager_exe(home_dir),
381
- "--licenses",
382
- ]
383
- ),
366
+ [self.sdkmanager_exe(home_dir), "--licenses"],
384
367
  env={"ANDROID_HOME": str(home_dir)},
385
368
  capture_output=False,
369
+ input=SDKMANAGER_ANSWERS,
386
370
  )
387
- if p.returncode != 0:
371
+ # sdkmanager exits with 0 when licenses are declined
372
+ license_file = home_dir / "licenses" / "android-sdk-license"
373
+ if p.returncode != 0 or not license_file.exists():
388
374
  self.log(p.stderr)
389
375
  raise RuntimeError("Error accepting Android SDK licenses")
390
376
 
@@ -415,7 +401,7 @@ class AndroidSDK:
415
401
  )
416
402
  return p.stdout
417
403
 
418
- def run(self, args, env=None, cwd=None, capture_output=True):
404
+ def run(self, args, env=None, cwd=None, capture_output=True, input=None):
419
405
  """
420
406
  Run a subprocess configured for Android SDK tooling.
421
407
 
@@ -427,6 +413,8 @@ class AndroidSDK:
427
413
  env: Optional additional environment variables.
428
414
  cwd: Optional working directory. Defaults to current directory.
429
415
  capture_output: Forwarded to subprocess helper.
416
+ input: Optional text for the subprocess's standard input. Forwarded
417
+ to subprocess helper.
430
418
 
431
419
  Returns:
432
420
  Subprocess result object returned by `flet_cli.utils.processes.run`.
@@ -447,4 +435,5 @@ class AndroidSDK:
447
435
  env=cmd_env,
448
436
  capture_output=capture_output,
449
437
  log=self.log,
438
+ input=input,
450
439
  )
@@ -0,0 +1,117 @@
1
+ import os
2
+ import stat
3
+ import sys
4
+ from collections.abc import Iterable
5
+ from pathlib import Path
6
+
7
+ PYVENV_CFG = "pyvenv.cfg"
8
+ PYCACHE_DIR = "__pycache__"
9
+
10
+
11
+ def _is_hidden(entry: os.DirEntry) -> bool:
12
+ """
13
+ Check whether a directory entry is hidden.
14
+
15
+ An entry is hidden when its name starts with `.` (on every platform) or,
16
+ on Windows, when it has the hidden file attribute.
17
+
18
+ Args:
19
+ entry: Directory entry to check.
20
+
21
+ Returns:
22
+ `True` if the entry is hidden, `False` otherwise.
23
+ """
24
+
25
+ if entry.name.startswith("."):
26
+ return True
27
+ if sys.platform != "win32":
28
+ return False
29
+ try:
30
+ attributes = entry.stat(follow_symlinks=False).st_file_attributes
31
+ except OSError:
32
+ return False
33
+ return bool(attributes & stat.FILE_ATTRIBUTE_HIDDEN)
34
+
35
+
36
+ def _is_venv(path: str) -> bool:
37
+ return os.path.isfile(os.path.join(path, PYVENV_CFG))
38
+
39
+
40
+ def _normalize(paths: Iterable[str]) -> set[str]:
41
+ return {os.path.normpath(p.replace("/", os.sep)) for p in paths}
42
+
43
+
44
+ def find_default_excludes(
45
+ app_path: Path, include: Iterable[str] = (), exclude: Iterable[str] = ()
46
+ ) -> list[str]:
47
+ """
48
+ Find app files and directories that are excluded from the package by default.
49
+
50
+ Matched are:
51
+
52
+ - hidden files and directories directly in `app_path` (a name starting
53
+ with `.`, or the hidden attribute on Windows);
54
+ - virtual environments (directories containing `pyvenv.cfg`) at any depth;
55
+ - `__pycache__` directories at any depth.
56
+
57
+ Directory symlinks are followed to match the packager. Links back to an
58
+ ancestor are always excluded, even if included explicitly, to prevent
59
+ recursive copying.
60
+
61
+ Args:
62
+ app_path: Root directory of the Python app being packaged.
63
+ include: Relative paths to keep even if matched by the rules above.
64
+ Both `/` and `\\` separators are accepted.
65
+ exclude: Relative paths already excluded from the package. They are
66
+ not scanned and not returned.
67
+
68
+ Returns:
69
+ Sorted relative paths joined with `os.sep`, the form serious_python
70
+ compares its exclude list against.
71
+ """
72
+
73
+ keep = _normalize(include)
74
+ skip = _normalize(exclude)
75
+ excludes: list[str] = []
76
+
77
+ def add(rel_path: str) -> bool:
78
+ if rel_path in keep:
79
+ return False
80
+ excludes.append(rel_path)
81
+ return True
82
+
83
+ # top-level hidden entries
84
+ subdirs: list[str] = []
85
+ with os.scandir(app_path) as entries:
86
+ for entry in entries:
87
+ if entry.name in skip:
88
+ continue
89
+ if _is_hidden(entry) and add(entry.name):
90
+ continue
91
+ if entry.is_dir(follow_symlinks=True):
92
+ subdirs.append(entry.name)
93
+
94
+ # virtual environments and __pycache__ at any depth
95
+ for subdir in subdirs:
96
+ ancestors = [app_path.resolve()]
97
+ for dirpath, dirnames, _ in os.walk(app_path / subdir, followlinks=True):
98
+ rel_dir = os.path.relpath(dirpath, app_path)
99
+ # Keep only this path's ancestors, not a global visited set: the
100
+ # packager follows symlinks and copies each alias independently.
101
+ del ancestors[len(Path(rel_dir).parts) :]
102
+ resolved = Path(dirpath).resolve()
103
+ if resolved in ancestors:
104
+ # Even an included cycle must be excluded from the copy, or
105
+ # serious_python would recurse into it indefinitely.
106
+ excludes.append(rel_dir)
107
+ dirnames.clear()
108
+ continue
109
+ ancestors.append(resolved)
110
+ if (os.path.basename(dirpath) == PYCACHE_DIR or _is_venv(dirpath)) and add(
111
+ rel_dir
112
+ ):
113
+ dirnames.clear()
114
+ continue
115
+ dirnames[:] = [d for d in dirnames if os.path.join(rel_dir, d) not in skip]
116
+
117
+ return sorted(excludes)
@@ -1,3 +1,4 @@
1
+ import contextlib
1
2
  import os
2
3
  import subprocess
3
4
  from typing import Optional
@@ -8,7 +9,14 @@ if is_windows():
8
9
  from ctypes import windll
9
10
 
10
11
 
11
- def run(args, cwd, env: Optional[dict] = None, capture_output=True, log=None):
12
+ def run(
13
+ args,
14
+ cwd,
15
+ env: Optional[dict] = None,
16
+ capture_output=True,
17
+ log=None,
18
+ input: Optional[str] = None,
19
+ ):
12
20
  """
13
21
  Execute a subprocess command with optional streamed logging.
14
22
 
@@ -22,6 +30,9 @@ def run(args, cwd, env: Optional[dict] = None, capture_output=True, log=None):
22
30
  capture_output: If `True`, run with `subprocess.run` and
23
31
  capture output in memory. If `False`, stream combined output line by line.
24
32
  log: Optional callback receiving each output line when `capture_output=False`.
33
+ input: Optional text written to the command's standard input, which is
34
+ then closed. When `capture_output=False`, it is written before any
35
+ output is read, so it must fit in the OS pipe buffer.
25
36
 
26
37
  Returns:
27
38
  A completed `subprocess.CompletedProcess`
@@ -49,6 +60,7 @@ def run(args, cwd, env: Optional[dict] = None, capture_output=True, log=None):
49
60
  process = subprocess.run(
50
61
  args,
51
62
  cwd=cwd,
63
+ input=input,
52
64
  capture_output=True,
53
65
  text=True,
54
66
  encoding="utf-8",
@@ -68,6 +80,12 @@ def run(args, cwd, env: Optional[dict] = None, capture_output=True, log=None):
68
80
  errors="replace",
69
81
  )
70
82
 
83
+ if input is not None:
84
+ # The process may exit or close its standard input before reading it all
85
+ with contextlib.suppress(OSError):
86
+ process.stdin.write(input)
87
+ process.stdin.close()
88
+
71
89
  try:
72
90
  while True:
73
91
  stdout_line = process.stdout.readline()
@@ -28,7 +28,7 @@ from flet_cli.utils.template_cache import get_cache_root
28
28
  # python-build release this flet pins. Keep in sync with serious_python's
29
29
  # `pythonReleaseDate` (lib/src/python_versions.dart) — both should track the
30
30
  # same python-build release.
31
- PYTHON_BUILD_RELEASE_DATE = "20260908"
31
+ PYTHON_BUILD_RELEASE_DATE = "20260921"
32
32
 
33
33
  RELEASE_DATE_ENV = "FLET_PYTHON_BUILD_RELEASE_DATE"
34
34
  MANIFEST_PATH_ENV = "FLET_PYTHON_BUILD_MANIFEST"
flet_cli/version.py CHANGED
@@ -1 +1 @@
1
- version = "1.0.0.dev2"
1
+ version = "1.0.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flet-cli
3
- Version: 1.0.0.dev2
3
+ Version: 1.0.2
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.0.dev2
13
- Requires-Dist: flet-platform-assets==1.0.0.dev2
12
+ Requires-Dist: flet==1.0.2
13
+ Requires-Dist: flet-platform-assets==1.0.2
14
14
  Requires-Dist: watchdog>=4.0.0
15
15
  Requires-Dist: packaging>=25.0
16
16
  Requires-Dist: qrcode>=7.4.2
@@ -29,6 +29,10 @@ Requires-Dist: flet-mcp; extra == "mcp"
29
29
 
30
30
  Flet CLI is a command-line interface tool for Flet, a framework for building interactive multi-platform applications using Python.
31
31
 
32
+ > **[Try Flet online in Flet Studio](https://studio.flet.dev)**
33
+ >
34
+ > Try an idea in your browser with no installation, then use the Flet CLI locally to develop and package your app. Write your own Python code or get help from the AI agent.
35
+
32
36
  ## Features
33
37
 
34
38
  - Create new Flet projects
@@ -1,5 +1,5 @@
1
- flet_cli/cli.py,sha256=IAHFW5kCW6KvHLthdRJqUgxc18xVNSdrSDf-MibGc-o,7544
2
- flet_cli/version.py,sha256=5y9WdVlv-PO-E_vksl24lRtbW31WAWKlERh977DNJRI,23
1
+ flet_cli/cli.py,sha256=TlUPz73bL6rcSQBBH05uAljU9f-ZsuefEKYR-SbdPbE,10115
2
+ flet_cli/version.py,sha256=tTF5v0pCQ3wj3WtOg6KXgEGkOMgBeRFQACKUYV29zxo,18
3
3
  flet_cli/__pyinstaller/__init__.py,sha256=KJsKpQ6uSVslADmFaOKaxCu-GOZZtcVKnti-ripD-ug,164
4
4
  flet_cli/__pyinstaller/config.py,sha256=MN2IPu53K1RDyh_KE4wIcQH8El_n4Z3fD6NO2iq8y74,20
5
5
  flet_cli/__pyinstaller/hook-flet.py,sha256=RbiFE8-7VBsY4Dwt7a9-9gW0gUFQGjOLY1-OBsZm_mI,506
@@ -9,25 +9,26 @@ flet_cli/__pyinstaller/utils.py,sha256=Im_XkCsnqactsQjKGBjUtQ9FDTPx-b-m2-8ZkEytq
9
9
  flet_cli/__pyinstaller/win_utils.py,sha256=wu0G98CUMkGfloZa2LTqJE7axAZPB2-TLBOHJiYUc0Q,4428
10
10
  flet_cli/__pyinstaller/rthooks/pyi_rth_localhost_fletd.py,sha256=O8lAxkHmNaXaZNzP-3VTuUhuglYwgJtzaeIKQa2neGU,2379
11
11
  flet_cli/commands/base.py,sha256=6MTAuA7kKdaKIZI4AyWPM1TJfFCxmRj7kc7z2h6E_4k,3496
12
- flet_cli/commands/build.py,sha256=X1yEM4KHxPA00g1Hq1ufvIR5fxQhC8OOEovHfdOZ3Wo,35824
13
- flet_cli/commands/build_base.py,sha256=ePV3obf3DA-YTPtTcBk0uc3IwDUNZXrYJQmJKmI_hdM,140745
12
+ flet_cli/commands/build.py,sha256=mtiSHyfCbkwmbxx2308KAUgROcS8eg31acm07VhKtW4,35770
13
+ flet_cli/commands/build_base.py,sha256=DJvFbWOhJUsyMih3QrlmQBni6U1oiUsCydgOlOiciJE,143782
14
14
  flet_cli/commands/clean.py,sha256=fIRr1ktoE9wzO0R-lAq985zbCH1dX6Sao5VyXiJxiOY,2250
15
15
  flet_cli/commands/create.py,sha256=gH4BZw122BcOWlAU7MJHg8epNx9K-7lCquybacHC5pE,5859
16
- flet_cli/commands/debug.py,sha256=dT80Z_4QEqSyleVGZdCif2u0oMrUKCraG0QmBwjYf30,6768
16
+ flet_cli/commands/debug.py,sha256=FNw6WGSYkuZFbz0bloyDJLKX_GxoKQ812tsOlO5W1Zk,6843
17
17
  flet_cli/commands/devices.py,sha256=7kn56lrZLN5LuG_X3IyMdSfdFa421yE4R1otTJr5Yrc,7953
18
18
  flet_cli/commands/doctor.py,sha256=uG2xUAT_FASB9cnLD9G83NTYdP9iuKhaHyQLjMB_E4c,1051
19
19
  flet_cli/commands/emulators.py,sha256=2VCpu-B70JMsmqNpQoDJoXAvOV-Ex_Kkosa27D16iRk,13215
20
20
  flet_cli/commands/flutter_base.py,sha256=1YgILbM3Qn6TSPU_GVitNJ2gQjnI_buBi8Vhd09TO6U,18285
21
21
  flet_cli/commands/mcp.py,sha256=AVEnf6YjICqoBQPKqBaHp27KHXxFVjH-IRpAi6dSjcM,2065
22
- flet_cli/commands/options.py,sha256=tSqztnV4uvXhMEk6VhYe_szoq7CGVTrYlrkdZwMK4ek,1068
23
- flet_cli/commands/pack.py,sha256=Lgm-536oTJqEPtYO2kpGoMS3Be2Rk6RuJ_6bwLcIDZM,26727
24
- flet_cli/commands/publish.py,sha256=jBnsHIwgovER59j6a6y5pMFr3WzhRU_SBbrb48J-r20,14532
25
- flet_cli/commands/run.py,sha256=RcEv8f-6pY4yVgnOubBvMJKqClZmkkckohm1kU22S4I,23515
22
+ flet_cli/commands/options.py,sha256=CD7Fw2CtUeuVrtM_oR1_OrAcMKF2EhRUoPuX9_3IsW8,2543
23
+ flet_cli/commands/pack.py,sha256=qyDLCPM0QxET21b-25Yqmtoje4bYte5XR5tLM0_Ep8Y,27045
24
+ flet_cli/commands/publish.py,sha256=Ra92AZ3NbBMFAnbJxBafaXvmF-3T2tXGO5V6MiHDYp8,14581
25
+ flet_cli/commands/run.py,sha256=7hH_6M7S6R7qkc_MNvjssI5sUUg8I8IOIEvYwOOQgA0,24248
26
26
  flet_cli/commands/serve.py,sha256=YrollC80u9JvuXHbI_qQ3wxpjcqpIQwFxdD5nIOeOT4,3143
27
- flet_cli/commands/test.py,sha256=aMcCASJd6qI1573XJnhfZi9jBZG4RnfWnxP4OvbPXak,12742
27
+ flet_cli/commands/test.py,sha256=mm9tp952BnTD9zWYcQWYyJX8DegKZgb8341OVDvDtQk,12814
28
28
  flet_cli/commands/test_host.py,sha256=GrRLpGJf4feJsQydKZaqGX8aY1aPXPEPWCsMqEEgA2k,394
29
29
  flet_cli/utils/android.py,sha256=aLANEliQs9B0TobjYVBYBDisr57y8Boo7ej9_KaRkx0,1635
30
- flet_cli/utils/android_sdk.py,sha256=ljgk0zWh4SX9UAWx7vJK6RyKRz97Y8Be_mEeQDvkli4,13889
30
+ flet_cli/utils/android_sdk.py,sha256=8jRccd0PArpovoNVqiAyp4me_AvQLUb8lmrSQjE0Xfc,14047
31
+ flet_cli/utils/app_excludes.py,sha256=A_QgScerUDKrBSXA4PL1_P9IPNjqRkBik2Y0uJ_W0UQ,3890
31
32
  flet_cli/utils/cli.py,sha256=wPtVyfrwOdJ35acr4nVpNiS2p4SQmFD_hDsgYBBzZng,1524
32
33
  flet_cli/utils/distros.py,sha256=q0-DOB35xY7JOf3My12sjoZUXYqNeZesBEzoc6VL0W0,3711
33
34
  flet_cli/utils/flutter.py,sha256=lcCIAAiKKAAuoGiN304mQCUAUQZYNbL4sxcMT9KSutQ,4767
@@ -38,14 +39,14 @@ flet_cli/utils/linux_deps.py,sha256=gdT3qC6gDVdaNndkb_tkq4alyv0mhru7n6O4pdqX2wc,
38
39
  flet_cli/utils/macos_sign.py,sha256=lkqHEZieroD5Nj90Cnz_dwMgaT3FJFvxaC8NV2bcP9c,53384
39
40
  flet_cli/utils/merge.py,sha256=cZcUL1z2iaGjjFifL1KCrEj2ywLR9x6eNtOj1F2Dr6U,780
40
41
  flet_cli/utils/plist.py,sha256=oqBCkeUjUqPIf5y7XaE2seSx64FEs7yc2cukvbjFoSU,1135
41
- flet_cli/utils/processes.py,sha256=S0ErmI5x9S6RNMe89cf5M4ROIofMtjNZRQ29foUllJ4,2885
42
+ flet_cli/utils/processes.py,sha256=MIX05itdoYyyE1L2N9NeTciJXk4yzjVMceONhUljwsA,3452
42
43
  flet_cli/utils/project_dependencies.py,sha256=kC8gZZyaGk-coNXrh6cyPM6i9RhUaXcBIvZmQ50PufY,4064
43
44
  flet_cli/utils/pyodide.py,sha256=RhumXYuovPxgLbu99pNNhVb9SRvBvhLGiUMtb9WxC5U,5058
44
45
  flet_cli/utils/pyproject_toml.py,sha256=o4fAWKXMyK8vdnAO41BLI7Lt3ohMlVdlwrpaifq30Cs,1408
45
- flet_cli/utils/python_versions.py,sha256=Ma9H3m6ZRTg2xqAjEbAKv25-0PH8wdNL1Xy7dw5gYbk,8129
46
+ flet_cli/utils/python_versions.py,sha256=SLMzyU5c-c8BIy3nZvvU5JqnnvGfBa2w1eBTbM9OuwQ,8129
46
47
  flet_cli/utils/template_cache.py,sha256=Lf_iuk30zTfaNFbRmrc8s07w1yE-PYjdovZztoiuPzc,1596
47
- flet_cli-1.0.0.dev2.dist-info/METADATA,sha256=ZnpYfIUOAYRKvAQtx7U4pGIe9bLKTh8qt9FPBL3yrdA,1348
48
- flet_cli-1.0.0.dev2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
49
- flet_cli-1.0.0.dev2.dist-info/entry_points.txt,sha256=UZFR426y3mfr0wgikEFPbZ6wtGwfgykif9Obw6R7Wnc,65
50
- flet_cli-1.0.0.dev2.dist-info/top_level.txt,sha256=4_BPVAJpcNofDe3XMxajlZLBNWzWqKhRynNSBCYTKVQ,9
51
- flet_cli-1.0.0.dev2.dist-info/RECORD,,
48
+ flet_cli-1.0.2.dist-info/METADATA,sha256=aUGuR5Xbr7datAJPO99gO_bjDlqUOj7cw-ZU7Q_ZAXg,1573
49
+ flet_cli-1.0.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
50
+ flet_cli-1.0.2.dist-info/entry_points.txt,sha256=UZFR426y3mfr0wgikEFPbZ6wtGwfgykif9Obw6R7Wnc,65
51
+ flet_cli-1.0.2.dist-info/top_level.txt,sha256=4_BPVAJpcNofDe3XMxajlZLBNWzWqKhRynNSBCYTKVQ,9
52
+ flet_cli-1.0.2.dist-info/RECORD,,