Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

double down on bun over fnm/npm #4906

Merged
merged 13 commits into from
Mar 17, 2025
1 change: 0 additions & 1 deletion .github/workflows/check_node_latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ on:

env:
TELEMETRY_ENABLED: false
REFLEX_USE_SYSTEM_NODE: true

jobs:
check_latest_node:
Expand Down
15 changes: 6 additions & 9 deletions .github/workflows/integration_tests_wsl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,8 @@ env:

jobs:
example-counter-wsl:
timeout-minutes: 30
# 2019 is more stable with WSL in GH actions
# https://github.com/actions/runner-images/issues/5151
# Confirmed through trial and error. 2022 has >80% failure rate (probably BSOD)
runs-on: windows-2019
timeout-minutes: 20
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Clone Reflex Examples Repo
Expand All @@ -36,15 +33,15 @@ jobs:
repository: reflex-dev/reflex-examples
path: reflex-examples

- uses: Vampire/setup-wsl@v3
- uses: Vampire/setup-wsl@v5
with:
distribution: Ubuntu-24.04

- name: Install Python
- name: Install packages
shell: wsl-bash {0}
run: |
apt update
apt install -y python3 python3-pip curl dos2unix zip unzip
sudo apt-get update
sudo apt-get install -y python3 python3-pip curl dos2unix zip unzip

- name: Install Uv
shell: wsl-bash {0}
Expand Down
2 changes: 1 addition & 1 deletion docker-example/production-one-port/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt

# Install reflex helper utilities like bun/fnm/node
# Install reflex helper utilities like bun/node
COPY rxconfig.py ./
RUN reflex init

Expand Down
5 changes: 1 addition & 4 deletions reflex/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,7 +596,7 @@ class EnvironmentVariables:
constants.CompileContext.UNDEFINED, internal=True
)

# Whether to use npm over bun to install frontend packages.
# Whether to use npm over bun to install and run the frontend.
REFLEX_USE_NPM: EnvVar[bool] = env_var(False)

# The npm registry to use.
Expand All @@ -614,9 +614,6 @@ class EnvironmentVariables:
# Whether to use the system installed bun. If set to false, bun will be bundled with the app.
REFLEX_USE_SYSTEM_BUN: EnvVar[bool] = env_var(False)

# Whether to use the system installed node and npm. If set to false, node and npm will be bundled with the app.
REFLEX_USE_SYSTEM_NODE: EnvVar[bool] = env_var(False)

# The working directory for the next.js commands.
REFLEX_WEB_WORKDIR: EnvVar[Path] = env_var(Path(constants.Dirs.WEB))

