feat: console prompts wrappers - #26
Conversation
|
Caution Review failedThe pull request is closed. WalkthroughThis PR refactors console I/O: removes legacy Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Cmd as BaseCommand<br/>(ConsoleInputTrait)
participant Prompts as Laravel Prompts
User->>Cmd: execute()
Cmd->>Cmd: getOptionOrPrompt(name, promptCb)
alt option provided
Cmd-->>User: return option value
else option missing
Cmd->>Prompts: prompt* wrapper via promptCb()
Prompts-->>Cmd: user input
Cmd-->>User: return prompted value
end
sequenceDiagram
autonumber
actor User
participant Cmd as BaseCommand<br/>(ConsoleOutputTrait)
User->>Cmd: execute()
Note over Cmd: Updated output helpers and formats
Cmd->>User: info() → "<fg=cyan>ℹ message</>"
Cmd->>User: success() → "✓ message"
Cmd->>User: warning() → "⚠ message"
Cmd->>User: error() → "✗ message"
Cmd->>User: h1(), hr(), writeln()
Cmd->>User: showCommandHint(cmd, options) → multi-line hint
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
tests/TestHelpers.php (1)
256-272: Complete the helper docblock.The new helper needs the parameter and return annotations called out in our PHP guidelines. Please extend the docblock accordingly. As per coding guidelines
/** * Create a TestConsoleCommand for testing with mocked dependencies. + * + * @param bool $envFileExists Whether the .env file should be treated as present. + * @param string $envContent Contents to expose through the mocked .env file. + * @param bool $inventoryFileExists Whether the inventory file should be treated as present. + * @param array|string $inventoryData Inventory structure or raw YAML to preload. + * @return TestConsoleCommand */tests/Unit/Traits/ConsoleInputTraitTest.php (1)
120-139: Consider the trade-offs of regex-based verification.This test verifies that each prompt wrapper calls
suppressPromptSpacing()by reading and parsing the source file with regex. While this ensures the implementation detail is correct, it's potentially fragile if the method implementation is refactored (e.g., extracting helper methods, adding complexity).However, given that this appears to be testing a critical requirement (consistent spacing suppression), the approach is acceptable for now.
Alternative approach to consider for the future: Mock the Laravel Prompts functions and verify that output has the expected ANSI escape sequences, which would test behavior rather than implementation.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
.cursor/rules/01-architecture.mdc(0 hunks).cursor/rules/03-commands.mdc(8 hunks)app/Traits/ConsoleInputTrait.php(2 hunks)app/Traits/ConsoleOutputTrait.php(4 hunks)tests/Fixtures/TestConsoleCommand.php(3 hunks)tests/TestHelpers.php(2 hunks)tests/Unit/Contracts/BaseCommandTest.php(1 hunks)tests/Unit/TestHelpersTest.php(1 hunks)tests/Unit/Traits/ConsoleInputTraitTest.php(2 hunks)tests/Unit/Traits/ConsoleOutputTraitTest.php(2 hunks)
💤 Files with no reviewable changes (1)
- .cursor/rules/01-architecture.mdc
🧰 Additional context used
📓 Path-based instructions (5)
**/*.php
📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)
**/*.php: Follow PSR-12 coding standard in all PHP files
Declare strict types in all PHP files (declare(strict_types=1);)
Use PHP 8.x features where appropriate (unions, match, attributes, readonly)
Use import statements (use ...) instead of fully qualified class names in code
All methods must declare explicit return types with proper generics (e.g., Collection<int, User>)
Prefer Dependency Injection over manual class resolution/instantiation
Use Symfony component classes (e.g., Filesystem, Process) instead of native PHP functions for easier mocking
All object creation must use $container->build(ClassName::class) except for value objects, DTOs, and pure data structures
Access the container via constructor injection in production code; tests may instantiate Container directly
Services must receive dependencies via constructor injection
Use SymfonyStyle consistently for all user-facing console output
Add DocBlock comments with minimalist descriptions, parameters, and return types for classes and functions
Use comments to separate sections and explain complex logic; avoid obvious or stale comments
Use the exact section header comment format with a single newline between headers/subheaders/paragraphs
**/*.php: Eliminate single-use private methods by inlining them directly
Cache expensive computed values by initializing them in the constructor instead of recomputing
Prefer direct property access over method calls when appropriate to avoid call overhead
Organize code into comment-separated sections; prefer alphabetical ordering when it does not conflict with logical grouping
Files:
tests/Unit/Traits/ConsoleOutputTraitTest.phptests/Unit/Traits/ConsoleInputTraitTest.phptests/TestHelpers.phptests/Unit/TestHelpersTest.phpapp/Traits/ConsoleOutputTrait.phptests/Fixtures/TestConsoleCommand.phpapp/Traits/ConsoleInputTrait.phptests/Unit/Contracts/BaseCommandTest.php
{tests/**,test/**,**/*@(Test|Spec).php}
📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)
Do not run or edit tests unless explicitly instructed
Files:
tests/Unit/Traits/ConsoleOutputTraitTest.phptests/Unit/Traits/ConsoleInputTraitTest.phptests/TestHelpers.phptests/Unit/TestHelpersTest.phptests/Fixtures/TestConsoleCommand.phptests/Unit/Contracts/BaseCommandTest.php
tests/**/*.php
📄 CodeRabbit inference engine (.cursor/rules/02-tests.mdc)
tests/**/*.php: Use Pest exclusively with it() syntax for tests
In unit tests, instantiate classes manually with explicit mocks; use the container only for integration tests
Keep individual test files under ~1.8x the size of the source they cover
Test core business logic; avoid testing the framework itself
Prefer dataset-driven testing with ->with([...]) for multiple scenarios
Eliminate overlapping tests; avoid two tests covering the same functionality
Consolidate assertions with expect(...)->and(...) when appropriate
Mock external dependencies only; do not mock internal behavior
Avoid performance tests unless performance is the primary concern
Do not sacrifice readability to hit size/ratio targets
Follow the AAA pattern (Arrange, Act, Assert) in tests; use ACT & ASSERT for exception tests when act triggers assertion
Organize tests with describe() blocks, beforeEach() setup, and extract helpers/traits for DRY
Forbid meaningless assertions (e.g., toBeTrue, not->toBeNull, type-only checks, sleep(...)); use time mocking instead of sleep
Prefer meaningful assertions on behavior and results, and proper mocks (e.g., expect domain outputs; mock->shouldReceive(...))
Unit tests: mock all external dependencies; test single units in isolation; execute in milliseconds
Integration tests: perform real file operations/external processes; cover CLI commands and full workflows
Ignore PHPStan issues in tests; avoid excessive PHPDoc added only to appease types
Files:
tests/Unit/Traits/ConsoleOutputTraitTest.phptests/Unit/Traits/ConsoleInputTraitTest.phptests/TestHelpers.phptests/Unit/TestHelpersTest.phptests/Fixtures/TestConsoleCommand.phptests/Unit/Contracts/BaseCommandTest.php
app/Traits/ConsoleOutputTrait.php
📄 CodeRabbit inference engine (.cursor/rules/03-commands.mdc)
Add output/formatting methods in ConsoleOutputTrait that format and display text and operate via
$this->io
Files:
app/Traits/ConsoleOutputTrait.php
app/Traits/ConsoleInputTrait.php
📄 CodeRabbit inference engine (.cursor/rules/03-commands.mdc)
Add input-gathering methods in ConsoleInputTrait that operate via
$this->input(prompt helpers, validators, transformers)
Files:
app/Traits/ConsoleInputTrait.php
🧠 Learnings (10)
📚 Learning: 2025-10-02T22:05:48.355Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-02T22:05:48.355Z
Learning: Applies to app/Traits/ConsoleOutputTrait.php : Add output/formatting methods in ConsoleOutputTrait that format and display text and operate via `$this->io`
Applied to files:
tests/Unit/Traits/ConsoleOutputTraitTest.phpapp/Traits/ConsoleOutputTrait.php.cursor/rules/03-commands.mdc
📚 Learning: 2025-10-02T22:05:48.355Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-02T22:05:48.355Z
Learning: Applies to app/Traits/ConsoleInputTrait.php : Add input-gathering methods in ConsoleInputTrait that operate via `$this->input` (prompt helpers, validators, transformers)
Applied to files:
tests/Unit/Traits/ConsoleInputTraitTest.phpapp/Traits/ConsoleInputTrait.php
📚 Learning: 2025-10-02T22:05:48.355Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-02T22:05:48.355Z
Learning: Applies to app/**/*Command.php : Use getOptionOrPrompt to detect provided options vs prompt interactively, leveraging Laravel Prompts parameters
Applied to files:
tests/Unit/Traits/ConsoleInputTraitTest.phptests/Fixtures/TestConsoleCommand.phpapp/Traits/ConsoleInputTrait.php.cursor/rules/03-commands.mdc
📚 Learning: 2025-10-02T22:05:48.355Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-02T22:05:48.355Z
Learning: Applies to app/**/*Command.php : Support both interactive prompts and CLI options; use getOptionOrPrompt for each option and showCommandHint to display the full non-interactive command
Applied to files:
app/Traits/ConsoleOutputTrait.php.cursor/rules/03-commands.mdc
📚 Learning: 2025-10-02T22:05:48.355Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-02T22:05:48.355Z
Learning: Applies to app/**/*Command.php : Use laravel/prompts for all user interactions (text, password, confirm, select, multiselect, suggest, search, spin) instead of Symfony IO
Applied to files:
app/Traits/ConsoleInputTrait.php.cursor/rules/03-commands.mdc
📚 Learning: 2025-10-02T22:05:48.355Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-02T22:05:48.355Z
Learning: Applies to app/Contracts/BaseCommand.php : If missing output functionality, add a new, reusable, minimally-scoped, well-documented method to BaseCommand
Applied to files:
tests/Unit/Contracts/BaseCommandTest.php.cursor/rules/03-commands.mdc
📚 Learning: 2025-10-02T22:05:48.355Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-02T22:05:48.355Z
Learning: Applies to app/**/*Command.php : Never use Symfony IO methods directly in commands; use BaseCommand custom methods (writeln, text, info, note, hr, h1, success, error, warning) exclusively
Applied to files:
.cursor/rules/03-commands.mdc
📚 Learning: 2025-10-02T22:05:48.355Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-02T22:05:48.355Z
Learning: Applies to app/**/*Command.php : Use status helpers (success, error with optional tip, warning) for all status messages to ensure consistent formatting
Applied to files:
.cursor/rules/03-commands.mdc
📚 Learning: 2025-10-02T22:05:48.355Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-10-02T22:05:48.355Z
Learning: Applies to app/Contracts/BaseCommand.php : Keep BaseCommand focused on shared initialization/configuration/orchestration; do not place individual I/O operations here
Applied to files:
.cursor/rules/03-commands.mdc
📚 Learning: 2025-10-01T13:56:16.653Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-10-01T13:56:16.653Z
Learning: Commands are responsible for console styling, error formatting, and user prompts
Applied to files:
.cursor/rules/03-commands.mdc
🧬 Code graph analysis (6)
tests/Unit/Traits/ConsoleOutputTraitTest.php (3)
tests/TestHelpers.php (1)
mockTestConsoleCommand(260-272)tests/Fixtures/TestConsoleCommand.php (2)
setTestMethod(40-44)execute(55-77)app/Contracts/BaseCommand.php (1)
execute(109-130)
tests/Unit/Traits/ConsoleInputTraitTest.php (2)
tests/TestHelpers.php (1)
mockTestConsoleCommand(260-272)tests/Fixtures/TestConsoleCommand.php (2)
setTestMethod(40-44)execute(55-77)
tests/TestHelpers.php (2)
app/Container.php (1)
Container(23-227)tests/Fixtures/TestConsoleCommand.php (1)
TestConsoleCommand(22-157)
tests/Unit/TestHelpersTest.php (2)
tests/TestHelpers.php (1)
mockTestConsoleCommand(260-272)tests/Fixtures/TestConsoleCommand.php (1)
TestConsoleCommand(22-157)
tests/Fixtures/TestConsoleCommand.php (1)
app/Traits/ConsoleInputTrait.php (3)
getOptionOrPrompt(58-76)promptText(106-124)promptSpin(345-353)
tests/Unit/Contracts/BaseCommandTest.php (1)
app/Traits/ConsoleOutputTrait.php (1)
writeln(23-29)
🔇 Additional comments (18)
tests/Unit/Traits/ConsoleInputTraitTest.php (5)
13-15: LGTM: Clean test setup with mock helper.The refactored setup using
mockTestConsoleCommand()provides consistent test isolation and reduces boilerplate.
24-60: LGTM: Comprehensive coverage of string option handling.The tests thoroughly cover the three scenarios for string options:
- Provided value: returns the value directly
- Empty string: executes the closure
- Missing option: executes the closure
This validates the core getOptionOrPrompt flow correctly.
65-87: LGTM: Boolean flag handling is well-tested.Tests correctly verify that VALUE_NONE flags return true when provided and execute the closure (returning false in this case) when not provided.
92-114: LGTM: Excellent use of parameterized testing.The dataset-driven approach efficiently covers multiple return types (string, boolean, integer, array) with appropriate assertions for each type.
141-151: LGTM: Clean test for promptSpin wrapper.Correctly verifies that the callback executes and returns the result.
tests/Fixtures/TestConsoleCommand.php (4)
52-52: LGTM: Boolean flag option added for testing.The 'yes' flag with VALUE_NONE type is properly configured for testing boolean flag handling in getOptionOrPrompt.
68-71: LGTM: Match cases updated for new test methods.All new test method mappings are properly registered in the execute method.
84-143: LGTM: Comprehensive test helper methods.The test methods effectively exercise different aspects of getOptionOrPrompt:
- testGetOptionOrPrompt: Basic closure-based prompting with promptText
- testGetOptionOrPromptEmpty: Tracks closure execution and handles empty/missing options
- testGetOptionOrPromptBoolean: Validates boolean flag behavior
- testGetOptionOrPromptTypes: Tests return type flexibility with proper type-based output formatting
Each method correctly outputs results in a format that tests can verify.
148-156: LGTM: Clean promptSpin wrapper test.Properly exercises the spin functionality with a callback and message parameter.
tests/Unit/Traits/ConsoleOutputTraitTest.php (5)
13-15: LGTM: Consistent test setup.Uses the same mockTestConsoleCommand() pattern as other trait tests for consistency.
21-44: LGTM: Clean writeln tests.Tests correctly verify both single-line and multi-line output formatting.
50-100: LGTM: Comprehensive message helper verification.Tests thoroughly validate that each message helper (info, success, warning, error) displays the correct symbol and message text with appropriate formatting.
106-130: LGTM: Heading and separator tests are clear.Properly verifies the heading icon and separator box-drawing characters.
136-188: LGTM: Thorough command hint testing.Tests cover:
- Basic command hint generation with options
- Option formatting with values
- Filtering of null/empty values
This ensures the showCommandHint method behaves correctly for non-interactive execution guidance.
app/Traits/ConsoleInputTrait.php (4)
7-17: LGTM: Clean imports for Laravel Prompts integration.All necessary Laravel Prompts functions are properly imported for the wrapper methods.
Based on learnings
58-76: LGTM: Well-designed option detection and prompting flow.The
getOptionOrPromptmethod correctly handles three scenarios:
- Boolean flags (VALUE_NONE): Returns true when flag is provided
- Non-empty strings: Returns the provided option value
- Empty/null values: Falls back to the closure-based prompt
The closure-based approach provides excellent flexibility, allowing callers to use any prompt type (text, select, confirm, etc.) as needed.
Based on learnings
87-92: LGTM: Necessary workaround for Laravel Prompts spacing.The ANSI escape sequence
\033[1A\033[2K(move cursor up one line and clear it) compensates for unwanted newlines that Laravel Prompts adds before each prompt. This is a reasonable workaround for improving the user experience.Note: This approach assumes terminal ANSI support, which is standard for modern terminals but may not work in all environments. The current implementation is acceptable given the target use case for CLI deployment tools.
106-353: LGTM: Comprehensive and well-documented prompt wrappers.All prompt wrapper methods follow a consistent pattern:
- Call
suppressPromptSpacing()for consistent spacing- Delegate to the corresponding Laravel Prompts function with named parameters
- Maintain explicit type hints and return types
- Include comprehensive docblocks with parameter descriptions
The wrappers cover all Laravel Prompts functions:
- Text input:
promptText,promptPassword,promptSuggest- Selection:
promptSelect,promptMultiselect,promptSearch- Confirmation:
promptConfirm,promptPause- Loading:
promptSpinThe use of generics in
promptSpin(templateT) correctly preserves the callback return type.Based on learnings
| $this->writeln(" <fg=gray>vendor/bin/deployer {$commandName} \\ </>"); | ||
|
|
||
| foreach ($parts as $index => $part) { | ||
| $last = $index === count($parts) - 1; | ||
| $this->writeln(" <fg=gray> {$part}</>".($last ? '' : '<fg=gray> \\ </>')); | ||
| } |
There was a problem hiding this comment.
Avoid dangling backslash when no options are rendered.
When $options normalise to an empty set (all values null/empty/false), the command hint still prints vendor/bin/deployer … \ on the headline. That backslash implies a continuation line that never appears, so the rendered command is invalid. Please gate the continuation marker on !empty($parts) before adding it.
- $this->writeln(" <fg=gray>vendor/bin/deployer {$commandName} \\ </>");
-
- foreach ($parts as $index => $part) {
- $last = $index === count($parts) - 1;
+ $hasOptions = $parts !== [];
+ $this->writeln(
+ $hasOptions
+ ? " <fg=gray>vendor/bin/deployer {$commandName} \\ </>"
+ : " <fg=gray>vendor/bin/deployer {$commandName}</>"
+ );
+
+ $lastIndex = count($parts) - 1;
+ foreach ($parts as $index => $part) {
+ $last = $index === $lastIndex;
$this->writeln(" <fg=gray> {$part}</>".($last ? '' : '<fg=gray> \\ </>'));
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| $this->writeln(" <fg=gray>vendor/bin/deployer {$commandName} \\ </>"); | |
| foreach ($parts as $index => $part) { | |
| $last = $index === count($parts) - 1; | |
| $this->writeln(" <fg=gray> {$part}</>".($last ? '' : '<fg=gray> \\ </>')); | |
| } | |
| $hasOptions = $parts !== []; | |
| $this->writeln( | |
| $hasOptions | |
| ? " <fg=gray>vendor/bin/deployer {$commandName} \\ </>" | |
| : " <fg=gray>vendor/bin/deployer {$commandName}</>" | |
| ); | |
| $lastIndex = count($parts) - 1; | |
| foreach ($parts as $index => $part) { | |
| $last = $index === $lastIndex; | |
| $this->writeln(" <fg=gray> {$part}</>".($last ? '' : '<fg=gray> \\ </>')); | |
| } |
🤖 Prompt for AI Agents
In app/Traits/ConsoleOutputTrait.php around lines 132 to 137, the headline
always prints a trailing continuation backslash even when $parts is empty; guard
the headline's " \ " suffix with a check like if (!empty($parts)) so the
backslash is only added when there are continuation lines, and ensure the
per-part loop only emits the " \ " separator for non-final parts (current logic
ok) so no dangling backslash is rendered when $options normalise to an empty
set.
Summary by CodeRabbit