Create ~/.reflex directory and update integration tests (#1419)

This commit is contained in:
Nikhil Rao 2023-07-27 16:39:53 -07:00 committed by GitHub
parent 3faad315ca
commit e26bba80a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 300 additions and 218 deletions

View File

@ -1,16 +1,14 @@
name: integration-test name: integration-test-examples
on: on:
pull_request_review: pull_request:
types: [submitted] branches: [main]
permissions: permissions:
contents: read contents: read
jobs: jobs:
build: build:
if: github.event.review.state == 'approved' && github.event.pull_request.base.ref == 'main'
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Specify python/node versions to test against # Specify python/node versions to test against
@ -65,6 +63,10 @@ jobs:
- name: Init Website - name: Init Website
working-directory: ./pynecone-examples/counter working-directory: ./pynecone-examples/counter
run: poetry run reflex init run: poetry run reflex init
- name: Validate Reflex's own installation of Node
run: |
/home/runner/.reflex/.nvm/versions/node/v*/bin/npm -v
/home/runner/.reflex/.nvm/versions/node/v*/bin/node -v
- name: Check for errors - name: Check for errors
run: | run: |
chmod +x ./scripts/integration.sh chmod +x ./scripts/integration.sh

View File

@ -1,16 +1,14 @@
name: integration-test name: integration-test-website
on: on:
pull_request_review: pull_request:
types: [submitted] branches: [ main ]
permissions: permissions:
contents: read contents: read
jobs: jobs:
build: build:
if: github.event.review.state == 'approved' && github.event.pull_request.base.ref == 'main'
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Specify python/node versions to test against # Specify python/node versions to test against

20
.github/workflows/reflex_init_test.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: reflex-init-test
on:
pull_request:
branches:
- main
jobs:
reflex-install-and-init:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- shell: bash
run: |
# Run reflex init in a docker container
# cwd is repo root
docker build -f integration/init-test/Dockerfile -t reflex-init-test integration/init-test
docker run --rm -v $(pwd):/reflex-repo/ reflex-init-test /reflex-repo/integration/init-test/in_docker_test_script.sh

View File

@ -0,0 +1,21 @@
FROM ubuntu:latest
ARG USERNAME=kerrigan
ARG USER_UID=1000
ARG USER_GID=$USER_UID
RUN groupadd --gid $USER_GID $USERNAME \
&& useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
#
# [Optional] Add sudo support. Omit if you don't need to install software after connecting.
&& apt-get update \
&& apt-get install -y sudo curl xz-utils python3 python3-pip python3.10-venv unzip \
&& echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \
&& chmod 0440 /etc/sudoers.d/$USERNAME
USER $USERNAME
RUN curl -sSL https://install.python-poetry.org | python3 -
RUN sudo ln -s /home/$USERNAME/.local/bin/poetry /usr/local/bin/poetry
WORKDIR /home/$USERNAME

View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -ex
echo "Preparing test project dir"
mkdir hello
python3 -m venv ~/hello/venv
source ~/hello/venv/bin/activate
echo "Installing reflex from local repo code"
cd /reflex-repo
poetry install
echo "Running reflex init in test project dir"
poetry run /bin/bash -c "cd ~/hello && reflex init"

View File

@ -165,12 +165,6 @@ class Config(Base):
# The environment mode. # The environment mode.
env: constants.Env = constants.Env.DEV env: constants.Env = constants.Env.DEV
# The path to the bun executable.
bun_path: str = constants.BUN_PATH
# Disable bun.
disable_bun: bool = False
# Additional frontend packages to install. # Additional frontend packages to install.
frontend_packages: List[str] = [] frontend_packages: List[str] = []

View File

@ -1,6 +1,7 @@
"""Constants used throughout the package.""" """Constants used throughout the package."""
import os import os
import platform
import re import re
from enum import Enum from enum import Enum
from types import SimpleNamespace from types import SimpleNamespace
@ -43,12 +44,41 @@ def get_value(key: str, default: Any = None, type_: Type = str) -> Type:
MODULE_NAME = "reflex" MODULE_NAME = "reflex"
# The current version of Reflex. # The current version of Reflex.
VERSION = metadata.version(MODULE_NAME) VERSION = metadata.version(MODULE_NAME)
# Minimum version of Node.js required to run Reflex.
MIN_NODE_VERSION = "16.8.0"
# Valid bun versions. # Project dependencies.
MIN_BUN_VERSION = "0.5.9" # The directory to store reflex dependencies.
MAX_BUN_VERSION = "0.6.9" 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 root directory of the reflex library. # The root directory of the reflex library.
@ -110,24 +140,6 @@ BACKEND_PORT = get_value("BACKEND_PORT", "8000")
API_URL = get_value("API_URL", "http://localhost:8000") API_URL = get_value("API_URL", "http://localhost:8000")
# The deploy url # The deploy url
DEPLOY_URL = get_value("DEPLOY_URL") DEPLOY_URL = get_value("DEPLOY_URL")
# bun root location
BUN_ROOT_PATH = "$HOME/.bun"
# The default path where bun is installed.
BUN_PATH = get_value("BUN_PATH", f"{BUN_ROOT_PATH}/bin/bun")
# Command to install bun.
INSTALL_BUN = f"curl -fsSL https://bun.sh/install | bash -s -- bun-v{MAX_BUN_VERSION}"
# Command to install nvm.
INSTALL_NVM = (
"curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash"
)
# nvm root location.
NVM_ROOT_PATH = f"$HOME/.nvm"
# The default path where node is installed.
NODE_PATH = get_value(
"NODE_PATH", f"{NVM_ROOT_PATH}/versions/node/v{MIN_NODE_VERSION}/bin/node"
)
# Command to install node.
INSTALL_NODE = f". {NVM_ROOT_PATH}/nvm.sh && nvm install {MIN_NODE_VERSION}"
# Default host in dev mode. # Default host in dev mode.
BACKEND_HOST = get_value("BACKEND_HOST", "0.0.0.0") BACKEND_HOST = get_value("BACKEND_HOST", "0.0.0.0")
# The default timeout when launching the gunicorn server. # The default timeout when launching the gunicorn server.

View File

@ -32,20 +32,12 @@ def init(
), ),
): ):
"""Initialize a new Reflex app in the current directory.""" """Initialize a new Reflex app in the current directory."""
# Get the app name.
app_name = prerequisites.get_default_app_name() if name is None else name app_name = prerequisites.get_default_app_name() if name is None else name
# Make sure they don't name the app "reflex".
if app_name == constants.MODULE_NAME:
console.print(
f"[red]The app directory cannot be named [bold]{constants.MODULE_NAME}."
)
raise typer.Exit()
console.rule(f"[bold]Initializing {app_name}") console.rule(f"[bold]Initializing {app_name}")
# Set up the web directory.
prerequisites.validate_and_install_bun() # Set up the web project.
prerequisites.validate_and_install_node() prerequisites.initialize_frontend_dependencies()
prerequisites.initialize_web_directory()
# Migrate Pynecone projects to Reflex. # Migrate Pynecone projects to Reflex.
prerequisites.migrate_to_reflex() prerequisites.migrate_to_reflex()
@ -57,11 +49,11 @@ def init(
build.set_reflex_project_hash() build.set_reflex_project_hash()
telemetry.send("init", get_config().telemetry_enabled) telemetry.send("init", get_config().telemetry_enabled)
else: else:
build.set_reflex_project_hash()
telemetry.send("reinit", get_config().telemetry_enabled) telemetry.send("reinit", get_config().telemetry_enabled)
# Initialize the .gitignore. # Initialize the .gitignore.
prerequisites.initialize_gitignore() prerequisites.initialize_gitignore()
# Finish initializing the app. # Finish initializing the app.
console.log(f"[bold green]Finished Initializing: {app_name}") console.log(f"[bold green]Finished Initializing: {app_name}")
@ -85,7 +77,7 @@ def run(
"""Run the app in the current directory.""" """Run the app in the current directory."""
if platform.system() == "Windows": if platform.system() == "Windows":
console.print( console.print(
"[yellow][WARNING] We strongly advise you to use Windows Subsystem for Linux (WSL) for optimal performance when using Reflex. Due to compatibility issues with one of our dependencies, Bun, you may experience slower performance on Windows. By using WSL, you can expect to see a significant speed increase." "[yellow][WARNING] We strongly advise using Windows Subsystem for Linux (WSL) for optimal performance with reflex."
) )
# Set ports as os env variables to take precedence over config and # Set ports as os env variables to take precedence over config and
# .env variables(if override_os_envs flag in config is set to False). # .env variables(if override_os_envs flag in config is set to False).

View File

@ -179,7 +179,7 @@ class AppHarness:
frontend_env = os.environ.copy() frontend_env = os.environ.copy()
frontend_env["PORT"] = "0" frontend_env["PORT"] = "0"
self.frontend_process = subprocess.Popen( self.frontend_process = subprocess.Popen(
[reflex.utils.prerequisites.get_package_manager(), "run", "dev"], [reflex.utils.prerequisites.get_install_package_manager(), "run", "dev"],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
encoding="utf-8", encoding="utf-8",
cwd=self.app_path / reflex.constants.WEB_DIR, cwd=self.app_path / reflex.constants.WEB_DIR,

View File

@ -206,21 +206,15 @@ def setup_frontend(
loglevel: constants.LogLevel = constants.LogLevel.ERROR, loglevel: constants.LogLevel = constants.LogLevel.ERROR,
disable_telemetry: bool = True, disable_telemetry: bool = True,
): ):
"""Set up the frontend. """Set up the frontend to run the app.
Args: Args:
root: The root path of the project. root: The root path of the project.
loglevel: The log level to use. loglevel: The log level to use.
disable_telemetry: Whether to disable the Next telemetry. disable_telemetry: Whether to disable the Next telemetry.
""" """
# Validate bun version.
prerequisites.validate_and_install_bun(initialize=False)
# Initialize the web directory if it doesn't exist.
web_dir = prerequisites.create_web_directory(root)
# Install frontend packages. # Install frontend packages.
prerequisites.install_frontend_packages(web_dir) prerequisites.install_frontend_packages()
# Copy asset files to public folder. # Copy asset files to public folder.
path_ops.cp( path_ops.cp(
@ -228,7 +222,7 @@ def setup_frontend(
dest=str(root / constants.WEB_ASSETS_DIR), dest=str(root / constants.WEB_ASSETS_DIR),
) )
# set the environment variables in client(env.json) # Set the environment variables in client (env.json).
set_environment_variables() set_environment_variables()
# Disable the Next telemetry. # Disable the Next telemetry.

View File

@ -90,9 +90,7 @@ def run_frontend_prod(
# Run the frontend in production mode. # Run the frontend in production mode.
console.rule("[bold green]App Running") console.rule("[bold green]App Running")
run_process_and_launch_url( run_process_and_launch_url([constants.NPM_PATH, "run", "prod"], loglevel)
[prerequisites.get_package_manager(), "run", "prod"], loglevel
)
def run_backend( def run_backend(
@ -122,12 +120,7 @@ def run_backend(
"--reload-dir", "--reload-dir",
app_name.split(".")[0], app_name.split(".")[0],
] ]
process = subprocess.Popen(cmd) subprocess.run(cmd)
try:
process.wait()
except KeyboardInterrupt:
process.terminate()
def run_backend_prod( def run_backend_prod(

View File

@ -24,25 +24,30 @@ from reflex import constants, model
from reflex.config import get_config from reflex.config import get_config
from reflex.utils import console, path_ops from reflex.utils import console, path_ops
IS_WINDOWS = platform.system() == "Windows"
def check_node_version(min_version=constants.MIN_NODE_VERSION):
def check_node_version():
"""Check the version of Node.js. """Check the version of Node.js.
Args:
min_version: The minimum version of Node.js required.
Returns: Returns:
Whether the version of Node.js is high enough. Whether the version of Node.js is valid.
""" """
try: try:
# Run the node -v command and capture the output # Run the node -v command and capture the output
result = subprocess.run( result = subprocess.run(
["node", "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE [constants.NODE_PATH, "-v"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
) )
# The output will be in the form "vX.Y.Z", but version.parse() can handle it # The output will be in the form "vX.Y.Z", but version.parse() can handle it
current_version = version.parse(result.stdout.decode()) current_version = version.parse(result.stdout.decode())
# Compare the version numbers # Compare the version numbers
return current_version >= version.parse(min_version) return (
current_version >= version.parse(constants.MIN_NODE_VERSION)
if IS_WINDOWS
else current_version == version.parse(constants.NODE_VERSION)
)
except Exception: except Exception:
return False return False
@ -56,7 +61,7 @@ def get_bun_version() -> Optional[version.Version]:
try: try:
# Run the bun -v command and capture the output # Run the bun -v command and capture the output
result = subprocess.run( result = subprocess.run(
[os.path.expandvars(get_config().bun_path), "-v"], [constants.BUN_PATH, "-v"],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
) )
@ -65,26 +70,50 @@ def get_bun_version() -> Optional[version.Version]:
return None return None
def get_package_manager() -> str: def get_windows_package_manager() -> str:
"""Get the package manager executable. """Get the package manager for windows.
Returns: Returns:
The path to the package manager. The path to the package manager for windows.
Raises: Raises:
FileNotFoundError: If bun or npm is not installed. FileNotFoundError: If bun or npm is not installed.
""" """
config = get_config() npm_path = path_ops.which("npm")
if npm_path is None:
raise FileNotFoundError("Reflex requires npm to be installed on Windows.")
return npm_path
def get_install_package_manager() -> str:
"""Get the package manager executable for installation.
currently on unix systems, bun is used for installation only.
Returns:
The path to the package manager.
"""
get_config()
# On Windows, we use npm instead of bun. # On Windows, we use npm instead of bun.
if platform.system() == "Windows" or config.disable_bun: if platform.system() == "Windows":
npm_path = path_ops.which("npm") return get_windows_package_manager()
if npm_path is None:
raise FileNotFoundError("Reflex requires npm to be installed on Windows.")
return npm_path
# On other platforms, we use bun. # On other platforms, we use bun.
return os.path.expandvars(get_config().bun_path) return constants.BUN_PATH
def get_package_manager() -> str:
"""Get the package manager executable for running app.
currently on unix systems, npm is used for running the app only.
Returns:
The path to the package manager.
"""
get_config()
if platform.system() == "Windows":
return get_windows_package_manager()
return constants.NPM_PATH
def get_app() -> ModuleType: def get_app() -> ModuleType:
@ -133,8 +162,20 @@ def get_default_app_name() -> str:
Returns: Returns:
The default app name. The default app name.
Raises:
Exit: if the app directory name is reflex.
""" """
return os.getcwd().split(os.path.sep)[-1].replace("-", "_") app_name = os.getcwd().split(os.path.sep)[-1].replace("-", "_")
# Make sure the app is not named "reflex".
if app_name == constants.MODULE_NAME:
console.print(
f"[red]The app directory cannot be named [bold]{constants.MODULE_NAME}."
)
raise typer.Exit()
return app_name
def create_config(app_name: str): def create_config(app_name: str):
@ -151,21 +192,6 @@ def create_config(app_name: str):
f.write(templates.RXCONFIG.render(app_name=app_name, config_name=config_name)) f.write(templates.RXCONFIG.render(app_name=app_name, config_name=config_name))
def create_web_directory(root: Path) -> str:
"""Creates a web directory in the given root directory
and returns the path to the directory.
Args:
root (Path): The root directory of the project.
Returns:
The path to the web directory.
"""
web_dir = str(root / constants.WEB_DIR)
path_ops.cp(constants.WEB_TEMPLATE_DIR, web_dir, overwrite=False)
return web_dir
def initialize_gitignore(): def initialize_gitignore():
"""Initialize the template .gitignore file.""" """Initialize the template .gitignore file."""
# The files to add to the .gitignore file. # The files to add to the .gitignore file.
@ -223,90 +249,66 @@ def initialize_web_directory():
json.dump(reflex_json, f, ensure_ascii=False) json.dump(reflex_json, f, ensure_ascii=False)
def validate_and_install_bun(initialize=True): def initialize_bun():
"""Check that bun version requirements are met. If they are not, """Check that bun requirements are met, and install if not."""
ask user whether to install required version. if IS_WINDOWS:
# Bun is not supported on Windows.
return
Args: # Check the bun version.
initialize: whether this function is called on `reflex init` or `reflex run`. if get_bun_version() != version.parse(constants.BUN_VERSION):
remove_existing_bun_installation()
Raises:
Exit: If the bun version is not supported.
"""
bun_version = get_bun_version()
if bun_version is not None and (
bun_version < version.parse(constants.MIN_BUN_VERSION)
or bun_version > version.parse(constants.MAX_BUN_VERSION)
):
console.print(
f"""[red]Bun version {bun_version} is not supported by Reflex. Please change your to bun version to be between {constants.MIN_BUN_VERSION} and {constants.MAX_BUN_VERSION}."""
)
action = console.ask(
"Enter 'yes' to install the latest supported bun version or 'no' to exit.",
choices=["yes", "no"],
default="no",
)
if action == "yes":
remove_existing_bun_installation()
install_bun()
return
else:
raise typer.Exit()
if initialize:
install_bun() install_bun()
def remove_existing_bun_installation(): def remove_existing_bun_installation():
"""Remove existing bun installation.""" """Remove existing bun installation."""
package_manager = get_package_manager() if os.path.exists(constants.BUN_PATH):
if os.path.exists(package_manager): path_ops.rm(constants.BUN_ROOT_PATH)
console.log("Removing bun...")
path_ops.rm(os.path.expandvars(constants.BUN_ROOT_PATH))
def validate_and_install_node(): def initialize_node():
"""Validate nodejs have install or not.""" """Validate nodejs have install or not."""
if not check_node_version(): if not check_node_version():
install_node() install_node()
def install_node(): def install_node():
"""Install nvm and nodejs onto the user's system. """Install nvm and nodejs for use by Reflex.
Independent of any existing system installations.
Raises: Raises:
FileNotFoundError: if unzip or curl packages are not found. FileNotFoundError: if unzip or curl packages are not found.
Exit: if installation failed Exit: if installation failed
""" """
if platform.system() != "Windows": if IS_WINDOWS:
# Only install if bun is not already installed.
console.log("Installing nvm...")
# Check if curl is installed
curl_path = path_ops.which("curl")
if curl_path is None:
raise FileNotFoundError("Reflex requires curl to be installed.")
result = subprocess.run(constants.INSTALL_NVM, shell=True)
if result.returncode != 0:
raise typer.Exit(code=result.returncode)
console.log("Installing node...")
result = subprocess.run(constants.INSTALL_NODE, shell=True)
if result.returncode != 0:
raise typer.Exit(code=result.returncode)
else:
console.print( console.print(
f"[red]Node.js version {constants.MIN_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.
path_ops.mkdir(constants.NVM_ROOT_PATH)
result = subprocess.run(constants.INSTALL_NVM, shell=True)
if result.returncode != 0:
raise typer.Exit(code=result.returncode)
console.log("Installing node...")
result = subprocess.run(constants.INSTALL_NODE, shell=True)
if result.returncode != 0:
raise typer.Exit(code=result.returncode)
def install_bun(): def install_bun():
"""Install bun onto the user's system. """Install bun onto the user's system.
@ -316,12 +318,12 @@ def install_bun():
Exit: if installation failed Exit: if installation failed
""" """
# Bun is not supported on Windows. # Bun is not supported on Windows.
if platform.system() == "Windows": if IS_WINDOWS:
console.log("Skipping bun installation on Windows.") console.log("Skipping bun installation on Windows.")
return return
# Only install if bun is not already installed. # Only install if bun is not already installed.
if not os.path.exists(get_package_manager()): if not os.path.exists(constants.BUN_PATH):
console.log("Installing bun...") console.log("Installing bun...")
# Check if curl is installed # Check if curl is installed
@ -340,20 +342,15 @@ def install_bun():
raise typer.Exit(code=result.returncode) raise typer.Exit(code=result.returncode)
def install_frontend_packages(web_dir: str): def install_frontend_packages():
"""Installs the base and custom frontend packages """Installs the base and custom frontend packages."""
into the given web directory.
Args:
web_dir: The directory where the frontend code is located.
"""
# Install the frontend packages. # Install the frontend packages.
console.rule("[bold]Installing frontend packages") console.rule("[bold]Installing frontend packages")
# Install the base packages. # Install the base packages.
subprocess.run( subprocess.run(
[get_package_manager(), "install"], [get_install_package_manager(), "install"],
cwd=web_dir, cwd=constants.WEB_DIR,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
) )
@ -361,8 +358,8 @@ def install_frontend_packages(web_dir: str):
packages = get_config().frontend_packages packages = get_config().frontend_packages
if len(packages) > 0: if len(packages) > 0:
subprocess.run( subprocess.run(
[get_package_manager(), "add", *packages], [get_install_package_manager(), "add", *packages],
cwd=web_dir, cwd=constants.WEB_DIR,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
) )
@ -389,6 +386,19 @@ def is_latest_template() -> bool:
return app_version == constants.VERSION return app_version == constants.VERSION
def initialize_frontend_dependencies():
"""Initialize all the frontend dependencies."""
# Create the reflex directory.
path_ops.mkdir(constants.REFLEX_DIR)
# Install the frontend dependencies.
initialize_bun()
initialize_node()
# Set up the web directory.
initialize_web_directory()
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."""
admin_dash = get_config().admin_dash admin_dash = get_config().admin_dash

View File

@ -134,8 +134,10 @@ 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["PATH"] = os.pathsep.join([constants.NODE_BIN_PATH, env["PATH"]])
kwargs = { kwargs = {
"env": os.environ, "env": env,
"stderr": subprocess.STDOUT, "stderr": subprocess.STDOUT,
"stdout": subprocess.PIPE, # Redirect stdout to a pipe "stdout": subprocess.PIPE, # Redirect stdout to a pipe
"universal_newlines": True, # Set universal_newlines to True for text mode "universal_newlines": True, # Set universal_newlines to True for text mode

View File

@ -1,9 +1,11 @@
import os import os
import subprocess
import typing import typing
from pathlib import Path from pathlib import Path
from typing import Any, List, Union from typing import Any, List, Union
import pytest import pytest
import typer
from packaging import version from packaging import version
from reflex import Env, constants from reflex import Env, constants
@ -18,7 +20,7 @@ def get_above_max_version():
max bun version plus one. max bun version plus one.
""" """
semantic_version_list = constants.MAX_BUN_VERSION.split(".") semantic_version_list = constants.BUN_VERSION.split(".")
semantic_version_list[-1] = str(int(semantic_version_list[-1]) + 1) # type: ignore semantic_version_list[-1] = str(int(semantic_version_list[-1]) + 1) # type: ignore
return ".".join(semantic_version_list) return ".".join(semantic_version_list)
@ -261,7 +263,7 @@ def test_format_route(route: str, expected: bool):
(VMAXPLUS1, False, "yes"), (VMAXPLUS1, False, "yes"),
], ],
) )
def test_bun_validate_and_install(mocker, bun_version, is_valid, prompt_input): def test_initialize_bun(mocker, bun_version, is_valid, prompt_input):
"""Test that the bun version on host system is validated properly. Also test that """Test that the bun version on host system is validated properly. Also test that
the required bun version is installed should the user opt for it. the required bun version is installed should the user opt for it.
@ -279,48 +281,23 @@ def test_bun_validate_and_install(mocker, bun_version, is_valid, prompt_input):
"reflex.utils.prerequisites.remove_existing_bun_installation" "reflex.utils.prerequisites.remove_existing_bun_installation"
) )
prerequisites.validate_and_install_bun() prerequisites.initialize_bun()
if not is_valid: if not is_valid:
remove_existing_bun_installation.assert_called_once() remove_existing_bun_installation.assert_called_once()
bun_install.assert_called_once() bun_install.assert_called_once()
def test_bun_validation_exception(mocker): def test_remove_existing_bun_installation(mocker):
"""Test that an exception is thrown and program exists when user selects no when asked
whether to install bun or not.
Args:
mocker: Pytest mocker.
"""
mocker.patch("reflex.utils.prerequisites.get_bun_version", return_value=V056)
mocker.patch("reflex.utils.prerequisites.console.ask", return_value="no")
with pytest.raises(RuntimeError):
prerequisites.validate_and_install_bun()
def test_remove_existing_bun_installation(mocker, tmp_path):
"""Test that existing bun installation is removed. """Test that existing bun installation is removed.
Args: Args:
mocker: Pytest mocker. mocker: Pytest mocker.
tmp_path: test path.
""" """
bun_location = tmp_path / ".bun" mocker.patch("reflex.utils.prerequisites.os.path.exists", return_value=True)
bun_location.mkdir() rm = mocker.patch("reflex.utils.prerequisites.path_ops.rm", mocker.Mock())
mocker.patch(
"reflex.utils.prerequisites.get_package_manager",
return_value=str(bun_location),
)
mocker.patch(
"reflex.utils.prerequisites.os.path.expandvars",
return_value=str(bun_location),
)
prerequisites.remove_existing_bun_installation() prerequisites.remove_existing_bun_installation()
rm.assert_called_once()
assert not bun_location.exists()
def test_setup_frontend(tmp_path, mocker): def test_setup_frontend(tmp_path, mocker):
@ -331,19 +308,15 @@ def test_setup_frontend(tmp_path, mocker):
tmp_path: root path of test case data directory tmp_path: root path of test case data directory
mocker: mocker object to allow mocking mocker: mocker object to allow mocking
""" """
web_folder = tmp_path / ".web" web_public_folder = tmp_path / ".web" / "public"
web_public_folder = web_folder / "public"
assets = tmp_path / "assets" assets = tmp_path / "assets"
assets.mkdir() assets.mkdir()
(assets / "favicon.ico").touch() (assets / "favicon.ico").touch()
assert str(web_folder) == prerequisites.create_web_directory(tmp_path)
mocker.patch("reflex.utils.prerequisites.install_frontend_packages") mocker.patch("reflex.utils.prerequisites.install_frontend_packages")
mocker.patch("reflex.utils.build.set_environment_variables") mocker.patch("reflex.utils.build.set_environment_variables")
build.setup_frontend(tmp_path, disable_telemetry=False) build.setup_frontend(tmp_path, disable_telemetry=False)
assert web_folder.exists()
assert web_public_folder.exists() assert web_public_folder.exists()
assert (web_public_folder / "favicon.ico").exists() assert (web_public_folder / "favicon.ico").exists()
@ -518,3 +491,60 @@ def test_initialize_non_existent_gitignore(tmp_path, mocker, gitignore_exists):
assert gitignore_file.exists() assert gitignore_file.exists()
file_content = [line.strip() for line in gitignore_file.open().readlines()] file_content = [line.strip() for line in gitignore_file.open().readlines()]
assert set(file_content) - expected == set() assert set(file_content) - expected == set()
def test_app_default_name(tmp_path, mocker):
"""Test that an error is raised if the app name is reflex.
Args:
tmp_path: Test working dir.
mocker: Pytest mocker object.
"""
reflex = tmp_path / "reflex"
reflex.mkdir()
mocker.patch("reflex.utils.prerequisites.os.getcwd", return_value=str(reflex))
with pytest.raises(typer.Exit):
prerequisites.get_default_app_name()
def test_node_install_windows(mocker):
"""Require user to install node manually for windows if node is not installed.
Args:
mocker: Pytest mocker object.
"""
mocker.patch("reflex.utils.prerequisites.IS_WINDOWS", True)
mocker.patch("reflex.utils.prerequisites.check_node_version", return_value=False)
with pytest.raises(typer.Exit):
prerequisites.initialize_node()
def test_node_install_unix(tmp_path, mocker):
nvm_root_path = tmp_path / ".reflex" / ".nvm"
mocker.patch("reflex.utils.prerequisites.constants.NVM_ROOT_PATH", nvm_root_path)
subprocess_run = mocker.patch(
"reflex.utils.prerequisites.subprocess.run",
return_value=subprocess.CompletedProcess(args="", returncode=0),
)
prerequisites.install_node()
assert nvm_root_path.exists()
subprocess_run.assert_called()
subprocess_run.call_count = 2
def test_node_install_without_curl(mocker):
"""Test that an error is thrown when installing node with curl not installed.
Args:
mocker: Pytest mocker object.
"""
mocker.patch("reflex.utils.prerequisites.path_ops.which", return_value=None)
with pytest.raises(FileNotFoundError):
prerequisites.install_node()