reflex 0.8.6a0__py3-none-any.whl → 0.8.7a1__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.
- reflex/.templates/jinja/web/vite.config.js.jinja2 +4 -1
- reflex/.templates/web/app/routes.js +0 -1
- reflex/.templates/web/utils/state.js +1 -11
- reflex/app.py +27 -23
- reflex/components/base/error_boundary.py +2 -0
- reflex/components/component.py +6 -4
- reflex/components/lucide/icon.py +4 -1
- reflex/components/lucide/icon.pyi +4 -1
- reflex/components/plotly/plotly.py +9 -9
- reflex/components/recharts/recharts.py +2 -2
- reflex/components/sonner/toast.py +7 -7
- reflex/components/sonner/toast.pyi +8 -8
- reflex/config.py +9 -2
- reflex/constants/base.py +2 -0
- reflex/constants/installer.py +6 -6
- reflex/constants/state.py +1 -0
- reflex/custom_components/custom_components.py +3 -3
- reflex/istate/manager.py +2 -1
- reflex/plugins/__init__.py +2 -0
- reflex/plugins/_screenshot.py +144 -0
- reflex/plugins/base.py +14 -1
- reflex/reflex.py +7 -6
- reflex/route.py +4 -0
- reflex/state.py +2 -2
- reflex/testing.py +3 -5
- reflex/utils/build.py +21 -3
- reflex/utils/exec.py +11 -11
- reflex/utils/frontend_skeleton.py +254 -0
- reflex/utils/js_runtimes.py +411 -0
- reflex/utils/prerequisites.py +17 -1383
- reflex/utils/rename.py +170 -0
- reflex/utils/telemetry.py +101 -10
- reflex/utils/templates.py +443 -0
- reflex/vars/base.py +3 -3
- {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/METADATA +2 -2
- {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/RECORD +39 -36
- reflex/.templates/web/utils/client_side_routing.js +0 -45
- reflex/components/core/client_side_routing.py +0 -70
- {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/WHEEL +0 -0
- {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/entry_points.txt +0 -0
- {reflex-0.8.6a0.dist-info → reflex-0.8.7a1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Plugin to enable screenshot functionality."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from reflex.plugins.base import Plugin as BasePlugin
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from starlette.requests import Request
|
|
9
|
+
from starlette.responses import Response
|
|
10
|
+
from typing_extensions import Unpack
|
|
11
|
+
|
|
12
|
+
from reflex.app import App
|
|
13
|
+
from reflex.plugins.base import PostCompileContext
|
|
14
|
+
from reflex.state import BaseState
|
|
15
|
+
|
|
16
|
+
ACTIVE_CONNECTIONS = "/_active_connections"
|
|
17
|
+
CLONE_STATE = "/_clone_state"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _deep_copy(state: "BaseState") -> "BaseState":
|
|
21
|
+
"""Create a deep copy of the state.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
state: The state to copy.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
A deep copy of the state.
|
|
28
|
+
"""
|
|
29
|
+
import copy
|
|
30
|
+
|
|
31
|
+
copy_of_state = copy.deepcopy(state)
|
|
32
|
+
|
|
33
|
+
def copy_substate(substate: "BaseState") -> "BaseState":
|
|
34
|
+
substate_copy = _deep_copy(substate)
|
|
35
|
+
|
|
36
|
+
substate_copy.parent_state = copy_of_state
|
|
37
|
+
|
|
38
|
+
return substate_copy
|
|
39
|
+
|
|
40
|
+
copy_of_state.substates = {
|
|
41
|
+
substate_name: copy_substate(substate)
|
|
42
|
+
for substate_name, substate in state.substates.items()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return copy_of_state
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ScreenshotPlugin(BasePlugin):
|
|
49
|
+
"""Plugin to handle screenshot functionality."""
|
|
50
|
+
|
|
51
|
+
def post_compile(self, **context: "Unpack[PostCompileContext]") -> None:
|
|
52
|
+
"""Called after the compilation of the plugin.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
context: The context for the plugin.
|
|
56
|
+
"""
|
|
57
|
+
app = context["app"]
|
|
58
|
+
self._add_active_connections_endpoint(app)
|
|
59
|
+
self._add_clone_state_endpoint(app)
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def _add_active_connections_endpoint(app: "App") -> None:
|
|
63
|
+
"""Add an endpoint to the app that returns the active connections.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
app: The application instance to which the endpoint will be added.
|
|
67
|
+
"""
|
|
68
|
+
if not app._api:
|
|
69
|
+
return
|
|
70
|
+
|
|
71
|
+
async def active_connections(_request: "Request") -> "Response":
|
|
72
|
+
from starlette.responses import JSONResponse
|
|
73
|
+
|
|
74
|
+
if not app.event_namespace:
|
|
75
|
+
return JSONResponse({})
|
|
76
|
+
|
|
77
|
+
return JSONResponse(app.event_namespace.token_to_sid)
|
|
78
|
+
|
|
79
|
+
app._api.add_route(
|
|
80
|
+
ACTIVE_CONNECTIONS,
|
|
81
|
+
active_connections,
|
|
82
|
+
methods=["GET"],
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def _add_clone_state_endpoint(app: "App") -> None:
|
|
87
|
+
"""Add an endpoint to the app that clones the current state.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
app: The application instance to which the endpoint will be added.
|
|
91
|
+
"""
|
|
92
|
+
if not app._api:
|
|
93
|
+
return
|
|
94
|
+
|
|
95
|
+
async def clone_state(request: "Request") -> "Response":
|
|
96
|
+
import uuid
|
|
97
|
+
|
|
98
|
+
from starlette.responses import JSONResponse
|
|
99
|
+
|
|
100
|
+
from reflex.state import _substate_key
|
|
101
|
+
|
|
102
|
+
if not app.event_namespace:
|
|
103
|
+
return JSONResponse({})
|
|
104
|
+
|
|
105
|
+
token_to_clone = await request.json()
|
|
106
|
+
|
|
107
|
+
if not isinstance(token_to_clone, str):
|
|
108
|
+
return JSONResponse(
|
|
109
|
+
{"error": "Token to clone must be a string."}, status_code=400
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
old_state = await app.state_manager.get_state(token_to_clone)
|
|
113
|
+
|
|
114
|
+
new_state = _deep_copy(old_state)
|
|
115
|
+
|
|
116
|
+
new_token = uuid.uuid4().hex
|
|
117
|
+
|
|
118
|
+
all_states = [new_state]
|
|
119
|
+
|
|
120
|
+
found_new = True
|
|
121
|
+
|
|
122
|
+
while found_new:
|
|
123
|
+
found_new = False
|
|
124
|
+
|
|
125
|
+
for state in all_states:
|
|
126
|
+
for substate in state.substates.values():
|
|
127
|
+
substate._was_touched = True
|
|
128
|
+
|
|
129
|
+
if substate not in all_states:
|
|
130
|
+
all_states.append(substate)
|
|
131
|
+
|
|
132
|
+
found_new = True
|
|
133
|
+
|
|
134
|
+
await app.state_manager.set_state(
|
|
135
|
+
_substate_key(new_token, new_state), new_state
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return JSONResponse(new_token)
|
|
139
|
+
|
|
140
|
+
app._api.add_route(
|
|
141
|
+
CLONE_STATE,
|
|
142
|
+
clone_state,
|
|
143
|
+
methods=["POST"],
|
|
144
|
+
)
|
reflex/plugins/base.py
CHANGED
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, ParamSpec, Protocol, TypedDict
|
|
|
7
7
|
from typing_extensions import Unpack
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
|
-
from reflex.app import UnevaluatedPage
|
|
10
|
+
from reflex.app import App, UnevaluatedPage
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class CommonContext(TypedDict):
|
|
@@ -44,6 +44,12 @@ class PreCompileContext(CommonContext):
|
|
|
44
44
|
unevaluated_pages: Sequence["UnevaluatedPage"]
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
class PostCompileContext(CommonContext):
|
|
48
|
+
"""Context for post-compile hooks."""
|
|
49
|
+
|
|
50
|
+
app: "App"
|
|
51
|
+
|
|
52
|
+
|
|
47
53
|
class Plugin:
|
|
48
54
|
"""Base class for all plugins."""
|
|
49
55
|
|
|
@@ -104,6 +110,13 @@ class Plugin:
|
|
|
104
110
|
context: The context for the plugin.
|
|
105
111
|
"""
|
|
106
112
|
|
|
113
|
+
def post_compile(self, **context: Unpack[PostCompileContext]) -> None:
|
|
114
|
+
"""Called after the compilation of the plugin.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
context: The context for the plugin.
|
|
118
|
+
"""
|
|
119
|
+
|
|
107
120
|
def __repr__(self):
|
|
108
121
|
"""Return a string representation of the plugin.
|
|
109
122
|
|
reflex/reflex.py
CHANGED
|
@@ -58,7 +58,7 @@ def _init(
|
|
|
58
58
|
ai: bool = False,
|
|
59
59
|
):
|
|
60
60
|
"""Initialize a new Reflex app in the given directory."""
|
|
61
|
-
from reflex.utils import exec, prerequisites
|
|
61
|
+
from reflex.utils import exec, frontend_skeleton, prerequisites, templates
|
|
62
62
|
|
|
63
63
|
# Show system info
|
|
64
64
|
exec.output_system_info()
|
|
@@ -80,13 +80,13 @@ def _init(
|
|
|
80
80
|
prerequisites.initialize_frontend_dependencies()
|
|
81
81
|
|
|
82
82
|
# Initialize the app.
|
|
83
|
-
template =
|
|
83
|
+
template = templates.initialize_app(app_name, template)
|
|
84
84
|
|
|
85
85
|
# Initialize the .gitignore.
|
|
86
|
-
|
|
86
|
+
frontend_skeleton.initialize_gitignore()
|
|
87
87
|
|
|
88
88
|
# Initialize the requirements.txt.
|
|
89
|
-
needs_user_manual_update =
|
|
89
|
+
needs_user_manual_update = frontend_skeleton.initialize_requirements_txt()
|
|
90
90
|
|
|
91
91
|
template_msg = f" using the {template} template" if template else ""
|
|
92
92
|
# Finish initializing the app.
|
|
@@ -371,7 +371,7 @@ def compile(dry: bool):
|
|
|
371
371
|
_init(name=get_config().app_name)
|
|
372
372
|
get_config(reload=True)
|
|
373
373
|
starting_time = time.monotonic()
|
|
374
|
-
prerequisites.
|
|
374
|
+
prerequisites.get_compiled_app(dry_run=dry)
|
|
375
375
|
elapsed_time = time.monotonic() - starting_time
|
|
376
376
|
console.success(f"App compiled successfully in {elapsed_time:.3f} seconds.")
|
|
377
377
|
|
|
@@ -737,9 +737,10 @@ def deploy(
|
|
|
737
737
|
def rename(new_name: str):
|
|
738
738
|
"""Rename the app in the current directory."""
|
|
739
739
|
from reflex.utils import prerequisites
|
|
740
|
+
from reflex.utils.rename import rename_app
|
|
740
741
|
|
|
741
742
|
prerequisites.validate_app_name(new_name)
|
|
742
|
-
|
|
743
|
+
rename_app(new_name, get_config().loglevel)
|
|
743
744
|
|
|
744
745
|
|
|
745
746
|
if TYPE_CHECKING:
|
reflex/route.py
CHANGED
|
@@ -6,6 +6,7 @@ import re
|
|
|
6
6
|
from collections.abc import Callable
|
|
7
7
|
|
|
8
8
|
from reflex import constants
|
|
9
|
+
from reflex.config import get_config
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def verify_route_validity(route: str) -> None:
|
|
@@ -211,6 +212,9 @@ def get_router(routes: list[str]) -> Callable[[str], str | None]:
|
|
|
211
212
|
Returns:
|
|
212
213
|
The first matching route, or None if no match is found.
|
|
213
214
|
"""
|
|
215
|
+
config = get_config()
|
|
216
|
+
if config.frontend_path:
|
|
217
|
+
path = path.removeprefix(config.frontend_path)
|
|
214
218
|
path = "/" + path.removeprefix("/").removesuffix("/")
|
|
215
219
|
if path == "/index":
|
|
216
220
|
path = "/"
|
reflex/state.py
CHANGED
|
@@ -509,7 +509,7 @@ class BaseState(EvenMoreBasicBaseState):
|
|
|
509
509
|
|
|
510
510
|
new_backend_vars = {
|
|
511
511
|
name: value
|
|
512
|
-
for name, value in cls.__dict__.items()
|
|
512
|
+
for name, value in list(cls.__dict__.items())
|
|
513
513
|
if types.is_backend_base_variable(name, cls)
|
|
514
514
|
}
|
|
515
515
|
# Add annotated backend vars that may not have a default value.
|
|
@@ -2491,7 +2491,7 @@ class OnLoadInternalState(State):
|
|
|
2491
2491
|
# Cache the app reference for subsequent calls.
|
|
2492
2492
|
if type(self)._app_ref is None:
|
|
2493
2493
|
type(self)._app_ref = app
|
|
2494
|
-
load_events = app.get_load_events(self.router.
|
|
2494
|
+
load_events = app.get_load_events(self.router.url.path)
|
|
2495
2495
|
if not load_events:
|
|
2496
2496
|
self.is_hydrated = True
|
|
2497
2497
|
return None # Fast path for navigation with no on_load events defined.
|
reflex/testing.py
CHANGED
|
@@ -45,7 +45,7 @@ from reflex.state import (
|
|
|
45
45
|
StateManagerRedis,
|
|
46
46
|
reload_state_module,
|
|
47
47
|
)
|
|
48
|
-
from reflex.utils import console
|
|
48
|
+
from reflex.utils import console, js_runtimes
|
|
49
49
|
from reflex.utils.export import export
|
|
50
50
|
from reflex.utils.types import ASGIApp
|
|
51
51
|
|
|
@@ -403,9 +403,7 @@ class AppHarness:
|
|
|
403
403
|
# Start the frontend.
|
|
404
404
|
self.frontend_process = reflex.utils.processes.new_process(
|
|
405
405
|
[
|
|
406
|
-
*
|
|
407
|
-
0
|
|
408
|
-
],
|
|
406
|
+
*js_runtimes.get_js_package_executor(raise_on_none=True)[0],
|
|
409
407
|
"run",
|
|
410
408
|
"dev",
|
|
411
409
|
],
|
|
@@ -1010,7 +1008,7 @@ class AppHarnessProd(AppHarness):
|
|
|
1010
1008
|
/ reflex.constants.Dirs.STATIC
|
|
1011
1009
|
)
|
|
1012
1010
|
error_page_map = {
|
|
1013
|
-
404: web_root / "404
|
|
1011
|
+
404: web_root / "404.html",
|
|
1014
1012
|
}
|
|
1015
1013
|
with Subdir404TCPServer(
|
|
1016
1014
|
("", 0),
|
reflex/utils/build.py
CHANGED
|
@@ -4,12 +4,13 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
import zipfile
|
|
7
|
-
from pathlib import Path
|
|
7
|
+
from pathlib import Path, PosixPath
|
|
8
8
|
|
|
9
9
|
from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
|
|
10
10
|
|
|
11
11
|
from reflex import constants
|
|
12
|
-
from reflex.
|
|
12
|
+
from reflex.config import get_config
|
|
13
|
+
from reflex.utils import console, js_runtimes, path_ops, prerequisites, processes
|
|
13
14
|
from reflex.utils.exec import is_in_app_harness
|
|
14
15
|
|
|
15
16
|
|
|
@@ -187,7 +188,7 @@ def build():
|
|
|
187
188
|
# Start the subprocess with the progress bar.
|
|
188
189
|
process = processes.new_process(
|
|
189
190
|
[
|
|
190
|
-
*
|
|
191
|
+
*js_runtimes.get_js_package_executor(raise_on_none=True)[0],
|
|
191
192
|
"run",
|
|
192
193
|
"export",
|
|
193
194
|
],
|
|
@@ -200,6 +201,23 @@ def build():
|
|
|
200
201
|
)
|
|
201
202
|
processes.show_progress("Creating Production Build", process, checkpoints)
|
|
202
203
|
_duplicate_index_html_to_parent_dir(wdir / constants.Dirs.STATIC)
|
|
204
|
+
path_ops.cp(
|
|
205
|
+
wdir / constants.Dirs.STATIC / constants.ReactRouter.SPA_FALLBACK,
|
|
206
|
+
wdir / constants.Dirs.STATIC / "404.html",
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
config = get_config()
|
|
210
|
+
|
|
211
|
+
if frontend_path := config.frontend_path.strip("/"):
|
|
212
|
+
frontend_path = PosixPath(frontend_path)
|
|
213
|
+
first_part = frontend_path.parts[0]
|
|
214
|
+
for child in list((wdir / constants.Dirs.STATIC).iterdir()):
|
|
215
|
+
if child.is_dir() and child.name == first_part:
|
|
216
|
+
continue
|
|
217
|
+
path_ops.mv(
|
|
218
|
+
child,
|
|
219
|
+
wdir / constants.Dirs.STATIC / frontend_path / child.name,
|
|
220
|
+
)
|
|
203
221
|
|
|
204
222
|
|
|
205
223
|
def setup_frontend(
|
reflex/utils/exec.py
CHANGED
|
@@ -231,17 +231,17 @@ def run_frontend(root: Path, port: str, backend_present: bool = True):
|
|
|
231
231
|
port: The port to run the frontend on.
|
|
232
232
|
backend_present: Whether the backend is present.
|
|
233
233
|
"""
|
|
234
|
-
from reflex.utils import
|
|
234
|
+
from reflex.utils import js_runtimes
|
|
235
235
|
|
|
236
236
|
# validate dependencies before run
|
|
237
|
-
|
|
237
|
+
js_runtimes.validate_frontend_dependencies(init=False)
|
|
238
238
|
|
|
239
239
|
# Run the frontend in development mode.
|
|
240
240
|
console.rule("[bold green]App Running")
|
|
241
241
|
os.environ["PORT"] = str(get_config().frontend_port if port is None else port)
|
|
242
242
|
run_process_and_launch_url(
|
|
243
243
|
[
|
|
244
|
-
*
|
|
244
|
+
*js_runtimes.get_js_package_executor(raise_on_none=True)[0],
|
|
245
245
|
"run",
|
|
246
246
|
"dev",
|
|
247
247
|
],
|
|
@@ -257,16 +257,16 @@ def run_frontend_prod(root: Path, port: str, backend_present: bool = True):
|
|
|
257
257
|
port: The port to run the frontend on.
|
|
258
258
|
backend_present: Whether the backend is present.
|
|
259
259
|
"""
|
|
260
|
-
from reflex.utils import
|
|
260
|
+
from reflex.utils import js_runtimes
|
|
261
261
|
|
|
262
262
|
# Set the port.
|
|
263
263
|
os.environ["PORT"] = str(get_config().frontend_port if port is None else port)
|
|
264
264
|
# validate dependencies before run
|
|
265
|
-
|
|
265
|
+
js_runtimes.validate_frontend_dependencies(init=False)
|
|
266
266
|
# Run the frontend in production mode.
|
|
267
267
|
console.rule("[bold green]App Running")
|
|
268
268
|
run_process_and_launch_url(
|
|
269
|
-
[*
|
|
269
|
+
[*js_runtimes.get_js_package_executor(raise_on_none=True)[0], "run", "prod"],
|
|
270
270
|
backend_present,
|
|
271
271
|
)
|
|
272
272
|
|
|
@@ -670,7 +670,7 @@ def output_system_info():
|
|
|
670
670
|
if console._LOG_LEVEL > constants.LogLevel.DEBUG:
|
|
671
671
|
return
|
|
672
672
|
|
|
673
|
-
from reflex.utils import
|
|
673
|
+
from reflex.utils import js_runtimes
|
|
674
674
|
|
|
675
675
|
config = get_config()
|
|
676
676
|
try:
|
|
@@ -684,13 +684,13 @@ def output_system_info():
|
|
|
684
684
|
|
|
685
685
|
dependencies = [
|
|
686
686
|
f"[Reflex {constants.Reflex.VERSION} with Python {platform.python_version()} (PATH: {sys.executable})]",
|
|
687
|
-
f"[Node {
|
|
687
|
+
f"[Node {js_runtimes.get_node_version()} (Minimum: {constants.Node.MIN_VERSION}) (PATH:{path_ops.get_node_path()})]",
|
|
688
688
|
]
|
|
689
689
|
|
|
690
690
|
system = platform.system()
|
|
691
691
|
|
|
692
692
|
dependencies.append(
|
|
693
|
-
f"[Bun {
|
|
693
|
+
f"[Bun {js_runtimes.get_bun_version()} (Minimum: {constants.Bun.MIN_VERSION}) (PATH: {path_ops.get_bun_path()})]"
|
|
694
694
|
)
|
|
695
695
|
|
|
696
696
|
if system == "Linux":
|
|
@@ -704,10 +704,10 @@ def output_system_info():
|
|
|
704
704
|
console.debug(f"{dep}")
|
|
705
705
|
|
|
706
706
|
console.debug(
|
|
707
|
-
f"Using package installer at: {
|
|
707
|
+
f"Using package installer at: {js_runtimes.get_nodejs_compatible_package_managers(raise_on_none=False)}"
|
|
708
708
|
)
|
|
709
709
|
console.debug(
|
|
710
|
-
f"Using package executer at: {
|
|
710
|
+
f"Using package executer at: {js_runtimes.get_js_package_executor(raise_on_none=False)}"
|
|
711
711
|
)
|
|
712
712
|
if system != "Windows":
|
|
713
713
|
console.debug(f"Unzip path: {path_ops.which('unzip')}")
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"""This module provides utility functions to initialize the frontend skeleton."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import random
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from reflex import constants
|
|
11
|
+
from reflex.compiler import templates
|
|
12
|
+
from reflex.config import Config, get_config
|
|
13
|
+
from reflex.utils import console, path_ops
|
|
14
|
+
from reflex.utils.prerequisites import get_project_hash, get_web_dir
|
|
15
|
+
from reflex.utils.registry import get_npm_registry
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def initialize_gitignore(
|
|
19
|
+
gitignore_file: Path = constants.GitIgnore.FILE,
|
|
20
|
+
files_to_ignore: set[str] | list[str] = constants.GitIgnore.DEFAULTS,
|
|
21
|
+
):
|
|
22
|
+
"""Initialize the template .gitignore file.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
gitignore_file: The .gitignore file to create.
|
|
26
|
+
files_to_ignore: The files to add to the .gitignore file.
|
|
27
|
+
"""
|
|
28
|
+
# Combine with the current ignored files.
|
|
29
|
+
current_ignore: list[str] = []
|
|
30
|
+
if gitignore_file.exists():
|
|
31
|
+
current_ignore = [ln.strip() for ln in gitignore_file.read_text().splitlines()]
|
|
32
|
+
|
|
33
|
+
if files_to_ignore == current_ignore:
|
|
34
|
+
console.debug(f"{gitignore_file} already up to date.")
|
|
35
|
+
return
|
|
36
|
+
files_to_ignore = [ln for ln in files_to_ignore if ln not in current_ignore]
|
|
37
|
+
files_to_ignore += current_ignore
|
|
38
|
+
|
|
39
|
+
# Write files to the .gitignore file.
|
|
40
|
+
gitignore_file.touch(exist_ok=True)
|
|
41
|
+
console.debug(f"Creating {gitignore_file}")
|
|
42
|
+
gitignore_file.write_text("\n".join(files_to_ignore) + "\n")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def initialize_requirements_txt() -> bool:
|
|
46
|
+
"""Initialize the requirements.txt file.
|
|
47
|
+
If absent and no pyproject.toml file exists, generate one for the user.
|
|
48
|
+
If the requirements.txt does not have reflex as dependency,
|
|
49
|
+
generate a requirement pinning current version and append to
|
|
50
|
+
the requirements.txt file.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
True if the user has to update the requirements.txt file.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
Exit: If the requirements.txt file cannot be read or written to.
|
|
57
|
+
"""
|
|
58
|
+
requirements_file_path = Path(constants.RequirementsTxt.FILE)
|
|
59
|
+
if (
|
|
60
|
+
not requirements_file_path.exists()
|
|
61
|
+
and Path(constants.PyprojectToml.FILE).exists()
|
|
62
|
+
):
|
|
63
|
+
return True
|
|
64
|
+
|
|
65
|
+
requirements_file_path.touch(exist_ok=True)
|
|
66
|
+
|
|
67
|
+
for encoding in [None, "utf-8"]:
|
|
68
|
+
try:
|
|
69
|
+
content = requirements_file_path.read_text(encoding)
|
|
70
|
+
break
|
|
71
|
+
except UnicodeDecodeError:
|
|
72
|
+
continue
|
|
73
|
+
except Exception as e:
|
|
74
|
+
console.error(f"Failed to read {requirements_file_path}.")
|
|
75
|
+
raise click.exceptions.Exit(1) from e
|
|
76
|
+
else:
|
|
77
|
+
return True
|
|
78
|
+
|
|
79
|
+
for line in content.splitlines():
|
|
80
|
+
if re.match(r"^reflex[^a-zA-Z0-9]", line):
|
|
81
|
+
console.debug(f"{requirements_file_path} already has reflex as dependency.")
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
console.debug(
|
|
85
|
+
f"Appending {constants.RequirementsTxt.DEFAULTS_STUB} to {requirements_file_path}"
|
|
86
|
+
)
|
|
87
|
+
with requirements_file_path.open("a", encoding=encoding) as f:
|
|
88
|
+
f.write(
|
|
89
|
+
"\n" + constants.RequirementsTxt.DEFAULTS_STUB + constants.Reflex.VERSION
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def initialize_web_directory():
|
|
96
|
+
"""Initialize the web directory on reflex init."""
|
|
97
|
+
console.log("Initializing the web directory.")
|
|
98
|
+
|
|
99
|
+
# Reuse the hash if one is already created, so we don't over-write it when running reflex init
|
|
100
|
+
project_hash = get_project_hash()
|
|
101
|
+
|
|
102
|
+
console.debug(f"Copying {constants.Templates.Dirs.WEB_TEMPLATE} to {get_web_dir()}")
|
|
103
|
+
path_ops.copy_tree(constants.Templates.Dirs.WEB_TEMPLATE, str(get_web_dir()))
|
|
104
|
+
|
|
105
|
+
console.debug("Initializing the web directory.")
|
|
106
|
+
initialize_package_json()
|
|
107
|
+
|
|
108
|
+
console.debug("Initializing the bun config file.")
|
|
109
|
+
initialize_bun_config()
|
|
110
|
+
|
|
111
|
+
console.debug("Initializing the .npmrc file.")
|
|
112
|
+
initialize_npmrc()
|
|
113
|
+
|
|
114
|
+
console.debug("Initializing the public directory.")
|
|
115
|
+
path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
|
|
116
|
+
|
|
117
|
+
console.debug("Initializing the react-router.config.js file.")
|
|
118
|
+
update_react_router_config()
|
|
119
|
+
|
|
120
|
+
console.debug("Initializing the vite.config.js file.")
|
|
121
|
+
initialize_vite_config()
|
|
122
|
+
|
|
123
|
+
console.debug("Initializing the reflex.json file.")
|
|
124
|
+
# Initialize the reflex json file.
|
|
125
|
+
init_reflex_json(project_hash=project_hash)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def update_react_router_config(prerender_routes: bool = False):
|
|
129
|
+
"""Update react-router.config.js config from Reflex config.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
prerender_routes: Whether to enable prerendering of routes.
|
|
133
|
+
"""
|
|
134
|
+
react_router_config_file_path = get_web_dir() / constants.ReactRouter.CONFIG_FILE
|
|
135
|
+
|
|
136
|
+
new_react_router_config = _update_react_router_config(
|
|
137
|
+
get_config(), prerender_routes=prerender_routes
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Overwriting the config file triggers a full server reload, so make sure
|
|
141
|
+
# there is actually a diff.
|
|
142
|
+
old_react_router_config = (
|
|
143
|
+
react_router_config_file_path.read_text()
|
|
144
|
+
if react_router_config_file_path.exists()
|
|
145
|
+
else ""
|
|
146
|
+
)
|
|
147
|
+
if old_react_router_config != new_react_router_config:
|
|
148
|
+
react_router_config_file_path.write_text(new_react_router_config)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _update_react_router_config(config: Config, prerender_routes: bool = False):
|
|
152
|
+
basename = "/" + (config.frontend_path or "").strip("/")
|
|
153
|
+
if not basename.endswith("/"):
|
|
154
|
+
basename += "/"
|
|
155
|
+
|
|
156
|
+
react_router_config = {
|
|
157
|
+
"basename": basename,
|
|
158
|
+
"future": {
|
|
159
|
+
"unstable_optimizeDeps": True,
|
|
160
|
+
},
|
|
161
|
+
"ssr": False,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if prerender_routes:
|
|
165
|
+
react_router_config["prerender"] = True
|
|
166
|
+
react_router_config["build"] = constants.Dirs.BUILD_DIR
|
|
167
|
+
|
|
168
|
+
return f"export default {json.dumps(react_router_config)};"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _compile_package_json():
|
|
172
|
+
return templates.PACKAGE_JSON.render(
|
|
173
|
+
scripts={
|
|
174
|
+
"dev": constants.PackageJson.Commands.DEV,
|
|
175
|
+
"export": constants.PackageJson.Commands.EXPORT,
|
|
176
|
+
"prod": constants.PackageJson.Commands.PROD,
|
|
177
|
+
},
|
|
178
|
+
dependencies=constants.PackageJson.DEPENDENCIES,
|
|
179
|
+
dev_dependencies=constants.PackageJson.DEV_DEPENDENCIES,
|
|
180
|
+
overrides=constants.PackageJson.OVERRIDES,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def initialize_package_json():
|
|
185
|
+
"""Render and write in .web the package.json file."""
|
|
186
|
+
output_path = get_web_dir() / constants.PackageJson.PATH
|
|
187
|
+
output_path.write_text(_compile_package_json())
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _compile_vite_config(config: Config):
|
|
191
|
+
# base must have exactly one trailing slash
|
|
192
|
+
base = "/"
|
|
193
|
+
if frontend_path := config.frontend_path.strip("/"):
|
|
194
|
+
base += frontend_path + "/"
|
|
195
|
+
return templates.VITE_CONFIG.render(base=base)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def initialize_vite_config():
|
|
199
|
+
"""Render and write in .web the vite.config.js file using Reflex config."""
|
|
200
|
+
vite_config_file_path = get_web_dir() / constants.ReactRouter.VITE_CONFIG_FILE
|
|
201
|
+
vite_config_file_path.write_text(_compile_vite_config(get_config()))
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def initialize_bun_config():
|
|
205
|
+
"""Initialize the bun config file."""
|
|
206
|
+
bun_config_path = get_web_dir() / constants.Bun.CONFIG_PATH
|
|
207
|
+
|
|
208
|
+
if (custom_bunfig := Path(constants.Bun.CONFIG_PATH)).exists():
|
|
209
|
+
bunfig_content = custom_bunfig.read_text()
|
|
210
|
+
console.info(f"Copying custom bunfig.toml inside {get_web_dir()} folder")
|
|
211
|
+
else:
|
|
212
|
+
best_registry = get_npm_registry()
|
|
213
|
+
bunfig_content = constants.Bun.DEFAULT_CONFIG.format(registry=best_registry)
|
|
214
|
+
|
|
215
|
+
bun_config_path.write_text(bunfig_content)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def initialize_npmrc():
|
|
219
|
+
"""Initialize the .npmrc file."""
|
|
220
|
+
npmrc_path = get_web_dir() / constants.Node.CONFIG_PATH
|
|
221
|
+
|
|
222
|
+
if (custom_npmrc := Path(constants.Node.CONFIG_PATH)).exists():
|
|
223
|
+
npmrc_content = custom_npmrc.read_text()
|
|
224
|
+
console.info(f"Copying custom .npmrc inside {get_web_dir()} folder")
|
|
225
|
+
else:
|
|
226
|
+
best_registry = get_npm_registry()
|
|
227
|
+
npmrc_content = constants.Node.DEFAULT_CONFIG.format(registry=best_registry)
|
|
228
|
+
|
|
229
|
+
npmrc_path.write_text(npmrc_content)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def init_reflex_json(project_hash: int | None):
|
|
233
|
+
"""Write the hash of the Reflex project to a REFLEX_JSON.
|
|
234
|
+
|
|
235
|
+
Reuse the hash if one is already created, therefore do not
|
|
236
|
+
overwrite it every time we run the reflex init command
|
|
237
|
+
.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
project_hash: The app hash.
|
|
241
|
+
"""
|
|
242
|
+
if project_hash is not None:
|
|
243
|
+
console.debug(f"Project hash is already set to {project_hash}.")
|
|
244
|
+
else:
|
|
245
|
+
# Get a random project hash.
|
|
246
|
+
project_hash = random.getrandbits(128)
|
|
247
|
+
console.debug(f"Setting project hash to {project_hash}.")
|
|
248
|
+
|
|
249
|
+
# Write the hash and version to the reflex json file.
|
|
250
|
+
reflex_json = {
|
|
251
|
+
"version": constants.Reflex.VERSION,
|
|
252
|
+
"project_hash": project_hash,
|
|
253
|
+
}
|
|
254
|
+
path_ops.update_json_file(get_web_dir() / constants.Reflex.JSON, reflex_json)
|