ENG-11433 Move reflex deploy out of the framework into the Cloud CLI - #6924
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…sting CLI The managed-platform deploy command (options and body) now lives in reflex_cli.v2.deploy; the reflex CLI registers it via cli.add_command. Flags and behavior are unchanged. The module lazily imports reflex internals in the command body since it only runs through the reflex CLI.
d82f9ea to
b822d53
Compare
Greptile SummaryThe PR moves
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains; the previously reported command-name collision is invalidated because the extension registration mechanism was removed and current HEAD registers the built-in hosting commands directly.
|
| Filename | Overview |
|---|---|
| packages/reflex-hosting-cli/src/reflex_cli/v2/deploy.py | Moves the deploy command and its existing option surface into the hosting CLI, using only the new supported framework interface. |
| reflex/hosting.py | Adds the explicit framework boundary for deployment preparation and artifact export. |
| reflex/reflex.py | Replaces the obsolete extension-discovery approach with direct guarded registration of deploy and cloud commands. |
| reflex/utils/exec.py | Extracts SSR flag and environment-variable arbitration without changing its precedence. |
| scripts/check_min_deps.py | Builds unpublished sibling packages into a local wheelhouse so isolated build environments can resolve development pins. |
| tests/units/reflex_cli/v2/test_deploy.py | Verifies command registration, option compatibility, standalone importability, and the supported framework boundary. |
Reviews (9): Last reviewed commit: "fix test cases for check_min_deps with n..." | Re-trigger Greptile
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
4 issues found and verified against the latest diff
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="tests/units/utils/test_cli_plugins.py">
<violation number="1" location="tests/units/utils/test_cli_plugins.py:282">
P3: This reflex-side test hardcodes `reflex_cli.v2.deploy`, a private module path of the separate reflex-hosting-cli package, to assert eager loading. If the hosting CLI renames or reorganizes that module, this framework test fails spuriously even though the decoupling behavior is unchanged. Derive the expected module from the discovered entry points instead (e.g., check that every module referenced by the entry-point value is present in sys.modules) so the test stays coupled to the entry-point contract rather than to the hosting CLI's internal layout.</violation>
</file>
<file name="reflex/utils/cli_plugins.py">
<violation number="1" location="reflex/utils/cli_plugins.py:75">
P2: When a Typer entry point cannot be converted, `typer.main.get_command` aborts construction of the entire `reflex` CLI. Catch conversion errors, log the plugin failure, and continue so the remaining commands and fallback placeholders remain available.</violation>
<violation number="2" location="reflex/utils/cli_plugins.py:163">
P2: When a contributed entry point exists but fails to load (`entry_point.load()` raises) or does not resolve to a click command, the code still registers the "install/upgrade reflex-hosting-cli" placeholder. That message is wrong for an installed, current package, so the user is told to reinstall or upgrade an already-correct package instead of the real cause. Track names that already had an entry point separately from truly absent ones, and give those a diagnostic that reflects the actual failure (the load error already logged, or the non-click object type).</violation>
<violation number="3" location="reflex/utils/cli_plugins.py:173">
P2: Guard duplicate names before registering the command. Click stores commands by name, so this call overwrites an existing built-in or earlier contributed command; reject or skip collisions instead.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
The command body now lives in reflex_cli.v2.deploy, so reflex/reflex.py imports it instead of defining it. That import is guarded: when the hosting CLI is absent, a stand-in of the same name is registered. It accepts any flags, so the user is told which package to install rather than getting a usage error about an option the real command understands. `login` and `logout` report the same way. deploy.py imported log_options from `reflex`, which is not a dependency of reflex-hosting-cli, so the package failed to import on its own. The shared click options move to reflex_base.utils.cli_options, which both packages already depend on; reflex/utils/cli_options.py re-exports them. The hosting CLI floor moves to the release carrying the moved module, held at the workspace development version until that ships.
b822d53 to
03a451c
Compare
…base Reverts the reflex_base.utils.cli_options move (and the click dependency it added to reflex-base). reflex/utils/cli_options.py holds the implementation again, and the hosting CLI carries its own copy in reflex_cli.utils.cli_options so deploy.py no longer imports reflex_base at module scope; the framework bits it needs at runtime moved into the lazy import block in the command body.
…ve-reflex-deploy-command-out-of-the-reflex-package-into # Conflicts: # reflex/reflex.py
…eploy interface The deploy command body needed six reflex internals (constants, config, environment, _init, export, prerequisites). Fold them into two supported functions in a new reflex.hosting module -- prepare_deploy() and export_for_deploy() -- so the framework can reshuffle its internals without breaking the hosting CLI. The shared --ssr/REFLEX_SSR arbitration moves to exec.arbitrate_ssr(), deduplicating the copy in the export command. A new test guards that deploy.py imports the framework only through reflex.hosting.
There was a problem hiding this comment.
All reported issues were addressed across 7 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…ve-reflex-deploy-command-out-of-the-reflex-package-into
…ged into main auth.py imported reflex_base.utils.log directly, which breaks the hosting CLI on reflex versions that predate reflex-base (guarded by test_cli_imports_without_reflex_base, added in reflex-dev#6939 after reflex-dev#6918's last CI run). Route it through the reflex_cli.utils.log shim instead. test_auth.py tripped pyright after the typer upgrade: typer now vendors click, so get_command()'s annotations are incompatible with click.testing.CliRunner. Cast the unwrapped group to click.Group, which is what get_command actually returns at runtime via _patch_typer.
When reflex-hosting-cli inserts a dev dependency into reflex's own pyproject.toml, the _build environment_ with `require-runtime-dependencies = true` demands that all dependencies be installed from packages. This mechanism satisfies that requirement by pre-building the dev dependencies as their own wheels.
…-reflex-deploy-command-out-of-the-reflex-package-into
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
…-context-refactor-jv3pig Picks up #6605 (memo ``RestProp`` CSS routing) and #6924 (``reflex deploy`` moved to the Cloud CLI). ``_create_component_definition`` takes the union: this branch's optional pre-analyzed ``params`` guard plus the ``rest_target_fields`` out-parameter #6605 threads into ``_evaluate_component_body``.
client_error (#6827): mismatch scenarios produce actionable errors and fatal sessions as designed; sanitization/truncation/rate-limits verified via raw socket.io probing. Found: a no-argument client_error emit raises an unhandled TypeError, bypassing the anti-abuse guards. logging_cli (#6863/#6865/#6867/#6924): JSON-lines purity verified for reflex-owned logs across init/export/run; deprecation shims and missing hosting-cli fallbacks work. Found: REFLEX_ENABLE_FULL_LOGGING's worker file handler is closed (records silently dropped, stdout JSON purity broken) and granian's own lines break strict --json parsing at debug level. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EMjBXPozsNeQNSBZecNH8x
Closes ENG-11433.
reflex deploylived inreflex/reflex.py, so cloud-specific code shipped inside the open-source framework. The implementation now lives inreflex-hosting-cli.What changed
The command moved.
reflex/reflex.pyloses 245 lines;reflex_cli/v2/deploy.pygains 236. The framework contains no cloud code.The import is guarded.
reflex/reflex.pyimports the moved command and falls back when it cannot:The typer conversion for
cloudis unchanged from before; it just moved inside theelse.A missing package no longer fails opaquely. The stand-in is a real command of the same name.
ignore_unknown_optionsplus aclick.UNPROCESSEDargument make it swallow every flag, soreflex deploy --app-name demoreportspip install reflex-hosting-cliinstead of dying on "No such option".loginandlogoutuse the same helper onImportError.Standalone importability, fixed.
deploy.pyimportedreflex.utils.cli_options, butreflexis not a dependency ofreflex-hosting-cli, so importing the package on its own raisedModuleNotFoundError. The shared click options moved toreflex_base.utils.cli_options(addingclick >=8.2to reflex-base);reflex/utils/cli_options.pyre-exports them for its existing callers.The framework surface is one supported module (from review: the command body necessitated six reflex internals —
constants,config,environment,reflex.reflex._init,utils.export,utils.prerequisites— that reflex would have to maintain forever). A newreflex/hosting.pyis now the explicit interface the framework supports for the hosting CLI:prepare_deploy(ssr=...)— sets the DEPLOY compile context, reconciles--ssrwithREFLEX_SSR, ensures the cwd is an initialized app, and returnsDeployPrep(app_name, loglevel, ssr).export_for_deploy(...)— thin wrapper overexport()that fillsloglevelfrom config. Theexport_fncallback shape stays owned by the hosting CLI.The command body imports nothing else from
reflex, andtest_deploy_uses_only_the_supported_framework_interfacefails if any otherreflex.*import sneaks back in. The--ssr/REFLEX_SSRarbitration that was duplicated betweendeployandreflex exportis now shared asexec.arbitrate_ssr().Answers to the issue's open questions
How is it wired up? A guarded import, with
reflex-hosting-clistill a required dependency.pip install reflexgives a workingreflex deployand nothing changes for existing users. Dropping the dependency would break them, which the "no breaking changes" bullet forbids.An earlier revision of this PR used a
reflex.cli_commandsentry point group instead. It was removed: the only thing it bought over a direct import was that the framework did not write the namereflex_cliin its source, which did not justify 173 lines of discovery machinery and an indirection that hides wherereflex deploycomes from. The cloud code is out of the framework either way, which is what the issue asks for.What if the Cloud CLI is not installed? The command name still exists and reports what to install, as above.
No UX change
Verified rather than assumed: the pre-move command is reconstructed from the merge-base copy of
reflex/reflex.pyand diffed against the live one. All 23 parameters match on name, opts, secondary opts, type, default,multipleandrequired, and the rendered--helpis identical. A separate check runs the command against a real app and asserts the 13 arguments still reachreflex_cli.v2.cli.deploy, and that--no-ssrand--exclude-from-backendstill reachexport()through theexport_fnclosure.Note for the release
reflex deploynow lives inreflex_cli.v2.deploy, which older hosting CLI releases do not carry, so the floor must exclude them. That release is unpublished, so the pin is the workspace development version0.1.70.post18.dev0— the same conventionreflex-hosting-clialready uses for itsreflex-basepin.scripts/check_min_deps.py --check-dev-pins reflexfails while it stands, so it must be re-pinned to the published version before release.One side effect:
check_min_deps.py reflexcannot build the root package while the dev pin stands.[tool.hatch.build.hooks.custom]setsrequire-runtime-dependencies = true, so runtime deps resolve inside the isolated build environment, which the script's local editable overrides never reach. The other packages still check.Hitchhiker: repair of #6918 against post-#6939 main
#6918 (
reflex cloud whoami/token) merged with day-old green checks, after #6939 had made reflex-base an optional dependency of the hosting CLI, so main is red. Since this branch merges main anyway, the repair rides along:auth.pyimportedreflex_base.utils.logat module scope, breakingtest_cli_imports_without_reflex_base; it now goes through thereflex_cli.utils.logshim.test_auth.pytripped pyright after the typer upgrade (typer now vendors click, soget_command()'s annotations are incompatible withclick.testing.CliRunner); the unwrapped group is cast toclick.Group, which is what_patch_typer'sget_commandactually returns at runtime.Tests
reflex deployhad no dedicated tests before this branch.tests/units/reflex_cli/v2/test_deploy.py— registration, the flag surface,--help, asys.meta_pathprobe that imports the module with everyrefleximport blocked (covers the standalone-import bug above), and the framework-interface boundary guard.tests/units/test_hosting.py—prepare_deploy(compile context, SSR arbitration, reinit path, returned config values) andexport_for_deploypass-through.tests/units/utils/test_exec.py—arbitrate_ssrboth branches.tests/units/test_reflex.py— the cloud commands resolve intoreflex_cli, and the stand-in reports the package rather than a usage error, including when the real command's flags are passed.Full suite: 7763 passed, 17 skipped. Ruff clean. Pyright: 0 errors.