Remove curl and parallelize node/bun install (#1458)
This commit is contained in:
parent
b67bba590d
commit
e1cb09e9d4
@ -45,42 +45,9 @@ MODULE_NAME = "reflex"
|
|||||||
# The current version of Reflex.
|
# The current version of Reflex.
|
||||||
VERSION = metadata.version(MODULE_NAME)
|
VERSION = metadata.version(MODULE_NAME)
|
||||||
|
|
||||||
# Project dependencies.
|
|
||||||
# The directory to store reflex dependencies.
|
|
||||||
REFLEX_DIR = os.path.expandvars("$HOME/.reflex")
|
|
||||||
|
|
||||||
# Bun config.
|
|
||||||
# The Bun version.
|
|
||||||
BUN_VERSION = "0.7.0"
|
|
||||||
# The directory to store the bun.
|
|
||||||
BUN_ROOT_PATH = f"{REFLEX_DIR}/.bun"
|
|
||||||
# The bun path.
|
|
||||||
BUN_PATH = f"{BUN_ROOT_PATH}/bin/bun"
|
|
||||||
# Command to install bun.
|
|
||||||
INSTALL_BUN = f"curl -fsSL https://bun.sh/install | env BUN_INSTALL={BUN_ROOT_PATH} bash -s -- bun-v{BUN_VERSION}"
|
|
||||||
|
|
||||||
# NVM / Node config.
|
|
||||||
# The Node version.
|
|
||||||
NODE_VERSION = "18.17.0"
|
|
||||||
# The minimum required node version.
|
|
||||||
MIN_NODE_VERSION = "16.8.0"
|
|
||||||
# The directory to store nvm.
|
|
||||||
NVM_ROOT_PATH = f"{REFLEX_DIR}/.nvm"
|
|
||||||
# The nvm path.
|
|
||||||
NVM_PATH = f"{NVM_ROOT_PATH}/nvm.sh"
|
|
||||||
# The node bin path.
|
|
||||||
NODE_BIN_PATH = f"{NVM_ROOT_PATH}/versions/node/v{NODE_VERSION}/bin"
|
|
||||||
# The default path where node is installed.
|
|
||||||
NODE_PATH = "node" if platform.system() == "Windows" else f"{NODE_BIN_PATH}/node"
|
|
||||||
# The default path where npm is installed.
|
|
||||||
NPM_PATH = "npm" if platform.system() == "Windows" else f"{NODE_BIN_PATH}/npm"
|
|
||||||
# Command to install nvm.
|
|
||||||
INSTALL_NVM = f"curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | env NVM_DIR={NVM_ROOT_PATH} bash"
|
|
||||||
# Command to install node.
|
|
||||||
INSTALL_NODE = f'bash -c "export NVM_DIR={NVM_ROOT_PATH} && . {NVM_ROOT_PATH}/nvm.sh && nvm install {NODE_VERSION}"'
|
|
||||||
|
|
||||||
|
|
||||||
# Files and directories used to init a new project.
|
# Files and directories used to init a new project.
|
||||||
|
# The directory to store reflex dependencies.
|
||||||
|
REFLEX_DIR = os.path.expandvars(os.path.join("$HOME", f".{MODULE_NAME}"))
|
||||||
# The root directory of the reflex library.
|
# The root directory of the reflex library.
|
||||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
# The name of the assets directory.
|
# The name of the assets directory.
|
||||||
@ -94,6 +61,42 @@ ASSETS_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, APP_ASSETS_DIR)
|
|||||||
# The jinja template directory.
|
# The jinja template directory.
|
||||||
JINJA_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, "jinja")
|
JINJA_TEMPLATE_DIR = os.path.join(TEMPLATE_DIR, "jinja")
|
||||||
|
|
||||||
|
# Bun config.
|
||||||
|
# The Bun version.
|
||||||
|
BUN_VERSION = "0.7.0"
|
||||||
|
# The directory to store the bun.
|
||||||
|
BUN_ROOT_PATH = os.path.join(REFLEX_DIR, ".bun")
|
||||||
|
# The bun path.
|
||||||
|
BUN_PATH = os.path.join(BUN_ROOT_PATH, "bin", "bun")
|
||||||
|
# URL to bun install script.
|
||||||
|
BUN_INSTALL_URL = "https://bun.sh/install"
|
||||||
|
|
||||||
|
# NVM / Node config.
|
||||||
|
# The NVM version.
|
||||||
|
NVM_VERSION = "0.39.1"
|
||||||
|
# The Node version.
|
||||||
|
NODE_VERSION = "18.17.0"
|
||||||
|
# The minimum required node version.
|
||||||
|
NODE_VERSION_MIN = "16.8.0"
|
||||||
|
# The directory to store nvm.
|
||||||
|
NVM_DIR = os.path.join(REFLEX_DIR, ".nvm")
|
||||||
|
# The nvm path.
|
||||||
|
NVM_PATH = os.path.join(NVM_DIR, "nvm.sh")
|
||||||
|
# The node bin path.
|
||||||
|
NODE_BIN_PATH = os.path.join(NVM_DIR, "versions", "node", f"v{NODE_VERSION}", "bin")
|
||||||
|
# The default path where node is installed.
|
||||||
|
NODE_PATH = (
|
||||||
|
"node" if platform.system() == "Windows" else os.path.join(NODE_BIN_PATH, "node")
|
||||||
|
)
|
||||||
|
# The default path where npm is installed.
|
||||||
|
NPM_PATH = (
|
||||||
|
"npm" if platform.system() == "Windows" else os.path.join(NODE_BIN_PATH, "npm")
|
||||||
|
)
|
||||||
|
# The URL to the nvm install script.
|
||||||
|
NVM_INSTALL_URL = (
|
||||||
|
f"https://raw.githubusercontent.com/nvm-sh/nvm/v{NVM_VERSION}/install.sh"
|
||||||
|
)
|
||||||
|
|
||||||
# The frontend directories in a project.
|
# The frontend directories in a project.
|
||||||
# The web folder where the NextJS app is compiled to.
|
# The web folder where the NextJS app is compiled to.
|
||||||
WEB_DIR = ".web"
|
WEB_DIR = ".web"
|
||||||
|
@ -147,7 +147,7 @@ def export_app(
|
|||||||
# Check for special strings and update the progress bar.
|
# Check for special strings and update the progress bar.
|
||||||
for special_string in checkpoints:
|
for special_string in checkpoints:
|
||||||
if special_string in line:
|
if special_string in line:
|
||||||
if special_string == "Export successful":
|
if special_string == checkpoints[-1]:
|
||||||
progress.update(task, completed=len(checkpoints))
|
progress.update(task, completed=len(checkpoints))
|
||||||
break # Exit the loop if the completion message is found
|
break # Exit the loop if the completion message is found
|
||||||
else:
|
else:
|
||||||
|
@ -9,12 +9,15 @@ import platform
|
|||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import threading
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from fileinput import FileInput
|
from fileinput import FileInput
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
import httpx
|
||||||
import typer
|
import typer
|
||||||
from alembic.util.exc import CommandError
|
from alembic.util.exc import CommandError
|
||||||
from packaging import version
|
from packaging import version
|
||||||
@ -44,7 +47,7 @@ def check_node_version():
|
|||||||
current_version = version.parse(result.stdout.decode())
|
current_version = version.parse(result.stdout.decode())
|
||||||
# Compare the version numbers
|
# Compare the version numbers
|
||||||
return (
|
return (
|
||||||
current_version >= version.parse(constants.MIN_NODE_VERSION)
|
current_version >= version.parse(constants.NODE_VERSION_MIN)
|
||||||
if IS_WINDOWS
|
if IS_WINDOWS
|
||||||
else current_version == version.parse(constants.NODE_VERSION)
|
else current_version == version.parse(constants.NODE_VERSION)
|
||||||
)
|
)
|
||||||
@ -273,39 +276,68 @@ def initialize_node():
|
|||||||
install_node()
|
install_node()
|
||||||
|
|
||||||
|
|
||||||
|
def download_and_run(url: str, *args, **env):
|
||||||
|
"""Download and run a script.
|
||||||
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: The url of the script.
|
||||||
|
args: The arguments to pass to the script.
|
||||||
|
env: The environment variables to use.
|
||||||
|
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exit: if installation failed
|
||||||
|
"""
|
||||||
|
# Download the script
|
||||||
|
response = httpx.get(url)
|
||||||
|
if response.status_code != httpx.codes.OK:
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# Save the script to a temporary file.
|
||||||
|
script = tempfile.NamedTemporaryFile()
|
||||||
|
with open(script.name, "w") as f:
|
||||||
|
f.write(response.text)
|
||||||
|
|
||||||
|
# Run the script.
|
||||||
|
env = {
|
||||||
|
**os.environ,
|
||||||
|
**env,
|
||||||
|
}
|
||||||
|
result = subprocess.run(["bash", f.name, *args], env=env)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise typer.Exit(code=result.returncode)
|
||||||
|
|
||||||
|
|
||||||
def install_node():
|
def install_node():
|
||||||
"""Install nvm and nodejs for use by Reflex.
|
"""Install nvm and nodejs for use by Reflex.
|
||||||
Independent of any existing system installations.
|
Independent of any existing system installations.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
FileNotFoundError: if unzip or curl packages are not found.
|
|
||||||
Exit: if installation failed
|
Exit: if installation failed
|
||||||
"""
|
"""
|
||||||
|
# NVM is not supported on Windows.
|
||||||
if IS_WINDOWS:
|
if IS_WINDOWS:
|
||||||
console.print(
|
console.print(
|
||||||
f"[red]Node.js version {constants.NODE_VERSION} or higher is required to run Reflex."
|
f"[red]Node.js version {constants.NODE_VERSION} or higher is required to run Reflex."
|
||||||
)
|
)
|
||||||
raise typer.Exit()
|
raise typer.Exit()
|
||||||
|
|
||||||
# Only install if bun is not already installed.
|
|
||||||
console.log("Installing nvm...")
|
|
||||||
|
|
||||||
# Check if curl is installed
|
|
||||||
# TODO no need to shell out to curl
|
|
||||||
curl_path = path_ops.which("curl")
|
|
||||||
if curl_path is None:
|
|
||||||
raise FileNotFoundError("Reflex requires curl to be installed.")
|
|
||||||
|
|
||||||
# Create the nvm directory and install.
|
# Create the nvm directory and install.
|
||||||
path_ops.mkdir(constants.NVM_ROOT_PATH)
|
path_ops.mkdir(constants.NVM_DIR)
|
||||||
result = subprocess.run(constants.INSTALL_NVM, shell=True)
|
env = {**os.environ, "NVM_DIR": constants.NVM_DIR}
|
||||||
|
download_and_run(constants.NVM_INSTALL_URL, **env)
|
||||||
if result.returncode != 0:
|
|
||||||
raise typer.Exit(code=result.returncode)
|
|
||||||
|
|
||||||
console.log("Installing node...")
|
|
||||||
result = subprocess.run(constants.INSTALL_NODE, shell=True)
|
|
||||||
|
|
||||||
|
# Install node.
|
||||||
|
# We use bash -c as we need to source nvm.sh to use nvm.
|
||||||
|
result = subprocess.run(
|
||||||
|
[
|
||||||
|
"bash",
|
||||||
|
"-c",
|
||||||
|
f". {constants.NVM_DIR}/nvm.sh && nvm install {constants.NODE_VERSION}",
|
||||||
|
],
|
||||||
|
env=env,
|
||||||
|
)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
raise typer.Exit(code=result.returncode)
|
raise typer.Exit(code=result.returncode)
|
||||||
|
|
||||||
@ -314,32 +346,27 @@ def install_bun():
|
|||||||
"""Install bun onto the user's system.
|
"""Install bun onto the user's system.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
FileNotFoundError: if unzip or curl packages are not found.
|
FileNotFoundError: If required packages are not found.
|
||||||
Exit: if installation failed
|
|
||||||
"""
|
"""
|
||||||
# Bun is not supported on Windows.
|
# Bun is not supported on Windows.
|
||||||
if IS_WINDOWS:
|
if IS_WINDOWS:
|
||||||
console.log("Skipping bun installation on Windows.")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Only install if bun is not already installed.
|
# Skip if bun is already installed.
|
||||||
if not os.path.exists(constants.BUN_PATH):
|
if os.path.exists(constants.BUN_PATH):
|
||||||
console.log("Installing bun...")
|
return
|
||||||
|
|
||||||
# Check if curl is installed
|
|
||||||
curl_path = path_ops.which("curl")
|
|
||||||
if curl_path is None:
|
|
||||||
raise FileNotFoundError("Reflex requires curl to be installed.")
|
|
||||||
|
|
||||||
# Check if unzip is installed
|
# Check if unzip is installed
|
||||||
unzip_path = path_ops.which("unzip")
|
unzip_path = path_ops.which("unzip")
|
||||||
if unzip_path is None:
|
if unzip_path is None:
|
||||||
raise FileNotFoundError("Reflex requires unzip to be installed.")
|
raise FileNotFoundError("Reflex requires unzip to be installed.")
|
||||||
|
|
||||||
result = subprocess.run(constants.INSTALL_BUN, shell=True)
|
# Run the bun install script.
|
||||||
|
download_and_run(
|
||||||
if result.returncode != 0:
|
constants.BUN_INSTALL_URL,
|
||||||
raise typer.Exit(code=result.returncode)
|
f"bun-v{constants.BUN_VERSION}",
|
||||||
|
BUN_INSTALL=constants.BUN_ROOT_PATH,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def install_frontend_packages():
|
def install_frontend_packages():
|
||||||
@ -417,12 +444,20 @@ def initialize_frontend_dependencies():
|
|||||||
path_ops.mkdir(constants.REFLEX_DIR)
|
path_ops.mkdir(constants.REFLEX_DIR)
|
||||||
|
|
||||||
# Install the frontend dependencies.
|
# Install the frontend dependencies.
|
||||||
initialize_bun()
|
threads = [
|
||||||
initialize_node()
|
threading.Thread(target=initialize_bun),
|
||||||
|
threading.Thread(target=initialize_node),
|
||||||
|
]
|
||||||
|
for thread in threads:
|
||||||
|
thread.start()
|
||||||
|
|
||||||
# Set up the web directory.
|
# Set up the web directory.
|
||||||
initialize_web_directory()
|
initialize_web_directory()
|
||||||
|
|
||||||
|
# Wait for the threads to finish.
|
||||||
|
for thread in threads:
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
|
||||||
def check_admin_settings():
|
def check_admin_settings():
|
||||||
"""Check if admin settings are set and valid for logging in cli app."""
|
"""Check if admin settings are set and valid for logging in cli app."""
|
||||||
|
@ -134,13 +134,15 @@ def new_process(args, **kwargs):
|
|||||||
Returns:
|
Returns:
|
||||||
Execute a child program in a new process.
|
Execute a child program in a new process.
|
||||||
"""
|
"""
|
||||||
env = os.environ.copy()
|
env = {
|
||||||
env["PATH"] = os.pathsep.join([constants.NODE_BIN_PATH, env["PATH"]])
|
**os.environ,
|
||||||
|
"PATH": os.pathsep.join([constants.NODE_BIN_PATH, os.environ["PATH"]]),
|
||||||
|
}
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"env": env,
|
"env": env,
|
||||||
"stderr": subprocess.STDOUT,
|
"stderr": subprocess.STDOUT,
|
||||||
"stdout": subprocess.PIPE, # Redirect stdout to a pipe
|
"stdout": subprocess.PIPE,
|
||||||
"universal_newlines": True, # Set universal_newlines to True for text mode
|
"universal_newlines": True,
|
||||||
"encoding": "UTF-8",
|
"encoding": "UTF-8",
|
||||||
**kwargs,
|
**kwargs,
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import typer
|
|||||||
from packaging import version
|
from packaging import version
|
||||||
|
|
||||||
from reflex import Env, constants
|
from reflex import Env, constants
|
||||||
|
from reflex.base import Base
|
||||||
from reflex.utils import build, format, imports, prerequisites, types
|
from reflex.utils import build, format, imports, prerequisites, types
|
||||||
from reflex.vars import Var
|
from reflex.vars import Var
|
||||||
|
|
||||||
@ -527,13 +528,20 @@ def test_node_install_windows(mocker):
|
|||||||
def test_node_install_unix(tmp_path, mocker):
|
def test_node_install_unix(tmp_path, mocker):
|
||||||
nvm_root_path = tmp_path / ".reflex" / ".nvm"
|
nvm_root_path = tmp_path / ".reflex" / ".nvm"
|
||||||
|
|
||||||
mocker.patch("reflex.utils.prerequisites.constants.NVM_ROOT_PATH", nvm_root_path)
|
mocker.patch("reflex.utils.prerequisites.constants.NVM_DIR", nvm_root_path)
|
||||||
subprocess_run = mocker.patch(
|
subprocess_run = mocker.patch(
|
||||||
"reflex.utils.prerequisites.subprocess.run",
|
"reflex.utils.prerequisites.subprocess.run",
|
||||||
return_value=subprocess.CompletedProcess(args="", returncode=0),
|
return_value=subprocess.CompletedProcess(args="", returncode=0),
|
||||||
)
|
)
|
||||||
mocker.patch("reflex.utils.prerequisites.IS_WINDOWS", False)
|
mocker.patch("reflex.utils.prerequisites.IS_WINDOWS", False)
|
||||||
|
|
||||||
|
class Resp(Base):
|
||||||
|
status_code = 200
|
||||||
|
text = "test"
|
||||||
|
|
||||||
|
mocker.patch("httpx.get", return_value=Resp())
|
||||||
|
mocker.patch("reflex.utils.prerequisites.download_and_run")
|
||||||
|
|
||||||
prerequisites.install_node()
|
prerequisites.install_node()
|
||||||
|
|
||||||
assert nvm_root_path.exists()
|
assert nvm_root_path.exists()
|
||||||
@ -541,14 +549,15 @@ def test_node_install_unix(tmp_path, mocker):
|
|||||||
subprocess_run.call_count = 2
|
subprocess_run.call_count = 2
|
||||||
|
|
||||||
|
|
||||||
def test_node_install_without_curl(mocker):
|
def test_bun_install_without_unzip(mocker):
|
||||||
"""Test that an error is thrown when installing node with curl not installed.
|
"""Test that an error is thrown when installing bun with unzip not installed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
mocker: Pytest mocker object.
|
mocker: Pytest mocker object.
|
||||||
"""
|
"""
|
||||||
mocker.patch("reflex.utils.prerequisites.path_ops.which", return_value=None)
|
mocker.patch("reflex.utils.path_ops.which", return_value=None)
|
||||||
|
mocker.patch("os.path.exists", return_value=False)
|
||||||
mocker.patch("reflex.utils.prerequisites.IS_WINDOWS", False)
|
mocker.patch("reflex.utils.prerequisites.IS_WINDOWS", False)
|
||||||
|
|
||||||
with pytest.raises(FileNotFoundError):
|
with pytest.raises(FileNotFoundError):
|
||||||
prerequisites.install_node()
|
prerequisites.install_bun()
|
||||||
|
Loading…
Reference in New Issue
Block a user