Skip to content

feat: key validation - #112

Merged
loadinglucian merged 5 commits into
mainfrom
feat/key-validation
Dec 3, 2025
Merged

feat: key validation#112
loadinglucian merged 5 commits into
mainfrom
feat/key-validation

Conversation

@loadinglucian

@loadinglucian loadinglucian commented Dec 3, 2025

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Bug Fixes

    • Improved input validation error handling with early exits for missing inputs
    • Removed extra blank lines in error output for cleaner messages
  • Documentation

    • Updated internal style and validation guidelines for improved consistency

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Dec 3, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This pull request refactors SSH private key path validation and prompting across multiple console commands by introducing a new promptPrivateKeyPath() helper method and updating documentation rules. The changes consolidate key path input handling patterns and enforce stricter validation semantics with mandatory braces and Yoda-style comparisons in code standards.

Changes

Cohort / File(s) Summary
Documentation Rules Updates
.cursor/commands/deslop.md, .cursor/commands/refactor.md, .cursor/rules/01-architecture.mdc, .cursor/rules/04-exceptions.mdc
Updated rules documentation with capitalization changes, expanded Yoda conditions to include literals, added mandatory braces rule for control structures, and converted comparisons to Yoda style.
Console Commands - Key Path Prompting
app/Console/Key/KeyAddDigitalOceanCommand.php, app/Console/Server/ServerAddCommand.php, app/Console/Server/ServerProvisionDigitalOceanCommand.php
Refactored SSH private key path prompting to delegate to new promptPrivateKeyPath() helper, introducing early exit on null input and replacing inline prompting/resolution logic.
Key Path Validation & Prompting Trait
app/Traits/KeysTrait.php
Added new promptPrivateKeyPath() method and validatePrivateKeyPathInputAllowEmpty() helper to centralize private key path validation and resolution with support for empty input fallback.
I/O Service Output
.cursor/rules/03-commands.mdc, app/Services/IOService.php
Updated Input Validation documentation with explicit validator signature patterns and removed trailing empty line after validation errors in getValidatedOptionOrPrompt().

Sequence Diagram

sequenceDiagram
    participant Cmd as Console Command
    participant Trait as KeysTrait
    participant IO as IOService
    participant FS as FilesystemService
    
    Cmd->>Trait: promptPrivateKeyPath()
    activate Trait
    Trait->>IO: getValidatedOptionOrPrompt(label, validate callback)
    activate IO
    IO->>IO: prompt user for input
    IO->>Trait: invoke validatePrivateKeyPathInputAllowEmpty($input)
    activate Trait
    alt Empty Input
        Trait->>FS: resolvePrivateKeyPath('')
        FS-->>Trait: default key path or null
    else Non-Empty Input
        Trait->>FS: expand path for $input
        FS-->>Trait: expanded path or null
    end
    Trait-->>IO: ?string (validation error or null)
    deactivate Trait
    alt Validation Failed
        IO-->>Trait: null (error already reported)
    else Validation Passed
        IO-->>Trait: validated path
    end
    deactivate IO
    alt Path is null
        Trait->>Trait: log failure message
        Trait-->>Cmd: Command::FAILURE
    else Path resolved
        Trait-->>Cmd: string (resolved path)
    end
    deactivate Trait
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

  • Focus areas: KeysTrait.php requires careful review of the new validation helper and integration with existing resolvePrivateKeyPath() logic; verify early-exit patterns in console commands correctly handle null/int return types from the new helper; review .cursor/rules/03-commands.mdc for accuracy of updated validation examples and fallback resolution semantics.

Possibly related PRs

Poem

🐰 Keys consolidated, paths now neat,
Early exits make the flow complete,
Validation rules with stricter sight,
Yoda comparisons, all so right,
Private paths resolved just right! 🔑

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: key validation' accurately captures the main objective of the PR—introducing key validation improvements and a new promptPrivateKeyPath() method across multiple files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/key-validation

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18d5edf and f3e7787.

📒 Files selected for processing (10)
  • .cursor/commands/deslop.md (2 hunks)
  • .cursor/commands/refactor.md (0 hunks)
  • .cursor/rules/01-architecture.mdc (1 hunks)
  • .cursor/rules/03-commands.mdc (6 hunks)
  • .cursor/rules/04-exceptions.mdc (2 hunks)
  • app/Console/Key/KeyAddDigitalOceanCommand.php (1 hunks)
  • app/Console/Server/ServerAddCommand.php (1 hunks)
  • app/Console/Server/ServerProvisionDigitalOceanCommand.php (1 hunks)
  • app/Services/IOService.php (0 hunks)
  • app/Traits/KeysTrait.php (3 hunks)
