reflex 0.8.13a1__py3-none-any.whl → 0.8.14__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.

Potentially problematic release.


This version of reflex might be problematic. Click here for more details.

Files changed (43) hide show
  1. reflex/app.py +25 -5
  2. reflex/compiler/templates.py +4 -0
  3. reflex/components/core/upload.py +9 -13
  4. reflex/components/core/upload.pyi +27 -9
  5. reflex/components/plotly/plotly.py +9 -9
  6. reflex/components/radix/primitives/__init__.py +1 -1
  7. reflex/components/radix/primitives/__init__.pyi +2 -1
  8. reflex/components/radix/primitives/base.py +31 -0
  9. reflex/components/radix/primitives/base.pyi +44 -0
  10. reflex/components/radix/primitives/dialog.py +148 -0
  11. reflex/components/radix/primitives/dialog.pyi +749 -0
  12. reflex/components/radix/primitives/slider.py +3 -17
  13. reflex/components/radix/primitives/slider.pyi +2 -4
  14. reflex/components/radix/themes/components/slider.py +1 -2
  15. reflex/components/radix/themes/components/slider.pyi +3 -6
  16. reflex/components/react_player/audio.pyi +23 -46
  17. reflex/components/react_player/react_player.pyi +40 -45
  18. reflex/components/react_player/video.pyi +23 -46
  19. reflex/constants/colors.py +1 -3
  20. reflex/constants/installer.py +5 -5
  21. reflex/custom_components/custom_components.py +18 -18
  22. reflex/environment.py +3 -0
  23. reflex/event.py +1 -1
  24. reflex/plugins/shared_tailwind.py +1 -1
  25. reflex/reflex.py +62 -24
  26. reflex/state.py +3 -2
  27. reflex/utils/exec.py +23 -4
  28. reflex/utils/frontend_skeleton.py +3 -5
  29. reflex/utils/js_runtimes.py +43 -33
  30. reflex/utils/prerequisites.py +5 -6
  31. reflex/utils/processes.py +10 -11
  32. reflex/utils/rename.py +3 -5
  33. reflex/utils/serializers.py +3 -7
  34. reflex/utils/templates.py +20 -22
  35. reflex/vars/base.py +3 -6
  36. reflex/vars/color.py +2 -68
  37. reflex/vars/object.py +1 -3
  38. reflex/vars/sequence.py +2 -2
  39. {reflex-0.8.13a1.dist-info → reflex-0.8.14.dist-info}/METADATA +1 -1
  40. {reflex-0.8.13a1.dist-info → reflex-0.8.14.dist-info}/RECORD +43 -41
  41. {reflex-0.8.13a1.dist-info → reflex-0.8.14.dist-info}/WHEEL +0 -0
  42. {reflex-0.8.13a1.dist-info → reflex-0.8.14.dist-info}/entry_points.txt +0 -0
  43. {reflex-0.8.13a1.dist-info → reflex-0.8.14.dist-info}/licenses/LICENSE +0 -0
