reflex 0.7.5a1__py3-none-any.whl → 0.7.6a0__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.

@@ -11,12 +11,10 @@ from contextlib import contextmanager
11
11
  from pathlib import Path
12
12
 
13
13
  import httpx
14
- import tomlkit
15
14
  import typer
16
- from tomlkit.exceptions import NonExistentKey, TOMLKitError
17
15
 
18
16
  from reflex import constants
19
- from reflex.config import environment, get_config
17
+ from reflex.config import get_config
20
18
  from reflex.constants import CustomComponents
21
19
  from reflex.utils import console
22
20
 
@@ -64,28 +62,6 @@ def _create_package_config(module_name: str, package_name: str):
64
62
  )
65
63
 
66
64
 
67
- def _get_package_config(exit_on_fail: bool = True) -> dict:
68
- """Get the package configuration from the pyproject.toml file.
69
-
70
- Args:
71
- exit_on_fail: Whether to exit if the pyproject.toml file is not found.
72
-
73
- Returns:
74
- The package configuration.
75
-
76
- Raises:
77
- Exit: If the pyproject.toml file is not found and exit_on_fail is True.
78
- """
79
- pyproject = Path(CustomComponents.PYPROJECT_TOML)
80
- try:
81
- return dict(tomlkit.loads(pyproject.read_bytes()))
82
- except (OSError, TOMLKitError) as ex:
83
- console.error(f"Unable to read from {pyproject} due to {ex}")
84
- if exit_on_fail:
85
- raise typer.Exit(code=1) from ex
86
- raise
87
-
88
-
89
65
  def _create_readme(module_name: str, package_name: str):
90
66
  """Create a package README file.
91
67
 
@@ -425,12 +401,13 @@ def _make_pyi_files():
425
401
  """Create pyi files for the custom component."""
426
402
  from reflex.utils.pyi_generator import PyiGenerator
427
403
 
428
- package_name = _get_package_config()["project"]["name"]
429
-
430
- for dir, _, _ in os.walk(f"./{package_name}"):
431
- if "__pycache__" in dir:
404
+ for top_level_dir in Path.cwd().iterdir():
405
+ if not top_level_dir.is_dir() or top_level_dir.name.startswith("."):
432
406
  continue
433
- PyiGenerator().scan_all([dir])
407
+ for dir, _, _ in top_level_dir.walk():
408
+ if "__pycache__" in dir.name:
409
+ continue
410
+ PyiGenerator().scan_all([dir])
434
411
 
435
412
 
436
413
  def _run_build():
@@ -465,359 +442,18 @@ def build(
465
442
  _run_build()
466
443
 
467
444
 
468
- def _validate_repository_name(repository: str | None) -> str:
469
- """Validate the repository name.
470
-
471
- Args:
472
- repository: The name of the repository.
473
-
474
- Returns:
475
- The name of the repository.
476
-
477
- Raises:
478
- Exit: If the repository name is not supported.
479
- """
480
- if repository is None:
481
- return "pypi"
482
- elif repository not in CustomComponents.REPO_URLS:
483
- console.error(
484
- f"Unsupported repository name. Allow {CustomComponents.REPO_URLS.keys()}, got {repository}"
485
- )
486
- raise typer.Exit(code=1)
487
- return repository
488
-
489
-
490
- def _validate_credentials(
491
- username: str | None, password: str | None, token: str | None
492
- ) -> tuple[str, str]:
493
- """Validate the credentials.
494
-
495
- Args:
496
- username: The username to use for authentication on python package repository.
497
- password: The password to use for authentication on python package repository.
498
- token: The token to use for authentication on python package repository.
499
-
500
- Raises:
501
- Exit: If the appropriate combination of credentials is not provided.
502
-
503
- Returns:
504
- The username and password.
505
- """
506
- if token is not None:
507
- if username is not None or password is not None:
508
- console.error("Cannot use token and username/password at the same time.")
509
- raise typer.Exit(code=1)
510
- username = "__token__"
511
- password = token
512
- elif username is None or password is None:
513
- console.error(
514
- "Must provide both username and password for authentication if not using a token."
515
- )
516
- raise typer.Exit(code=1)
517
-
518
- return username, password
519
-
520
-
521
- def _get_version_to_publish() -> str:
522
- """Get the version to publish from the pyproject.toml.
523
-
524
- Returns:
525
- The version to publish.
526
- """
527
- try:
528
- return _get_package_config()["project"]["version"]
529
- except NonExistentKey:
530
- # Try to get the version from dynamic sources
531
- import build.util
532
-
533
- return build.util.project_wheel_metadata(".", isolated=True)["version"]
534
-
535
-
536
- def _ensure_dist_dir(version_to_publish: str, build: bool):
537
- """Ensure the distribution directory and the expected files exist.
538
-
539
- Args:
540
- version_to_publish: The version to be published.
541
- build: Whether to build the package first.
445
+ @custom_components_cli.command(name="publish", deprecated=True)
446
+ def publish():
447
+ """Publish a custom component. This command is deprecated and will be removed in future releases.
542
448
 
543
449
  Raises:
544
- Exit: If the distribution directory does not exist, or the expected files are not found.
450
+ Exit: If the publish command fails.
545
451
  """
