Compare commits

...

2 Commits

Author SHA1 Message Date
Masen Furer
af7af376d0
[REF-1586] [WiP] Use bun as a package manager on windows
Although bun support for windows is currently extremely limited/broken,
eventually we want to migrate over to it for the installation speed gains over
npm.
2024-03-06 18:12:53 -08:00
Masen Furer
ef457afe11
[REF-2086] Avoid "Warning: The path to the Node binary could not be found."
When bootstrapping node itself, we do not need to warn the user that node
cannot be found -- it's obviously not found because we're installing it right
now. This warning leads some folks to believe that they need to install node
themselves (for example in a docker container), when in reality, reflex will
always try to install it's own known-good version of node first.

Also avoid a nasty traceback when Reflex attempts to run node or npm, but it's
not actually installed (instead, the warning will be displayed and it will say
"Invalid Command").

Move the recently added check for node before installing node above the
conditional so it can also skip node installation on Windows, which is
especially slow.
2024-03-06 17:50:36 -08:00
4 changed files with 56 additions and 30 deletions

View File

@ -26,6 +26,8 @@ class Ext(SimpleNamespace):
CSS = ".css"
# The extension for zip files.
ZIP = ".zip"
# The extension for executable files on Windows.
EXE = ".exe"
class CompileVars(SimpleNamespace):

View File

@ -27,6 +27,15 @@ def set_log_level(log_level: LogLevel):
_LOG_LEVEL = log_level
def is_debug() -> bool:
"""Check if the log level is debug.
Returns:
True if the log level is debug.
"""
return _LOG_LEVEL <= LogLevel.DEBUG
def print(msg: str, **kwargs):
"""Print a message.
@ -44,7 +53,7 @@ def debug(msg: str, **kwargs):
msg: The debug message.
kwargs: Keyword arguments to pass to the print function.
"""
if _LOG_LEVEL <= LogLevel.DEBUG:
if is_debug():
msg_ = f"[blue]Debug: {msg}[/blue]"
if progress := kwargs.pop("progress", None):
progress.console.print(msg_, **kwargs)

View File

@ -34,6 +34,8 @@ from reflex.compiler import templates
from reflex.config import Config, get_config
from reflex.utils import console, path_ops, processes
CURRENTLY_INSTALLING_NODE = False
def check_latest_package_version(package_name: str):
"""Check if the latest version of the package is installed.
@ -103,8 +105,11 @@ def get_node_version() -> version.Version | None:
Returns:
The version of node.
"""
node_path = path_ops.get_node_path()
if node_path is None:
return None
try:
result = processes.new_process([path_ops.get_node_path(), "-v"], run=True)
result = processes.new_process([node_path, "-v"], run=True)
# The output will be in the form "vX.Y.Z", but version.parse() can handle it
return version.parse(result.stdout) # type: ignore
except (FileNotFoundError, TypeError):
@ -140,16 +145,13 @@ def get_bun_version() -> version.Version | None:
def get_install_package_manager() -> str | None:
"""Get the package manager executable for installation.
Currently on unix systems, bun is used for installation only.
Currently, bun is used for installation only.
Returns:
The path to the package manager.
"""
# On Windows, we use npm instead of bun.
if constants.IS_WINDOWS:
return get_package_manager()
# On other platforms, we use bun.
return get_config().bun_path + constants.Ext.EXE
return get_config().bun_path
@ -606,6 +608,11 @@ def install_node():
console.debug("")
return
# Skip installation if check_node_version() checks out
if check_node_version():
console.debug("Skipping node installation as it is already installed.")
return
path_ops.mkdir(constants.Fnm.DIR)
if not os.path.exists(constants.Fnm.EXE):
download_and_extract_fnm_zip()
@ -622,10 +629,6 @@ def install_node():
],
)
else: # All other platforms (Linux, MacOS).
# TODO we can skip installation if check_node_version() checks out
if check_node_version():
console.debug("Skipping node installation as it is already installed.")
return
# Add execute permissions to fnm executable.
os.chmod(constants.Fnm.EXE, stat.S_IXUSR)
# Install node.
@ -655,11 +658,6 @@ def install_bun():
Raises:
FileNotFoundError: If required packages are not found.
"""
# Bun is not supported on Windows.
if constants.IS_WINDOWS:
console.debug("Skipping bun installation on Windows.")
return
# Skip if bun is already installed.
if os.path.exists(get_config().bun_path) and get_bun_version() == version.parse(
constants.Bun.VERSION
@ -668,16 +666,25 @@ def install_bun():
return
# if unzip is installed
unzip_path = path_ops.which("unzip")
if unzip_path is None:
raise FileNotFoundError("Reflex requires unzip to be installed.")
if constants.IS_WINDOWS:
processes.new_process(
["powershell", "-c", f"irm {constants.Bun.INSTALL_URL}.ps1|iex"],
env={"BUN_INSTALL": constants.Bun.ROOT_PATH},
shell=True,
run=True,
show_logs=console.is_debug(),
)
else:
unzip_path = path_ops.which("unzip")
if unzip_path is None:
raise FileNotFoundError("Reflex requires unzip to be installed.")
# Run the bun install script.
download_and_run(
constants.Bun.INSTALL_URL,
f"bun-v{constants.Bun.VERSION}",
BUN_INSTALL=constants.Bun.ROOT_PATH,
)
# Run the bun install script.
download_and_run(
constants.Bun.INSTALL_URL,
f"bun-v{constants.Bun.VERSION}",
BUN_INSTALL=constants.Bun.ROOT_PATH,
)
def _write_cached_procedure_file(payload: str, cache_file: str):
@ -874,9 +881,6 @@ def validate_frontend_dependencies(init=True):
)
raise typer.Exit(1)
if constants.IS_WINDOWS:
return
if init:
# we only need bun for package install on `reflex init`.
validate_bun()
@ -926,8 +930,12 @@ def initialize_frontend_dependencies():
"""Initialize all the frontend dependencies."""
# validate dependencies before install
validate_frontend_dependencies()
# Avoid warning about Node installation while we're trying to install it.
global CURRENTLY_INSTALLING_NODE
CURRENTLY_INSTALLING_NODE = True
# Install the frontend dependencies.
processes.run_concurrently(install_node, install_bun)
CURRENTLY_INSTALLING_NODE = False
# Set up the web directory.
initialize_web_directory()

View File

@ -135,13 +135,20 @@ def new_process(args, run: bool = False, show_logs: bool = False, **kwargs):
Returns:
Execute a child program in a new process.
Raises:
Exit: When attempting to run a command with a None value.
"""
node_bin_path = path_ops.get_node_bin_path()
if not node_bin_path:
if not node_bin_path and not prerequisites.CURRENTLY_INSTALLING_NODE:
console.warn(
"The path to the Node binary could not be found. Please ensure that Node is properly "
"installed and added to your system's PATH environment variable."
"installed and added to your system's PATH environment variable or try running "
"`reflex init` again."
)
if None in args:
console.error(f"Invalid command: {args}")
raise typer.Exit(1)
# Add the node bin path to the PATH environment variable.
env = {
**os.environ,