diff --git a/reflex/compiler/templates.py b/reflex/compiler/templates.py index 2abcb6dd533..a8a7dbe4ec2 100644 --- a/reflex/compiler/templates.py +++ b/reflex/compiler/templates.py @@ -502,6 +502,7 @@ def vite_config_template( force_full_reload: bool, experimental_hmr: bool, sourcemap: bool | Literal["inline", "hidden"], + allowed_hosts: bool | list[str] = False, ): """Template for vite.config.js. @@ -511,10 +512,17 @@ def vite_config_template( force_full_reload: Whether to force a full reload on changes. experimental_hmr: Whether to enable experimental HMR features. sourcemap: The sourcemap configuration. + allowed_hosts: Allow all hosts (True), specific hosts (list of strings), or only localhost (False). Returns: Rendered vite.config.js content as string. """ + if allowed_hosts is True: + allowed_hosts_line = "\n allowedHosts: true," + elif isinstance(allowed_hosts, list) and allowed_hosts: + allowed_hosts_line = f"\n allowedHosts: {json.dumps(allowed_hosts)}," + else: + allowed_hosts_line = "" return rf"""import {{ fileURLToPath, URL }} from "url"; import {{ reactRouter }} from "@react-router/dev/vite"; import {{ defineConfig }} from "vite"; @@ -586,7 +594,7 @@ def vite_config_template( hmr: {"true" if experimental_hmr else "false"}, }}, server: {{ - port: process.env.PORT, + port: process.env.PORT,{allowed_hosts_line} hmr: {"true" if hmr else "false"}, watch: {{ ignored: [ diff --git a/reflex/config.py b/reflex/config.py index 6977d745631..225fe5bf2be 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -213,6 +213,11 @@ class BaseConfig: dataclasses.field(default=("*",)) ) + # Allowed hosts for the Vite dev server. Set to True to allow all hosts, + # or provide a list of hostnames (e.g. ["myservice.local"]) to allow specific ones. + # Prevents 403 errors in Docker, Codespaces, reverse proxies, etc. + vite_allowed_hosts: bool | list[str] = False + # Whether to use React strict mode. react_strict_mode: bool = True diff --git a/reflex/environment.py b/reflex/environment.py index 279fc5f60c1..ec8f08c5401 100644 --- a/reflex/environment.py +++ b/reflex/environment.py @@ -243,8 +243,14 @@ def interpret_env_var_value( field_type = value_inside_optional(field_type) if is_union(field_type): - msg = f"Union types are not supported for environment variables: {field_name}." - raise ValueError(msg) + errors = [] + for arg in (union_types := get_args(field_type)): + try: + return interpret_env_var_value(value, arg, field_name) + except (ValueError, EnvironmentVarValueError) as e: # noqa: PERF203 + errors.append(e) + msg = f"Could not interpret {value!r} for {field_name} as any of {union_types}: {errors}" + raise EnvironmentVarValueError(msg) value = value.strip() diff --git a/reflex/utils/frontend_skeleton.py b/reflex/utils/frontend_skeleton.py index 96ced280fe2..6084219df03 100644 --- a/reflex/utils/frontend_skeleton.py +++ b/reflex/utils/frontend_skeleton.py @@ -197,6 +197,7 @@ def _compile_vite_config(config: Config): force_full_reload=environment.VITE_FORCE_FULL_RELOAD.get(), experimental_hmr=environment.VITE_EXPERIMENTAL_HMR.get(), sourcemap=environment.VITE_SOURCEMAP.get(), + allowed_hosts=config.vite_allowed_hosts, ) diff --git a/tests/units/test_environment.py b/tests/units/test_environment.py index 839fea4545a..0665c6f5f0d 100644 --- a/tests/units/test_environment.py +++ b/tests/units/test_environment.py @@ -190,10 +190,19 @@ def test_interpret_enum(self): result = interpret_env_var_value("value1", _TestEnum, "TEST_FIELD") assert result == _TestEnum.VALUE1 - def test_interpret_union_error(self): - """Test that union types raise an error.""" - with pytest.raises(ValueError, match="Union types are not supported"): - interpret_env_var_value("test", int | str, "TEST_FIELD") + def test_interpret_union_tries_each_type(self): + """Test that union types try each type in order.""" + # str matches first + assert interpret_env_var_value("hello", int | str, "TEST_FIELD") == "hello" + # int matches first + assert interpret_env_var_value("42", int | str, "TEST_FIELD") == 42 + # bool matches before str + assert interpret_env_var_value("true", bool | str, "TEST_FIELD") is True + + def test_interpret_union_no_match(self): + """Test that union types raise an error if no type matches.""" + with pytest.raises(EnvironmentVarValueError, match="Could not interpret"): + interpret_env_var_value("not_a_number", int | bool, "TEST_FIELD") def test_interpret_unsupported_type(self): """Test unsupported type raises an error."""