diff --git a/comfy/comfy_api_env.py b/comfy/comfy_api_env.py new file mode 100644 index 000000000000..c6f2ef0776c5 --- /dev/null +++ b/comfy/comfy_api_env.py @@ -0,0 +1,46 @@ +"""Runtime config the frontend reads from /features to follow --comfy-api-base. + +For a non-prod comfy.org backend (staging or an ephemeral preview env), "/features" exposes the api and +platform base so the frontend talks to it without a rebuild; the frontend picks the Firebase project from the api base. +Prod bases are left alone and keep their build-time defaults. +""" + +from typing import Any +from urllib.parse import urlparse + +from comfy.cli_args import args + +# Staging and the ephemeral preview envs ("testenvs") are one tier: same dev Firebase project and platform. +_STAGING_API_HOST = "stagingapi.comfy.org" +_TESTENV_HOST_SUFFIX = ".testenvs.comfy.org" +_STAGING_PLATFORM_BASE_URL = "https://stagingplatform.comfy.org" + + +def _is_staging_tier(host: str) -> bool: + return host == _STAGING_API_HOST or host.endswith(_TESTENV_HOST_SUFFIX) + + +def normalize_comfy_api_base(url: str) -> str: + """Rewrite a testenv's friendly main host to its comfy-api '-registry' sibling.""" + parsed = urlparse(url) + host = parsed.hostname or "" + if not host.endswith(_TESTENV_HOST_SUFFIX): + return url + label = host[: -len(_TESTENV_HOST_SUFFIX)] + if label.endswith("-registry"): + return url + return f"{parsed.scheme or 'https'}://{label}-registry{_TESTENV_HOST_SUFFIX}" + + +def frontend_config_for_base(base_url: str) -> dict[str, Any] | None: + """The /features overrides for a staging-tier base, or None for prod.""" + if not _is_staging_tier(urlparse(base_url).hostname or ""): + return None + return { + "comfy_api_base_url": normalize_comfy_api_base(base_url).rstrip("/"), + "comfy_platform_base_url": _STAGING_PLATFORM_BASE_URL, + } + + +def get_frontend_config() -> dict[str, Any] | None: + return frontend_config_for_base(getattr(args, "comfy_api_base", "") or "") diff --git a/comfy_api/feature_flags.py b/comfy_api/feature_flags.py index 0f30608a91c4..58b719ec2805 100644 --- a/comfy_api/feature_flags.py +++ b/comfy_api/feature_flags.py @@ -9,6 +9,7 @@ from typing import Any, TypedDict from comfy.cli_args import args +from comfy.comfy_api_env import get_frontend_config class FeatureFlagInfo(TypedDict): @@ -162,4 +163,11 @@ def get_server_features() -> dict[str, Any]: Returns: Dictionary of server feature flags """ - return SERVER_FEATURE_FLAGS.copy() + features = SERVER_FEATURE_FLAGS.copy() + # When --comfy-api-base targets a staging-tier comfy.org backend (the staging api host or an ephemeral testenv), + # surface the api + platform base so the frontend can reach it without a rebuild + # (it derives the Firebase project from the api base). Prod / self-hosted bases keep build-time defaults. + overrides = get_frontend_config() + if overrides: + features.update(overrides) + return features diff --git a/comfy_api_nodes/util/_helpers.py b/comfy_api_nodes/util/_helpers.py index 6b8121cab7ad..7eb1ec664301 100644 --- a/comfy_api_nodes/util/_helpers.py +++ b/comfy_api_nodes/util/_helpers.py @@ -11,6 +11,7 @@ from yarl import URL from comfy.cli_args import args +from comfy.comfy_api_env import normalize_comfy_api_base from comfy.deploy_environment import get_deploy_environment from comfy.model_management import processing_interrupted from comfy_api.latest import IO @@ -63,7 +64,7 @@ def get_comfy_api_headers(node_cls: type[IO.ComfyNode]) -> dict[str, str]: def default_base_url() -> str: - return getattr(args, "comfy_api_base", "https://api.comfy.org") + return normalize_comfy_api_base(getattr(args, "comfy_api_base", "https://api.comfy.org")) async def sleep_with_interrupt( diff --git a/tests-unit/feature_flags_test.py b/tests-unit/feature_flags_test.py index 8ec52a124ae9..12da6962931e 100644 --- a/tests-unit/feature_flags_test.py +++ b/tests-unit/feature_flags_test.py @@ -11,6 +11,10 @@ _coerce_flag_value, _parse_cli_feature_flags, ) +from comfy.comfy_api_env import ( + frontend_config_for_base, + normalize_comfy_api_base, +) class TestFeatureFlags: @@ -181,3 +185,50 @@ def test_registry_entries_have_required_fields(self): assert "type" in info, f"{key} missing 'type'" assert "default" in info, f"{key} missing 'default'" assert "description" in info, f"{key} missing 'description'" + + +class TestComfyApiEnv: + """--comfy-api-base staging-tier detection + testenv main-host -> -registry rewrite.""" + + @pytest.mark.parametrize( + "url, expected", + [ + # testenv friendly main host -> comfy-api -registry sibling (slash trimmed) + ("https://pr-4398.testenvs.comfy.org", "https://pr-4398-registry.testenvs.comfy.org"), + ("https://pr-4398.testenvs.comfy.org/", "https://pr-4398-registry.testenvs.comfy.org"), + ("https://pr-4398-registry.testenvs.comfy.org", "https://pr-4398-registry.testenvs.comfy.org"), + # staging + everything else -> unchanged (no -registry split) + ("https://stagingapi.comfy.org", "https://stagingapi.comfy.org"), + ("https://api.comfy.org", "https://api.comfy.org"), + ("https://pr-1.testenvs.comfy.org.evil.com", "https://pr-1.testenvs.comfy.org.evil.com"), + ("", ""), + ], + ) + def test_normalize_comfy_api_base(self, url, expected): + assert normalize_comfy_api_base(url) == expected + + def test_config_for_staging_tier_else_none(self): + # ephemeral testenv: friendly main host -> -registry, staging platform + eph = frontend_config_for_base("https://pr-1234.testenvs.comfy.org/") + assert eph["comfy_api_base_url"] == "https://pr-1234-registry.testenvs.comfy.org" + assert eph["comfy_platform_base_url"] == "https://stagingplatform.comfy.org" + # staging api host: emitted as-is + stg = frontend_config_for_base("https://stagingapi.comfy.org") + assert stg["comfy_api_base_url"] == "https://stagingapi.comfy.org" + assert stg["comfy_platform_base_url"] == "https://stagingplatform.comfy.org" + # prod / unknown: nothing + assert frontend_config_for_base("https://api.comfy.org") is None + + def test_server_features_merge_only_for_staging_tier(self, monkeypatch): + def set_base(url): + monkeypatch.setattr( + "comfy.comfy_api_env.args", + type("Args", (), {"comfy_api_base": url})(), + ) + + set_base("https://stagingapi.comfy.org") + assert "comfy_api_base_url" in get_server_features() + set_base("https://pr-7.testenvs.comfy.org") + assert "comfy_api_base_url" in get_server_features() + set_base("https://api.comfy.org") + assert "comfy_api_base_url" not in get_server_features()