546
- dist_dir = Path(CustomComponents.DIST_DIR)
547
-
548
- if build:
549
- # Need to check if the files here are for the version to be published.
550
- if dist_dir.exists():
551
- # Check if the distribution files are for the version to be published.
552
- needs_rebuild = False
553
- for suffix in CustomComponents.DISTRIBUTION_FILE_SUFFIXES:
554
- if not list(dist_dir.glob(f"*{version_to_publish}*{suffix}")):
555
- console.debug(
556
- f"Expected distribution file with suffix {suffix} for version {version_to_publish} not found in directory {dist_dir.name}"
557
- )
558
- needs_rebuild = True
559
- break
560
- else:
561
- needs_rebuild = True
562
-
563
- if not needs_rebuild:
564
- needs_rebuild = (
565
- console.ask(
566
- "Distribution files for the version to be published already exist. Do you want to rebuild?",
567
- choices=["y", "n"],
568
- default="n",
569
- )
570
- == "y"
571
- )
572
- if needs_rebuild:
573
- _run_build()
574
-
575
- # Check if the distribution directory exists.
576
- if not dist_dir.exists():
577
- console.error(f"Directory {dist_dir.name} does not exist. Please build first.")
578
- raise typer.Exit(code=1)
579
-
580
- # Check if the distribution directory is indeed a directory.
581
- if not dist_dir.is_dir():
582
- console.error(
583
- f"{dist_dir.name} is not a directory. If this is a file you added, move it and rebuild."
584
- )
585
- raise typer.Exit(code=1)
586
-
587
- # Check if the distribution files exist.
588
- for suffix in CustomComponents.DISTRIBUTION_FILE_SUFFIXES:
589
- if not list(dist_dir.glob(f"*{suffix}")):
590
- console.error(
591
- f"Expected distribution file with suffix {suffix} in directory {dist_dir.name}"
592
- )
593
- raise typer.Exit(code=1)
594
-
595
-
596
- @custom_components_cli.command(name="publish")
597
- def publish(
598
- repository: str | None = typer.Option(
599
- None,
600
- "-r",
601
- "--repository",
602
- help="The name of the repository. Defaults to pypi. Only supports pypi and testpypi (Test PyPI) for now.",
603
- ),
604
- token: str | None = typer.Option(
605
- None,
606
- "-t",
607
- "--token",
608
- help="The API token to use for authentication on python package repository. If token is provided, no username/password should be provided at the same time",
609
- ),
610
- username: str | None = typer.Option(
611
- environment.TWINE_USERNAME.get(),
612
- "-u",
613
- "--username",
614
- show_default="TWINE_USERNAME environment variable value if set",
615
- help="The username to use for authentication on python package repository. Username and password must both be provided.",
616
- ),
617
- password: str | None = typer.Option(
618
- environment.TWINE_PASSWORD.get(),
619
- "-p",
620
- "--password",
621
- show_default="TWINE_PASSWORD environment variable value if set",
622
- help="The password to use for authentication on python package repository. Username and password must both be provided.",
623
- ),
624
- build: bool = typer.Option(
625
- True,
626
- help="Whether to build the package before publishing. If the package is already built, set this to False.",
627
- ),
628
- share: bool = typer.Option(
629
- True,
630
- help="Whether to prompt to share more details on the published package. Only applicable when published to PyPI. Defaults to True.",
631
- ),
632
- validate_project_info: bool = typer.Option(
633
- True,
634
- help="Whether to interactively validate the project information in the pyproject.toml file.",
635
- ),
636
- loglevel: constants.LogLevel | None = typer.Option(
637
- None, help="The log level to use."
638
- ),
639
- ):
640
- """Publish a custom component. Must be run from the project root directory where the pyproject.toml is.
641
-
642
- Args:
643
- repository: The name of the Python package repository, such pypi, testpypi.
644
- token: The token to use for authentication on python package repository. If token is provided, no username/password should be provided at the same time.
645
- username: The username to use for authentication on python package repository.
646
- password: The password to use for authentication on python package repository.
647
- build: Whether to build the distribution files. Defaults to True.
648
- share: Whether to prompt to share more details on the published package. Defaults to True.
649
- validate_project_info: whether to interactively validate the project information in the pyproject.toml file. Defaults to True.
650
- loglevel: The log level to use.
651
-
652
- Raises:
653
- Exit: If arguments provided are not correct or the publish fails.
654
- """
655
- console.set_log_level(loglevel or get_config().loglevel)
656
-
657
- # Validate the repository name.
658
- repository = _validate_repository_name(repository)
659
- console.print(f"Publishing custom component to {repository}...")
660
-
661
- # Validate the credentials.
662
- username, password = _validate_credentials(username, password, token)
663
-
664
- # Minimal Validation of the pyproject.toml.
665
- _min_validate_project_info()
666
-
667
- # Get the version to publish from the pyproject.toml.
668
- version_to_publish = _get_version_to_publish()
669
-
670
- # Validate the distribution directory.
671
- _ensure_dist_dir(version_to_publish=version_to_publish, build=build)
672
-
673
- if validate_project_info and (
674
- console.ask(
675
- "Would you like to interactively review the package information?",
676
- choices=["y", "n"],
677
- default="y",
678
- )
679
- == "y"
680
- ):
681
- _validate_project_info()
682
-
683
- publish_cmds = [
684
- sys.executable,
685
- "-m",
686
- "twine",
687
- "upload",
688
- "--repository-url",
689
- CustomComponents.REPO_URLS[repository],
690
- "--username",
691
- username,
692
- "--password",
693
- password,
694
- "--non-interactive",
695
- f"{CustomComponents.DIST_DIR}/*{version_to_publish}*",
696
- ]
697
- if _run_commands_in_subprocess(publish_cmds):
698
- console.info("Custom component published successfully!")
699
- else:
700
- raise typer.Exit(1)
701
-
702
- # Only prompt to share more details on the published package if it is published to PyPI.
703
- if repository != "pypi" or not share:
704
- return
705
-
706
- # Ask user to share more details on the published package.
707
- if (
708
- console.ask(
709
- "Would you like to include your published component on our gallery?",
710
- choices=["y", "n"],
711
- default="y",
712
- )
713
- == "n"
714
- ):
715
- console.print(
716
- "If you decide to do this later, you can run `reflex component share` command. Thank you!"
717
- )
718
- return
719
-
720
- _collect_details_for_gallery()
721
-
722
-
723
- def _process_entered_list(input: str | None) -> list | None:
724
- """Process the user entered comma separated list into a list if applicable.
725
-
726
- Args:
727
- input: the user entered comma separated list
728
-
729
- Returns:
730
- The list of items or None.
731
- """
732
- return [t.strip() for t in (input or "").split(",") if t if input] or None
733
-
734
-
735
- def _min_validate_project_info():
736
- """Ensures minimal project information in the pyproject.toml file.
737
-
738
- Raises:
739
- Exit: If the pyproject.toml file is ill-formed.
740
- """
741
- pyproject_toml = _get_package_config()
742
-
743
- project = pyproject_toml.get("project")
744
- if project is None:
745
- console.error(
746
- f"The project section is not found in {CustomComponents.PYPROJECT_TOML}"
747
- )
748
- raise typer.Exit(code=1)
749
-
750
- if not project.get("name"):
751
- console.error(
752
- f"The project name is not found in {CustomComponents.PYPROJECT_TOML}"
753
- )
754
- raise typer.Exit(code=1)
755
-
756
- if not project.get("version") and "version" not in project.get("dynamic", []):
757
- console.error(
758
- f"The project version is not found in {CustomComponents.PYPROJECT_TOML}"
759
- )
760
- raise typer.Exit(code=1)
761
-
762
-
763
- def _validate_project_info():
764
- """Validate the project information in the pyproject.toml file.
765
-
766
- Raises:
767
- Exit: If the pyproject.toml file is ill-formed.
768
- """
769
- pyproject_toml = _get_package_config()
770
- project = pyproject_toml["project"]
771
- console.print(
772
- f"Double check the information before publishing: {project['name']} version {_get_version_to_publish()}"
773
- )
774
-
775
- console.print("Update or enter to keep the current information.")
776
- project["description"] = console.ask(
777
- "short description", default=project.get("description", "")
778
- )
779
- # PyPI only shows the first author.
780
- author = project.get("authors", [{}])[0]
781
- author["name"] = console.ask("Author Name", default=author.get("name", ""))
782
- author["email"] = console.ask("Author Email", default=author.get("email", ""))
783
-
784
- console.print(f"Current keywords are: {project.get('keywords') or []}")
785
- keyword_action = console.ask(
786
- "Keep, replace or append?", choices=["k", "r", "a"], default="k"
787
- )
788
- new_keywords = []
789
- if keyword_action == "r":
790
- new_keywords = (
791
- _process_entered_list(
792
- console.ask("Enter new set of keywords separated by commas")
793
- )
794
- or []
795
- )
796
- project["keywords"] = new_keywords
797
- elif keyword_action == "a":
798
- new_keywords = (
799
- _process_entered_list(
800
- console.ask("Enter new set of keywords separated by commas")
801
- )
802
- or []
803
- )
804
- project["keywords"] = project.get("keywords", []) + new_keywords
805
-
806
- if not project.get("urls"):
807
- project["urls"] = {}
808
- project["urls"]["homepage"] = console.ask(
809
- "homepage URL", default=project["urls"].get("homepage", "")
452
+ console.error(
453
+ "The publish command is deprecated. You can use `reflex component build` followed by `twine upload` or a similar publishing command to publish your custom component."
454
+ "\nIf you want to share your custom component with the Reflex community, please use `reflex component share`."
810
455
  )
811
- project["urls"]["source"] = console.ask(
812
- "source code URL", default=project["urls"].get("source", "")
813
- )
814
- pyproject_toml["project"] = project
815
- try:
816
- with CustomComponents.PYPROJECT_TOML.open("w") as f:
817
- tomlkit.dump(pyproject_toml, f)
818
- except (OSError, TOMLKitError) as ex:
819
- console.error(f"Unable to write to pyproject.toml due to {ex}")
820
- raise typer.Exit(code=1) from ex
456
+ raise typer.Exit(code=1)
821
457
 
822
458
 
823
459
  def _collect_details_for_gallery():
@@ -840,14 +476,8 @@ def _collect_details_for_gallery():
840
476
 
841
477
  console.rule("[bold]Custom Component Information")
842
478
  params = {}
843
- package_name = None
844
- try:
845
- package_name = _get_package_config(exit_on_fail=False)["project"]["name"]
846
- except (TOMLKitError, KeyError) as ex:
847
- console.debug(
848
- f"Unable to read from pyproject.toml in current directory due to {ex}"
849
- )
850
- package_name = console.ask("[ Published python package name ]")
479
+
480
+ package_name = console.ask("[ Published python package name ]")
851
481
  console.print(f"[ Custom component package name ] : {package_name}")
852
482
  params["package_name"] = package_name
853
483
 
reflex/event.py CHANGED
@@ -219,7 +219,7 @@ class EventHandler(EventActionsMixin):
219
219
  from reflex.utils.exceptions import EventHandlerTypeError
220
220
 
221
221
  # Get the function args.
222
- fn_args = inspect.getfullargspec(self.fn).args[1:]
222
+ fn_args = list(inspect.signature(self.fn).parameters)[1:]
223
223
  fn_args = (Var(_js_expr=arg) for arg in fn_args)
224
224
 
225
225
  # Construct the payload.
@@ -317,7 +317,9 @@ class EventSpec(EventActionsMixin):
317
317
  from reflex.utils.exceptions import EventHandlerTypeError
318
318
 
319
319
  # Get the remaining unfilled function args.
320
- fn_args = inspect.getfullargspec(self.handler.fn).args[1 + len(self.args) :]
320
+ fn_args = list(inspect.signature(self.handler.fn).parameters)[
321
+ 1 + len(self.args) :
322
+ ]
321
323
  fn_args = (Var(_js_expr=arg) for arg in fn_args)
322
324
 
323
325
  # Construct the payload.
@@ -1264,7 +1266,9 @@ def call_event_handler(
1264
1266
  type_hints_of_provided_callback = {}
1265
1267
 
1266
1268
  if event_spec_return_types:
1267
- event_callback_spec = inspect.getfullargspec(event_callback.fn)
1269
+ event_callback_spec_args = list(
1270
+ inspect.signature(event_callback.fn).parameters.keys()
1271
+ )
1268
1272
 
1269
1273
  for event_spec_index, event_spec_return_type in enumerate(
1270
1274
  event_spec_return_types
@@ -1277,7 +1281,7 @@ def call_event_handler(
1277
1281
 
1278
1282
  # check that args of event handler are matching the spec if type hints are provided
1279
1283
  for i, arg in enumerate(
1280
- event_callback_spec.args[1 : len(args_types_without_vars) + 1]
1284
+ event_callback_spec_args[1 : len(args_types_without_vars) + 1]
1281
1285
  ):
1282
1286
  if arg not in type_hints_of_provided_callback:
1283
1287
  continue
@@ -1320,7 +1324,7 @@ def call_event_handler(
1320
1324
 
1321
1325
  given_string = ", ".join(
1322
1326
  repr(type_hints_of_provided_callback.get(arg, Any))
1323
- for arg in event_callback_spec.args[1:]
1327
+ for arg in event_callback_spec_args[1:]
1324
1328
  ).replace("[", "\\[")
1325
1329
 
1326
1330
  console.warn(
@@ -1424,12 +1428,16 @@ def check_fn_match_arg_spec(
1424
1428
  Raises:
1425
1429
  EventFnArgMismatchError: Raised if the number of mandatory arguments do not match
1426
1430
  """
1427
- user_args = inspect.getfullargspec(user_func).args
1431
+ user_args = list(inspect.signature(user_func).parameters)
1428
1432
  # Drop the first argument if it's a bound method
1429
1433
  if inspect.ismethod(user_func) and user_func.__self__ is not None:
1430
1434
  user_args = user_args[1:]
1431
1435
 
1432
- user_default_args = inspect.getfullargspec(user_func).defaults
1436
+ user_default_args = [
1437
+ p.default
1438
+ for p in inspect.signature(user_func).parameters.values()
1439
+ if p.default is not inspect.Parameter.empty
1440
+ ]
1433
1441
  number_of_user_args = len(user_args) - number_of_bound_args
1434
1442
  number_of_user_default_args = len(user_default_args) if user_default_args else 0
1435
1443
 
@@ -1476,7 +1484,7 @@ def call_event_fn(
1476
1484
 
1477
1485
  parsed_args = parse_args_spec(arg_spec)
1478
1486
 
1479
- number_of_fn_args = len(inspect.getfullargspec(fn).args)
1487
+ number_of_fn_args = len(inspect.signature(fn).parameters)
1480
1488
 
1481
1489
  # Call the function with the parsed args.
1482
1490
  out = fn(*[*parsed_args][:number_of_fn_args])
@@ -1520,7 +1528,7 @@ def get_handler_args(
1520
1528
  Returns:
1521
1529
  The handler args.
1522
1530
  """
1523
- args = inspect.getfullargspec(event_spec.handler.fn).args
1531
+ args = inspect.signature(event_spec.handler.fn).parameters
1524
1532
 
1525
1533
  return event_spec.args if len(args) > 1 else ()
1526
1534
 
reflex/reflex.py CHANGED
@@ -97,11 +97,18 @@ def _init(
97
97
  prerequisites.initialize_gitignore()
98
98
 
99
99
  # Initialize the requirements.txt.
100
- prerequisites.initialize_requirements_txt()
100
+ wrote_to_requirements = prerequisites.initialize_requirements_txt()
101
101
 
102
102
  template_msg = f" using the {template} template" if template else ""
103
103
  # Finish initializing the app.
104
- console.success(f"Initialized {app_name}{template_msg}")
104
+ console.success(
105
+ f"Initialized {app_name}{template_msg}."
106
+ + (
107
+ f" Make sure to add {constants.RequirementsTxt.DEFAULTS_STUB + constants.Reflex.VERSION} to your requirements.txt or pyproject.toml file."
108
+ if not wrote_to_requirements
109
+ else ""
110
+ )
111
+ )
105
112
 
106
113
 
107
114
  @cli.command()
reflex/state.py CHANGED
@@ -1702,7 +1702,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1702
1702
 
1703
1703
  try:
1704
1704
  # Get the delta after processing the event.
1705
- delta = await _resolve_delta(state.get_delta())
1705
+ delta = await state._get_resolved_delta()
1706
1706
  state._clean()
1707
1707
 
1708
1708
  return StateUpdate(
@@ -1873,11 +1873,13 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1873
1873
  tuple(state_name.split("."))
1874
1874
  )
1875
1875
  defining_state.dirty_vars.add(cvar)
1876
- dirty_vars.add(cvar)
1877
1876
  actual_var = defining_state.computed_vars.get(cvar)
1878
1877
  if actual_var is not None:
1879
1878
  actual_var.mark_dirty(instance=defining_state)
1880
- if defining_state is not self:
1879
+ if defining_state is self:
1880
+ dirty_vars.add(cvar)
1881
+ else:
1882
+ # mark dirty where this var is defined
1881
1883
  defining_state._mark_dirty()
1882
1884
 
1883
1885
  def _expired_computed_vars(self) -> set[str]:
@@ -1947,6 +1949,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
1947
1949
  # Return the delta.
1948
1950
  return delta
1949
1951
 
1952
+ async def _get_resolved_delta(self) -> Delta:
1953
+ """Get the delta for the state after resolving all coroutines.
1954
+
1955
+ Returns:
1956
+ The resolved delta for the state.
1957
+ """
1958
+ return await _resolve_delta(self.get_delta())
1959
+
1950
1960
  def _mark_dirty(self):
1951
1961
  """Mark the substate and all parent states as dirty."""
1952
1962
  state_name = self.get_name()
@@ -11,8 +11,17 @@ from reflex.components.component import Component
11
11
  from reflex.components.core.banner import has_connection_errors
12
12
  from reflex.components.core.cond import cond
13
13
  from reflex.constants import Endpoint
14
+ from reflex.utils.decorator import once
14
15
 
15
- redirect_script = """
16
+
17
+ @once
18
+ def redirect_script() -> str:
19
+ """Get the redirect script for Github Codespaces.
20
+
21
+ Returns:
22
+ The redirect script as a string.
23
+ """
24
+ return """
16
25
  const thisUrl = new URL(window.location.href);
17
26
  const params = new URLSearchParams(thisUrl.search)
18
27
 
@@ -61,7 +70,7 @@ def codespaces_auto_redirect() -> list[Component]:
61
70
  A list containing the conditional redirect component, or empty list.
62
71
  """
63
72
  if is_running_in_codespaces():
64
- return [cond(has_connection_errors, Script.create(redirect_script))]
73
+ return [cond(has_connection_errors, Script.create(redirect_script()))]
65
74
  return []
66
75
 
67
76
 
@@ -87,5 +96,5 @@ async def auth_codespace() -> HTMLResponse:
87
96
  </body>
88
97
  </html>
89
98
  """
90
- % redirect_script
99
+ % redirect_script()
91
100
  )
reflex/utils/exec.py CHANGED
@@ -603,13 +603,13 @@ def output_system_info():
603
603
 
604
604
  dependencies = [
605
605
  f"[Reflex {constants.Reflex.VERSION} with Python {platform.python_version()} (PATH: {sys.executable})]",
606
- f"[Node {prerequisites.get_node_version()} (Expected: {constants.Node.VERSION}) (PATH:{path_ops.get_node_path()})]",
606
+ f"[Node {prerequisites.get_node_version()} (Minimum: {constants.Node.MIN_VERSION}) (PATH:{path_ops.get_node_path()})]",
607
607
  ]
608
608
 
609
609
  system = platform.system()
610
610
 
611
611
  dependencies.append(
612
- f"[Bun {prerequisites.get_bun_version()} (Expected: {constants.Bun.VERSION}) (PATH: {path_ops.get_bun_path()})]"
612
+ f"[Bun {prerequisites.get_bun_version()} (Minimum: {constants.Bun.MIN_VERSION}) (PATH: {path_ops.get_bun_path()})]"
613
613
  )
614
614
 
615
615
  if system == "Linux":