@@ -359,7 +359,7 @@ def _get_default_library_name_parts() -> list[str]:
359
359
  """Get the default library name. Based on the current directory name, remove any non-alphanumeric characters.
360
360
 
361
361
  Raises:
362
- Exit: If the current directory name is not suitable for python projects, and we cannot find a valid library name based off it.
362
+ SystemExit: If the current directory name is not suitable for python projects, and we cannot find a valid library name based off it.
363
363
 
364
364
  Returns:
365
365
  The parts of default library name.
@@ -377,13 +377,13 @@ def _get_default_library_name_parts() -> list[str]:
377
377
  console.error(
378
378
  f"Based on current directory name {current_dir_name}, the library name is {constants.Reflex.MODULE_NAME}. This package already exists. Please use --library-name to specify a different name."
379
379
  )
380
- raise click.exceptions.Exit(code=1)
380
+ raise SystemExit(1)
381
381
  if not parts:
382
382
  # The folder likely has a name not suitable for python paths.
383
383
  console.error(
384
384
  f"Could not find a valid library name based on the current directory: got {current_dir_name}."
385
385
  )
386
- raise click.exceptions.Exit(code=1)
386
+ raise SystemExit(1)
387
387
  return parts
388
388
 
389
389
 
@@ -408,7 +408,7 @@ def _validate_library_name(library_name: str | None) -> NameVariants:
408
408
  library_name: The name of the library if picked otherwise None.
409
409
 
410
410
  Raises:
411
- Exit: If the library name is not suitable for python projects.
411
+ SystemExit: If the library name is not suitable for python projects.
412
412
 
413
413
  Returns:
414
414
  A tuple containing the various names such as package name, class name, etc., needed for the project.
@@ -419,7 +419,7 @@ def _validate_library_name(library_name: str | None) -> NameVariants:
419
419
  console.error(
420
420
  f"Please use only alphanumeric characters or dashes: got {library_name}"
421
421
  )
422
- raise click.exceptions.Exit(code=1)
422
+ raise SystemExit(1)
423
423
 
424
424
  # If not specified, use the current directory name to form the module name.
425
425
  name_parts = (
@@ -513,13 +513,13 @@ def init(
513
513
  install: Whether to install package from this local custom component in editable mode.
514
514
 
515
515
  Raises:
516
- Exit: If the pyproject.toml already exists.
516
+ SystemExit: If the pyproject.toml already exists.
517
517
  """
518
518
  from reflex.utils import exec
519
519
 
520
520
  if CustomComponents.PYPROJECT_TOML.exists():
521
521
  console.error(f"A {CustomComponents.PYPROJECT_TOML} already exists. Aborting.")
522
- click.exceptions.Exit(code=1)
522
+ raise SystemExit(1)
523
523
 
524
524
  # Show system info.
525
525
  exec.output_system_info()