💤 Files with no reviewable changes (2)
  • app/Services/IOService.php
  • .cursor/commands/refactor.md
🧰 Additional context used
📓 Path-based instructions (3)
**/*.php

📄 CodeRabbit inference engine (.cursor/rules/00-main.mdc)

**/*.php: Eliminate single-use methods: inline if called once
Cache computed values: initialize expensive calculations in constructor
Avoid method call overhead: direct property access when appropriate
Group related functions into comment-separated sections
Order alphabetically after grouping logically

**/*.php: Adhere to PSR-12 standard with strict types declaration, and use PHP 8.x features (unions, match expressions, attributes, readonly properties)
Use explicit return types with generics (e.g., Collection<int, User>)
Use Dependency Injection via Symfony patterns for all dependencies
Use Symfony classes over native PHP functions (Filesystem, Process) for testability
Always use Yoda conditions: place constants on the left side of comparisons to prevent accidental assignment
Use @var annotations to help PHPStan understand types it cannot infer, never use assert() in production code
Always add use statements for vendor packages and project classes; root namespace FQDNs acceptable only for built-in exceptions (\InvalidArgumentException, \RuntimeException)
Use $container->build(ClassName::class) for all object creation in production code; except for DTOs, value objects, and pure data structures
All dependencies must be injected via constructor signatures; no circular dependencies allowed
Use minimalist DocBlock comments with parameter and return type documentation; avoid obvious comments; separate sections visually with structured header formatting

Files:

  • app/Console/Key/KeyAddDigitalOceanCommand.php
  • app/Console/Server/ServerProvisionDigitalOceanCommand.php
  • app/Console/Server/ServerAddCommand.php
  • app/Traits/KeysTrait.php
**/*Command.php

📄 CodeRabbit inference engine (.cursor/rules/03-commands.mdc)

**/*Command.php: NEVER use Symfony IO methods directly - use BaseCommand methods exclusively. All console output flows through custom methods in BaseCommand for consistent TUI styling.
Use BaseCommand custom IO methods for output: $this->out(), $this->hr(), $this->h1(), $this->displayDeets(), $this->yay(), $this->nay(), $this->warn(), $this->info(), $this->ul(), $this->ol()
Use BaseCommand input methods for user interaction: $this->promptText(), $this->promptPassword(), $this->promptConfirm(), $this->promptSelect(), $this->promptMultiselect(), $this->promptSuggest(), $this->promptSearch(), $this->promptPause(), $this->promptSpin()
Use laravel/prompts for ALL user interactions in Symfony Console commands with functions: text(), password(), confirm(), select(), multiselect(), suggest(), search(), spin()
Support both interactive prompts AND CLI options using getOptionOrPrompt() method in Symfony Console commands
Use InputOption::VALUE_REQUIRED for text/data options in addOption() calls
Use InputOption::VALUE_NONE for simple boolean flags (--yes/-y) in addOption() calls
Use InputOption::VALUE_NEGATABLE for tri-state flags (--flag or --no-flag) in addOption() calls
For multi-path prompts in Symfony Console commands, create separate CLI options for each path instead of a single option with conditional logic
Detect and reject conflicting CLI options in Symfony Console commands before proceeding with execution
For multiselect prompts that accept CLI input, handle both array (from prompt) and comma-separated string (from CLI option) input formats
Validate CLI-provided values explicitly against allowed options/resources when using getOptionOrPrompt() (non-validated version)
For resource selection from existing resources, use promptSelect() with resource names and validate that CLI-provided names exist
Use --server option for selecting existing servers, --domain for existing sites, --name for defining new resource names in Symfony Console commands
Use only --yes/-y and --f...

Files:

  • app/Console/Key/KeyAddDigitalOceanCommand.php
  • app/Console/Server/ServerProvisionDigitalOceanCommand.php
  • app/Console/Server/ServerAddCommand.php
**/*Trait.php

📄 CodeRabbit inference engine (.cursor/rules/04-exceptions.mdc)

**/*Trait.php: Input validation methods in traits must use the naming pattern validate*Input() and return ?string (error message or null) for prompt/CLI option validation
Heavy I/O validation methods in traits must use the naming pattern validate*() (without 'Input' suffix) and throw \RuntimeException with complete, user-facing messages
Orchestration traits mixed into Commands must catch exceptions and display them directly via $this->nay() without adding redundant prefixes like 'Failed to...'

Files:

  • app/Traits/KeysTrait.php
🧠 Learnings (28)
📚 Learning: 2025-12-03T12:35:37.811Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-12-03T12:35:37.811Z
Learning: Applies to **/*Command.php : Use `laravel/prompts` for ALL user interactions in Symfony Console commands with functions: text(), password(), confirm(), select(), multiselect(), suggest(), search(), spin()

