Преглед изворни кода

Automatic Install FNM and Node for Windows: (#1566)

Elijah Ahianyo пре 1 година
родитељ
комит
98fae89319
8 измењених фајлова са 228 додато и 67 уклоњено
  1. 101 7
      poetry.lock
  2. 1 0
      pyproject.toml
  3. 32 9
      reflex/constants.py
  4. 2 0
      reflex/utils/build.py
  5. 2 3
      reflex/utils/exec.py
  6. 62 34
      reflex/utils/prerequisites.py
  7. 1 1
      tests/test_testing.py
  8. 27 13
      tests/test_utils.py

+ 101 - 7
poetry.lock

@@ -1,9 +1,10 @@
-# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand.
 
 
 [[package]]
 [[package]]
 name = "alembic"
 name = "alembic"
 version = "1.11.1"
 version = "1.11.1"
 description = "A database migration tool for SQLAlchemy."
 description = "A database migration tool for SQLAlchemy."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -25,6 +26,7 @@ tz = ["python-dateutil"]
 name = "anyio"
 name = "anyio"
 version = "3.7.1"
 version = "3.7.1"
 description = "High level compatibility layer for multiple asynchronous event loop implementations"
 description = "High level compatibility layer for multiple asynchronous event loop implementations"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -47,6 +49,7 @@ trio = ["trio (<0.22)"]
 name = "async-timeout"
 name = "async-timeout"
 version = "4.0.2"
 version = "4.0.2"
 description = "Timeout context manager for asyncio programs"
 description = "Timeout context manager for asyncio programs"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
@@ -61,6 +64,7 @@ typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
 name = "asynctest"
 name = "asynctest"
 version = "0.13.0"
 version = "0.13.0"
 description = "Enhance the standard unittest package with features for testing asyncio libraries"
 description = "Enhance the standard unittest package with features for testing asyncio libraries"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.5"
 python-versions = ">=3.5"
 files = [
 files = [
@@ -72,6 +76,7 @@ files = [
 name = "attrs"
 name = "attrs"
 version = "23.1.0"
 version = "23.1.0"
 description = "Classes Without Boilerplate"
 description = "Classes Without Boilerplate"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -93,6 +98,7 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte
 name = "bidict"
 name = "bidict"
 version = "0.22.1"
 version = "0.22.1"
 description = "The bidirectional mapping library for Python."
 description = "The bidirectional mapping library for Python."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -109,6 +115,7 @@ test = ["hypothesis", "pytest", "pytest-benchmark[histogram]", "pytest-cov", "py
 name = "black"
 name = "black"
 version = "22.12.0"
 version = "22.12.0"
 description = "The uncompromising code formatter."
 description = "The uncompromising code formatter."
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -145,6 +152,7 @@ uvloop = ["uvloop (>=0.15.2)"]
 name = "certifi"
 name = "certifi"
 version = "2023.7.22"
 version = "2023.7.22"
 description = "Python package for providing Mozilla's CA Bundle."
 description = "Python package for providing Mozilla's CA Bundle."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
@@ -156,6 +164,7 @@ files = [
 name = "cffi"
 name = "cffi"
 version = "1.15.1"
 version = "1.15.1"
 description = "Foreign Function Interface for Python calling C code."
 description = "Foreign Function Interface for Python calling C code."
+category = "dev"
 optional = false
 optional = false
 python-versions = "*"
 python-versions = "*"
 files = [
 files = [
@@ -232,6 +241,7 @@ pycparser = "*"
 name = "cfgv"
 name = "cfgv"
 version = "3.3.1"
 version = "3.3.1"
 description = "Validate configuration and produce human readable error messages."
 description = "Validate configuration and produce human readable error messages."
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.6.1"
 python-versions = ">=3.6.1"
 files = [
 files = [
@@ -243,6 +253,7 @@ files = [
 name = "click"
 name = "click"
 version = "8.1.6"
 version = "8.1.6"
 description = "Composable command line interface toolkit"
 description = "Composable command line interface toolkit"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -258,6 +269,7 @@ importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
 name = "cloudpickle"
 name = "cloudpickle"
 version = "2.2.1"
 version = "2.2.1"
 description = "Extended pickling support for Python objects"
 description = "Extended pickling support for Python objects"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
@@ -269,6 +281,7 @@ files = [
 name = "colorama"
 name = "colorama"
 version = "0.4.6"
 version = "0.4.6"
 description = "Cross-platform colored terminal text."
 description = "Cross-platform colored terminal text."
+category = "main"
 optional = false
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
 files = [
 files = [
@@ -280,6 +293,7 @@ files = [
 name = "coverage"
 name = "coverage"
 version = "7.2.7"
 version = "7.2.7"
 description = "Code coverage measurement for Python"
 description = "Code coverage measurement for Python"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -355,6 +369,7 @@ toml = ["tomli"]
 name = "darglint"
 name = "darglint"
 version = "1.8.1"
 version = "1.8.1"
 description = "A utility for ensuring Google-style docstrings stay up to date with the source code."
 description = "A utility for ensuring Google-style docstrings stay up to date with the source code."
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.6,<4.0"
 python-versions = ">=3.6,<4.0"
 files = [
 files = [
@@ -366,6 +381,7 @@ files = [
 name = "distlib"
 name = "distlib"
 version = "0.3.7"
 version = "0.3.7"
 description = "Distribution utilities"
 description = "Distribution utilities"
+category = "dev"
 optional = false
 optional = false
 python-versions = "*"
 python-versions = "*"
 files = [
 files = [
@@ -377,6 +393,7 @@ files = [
 name = "exceptiongroup"
 name = "exceptiongroup"
 version = "1.1.2"
 version = "1.1.2"
 description = "Backport of PEP 654 (exception groups)"
 description = "Backport of PEP 654 (exception groups)"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -391,6 +408,7 @@ test = ["pytest (>=6)"]
 name = "fastapi"
 name = "fastapi"
 version = "0.96.1"
 version = "0.96.1"
 description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
 description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -412,6 +430,7 @@ test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==23.1.0)", "coverage[toml] (>=6
 name = "filelock"
 name = "filelock"
 version = "3.12.2"
 version = "3.12.2"
 description = "A platform independent file lock."
 description = "A platform independent file lock."
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -427,6 +446,7 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "p
 name = "greenlet"
 name = "greenlet"
 version = "2.0.2"
 version = "2.0.2"
 description = "Lightweight in-process concurrent programming"
 description = "Lightweight in-process concurrent programming"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
 files = [
 files = [
@@ -500,6 +520,7 @@ test = ["objgraph", "psutil"]
 name = "gunicorn"
 name = "gunicorn"
 version = "20.1.0"
 version = "20.1.0"
 description = "WSGI HTTP Server for UNIX"
 description = "WSGI HTTP Server for UNIX"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.5"
 python-versions = ">=3.5"
 files = [
 files = [
@@ -520,6 +541,7 @@ tornado = ["tornado (>=0.2)"]
 name = "h11"
 name = "h11"
 version = "0.14.0"
 version = "0.14.0"
 description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
 description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -534,6 +556,7 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
 name = "httpcore"
 name = "httpcore"
 version = "0.17.3"
 version = "0.17.3"
 description = "A minimal low-level HTTP client."
 description = "A minimal low-level HTTP client."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -545,16 +568,17 @@ files = [
 anyio = ">=3.0,<5.0"
 anyio = ">=3.0,<5.0"
 certifi = "*"
 certifi = "*"
 h11 = ">=0.13,<0.15"
 h11 = ">=0.13,<0.15"
-sniffio = "==1.*"
+sniffio = ">=1.0.0,<2.0.0"
 
 
 [package.extras]
 [package.extras]
 http2 = ["h2 (>=3,<5)"]
 http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (==1.*)"]
+socks = ["socksio (>=1.0.0,<2.0.0)"]
 
 
 [[package]]
 [[package]]
 name = "httpx"
 name = "httpx"
 version = "0.24.1"
 version = "0.24.1"
 description = "The next generation HTTP client."
 description = "The next generation HTTP client."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -570,14 +594,15 @@ sniffio = "*"
 
 
 [package.extras]
 [package.extras]
 brotli = ["brotli", "brotlicffi"]
 brotli = ["brotli", "brotlicffi"]
-cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
+cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"]
 http2 = ["h2 (>=3,<5)"]
 http2 = ["h2 (>=3,<5)"]
-socks = ["socksio (==1.*)"]
+socks = ["socksio (>=1.0.0,<2.0.0)"]
 
 
 [[package]]
 [[package]]
 name = "identify"
 name = "identify"
 version = "2.5.26"
 version = "2.5.26"
 description = "File identification library for Python"
 description = "File identification library for Python"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.8"
 python-versions = ">=3.8"
 files = [
 files = [
@@ -592,6 +617,7 @@ license = ["ukkonen"]
 name = "idna"
 name = "idna"
 version = "3.4"
 version = "3.4"
 description = "Internationalized Domain Names in Applications (IDNA)"
 description = "Internationalized Domain Names in Applications (IDNA)"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.5"
 python-versions = ">=3.5"
 files = [
 files = [
@@ -603,6 +629,7 @@ files = [
 name = "importlib-metadata"
 name = "importlib-metadata"
 version = "6.7.0"
 version = "6.7.0"
 description = "Read metadata from Python packages"
 description = "Read metadata from Python packages"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -623,6 +650,7 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs
 name = "importlib-resources"
 name = "importlib-resources"
 version = "5.12.0"
 version = "5.12.0"
 description = "Read resources from Python packages"
 description = "Read resources from Python packages"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -641,6 +669,7 @@ testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-chec
 name = "iniconfig"
 name = "iniconfig"
 version = "2.0.0"
 version = "2.0.0"
 description = "brain-dead simple config-ini parsing"
 description = "brain-dead simple config-ini parsing"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -652,6 +681,7 @@ files = [
 name = "jinja2"
 name = "jinja2"
 version = "3.1.2"
 version = "3.1.2"
 description = "A very fast and expressive template engine."
 description = "A very fast and expressive template engine."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -669,6 +699,7 @@ i18n = ["Babel (>=2.7)"]
 name = "mako"
 name = "mako"
 version = "1.2.4"
 version = "1.2.4"
 description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
 description = "A super-fast templating language that borrows the best ideas from the existing templating languages."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -689,6 +720,7 @@ testing = ["pytest"]
 name = "markdown-it-py"
 name = "markdown-it-py"
 version = "2.2.0"
 version = "2.2.0"
 description = "Python port of markdown-it. Markdown parsing, done right!"
 description = "Python port of markdown-it. Markdown parsing, done right!"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -714,6 +746,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
 name = "markupsafe"
 name = "markupsafe"
 version = "2.1.3"
 version = "2.1.3"
 description = "Safely add untrusted strings to HTML/XML markup."
 description = "Safely add untrusted strings to HTML/XML markup."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -773,6 +806,7 @@ files = [
 name = "mdurl"
 name = "mdurl"
 version = "0.1.2"
 version = "0.1.2"
 description = "Markdown URL utilities"
 description = "Markdown URL utilities"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -784,6 +818,7 @@ files = [
 name = "mypy-extensions"
 name = "mypy-extensions"
 version = "1.0.0"
 version = "1.0.0"
 description = "Type system extensions for programs checked with the mypy type checker."
 description = "Type system extensions for programs checked with the mypy type checker."
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.5"
 python-versions = ">=3.5"
 files = [
 files = [
@@ -795,6 +830,7 @@ files = [
 name = "nodeenv"
 name = "nodeenv"
 version = "1.8.0"
 version = "1.8.0"
 description = "Node.js virtual environment builder"
 description = "Node.js virtual environment builder"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
 python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
 files = [
 files = [
@@ -809,6 +845,7 @@ setuptools = "*"
 name = "numpy"
 name = "numpy"
 version = "1.21.6"
 version = "1.21.6"
 description = "NumPy is the fundamental package for array computing with Python."
 description = "NumPy is the fundamental package for array computing with Python."
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7,<3.11"
 python-versions = ">=3.7,<3.11"
 files = [
 files = [
@@ -849,6 +886,7 @@ files = [
 name = "numpy"
 name = "numpy"
 version = "1.24.4"
 version = "1.24.4"
 description = "Fundamental package for array computing in Python"
 description = "Fundamental package for array computing in Python"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.8"
 python-versions = ">=3.8"
 files = [
 files = [
@@ -886,6 +924,7 @@ files = [
 name = "numpy"
 name = "numpy"
 version = "1.25.2"
 version = "1.25.2"
 description = "Fundamental package for array computing in Python"
 description = "Fundamental package for array computing in Python"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.9"
 python-versions = ">=3.9"
 files = [
 files = [
@@ -920,6 +959,7 @@ files = [
 name = "outcome"
 name = "outcome"
 version = "1.2.0"
 version = "1.2.0"
 description = "Capture the outcome of Python function calls."
 description = "Capture the outcome of Python function calls."
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -934,6 +974,7 @@ attrs = ">=19.2.0"
 name = "packaging"
 name = "packaging"
 version = "23.1"
 version = "23.1"
 description = "Core utilities for Python packages"
 description = "Core utilities for Python packages"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -945,6 +986,7 @@ files = [
 name = "pandas"
 name = "pandas"
 version = "1.1.5"
 version = "1.1.5"
 description = "Powerful data structures for data analysis, time series, and statistics"
 description = "Powerful data structures for data analysis, time series, and statistics"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.6.1"
 python-versions = ">=3.6.1"
 files = [
 files = [
@@ -986,6 +1028,7 @@ test = ["hypothesis (>=3.58)", "pytest (>=4.0.2)", "pytest-xdist"]
 name = "pandas"
 name = "pandas"
 version = "1.5.3"
 version = "1.5.3"
 description = "Powerful data structures for data analysis, time series, and statistics"
 description = "Powerful data structures for data analysis, time series, and statistics"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.8"
 python-versions = ">=3.8"
 files = [
 files = [
@@ -1034,6 +1077,7 @@ test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"]
 name = "pathspec"
 name = "pathspec"
 version = "0.11.2"
 version = "0.11.2"
 description = "Utility library for gitignore style pattern matching of file paths."
 description = "Utility library for gitignore style pattern matching of file paths."
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1045,6 +1089,7 @@ files = [
 name = "platformdirs"
 name = "platformdirs"
 version = "3.10.0"
 version = "3.10.0"
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1063,6 +1108,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co
 name = "plotly"
 name = "plotly"
 version = "5.15.0"
 version = "5.15.0"
 description = "An open-source, interactive data visualization library for Python"
 description = "An open-source, interactive data visualization library for Python"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
@@ -1078,6 +1124,7 @@ tenacity = ">=6.2.0"
 name = "pluggy"
 name = "pluggy"
 version = "1.2.0"
 version = "1.2.0"
 description = "plugin and hook calling mechanisms for python"
 description = "plugin and hook calling mechanisms for python"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1096,6 +1143,7 @@ testing = ["pytest", "pytest-benchmark"]
 name = "pre-commit"
 name = "pre-commit"
 version = "3.3.3"
 version = "3.3.3"
 description = "A framework for managing and maintaining multi-language pre-commit hooks."
 description = "A framework for managing and maintaining multi-language pre-commit hooks."
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.8"
 python-versions = ">=3.8"
 files = [
 files = [
@@ -1114,6 +1162,7 @@ virtualenv = ">=20.10.0"
 name = "psutil"
 name = "psutil"
 version = "5.9.5"
 version = "5.9.5"
 description = "Cross-platform lib for process and system monitoring in Python."
 description = "Cross-platform lib for process and system monitoring in Python."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
 files = [
@@ -1140,6 +1189,7 @@ test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
 name = "pycparser"
 name = "pycparser"
 version = "2.21"
 version = "2.21"
 description = "C parser in Python"
 description = "C parser in Python"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
 files = [
@@ -1151,6 +1201,7 @@ files = [
 name = "pydantic"
 name = "pydantic"
 version = "1.10.12"
 version = "1.10.12"
 description = "Data validation and settings management using python type hints"
 description = "Data validation and settings management using python type hints"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1203,6 +1254,7 @@ email = ["email-validator (>=1.0.3)"]
 name = "pygments"
 name = "pygments"
 version = "2.15.1"
 version = "2.15.1"
 description = "Pygments is a syntax highlighting package written in Python."
 description = "Pygments is a syntax highlighting package written in Python."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1217,6 +1269,7 @@ plugins = ["importlib-metadata"]
 name = "pyright"
 name = "pyright"
 version = "1.1.318"
 version = "1.1.318"
 description = "Command line wrapper for pyright"
 description = "Command line wrapper for pyright"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1236,6 +1289,7 @@ dev = ["twine (>=3.4.1)"]
 name = "pysocks"
 name = "pysocks"
 version = "1.7.1"
 version = "1.7.1"
 description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
 description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information."
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 files = [
 files = [
@@ -1248,6 +1302,7 @@ files = [
 name = "pytest"
 name = "pytest"
 version = "7.4.0"
 version = "7.4.0"
 description = "pytest: simple powerful testing with Python"
 description = "pytest: simple powerful testing with Python"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1271,6 +1326,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no
 name = "pytest-asyncio"
 name = "pytest-asyncio"
 version = "0.20.3"
 version = "0.20.3"
 description = "Pytest support for asyncio"
 description = "Pytest support for asyncio"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1290,6 +1346,7 @@ testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy
 name = "pytest-cov"
 name = "pytest-cov"
 version = "4.1.0"
 version = "4.1.0"
 description = "Pytest plugin for measuring coverage."
 description = "Pytest plugin for measuring coverage."
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1308,6 +1365,7 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale
 name = "pytest-mock"
 name = "pytest-mock"
 version = "3.11.1"
 version = "3.11.1"
 description = "Thin-wrapper around the mock package for easier use with pytest"
 description = "Thin-wrapper around the mock package for easier use with pytest"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1325,6 +1383,7 @@ dev = ["pre-commit", "pytest-asyncio", "tox"]
 name = "python-dateutil"
 name = "python-dateutil"
 version = "2.8.2"
 version = "2.8.2"
 description = "Extensions to the standard Python datetime module"
 description = "Extensions to the standard Python datetime module"
+category = "dev"
 optional = false
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
 files = [
 files = [
@@ -1339,6 +1398,7 @@ six = ">=1.5"
 name = "python-dotenv"
 name = "python-dotenv"
 version = "0.13.0"
 version = "0.13.0"
 description = "Add .env support to your django/flask apps in development and deployments"
 description = "Add .env support to your django/flask apps in development and deployments"
+category = "main"
 optional = false
 optional = false
 python-versions = "*"
 python-versions = "*"
 files = [
 files = [
@@ -1353,6 +1413,7 @@ cli = ["click (>=5.0)"]
 name = "python-engineio"
 name = "python-engineio"
 version = "4.5.1"
 version = "4.5.1"
 description = "Engine.IO server and client for Python"
 description = "Engine.IO server and client for Python"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
@@ -1369,6 +1430,7 @@ docs = ["sphinx"]
 name = "python-multipart"
 name = "python-multipart"
 version = "0.0.5"
 version = "0.0.5"
 description = "A streaming multipart parser for Python"
 description = "A streaming multipart parser for Python"
+category = "main"
 optional = false
 optional = false
 python-versions = "*"
 python-versions = "*"
 files = [
 files = [
@@ -1382,6 +1444,7 @@ six = ">=1.4.0"
 name = "python-socketio"
 name = "python-socketio"
 version = "5.8.0"
 version = "5.8.0"
 description = "Socket.IO server and client for Python"
 description = "Socket.IO server and client for Python"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
@@ -1401,6 +1464,7 @@ client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
 name = "pytz"
 name = "pytz"
 version = "2023.3"
 version = "2023.3"
 description = "World timezone definitions, modern and historical"
 description = "World timezone definitions, modern and historical"
+category = "dev"
 optional = false
 optional = false
 python-versions = "*"
 python-versions = "*"
 files = [
 files = [
@@ -1412,6 +1476,7 @@ files = [
 name = "pyyaml"
 name = "pyyaml"
 version = "6.0.1"
 version = "6.0.1"
 description = "YAML parser and emitter for Python"
 description = "YAML parser and emitter for Python"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
@@ -1461,6 +1526,7 @@ files = [
 name = "redis"
 name = "redis"
 version = "4.6.0"
 version = "4.6.0"
 description = "Python client for Redis database and key-value store"
 description = "Python client for Redis database and key-value store"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1481,6 +1547,7 @@ ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"
 name = "rich"
 name = "rich"
 version = "13.5.1"
 version = "13.5.1"
 description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
 description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7.0"
 python-versions = ">=3.7.0"
 files = [
 files = [
@@ -1500,6 +1567,7 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
 name = "ruff"
 name = "ruff"
 version = "0.0.244"
 version = "0.0.244"
 description = "An extremely fast Python linter, written in Rust."
 description = "An extremely fast Python linter, written in Rust."
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1525,6 +1593,7 @@ files = [
 name = "selenium"
 name = "selenium"
 version = "4.10.0"
 version = "4.10.0"
 description = ""
 description = ""
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1542,6 +1611,7 @@ urllib3 = {version = ">=1.26,<3", extras = ["socks"]}
 name = "setuptools"
 name = "setuptools"
 version = "68.0.0"
 version = "68.0.0"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1558,6 +1628,7 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (
 name = "six"
 name = "six"
 version = "1.16.0"
 version = "1.16.0"
 description = "Python 2 and 3 compatibility utilities"
 description = "Python 2 and 3 compatibility utilities"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
 files = [
 files = [
@@ -1569,6 +1640,7 @@ files = [
 name = "sniffio"
 name = "sniffio"
 version = "1.3.0"
 version = "1.3.0"
 description = "Sniff out which async library your code is running under"
 description = "Sniff out which async library your code is running under"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1580,6 +1652,7 @@ files = [
 name = "sortedcontainers"
 name = "sortedcontainers"
 version = "2.4.0"
 version = "2.4.0"
 description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
 description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
+category = "dev"
 optional = false
 optional = false
 python-versions = "*"
 python-versions = "*"
 files = [
 files = [
@@ -1591,6 +1664,7 @@ files = [
 name = "sqlalchemy"
 name = "sqlalchemy"
 version = "1.4.41"
 version = "1.4.41"
 description = "Database Abstraction Library"
 description = "Database Abstraction Library"
+category = "main"
 optional = false
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
 files = [
 files = [
@@ -1638,7 +1712,7 @@ files = [
 ]
 ]
 
 
 [package.dependencies]
 [package.dependencies]
-greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\")"}
+greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and platform_machine == \"aarch64\" or python_version >= \"3\" and platform_machine == \"ppc64le\" or python_version >= \"3\" and platform_machine == \"x86_64\" or python_version >= \"3\" and platform_machine == \"amd64\" or python_version >= \"3\" and platform_machine == \"AMD64\" or python_version >= \"3\" and platform_machine == \"win32\" or python_version >= \"3\" and platform_machine == \"WIN32\""}
 importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
 importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
 
 
 [package.extras]
 [package.extras]
@@ -1666,6 +1740,7 @@ sqlcipher = ["sqlcipher3-binary"]
 name = "sqlalchemy2-stubs"
 name = "sqlalchemy2-stubs"
 version = "0.0.2a35"
 version = "0.0.2a35"
 description = "Typing Stubs for SQLAlchemy 1.4"
 description = "Typing Stubs for SQLAlchemy 1.4"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
@@ -1680,6 +1755,7 @@ typing-extensions = ">=3.7.4"
 name = "sqlmodel"
 name = "sqlmodel"
 version = "0.0.8"
 version = "0.0.8"
 description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness."
 description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6.1,<4.0.0"
 python-versions = ">=3.6.1,<4.0.0"
 files = [
 files = [
@@ -1696,6 +1772,7 @@ sqlalchemy2-stubs = "*"
 name = "starlette"
 name = "starlette"
 version = "0.27.0"
 version = "0.27.0"
 description = "The little ASGI library that shines."
 description = "The little ASGI library that shines."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1714,6 +1791,7 @@ full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyam
 name = "starlette-admin"
 name = "starlette-admin"
 version = "0.9.0"
 version = "0.9.0"
 description = "Fast, beautiful and extensible administrative interface framework for Starlette/FastApi applications"
 description = "Fast, beautiful and extensible administrative interface framework for Starlette/FastApi applications"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1736,6 +1814,7 @@ test = ["aiomysql (>=0.1.1,<0.2.0)", "aiosqlite (>=0.17.0,<0.20.0)", "arrow (>=1
 name = "tenacity"
 name = "tenacity"
 version = "8.2.2"
 version = "8.2.2"
 description = "Retry code until it succeeds"
 description = "Retry code until it succeeds"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
@@ -1750,6 +1829,7 @@ doc = ["reno", "sphinx", "tornado (>=4.5)"]
 name = "toml"
 name = "toml"
 version = "0.10.2"
 version = "0.10.2"
 description = "Python Library for Tom's Obvious, Minimal Language"
 description = "Python Library for Tom's Obvious, Minimal Language"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 files = [
 files = [
@@ -1761,6 +1841,7 @@ files = [
 name = "tomli"
 name = "tomli"
 version = "2.0.1"
 version = "2.0.1"
 description = "A lil' TOML parser"
 description = "A lil' TOML parser"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1772,6 +1853,7 @@ files = [
 name = "trio"
 name = "trio"
 version = "0.22.2"
 version = "0.22.2"
 description = "A friendly Python library for async concurrency and I/O"
 description = "A friendly Python library for async concurrency and I/O"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1792,6 +1874,7 @@ sortedcontainers = "*"
 name = "trio-websocket"
 name = "trio-websocket"
 version = "0.10.3"
 version = "0.10.3"
 description = "WebSocket library for Trio"
 description = "WebSocket library for Trio"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1808,6 +1891,7 @@ wsproto = ">=0.14"
 name = "typed-ast"
 name = "typed-ast"
 version = "1.5.5"
 version = "1.5.5"
 description = "a fork of Python 2 and 3 ast modules with type comment support"
 description = "a fork of Python 2 and 3 ast modules with type comment support"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
@@ -1858,6 +1942,7 @@ files = [
 name = "typer"
 name = "typer"
 version = "0.4.2"
 version = "0.4.2"
 description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
 description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
@@ -1878,6 +1963,7 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=5.2,<6.0)", "isort (>=5.0.6,<6.
 name = "typing-extensions"
 name = "typing-extensions"
 version = "4.7.1"
 version = "4.7.1"
 description = "Backported and Experimental Type Hints for Python 3.7+"
 description = "Backported and Experimental Type Hints for Python 3.7+"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1889,6 +1975,7 @@ files = [
 name = "urllib3"
 name = "urllib3"
 version = "2.0.4"
 version = "2.0.4"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
 description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1909,6 +1996,7 @@ zstd = ["zstandard (>=0.18.0)"]
 name = "uvicorn"
 name = "uvicorn"
 version = "0.20.0"
 version = "0.20.0"
 description = "The lightning-fast ASGI server."
 description = "The lightning-fast ASGI server."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1928,6 +2016,7 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)",
 name = "virtualenv"
 name = "virtualenv"
 version = "20.24.2"
 version = "20.24.2"
 description = "Virtual Python Environment builder"
 description = "Virtual Python Environment builder"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -1948,6 +2037,7 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess
 name = "watchdog"
 name = "watchdog"
 version = "2.3.1"
 version = "2.3.1"
 description = "Filesystem events monitoring"
 description = "Filesystem events monitoring"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.6"
 python-versions = ">=3.6"
 files = [
 files = [
@@ -1988,6 +2078,7 @@ watchmedo = ["PyYAML (>=3.10)"]
 name = "watchfiles"
 name = "watchfiles"
 version = "0.19.0"
 version = "0.19.0"
 description = "Simple, modern and high performance file watching and code reload in python."
 description = "Simple, modern and high performance file watching and code reload in python."
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -2022,6 +2113,7 @@ anyio = ">=3.0.0"
 name = "websockets"
 name = "websockets"
 version = "10.4"
 version = "10.4"
 description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
 description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -2100,6 +2192,7 @@ files = [
 name = "wsproto"
 name = "wsproto"
 version = "1.2.0"
 version = "1.2.0"
 description = "WebSockets state-machine based protocol implementation"
 description = "WebSockets state-machine based protocol implementation"
+category = "dev"
 optional = false
 optional = false
 python-versions = ">=3.7.0"
 python-versions = ">=3.7.0"
 files = [
 files = [
@@ -2114,6 +2207,7 @@ h11 = ">=0.9.0,<1"
 name = "zipp"
 name = "zipp"
 version = "3.15.0"
 version = "3.15.0"
 description = "Backport of pathlib-compatible object wrapper for zip files"
 description = "Backport of pathlib-compatible object wrapper for zip files"
+category = "main"
 optional = false
 optional = false
 python-versions = ">=3.7"
 python-versions = ">=3.7"
 files = [
 files = [
@@ -2128,4 +2222,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
 [metadata]
 [metadata]
 lock-version = "2.0"
 lock-version = "2.0"
 python-versions = "^3.7"
 python-versions = "^3.7"
-content-hash = "ac27016107e8a033aa39d9a712d3ef685132e22ede599a26214b17da6ff35829"
+content-hash = "ba03a445b7e59587264636a98b52595e020ae58570ac79b0fb1b9564c9769c59"

+ 1 - 0
pyproject.toml

@@ -46,6 +46,7 @@ starlette-admin = "^0.9.0"
 python-dotenv = "^0.13.0"
 python-dotenv = "^0.13.0"
 importlib-metadata = {version = "^6.7.0", python = ">=3.7, <3.8"}
 importlib-metadata = {version = "^6.7.0", python = ">=3.7, <3.8"}
 alembic = "^1.11.1"
 alembic = "^1.11.1"
+platformdirs = "^3.10.0"
 
 
 [tool.poetry.group.dev.dependencies]
 [tool.poetry.group.dev.dependencies]
 pytest = "^7.1.2"
 pytest = "^7.1.2"

+ 32 - 9
reflex/constants.py

@@ -8,12 +8,16 @@ from enum import Enum
 from types import SimpleNamespace
 from types import SimpleNamespace
 from typing import Any, Type
 from typing import Any, Type
 
 
+from platformdirs import PlatformDirs
+
 # importlib is only available for Python 3.8+ so we need the backport for Python 3.7
 # importlib is only available for Python 3.8+ so we need the backport for Python 3.7
 try:
 try:
     from importlib import metadata
     from importlib import metadata
 except ImportError:
 except ImportError:
     import importlib_metadata as metadata  # pyright: ignore[reportMissingImports]
     import importlib_metadata as metadata  # pyright: ignore[reportMissingImports]
 
 
+IS_WINDOWS = platform.system() == "Windows"
+
 
 
 def get_value(key: str, default: Any = None, type_: Type = str) -> Type:
 def get_value(key: str, default: Any = None, type_: Type = str) -> Type:
     """Get the value for the constant.
     """Get the value for the constant.
@@ -48,7 +52,17 @@ VERSION = metadata.version(MODULE_NAME)
 
 
 # Files and directories used to init a new project.
 # Files and directories used to init a new project.
 # The directory to store reflex dependencies.
 # The directory to store reflex dependencies.
-REFLEX_DIR = os.path.expandvars(os.path.join("$HOME", f".{MODULE_NAME}"))
+REFLEX_DIR = (
+    # on windows, we use C:/Users/<username>/AppData/Local/reflex.
+    PlatformDirs(MODULE_NAME, False).user_data_dir
+    if IS_WINDOWS
+    else 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.
@@ -79,29 +93,38 @@ BUN_INSTALL_URL = "https://bun.sh/install"
 # NVM / Node config.
 # NVM / Node config.
 # The NVM version.
 # The NVM version.
 NVM_VERSION = "0.39.1"
 NVM_VERSION = "0.39.1"
+# The FNM version.
+FNM_VERSION = "1.35.1"
 # The Node version.
 # The Node version.
 NODE_VERSION = "18.17.0"
 NODE_VERSION = "18.17.0"
 # The minimum required node version.
 # The minimum required node version.
 NODE_VERSION_MIN = "16.8.0"
 NODE_VERSION_MIN = "16.8.0"
 # The directory to store nvm.
 # The directory to store nvm.
 NVM_DIR = os.path.join(REFLEX_DIR, ".nvm")
 NVM_DIR = os.path.join(REFLEX_DIR, ".nvm")
+# The directory to store fnm.
+FNM_DIR = os.path.join(REFLEX_DIR, "fnm")
+# The fnm executable binary.
+FNM_EXE = os.path.join(FNM_DIR, "fnm.exe")
 # The nvm path.
 # The nvm path.
 NVM_PATH = os.path.join(NVM_DIR, "nvm.sh")
 NVM_PATH = os.path.join(NVM_DIR, "nvm.sh")
 # The node bin path.
 # 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")
+NODE_BIN_PATH = (
+    os.path.join(NVM_DIR, "versions", "node", f"v{NODE_VERSION}", "bin")
+    if not IS_WINDOWS
+    else os.path.join(FNM_DIR, "node-versions", f"v{NODE_VERSION}", "installation")
 )
 )
+# The default path where node is installed.
+NODE_PATH = os.path.join(NODE_BIN_PATH, "node.exe" if IS_WINDOWS else "node")
 # The default path where npm is installed.
 # The default path where npm is installed.
-NPM_PATH = (
-    "npm" if platform.system() == "Windows" else os.path.join(NODE_BIN_PATH, "npm")
-)
+NPM_PATH = os.path.join(NODE_BIN_PATH, "npm")
 # The URL to the nvm install script.
 # The URL to the nvm install script.
 NVM_INSTALL_URL = (
 NVM_INSTALL_URL = (
     f"https://raw.githubusercontent.com/nvm-sh/nvm/v{NVM_VERSION}/install.sh"
     f"https://raw.githubusercontent.com/nvm-sh/nvm/v{NVM_VERSION}/install.sh"
 )
 )
-
+# The URL to the fnm release binary
+FNM_WINDOWS_INSTALL_URL = (
+    f"https://github.com/Schniz/fnm/releases/download/v{FNM_VERSION}/fnm-windows.zip"
+)
 # 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"

+ 2 - 0
reflex/utils/build.py

@@ -124,6 +124,7 @@ def export(
         process = processes.new_process(
         process = processes.new_process(
             [prerequisites.get_package_manager(), "run", command],
             [prerequisites.get_package_manager(), "run", command],
             cwd=constants.WEB_DIR,
             cwd=constants.WEB_DIR,
+            shell=constants.IS_WINDOWS,
         )
         )
         processes.show_progress("Creating Production Build", process, checkpoints)
         processes.show_progress("Creating Production Build", process, checkpoints)
 
 
@@ -201,6 +202,7 @@ def setup_frontend(
             ],
             ],
             cwd=constants.WEB_DIR,
             cwd=constants.WEB_DIR,
             stdout=subprocess.DEVNULL,
             stdout=subprocess.DEVNULL,
+            shell=constants.IS_WINDOWS,
         )
         )
 
 
 
 

+ 2 - 3
reflex/utils/exec.py

@@ -30,8 +30,7 @@ def run_process_and_launch_url(
         run_command: The command to run.
         run_command: The command to run.
     """
     """
     process = processes.new_process(
     process = processes.new_process(
-        run_command,
-        cwd=constants.WEB_DIR,
+        run_command, cwd=constants.WEB_DIR, shell=constants.IS_WINDOWS
     )
     )
 
 
     if process.stdout:
     if process.stdout:
@@ -137,7 +136,7 @@ def run_backend_prod(
             str(port),
             str(port),
             f"{app_name}:{constants.APP_VAR}",
             f"{app_name}:{constants.APP_VAR}",
         ]
         ]
-        if prerequisites.IS_WINDOWS
+        if constants.IS_WINDOWS
         else [
         else [
             *constants.RUN_BACKEND_PROD,
             *constants.RUN_BACKEND_PROD,
             "--bind",
             "--bind",

+ 62 - 34
reflex/utils/prerequisites.py

@@ -5,10 +5,10 @@ from __future__ import annotations
 import glob
 import glob
 import json
 import json
 import os
 import os
-import platform
 import re
 import re
 import sys
 import sys
 import tempfile
 import tempfile
+import zipfile
 from fileinput import FileInput
 from fileinput import FileInput
 from pathlib import Path
 from pathlib import Path
 from types import ModuleType
 from types import ModuleType
@@ -24,8 +24,6 @@ from reflex import constants, model
 from reflex.config import get_config
 from reflex.config import get_config
 from reflex.utils import console, path_ops, processes
 from reflex.utils import console, path_ops, processes
 
 
-IS_WINDOWS = platform.system() == "Windows"
-
 
 
 def check_node_version() -> bool:
 def check_node_version() -> bool:
     """Check the version of Node.js.
     """Check the version of Node.js.
@@ -44,7 +42,7 @@ def check_node_version() -> bool:
     # Compare the version numbers
     # Compare the version numbers
     return (
     return (
         current_version >= version.parse(constants.NODE_VERSION_MIN)
         current_version >= version.parse(constants.NODE_VERSION_MIN)
-        if IS_WINDOWS
+        if constants.IS_WINDOWS
         else current_version == version.parse(constants.NODE_VERSION)
         else current_version == version.parse(constants.NODE_VERSION)
     )
     )
 
 
@@ -85,11 +83,9 @@ def get_install_package_manager() -> str:
     Returns:
     Returns:
         The path to the package manager.
         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 IS_WINDOWS:
-        return get_windows_package_manager()
+    if constants.IS_WINDOWS:
+        return constants.NPM_PATH
 
 
     # On other platforms, we use bun.
     # On other platforms, we use bun.
     return get_config().bun_path
     return get_config().bun_path
@@ -102,10 +98,6 @@ def get_package_manager() -> str:
     Returns:
     Returns:
         The path to the package manager.
         The path to the package manager.
     """
     """
-    get_config()
-
-    if IS_WINDOWS:
-        return get_windows_package_manager()
     return constants.NPM_PATH
     return constants.NPM_PATH
 
 
 
 
@@ -279,24 +271,59 @@ def download_and_run(url: str, *args, show_status: bool = False, **env):
     show(f"Installing {url}", process)
     show(f"Installing {url}", process)
 
 
 
 
-def install_node():
-    """Install nvm and nodejs for use by Reflex.
-       Independent of any existing system installations.
+def download_and_extract_fnm_zip(url: str):
+    """Download and run a script.
+
+    Args:
+        url: The url of the fnm release zip binary.
 
 
     Raises:
     Raises:
-        Exit: if installation failed
+        Exit: If an error occurs while downloading or extracting the FNM zip.
     """
     """
-    if IS_WINDOWS:
-        # See if existing node is good enough.
-        # On Windows, this must be installed manually, outside of Reflex.
-        if not check_node_version():
-            # We don't currently support auto install of node on Windows
-            # because NVM is not supported there
-            console.error(
-                f"Node.js version {constants.NODE_VERSION} or higher is required to run Reflex."
-            )
-            raise typer.Exit(1)
-        return
+    # TODO: make this OS agnostic
+    # Download the zip file
+    console.debug(f"Downloading {url}")
+    fnm_zip_file = f"{constants.FNM_DIR}\\fnm_windows.zip"
+    # Function to download and extract the FNM zip release
+    try:
+        # Download the FNM zip release
+        # TODO: show progress to improve UX
+        with httpx.stream("GET", url, follow_redirects=True) as response:
+            response.raise_for_status()
+            with open(fnm_zip_file, "wb") as output_file:
+                for chunk in response.iter_bytes():
+                    output_file.write(chunk)
+
+        # Extract the downloaded zip file
+        with zipfile.ZipFile(fnm_zip_file, "r") as zip_ref:
+            zip_ref.extractall(constants.FNM_DIR)
+
+        console.debug("FNM for Windows downloaded and extracted successfully.")
+    except Exception as e:
+        console.error(f"An error occurred while downloading fnm package: {e}")
+        raise typer.Exit(1) from e
+    finally:
+        # Clean up the downloaded zip file
+        path_ops.rm(fnm_zip_file)
+
+
+def install_node():
+    """Install nvm and nodejs for use by Reflex.
+    Independent of any existing system installations.
+    """
+    if constants.IS_WINDOWS:
+        path_ops.mkdir(constants.FNM_DIR)
+        if not os.path.exists(constants.FNM_EXE):
+            download_and_extract_fnm_zip(constants.FNM_WINDOWS_INSTALL_URL)
+
+        # Install node.
+        process = processes.new_process(
+            [
+                "powershell",
+                "-Command",
+                f'& "{constants.FNM_EXE}" install {constants.NODE_VERSION} --fnm-dir "{constants.FNM_DIR}"',
+            ],
+        )
     else:  # All other platforms (Linux, MacOS)
     else:  # All other platforms (Linux, MacOS)
         # TODO we can skip installation if check_node_version() checks out
         # TODO we can skip installation if check_node_version() checks out
         # Create the nvm directory and install.
         # Create the nvm directory and install.
@@ -314,7 +341,7 @@ def install_node():
             ],
             ],
             env=env,
             env=env,
         )
         )
-        processes.show_status("Installing node", process)
+    processes.show_status("Installing node", process)
 
 
 
 
 def install_bun():
 def install_bun():
@@ -324,7 +351,7 @@ def install_bun():
         FileNotFoundError: If required packages are not found.
         FileNotFoundError: If required packages are not found.
     """
     """
     # Bun is not supported on Windows.
     # Bun is not supported on Windows.
-    if IS_WINDOWS:
+    if constants.IS_WINDOWS:
         console.debug("Skipping bun installation on Windows.")
         console.debug("Skipping bun installation on Windows.")
         return
         return
 
 
@@ -352,6 +379,7 @@ def install_frontend_packages():
     process = processes.new_process(
     process = processes.new_process(
         [get_install_package_manager(), "install", "--loglevel", "silly"],
         [get_install_package_manager(), "install", "--loglevel", "silly"],
         cwd=constants.WEB_DIR,
         cwd=constants.WEB_DIR,
+        shell=constants.IS_WINDOWS,
     )
     )
     processes.show_status("Installing base frontend packages", process)
     processes.show_status("Installing base frontend packages", process)
 
 
@@ -361,6 +389,7 @@ def install_frontend_packages():
         process = processes.new_process(
         process = processes.new_process(
             [get_install_package_manager(), "add", *packages],
             [get_install_package_manager(), "add", *packages],
             cwd=constants.WEB_DIR,
             cwd=constants.WEB_DIR,
+            shell=constants.IS_WINDOWS,
         )
         )
         processes.show_status("Installing custom frontend packages", process)
         processes.show_status("Installing custom frontend packages", process)
 
 
@@ -375,7 +404,7 @@ def check_initialized(frontend: bool = True):
         Exit: If the app is not initialized.
         Exit: If the app is not initialized.
     """
     """
     has_config = os.path.exists(constants.CONFIG_FILE)
     has_config = os.path.exists(constants.CONFIG_FILE)
-    has_reflex_dir = not frontend or IS_WINDOWS or os.path.exists(constants.REFLEX_DIR)
+    has_reflex_dir = not frontend or os.path.exists(constants.REFLEX_DIR)
     has_web_dir = not frontend or os.path.exists(constants.WEB_DIR)
     has_web_dir = not frontend or os.path.exists(constants.WEB_DIR)
 
 
     # Check if the app is initialized.
     # Check if the app is initialized.
@@ -393,7 +422,7 @@ def check_initialized(frontend: bool = True):
         raise typer.Exit(1)
         raise typer.Exit(1)
 
 
     # Print a warning for Windows users.
     # Print a warning for Windows users.
-    if IS_WINDOWS:
+    if constants.IS_WINDOWS:
         console.warn(
         console.warn(
             """Windows Subsystem for Linux (WSL) is recommended for improving initial install times."""
             """Windows Subsystem for Linux (WSL) is recommended for improving initial install times."""
         )
         )
@@ -440,7 +469,7 @@ def validate_bun():
 
 
 def validate_frontend_dependencies():
 def validate_frontend_dependencies():
     """Validate frontend dependencies to ensure they meet requirements."""
     """Validate frontend dependencies to ensure they meet requirements."""
-    if IS_WINDOWS:
+    if constants.IS_WINDOWS:
         return
         return
     return validate_bun()
     return validate_bun()
 
 
@@ -448,8 +477,7 @@ def validate_frontend_dependencies():
 def initialize_frontend_dependencies():
 def initialize_frontend_dependencies():
     """Initialize all the frontend dependencies."""
     """Initialize all the frontend dependencies."""
     # Create the reflex directory.
     # Create the reflex directory.
-    if not IS_WINDOWS:
-        path_ops.mkdir(constants.REFLEX_DIR)
+    path_ops.mkdir(constants.REFLEX_DIR)
     # validate dependencies before install
     # validate dependencies before install
     validate_frontend_dependencies()
     validate_frontend_dependencies()
     # Install the frontend dependencies.
     # Install the frontend dependencies.

+ 1 - 1
tests/test_testing.py

@@ -1,6 +1,6 @@
 """Unit tests for the included testing tools."""
 """Unit tests for the included testing tools."""
+from reflex.constants import IS_WINDOWS
 from reflex.testing import AppHarness
 from reflex.testing import AppHarness
-from reflex.utils.prerequisites import IS_WINDOWS
 
 
 
 
 def test_app_harness(tmp_path):
 def test_app_harness(tmp_path):

+ 27 - 13
tests/test_utils.py

@@ -513,24 +513,42 @@ def test_app_default_name(tmp_path, mocker):
         prerequisites.get_default_app_name()
         prerequisites.get_default_app_name()
 
 
 
 
-def test_node_install_windows(mocker):
+def test_node_install_windows(tmp_path, mocker):
     """Require user to install node manually for windows if node is not installed.
     """Require user to install node manually for windows if node is not installed.
 
 
     Args:
     Args:
+        tmp_path: Test working dir.
         mocker: Pytest mocker object.
         mocker: Pytest mocker object.
     """
     """
-    mocker.patch("reflex.utils.prerequisites.IS_WINDOWS", True)
-    mocker.patch("reflex.utils.prerequisites.check_node_version", return_value=False)
+    fnm_root_path = tmp_path / "reflex" / "fnm"
+    fnm_exe = fnm_root_path / "fnm.exe"
 
 
-    with pytest.raises(typer.Exit):
-        prerequisites.install_node()
+    mocker.patch("reflex.utils.prerequisites.constants.FNM_DIR", fnm_root_path)
+    mocker.patch("reflex.utils.prerequisites.constants.FNM_EXE", fnm_exe)
+    mocker.patch("reflex.utils.prerequisites.constants.IS_WINDOWS", True)
+    mocker.patch("reflex.utils.processes.new_process")
+    mocker.patch("reflex.utils.processes.stream_logs")
+
+    class Resp(Base):
+        status_code = 200
+        text = "test"
+
+    mocker.patch("httpx.stream", return_value=Resp())
+    download = mocker.patch("reflex.utils.prerequisites.download_and_extract_fnm_zip")
+    mocker.patch("reflex.utils.prerequisites.zipfile.ZipFile")
+    mocker.patch("reflex.utils.prerequisites.path_ops.rm")
+
+    prerequisites.install_node()
+
+    assert fnm_root_path.exists()
+    download.assert_called_once()
 
 
 
 
 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_DIR", nvm_root_path)
     mocker.patch("reflex.utils.prerequisites.constants.NVM_DIR", nvm_root_path)
-    mocker.patch("reflex.utils.prerequisites.IS_WINDOWS", False)
+    mocker.patch("reflex.utils.prerequisites.constants.IS_WINDOWS", False)
 
 
     class Resp(Base):
     class Resp(Base):
         status_code = 200
         status_code = 200
@@ -556,13 +574,12 @@ def test_bun_install_without_unzip(mocker):
     """
     """
     mocker.patch("reflex.utils.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("os.path.exists", return_value=False)
-    mocker.patch("reflex.utils.prerequisites.IS_WINDOWS", False)
+    mocker.patch("reflex.utils.prerequisites.constants.IS_WINDOWS", False)
 
 
     with pytest.raises(FileNotFoundError):
     with pytest.raises(FileNotFoundError):
         prerequisites.install_bun()
         prerequisites.install_bun()
 
 
 
 
-# from
 @pytest.mark.parametrize("is_windows", [True, False])
 @pytest.mark.parametrize("is_windows", [True, False])
 def test_create_reflex_dir(mocker, is_windows):
 def test_create_reflex_dir(mocker, is_windows):
     """Test that a reflex directory is created on initializing frontend
     """Test that a reflex directory is created on initializing frontend
@@ -572,7 +589,7 @@ def test_create_reflex_dir(mocker, is_windows):
         mocker: Pytest mocker object.
         mocker: Pytest mocker object.
         is_windows: Whether platform is windows.
         is_windows: Whether platform is windows.
     """
     """
-    mocker.patch("reflex.utils.prerequisites.IS_WINDOWS", is_windows)
+    mocker.patch("reflex.utils.prerequisites.constants.IS_WINDOWS", is_windows)
     mocker.patch("reflex.utils.prerequisites.processes.run_concurrently", mocker.Mock())
     mocker.patch("reflex.utils.prerequisites.processes.run_concurrently", mocker.Mock())
     mocker.patch("reflex.utils.prerequisites.initialize_web_directory", mocker.Mock())
     mocker.patch("reflex.utils.prerequisites.initialize_web_directory", mocker.Mock())
     create_cmd = mocker.patch(
     create_cmd = mocker.patch(
@@ -581,7 +598,4 @@ def test_create_reflex_dir(mocker, is_windows):
 
 
     prerequisites.initialize_frontend_dependencies()
     prerequisites.initialize_frontend_dependencies()
 
 
-    if is_windows:
-        assert not create_cmd.called
-    else:
-        assert create_cmd.called
+    assert create_cmd.called