refactor: server install modularization - #86
Conversation
Remove ServerInstallPhpCommand and integrate PHP installation functionality into the main ServerInstallCommand. Add support for PHP extensions and improve command options.
Add new HttpService for handling HTTP operations and API calls. Provides centralized HTTP client functionality for the application.
Remove the large monolithic server-install.sh playbook in preparation for splitting it into smaller, focused playbooks.
Add new package-list.sh playbook that handles package list updates and repository configuration. Supports gathering PHP versions and extensions for informed package selection.
Add three new focused installation playbooks: - install-base.sh: Installs Caddy and Git - install-deployer.sh: Sets up deployer user and SSH keys - install-bun.sh: Installs Bun JavaScript runtime
Rename server-install-php.sh to install-php.sh for consistency with other install-* playbooks. Update functionality to work with the new modular installation approach.
Update demo-site.sh, helpers.sh, and server-info.sh to work with the new modular installation approach and improved package management.
Update .cursor/rules/06-playbooks.mdc to reflect the new modular playbook structure and installation approach.
WalkthroughRefactors server installation into modular playbooks (package-list, install-base, install-bun, install-deployer, install-php, demo-site), removes the monolithic server-install.sh and ServerInstallPhpCommand, adds an HttpService and injects it into BaseCommand, updates ServerInstallCommand to orchestrate playbooks and PHP flow, and standardizes playbook outputs and progress messaging. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI as ServerInstallCommand
participant Http as HttpService
participant Playbook as Remote Playbooks
participant Remote as Remote Server
User->>CLI: server:install --server <id>
CLI->>Playbook: package-list.sh (DEPLOYER_GATHER_PHP?)
Playbook->>Remote: configure repos, apt update
Playbook-->>CLI: php metadata (versions + extensions)
CLI->>Playbook: install-base.sh
Playbook->>Remote: install packages, configure Caddy
Playbook-->>CLI: status
CLI->>Playbook: install-bun.sh
Playbook->>Remote: install Bun
Playbook-->>CLI: status
CLI->>Playbook: install-deployer.sh
Playbook->>Remote: create deployer user, ssh key
Playbook-->>CLI: deployer public key
CLI->>User: prompt choose PHP version & extensions
User-->>CLI: selection
CLI->>Playbook: install-php.sh (selected extensions)
Playbook->>Remote: install PHP packages, restart php-fpm
Playbook-->>CLI: status
CLI->>Playbook: demo-site.sh
Playbook->>Remote: create site, configure Caddy
Playbook-->>CLI: status
CLI->>Http: verifyUrl(<url>)
Http-->>CLI: {success, status_code, body}
CLI-->>User: verification result & next steps
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used📓 Path-based instructions (1)**/*.php📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)
Files:
🧠 Learnings (1)📓 Common learnings🔇 Additional comments (6)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
app/Traits/ServersTrait.php (1)
258-301: PHP versions rendering is solid; consider tightening the phpdocThe new loop correctly handles both the new
{version, extensions}structure and the legacy scalar format, and default detection is robust. The only nit is the inline doc:/** @var string|int|float */ $version = $versionData['version'];Most static analysers expect the variable name:
-/** @var string|int|float */ +/** @var string|int|float $version */ $version = $versionData['version'];Purely optional polish; behavior is fine as-is.
playbooks/install-bun.sh (1)
1-55: Bun install flow looks good; double‑checkrun_cmdavailabilityThe playbook is idempotent, fails fast on install/output errors, and matches the DEPLOYER_OUTPUT_FILE YAML pattern—nice.
One thing to confirm: this script relies on
run_cmdbut the helpers import is commented out with the note about automatic inlining:# Shared helpers are automatically inlined when executing playbooks remotely # source "$(dirname "$0")/helpers.sh"If there are any code paths (e.g., local/manual execution) where the inlining doesn’t happen,
run_cmdwill be undefined. If that’s a possibility, consider either:
- Un‑commenting the
sourceline, or- Documenting that this script must only be invoked through the mechanism that inlines
helpers.sh.playbooks/demo-site.sh (1)
145-201: Caddy + PHP-FPM wiring is sound; consider double-checking php_fastcgi upstreamUsing
detect_php_default()plus a version-specific PHP-FPM socket and addingphp_fastcgi+file_serveris a good match for the Debian/Ubuntu layout, and restarting PHP-FPM after reloading Caddy is reasonable. One thing to double-check: withphp_fpm_socketincluding a leading/,php_fastcgi unix//${php_fpm_socket}expands tounix///run/...; if you haven’t already validated this with Caddy, it may be worth confirming it behaves as expected or adjusting the socket string.Also applies to: 222-227
.cursor/rules/06-playbooks.mdc (1)
210-225: Rules align with new playbooks; verify referenced helper example pathThe updated guidance about shared helpers, distro env vars, and arrow-style action messages matches how the new Bash playbooks are written. One thing to confirm: the text references
playbooks/package-manager.shas an example, while this PR introducesplaybooks/package-list.sh. Ifpackage-manager.shno longer exists or isn’t the canonical example, it’s worth updating the reference.Also applies to: 254-293
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
.cursor/rules/06-playbooks.mdc(8 hunks)app/Console/Server/ServerInstallCommand.php(7 hunks)app/Console/Server/ServerInstallPhpCommand.php(0 hunks)app/Contracts/BaseCommand.php(2 hunks)app/Services/HttpService.php(1 hunks)app/SymfonyApp.php(0 hunks)app/Traits/ServersTrait.php(1 hunks)playbooks/demo-site.sh(9 hunks)playbooks/helpers.sh(2 hunks)playbooks/install-base.sh(1 hunks)playbooks/install-bun.sh(1 hunks)playbooks/install-deployer.sh(1 hunks)playbooks/install-php.sh(8 hunks)playbooks/package-list.sh(1 hunks)playbooks/server-info.sh(7 hunks)playbooks/server-install.sh(0 hunks)
💤 Files with no reviewable changes (3)
- app/SymfonyApp.php
- app/Console/Server/ServerInstallPhpCommand.php
- playbooks/server-install.sh
🧰 Additional context used
📓 Path-based instructions (1)
**/*.php
📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)
**/*.php: Eliminate single-use methods: inline if a method is called only once
Cache computed values: initialize expensive calculations in the constructor
Avoid method call overhead: prefer direct property access when appropriate
Files:
app/Contracts/BaseCommand.phpapp/Services/HttpService.phpapp/Traits/ServersTrait.phpapp/Console/Server/ServerInstallCommand.php
🧠 Learnings (10)
📚 Learning: 2025-09-22T11:10:21.459Z
Learnt from: CR
Repo: deployer-plus/deployer-php PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-09-22T11:10:21.459Z
Learning: Applies to app/**/Command/**/*.php : Commands receive Services via constructor injection
Applied to files:
app/Contracts/BaseCommand.php
📚 Learning: 2025-10-24T20:00:14.534Z
Learnt from: CR
Repo: bigpixelrocket/deployer-php PR: 0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-24T20:00:14.534Z
Learning: Applies to tests/TestHelpers.php : When BaseCommand gains a new service, update mockCommandContainer() in tests/TestHelpers.php: add parameter, build/default it, and bind it
Applied to files:
app/Contracts/BaseCommand.php
📚 Learning: 2025-09-22T11:10:21.459Z
Learnt from: CR
Repo: deployer-plus/deployer-php PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-09-22T11:10:21.459Z
Learning: Applies to app/**/Command/**/*.php : Commands may only depend on Services (not other Commands)
Applied to files:
app/Contracts/BaseCommand.php
📚 Learning: 2025-09-22T11:10:21.459Z
Learnt from: CR
Repo: deployer-plus/deployer-php PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-09-22T11:10:21.459Z
Learning: Applies to app/**/@(Service|Services)/**/*.php : Extract complex orchestration shared by multiple Commands into dedicated Services
Applied to files:
app/Contracts/BaseCommand.php
📚 Learning: 2025-09-22T11:10:21.459Z
Learnt from: CR
Repo: deployer-plus/deployer-php PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-09-22T11:10:21.459Z
Learning: Applies to app/**/@(Service|Services)/**/*.php : Services receive other Services/utilities via constructor injection
Applied to files:
app/Contracts/BaseCommand.php
📚 Learning: 2025-09-22T11:10:21.459Z
Learnt from: CR
Repo: deployer-plus/deployer-php PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-09-22T11:10:21.459Z
Learning: Applies to app/**/@(Service|Services)/**/*.php : Services may depend on other Services or utilities
Applied to files:
app/Contracts/BaseCommand.php
📚 Learning: 2025-09-22T11:10:21.459Z
Learnt from: CR
Repo: deployer-plus/deployer-php PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-09-22T11:10:21.459Z
Learning: Applies to app/**/@(Service|Services)/**/*.php : Services provide atomic, reusable functionality and must not perform console I/O
Applied to files:
app/Contracts/BaseCommand.php
📚 Learning: 2025-09-22T11:10:21.459Z
Learnt from: CR
Repo: deployer-plus/deployer-php PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-09-22T11:10:21.459Z
Learning: Applies to app/**/@(Service|Services)/**/*.php : Services must be stateless and use dependency injection
Applied to files:
app/Contracts/BaseCommand.php
📚 Learning: 2025-09-22T11:10:21.459Z
Learnt from: CR
Repo: deployer-plus/deployer-php PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-09-22T11:10:21.459Z
Learning: Applies to app/**/@(Service|Services)/**/*.php : Services handle core business logic, external API calls, and file operations
Applied to files:
app/Contracts/BaseCommand.php
📚 Learning: 2025-09-22T11:10:21.459Z
Learnt from: CR
Repo: deployer-plus/deployer-php PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-09-22T11:10:21.459Z
Learning: Applies to app/**/*.php : Use a ServiceContainer/DI container for all object creation
Applied to files:
app/Contracts/BaseCommand.php
🧬 Code graph analysis (8)
app/Contracts/BaseCommand.php (1)
app/Services/HttpService.php (1)
HttpService(14-41)
playbooks/package-list.sh (1)
playbooks/helpers.sh (2)
apt_get_with_retry(97-133)run_cmd(18-24)
playbooks/demo-site.sh (3)
playbooks/helpers.sh (2)
detect_php_default(33-53)run_cmd(18-24)playbooks/server-install.sh (2)
setup_deploy_directories(342-380)setup_caddy_structure(156-197)playbooks/server-install-php.sh (2)
update_caddy_config(236-304)configure_php_fpm(128-166)
playbooks/install-deployer.sh (1)
playbooks/helpers.sh (1)
run_cmd(18-24)
playbooks/install-php.sh (1)
playbooks/helpers.sh (2)
apt_get_with_retry(97-133)run_cmd(18-24)
playbooks/install-bun.sh (1)
playbooks/helpers.sh (1)
run_cmd(18-24)
playbooks/server-info.sh (1)
playbooks/helpers.sh (1)
detect_php_default(33-53)
playbooks/install-base.sh (1)
playbooks/helpers.sh (2)
apt_get_with_retry(97-133)run_cmd(18-24)
🔇 Additional comments (17)
playbooks/helpers.sh (1)
74-74: Message text cleanup looks goodDropping the leading checkmark keeps these logs consistent with the new, less noisy progress style; no behavioral changes introduced.
Also applies to: 116-116
app/Contracts/BaseCommand.php (1)
14-15: HttpService injection aligns with the command/service architecture—verify wiring & testsAdding
HttpServiceas a constructor dependency onBaseCommandmatches the pattern of “commands receive services via constructor injection” and keeps HTTP concerns in a dedicated service, which is good.Because
BaseCommandsits under all commands, please double‑check:
- The DI/container wiring that constructs commands now passes an
HttpServiceinstance.- Test helpers (notably
tests/TestHelpers::mockCommandContainer()) are updated to provide a default/mockHttpService, as per the project rule that any new BaseCommand dependency must be reflected there. Based on learnings.Also applies to: 39-59
playbooks/demo-site.sh (2)
12-31: Env validation and newrequire_services()flow look consistentRequiring
DEPLOYER_DISTROand addingrequire_services()inmain()gives a clear, early failure path if Caddy/PHP/PHP config are missing and aligns with the playbook rules. Nothing blocking here.Also applies to: 58-79, 234-239
88-136:setup_demo_site()is idempotent and safely tightens permissionsDirectory creation,
index.phpprovisioning, and ownership/permission changes are all guarded with existence checks and explicit error handling, so repeated runs are safe and predictable.playbooks/install-base.sh (1)
68-127: Caddy base config and YAML output follow the documented playbook patternDirectory creation, marker-based Caddyfile management, localhost snippet creation, and conditional reload are all idempotent and guarded with errors. The main function uses the standard
main()+ YAML-write pattern with proper error checking.Also applies to: 133-147
playbooks/server-info.sh (2)
196-217: PHP extension detection andphp.versionsYAML structure look correct
detect_php_extensions()usesphp<version> -m, filters section headers, and returns a comma-separated list which is rendered asextensions: [ext1,ext2,...]. That matches the newphp: { default, versions: [...] }shape and is backward-compatible with theinstallPhp()logic that only requires theversionfield. No functional issues here; extensions are informative and won’t break consumers even if the list is long.Also applies to: 465-487
417-444: Consolidated PHP-FPM metrics emission is coherent and guardedAccumulating per-version FPM metrics into
php_fpm_yamland writing the block only whenhas_fpm_metricsis true avoids sparse structures and keeps the final YAML compact. Indentation underphp_fpm:and fallback to{}when no metrics exist both look correct, and errors on writes are handled appropriately.Also applies to: 488-516
playbooks/install-deployer.sh (2)
45-147: Deployer user/group and SSH setup is idempotent and secureGroup membership adjustments for
caddy/www-data, service restarts,.sshdirectory creation, key generation, and permission fixes are all guarded with existence checks and clear error handling. Re-running the playbook safely converges state without duplicating work.
153-160: Main deployer flow and YAML output are straightforwardCreating the
deployeruser if missing, resolving the home viagetent, delegating to setup helpers, and emitting a small YAML payload withdeploy_public_keymatches the documented playbook pattern and should integrate cleanly with the command layer.Also applies to: 166-204
playbooks/package-list.sh (1)
47-75: Caching helpers for apt and PHP metadata are sensible and bounded
smart_apt_update()andget_php_with_cache()use simple timestamp files in/tmpto throttle expensive operations and avoid redundant PHP detection, while honoring aforceflag. Error handling on apt failures is explicit, and gating PHP gathering behindDEPLOYER_GATHER_PHP == 'true'keeps the script cheap by default.Also applies to: 86-153
app/Console/Server/ServerInstallCommand.php (1)
35-39: New multi-stage install flow and replay options are coherentWiring
package-list,install-base,install-php,install-bun,install-deployer, anddemo-sitethroughexecutePlaybook()with distro/permissions env vars produces a clear, linear flow, and the replay options now includephp-versionandphp-extensions(withphp-defaultonly when actually prompted), which keeps command replay faithful to the interactive path. From the command side this looks consistent; correctness now mostly depends on the playbooks’ YAML contracts, which your new scripts appear to satisfy.Also applies to: 81-119, 124-177, 182-201, 235-246
playbooks/install-php.sh (6)
1-41: Documentation and environment setup look solid.The header clearly documents the playbook's scope, and the validation of all required environment variables is comprehensive. The new
DEPLOYER_PHP_EXTENSIONSvariable is properly documented and validated.
58-75: Dynamic extension parsing is clean and idempotent.The extension parsing logic correctly splits, trims, and constructs package names. The array expansion to
apt_get_with_retryis properly handled, and error handling is appropriate.
84-122: PHP-FPM configuration is idempotent and well-structured.The socket ownership and permission settings are correctly applied via sed. The conditional enable check prevents redundant systemctl operations, and the final restart is idempotent (starts if not running, or restarts if already running).
131-144: Default version setup is gated correctly and suppresses non-critical errors appropriately.The early return ensures idempotency, and the conditional error suppression is reasonable for optional system-level operations.
153-220: Caddy configuration update is idempotent and safely structured.The script checks for existing configuration at multiple levels (file existence, endpoint presence, marker validation) to avoid redundant updates. Temporary file handling for safe in-place updates is correct, and the Caddy reload is appropriately gated to only run if the service is active.
226-240: Main routine is clean and straightforward.The sequential task execution and YAML output are appropriate. Error handling from individual functions will propagate correctly via the
set -o pipefailat the top of the script.
- Add exception handling to HttpService::verifyUrl() for network failures - Return proper error status (status_code: 0) for connection issues - Improve IPv6 URL handling in ServerInstallCommand - Filter empty extension values and use strict empty array checks - Provide clearer error messages for connection vs HTTP status failures
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
app/Console/Server/ServerInstallCommand.php (2)
283-345: PHP version selection: consider explicit validation for CLI overridesThe discovery of available versions from
packageList['php']and support for both old/newinfo['php']formats is nicely robust. One small improvement: when--php-versionis provided on the CLI with a value not present in$phpVersions, the user currently receives a generic “No extensions available for PHP X” error. You could fail earlier with a clearer message by validating the chosen version against$phpVersions:$phpVersion = (string) $this->io->getOptionOrPrompt( 'php-version', fn () => $this->io->promptSelect( label: 'PHP version:', options: $phpVersions, default: $defaultVersion ) ); + + // Ensure CLI-provided version is actually available + if (!in_array($phpVersion, $phpVersions, true)) { + $this->io->error( + "PHP version {$phpVersion} is not available. " . + 'Available versions: ' . implode(', ', $phpVersions) + ); + + return Command::FAILURE; + }This keeps error reporting tight and avoids routing “bad CLI version” through the “no extensions” branch.
328-372: Extension selection flow is strong; add validation for unknown CLI extensionsThe default-extension filtering and the improved CSV parsing for
--php-extensions(trimming + filtering out empties) are good. To catch typos in CLI-specified extensions, you might validate that all selected extensions exist in$availableExtensionsbefore continuing:if (is_string($selectedExtensions)) { $selectedExtensions = array_filter( array_map(trim(...), explode(',', $selectedExtensions)), static fn (string $ext): bool => $ext !== '' ); } - if (!is_array($selectedExtensions) || $selectedExtensions === []) { + if (!is_array($selectedExtensions)) { + $this->io->error('Invalid PHP extensions selection'); + + return Command::FAILURE; + } + + // Ensure all selected extensions are known for this PHP version + $unknown = array_diff($selectedExtensions, $availableExtensions); + if ($unknown !== []) { + $this->io->error( + 'Unknown PHP extensions for PHP ' . $phpVersion . ': ' . implode(', ', $unknown) + ); + + return Command::FAILURE; + } + + if ($selectedExtensions === []) { $this->io->error('At least one extension must be selected'); return Command::FAILURE; }This keeps the Ansible layer from ever seeing invalid extension names and gives immediate, actionable feedback to the user.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
.cursor/commands/review-pr-comment.md(1 hunks)app/Console/Server/ServerInstallCommand.php(7 hunks)app/Services/HttpService.php(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- app/Services/HttpService.php
🧰 Additional context used
📓 Path-based instructions (1)
**/*.php
📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)
**/*.php: Eliminate single-use methods: inline if a method is called only once
Cache computed values: initialize expensive calculations in the constructor
Avoid method call overhead: prefer direct property access when appropriate
Files:
app/Console/Server/ServerInstallCommand.php
🔇 Additional comments (13)
.cursor/commands/review-pr-comment.md (1)
1-1: This is a meta-instruction file, not reviewable code.The file contains an instruction template but lacks the actual PR comment content needed to perform the requested assessment. Please provide the PR comment that requires evaluation, or clarify if other modified files from PR #86 should be included in this review.
app/Console/Server/ServerInstallCommand.php (12)
7-9: ServerDTO import and typing are consistentThe added
ServerDTOimport matches the newinstallPhpsignature and keeps the command’s server parameter strongly typed; no issues here.
31-39: New PHP-related CLI options are wired correctlyUsing
VALUE_NEGATABLEforphp-defaultand a required CSV option forphp-extensionscleanly matches the later prompting logic and replay generation.
81-119: package-list + install-base sequencing looks solidIntroducing the
package-listplaybook beforeinstall-base(with early return on integer status) gives you structured PHP/package data up front while preserving the existing failure-propagation pattern; this flow reads clean and cohesive.
124-135: installPhp orchestration and result unpacking are clearDelegating the PHP-specific logic to
installPhp()and then unpackingphp_version,php_default(_prompted), andphp_extensionsfor later steps keepsexecute()readable while passing along all state needed for replay and downstream playbooks.
136-176: Bun and deployer-user steps integrate well; confirm return-code semanticsThe new
install-bunandinstall-deployerplaybook calls mirror the existing pattern and early-return on integer results; the only nuance is that deployer failures return the underlying playbook code while demo-site failures normalize toCommand::FAILURE. If that distinction is intentional (e.g., for more granular CI/debugging on the deployer step), the current handling is fine; otherwise you may want to standardize on one convention.
183-190: Passing distro into demo-site playbook is consistentAdding
DEPLOYER_DISTROto thedemo-siteplaybook vars aligns this step with the earlier playbooks and should simplify distro-specific behavior in Ansible.
206-215: IPv6 URL bracketing fixed; double-check host:port assumptionsWrapping any host containing
:in brackets correctly handles IPv6 literals, including::1. This assumes$server->hostnever carries an IPv4host:port(e.g.example.com:8080), which would otherwise becomehttp://[example.com:8080]. If the domain model guarantees “host-only” here, you’re good; if not, you may want a more explicit IPv6 detection or separate port field.
236-247: Command replay now covers PHP extensions and conditional defaultIncluding
php-extensionsin$replayOptionsand only emittingphp-defaultwhen the “default” decision path was actually in play ($phpDefaultPrompted) makes the replay command accurately reflect user choices without cluttering the happy-path case.
251-267: Single-use installPhp helper is justified despite the guidelineWhile
installPhp()is technically a single-use private method, its size, branching, and dedicated responsibility (version discovery, extension selection, default handling, playbook invocation, and replay metadata) justify keeping it extracted for readability and testability rather than inlining intoexecute().
378-405: Default-version handling is backward-compatible and intuitiveThe logic that auto-defaults the first PHP install, detects when the chosen version is already the default, and otherwise delegates to
php-default(option or prompt) is clear and respects both the oldphp.defaultformat and the newerphp.versionsstructure.
410-444: install-php playbook invocation and return shape are consistentRe-deriving
$distroand$permissionsfrom$infokeepsinstallPhp()self-contained, and the playbook call uses a coherent var set (DEPLOYER_PHP_VERSION,DEPLOYER_PHP_SET_DEFAULT,DEPLOYER_PHP_EXTENSIONS). Returning a structured array alongsideCommand::SUCCESSintegrates cleanly with the caller and replay logic.
458-504: HttpService-based verification is clean and user-orientedSwitching to
$this->http->verifyUrl($url)and branching onsuccess/status_codeyields clear user messages for network failures, non-200 HTTP responses, and content mismatches, while thenextStepslines give helpful guidance (and optionally surface the deploy key). AssumingverifyUrlalways returnssuccess,status_code, andbody, this is a solid improvement over inlined HTTP handling.
…tensions - Validate CLI-provided PHP version against available versions - Validate CLI-provided PHP extensions against available extensions for selected version - Provide clear error messages for invalid inputs instead of generic failures
Summary by CodeRabbit
New Features
Improvements
Removed