Applied to files:

  • app/Console/Server/ServerProvisionDigitalOceanCommand.php
  • app/Traits/KeysTrait.php
  • .cursor/rules/03-commands.mdc
📚 Learning: 2025-12-03T12:35:37.811Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-12-03T12:35:37.811Z
Learning: Applies to **/*Command.php : Use --server option for selecting existing servers, --domain for existing sites, --name for defining new resource names in Symfony Console commands

Applied to files:

  • app/Console/Server/ServerAddCommand.php
  • .cursor/rules/03-commands.mdc
📚 Learning: 2025-12-03T12:36:37.985Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/rules.mdc:0-0
Timestamp: 2025-12-03T12:36:37.985Z
Learning: Before committing rule changes, remove outdated file references, check for duplication, verify no contradictions, test code examples, and run token count comparison

Applied to files:

  • .cursor/commands/deslop.md
  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-12-03T12:36:37.985Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/rules.mdc:0-0
Timestamp: 2025-12-03T12:36:37.985Z
Learning: Applies to **/*rules*.{md,mdc} : Avoid repetitive CRITICAL/IMMUTABLE warnings in rule files

Applied to files:

  • .cursor/commands/deslop.md
  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-12-03T12:35:01.148Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-12-03T12:35:01.148Z
Learning: Applies to **/*.php : Always use Yoda conditions: place constants on the left side of comparisons to prevent accidental assignment

Applied to files:

  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-12-03T12:35:01.148Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-12-03T12:35:01.148Z
Learning: Applies to **/*.php : Adhere to PSR-12 standard with strict types declaration, and use PHP 8.x features (unions, match expressions, attributes, readonly properties)

Applied to files:

  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-12-03T12:36:37.985Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/rules.mdc:0-0
Timestamp: 2025-12-03T12:36:37.985Z
Learning: Applies to **/*rules*.{md,mdc} : Remove examples for well-known patterns (AAA, SOLID, etc.) from rule files

Applied to files:

  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-12-03T12:35:01.148Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-12-03T12:35:01.148Z
Learning: Applies to **/*.php : Use explicit return types with generics (e.g., `Collection<int, User>`)

Applied to files:

  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-12-03T12:35:01.148Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-12-03T12:35:01.148Z
Learning: Applies to **/*.php : Use Symfony classes over native PHP functions (Filesystem, Process) for testability

Applied to files:

  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-12-03T12:35:01.148Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-12-03T12:35:01.148Z
Learning: Applies to **/*.php : Use `var` annotations to help PHPStan understand types it cannot infer, never use `assert()` in production code

Applied to files:

  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-12-03T12:35:01.148Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-12-03T12:35:01.148Z
Learning: Applies to **/*.php : Use Dependency Injection via Symfony patterns for all dependencies

Applied to files:

  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-12-03T12:35:01.148Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-12-03T12:35:01.148Z
Learning: Applies to **/Service/**/*.php : Service layer should contain atomic, reusable functionality with no console I/O; accept/return plain PHP data types and handle business logic via dependency-injected dependencies

Applied to files:

  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-12-03T12:35:18.938Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-12-03T12:35:18.938Z
Learning: PHPStan static analysis rules do not apply to test files; focus on functionality over compliance and avoid excessive phpdoc to satisfy type checkers

Applied to files:

  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-12-03T12:35:01.148Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-12-03T12:35:01.148Z
Learning: Applies to **/*.php : Always add `use` statements for vendor packages and project classes; root namespace FQDNs acceptable only for built-in exceptions (`\InvalidArgumentException`, `\RuntimeException`)

Applied to files:

  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-12-03T12:35:01.148Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-12-03T12:35:01.148Z
Learning: Applies to **/*.php : All dependencies must be injected via constructor signatures; no circular dependencies allowed

Applied to files:

  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-12-03T12:35:01.148Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/01-architecture.mdc:0-0
Timestamp: 2025-12-03T12:35:01.148Z
Learning: Applies to **/Command/**/*.php : Command layer should handle user interaction, orchestrate Services, and format output via SymfonyStyle; never contain business logic or invoke other commands

Applied to files:

  • .cursor/rules/01-architecture.mdc