Expand Down
3 changes: 1 addition & 2 deletions reflex/constants/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
)
from .custom_components import CustomComponents
from .event import Endpoint, EventTriggers, SocketEvent
from .installer import Bun, Fnm, Node, PackageJson
from .installer import Bun, Node, PackageJson
from .route import (
ROUTE_NOT_FOUND,
ROUTER,
Expand Down Expand Up @@ -94,7 +94,6 @@
"EventTriggers",
"Expiration",
"Ext",
"Fnm",
"GitIgnore",
"Hooks",
"Imports",
Expand Down
103 changes: 3 additions & 100 deletions reflex/constants/installer.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,22 @@
"""File for constants related to the installation process. (Bun/FNM/Node)."""
"""File for constants related to the installation process. (Bun/Node)."""

from __future__ import annotations

import platform
from pathlib import Path
from types import SimpleNamespace

from .base import IS_WINDOWS
from .utils import classproperty


def get_fnm_name() -> str | None:
"""Get the appropriate fnm executable name based on the current platform.

Returns:
The fnm executable name for the current platform.
"""
platform_os = platform.system()

if platform_os == "Windows":
return "fnm-windows"
elif platform_os == "Darwin":
return "fnm-macos"
elif platform_os == "Linux":
machine = platform.machine()
if machine == "arm" or machine.startswith("armv7"):
return "fnm-arm32"
elif machine.startswith("aarch") or machine.startswith("armv8"):
return "fnm-arm64"
return "fnm-linux"
return None


# Bun config.
class Bun(SimpleNamespace):
"""Bun constants."""

# The Bun version.
VERSION = "1.2.0"
VERSION = "1.2.4"

# Min Bun Version
MIN_VERSION = "1.1.0"
MIN_VERSION = "1.2.4"

# URL to bun install script.
INSTALL_URL = "https://raw.githubusercontent.com/reflex-dev/reflex/main/scripts/bun_install.sh"
Expand Down Expand Up @@ -81,43 +57,6 @@ def DEFAULT_PATH(cls):
"""


# FNM config.
class Fnm(SimpleNamespace):
"""FNM constants."""

# The FNM version.
VERSION = "1.35.1"

FILENAME = get_fnm_name()

# The URL to the fnm release binary
INSTALL_URL = (
f"https://github.com/Schniz/fnm/releases/download/v{VERSION}/{FILENAME}.zip"
)

@classproperty
@classmethod
def DIR(cls) -> Path:
"""The directory to store fnm.

Returns:
The directory to store fnm.
"""
from reflex.config import environment

return environment.REFLEX_DIR.get() / "fnm"

@classproperty
@classmethod
def EXE(cls):
"""The fnm executable binary.

Returns:
The fnm executable binary.
"""
return cls.DIR / ("fnm.exe" if IS_WINDOWS else "fnm")


# Node / NPM config
class Node(SimpleNamespace):
"""Node/ NPM constants."""
Expand All @@ -127,42 +66,6 @@ class Node(SimpleNamespace):
# The minimum required node version.
MIN_VERSION = "18.18.0"

@classproperty
@classmethod
def BIN_PATH(cls):
"""The node bin path.

Returns:
The node bin path.
"""
return (
Fnm.DIR
/ "node-versions"
/ f"v{cls.VERSION}"
/ "installation"
/ ("bin" if not IS_WINDOWS else "")
)

@classproperty
@classmethod
def PATH(cls):
"""The default path where node is installed.

Returns:
The default path where node is installed.
"""
return cls.BIN_PATH / ("node.exe" if IS_WINDOWS else "node")

@classproperty
@classmethod
def NPM_PATH(cls):
"""The default path where npm is installed.

Returns:
The default path where npm is installed.
"""
return cls.BIN_PATH / "npm"


class PackageJson(SimpleNamespace):
"""Constants used to build the package.json file."""
Expand Down
8 changes: 7 additions & 1 deletion reflex/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,13 @@ def _start_frontend(self):

# Start the frontend.
self.frontend_process = reflex.utils.processes.new_process(
[reflex.utils.prerequisites.get_package_manager(), "run", "dev"],
[
*reflex.utils.prerequisites.get_js_package_executor(raise_on_none=True)[
0
],
"run",
"dev",
],
cwd=self.app_path / reflex.utils.prerequisites.get_web_dir(),
env={"PORT": "0"},
**FRONTEND_POPEN_ARGS,
Expand Down
4 changes: 2 additions & 2 deletions reflex/utils/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def build(

# Start the subprocess with the progress bar.
process = processes.new_process(
[prerequisites.get_package_manager(), "run", command],
[*prerequisites.get_js_package_executor(raise_on_none=True)[0], "run", command],
cwd=wdir,
shell=constants.IS_WINDOWS,
)
Expand Down Expand Up @@ -248,7 +248,7 @@ def setup_frontend(
if disable_telemetry:
processes.new_process(
[
prerequisites.get_package_manager(),
*prerequisites.get_js_package_executor(raise_on_none=True)[0],
"run",
"next",
"telemetry",
Expand Down
21 changes: 10 additions & 11 deletions reflex/utils/exec.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,11 @@ def run_frontend(root: Path, port: str, backend_present: bool = True):
console.rule("[bold green]App Running")
os.environ["PORT"] = str(get_config().frontend_port if port is None else port)
run_process_and_launch_url(
[prerequisites.get_package_manager(), "run", "dev"],
[
*prerequisites.get_js_package_executor(raise_on_none=True)[0],
"run",
"dev",
],
backend_present,
)

Expand All @@ -176,7 +180,7 @@ def run_frontend_prod(root: Path, port: str, backend_present: bool = True):
# Run the frontend in production mode.
console.rule("[bold green]App Running")
run_process_and_launch_url(
[prerequisites.get_package_manager(), "run", "prod"],
[*prerequisites.get_js_package_executor(raise_on_none=True)[0], "run", "prod"],
backend_present,
)

Expand Down Expand Up @@ -518,13 +522,8 @@ def output_system_info():

system = platform.system()

fnm_info = f"[FNM {prerequisites.get_fnm_version()} (Expected: {constants.Fnm.VERSION}) (PATH: {constants.Fnm.EXE})]"

dependencies.extend(
[
fnm_info,
f"[Bun {prerequisites.get_bun_version()} (Expected: {constants.Bun.VERSION}) (PATH: {path_ops.get_bun_path()})]",
],
dependencies.append(
f"[Bun {prerequisites.get_bun_version()} (Expected: {constants.Bun.VERSION}) (PATH: {path_ops.get_bun_path()})]"
)

if system == "Linux":
Expand All @@ -540,10 +539,10 @@ def output_system_info():
console.debug(f"{dep}")

console.debug(
f"Using package installer at: {prerequisites.get_install_package_manager(on_failure_return_none=True)}"
f"Using package installer at: {prerequisites.get_nodejs_compatible_package_managers(raise_on_none=False)}"
)
console.debug(
f"Using package executer at: {prerequisites.get_package_manager(on_failure_return_none=True)}"
f"Using package executer at: {prerequisites.get_js_package_executor(raise_on_none=False)}"
)
if system != "Windows":
console.debug(f"Unzip path: {path_ops.which('unzip')}")
Expand Down
26 changes: 3 additions & 23 deletions reflex/utils/path_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import stat
from pathlib import Path

from reflex import constants
from reflex.config import environment, get_config

# Shorthand for join.
Expand Down Expand Up @@ -146,15 +145,6 @@ def which(program: str | Path) -> Path | None:
return Path(which_result) if which_result else None


def use_system_node() -> bool:
"""Check if the system node should be used.

Returns:
Whether the system node should be used.
"""
return environment.REFLEX_USE_SYSTEM_NODE.get()


def use_system_bun() -> bool:
"""Check if the system bun should be used.

Expand All @@ -170,11 +160,7 @@ def get_node_bin_path() -> Path | None:
Returns:
The path to the node bin folder.
"""
bin_path = Path(constants.Node.BIN_PATH)
if not bin_path.exists():
path = which("node")
return path.parent.absolute() if path else None
return bin_path.absolute()
return bin_path.parent.absolute() if (bin_path := get_node_path()) else None


def get_node_path() -> Path | None:
Expand All @@ -183,10 +169,7 @@ def get_node_path() -> Path | None:
Returns:
The path to the node binary file.
"""
node_path = Path(constants.Node.PATH)
if use_system_node() or not node_path.exists():
node_path = which("node")
return node_path
return which("node")


def get_npm_path() -> Path | None:
Expand All @@ -195,10 +178,7 @@ def get_npm_path() -> Path | None:
Returns:
The path to the npm binary file.
"""
npm_path = Path(constants.Node.NPM_PATH)
if use_system_node() or not npm_path.exists():
npm_path = which("npm")
return npm_path.absolute() if npm_path else None
return npm_path.absolute() if (npm_path := which("npm")) else None


def get_bun_path() -> Path | None:
Expand Down
Loading
Loading