@@ -544,7 +544,7 @@ def init(
544
544
  if _pip_install_on_demand(package_name=".", install_args=["-e"]):
545
545
  console.info(f"Package {package_name} installed!")
546
546
  else:
547
- raise click.exceptions.Exit(code=1)
547
+ raise SystemExit(1)
548
548
 
549
549
  console.print("[bold]Custom component initialized successfully!")
550
550
  console.rule("[bold]Project Summary")
@@ -627,7 +627,7 @@ def _run_build():
627
627
  """Run the build command.
628
628
 
629
629
  Raises:
630
- Exit: If the build fails.
630
+ SystemExit: If the build fails.
631
631
  """
632
632
  console.print("Building custom component...")
633
633
 
@@ -637,7 +637,7 @@ def _run_build():
637
637
  if _run_commands_in_subprocess(cmds):
638
638
  console.info("Custom component built successfully!")
639
639
  else:
640
- raise click.exceptions.Exit(code=1)
640
+ raise SystemExit(1)
641
641
 
642
642
 
643
643
  @custom_components_cli.command(name="build")
@@ -651,7 +651,7 @@ def _collect_details_for_gallery():
651
651
  """Helper to collect details on the custom component to be included in the gallery.
652
652
 
653
653
  Raises:
654
- Exit: If pyproject.toml file is ill-formed or the request to the backend services fails.
654
+ SystemExit: If pyproject.toml file is ill-formed or the request to the backend services fails.
655
655
  """
656
656
  import httpx
657
657
  from reflex_cli.utils import hosting
@@ -664,7 +664,7 @@ def _collect_details_for_gallery():
664
664
  console.error(
665
665
  "Unable to authenticate with Reflex backend services. Make sure you are logged in."
666
666
  )
667
- raise click.exceptions.Exit(code=1)
667
+ raise SystemExit(1)
668
668
 
669
669
  console.rule("[bold]Custom Component Information")
670
670
  params = {}
@@ -694,11 +694,11 @@ def _collect_details_for_gallery():
694
694
  console.error(
695
695
  f"{package_name} is owned by another user. Unable to update the information for it."
696
696
  )
697
- raise click.exceptions.Exit(code=1)
697
+ raise SystemExit(1)
698
698
  response.raise_for_status()
699
699
  except httpx.HTTPError as he:
700
700
  console.error(f"Unable to complete request due to {he}.")
701
- raise click.exceptions.Exit(code=1) from he
701
+ raise SystemExit(1) from None
702
702
 
703
703
  files = []
704
704
  if (image_file_and_extension := _get_file_from_prompt_in_loop()) is not None:
@@ -733,7 +733,7 @@ def _collect_details_for_gallery():
733
733
 
734
734
  except httpx.HTTPError as he:
735
735
  console.error(f"Unable to complete request due to {he}.")
736
- raise click.exceptions.Exit(code=1) from he
736
+ raise SystemExit(1) from None
737
737
 
738
738
  console.info("Custom component information successfully shared!")
739
739
 
@@ -769,7 +769,7 @@ def _get_file_from_prompt_in_loop() -> tuple[bytes, str] | None:
769
769
  image_file = image_file_path.read_bytes()
770
770
  except OSError as ose:
771
771
  console.error(f"Unable to read the {file_extension} file due to {ose}")
772
- raise click.exceptions.Exit(code=1) from ose
772
+ raise SystemExit(1) from None
773
773
  else:
774
774
  return image_file, file_extension
775
775
 
@@ -790,9 +790,9 @@ def install():
790
790
  """Install package from this local custom component in editable mode.
791
791
 
792
792
  Raises:
793
- Exit: If unable to install the current directory in editable mode.
793
+ SystemExit: If unable to install the current directory in editable mode.
794
794
  """
795
795
  if _pip_install_on_demand(package_name=".", install_args=["-e"]):
796
796
  console.info("Package installed successfully!")
797
797
  else:
798
- raise click.exceptions.Exit(code=1)
798
+ raise SystemExit(1)
reflex/environment.py CHANGED
@@ -660,6 +660,9 @@ class EnvironmentVariables:
660
660
  # Whether to enable SSR for the frontend.
661
661
  REFLEX_SSR: EnvVar[bool] = env_var(True)
662
662
 
663
+ # Whether to mount the compiled frontend app in the backend server in production.
664
+ REFLEX_MOUNT_FRONTEND_COMPILED_APP: EnvVar[bool] = env_var(False, internal=True)
665
+
663
666
 
664
667
  environment = EnvironmentVariables()
665
668
 
reflex/event.py CHANGED
@@ -875,7 +875,7 @@ class FileUpload:
875
875
  upload_files_context_var_data,
876
876
  )
877
877
 