📚 Learning: 2025-12-03T12:35:51.906Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/04-exceptions.mdc:0-0
Timestamp: 2025-12-03T12:35:51.906Z
Learning: Applies to **/*Trait.php : Input validation methods in traits must use the naming pattern `validate*Input()` and return `?string` (error message or null) for prompt/CLI option validation

Applied to files:

  • app/Traits/KeysTrait.php
  • .cursor/rules/03-commands.mdc
📚 Learning: 2025-12-03T12:35:37.811Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-12-03T12:35:37.811Z
Learning: Applies to **/*Command.php : For multi-path prompts in Symfony Console commands, create separate CLI options for each path instead of a single option with conditional logic

Applied to files:

  • app/Traits/KeysTrait.php
  • .cursor/rules/03-commands.mdc
📚 Learning: 2025-12-03T12:35:37.811Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-12-03T12:35:37.811Z
Learning: Applies to **/*Command.php : NEVER use Symfony IO methods directly - use BaseCommand methods exclusively. All console output flows through custom methods in BaseCommand for consistent TUI styling.

Applied to files:

  • app/Traits/KeysTrait.php
📚 Learning: 2025-12-03T12:35:37.811Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-12-03T12:35:37.811Z
Learning: Applies to **/*Command.php : For resource selection from existing resources, use promptSelect() with resource names and validate that CLI-provided names exist

Applied to files:

  • app/Traits/KeysTrait.php
  • .cursor/rules/03-commands.mdc
📚 Learning: 2025-12-03T12:35:37.811Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-12-03T12:35:37.811Z
Learning: Applies to **/*ValidationTrait.php : Validation methods in traits MUST accept mixed and return ?string (error message or null), not throw exceptions. Use naming convention: validateNameInput() for prompts/options, validateName() for heavy I/O operations.

Applied to files:

  • .cursor/rules/03-commands.mdc
📚 Learning: 2025-12-03T12:35:51.906Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/04-exceptions.mdc:0-0
Timestamp: 2025-12-03T12:35:51.906Z
Learning: Applies to **/*Trait.php : Heavy I/O validation methods in traits must use the naming pattern `validate*()` (without 'Input' suffix) and throw `\RuntimeException` with complete, user-facing messages

Applied to files:

  • .cursor/rules/03-commands.mdc
📚 Learning: 2025-12-03T12:35:37.811Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-12-03T12:35:37.811Z
Learning: Applies to **/*Command.php : Validate CLI-provided values explicitly against allowed options/resources when using getOptionOrPrompt() (non-validated version)

Applied to files:

  • .cursor/rules/03-commands.mdc
📚 Learning: 2025-12-03T12:35:37.811Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-12-03T12:35:37.811Z
Learning: Applies to **/*Command.php : Support both interactive prompts AND CLI options using getOptionOrPrompt() method in Symfony Console commands

Applied to files:

  • .cursor/rules/03-commands.mdc
📚 Learning: 2025-12-03T12:35:37.811Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-12-03T12:35:37.811Z
Learning: Applies to **/*Command.php : Use BaseCommand input methods for user interaction: $this->promptText(), $this->promptPassword(), $this->promptConfirm(), $this->promptSelect(), $this->promptMultiselect(), $this->promptSuggest(), $this->promptSearch(), $this->promptPause(), $this->promptSpin()

Applied to files:

  • .cursor/rules/03-commands.mdc
📚 Learning: 2025-12-03T12:35:37.811Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-12-03T12:35:37.811Z
Learning: Applies to **/*Command.php : Use InputOption::VALUE_NONE for simple boolean flags (--yes/-y) in addOption() calls

Applied to files:

  • .cursor/rules/03-commands.mdc
📚 Learning: 2025-12-03T12:35:37.811Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-12-03T12:35:37.811Z
Learning: Applies to **/*Command.php : For multiselect prompts that accept CLI input, handle both array (from prompt) and comma-separated string (from CLI option) input formats

Applied to files:

  • .cursor/rules/03-commands.mdc
📚 Learning: 2025-12-03T12:35:37.811Z
Learnt from: CR
Repo: loadinglucian/deployer PR: 0
File: .cursor/rules/03-commands.mdc:0-0
Timestamp: 2025-12-03T12:35:37.811Z
Learning: Applies to **/*Command.php : Detect and reject conflicting CLI options in Symfony Console commands before proceeding with execution

Applied to files:

  • .cursor/rules/03-commands.mdc
🧬 Code graph analysis (3)
app/Console/Server/ServerProvisionDigitalOceanCommand.php (1)
app/Traits/KeysTrait.php (1)
  • promptPrivateKeyPath (90-119)
app/Console/Server/ServerAddCommand.php (1)
app/Traits/KeysTrait.php (1)
  • promptPrivateKeyPath (90-119)
