reflex 0.7.3a2__py3-none-any.whl → 0.7.4__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/web/components/reflex/radix_themes_color_mode_provider.js +9 -1
- reflex/app.py +21 -5
- reflex/base.py +3 -3
- reflex/compiler/compiler.py +68 -8
- reflex/components/component.py +6 -3
- 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 +23 -23
- 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 +22 -4
- reflex/state.py +9 -1
- reflex/testing.py +7 -1
- reflex/utils/build.py +3 -4
- reflex/utils/exec.py +156 -75
- reflex/utils/net.py +107 -18
- reflex/utils/path_ops.py +15 -25
- reflex/utils/prerequisites.py +225 -189
- reflex/utils/processes.py +70 -35
- reflex/utils/redir.py +3 -1
- reflex/utils/registry.py +16 -8
- reflex/vars/base.py +2 -38
- reflex/vars/datetime.py +10 -34
- reflex/vars/number.py +16 -112
- reflex/vars/sequence.py +99 -108
- {reflex-0.7.3a2.dist-info → reflex-0.7.4.dist-info}/METADATA +4 -2
- {reflex-0.7.3a2.dist-info → reflex-0.7.4.dist-info}/RECORD +38 -39
- reflex/app_module_for_backend.py +0 -33
- {reflex-0.7.3a2.dist-info → reflex-0.7.4.dist-info}/WHEEL +0 -0
- {reflex-0.7.3a2.dist-info → reflex-0.7.4.dist-info}/entry_points.txt +0 -0
- {reflex-0.7.3a2.dist-info → reflex-0.7.4.dist-info}/licenses/LICENSE +0 -0
reflex/utils/prerequisites.py
CHANGED
|
@@ -8,13 +8,13 @@ import functools
|
|
|
8
8
|
import importlib
|
|
9
9
|
import importlib.metadata
|
|
10
10
|
import importlib.util
|
|
11
|
+
import io
|
|
11
12
|
import json
|
|
12
13
|
import os
|
|
13
14
|
import platform
|
|
14
15
|
import random
|
|
15
16
|
import re
|
|
16
17
|
import shutil
|
|
17
|
-
import stat
|
|
18
18
|
import sys
|
|
19
19
|
import tempfile
|
|
20
20
|
import time
|
|
@@ -23,7 +23,7 @@ import zipfile
|
|
|
23
23
|
from datetime import datetime
|
|
24
24
|
from pathlib import Path
|
|
25
25
|
from types import ModuleType
|
|
26
|
-
from typing import Callable, NamedTuple
|
|
26
|
+
from typing import Callable, NamedTuple, Sequence
|
|
27
27
|
from urllib.parse import urlparse
|
|
28
28
|
|
|
29
29
|
import httpx
|
|
@@ -43,13 +43,11 @@ from reflex.utils.exceptions import (
|
|
|
43
43
|
SystemPackageMissingError,
|
|
44
44
|
)
|
|
45
45
|
from reflex.utils.format import format_library_name
|
|
46
|
-
from reflex.utils.registry import
|
|
46
|
+
from reflex.utils.registry import get_npm_registry
|
|
47
47
|
|
|
48
48
|
if typing.TYPE_CHECKING:
|
|
49
49
|
from reflex.app import App
|
|
50
50
|
|
|
51
|
-
CURRENTLY_INSTALLING_NODE = False
|
|
52
|
-
|
|
53
51
|
|
|
54
52
|
class AppInfo(NamedTuple):
|
|
55
53
|
"""A tuple containing the app instance and module."""
|
|
@@ -117,11 +115,13 @@ def check_latest_package_version(package_name: str):
|
|
|
117
115
|
if environment.REFLEX_CHECK_LATEST_VERSION.get() is False:
|
|
118
116
|
return
|
|
119
117
|
try:
|
|
118
|
+
console.debug(f"Checking for the latest version of {package_name}...")
|
|
120
119
|
# Get the latest version from PyPI
|
|
121
120
|
current_version = importlib.metadata.version(package_name)
|
|
122
121
|
url = f"https://pypi.org/pypi/{package_name}/json"
|
|
123
122
|
response = net.get(url)
|
|
124
123
|
latest_version = response.json()["info"]["version"]
|
|
124
|
+
console.debug(f"Latest version of {package_name}: {latest_version}")
|
|
125
125
|
if get_or_set_last_reflex_version_check_datetime():
|
|
126
126
|
# Versions were already checked and saved in reflex.json, no need to warn again
|
|
127
127
|
return
|
|
@@ -131,6 +131,7 @@ def check_latest_package_version(package_name: str):
|
|
|
131
131
|
f"Your version ({current_version}) of {package_name} is out of date. Upgrade to {latest_version} with 'pip install {package_name} --upgrade'"
|
|
132
132
|
)
|
|
133
133
|
except Exception:
|
|
134
|
+
console.debug(f"Failed to check for the latest version of {package_name}.")
|
|
134
135
|
pass
|
|
135
136
|
|
|
136
137
|
|
|
@@ -186,29 +187,11 @@ def get_node_version() -> version.Version | None:
|
|
|
186
187
|
try:
|
|
187
188
|
result = processes.new_process([node_path, "-v"], run=True)
|
|
188
189
|
# The output will be in the form "vX.Y.Z", but version.parse() can handle it
|
|
189
|
-
return version.parse(result.stdout)
|
|
190
|
+
return version.parse(result.stdout)
|
|
190
191
|
except (FileNotFoundError, TypeError):
|
|
191
192
|
return None
|
|
192
193
|
|
|
193
194
|
|
|
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
195
|
def get_bun_version() -> version.Version | None:
|
|
213
196
|
"""Get the version of bun.
|
|
214
197
|
|
|
@@ -221,7 +204,7 @@ def get_bun_version() -> version.Version | None:
|
|
|
221
204
|
try:
|
|
222
205
|
# Run the bun -v command and capture the output
|
|
223
206
|
result = processes.new_process([str(bun_path), "-v"], run=True)
|
|
224
|
-
return version.parse(str(result.stdout))
|
|
207
|
+
return version.parse(str(result.stdout))
|
|
225
208
|
except FileNotFoundError:
|
|
226
209
|
return None
|
|
227
210
|
except version.InvalidVersion as e:
|
|
@@ -231,42 +214,107 @@ def get_bun_version() -> version.Version | None:
|
|
|
231
214
|
return None
|
|
232
215
|
|
|
233
216
|
|
|
234
|
-
def
|
|
235
|
-
"""
|
|
236
|
-
|
|
217
|
+
def prefer_npm_over_bun() -> bool:
|
|
218
|
+
"""Check if npm should be preferred over bun.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
If npm should be preferred over bun.
|
|
222
|
+
"""
|
|
223
|
+
return npm_escape_hatch() or (
|
|
224
|
+
constants.IS_WINDOWS and windows_check_onedrive_in_path()
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def get_nodejs_compatible_package_managers(
|
|
229
|
+
raise_on_none: bool = True,
|
|
230
|
+
) -> Sequence[str]:
|
|
231
|
+
"""Get the package manager executable for installation. Typically, bun is used for installation.
|
|
237
232
|
|
|
238
233
|
Args:
|
|
239
|
-
|
|
234
|
+
raise_on_none: Whether to raise an error if the package manager is not found.
|
|
240
235
|
|
|
241
236
|
Returns:
|
|
242
237
|
The path to the package manager.
|
|
238
|
+
|
|
239
|
+
Raises:
|
|
240
|
+
FileNotFoundError: If the package manager is not found and raise_on_none is True.
|
|
241
|
+
"""
|
|
242
|
+
bun_package_manager = (
|
|
243
|
+
str(bun_path) if (bun_path := path_ops.get_bun_path()) else None
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
npm_package_manager = (
|
|
247
|
+
str(npm_path) if (npm_path := path_ops.get_npm_path()) else None
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
if prefer_npm_over_bun():
|
|
251
|
+
package_managers = [npm_package_manager, bun_package_manager]
|
|
252
|
+
else:
|
|
253
|
+
package_managers = [bun_package_manager, npm_package_manager]
|
|
254
|
+
|
|
255
|
+
package_managers = list(filter(None, package_managers))
|
|
256
|
+
|
|
257
|
+
if not package_managers and not raise_on_none:
|
|
258
|
+
raise FileNotFoundError(
|
|
259
|
+
"Bun or npm not found. You might need to rerun `reflex init` or install either."
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
return package_managers
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def is_outdated_nodejs_installed():
|
|
266
|
+
"""Check if the installed Node.js version is outdated.
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
If the installed Node.js version is outdated.
|
|
243
270
|
"""
|
|
244
|
-
|
|
245
|
-
|
|
271
|
+
current_version = get_node_version()
|
|
272
|
+
if current_version is not None and current_version < version.parse(
|
|
273
|
+
constants.Node.MIN_VERSION
|
|
246
274
|
):
|
|
247
|
-
|
|
248
|
-
|
|
275
|
+
console.warn(
|
|
276
|
+
f"Your version ({current_version}) of Node.js is out of date. Upgrade to {constants.Node.MIN_VERSION} or higher."
|
|
277
|
+
)
|
|
278
|
+
return True
|
|
279
|
+
return False
|
|
249
280
|
|
|
250
281
|
|
|
251
|
-
def
|
|
252
|
-
"""Get the package
|
|
253
|
-
|
|
282
|
+
def get_js_package_executor(raise_on_none: bool = False) -> Sequence[Sequence[str]]:
|
|
283
|
+
"""Get the paths to package managers for running commands. Ordered by preference.
|
|
284
|
+
This is currently identical to get_install_package_managers, but may change in the future.
|
|
254
285
|
|
|
255
286
|
Args:
|
|
256
|
-
|
|
287
|
+
raise_on_none: Whether to raise an error if no package managers is not found.
|
|
257
288
|
|
|
258
289
|
Returns:
|
|
259
|
-
The
|
|
290
|
+
The paths to the package managers as a list of lists, where each list is the command to run and its arguments.
|
|
260
291
|
|
|
261
292
|
Raises:
|
|
262
|
-
FileNotFoundError: If
|
|
293
|
+
FileNotFoundError: If no package managers are found and raise_on_none is True.
|
|
263
294
|
"""
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
295
|
+
bun_package_manager = (
|
|
296
|
+
[str(bun_path)] + (["--bun"] if is_outdated_nodejs_installed() else [])
|
|
297
|
+
if (bun_path := path_ops.get_bun_path())
|
|
298
|
+
else None
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
npm_package_manager = (
|
|
302
|
+
[str(npm_path)] if (npm_path := path_ops.get_npm_path()) else None
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
if prefer_npm_over_bun():
|
|
306
|
+
package_managers = [npm_package_manager, bun_package_manager]
|
|
307
|
+
else:
|
|
308
|
+
package_managers = [bun_package_manager, npm_package_manager]
|
|
309
|
+
|
|
310
|
+
package_managers = list(filter(None, package_managers))
|
|
311
|
+
|
|
312
|
+
if not package_managers and raise_on_none:
|
|
313
|
+
raise FileNotFoundError(
|
|
314
|
+
"Bun or npm not found. You might need to rerun `reflex init` or install either."
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
return package_managers
|
|
270
318
|
|
|
271
319
|
|
|
272
320
|
def windows_check_onedrive_in_path() -> bool:
|
|
@@ -278,8 +326,8 @@ def windows_check_onedrive_in_path() -> bool:
|
|
|
278
326
|
return "onedrive" in str(Path.cwd()).lower()
|
|
279
327
|
|
|
280
328
|
|
|
281
|
-
def
|
|
282
|
-
"""
|
|
329
|
+
def npm_escape_hatch() -> bool:
|
|
330
|
+
"""If the user sets REFLEX_USE_NPM, prefer npm over bun.
|
|
283
331
|
|
|
284
332
|
Returns:
|
|
285
333
|
If the user has set REFLEX_USE_NPM.
|
|
@@ -368,6 +416,15 @@ def get_and_validate_app(reload: bool = False) -> AppInfo:
|
|
|
368
416
|
return AppInfo(app=app, module=app_module)
|
|
369
417
|
|
|
370
418
|
|
|
419
|
+
def validate_app(reload: bool = False) -> None:
|
|
420
|
+
"""Validate the app instance based on the default config.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
reload: Re-import the app module from disk
|
|
424
|
+
"""
|
|
425
|
+
get_and_validate_app(reload=reload)
|
|
426
|
+
|
|
427
|
+
|
|
371
428
|
def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
|
|
372
429
|
"""Get the app module based on the default config after first compiling it.
|
|
373
430
|
|
|
@@ -386,6 +443,86 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType:
|
|
|
386
443
|
return app_module
|
|
387
444
|
|
|
388
445
|
|
|
446
|
+
def compile_app(reload: bool = False, export: bool = False) -> None:
|
|
447
|
+
"""Compile the app module based on the default config.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
reload: Re-import the app module from disk
|
|
451
|
+
export: Compile the app for export
|
|
452
|
+
"""
|
|
453
|
+
get_compiled_app(reload=reload, export=export)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _can_colorize() -> bool:
|
|
457
|
+
"""Check if the output can be colorized.
|
|
458
|
+
|
|
459
|
+
Copied from _colorize.can_colorize.
|
|
460
|
+
|
|
461
|
+
https://raw.githubusercontent.com/python/cpython/refs/heads/main/Lib/_colorize.py
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
If the output can be colorized
|
|
465
|
+
"""
|
|
466
|
+
file = sys.stdout
|
|
467
|
+
|
|
468
|
+
if not sys.flags.ignore_environment:
|
|
469
|
+
if os.environ.get("PYTHON_COLORS") == "0":
|
|
470
|
+
return False
|
|
471
|
+
if os.environ.get("PYTHON_COLORS") == "1":
|
|
472
|
+
return True
|
|
473
|
+
if os.environ.get("NO_COLOR"):
|
|
474
|
+
return False
|
|
475
|
+
if os.environ.get("FORCE_COLOR"):
|
|
476
|
+
return True
|
|
477
|
+
if os.environ.get("TERM") == "dumb":
|
|
478
|
+
return False
|
|
479
|
+
|
|
480
|
+
if not hasattr(file, "fileno"):
|
|
481
|
+
return False
|
|
482
|
+
|
|
483
|
+
if sys.platform == "win32":
|
|
484
|
+
try:
|
|
485
|
+
import nt
|
|
486
|
+
|
|
487
|
+
if not nt._supports_virtual_terminal():
|
|
488
|
+
return False
|
|
489
|
+
except (ImportError, AttributeError):
|
|
490
|
+
return False
|
|
491
|
+
|
|
492
|
+
try:
|
|
493
|
+
return os.isatty(file.fileno())
|
|
494
|
+
except io.UnsupportedOperation:
|
|
495
|
+
return file.isatty()
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def compile_or_validate_app(compile: bool = False) -> bool:
|
|
499
|
+
"""Compile or validate the app module based on the default config.
|
|
500
|
+
|
|
501
|
+
Args:
|
|
502
|
+
compile: Whether to compile the app.
|
|
503
|
+
|
|
504
|
+
Returns:
|
|
505
|
+
If the app is compiled successfully.
|
|
506
|
+
"""
|
|
507
|
+
try:
|
|
508
|
+
if compile:
|
|
509
|
+
compile_app()
|
|
510
|
+
else:
|
|
511
|
+
validate_app()
|
|
512
|
+
except Exception as e:
|
|
513
|
+
import traceback
|
|
514
|
+
|
|
515
|
+
sys_exception = sys.exception()
|
|
516
|
+
|
|
517
|
+
try:
|
|
518
|
+
colorize = _can_colorize()
|
|
519
|
+
traceback.print_exception(e, colorize=colorize) # pyright: ignore[reportCallIssue]
|
|
520
|
+
except Exception:
|
|
521
|
+
traceback.print_exception(sys_exception)
|
|
522
|
+
return False
|
|
523
|
+
return True
|
|
524
|
+
|
|
525
|
+
|
|
389
526
|
def get_redis() -> Redis | None:
|
|
390
527
|
"""Get the asynchronous redis client.
|
|
391
528
|
|
|
@@ -768,11 +905,12 @@ def initialize_app_directory(
|
|
|
768
905
|
|
|
769
906
|
console.debug(f"Using {template_name=} {template_dir=} {template_code_dir_name=}.")
|
|
770
907
|
|
|
771
|
-
# Remove
|
|
772
|
-
for
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
908
|
+
# Remove __pycache__ dirs in template directory and current directory.
|
|
909
|
+
for pycache_dir in [
|
|
910
|
+
*template_dir.glob("**/__pycache__"),
|
|
911
|
+
*Path.cwd().glob("**/__pycache__"),
|
|
912
|
+
]:
|
|
913
|
+
shutil.rmtree(pycache_dir, ignore_errors=True)
|
|
776
914
|
|
|
777
915
|
for file in template_dir.iterdir():
|
|
778
916
|
# Copy the file to current directory but keep the name the same.
|
|
@@ -816,16 +954,22 @@ def initialize_web_directory():
|
|
|
816
954
|
# Reuse the hash if one is already created, so we don't over-write it when running reflex init
|
|
817
955
|
project_hash = get_project_hash()
|
|
818
956
|
|
|
957
|
+
console.debug(f"Copying {constants.Templates.Dirs.WEB_TEMPLATE} to {get_web_dir()}")
|
|
819
958
|
path_ops.cp(constants.Templates.Dirs.WEB_TEMPLATE, str(get_web_dir()))
|
|
820
959
|
|
|
960
|
+
console.debug("Initializing the web directory.")
|
|
821
961
|
initialize_package_json()
|
|
822
962
|
|
|
963
|
+
console.debug("Initializing the bun config file.")
|
|
823
964
|
initialize_bun_config()
|
|
824
965
|
|
|
966
|
+
console.debug("Initializing the public directory.")
|
|
825
967
|
path_ops.mkdir(get_web_dir() / constants.Dirs.PUBLIC)
|
|
826
968
|
|
|
969
|
+
console.debug("Initializing the next.config.js file.")
|
|
827
970
|
update_next_config()
|
|
828
971
|
|
|
972
|
+
console.debug("Initializing the reflex.json file.")
|
|
829
973
|
# Initialize the reflex json file.
|
|
830
974
|
init_reflex_json(project_hash=project_hash)
|
|
831
975
|
|
|
@@ -862,7 +1006,7 @@ def initialize_bun_config():
|
|
|
862
1006
|
bunfig_content = custom_bunfig.read_text()
|
|
863
1007
|
console.info(f"Copying custom bunfig.toml inside {get_web_dir()} folder")
|
|
864
1008
|
else:
|
|
865
|
-
best_registry =
|
|
1009
|
+
best_registry = get_npm_registry()
|
|
866
1010
|
bunfig_content = constants.Bun.DEFAULT_CONFIG.format(registry=best_registry)
|
|
867
1011
|
|
|
868
1012
|
bun_config_path.write_text(bunfig_content)
|
|
@@ -970,92 +1114,6 @@ def download_and_run(url: str, *args, show_status: bool = False, **env):
|
|
|
970
1114
|
show(f"Installing {url}", process)
|
|
971
1115
|
|
|
972
1116
|
|
|
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
1117
|
def install_bun():
|
|
1060
1118
|
"""Install bun onto the user's system.
|
|
1061
1119
|
|
|
@@ -1069,7 +1127,9 @@ def install_bun():
|
|
|
1069
1127
|
)
|
|
1070
1128
|
|
|
1071
1129
|
# Skip if bun is already installed.
|
|
1072
|
-
if get_bun_version()
|
|
1130
|
+
if (current_version := get_bun_version()) and current_version >= version.parse(
|
|
1131
|
+
constants.Bun.MIN_VERSION
|
|
1132
|
+
):
|
|
1073
1133
|
console.debug("Skipping bun installation as it is already installed.")
|
|
1074
1134
|
return
|
|
1075
1135
|
|
|
@@ -1157,38 +1217,19 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
1157
1217
|
packages: A list of package names to be installed.
|
|
1158
1218
|
config: The config object.
|
|
1159
1219
|
|
|
1160
|
-
Raises:
|
|
1161
|
-
FileNotFoundError: If the package manager is not found.
|
|
1162
|
-
|
|
1163
1220
|
Example:
|
|
1164
1221
|
>>> install_frontend_packages(["react", "react-dom"], get_config())
|
|
1165
1222
|
"""
|
|
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
|
|
1223
|
+
install_package_managers = get_nodejs_compatible_package_managers(
|
|
1224
|
+
raise_on_none=True
|
|
1178
1225
|
)
|
|
1179
1226
|
|
|
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
|
-
)
|
|
1227
|
+
primary_package_manager = install_package_managers[0]
|
|
1228
|
+
fallbacks = install_package_managers[1:]
|
|
1188
1229
|
|
|
1189
|
-
processes.
|
|
1190
|
-
[
|
|
1191
|
-
|
|
1230
|
+
processes.run_process_with_fallbacks(
|
|
1231
|
+
[primary_package_manager, "install", "--legacy-peer-deps"],
|
|
1232
|
+
fallbacks=fallbacks,
|
|
1192
1233
|
analytics_enabled=True,
|
|
1193
1234
|
show_status_message="Installing base frontend packages",
|
|
1194
1235
|
cwd=get_web_dir(),
|
|
@@ -1196,16 +1237,16 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
1196
1237
|
)
|
|
1197
1238
|
|
|
1198
1239
|
if config.tailwind is not None:
|
|
1199
|
-
processes.
|
|
1240
|
+
processes.run_process_with_fallbacks(
|
|
1200
1241
|
[
|
|
1201
|
-
|
|
1242
|
+
primary_package_manager,
|
|
1202
1243
|
"add",
|
|
1203
1244
|
"--legacy-peer-deps",
|
|
1204
1245
|
"-d",
|
|
1205
1246
|
constants.Tailwind.VERSION,
|
|
1206
1247
|
*((config.tailwind or {}).get("plugins", [])),
|
|
1207
1248
|
],
|
|
1208
|
-
|
|
1249
|
+
fallbacks=fallbacks,
|
|
1209
1250
|
analytics_enabled=True,
|
|
1210
1251
|
show_status_message="Installing tailwind",
|
|
1211
1252
|
cwd=get_web_dir(),
|
|
@@ -1214,9 +1255,9 @@ def install_frontend_packages(packages: set[str], config: Config):
|
|
|
1214
1255
|
|
|
1215
1256
|
# Install custom packages defined in frontend_packages
|
|
1216
1257
|
if len(packages) > 0:
|
|
1217
|
-
processes.
|
|
1218
|
-
[
|
|
1219
|
-
|
|
1258
|
+
processes.run_process_with_fallbacks(
|
|
1259
|
+
[primary_package_manager, "add", "--legacy-peer-deps", *packages],
|
|
1260
|
+
fallbacks=fallbacks,
|
|
1220
1261
|
analytics_enabled=True,
|
|
1221
1262
|
show_status_message="Installing frontend packages from config and components",
|
|
1222
1263
|
cwd=get_web_dir(),
|
|
@@ -1338,24 +1379,19 @@ def validate_frontend_dependencies(init: bool = True):
|
|
|
1338
1379
|
Exit: If the package manager is invalid.
|
|
1339
1380
|
"""
|
|
1340
1381
|
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)
|
|
1382
|
+
try:
|
|
1383
|
+
get_js_package_executor(raise_on_none=True)
|
|
1384
|
+
except FileNotFoundError as e:
|
|
1385
|
+
raise typer.Exit(1) from e
|
|
1349
1386
|
|
|
1387
|
+
if prefer_npm_over_bun():
|
|
1350
1388
|
if not check_node_version():
|
|
1351
1389
|
node_version = get_node_version()
|
|
1352
1390
|
console.error(
|
|
1353
1391
|
f"Reflex requires node version {constants.Node.MIN_VERSION} or higher to run, but the detected version is {node_version}",
|
|
1354
1392
|
)
|
|
1355
1393
|
raise typer.Exit(1)
|
|
1356
|
-
|
|
1357
|
-
if init:
|
|
1358
|
-
# we only need bun for package install on `reflex init`.
|
|
1394
|
+
else:
|
|
1359
1395
|
validate_bun()
|
|
1360
1396
|
|
|
1361
1397
|
|
|
@@ -1366,6 +1402,7 @@ def ensure_reflex_installation_id() -> int | None:
|
|
|
1366
1402
|
Distinct id.
|
|
1367
1403
|
"""
|
|
1368
1404
|
try:
|
|
1405
|
+
console.debug("Ensuring reflex installation id.")
|
|
1369
1406
|
initialize_reflex_user_directory()
|
|
1370
1407
|
installation_id_file = environment.REFLEX_DIR.get() / "installation_id"
|
|
1371
1408
|
|
|
@@ -1392,6 +1429,7 @@ def ensure_reflex_installation_id() -> int | None:
|
|
|
1392
1429
|
|
|
1393
1430
|
def initialize_reflex_user_directory():
|
|
1394
1431
|
"""Initialize the reflex user directory."""
|
|
1432
|
+
console.debug(f"Creating {environment.REFLEX_DIR.get()}")
|
|
1395
1433
|
# Create the reflex directory.
|
|
1396
1434
|
path_ops.mkdir(environment.REFLEX_DIR.get())
|
|
1397
1435
|
|
|
@@ -1399,13 +1437,11 @@ def initialize_reflex_user_directory():
|
|
|
1399
1437
|
def initialize_frontend_dependencies():
|
|
1400
1438
|
"""Initialize all the frontend dependencies."""
|
|
1401
1439
|
# validate dependencies before install
|
|
1440
|
+
console.debug("Validating frontend dependencies.")
|
|
1402
1441
|
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
1442
|
# Install the frontend dependencies.
|
|
1407
|
-
|
|
1408
|
-
|
|
1443
|
+
console.debug("Installing or validating bun.")
|
|
1444
|
+
install_bun()
|
|
1409
1445
|
# Set up the web directory.
|
|
1410
1446
|
initialize_web_directory()
|
|
1411
1447
|
|
|
@@ -1610,7 +1646,7 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str
|
|
|
1610
1646
|
console.error(f"Failed to unzip the template: {uze}")
|
|
1611
1647
|
raise typer.Exit(1) from uze
|
|
1612
1648
|
|
|
1613
|
-
if len(subdirs :=
|
|
1649
|
+
if len(subdirs := list(unzip_dir.iterdir())) != 1:
|
|
1614
1650
|
console.error(f"Expected one directory in the zip, found {subdirs}")
|
|
1615
1651
|
raise typer.Exit(1)
|
|
1616
1652
|
|