reflex 0.7.3a2__py3-none-any.whl → 0.7.4a1__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/app.py +17 -1
- reflex/compiler/compiler.py +63 -8
- reflex/components/core/client_side_routing.py +3 -3
- reflex/components/core/cond.py +20 -12
- reflex/components/core/upload.py +1 -1
- reflex/components/dynamic.py +2 -4
- reflex/components/lucide/icon.py +20 -27
- reflex/components/plotly/plotly.py +9 -9
- reflex/components/recharts/recharts.py +2 -2
- reflex/components/sonner/toast.py +1 -1
- reflex/config.py +19 -22
- reflex/constants/__init__.py +1 -2
- reflex/constants/base.py +3 -0
- reflex/constants/installer.py +8 -105
- reflex/custom_components/custom_components.py +8 -3
- reflex/reflex.py +18 -4
- reflex/state.py +9 -1
- reflex/testing.py +7 -1
- reflex/utils/build.py +3 -4
- reflex/utils/exec.py +146 -71
- reflex/utils/path_ops.py +15 -25
- reflex/utils/prerequisites.py +133 -182
- reflex/utils/processes.py +24 -21
- reflex/utils/registry.py +5 -5
- reflex/vars/base.py +2 -3
- reflex/vars/sequence.py +84 -0
- {reflex-0.7.3a2.dist-info → reflex-0.7.4a1.dist-info}/METADATA +3 -3
- {reflex-0.7.3a2.dist-info → reflex-0.7.4a1.dist-info}/RECORD +31 -32
- reflex/app_module_for_backend.py +0 -33
- {reflex-0.7.3a2.dist-info → reflex-0.7.4a1.dist-info}/WHEEL +0 -0
- {reflex-0.7.3a2.dist-info → reflex-0.7.4a1.dist-info}/entry_points.txt +0 -0
- {reflex-0.7.3a2.dist-info → reflex-0.7.4a1.dist-info}/licenses/LICENSE +0 -0
reflex/utils/path_ops.py
CHANGED
|
@@ -9,7 +9,6 @@ import shutil
|
|
|
9
9
|
import stat
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
from reflex import constants
|
|
13
12
|
from reflex.config import environment, get_config
|
|
14
13
|
|
|
15
14
|
# Shorthand for join.
|
|
@@ -43,13 +42,19 @@ def rm(path: str | Path):
|
|
|
43
42
|
path.unlink()
|
|
44
43
|
|
|
45
44
|
|
|
46
|
-
def cp(
|
|
45
|
+
def cp(
|
|
46
|
+
src: str | Path,
|
|
47
|
+
dest: str | Path,
|
|
48
|
+
overwrite: bool = True,
|
|
49
|
+
ignore: tuple[str, ...] | None = None,
|
|
50
|
+
) -> bool:
|
|
47
51
|
"""Copy a file or directory.
|
|
48
52
|
|
|
49
53
|
Args:
|
|
50
54
|
src: The path to the file or directory.
|
|
51
55
|
dest: The path to the destination.
|
|
52
56
|
overwrite: Whether to overwrite the destination.
|
|
57
|
+
ignore: Ignoring files and directories that match one of the glob-style patterns provided
|
|
53
58
|
|
|
54
59
|
Returns:
|
|
55
60
|
Whether the copy was successful.
|
|
@@ -61,7 +66,11 @@ def cp(src: str | Path, dest: str | Path, overwrite: bool = True) -> bool:
|
|
|
61
66
|
return False
|
|
62
67
|
if src.is_dir():
|
|
63
68
|
rm(dest)
|
|
64
|
-
shutil.copytree(
|
|
69
|
+
shutil.copytree(
|
|
70
|
+
src,
|
|
71
|
+
dest,
|
|
72
|
+
ignore=shutil.ignore_patterns(*ignore) if ignore is not None else ignore,
|
|
73
|
+
)
|
|
65
74
|
else:
|
|
66
75
|
shutil.copyfile(src, dest)
|
|
67
76
|
return True
|
|
@@ -146,15 +155,6 @@ def which(program: str | Path) -> Path | None:
|
|
|
146
155
|
return Path(which_result) if which_result else None
|
|
147
156
|
|
|
148
157
|
|
|
149
|
-
def use_system_node() -> bool:
|
|
150
|
-
"""Check if the system node should be used.
|
|
151
|
-
|
|
152
|
-
Returns:
|
|
153
|
-
Whether the system node should be used.
|
|
154
|
-
"""
|
|
155
|
-
return environment.REFLEX_USE_SYSTEM_NODE.get()
|
|
156
|
-
|
|
157
|
-
|
|
158
158
|
def use_system_bun() -> bool:
|
|
159
159
|
"""Check if the system bun should be used.
|
|
160
160
|
|
|
@@ -170,11 +170,7 @@ def get_node_bin_path() -> Path | None:
|
|
|
170
170
|
Returns:
|
|
171
171
|
The path to the node bin folder.
|
|
172
172
|
"""
|
|
173
|
-
bin_path
|
|
174
|
-
if not bin_path.exists():
|
|
175
|
-
path = which("node")
|
|
176
|
-
return path.parent.absolute() if path else None
|
|
177
|
-
return bin_path.absolute()
|
|
173
|
+
return bin_path.parent.absolute() if (bin_path := get_node_path()) else None
|
|
178
174
|
|
|
179
175
|
|
|
180
176
|
def get_node_path() -> Path | None:
|
|
@@ -183,10 +179,7 @@ def get_node_path() -> Path | None:
|
|
|
183
179
|
Returns:
|
|
184
180
|
The path to the node binary file.
|
|
185
181
|
"""
|
|
186
|
-
|
|
187
|
-
if use_system_node() or not node_path.exists():
|
|
188
|
-
node_path = which("node")
|
|
189
|
-
return node_path
|
|
182
|
+
return which("node")
|
|
190
183
|
|
|
191
184
|
|
|
192
185
|
def get_npm_path() -> Path | None:
|
|
@@ -195,10 +188,7 @@ def get_npm_path() -> Path | None:
|
|
|
195
188
|
Returns:
|
|
196
189
|
The path to the npm binary file.
|
|
197
190
|
"""
|
|
198
|
-
npm_path
|
|
199
|
-
if use_system_node() or not npm_path.exists():
|
|
200
|
-
npm_path = which("npm")
|
|
201
|
-
return npm_path.absolute() if npm_path else None
|
|
191
|
+
return npm_path.absolute() if (npm_path := which("npm")) else None
|
|
202
192
|
|
|
203
193
|
|
|
204
194
|
def get_bun_path() -> Path | None:
|
reflex/utils/prerequisites.py
CHANGED
|
@@ -14,7 +14,6 @@ import platform
|
|
|
14
14
|
import random
|
|
15
15
|
import re
|
|
16
16
|
import shutil
|
|
17
|
-
import stat
|
|
18
17
|
import sys
|
|
19
18
|
import tempfile
|
|
20
19
|
import time
|
|
@@ -23,7 +22,7 @@ import zipfile
|
|
|
23
22
|
from datetime import datetime
|
|
24
23
|
from pathlib import Path
|
|
25
24
|
from types import ModuleType
|
|
26
|
-
from typing import Callable, NamedTuple
|
|
25
|
+
from typing import Callable, NamedTuple, Sequence
|
|
27
26
|
from urllib.parse import urlparse
|
|
28
27
|
|
|
29
28
|
import httpx
|
|
@@ -43,13 +42,11 @@ from reflex.utils.exceptions import (
|
|
|
43
42
|
SystemPackageMissingError,
|
|
44
43
|
)
|
|
45
44
|
from reflex.utils.format import format_library_name
|
|
46
|
-
from reflex.utils.registry import
|
|
45
|
+
from reflex.utils.registry import get_npm_registry
|
|
47
46
|
|
|
48
47
|
if typing.TYPE_CHECKING:
|
|
49
48
|
from reflex.app import App
|
|
50
49
|
|
|
51
|
-
CURRENTLY_INSTALLING_NODE = False
|
|
52
|
-
|
|
53
50
|
|
|
54
51
|
class AppInfo(NamedTuple):
|
|
55
52
|
"""A tuple containing the app instance and module."""
|
|
@@ -191,24 +188,6 @@ def get_node_version() -> version.Version | None:
|
|
|
191
188
|
return None
|
|
192
189
|
|
|
193
190
|
|
|
194
|
-
def get_fnm_version() -> version.Version | None:
|
|
195
|
-
"""Get the version of fnm.
|
|
196
|
-
|
|
197
|
-
Returns:
|
|
198
|
-
The version of FNM.
|
|
199
|
-
"""
|
|
200
|
-
try:
|
|
201
|
-
result = processes.new_process([constants.Fnm.EXE, "--version"], run=True)
|
|
202
|
-
return version.parse(result.stdout.split(" ")[1]) # pyright: ignore [reportOptionalMemberAccess, reportAttributeAccessIssue]
|
|
203
|
-
except (FileNotFoundError, TypeError):
|
|
204
|
-
return None
|
|
205
|
-
except version.InvalidVersion as e:
|
|
206
|
-
console.warn(
|
|
207
|
-
f"The detected fnm version ({e.args[0]}) is not valid. Defaulting to None."
|
|
208
|
-
)
|
|
209
|
-
return None
|
|
210
|
-
|
|
211
|
-
|
|
212
191
|
def get_bun_version() -> version.Version | None:
|
|
213
192
|
"""Get the version of bun.
|
|
214
193
|
|
|
@@ -231,42 +210,107 @@ def get_bun_version() -> version.Version | None:
|
|
|
231
210
|
return None
|
|
232
211
|
|
|
233
212
|
|
|
234
|
-
def
|
|
235
|
-
"""
|
|
236
|
-
|
|
213
|
+
def prefer_npm_over_bun() -> bool:
|
|
214
|
+
"""Check if npm should be preferred over bun.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
If npm should be preferred over bun.
|
|
218
|
+
"""
|
|
219
|
+
return npm_escape_hatch() or (
|
|
220
|
+
constants.IS_WINDOWS and windows_check_onedrive_in_path()
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def get_nodejs_compatible_package_managers(
|
|
225
|
+
raise_on_none: bool = True,
|
|
226
|
+
) -> Sequence[str]:
|
|
227
|
+
"""Get the package manager executable for installation. Typically, bun is used for installation.
|
|
237
228
|
|
|
238
229
|
Args:
|
|
239
|
-
|
|
230
|
+
raise_on_none: Whether to raise an error if the package manager is not found.
|
|
240
231
|
|
|
241
232
|
Returns:
|
|
242
233
|
The path to the package manager.
|
|
234
|
+
|
|
235
|
+
Raises:
|
|
236
|
+
FileNotFoundError: If the package manager is not found and raise_on_none is True.
|
|
237
|
+
"""
|
|
238
|
+
bun_package_manager = (
|
|
239
|
+
str(bun_path) if (bun_path := path_ops.get_bun_path()) else None
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
npm_package_manager = (
|
|
243
|
+
str(npm_path) if (npm_path := path_ops.get_npm_path()) else None
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
if prefer_npm_over_bun():
|
|
247
|
+
package_managers = [npm_package_manager, bun_package_manager]
|
|
248
|
+
else:
|
|
249
|
+
package_managers = [bun_package_manager, npm_package_manager]
|
|
250
|
+
|
|
251
|
+
package_managers = list(filter(None, package_managers))
|
|
252
|
+
|
|
253
|
+
if not package_managers and not raise_on_none:
|
|
254
|
+
raise FileNotFoundError(
|
|
255
|
+
"Bun or npm not found. You might need to rerun `reflex init` or install either."
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
return package_managers
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def is_outdated_nodejs_installed():
|
|
262
|
+
"""Check if the installed Node.js version is outdated.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
If the installed Node.js version is outdated.
|
|
243
266
|
"""
|
|
244
|
-
|
|
245
|
-
|
|
267
|
+
current_version = get_node_version()
|
|
268
|
+
if current_version is not None and current_version < version.parse(
|
|
269
|
+
constants.Node.MIN_VERSION
|
|
246
270
|
):
|
|
247
|
-
|
|
248
|
-
|
|
271
|
+
console.warn(
|
|
272
|
+
f"Your version ({current_version}) of Node.js is out of date. Upgrade to {constants.Node.MIN_VERSION} or higher."
|
|
273
|
+
)
|
|
274
|
+
return True
|
|
275
|
+
return False
|
|
249
276
|
|
|
250
277
|
|
|
251
|
-
def
|
|
252
|
-
"""Get the package
|
|
253
|
-
|
|
278
|
+
def get_js_package_executor(raise_on_none: bool = False) -> Sequence[Sequence[str]]:
|
|
279
|
+
"""Get the paths to package managers for running commands. Ordered by preference.
|
|
280
|
+
This is currently identical to get_install_package_managers, but may change in the future.
|
|
254
281
|
|
|
255
282
|
Args:
|
|
256
|
-
|
|
283
|
+
raise_on_none: Whether to raise an error if no package managers is not found.
|
|
257
284
|
|
|
258
285
|
Returns:
|
|
259
|
-
The
|
|
286
|
+
The paths to the package managers as a list of lists, where each list is the command to run and its arguments.
|
|
260
287
|
|
|
261
288
|
Raises:
|
|
262
|
-
FileNotFoundError: If
|
|
289
|
+
FileNotFoundError: If no package managers are found and raise_on_none is True.
|
|
263
290
|
"""
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
291
|
+
bun_package_manager = (
|
|
292
|
+
[str(bun_path)] + (["--bun"] if is_outdated_nodejs_installed() else [])
|
|
293
|
+
if (bun_path := path_ops.get_bun_path())
|
|
294
|
+
else None
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
npm_package_manager = (
|
|
298
|
+
[str(npm_path)] if (npm_path := path_ops.get_npm_path()) else None
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
if prefer_npm_over_bun():
|
|
302
|
+
package_managers = [npm_package_manager, bun_package_manager]
|
|
303
|
+
else:
|
|
304
|
+
package_managers = [bun_package_manager, npm_package_manager]
|
|
305
|
+
|
|
306
|
+
package_managers = list(filter(None, package_managers))
|
|
307
|
+
|
|
308
|
+
if not package_managers and raise_on_none:
|
|
309
|
+
raise FileNotFoundError(
|
|
310
|
+
"Bun or npm not found. You might need to rerun `reflex init` or install either."
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
return package_managers
|
|
270
314
|
|
|
271
315
|
|
|
272
316
|
def windows_check_onedrive_in_path() -> bool:
|
|
@@ -278,8 +322,8 @@ def windows_check_onedrive_in_path() -> bool:
|
|
|
278
322
|
return "onedrive" in str(Path.cwd()).lower()
|
|
279
323
|
|
|
280
324
|
|
|
281
|
-
def
|
|
282
|
-
"""
|
|
325
|
+
def npm_escape_hatch() -> bool:
|
|
326
|
+
"""If the user sets REFLEX_USE_NPM, prefer npm over bun.
|
|
283
327
|
|
|
284
328
|
Returns:
|
|
285
329
|
If the user has set REFLEX_USE_NPM.
|
|
@@ -368,6 +412,15 @@ def get_and_validate_app(reload: bool = False) -> AppInfo:
|
|
|
368
412
|
return AppInfo(app=app, module=app_module)
|
|
369
413
|
|
|
370
414
|
|
|
415
|
+
def validate_app(reload: bool = False) -> None:
|
|
416
|
+
"""Validate the app instance based on the default config.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
reload: Re-import the app module from disk
|
|
420
|
+
"""
|
|
421
|
+
get_and_validate_app(reload=reload)
|
|
422
|
+
|
|
423
|
+
|
|
371
424
|
def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
|
|
372
425
|
"""Get the app module based on the default config after first compiling it.
|
|
373
426
|
|
|
@@ -386,6 +439,16 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
|
|
|
386
439
|
return app_module
|
|
387
440
|
|
|
388
441
|
|
|
442
|
+
def compile_app(reload: bool = False, export: bool = False) -> None:
|
|
443
|
+
"""Compile the app module based on the default config.
|
|
444
|
+
|
|
445
|
+
Args:
|
|
446
|
+
reload: Re-import the app module from disk
|
|
447
|
+
export: Compile the app for export
|
|
448
|
+
"""
|
|
449
|
+
get_compiled_app(reload=reload, export=export)
|
|
450
|
+
|
|
451
|
+
|
|
389
452
|
def get_redis() -> Redis | None:
|
|
390
453
|
"""Get the asynchronous redis client.
|
|
391
454
|
|
|
@@ -862,7 +925,7 @@ def initialize_bun_config():
|
|
|
862
925
|
bunfig_content = custom_bunfig.read_text()
|
|
863
926
|
console.info(f"Copying custom bunfig.toml inside {get_web_dir()} folder")
|
|
864
927
|
else:
|
|
865
|
-
best_registry =
|
|
928
|
+
best_registry = get_npm_registry()
|
|
866
929
|
bunfig_content = constants.Bun.DEFAULT_CONFIG.format(registry=best_registry)
|
|
867
930
|
|
|
868
931
|
bun_config_path.write_text(bunfig_content)
|
|
@@ -970,92 +1033,6 @@ def download_and_run(url: str, *args, show_status: bool = False, **env):
|
|
|
970
1033
|
show(f"Installing {url}", process)
|
|
971
1034
|
|
|
972
1035
|
|
|
973
|
-
def download_and_extract_fnm_zip():
|
|
974
|
-
"""Download and run a script.
|
|
975
|
-
|
|
976
|
-
Raises:
|
|
977
|
-
Exit: If an error occurs while downloading or extracting the FNM zip.
|
|
978
|
-
"""
|
|
979
|
-
# Download the zip file
|
|
980
|
-
url = constants.Fnm.INSTALL_URL
|
|
981
|
-
console.debug(f"Downloading {url}")
|
|
982
|
-
fnm_zip_file: Path = constants.Fnm.DIR / f"{constants.Fnm.FILENAME}.zip"
|
|
983
|
-
# Function to download and extract the FNM zip release.
|
|
984
|
-
try:
|
|
985
|
-
# Download the FNM zip release.
|
|
986
|
-
# TODO: show progress to improve UX
|
|
987
|
-
response = net.get(url, follow_redirects=True)
|
|
988
|
-
response.raise_for_status()
|
|
989
|
-
with fnm_zip_file.open("wb") as output_file:
|
|
990
|
-
for chunk in response.iter_bytes():
|
|
991
|
-
output_file.write(chunk)
|
|
992
|
-
|
|
993
|
-
# Extract the downloaded zip file.
|
|
994
|
-
with zipfile.ZipFile(fnm_zip_file, "r") as zip_ref:
|
|
995
|
-
zip_ref.extractall(constants.Fnm.DIR)
|
|
996
|
-
|
|
997
|
-
console.debug("FNM package downloaded and extracted successfully.")
|
|
998
|
-
except Exception as e:
|
|
999
|
-
console.error(f"An error occurred while downloading fnm package: {e}")
|
|
1000
|
-
raise typer.Exit(1) from e
|
|
1001
|
-
finally:
|
|
1002
|
-
# Clean up the downloaded zip file.
|
|
1003
|
-
path_ops.rm(fnm_zip_file)
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
def install_node():
|
|
1007
|
-
"""Install fnm and nodejs for use by Reflex.
|
|
1008
|
-
Independent of any existing system installations.
|
|
1009
|
-
"""
|
|
1010
|
-
if not constants.Fnm.FILENAME:
|
|
1011
|
-
# fnm only support Linux, macOS and Windows distros.
|
|
1012
|
-
console.debug("")
|
|
1013
|
-
return
|
|
1014
|
-
|
|
1015
|
-
# Skip installation if check_node_version() checks out
|
|
1016
|
-
if check_node_version():
|
|
1017
|
-
console.debug("Skipping node installation as it is already installed.")
|
|
1018
|
-
return
|
|
1019
|
-
|
|
1020
|
-
path_ops.mkdir(constants.Fnm.DIR)
|
|
1021
|
-
if not constants.Fnm.EXE.exists():
|
|
1022
|
-
download_and_extract_fnm_zip()
|
|
1023
|
-
|
|
1024
|
-
if constants.IS_WINDOWS:
|
|
1025
|
-
# Install node
|
|
1026
|
-
fnm_exe = Path(constants.Fnm.EXE).resolve()
|
|
1027
|
-
fnm_dir = Path(constants.Fnm.DIR).resolve()
|
|
1028
|
-
process = processes.new_process(
|
|
1029
|
-
[
|
|
1030
|
-
"powershell",
|
|
1031
|
-
"-Command",
|
|
1032
|
-
f'& "{fnm_exe}" install {constants.Node.VERSION} --fnm-dir "{fnm_dir}"',
|
|
1033
|
-
],
|
|
1034
|
-
)
|
|
1035
|
-
else: # All other platforms (Linux, MacOS).
|
|
1036
|
-
# Add execute permissions to fnm executable.
|
|
1037
|
-
constants.Fnm.EXE.chmod(stat.S_IXUSR)
|
|
1038
|
-
# Install node.
|
|
1039
|
-
# Specify arm64 arch explicitly for M1s and M2s.
|
|
1040
|
-
architecture_arg = (
|
|
1041
|
-
["--arch=arm64"]
|
|
1042
|
-
if platform.system() == "Darwin" and platform.machine() == "arm64"
|
|
1043
|
-
else []
|
|
1044
|
-
)
|
|
1045
|
-
|
|
1046
|
-
process = processes.new_process(
|
|
1047
|
-
[
|
|
1048
|
-
constants.Fnm.EXE,
|
|
1049
|
-
"install",
|
|
1050
|
-
*architecture_arg,
|
|
1051
|
-
constants.Node.VERSION,
|
|
1052
|
-
"--fnm-dir",
|
|
1053
|
-
constants.Fnm.DIR,
|
|
1054
|
-
],
|
|
1055
|
-
)
|
|
1056
|
-
processes.show_status("Installing node", process)
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
1036
|
def install_bun():
|
|
1060
1037
|
"""Install bun onto the user's system.
|
|
1061
1038
|
|
|
@@ -1069,7 +1046,9 @@ def install_bun():
|
|
|
1069
1046
|
)
|
|
1070
1047
|
|
|
1071
1048
|
# Skip if bun is already installed.
|
|
1072
|
-
if get_bun_version()
|
|
1049
|
+
if (current_version := get_bun_version()) and current_version >= version.parse(
|
|
1050
|
+
constants.Bun.MIN_VERSION
|
|
1051
|
+
):
|
|
1073
1052
|
console.debug("Skipping bun installation as it is already installed.")
|
|
1074
1053
|
return
|
|
1075
1054
|
|
|
@@ -1157,38 +1136,19 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
1157
1136
|
packages: A list of package names to be installed.
|
|
1158
1137
|
config: The config object.
|
|
1159
1138
|
|
|
1160
|
-
Raises:
|
|
1161
|
-
FileNotFoundError: If the package manager is not found.
|
|
1162
|
-
|
|
1163
1139
|
Example:
|
|
1164
1140
|
>>> install_frontend_packages(["react", "react-dom"], get_config())
|
|
1165
1141
|
"""
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
get_package_manager(on_failure_return_none=True)
|
|
1169
|
-
if (
|
|
1170
|
-
not constants.IS_WINDOWS
|
|
1171
|
-
or (constants.IS_WINDOWS and not windows_check_onedrive_in_path())
|
|
1172
|
-
)
|
|
1173
|
-
else None
|
|
1174
|
-
)
|
|
1175
|
-
|
|
1176
|
-
install_package_manager = (
|
|
1177
|
-
get_install_package_manager(on_failure_return_none=True) or fallback_command
|
|
1142
|
+
install_package_managers = get_nodejs_compatible_package_managers(
|
|
1143
|
+
raise_on_none=True
|
|
1178
1144
|
)
|
|
1179
1145
|
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
"Could not find a package manager to install frontend packages. You may need to run `reflex init`."
|
|
1183
|
-
)
|
|
1184
|
-
|
|
1185
|
-
fallback_command = (
|
|
1186
|
-
fallback_command if fallback_command is not install_package_manager else None
|
|
1187
|
-
)
|
|
1146
|
+
primary_package_manager = install_package_managers[0]
|
|
1147
|
+
fallbacks = install_package_managers[1:]
|
|
1188
1148
|
|
|
1189
|
-
processes.
|
|
1190
|
-
[
|
|
1191
|
-
|
|
1149
|
+
processes.run_process_with_fallbacks(
|
|
1150
|
+
[primary_package_manager, "install", "--legacy-peer-deps"],
|
|
1151
|
+
fallbacks=fallbacks,
|
|
1192
1152
|
analytics_enabled=True,
|
|
1193
1153
|
show_status_message="Installing base frontend packages",
|
|
1194
1154
|
cwd=get_web_dir(),
|
|
@@ -1196,16 +1156,16 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
1196
1156
|
)
|
|
1197
1157
|
|
|
1198
1158
|
if config.tailwind is not None:
|
|
1199
|
-
processes.
|
|
1159
|
+
processes.run_process_with_fallbacks(
|
|
1200
1160
|
[
|
|
1201
|
-
|
|
1161
|
+
primary_package_manager,
|
|
1202
1162
|
"add",
|
|
1203
1163
|
"--legacy-peer-deps",
|
|
1204
1164
|
"-d",
|
|
1205
1165
|
constants.Tailwind.VERSION,
|
|
1206
1166
|
*((config.tailwind or {}).get("plugins", [])),
|
|
1207
1167
|
],
|
|
1208
|
-
|
|
1168
|
+
fallbacks=fallbacks,
|
|
1209
1169
|
analytics_enabled=True,
|
|
1210
1170
|
show_status_message="Installing tailwind",
|
|
1211
1171
|
cwd=get_web_dir(),
|
|
@@ -1214,9 +1174,9 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
1214
1174
|
|
|
1215
1175
|
# Install custom packages defined in frontend_packages
|
|
1216
1176
|
if len(packages) > 0:
|
|
1217
|
-
processes.
|
|
1218
|
-
[
|
|
1219
|
-
|
|
1177
|
+
processes.run_process_with_fallbacks(
|
|
1178
|
+
[primary_package_manager, "add", "--legacy-peer-deps", *packages],
|
|
1179
|
+
fallbacks=fallbacks,
|
|
1220
1180
|
analytics_enabled=True,
|
|
1221
1181
|
show_status_message="Installing frontend packages from config and components",
|
|
1222
1182
|
cwd=get_web_dir(),
|
|
@@ -1338,24 +1298,19 @@ def validate_frontend_dependencies(init: bool = True):
|
|
|
1338
1298
|
Exit: If the package manager is invalid.
|
|
1339
1299
|
"""
|
|
1340
1300
|
if not init:
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
console.error(
|
|
1346
|
-
"Could not find NPM package manager. Make sure you have node installed."
|
|
1347
|
-
)
|
|
1348
|
-
raise typer.Exit(1)
|
|
1301
|
+
try:
|
|
1302
|
+
get_js_package_executor(raise_on_none=True)
|
|
1303
|
+
except FileNotFoundError as e:
|
|
1304
|
+
raise typer.Exit(1) from e
|
|
1349
1305
|
|
|
1306
|
+
if prefer_npm_over_bun():
|
|
1350
1307
|
if not check_node_version():
|
|
1351
1308
|
node_version = get_node_version()
|
|
1352
1309
|
console.error(
|
|
1353
1310
|
f"Reflex requires node version {constants.Node.MIN_VERSION} or higher to run, but the detected version is {node_version}",
|
|
1354
1311
|
)
|
|
1355
1312
|
raise typer.Exit(1)
|
|
1356
|
-
|
|
1357
|
-
if init:
|
|
1358
|
-
# we only need bun for package install on `reflex init`.
|
|
1313
|
+
else:
|
|
1359
1314
|
validate_bun()
|
|
1360
1315
|
|
|
1361
1316
|
|
|
@@ -1400,12 +1355,8 @@ def initialize_frontend_dependencies():
|
|
|
1400
1355
|
"""Initialize all the frontend dependencies."""
|
|
1401
1356
|
# validate dependencies before install
|
|
1402
1357
|
validate_frontend_dependencies()
|
|
1403
|
-
# Avoid warning about Node installation while we're trying to install it.
|
|
1404
|
-
global CURRENTLY_INSTALLING_NODE
|
|
1405
|
-
CURRENTLY_INSTALLING_NODE = True
|
|
1406
1358
|
# Install the frontend dependencies.
|
|
1407
|
-
processes.run_concurrently(
|
|
1408
|
-
CURRENTLY_INSTALLING_NODE = False
|
|
1359
|
+
processes.run_concurrently(install_bun)
|
|
1409
1360
|
# Set up the web directory.
|
|
1410
1361
|
initialize_web_directory()
|
|
1411
1362
|
|
|
@@ -1610,7 +1561,7 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
|
|
|
1610
1561
|
console.error(f"Failed to unzip the template: {uze}")
|
|
1611
1562
|
raise typer.Exit(1) from uze
|
|
1612
1563
|
|
|
1613
|
-
if len(subdirs :=
|
|
1564
|
+
if len(subdirs := list(unzip_dir.iterdir())) != 1:
|
|
1614
1565
|
console.error(f"Expected one directory in the zip, found {subdirs}")
|
|
1615
1566
|
raise typer.Exit(1)
|
|
1616
1567
|
|
reflex/utils/processes.py
CHANGED
|
@@ -10,7 +10,7 @@ import signal
|
|
|
10
10
|
import subprocess
|
|
11
11
|
from concurrent import futures
|
|
12
12
|
from pathlib import Path
|
|
13
|
-
from typing import Callable, Generator, Tuple
|
|
13
|
+
from typing import Any, Callable, Generator, Sequence, Tuple
|
|
14
14
|
|
|
15
15
|
import psutil
|
|
16
16
|
import typer
|
|
@@ -171,14 +171,9 @@ def new_process(
|
|
|
171
171
|
|
|
172
172
|
# Add node_bin_path to the PATH environment variable.
|
|
173
173
|
if not environment.REFLEX_BACKEND_ONLY.get():
|
|
174
|
-
node_bin_path =
|
|
175
|
-
if
|
|
176
|
-
|
|
177
|
-
"The path to the Node binary could not be found. Please ensure that Node is properly "
|
|
178
|
-
"installed and added to your system's PATH environment variable or try running "
|
|
179
|
-
"`reflex init` again."
|
|
180
|
-
)
|
|
181
|
-
path_env = os.pathsep.join([node_bin_path, path_env])
|
|
174
|
+
node_bin_path = path_ops.get_node_bin_path()
|
|
175
|
+
if node_bin_path:
|
|
176
|
+
path_env = os.pathsep.join([str(node_bin_path), path_env])
|
|
182
177
|
|
|
183
178
|
env: dict[str, str] = {
|
|
184
179
|
**os.environ,
|
|
@@ -202,7 +197,7 @@ def new_process(
|
|
|
202
197
|
|
|
203
198
|
@contextlib.contextmanager
|
|
204
199
|
def run_concurrently_context(
|
|
205
|
-
*fns: Callable |
|
|
200
|
+
*fns: Callable[..., Any] | tuple[Callable[..., Any], ...],
|
|
206
201
|
) -> Generator[list[futures.Future], None, None]:
|
|
207
202
|
"""Run functions concurrently in a thread pool.
|
|
208
203
|
|
|
@@ -218,14 +213,14 @@ def run_concurrently_context(
|
|
|
218
213
|
return
|
|
219
214
|
|
|
220
215
|
# Convert the functions to tuples.
|
|
221
|
-
fns =
|
|
216
|
+
fns = tuple(fn if isinstance(fn, tuple) else (fn,) for fn in fns)
|
|
222
217
|
|
|
223
218
|
# Run the functions concurrently.
|
|
224
219
|
executor = None
|
|
225
220
|
try:
|
|
226
221
|
executor = futures.ThreadPoolExecutor(max_workers=len(fns))
|
|
227
222
|
# Submit the tasks.
|
|
228
|
-
tasks = [executor.submit(*fn) for fn in fns]
|
|
223
|
+
tasks = [executor.submit(*fn) for fn in fns]
|
|
229
224
|
|
|
230
225
|
# Yield control back to the main thread while tasks are running.
|
|
231
226
|
yield tasks
|
|
@@ -380,11 +375,11 @@ def get_command_with_loglevel(command: list[str]) -> list[str]:
|
|
|
380
375
|
return command
|
|
381
376
|
|
|
382
377
|
|
|
383
|
-
def
|
|
378
|
+
def run_process_with_fallbacks(
|
|
384
379
|
args: list[str],
|
|
385
380
|
*,
|
|
386
381
|
show_status_message: str,
|
|
387
|
-
|
|
382
|
+
fallbacks: str | Sequence[str] | Sequence[Sequence[str]] | None = None,
|
|
388
383
|
analytics_enabled: bool = False,
|
|
389
384
|
**kwargs,
|
|
390
385
|
):
|
|
@@ -393,12 +388,12 @@ def run_process_with_fallback(
|
|
|
393
388
|
Args:
|
|
394
389
|
args: A string, or a sequence of program arguments.
|
|
395
390
|
show_status_message: The status message to be displayed in the console.
|
|
396
|
-
|
|
391
|
+
fallbacks: The fallback command to run if the initial command fails.
|
|
397
392
|
analytics_enabled: Whether analytics are enabled for this command.
|
|
398
393
|
kwargs: Kwargs to pass to new_process function.
|
|
399
394
|
"""
|
|
400
395
|
process = new_process(get_command_with_loglevel(args), **kwargs)
|
|
401
|
-
if
|
|
396
|
+
if not fallbacks:
|
|
402
397
|
# No fallback given, or this _is_ the fallback command.
|
|
403
398
|
show_status(
|
|
404
399
|
show_status_message,
|
|
@@ -408,16 +403,24 @@ def run_process_with_fallback(
|
|
|
408
403
|
else:
|
|
409
404
|
# Suppress errors for initial command, because we will try to fallback
|
|
410
405
|
show_status(show_status_message, process, suppress_errors=True)
|
|
406
|
+
|
|
407
|
+
current_fallback = fallbacks[0] if not isinstance(fallbacks, str) else fallbacks
|
|
408
|
+
next_fallbacks = fallbacks[1:] if not isinstance(fallbacks, str) else None
|
|
409
|
+
|
|
411
410
|
if process.returncode != 0:
|
|
412
411
|
# retry with fallback command.
|
|
413
|
-
|
|
412
|
+
fallback_with_args = (
|
|
413
|
+
[current_fallback, *args[1:]]
|
|
414
|
+
if isinstance(fallbacks, str)
|
|
415
|
+
else [*current_fallback, *args[1:]]
|
|
416
|
+
)
|
|
414
417
|
console.warn(
|
|
415
|
-
f"There was an error running command: {args}. Falling back to: {
|
|
418
|
+
f"There was an error running command: {args}. Falling back to: {fallback_with_args}."
|
|
416
419
|
)
|
|
417
|
-
|
|
418
|
-
|
|
420
|
+
run_process_with_fallbacks(
|
|
421
|
+
fallback_with_args,
|
|
419
422
|
show_status_message=show_status_message,
|
|
420
|
-
|
|
423
|
+
fallbacks=next_fallbacks,
|
|
421
424
|
analytics_enabled=analytics_enabled,
|
|
422
425
|
**kwargs,
|
|
423
426
|
)
|