app/Traits/KeysTrait.php (3)
app/Services/IOService.php (3)
  • IOService (30-488)
  • getValidatedOptionOrPrompt (211-233)
  • promptText (251-267)
app/Services/FilesystemService.php (1)
  • expandPath (124-154)
app/Contracts/BaseCommand.php (1)
  • nay (222-225)
🔇 Additional comments (19)
.cursor/commands/deslop.md (1)

1-10: LGTM!

The header capitalization is now consistent, and broadening from "style inconsistency" to "violation inconsistent with ... our rules" clarifies the scope of what to remove during cleanup.

.cursor/rules/04-exceptions.mdc (2)

62-68: LGTM!

Yoda conditions applied correctly: '' === trim($name) and null !== $this->servers->findByName($name) prevent accidental assignment and align with the updated PHP standards.


118-121: LGTM!

Yoda condition 0 !== $result['exit_code'] is consistent with the updated architecture rules.

.cursor/rules/01-architecture.mdc (2)

15-16: LGTM!

Good expansion of Yoda conditions to explicitly include literals, and the mandatory braces rule aligns with PSR-12 and prevents subtle bugs from single-line control structures.


18-50: LGTM!

The examples are clear and comprehensive. The note on lines 32-34 correctly identifies that Yoda conditions don't apply when comparing two variables, which avoids confusion.

.cursor/rules/03-commands.mdc (7)

111-159: LGTM!

The reworked Input Validation section provides clear principles and a formal validator signature. The examples properly use Yoda conditions and the naming convention (validate*Input() vs validate*()) is well-documented.


161-182: LGTM!

The "Optional Input with Fallback Resolution" pattern aligns with the new validatePrivateKeyPathInputAllowEmpty implementation in KeysTrait.php. The distinction between empty input triggering default resolution vs. non-empty input expanding paths is clearly documented.


184-223: LGTM!

The "Selection from Dynamic Data" pattern and error message guidelines provide actionable examples. The common mistakes section highlights important gotchas (missing null check, silent fallback, CLI bypass).


273-282: LGTM!

Yoda conditions applied correctly in the multiselect handling: '' !== $item and [] !== $unknown.


307-336: LGTM!

Multi-path prompts section correctly uses Yoda conditions (null !== $customKeyPath, 'generate' === $choice) and demonstrates proper handling of conflicting options.


425-431: LGTM!

Resource selection pattern correctly uses Yoda condition null === $server for the existence check.


488-492: LGTM!

Command replay conditional correctly uses Yoda condition null !== $deployKeyPath.

app/Console/Server/ServerProvisionDigitalOceanCommand.php (1)

317-320: LGTM!

Clean refactor delegating private key path handling to promptPrivateKeyPath(). The is_int() check correctly identifies Command::FAILURE return, and the error message is handled within the trait method.

app/Console/Key/KeyAddDigitalOceanCommand.php (1)

124-127: LGTM!

The early null check with Yoda condition (null === $publicKeyPathRaw) prevents passing null to resolvePublicKeyPath() and properly aborts when validation fails. The error is already displayed by getValidatedOptionOrPrompt.

app/Console/Server/ServerAddCommand.php (1)

185-188: LGTM!

Clean refactor to use the centralized promptPrivateKeyPath() helper. The is_int() check correctly handles Command::FAILURE, and the error message is displayed within the trait method.

app/Traits/KeysTrait.php (4)

8-9: LGTM!

Necessary imports added for IOService and Command to support the new prompt flow and return type.


14-17: LGTM!

Updated docblock correctly documents the IOService property requirement alongside the existing FilesystemService requirement.


85-119: LGTM!

Well-structured implementation of promptPrivateKeyPath():

  • Return type string|int clearly distinguishes success (resolved path) from failure (Command::FAILURE)
  • Yoda conditions used correctly (null === $pathRaw, '' === trim($pathRaw), null === $resolved)
  • Proper branching: empty input triggers fallback resolution, non-empty input expands the user-provided path
  • Actionable error message guides users to create default keys or specify a path

288-304: LGTM!

The validatePrivateKeyPathInputAllowEmpty() helper follows the validate*Input() naming convention and correctly:

  • Returns ?string as required for validation methods
  • Uses Yoda condition ('' === trim($path))
  • Returns null for empty paths to allow default resolution
  • Delegates to validatePrivateKeyPathInput() for non-empty paths to enforce strict validation

Comment @coderabbitai help to get the list of available commands and usage tips.

@loadinglucian
loadinglucian merged commit 7b66ea8 into main Dec 3, 2025
5 checks passed
@loadinglucian
loadinglucian deleted the feat/key-validation branch December 3, 2025 21:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant