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 the managed
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| packages/reflex-hosting-cli/src/reflex_cli/v2/deploy.py | Relocates the deploy command into the hosting CLI while retaining its options and forwarding deployment preparation and export through the framework interface. |
| reflex/hosting.py | Adds the framework-facing preparation and export boundary consumed by the relocated deploy command. |
| reflex/reflex.py | Removes the embedded deploy implementation and directly registers hosting commands with missing-package fallbacks; the previously reported plugin collision path is gone. |
| reflex/utils/exec.py | Extracts the existing SSR environment arbitration into a shared helper used by export and deploy. |
| pyproject.toml | Raises the hosting CLI dependency floor to the workspace version containing the relocated command. |
| tests/units/reflex_cli/v2/test_deploy.py | Verifies command registration, standalone importability, option preservation, and the supported framework import boundary. |
Reviews (7): Last reviewed commit: "fix(hosting-cli): repair the reflex-base..." | 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.
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.