878
- upload_id = self.upload_id or DEFAULT_UPLOAD_ID
878
+ upload_id = self.upload_id if self.upload_id is not None else DEFAULT_UPLOAD_ID
879
879
  spec_args = [
880
880
  (
881
881
  Var(_js_expr="files"),
@@ -179,7 +179,7 @@ class TailwindPlugin(PluginBase):
179
179
  config: TailwindConfig = dataclasses.field(
180
180
  default_factory=lambda: TailwindConfig(
181
181
  plugins=[
182
- "@tailwindcss/typography@0.5.18",
182
+ "@tailwindcss/typography@0.5.19",
183
183
  ],
184
184
  )
185
185
  )
reflex/reflex.py CHANGED
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import atexit
6
5
  from importlib.util import find_spec
7
6
  from pathlib import Path
8
7
  from typing import TYPE_CHECKING
@@ -132,8 +131,11 @@ def _run(
132
131
  frontend_port: int | None = None,
133
132
  backend_port: int | None = None,
134
133
  backend_host: str | None = None,
134
+ single_port: bool = False,
135
135
  ):
136
136
  """Run the app in the given directory."""
137
+ import atexit
138
+
137
139
  from reflex.utils import build, exec, prerequisites, processes
138
140
 
139
141
  config = get_config()
@@ -173,7 +175,9 @@ def _run(
173
175
  auto_increment=auto_increment_frontend,
174
176
  )
175
177
 
176
- if backend:
178
+ if single_port:
179
+ backend_port = frontend_port
180
+ elif backend:
177
181
  auto_increment_backend = not bool(backend_port or config.backend_port)
178
182
 
179
183
  backend_port = processes.handle_port(
@@ -223,23 +227,23 @@ def _run(
223
227
  if not return_result:
224
228
  raise SystemExit(1)
225
229
 
230
+ if env != constants.Env.PROD and env != constants.Env.DEV:
231
+ msg = f"Invalid env: {env}. Must be DEV or PROD."
232
+ raise ValueError(msg)
233
+
226
234
  # Get the frontend and backend commands, based on the environment.
227
- setup_frontend = frontend_cmd = backend_cmd = None
228
235
  if env == constants.Env.DEV:
229
236
  setup_frontend, frontend_cmd, backend_cmd = (
230
237
  build.setup_frontend,
231
238
  exec.run_frontend,
232
239
  exec.run_backend,
233
240
  )
234
- if env == constants.Env.PROD:
241
+ elif env == constants.Env.PROD:
235
242
  setup_frontend, frontend_cmd, backend_cmd = (
236
243
  build.setup_frontend_prod,
237
244
  exec.run_frontend_prod,
238
245
  exec.run_backend_prod,
239
246
  )
240
- if not setup_frontend or not frontend_cmd or not backend_cmd:
241
- msg = f"Invalid env: {env}. Must be DEV or PROD."
242
- raise ValueError(msg)
243
247
 
244
248
  # Post a telemetry event.
245
249
  telemetry.send(f"run-{env.value}")
@@ -251,7 +255,7 @@ def _run(
251
255
  commands = []
252
256
 
253
257
  # Run the frontend on a separate thread.
254
- if frontend:
258
+ if frontend and not single_port:
255
259
  setup_frontend(Path.cwd())
256
260
  commands.append((frontend_cmd, Path.cwd(), frontend_port, backend))
257
261
 
@@ -267,21 +271,30 @@ def _run(
267
271
  )
268
272
  )
269
273
 
270
- # Start the frontend and backend.
271
- with processes.run_concurrently_context(*commands):
272
- # In dev mode, run the backend on the main thread.
273
- if backend and backend_port and env == constants.Env.DEV:
274
- backend_cmd(
275
- backend_host,
276
- int(backend_port),
277
- config.loglevel.subprocess_level(),
278
- frontend,
279
- )
280
- # The windows uvicorn bug workaround
281
- # https://github.com/reflex-dev/reflex/issues/2335
282
- if constants.IS_WINDOWS and exec.frontend_process:
283
- # Sends SIGTERM in windows
284
- exec.kill(exec.frontend_process.pid)
274
+ if single_port:
275
+ setup_frontend(Path.cwd())
276
+ backend_function, *args = commands[0]
277
+ exec.notify_app_running()
278
+ exec.notify_frontend(
279
+ f"http://0.0.0.0:{get_config().frontend_port}", backend_present=True
280
+ )
281
+ backend_function(*args, mount_frontend_compiled_app=True)
282
+ else:
283
+ # Start the frontend and backend.
284
+ with processes.run_concurrently_context(*commands):
285
+ # In dev mode, run the backend on the main thread.
286
+ if backend and backend_port and env == constants.Env.DEV:
287
+ backend_cmd(
288
+ backend_host,
289
+ int(backend_port),
290
+ config.loglevel.subprocess_level(),
291
+ frontend,
292
+ )
293
+ # The windows uvicorn bug workaround
294
+ # https://github.com/reflex-dev/reflex/issues/2335
295
+ if constants.IS_WINDOWS and exec.frontend_process:
296
+ # Sends SIGTERM in windows
297
+ exec.kill(exec.frontend_process.pid)
285
298
 
286
299
 
287
300
  @cli.command()
@@ -322,6 +335,12 @@ def _run(
322
335
  "--backend-host",
323
336
  help="Specify the backend host.",
324
337
  )
338
+ @click.option(
339
+ "--single-port",
340
+ is_flag=True,
341
+ help="Run both frontend and backend on the same port.",
342
+ default=False,
343
+ )
325
344
  def run(
326
345
  env: LITERAL_ENV,
327
346
  frontend_only: bool,
@@ -329,11 +348,29 @@ def run(
329
348
  frontend_port: int | None,
330
349
  backend_port: int | None,
331
350
  backend_host: str | None,
351
+ single_port: bool,
332
352
  ):
333
353
  """Run the app in the current directory."""
334
354
  if frontend_only and backend_only:
335
355
  console.error("Cannot use both --frontend-only and --backend-only options.")
336
- raise click.exceptions.Exit(1)
356
+ raise SystemExit(1)
357
+
358
+ if single_port:
359
+ if env != constants.Env.PROD.value:
360
+ console.error("--single-port can only be used with --env=PROD.")
361
+ raise click.exceptions.Exit(1)
362
+ if frontend_only or backend_only:
363
+ console.error(
364
+ "Cannot use --single-port with --frontend-only or --backend-only options."
365
+ )
366
+ raise click.exceptions.Exit(1)
367
+ if backend_port and frontend_port and backend_port != frontend_port:
368
+ console.error(
369
+ "When using --single-port, --backend-port and --frontend-port must be the same."
370
+ )
371
+ raise click.exceptions.Exit(1)
372
+ elif frontend_port and backend_port and frontend_port == backend_port:
373
+ single_port = True
337
374
 
338
375
  config = get_config()
339
376
 
@@ -352,6 +389,7 @@ def run(
352
389
  frontend_port,
353
390
  backend_port,
354
391
  backend_host,
392
+ single_port,
355
393
  )
356
394
 
357
395
 
reflex/state.py CHANGED
@@ -533,7 +533,7 @@ class BaseState(EvenMoreBasicBaseState):
533
533
  cls._check_overridden_computed_vars()
534
534
 
535
535
  new_backend_vars = {
536
- name: value
536
+ name: value if not isinstance(value, Field) else value.default_value()
537
537
  for name, value in list(cls.__dict__.items())
538
538
  if types.is_backend_base_variable(name, cls)
539
539
  }
@@ -1207,7 +1207,8 @@ class BaseState(EvenMoreBasicBaseState):
1207
1207
  The default value of the var or None.
1208
1208
  """
1209
1209
  try:
1210
- return getattr(cls, name)
1210
+ value = getattr(cls, name)
1211
+ return value if not isinstance(value, Field) else value.default_value()
1211
1212
  except AttributeError:
1212
1213
  try:
1213
1214
  return types.get_default_value_for_type(annotation_value)
reflex/utils/exec.py CHANGED
@@ -145,6 +145,18 @@ def kill(proc_pid: int):
145
145
  process.kill()
146
146
 
147
147
 
148
+ def notify_frontend(url: str, backend_present: bool):
149
+ """Output a string notifying where the frontend is running.
150
+
151
+ Args:
152
+ url: The URL where the frontend is running.
153
+ backend_present: Whether the backend is present.
154
+ """
155
+ console.print(
156
+ f"App running at: [bold green]{url.rstrip('/')}/[/bold green]{' (Frontend-only mode)' if not backend_present else ''}"
157
+ )
158
+
159
+
148
160
  def notify_backend():
149
161
  """Output a string notifying where the backend is running."""
150
162
  console.print(
@@ -210,9 +222,7 @@ def run_process_and_launch_url(
210
222
  if get_config().frontend_path != "":
211
223
  url = urljoin(url, get_config().frontend_path)
212
224
 
213
- console.print(
214
- f"App running at: [bold green]{url}[/bold green]{' (Frontend-only mode)' if not backend_present else ''}"
215
- )
225
+ notify_frontend(url, backend_present)
216
226
  if backend_present:
217
227
  notify_backend()
218
228
  first_run = False
@@ -249,6 +259,11 @@ def run_frontend(root: Path, port: str, backend_present: bool = True):
249
259
  )
250
260
 
251
261
 
262
+ def notify_app_running():
263
+ """Notify that the app is running."""
264
+ console.rule("[bold green]App Running")
265
+
266
+
252
267
  def run_frontend_prod(root: Path, port: str, backend_present: bool = True):
253
268
  """Run the frontend.
254
269
 
@@ -264,7 +279,7 @@ def run_frontend_prod(root: Path, port: str, backend_present: bool = True):
264
279
  # validate dependencies before run
265
280
  js_runtimes.validate_frontend_dependencies(init=False)
266
281
  # Run the frontend in production mode.
267
- console.rule("[bold green]App Running")
282
+ notify_app_running()
268
283
  run_process_and_launch_url(
269
284
  [*js_runtimes.get_js_package_executor(raise_on_none=True)[0], "run", "prod"],
270
285
  backend_present,
@@ -552,6 +567,7 @@ def run_backend_prod(
552
567
  port: int,
553
568
  loglevel: constants.LogLevel = constants.LogLevel.ERROR,
554
569
  frontend_present: bool = False,
570
+ mount_frontend_compiled_app: bool = False,
555
571
  ):
556
572
  """Run the backend.
557
573
 
@@ -560,10 +576,13 @@ def run_backend_prod(
560
576
  port: The app port
561
577
  loglevel: The log level.
562
578
  frontend_present: Whether the frontend is present.
579
+ mount_frontend_compiled_app: Whether to mount the compiled frontend app with the backend.
563
580
  """
564
581
  if not frontend_present:
565
582
  notify_backend()
566
583
 
584
+ environment.REFLEX_MOUNT_FRONTEND_COMPILED_APP.set(mount_frontend_compiled_app)
585
+
567
586
  if should_use_granian():
568
587
  run_granian_backend_prod(host, port, loglevel)
569
588
  else:
@@ -5,8 +5,6 @@ import random
5
5
  import re
6
6
  from pathlib import Path
7
7
 
8
- import click
9
-
10
8
  from reflex import constants
11
9
  from reflex.compiler import templates
12
10
  from reflex.config import Config, get_config
@@ -54,7 +52,7 @@ def initialize_requirements_txt() -> bool:
54
52
  True if the user has to update the requirements.txt file.
55
53
 
56
54
  Raises:
57
- Exit: If the requirements.txt file cannot be read or written to.
55
+ SystemExit: If the requirements.txt file cannot be read or written to.
58
56
  """
59
57
  requirements_file_path = Path(constants.RequirementsTxt.FILE)
60
58
  if (
@@ -72,8 +70,8 @@ def initialize_requirements_txt() -> bool:
72
70
  except UnicodeDecodeError:
73
71
  continue
74
72
  except Exception as e:
75
- console.error(f"Failed to read {requirements_file_path}.")
76
- raise click.exceptions.Exit(1) from e
73
+ console.error(f"Failed to read {requirements_file_path} due to {e}.")
74
+ raise SystemExit(1) from None
77
75
  else:
78
76
  return True
79
77
 
@@ -6,7 +6,6 @@ import tempfile
6
6
  from collections.abc import Sequence
7
7
  from pathlib import Path
8
8
 
9
- import click
10
9
  from packaging import version
11
10
 
12
11
  from reflex import constants
@@ -30,22 +29,45 @@ def check_node_version() -> bool:
30
29
  )
31
30
 
32
31
 
33
- @once
34
- def get_node_version() -> version.Version | None:
35
- """Get the version of node.
32
+ def _get_version_of_executable(
33
+ executable_path: Path | None, version_arg: str = "--version"
34
+ ) -> version.Version | None:
35
+ """Get the version of an executable.
36
+
37
+ Args:
38
+ executable_path: The path to the executable.
39
+ version_arg: The argument to pass to the executable to get its version.
36
40
 
37
41
  Returns:
38
- The version of node.
42
+ The version of the executable.
39
43
  """
40
- node_path = path_ops.get_node_path()
41
- if node_path is None:
44
+ if executable_path is None:
42
45
  return None
43
46
  try:
44
- result = processes.new_process([node_path, "-v"], run=True)
45
- # The output will be in the form "vX.Y.Z", but version.parse() can handle it
46
- return version.parse(result.stdout)
47
+ result = processes.new_process([executable_path, version_arg], run=True)
48
+ if result.returncode != 0:
49
+ console.error(
50
+ f"Failed to run {executable_path} {version_arg} to get version. Return code: {result.returncode}. Standard error: {result.stderr!r}."
51
+ )
52
+ return None
53
+ return version.parse(result.stdout.strip())
47
54
  except (FileNotFoundError, TypeError):
48
55
  return None
56
+ except version.InvalidVersion as e:
57
+ console.warn(
58
+ f"The detected version of {executable_path} ({e.args[0]}) is not valid. Defaulting to None."
59
+ )
60
+ return None
61
+
62
+
63
+ @once
64
+ def get_node_version() -> version.Version | None:
65
+ """Get the version of node.
66
+
67
+ Returns:
68
+ The version of node.
69
+ """
70
+ return _get_version_of_executable(path_ops.get_node_path())
49
71
 
50
72
 
51
73
  def get_bun_version(bun_path: Path | None = None) -> version.Version | None:
@@ -57,20 +79,7 @@ def get_bun_version(bun_path: Path | None = None) -> version.Version | None:
57
79
  Returns:
58
80
  The version of bun.
59
81
  """
60
- bun_path = bun_path or path_ops.get_bun_path()
61
- if bun_path is None:
62
- return None
63
- try:
64
- # Run the bun -v command and capture the output
65
- result = processes.new_process([str(bun_path), "-v"], run=True)
66
- return version.parse(str(result.stdout))
67
- except FileNotFoundError:
68
- return None
69
- except version.InvalidVersion as e:
70
- console.warn(
71
- f"The detected bun version ({e.args[0]}) is not valid. Defaulting to None."
72
- )
73
- return None
82
+ return _get_version_of_executable(bun_path or path_ops.get_bun_path())
74
83
 
75
84
 
76
85
  def npm_escape_hatch() -> bool:
@@ -193,7 +202,7 @@ def download_and_run(url: str, *args, show_status: bool = False, **env):
193
202
  env: The environment variables to use.
194
203
 
195
204
  Raises:
196
- Exit: If the script fails to download.
205
+ SystemExit: If the script fails to download.
197
206
  """
198
207
  import httpx
199
208
 
@@ -206,7 +215,7 @@ def download_and_run(url: str, *args, show_status: bool = False, **env):
206
215
  console.error(
207
216
  f"Failed to download bun install script. You can install or update bun manually from https://bun.com \n{e}"
208
217
  )
209
- raise click.exceptions.Exit(1) from None
218
+ raise SystemExit(1) from None
210
219
 
211
220
  # Save the script to a temporary file.
212
221
  with tempfile.NamedTemporaryFile() as tempfile_file:
@@ -226,7 +235,7 @@ def install_bun():
226
235
 
227
236
  Raises:
228
237
  SystemPackageMissingError: If "unzip" is missing.
229
- Exit: If REFLEX_USE_NPM is set but Node.js is not installed.
238
+ SystemExit: If REFLEX_USE_NPM is set but Node.js is not installed.
230
239
  """
231
240
  if npm_escape_hatch():
232
241
  if get_node_version() is not None:
@@ -237,7 +246,7 @@ def install_bun():
237
246
  console.error(
238
247
  "REFLEX_USE_NPM is set, but Node.js is not installed. Please install Node.js to use npm."
239
248
  )
240
- raise click.exceptions.Exit(1)
249
+ raise SystemExit(1)
241
250
 
242
251
  bun_path = path_ops.get_bun_path()
243
252
 
@@ -290,7 +299,7 @@ def validate_bun(bun_path: Path | None = None):
290
299
  bun_path: The path to the bun executable. If None, the default bun path is used.
291
300
 
292
301
  Raises:
293
- Exit: If custom specified bun does not exist or does not meet requirements.
302
+ SystemExit: If custom specified bun does not exist or does not meet requirements.
294
303
  """
295
304
  bun_path = bun_path or path_ops.get_bun_path()
296
305
 
@@ -304,7 +313,7 @@ def validate_bun(bun_path: Path | None = None):
304
313
  console.error(
305
314
  "Failed to obtain bun version. Make sure the specified bun path in your config is correct."
306
315
  )
307
- raise click.exceptions.Exit(1)
316
+ raise SystemExit(1)
308
317
  if bun_version < version.parse(constants.Bun.MIN_VERSION):
309
318
  console.warn(
310
319
  f"Reflex requires bun version {constants.Bun.MIN_VERSION} or higher to run, but the detected version is "
@@ -320,20 +329,21 @@ def validate_frontend_dependencies(init: bool = True):
320
329
  init: whether running `reflex init`
321
330
 
322
331
  Raises:
323
- Exit: If the package manager is invalid.
332
+ SystemExit: If the package manager is invalid.
324
333
  """
325
334
  if not init:
326
335
  try:
327
336
  get_js_package_executor(raise_on_none=True)
328
337
  except FileNotFoundError as e:
329
- raise click.exceptions.Exit(1) from e
338
+ console.error(f"Failed to find a valid package manager due to {e}.")
339
+ raise SystemExit(1) from None
330
340
 
331
341
  if prefer_npm_over_bun() and not check_node_version():
332
342
  node_version = get_node_version()
333
343
  console.error(
334
344
  f"Reflex requires node version {constants.Node.MIN_VERSION} or higher to run, but the detected version is {node_version}",
335
345
  )
336
- raise click.exceptions.Exit(1)
346
+ raise SystemExit(1)
337
347
 
338
348
 
339
349
  def remove_existing_bun_installation():
@@ -15,7 +15,6 @@ from pathlib import Path
15
15
  from types import ModuleType
16
16
  from typing import NamedTuple
17
17
 
18
- import click
19
18
  from alembic.util.exc import CommandError
20
19
  from packaging import version
21
20
  from redis import Redis as RedisSync
@@ -444,7 +443,7 @@ def validate_app_name(app_name: str | None = None) -> str:
444
443
  The app name after validation.
445
444
 
446
445
  Raises:
447
- Exit: if the app directory name is reflex or if the name is not standard for a python package name.
446
+ SystemExit: if the app directory name is reflex or if the name is not standard for a python package name.
448
447
  """
449
448
  app_name = app_name if app_name else Path.cwd().name.replace("-", "_")
450
449
  # Make sure the app is not named "reflex".
@@ -452,14 +451,14 @@ def validate_app_name(app_name: str | None = None) -> str:
452
451
  console.error(
453
452
  f"The app directory cannot be named [bold]{constants.Reflex.MODULE_NAME}[/bold]."
454
453
  )
455
- raise click.exceptions.Exit(1)
454
+ raise SystemExit(1)
456
455
 
457
456
  # Make sure the app name is standard for a python package name.
458
457
  if not re.match(r"^[a-zA-Z][a-zA-Z0-9_]*$", app_name):
459
458
  console.error(
460
459
  "The app directory name must start with a letter and can contain letters, numbers, and underscores."
461
460
  )
462
- raise click.exceptions.Exit(1)
461
+ raise SystemExit(1)
463
462
 
464
463
  return app_name
465
464
 
@@ -499,13 +498,13 @@ def assert_in_reflex_dir():
499
498
  """Assert that the current working directory is the reflex directory.
500
499
 
501
500
  Raises:
502
- Exit: If the current working directory is not the reflex directory.
501
+ SystemExit: If the current working directory is not the reflex directory.
503
502
  """
504
503
  if not constants.Config.FILE.exists():
505
504
  console.error(
506
505
  f"[cyan]{constants.Config.FILE}[/cyan] not found. Move to the root folder of your project, or run [bold]{constants.Reflex.MODULE_NAME} init[/bold] to start a new project."
507
506
  )
508
- raise click.exceptions.Exit(1)
507
+ raise SystemExit(1)
509
508
 
510
509
 
511
510
  def needs_reinit() -> bool: