[#2846] Grew the Vortex CLI into a multi-verb tool with 'update', 'configure' and 'doctor'. - #2887
Conversation
Option lookups in 'OptionsResolver::resolve()' now tolerate an absent key so verbs can define only the options they offer.
The class moves across rather than being rewritten, so every reported behaviour survives: the four checked tools, the installed-and-running distinction, per-tool install instructions, the version summary and '--only'. Two tests pin that surface so it cannot quietly shrink.
The questions are declared by the build rather than by a verb, so '--schema', '--validate' and '--agent-help' answer identically wherever the trait is mounted. A bare invocation resolves to a different verb depending on the target directory, and an agent must be able to describe the questions either way.
…mand'. Downloading, collecting answers, processing and copying is one flow shared by every verb that applies a template, so it moves to a base class and 'install' becomes a facade over it. Closing guidance is now suppressed on non-interactive runs so a caller's stdout stays clean; build failure output still prints on every path because it explains a non-zero exit code. The header names the operation, which follows the destination state rather than the command name.
Updating is now a verb of its own rather than re-running 'install' over an existing project. '--to' names the target template version directly; an explicit '--uri' names both repository and ref, so it stays the more specific input and wins. The cross-major refusal and its pointer to the matching release carry over unchanged.
Reconfigures a project in place, with no template download: the project is both the tree answers are read from and the tree they are written to. The working directory is set past the environment so an ambient variable cannot redirect the write target, and a relative destination is resolved before any value is derived from it. '--apply' is honoured on both the interactive and the scripted path - the two differ only in how answers are reported, so there is no second branch to forget and nothing excluded from coverage. Writing is refused outside a Vortex project, and confirmed first when a person is watching.
An existing Vortex project is reconfigured, anything else gets a fresh install. The router declares no options of its own and passes the input through, so the selected verb sees exactly what was typed and there is no second copy of the option set to drift. It reads the destination from the raw input rather than a bound option, because a destination that does not exist yet is the ordinary fresh-install case. The agent surface is reachable through both routes, so a downloaded binary run with no arguments can still describe its own questions. Each verb is also exercised from the built PHAR.
'vortex-update' calls the verb rather than re-running an install through the default command. The template harness gains a 'runUpdate()' alongside 'runInstall()', and the two CLI scenarios that install and then re-run against a newer template now exercise the update verb, which is what they were always describing.
The CLI reference now covers every verb rather than installation alone, including what 'configure' can and cannot change in an already-installed project. The agent instructions describe the verbs, the bare invocation entry point and 'configure --apply', so an agent can drive the whole surface from '--agent-help'.
Running the CLI loads the destination's '.env' into the process, so a functional test left variables such as 'VORTEX_PROJECT' resolving in whichever test ran next - and the discovery tests read exactly those. Locations are derived from the working directory, so a test that chdir'd elsewhere resolved later paths against the wrong root. Both are now cleared on teardown, which makes the suite independent of execution order.
The Pygmy fallback piped 'docker ps' into 'grep', but commands run without a shell, so the pipe was never interpreted and the branch could not succeed - the container list is now read and matched in PHP. Docker Compose reports the version of whichever form is present rather than assuming the modern subcommand, so a legacy-only host no longer reports a bare 'Available'. An injected process runner is given the working directory like any other, and 'build' now runs the checks against the directory being built. '--destination' gains the '-d' shortcut on every verb, matching what the router already accepts, and the '--uri' description names '#' as the ref separator.
WalkthroughThe CLI now supports commandless routing, shared installation logic, template updates, in-place configuration, agent surfaces, and environment diagnostics. Tests, PHAR coverage, tooling, and documentation reflect the expanded command set. ChangesCLI command surface
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related issues
Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.vortex/cli/src/Command/AbstractInstallCommand.php:
- Around line 221-226: Update the exception handling in doInstall() to frame the
failure message using the same destination-derived operation wording as
InstallPresenter::header(), so update failures are reported as updates rather
than installations. Keep the existing error details and failure return intact,
and update the corresponding INSTALL_ERROR_* constants in TuiOutput.php to match
the revised wording.
In @.vortex/cli/src/Command/AgentSurfaceTrait.php:
- Around line 99-108: Update the --prompts handling in AgentSurfaceTrait to
detect an existing but unreadable file and report the established precise
read-error message instead of treating it as invalid JSON. Decode the prompt
content only once into an associative array, validate that decoding succeeded
and produced an object-shaped associative structure using
is_array()/array_is_list(), while preserving empty JSON object {} as valid if
required, then reuse that decoded value as $user_config.
In @.vortex/cli/src/Command/DoctorCommand.php:
- Around line 321-325: Guard both Docker probes in hasAmazeeioContainers and the
related fallback path with processRunner->commandExists('docker') before calling
ProcessRunner::run(). Return the existing unavailable result (FALSE or NULL as
appropriate) when Docker is absent, and add tests covering each probe path
without Docker installed.
In @.vortex/cli/src/Command/UpdateCommand.php:
- Around line 84-96: Update targetUri() to validate a non-empty --to version
against the CLI major before constructing the repository reference, using the
existing assertMajorCompatibility() behavior or related version symbols. Reject
mismatched requested majors and preserve explicit --uri precedence and
same-major reference generation.
In @.vortex/docs/content/cli.mdx:
- Line 147: Revise the documentation statement near the closing guidance
description so it does not promise clean stdout for every non-interactive
command. State only that footer guidance is suppressed, or limit the JSON-only
stdout claim specifically to configure; preserve installation status output for
non-interactive install runs.
In @.vortex/tooling/src/vortex-update:
- Around line 94-100: Ensure the packaged v1 Vortex CLI artifact supports the
update command before deploying this script. If that artifact cannot be updated,
modify the TASK running the CLI to detect the installed CLI version and invoke a
compatible command instead of unconditionally calling update.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 26b08a69-14fa-4258-8da2-e4f6cb078603
📒 Files selected for processing (33)
.vortex/cli/src/Command/AbstractInstallCommand.php.vortex/cli/src/Command/AgentSurfaceTrait.php.vortex/cli/src/Command/BuildCommand.php.vortex/cli/src/Command/ConfigureCommand.php.vortex/cli/src/Command/DestinationAwareTrait.php.vortex/cli/src/Command/DoctorCommand.php.vortex/cli/src/Command/InstallCommand.php.vortex/cli/src/Command/RouteCommand.php.vortex/cli/src/Command/UpdateCommand.php.vortex/cli/src/Prompts/InstallPresenter.php.vortex/cli/src/Prompts/PromptManager.php.vortex/cli/src/Schema/AgentHelp.php.vortex/cli/src/Utils/OptionsResolver.php.vortex/cli/src/Utils/Project.php.vortex/cli/tests/Functional/Command/BuildCommandTest.php.vortex/cli/tests/Functional/Command/ConfigureCommandTest.php.vortex/cli/tests/Functional/Command/DoctorCommandTest.php.vortex/cli/tests/Functional/Command/InstallCommandTest.php.vortex/cli/tests/Functional/Command/RouteCommandTest.php.vortex/cli/tests/Functional/Command/UpdateCommandTest.php.vortex/cli/tests/Functional/FunctionalTestCase.php.vortex/cli/tests/Functional/Handlers/AbstractHandlerProcessTestCase.php.vortex/cli/tests/Functional/PharTest.php.vortex/cli/tests/Helpers/TuiOutput.php.vortex/cli/tests/Unit/ProjectTest.php.vortex/cli/tests/Unit/UnitTestCase.php.vortex/cli/vortex.vortex/docs/content/cli.mdx.vortex/docs/content/contributing/maintenance/cli.mdx.vortex/tests/phpunit/Functional/CliTest.php.vortex/tests/phpunit/Traits/SutTrait.php.vortex/tooling/src/vortex-update.vortex/tooling/tests/Unit/UpdateVortexTest.php
This comment has been minimized.
This comment has been minimized.
1 similar comment
This comment has been minimized.
This comment has been minimized.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## 2.x #2887 +/- ##
==========================================
- Coverage 88.15% 87.66% -0.49%
==========================================
Files 98 104 +6
Lines 5412 5562 +150
Branches 3 3
==========================================
+ Hits 4771 4876 +105
- Misses 641 686 +45 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
📖 Documentation preview for this pull request has been deployed to Netlify: https://6a726d1135a11fbe1a355b9b--vortex-docs.netlify.app This preview is rebuilt on every commit and is not the production documentation site. |
…gets, and pinned the update endpoint. The Docker probes now check the binary is resolvable first: the runner refuses to execute a command it cannot find, so 'doctor' aborted on a host without Docker instead of reporting it as missing - the one thing the command exists to do. A named '--to' version from another major is refused, since the destination gate compares the project against the build and says nothing about the version being requested, leaving a route to pull a template across a breaking boundary. The tooling downloads the CLI from the major-specific path matching its own line, so an update fetches a build that carries the commands it calls. A failed run is now framed by the destination like the header already was, an unreadable answers file is reported as unreadable rather than as broken JSON, and the docs no longer promise clean stdout for every non-interactive command.
|
Code coverage (threshold: 90%) Per-class coverage |
This comment has been minimized.
This comment has been minimized.
2 similar comments
This comment has been minimized.
This comment has been minimized.
|
Code coverage (threshold: 90%) Per-class coverage |
Closes #2846
Summary
Grows the Vortex CLI from a single-purpose installer into a multi-verb tool. The command surface expands from
install(default),check-requirements,buildtoinstall,update,configure,doctor,build, plus a hiddenroutecommand that becomes the new default. A bare invocation now resolves by the state of the target directory: an existing Vortex project is reconfigured, anything else gets a fresh install. The download/collect/process/copy flow shared byinstallandupdateis extracted intoAbstractInstallCommand, and the--schema/--validate/--agent-helpagent surface is extracted intoAgentSurfaceTraitso it answers identically oninstall,updateandconfigure. Three pre-existing defects in the renameddoctorcommand are fixed along the way, a wrong-major CLI download in the tooling is corrected, and a test-isolation bug that made unit test outcomes depend on suite order is resolved.Changes
Routing
RouteCommandis the new default command (hidden fromlist). It resolves a bare invocation by the state of the target directory - an existing Vortex project routes toconfigure, anything else routes toinstall. It declares no options of its own (ignoreValidationErrors()) and passes input through untouched; the target directory is read from the raw input rather than a bound option, since a destination that does not exist yet is the ordinary fresh-install case.updatecommandNew first-class verb replacing "re-run install over an existing project".
--tonames the target template version, resolved against the official repository; an explicit--urinames both repository and ref and wins over--to. The cross-major refusal on the destination, and its pointer to the matching release, carry over unchanged.--tois additionally gated on the version being requested. The destination gate compares the project against the build and says nothing about the requested ref, andreleasePrefixconstrains only thestableresolution - so a named version resolved straight to an archive was a route to pull a template across a breaking boundary into a project the build considered compatible. A--towhose major differs from the build's is now refused, naming the release that can serve it. A branch, tag alias or commit carries no major and is left alone, as is an unstamped build.configurecommandNew verb that reconfigures a project in place with no download - the project is both the tree answers are read from and the tree they are written to.
--applyis honoured on both the interactive and scripted paths (ConfigureCommandTestdata-provides over both, since command interactivity is decoupled from prompt interactivity). Without--applynothing on disk changes: a scripted run reports the collected answers as JSON on stdout, which doubles as a way to read a project's current configuration. Writing is refused outside a Vortex project and confirmed first when a person is watching.doctorcommandReplaces
check-requirements- the class is renamed, not rewritten, so every reported behaviour survives: the four checked tools, the installed-vs-running distinction, per-tool install instructions, the version summary,--only. Two new tests pin that surface.Three pre-existing defects are fixed:
docker ps --format "{{.Names}}" | grep -q amazeeiointo a runner that executes without a shell, so the pipe was never interpreted and the branch could never succeed (the dead code carried a@phpstan-ignore notIdentical.alwaysFalse). The container list is now read and matched in PHP.ProcessRunner::resolveCommand()throwsCommand not foundfor an unresolvable binary, so on a host without Docker the command aborted instead of reporting Docker as missing - which is the one thing it exists to do. Both probes now guard oncommandExists('docker').docker compose versionordocker-compose --version), so a legacy-only host no longer reports a bare "Available" with no version.Shared machinery
AbstractInstallCommandholds the download/collect/process/copy flow shared byinstallandupdate; both are now thin subclasses. A failed run is framed by the destination the same way the header already was, so a run can no longer announce itself as an update and fail as an installation.AgentSurfaceTraitputs--schema/--validate/--agent-helponinstall,updateandconfigurefrom one place, so a downloaded binary run with no arguments can describe its own questions whichever verb it resolves to. An existing but unreadable--promptsfile is reported as unreadable rather than as broken JSON, matching whatOptionsResolveralready raises for the same condition.Project::isVortex()gives project detection one home;OptionsResolverandRouteCommandboth call it instead of duplicating the README badge check.Behaviour change worth flagging
Closing guidance (the "review the changes" box) is now suppressed on non-interactive runs, so a scripted caller's stdout is not polluted with guidance nobody is reading. Progress and status output is unaffected, and build-failure output still prints on every path, since it explains a non-zero exit code rather than guiding a person. One consequence:
ahoy update-vortex, which runs the CLI with--no-interaction, no longer prints its closing box.Test isolation
Running the CLI
putenv()s the destination's.envinto the process, and the unit discovery tests read exactly those variables, so a functional test running first made a large batch of unrelated unit tests fail depending on suite order. The working directory had the same problem, since locations are derived from it. Both are now cleared on teardown, making the suite independent of execution order.Consumers
.vortex/tooling/src/vortex-updatenow calls theupdateverb explicitly instead of relying on the default command.https://www.vortextemplate.com/v1/installon the2.xbranch. That default is inherited frommain, where it is correct and where the script states the rationale itself: pinned to the major-specific path so a project always updates within its own major line. On2.xit meant fetching the1.xCLI, which was already wrong before this change - the downloaded1.xbuild would refuse the2.xdestination through the major gate - and callingupdatewould have turned that refusal intoCommand "update" is not defined. Now pinned to/v2/install, which is both the documented intent and the build carrying the commands the script calls.SutTrait) gainsrunCli($verb, ...)withrunInstall()andrunUpdate()as thin wrappers; the two CLI scenarios inCliTestnow exerciserunUpdate().Docs
cli.mdxcovers the full command surface, including a note thatconfigureis subtractive on this engine: handlers that remove things (a service, a CI provider's files, the AI agent instructions) take effect, but placeholder-filling answers such as the site name do not, because the placeholders were consumed at install time.updateis what brings template changes back in. This subtractive behaviour is inherited from the shared processing pipeline - changing it means changing how answers are processed, which is out of scope for this change.Before / After