Skip to content

feat: add site commands - #46

Merged
loadinglucian merged 17 commits into
mainfrom
feat/add-site-commands
Oct 16, 2025
Merged

feat: add site commands#46
loadinglucian merged 17 commits into
mainfrom
feat/add-site-commands

Conversation

@loadinglucian

@loadinglucian loadinglucian commented Oct 16, 2025

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • New site management commands: add, delete, and list sites (supports Git and local sites).
    • Server listing now displays associated sites and separates entries for readability.
  • Bug Fixes

    • Prevents deleting servers that still have sites; lists blocking sites when deletion is blocked.
  • Chores

    • Simplified server-add flow by removing the interactive SSH connectivity check; clearer prompts and hints.

Add SiteAddCommand, SiteDeleteCommand, and SiteListCommand with
comprehensive site management functionality. Includes SiteHelpersTrait
and SiteValidationTrait for reusable site operations and validation logic.

Commands follow the established Server command patterns for consistency
and maintainability.
Update SiteDTO with improved type hints and properties. Enhance
SiteRepository with additional query and persistence methods to support
the new site management command functionality.
Register new Site commands in SymfonyApp. Update TestConsoleCommand
fixture to support site command testing patterns.
Align ServerAddCommand, ServerDeleteCommand, and ServerListCommand with
improved patterns and consistency standards used across the codebase.
Add full integration and unit test coverage for Site commands:
- SiteAddCommandTest, SiteDeleteCommandTest, SiteListCommandTest
- SiteDTOTest, SiteRepositoryTest updates
- SiteHelpersTraitTest, SiteValidationTraitTest
Update ServerAddCommandTest, ServerDeleteCommandTest, and
ServerListCommandTest to align with new testing patterns and standards.
- Extracts git detection logic into reusable service layer
- Provides detectRemoteUrl() and detectCurrentBranch() methods
- Supports custom working directory parameter
- Includes comprehensive unit tests with 7 test cases
- Follows architecture rules for business logic separation
- Add GitService to BaseCommand constructor (alphabetically ordered)
- Update mockCommandContainer() to bind GitService
- Add mockGitService() test helper function
- Update TestConsoleCommand to include GitService parameter
- Add selectServers() and selectSite() to test methods
- Add --servers option to test command configuration
- Replace private git detection methods with GitService calls
- Add early server existence validation for CLI options
- Wrap selectServers() in try-catch for error handling
- Reduces command-layer business logic per architecture rules
- Improves user experience with immediate validation feedback
- Validate CLI-provided server names immediately during parsing
- Throw descriptive RuntimeException for non-existent servers
- Add unit tests for successful and failed validations
- Provides early feedback for CLI option errors
- Interactive prompts already validated via UI constraints
- Merge single/multiple site tests into dataset-driven test
- Reduce redundant assertions and test overlap
- Decrease file size from 133 to 106 lines
- Improve test-to-code ratio from 2.38x to 1.89x
- Maintains comprehensive coverage with 5 test scenarios
Add validateRepoInput() method to SiteValidationTrait to validate
git repository URL format before prompting. Validates that URLs start
with git@, https://, http://, or ssh://.

Update SiteAddCommand to use getValidatedOptionOrPrompt() instead of
getOptionOrPrompt() for repository input, providing real-time validation
as users type and preventing invalid URLs from being accepted.
Add comprehensive test coverage for validateRepoInput() method using
dataset-driven testing. Tests cover valid URLs (HTTPS, HTTP, SSH formats),
invalid formats (missing protocol, wrong protocol, paths), empty/whitespace
input, and type validation.
Add blank line after validation error messages in getValidatedOptionOrPrompt()
to improve visual separation between error output and subsequent prompts,
enhancing readability in interactive CLI workflows.
@coderabbitai

coderabbitai Bot commented Oct 16, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds site management CLI (site:add/site:delete/site:list), Site DTO and repository changes (nullable repo/branch, isLocal, findByServer), a GitService, site helper/validation traits, updates server commands to be site-aware, injects GitService into BaseCommand, updates tests/fixtures, and adds a .gitignore entry.

Changes

Cohort / File(s) Summary
New Site Commands
app/Console/Site/SiteAddCommand.php, app/Console/Site/SiteDeleteCommand.php, app/Console/Site/SiteListCommand.php
Add CLI commands to add, delete, and list sites with prompts/options, confirmations, and output hints.
Site Traits
app/Traits/SiteHelpersTrait.php, app/Traits/SiteValidationTrait.php
Add helpers for selecting/displaying sites and servers plus validation routines (domain, repo, branch, git-access, server existence).
DTO & Repository
app/DTOs/SiteDTO.php, app/Repositories/SiteRepository.php
Make repo/branch nullable, add isLocal() on SiteDTO, update hydrate/dehydrate logic, and add findByServer(string): array.
Git Service
app/Services/GitService.php
New service to detect git remote URL and current branch via ProcessService, returning null on failure.
Base Command DI
app/Contracts/BaseCommand.php
Add GitService parameter/property to constructor and store it as a protected readonly dependency.
Server Commands
app/Console/Server/ServerAddCommand.php, app/Console/Server/ServerDeleteCommand.php, app/Console/Server/ServerListCommand.php
Remove SSH-connectivity checks/options from ServerAdd; prevent deletion when sites reference a server in ServerDelete; display associated sites per server in ServerList.
App Registration & IO
app/SymfonyApp.php, app/Services/IOService.php
Register new site commands in app; IOService prints an extra blank line after option validation errors.
Tests & Fixtures
tests/TestHelpers.php, tests/Fixtures/TestConsoleCommand.php, tests/.../Site*, tests/.../Server*, tests/Unit/...
Add/adjust unit and integration tests for Site commands, Site traits, GitService, SiteRepository/SiteDTO changes; update test helpers to provide/mock GitService and extend fixtures.
Git Ignore
.gitignore
Add .cursor/.agent-tools/ ignore pattern.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant Cmd as SiteAddCommand
    participant IO as IOService
    participant Val as SiteValidationTrait
    participant Git as GitService
    participant Repo as SiteRepository

    U->>Cmd: run site:add
    Cmd->>Repo: ensure servers exist
    alt no servers
        Cmd->>IO: show error & guidance
        Cmd-->>U: exit failure
    else servers exist
        Cmd->>IO: prompt domain/type/repo/branch/servers
        IO-->>U: prompts
        U-->>IO: provide inputs
        IO->>Val: validate inputs
        Val->>Repo: findByDomain (duplicate check)
        alt type == git
            Cmd->>Git: detectRemoteUrl / detectCurrentBranch
            Git-->>Cmd: values or null
        end
        Cmd->>Val: validateServers(selected)
        Cmd->>Repo: create(SiteDTO)
        Repo-->>Cmd: success
        Cmd->>IO: show success & hint
        Cmd-->>U: exit success
    end
Loading
sequenceDiagram
    participant U as User
    participant Cmd as ServerDeleteCommand
    participant RepoS as ServerRepository
    participant RepoSite as SiteRepository
    participant IO as IOService

    U->>Cmd: run server:delete --server NAME
    Cmd->>RepoSite: findByServer(NAME)
    alt sites exist
        RepoSite-->>Cmd: list of sites
        Cmd->>IO: show error + list
        Cmd-->>U: exit failure
    else no sites
        Cmd->>IO: confirm deletion
        alt confirmed
            Cmd->>RepoS: delete(NAME)
            RepoS-->>Cmd: success
            Cmd->>IO: show success
        else
            Cmd->>IO: show cancelled
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

  • bigpixelrocket/deployer-php#38 — modifies BaseCommand constructor and dependency wiring; directly related to adding/reordering injected services (GitService).
  • bigpixelrocket/deployer-php#27 — overlaps server command edits (ServerAdd/ServerDelete/ServerList) and related behavior changes.
  • bigpixelrocket/deployer-php#26 — adjusts test fixtures/command test wiring; related to test helper and fixture updates here.

Poem

🐇 I hopped in to add sites with cheer,

Git hums the branch that I hear,
Servers now show the sites they host,
Validation guards what matters most,
I nibble stray bugs and stash them near.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “feat: add site commands” directly reflects the core functionality introduced by the pull request, namely the new site-related console commands. It is concise, specific, and uses a conventional commit style that clearly signals a feature addition to teammates reviewing the history.
✨ 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/add-site-commands

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (8)
tests/Unit/Services/GitServiceTest.php (1)

13-26: Consider making environment-dependent tests more deterministic.

The conditional assertions at Lines 21-25 and 68-72 check type when non-null but don't validate meaningful behavior. Since these tests depend on whether the test runs in a git repository, consider:

  • Setting up a temporary git repository in the test for deterministic success cases
  • Or documenting that these tests validate "no crashes" behavior in any environment

As-is, the tests provide basic coverage but could be strengthened.

Also applies to: 60-73

tests/Integration/Console/Site/SiteAddCommandTest.php (1)

16-36: Consider using SiteDTO objects for consistency.

The test helper accepts ServerDTO objects for $existingServers but raw arrays for $existingSites. While this works, it creates an inconsistency with tests/Integration/Console/Site/SiteListCommandTest.php (lines 16-37), which accepts SiteDTO objects and maps them to arrays.

For consistency across the test suite, consider standardizing on one approach. The simpler pattern (accepting raw arrays, as done here) is more direct and requires less ceremony.

If you choose to standardize on raw arrays (recommended for simplicity), update SiteListCommandTest to match this pattern. If you prefer the DTO approach, update this helper and others to accept SiteDTO objects.

tests/Integration/Console/Site/SiteListCommandTest.php (1)

16-37: Simplify by accepting raw arrays directly.

The test helper accepts SiteDTO objects (line 19's type hint) and immediately maps them to raw arrays (lines 23-31). This adds unnecessary ceremony compared to the pattern used in other site command tests.

Consider accepting raw arrays directly, matching the pattern in SiteAddCommandTest and SiteDeleteCommandTest:

-/**
- * @param array<int, SiteDTO> $existingSites
- */
-function createSiteListCommandTester(array $existingSites = []): CommandTester
+function createSiteListCommandTester(array $existingSites = []): CommandTester
 {
     // Build inventory data with sites
     $inventoryData = [
-        'sites' => array_map(
-            fn (SiteDTO $site) => [
-                'domain' => $site->domain,
-                'repo' => $site->repo,
-                'branch' => $site->branch,
-                'servers' => $site->servers,
-            ],
-            $existingSites
-        ),
+        'sites' => $existingSites,
     ];

Then update the test data at lines 50-53 to provide raw arrays instead of constructing and deconstructing SiteDTOs.

app/DTOs/SiteDTO.php (1)

15-16: Clarify servers doc: they are server names, not hostnames/addresses

Repository methods treat servers as names (e.g., 'web1'), not hostnames/IPs. Update the doc to avoid confusion.

- * @param array<int, string> $servers Ordered list of server hostnames or addresses associated with the site.
+ * @param array<int, string> $servers Ordered list of server names associated with the site (as defined in inventory).
app/Services/GitService.php (1)

42-49: Minor robustness: treat empty output as null and catch Throwable

Avoid returning empty strings and catch all throwables.

-            if ($process->isSuccessful()) {
-                return trim($process->getOutput());
-            }
+            if ($process->isSuccessful()) {
+                $out = trim($process->getOutput());
+                return $out !== '' ? $out : null;
+            }
@@
-        } catch (\Exception) {
+        } catch (\Throwable) {
             return null;
         }

Also applies to: 72-79

app/Console/Site/SiteDeleteCommand.php (2)

51-56: Show command hint on early SUCCESS when no site selected

Guideline: always call showCommandHint() before returning SUCCESS. If selectSite() returns SUCCESS (e.g., no sites), display the hint.

-        if ($selection['site'] === null) {
-            return $selection['exit_code'];
-        }
+        if ($selection['site'] === null) {
+            if ($selection['exit_code'] === Command::SUCCESS) {
+                $this->io->showCommandHint('site:delete', [
+                    'site' => $input->getOption('site'),
+                    'yes' => (bool) $input->getOption('yes'),
+                ]);
+            }
+            return $selection['exit_code'];
+        }

As per coding guidelines.


74-79: Show command hint when user cancels (SUCCESS path)

Cancellation returns SUCCESS; include the non-interactive hint for consistency.

-        if (!$confirmed) {
-            $this->io->warning('Cancelled deleting site');
-            $this->io->writeln('');
-
-            return Command::SUCCESS;
-        }
+        if (!$confirmed) {
+            $this->io->warning('Cancelled deleting site');
+            $this->io->writeln('');
+            $this->io->showCommandHint('site:delete', [
+                'site' => $input->getOption('site'),
+                'yes' => (bool) $input->getOption('yes'),
+            ]);
+            return Command::SUCCESS;
+        }

As per coding guidelines.

app/Traits/SiteValidationTrait.php (1)

63-90: Prefer array_reduce or in_array for prefix checking.

Consider simplifying the prefix validation logic for improved readability.

Apply this diff:

-        // Basic format check - should start with git@, https://, http://, or ssh://
         $repo = trim($repo);
         $validPrefixes = ['git@', 'https://', 'http://', 'ssh://'];
-        $hasValidPrefix = false;
-
-        foreach ($validPrefixes as $prefix) {
-            if (str_starts_with($repo, $prefix)) {
-                $hasValidPrefix = true;
-                break;
-            }
-        }
+        
+        // Basic format check - should start with git@, https://, http://, or ssh://
+        $hasValidPrefix = false;
+        foreach ($validPrefixes as $prefix) {
+            if (str_starts_with($repo, $prefix)) {
+                $hasValidPrefix = true;
+                break;
+            }
+        }
 
         if (!$hasValidPrefix) {

Alternatively, use a more functional approach:

-        // Basic format check - should start with git@, https://, http://, or ssh://
         $repo = trim($repo);
         $validPrefixes = ['git@', 'https://', 'http://', 'ssh://'];
-        $hasValidPrefix = false;
-
-        foreach ($validPrefixes as $prefix) {
-            if (str_starts_with($repo, $prefix)) {
-                $hasValidPrefix = true;
-                break;
-            }
-        }
+        
+        // Basic format check - should start with git@, https://, http://, or ssh://
+        $hasValidPrefix = array_reduce(
+            $validPrefixes,
+            fn($carry, $prefix) => $carry || str_starts_with($repo, $prefix),
+            false
+        );
 
         if (!$hasValidPrefix) {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ede53f and 8b09384.

📒 Files selected for processing (28)
  • .gitignore (1 hunks)
  • app/Console/Server/ServerAddCommand.php (2 hunks)
  • app/Console/Server/ServerDeleteCommand.php (1 hunks)
  • app/Console/Server/ServerListCommand.php (3 hunks)
  • app/Console/Site/SiteAddCommand.php (1 hunks)
  • app/Console/Site/SiteDeleteCommand.php (1 hunks)
  • app/Console/Site/SiteListCommand.php (1 hunks)
  • app/Contracts/BaseCommand.php (2 hunks)
  • app/DTOs/SiteDTO.php (1 hunks)
  • app/Repositories/SiteRepository.php (2 hunks)
  • app/Services/GitService.php (1 hunks)
  • app/Services/IOService.php (1 hunks)
  • app/SymfonyApp.php (2 hunks)
  • app/Traits/SiteHelpersTrait.php (1 hunks)
  • app/Traits/SiteValidationTrait.php (1 hunks)
  • tests/Fixtures/TestConsoleCommand.php (6 hunks)
  • tests/Integration/Console/Server/ServerAddCommandTest.php (7 hunks)
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php (2 hunks)
  • tests/Integration/Console/Server/ServerListCommandTest.php (5 hunks)
  • tests/Integration/Console/Site/SiteAddCommandTest.php (1 hunks)
  • tests/Integration/Console/Site/SiteDeleteCommandTest.php (1 hunks)
  • tests/Integration/Console/Site/SiteListCommandTest.php (1 hunks)
  • tests/TestHelpers.php (5 hunks)
  • tests/Unit/DTOs/SiteDTOTest.php (2 hunks)
  • tests/Unit/Repositories/SiteRepositoryTest.php (4 hunks)
  • tests/Unit/Services/GitServiceTest.php (1 hunks)
  • tests/Unit/Traits/SiteHelpersTraitTest.php (1 hunks)
  • tests/Unit/Traits/SiteValidationTraitTest.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.php

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

**/*.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

**/*.php: Adhere to PSR-12 coding style in all PHP files
Declare strict_types=1 at the top of every PHP file
Prefer PHP 8.x features (union types, match, attributes, readonly) where appropriate
Always import classes with use statements; avoid fully qualified class names in code bodies
All methods must declare explicit return types; use proper generics in types/docblocks (e.g., Collection<int, User>)
Use dependency injection instead of manually resolving or instantiating classes
Prefer Symfony component classes (e.g., Filesystem, Process) over native PHP functions for testability
All object creation must use $container->build(ClassName::class) instead of new, except for value objects/DTOs/pure data structures
In production code, access the Container via constructor injection, not via static/global access
Add minimal DocBlock comments with descriptions, parameters, and return types for classes and functions
Use comments as visual separators for sections/subsections with a single newline between header, subheader, and paragraph; avoid obvious or stale comments
Run rector on changed PHP files before completing a task
Run pint on changed PHP files to fix code style before completing a task

Files:

  • app/Console/Server/ServerDeleteCommand.php
  • app/Services/GitService.php
  • app/DTOs/SiteDTO.php
  • app/Services/IOService.php
  • tests/TestHelpers.php
  • app/Console/Server/ServerAddCommand.php
  • app/Repositories/SiteRepository.php
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php
  • tests/Integration/Console/Site/SiteDeleteCommandTest.php
  • app/Console/Server/ServerListCommand.php
  • tests/Unit/DTOs/SiteDTOTest.php
  • app/Contracts/BaseCommand.php
  • app/Traits/SiteValidationTrait.php
  • tests/Unit/Traits/SiteValidationTraitTest.php
  • tests/Unit/Services/GitServiceTest.php
  • tests/Integration/Console/Server/ServerListCommandTest.php
  • app/Traits/SiteHelpersTrait.php
  • tests/Fixtures/TestConsoleCommand.php
  • tests/Integration/Console/Site/SiteAddCommandTest.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
  • app/Console/Site/SiteAddCommand.php
  • app/SymfonyApp.php
  • app/Console/Site/SiteListCommand.php
  • tests/Unit/Repositories/SiteRepositoryTest.php
  • app/Console/Site/SiteDeleteCommand.php
  • tests/Integration/Console/Site/SiteListCommandTest.php
  • tests/Unit/Traits/SiteHelpersTraitTest.php
**/*Command.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

**/*Command.php: Commands handle user interaction (input/output) and orchestrate services
Commands must not contain business logic; delegate business logic to Services
Commands must not duplicate orchestration logic; extract shared orchestration to Services
Commands should not invoke other commands (no proxy commands)
Use SymfonyStyle consistently for all user-facing console output

Files:

  • app/Console/Server/ServerDeleteCommand.php
  • app/Console/Server/ServerAddCommand.php
  • app/Console/Server/ServerListCommand.php
  • app/Contracts/BaseCommand.php
  • tests/Fixtures/TestConsoleCommand.php
  • app/Console/Site/SiteAddCommand.php
  • app/Console/Site/SiteListCommand.php
  • app/Console/Site/SiteDeleteCommand.php
app/Console/**/*Command.php

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

app/Console/**/*Command.php: Never call SymfonyStyle IO directly in commands; use BaseCommand custom IO methods (writeln, hr, h1, success, error, warning, info) exclusively
Use status helper methods (success, error, warning, info) for all status messages to ensure consistent formatting
Use laravel/prompts for all user interactions (text, password, confirm, select, multiselect, suggest, search, spin)
Commands orchestrate services and format all console IO; keep business logic output out of services
Support both interactive prompts and CLI options using getOptionOrPrompt(optionName, promptCallback) for dual-mode commands
Use getValidatedOptionOrPrompt for inputs that require validation; inject validator once and reuse for prompts and options
Define command inputs using OPTIONS only, never ARGUMENTS, to enable getOptionOrPrompt pattern
Follow option naming conventions: --server/--site for selecting existing resources; --name for defining new resource; --host, --port for server config; --yes/-y for confirmations; --skip to bypass validation
Pair every defined option with getOptionOrPrompt to provide both CLI and interactive flows
Boolean flags must use VALUE_NONE; data inputs must use VALUE_REQUIRED
Only --yes gets a short flag (-y); do not assign short flags to other options
Always call showCommandHint() before returning Command::SUCCESS to display non-interactive usage

Files:

  • app/Console/Server/ServerDeleteCommand.php
  • app/Console/Server/ServerAddCommand.php
  • app/Console/Server/ServerListCommand.php
  • app/Console/Site/SiteAddCommand.php
  • app/Console/Site/SiteListCommand.php
  • app/Console/Site/SiteDeleteCommand.php
**/*Service.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

**/*Service.php: Services provide atomic, reusable functionality with no console I/O
Services accept and return plain PHP data types (no console I/O)
Services must be dependency-injected via constructor; declare dependencies in constructor signatures
Extract complex orchestration shared by multiple Commands into dedicated Services
Stateful services should use lazy loading when initialization is expensive or path-dependent
State must be initialized explicitly via public methods (e.g., load(), initialize()) before use
Services should document their stateful nature and initialization requirements

Files:

  • app/Services/GitService.php
  • app/Services/IOService.php
app/Services/**/*.php

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

Services must return plain data and never perform console IO (no SymfonyStyle, no Laravel Prompts)

Files:

  • app/Services/GitService.php
  • app/Services/IOService.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/TestHelpers.php
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php
  • tests/Integration/Console/Site/SiteDeleteCommandTest.php
  • tests/Unit/DTOs/SiteDTOTest.php
  • tests/Unit/Traits/SiteValidationTraitTest.php
  • tests/Unit/Services/GitServiceTest.php
  • tests/Integration/Console/Server/ServerListCommandTest.php
  • tests/Fixtures/TestConsoleCommand.php
  • tests/Integration/Console/Site/SiteAddCommandTest.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
  • tests/Unit/Repositories/SiteRepositoryTest.php
  • tests/Integration/Console/Site/SiteListCommandTest.php
  • tests/Unit/Traits/SiteHelpersTraitTest.php
tests/**/*.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

tests/**/*.php: In tests, direct Container instantiation and bind() for mocks is allowed and encouraged for isolation
Do not run PHPStan on test files; tests are excluded from static analysis

tests/**/*.php: Use Pest exclusively with it() syntax for all tests
Unit tests (services/utilities) must use manual instantiation of dependencies
Command/integration tests must use mockCommandContainer() for building commands and overriding services
Use container auto-wiring only when verifying DI configuration or multi-service integration
Keep test files minimal: target under 1.8x the size of the source they cover without sacrificing readability
Test core business logic only; avoid testing the framework itself
Prefer dataset-driven testing using ->with([...]) for multiple scenarios
Eliminate overlapping tests; do not cover the same functionality in multiple tests
Consolidate assertions with chained expectations (expect(...)->toX()->and(...)->toY())
Mock only external dependencies; keep unit tests isolated
Avoid performance tests unless performance is the primary concern
Do not sacrifice readability to meet size/ratio targets
Follow the AAA pattern (Arrange, Act, Assert) in all tests; add Cleanup when needed
For exception tests, combine steps as // ACT & ASSERT when the act triggers the assertion
Organize tests with describe() blocks, beforeEach() setup, and shared helpers/traits for DRY
Avoid meaningless assertions (type-only checks, generic truthiness/nullness, sleeping; use time mocking)
Prefer meaningful assertions invoking real behavior (config values, validators, mocks with expectations)
Unit tests: mock all external dependencies, test single units, run in milliseconds
Integration tests: use real filesystem/externals, cover CLI commands and full workflows
Ignore PHPStan issues in tests; focus on test functionality over strict types
Avoid excessive phpdoc in tests solely to appease types

Files:

  • tests/TestHelpers.php
  • tests/Integration/Console/Server/ServerDeleteCommandTest.php
  • tests/Integration/Console/Site/SiteDeleteCommandTest.php
  • tests/Unit/DTOs/SiteDTOTest.php
  • tests/Unit/Traits/SiteValidationTraitTest.php
  • tests/Unit/Services/GitServiceTest.php
  • tests/Integration/Console/Server/ServerListCommandTest.php
  • tests/Fixtures/TestConsoleCommand.php
  • tests/Integration/Console/Site/SiteAddCommandTest.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
  • tests/Unit/Repositories/SiteRepositoryTest.php
  • tests/Integration/Console/Site/SiteListCommandTest.php
  • tests/Unit/Traits/SiteHelpersTraitTest.php
tests/TestHelpers.php

📄 CodeRabbit inference engine (.cursor/rules/02-tests.mdc)

When BaseCommand adds a new service, update mockCommandContainer() in tests/TestHelpers.php by adding a parameter, building or accepting the service, and binding it to the container

Files:

  • tests/TestHelpers.php
app/{Contracts/BaseCommand.php,Traits/ConsoleOutputTrait.php}

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

If output functionality is missing, add a new method to BaseCommand/ConsoleOutputTrait with modern styling and documentation

Files:

  • app/Contracts/BaseCommand.php
app/Contracts/BaseCommand.php

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

Limit BaseCommand to shared initialization, configuration, and orchestration logic; do not place individual IO operations here

Files:

  • app/Contracts/BaseCommand.php
app/Traits/*ValidationTrait.php

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

app/Traits/*ValidationTrait.php: Validation methods must accept mixed input and return ?string (error message or null); do not type-hint to string or throw for basic validation
Use exception-throwing validate*() methods only for heavy I/O validations (e.g., git checks), not for simple input validation

Files:

  • app/Traits/SiteValidationTrait.php
🧠 Learnings (2)
📚 Learning: 2025-10-12T15:50:03.841Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-12T15:50:03.841Z
Learning: Applies to tests/TestHelpers.php : When BaseCommand adds a new service, update mockCommandContainer() in tests/TestHelpers.php by adding a parameter, building or accepting the service, and binding it to the container

Applied to files:

  • tests/TestHelpers.php
  • tests/Fixtures/TestConsoleCommand.php
  • tests/Integration/Console/Server/ServerAddCommandTest.php
📚 Learning: 2025-10-12T15:50:03.841Z
Learnt from: CR
PR: bigpixelrocket/deployer-php#0
File: .cursor/rules/02-tests.mdc:0-0
Timestamp: 2025-10-12T15:50:03.841Z
Learning: Applies to tests/**/*.php : Command/integration tests must use mockCommandContainer() for building commands and overriding services

Applied to files:

  • tests/TestHelpers.php
🧬 Code graph analysis (24)
app/Console/Server/ServerDeleteCommand.php (2)
app/Repositories/SiteRepository.php (1)
  • findByServer (111-124)
app/Services/IOService.php (2)
  • writeln (458-464)
  • error (493-496)
app/Services/GitService.php (1)
app/Services/ProcessService.php (2)
  • ProcessService (12-49)
  • run (33-48)
app/DTOs/SiteDTO.php (1)
app/DTOs/ServerDTO.php (1)
  • ServerDTO (7-17)
tests/TestHelpers.php (2)
app/Services/GitService.php (1)
  • GitService (12-81)
app/Container.php (1)
  • bind (46-50)
app/Repositories/SiteRepository.php (1)
app/DTOs/SiteDTO.php (2)
  • SiteDTO (7-32)
  • isLocal (28-31)
tests/Integration/Console/Server/ServerDeleteCommandTest.php (2)
app/DTOs/ServerDTO.php (1)
  • ServerDTO (7-17)
app/Console/Server/ServerDeleteCommand.php (1)
  • execute (40-115)
tests/Integration/Console/Site/SiteDeleteCommandTest.php (3)
app/DTOs/SiteDTO.php (1)
  • SiteDTO (7-32)
tests/TestHelpers.php (1)
  • mockCommandContainer (365-407)
app/Container.php (1)
  • build (59-85)
app/Console/Server/ServerListCommand.php (3)
app/Services/IOService.php (2)
  • h1 (501-507)
  • writeln (458-464)
app/Traits/ServerHelpersTrait.php (2)
  • displayServerDeets (74-84)
  • ServerHelpersTrait (20-86)
app/Repositories/SiteRepository.php (1)
  • findByServer (111-124)
tests/Unit/DTOs/SiteDTOTest.php (1)
app/DTOs/SiteDTO.php (2)
  • isLocal (28-31)
  • SiteDTO (7-32)
app/Contracts/BaseCommand.php (1)
app/Services/GitService.php (1)
  • GitService (12-81)
app/Traits/SiteValidationTrait.php (3)
app/Repositories/SiteRepository.php (1)
  • findByDomain (75-86)
app/Services/ProcessService.php (1)
  • run (33-48)
app/Repositories/ServerRepository.php (1)
  • findByName (70-81)
tests/Unit/Traits/SiteValidationTraitTest.php (2)
app/Traits/SiteValidationTrait.php (5)
  • validateDomainInput (19-38)
  • validateBranchInput (45-56)
  • validateRepoInput (63-90)
  • validateGitRepo (97-123)
  • validateServers (131-143)
tests/TestHelpers.php (2)
  • mockSiteRepository (320-333)
  • mockServerRepository (294-307)
tests/Unit/Services/GitServiceTest.php (2)
tests/TestHelpers.php (1)
  • mockGitService (271-275)
app/Services/GitService.php (2)
  • detectRemoteUrl (28-50)
  • detectCurrentBranch (58-80)
tests/Integration/Console/Server/ServerListCommandTest.php (4)
app/DTOs/SiteDTO.php (1)
  • SiteDTO (7-32)
app/DTOs/ServerDTO.php (1)
  • ServerDTO (7-17)
app/Console/Server/ServerListCommand.php (1)
  • execute (28-77)
tests/Unit/Contracts/BaseCommandTest.php (2)
  • execute (29-34)
  • inventory (79-107)
app/Traits/SiteHelpersTrait.php (5)
app/DTOs/ServerDTO.php (1)
  • ServerDTO (7-17)
app/DTOs/SiteDTO.php (2)
  • SiteDTO (7-32)
  • isLocal (28-31)
app/Repositories/ServerRepository.php (2)
  • ServerRepository (15-189)
  • findByName (70-81)
app/Repositories/SiteRepository.php (2)
  • SiteRepository (15-208)
  • findByDomain (75-86)
app/Services/IOService.php (7)
  • IOService (30-578)
  • warning (485-488)
  • writeln (458-464)
  • getOptionOrPrompt (84-132)
  • promptSelect (304-322)
  • error (493-496)
  • promptMultiselect (337-357)
tests/Fixtures/TestConsoleCommand.php (6)
app/Services/GitService.php (1)
  • GitService (12-81)
app/Services/IOService.php (1)
  • IOService (30-578)
app/Repositories/ServerRepository.php (1)
  • ServerRepository (15-189)
app/Repositories/SiteRepository.php (1)
  • SiteRepository (15-208)
app/Contracts/BaseCommand.php (1)
  • __construct (36-53)
app/Traits/SiteHelpersTrait.php (3)
  • displaySiteDeets (127-143)
  • selectServers (81-122)
  • selectSite (29-71)
tests/Integration/Console/Site/SiteAddCommandTest.php (3)
app/DTOs/ServerDTO.php (1)
  • ServerDTO (7-17)
tests/TestHelpers.php (1)
  • mockCommandContainer (365-407)
app/Container.php (1)
  • build (59-85)
tests/Integration/Console/Server/ServerAddCommandTest.php (2)
tests/TestHelpers.php (1)
  • mockCommandContainer (365-407)
tests/Fixtures/MockSSHService.php (1)
  • MockSSHService (27-87)
app/Console/Site/SiteAddCommand.php (6)
app/Contracts/BaseCommand.php (1)
  • BaseCommand (27-152)
app/DTOs/SiteDTO.php (2)
  • SiteDTO (7-32)
  • isLocal (28-31)
app/Services/IOService.php (11)
  • hr (512-518)
  • h1 (501-507)
  • warning (485-488)
  • writeln (458-464)
  • getValidatedOptionOrPrompt (159-182)
  • promptText (200-218)
  • getOptionOrPrompt (84-132)
  • promptSelect (304-322)
  • error (493-496)
  • success (477-480)
  • showCommandHint (525-561)
app/Traits/SiteValidationTrait.php (4)
  • validateDomainInput (19-38)
  • validateRepoInput (63-90)
  • validateBranchInput (45-56)
  • validateServers (131-143)
app/Services/GitService.php (2)
  • detectRemoteUrl (28-50)
  • detectCurrentBranch (58-80)
app/Traits/SiteHelpersTrait.php (2)
  • selectServers (81-122)
  • displaySiteDeets (127-143)
app/Console/Site/SiteListCommand.php (3)
app/Contracts/BaseCommand.php (1)
  • BaseCommand (27-152)
app/Services/IOService.php (4)
  • hr (512-518)
  • warning (485-488)
  • writeln (458-464)
  • h1 (501-507)
app/Traits/SiteHelpersTrait.php (1)
  • displaySiteDeets (127-143)
tests/Unit/Repositories/SiteRepositoryTest.php (4)
app/DTOs/SiteDTO.php (2)
  • SiteDTO (7-32)
  • isLocal (28-31)
app/Repositories/SiteRepository.php (6)
  • create (55-67)
  • findByDomain (75-86)
  • all (93-103)
  • SiteRepository (15-208)
  • loadInventory (35-47)
  • findByServer (111-124)
tests/TestHelpers.php (1)
  • mockInventoryService (143-159)
app/Services/InventoryService.php (1)
  • loadInventoryFile (97-111)
app/Console/Site/SiteDeleteCommand.php (3)
app/Contracts/BaseCommand.php (1)
  • BaseCommand (27-152)
app/Services/IOService.php (8)
  • hr (512-518)
  • h1 (501-507)
  • writeln (458-464)
  • getOptionOrPrompt (84-132)
  • promptConfirm (260-276)
  • warning (485-488)
  • success (477-480)
  • showCommandHint (525-561)
app/Traits/SiteHelpersTrait.php (2)
  • selectSite (29-71)
  • displaySiteDeets (127-143)
tests/Integration/Console/Site/SiteListCommandTest.php (3)
app/DTOs/SiteDTO.php (1)
  • SiteDTO (7-32)
tests/TestHelpers.php (1)
  • mockCommandContainer (365-407)
app/Container.php (1)
  • build (59-85)
tests/Unit/Traits/SiteHelpersTraitTest.php (3)
app/DTOs/SiteDTO.php (1)
  • SiteDTO (7-32)
tests/TestHelpers.php (1)
  • mockCommandContainer (365-407)
tests/Fixtures/TestConsoleCommand.php (2)
  • TestConsoleCommand (29-295)
  • setTestMethod (67-71)
🪛 GitHub Actions: Rector
app/Services/GitService.php

[error] 9-9: vendor/bin/rector --dry-run: 1 file would be changed by Rector (ReadOnlyClassRector). Process exited with code 2.

🔇 Additional comments (65)
.gitignore (1)

2-2: LGTM!

The addition of .cursor/.agent-tools/ to the ignore list is appropriate for excluding IDE-specific tooling artifacts.

app/Console/Server/ServerDeleteCommand.php (2)

60-75: LGTM! Good referential integrity guard.

The pre-deletion check prevents orphaning sites by blocking server deletion when sites are still associated. The error message clearly lists the blocking sites and provides actionable feedback.


80-80: LGTM! Improved visual separation.

The blank line before the confirmation prompt improves readability and user experience.

app/Services/IOService.php (1)

175-175: LGTM! Consistent error formatting.

The blank line after validation errors harmonizes the output formatting with other error-handling flows in the codebase, improving readability.

app/SymfonyApp.php (1)

11-13: LGTM! Proper command registration.

The new Site commands are correctly imported and registered, following the same pattern as the existing Server commands. The implementation maintains consistency with the established command registration approach.

Also applies to: 126-128

app/Console/Server/ServerListCommand.php (2)

9-9: LGTM! Trait integration for site functionality.

The addition of SiteHelpersTrait enables site-related operations in the server list command, supporting the server-site relationship display.

Also applies to: 22-22


54-73: LGTM! Enhanced server listing with site associations.

The server list now displays associated sites for each server and adds visual separators between entries, significantly improving the information density and readability of the command output. The implementation correctly uses findByServer() to retrieve site associations.

tests/Unit/Services/GitServiceTest.php (1)

28-43: LGTM! Comprehensive error handling and edge case coverage.

The tests properly validate:

  • Null returns for non-git directories (with proper cleanup)
  • Null returns for invalid directories
  • Whitespace trimming behavior

The test structure follows AAA pattern with appropriate use of finally blocks for cleanup.

Also applies to: 75-90, 45-54, 92-101, 103-118

tests/Unit/DTOs/SiteDTOTest.php (2)

8-23: LGTM! Comprehensive git site validation.

The test correctly validates all properties of a git-based site, including the new isLocal() method returning false for sites with a repository.


25-40: LGTM! Local site support validated.

The new test case properly validates local sites without git repositories, ensuring isLocal() returns true when both repo and branch are null.

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

20-20: Verify removal of SSH connectivity testing is intentional.

The command has been significantly simplified by removing:

  • SSH connectivity verification flow
  • --skip option to bypass verification
  • --yes option for auto-confirmation
  • Connection failure handling and guidance

While this simplifies the code, servers can now be added without verifying they're reachable, potentially leading to configuration errors being discovered later during actual deployment rather than at registration time.

Confirm this behavior change aligns with the intended user experience and that SSH verification is handled elsewhere in the workflow (e.g., at deployment time).

Also applies to: 41-41, 147-173

tests/Integration/Console/Site/SiteAddCommandTest.php (4)

47-72: LGTM!

The test correctly validates the non-interactive addition of a git site with all required options, asserting both the exit code and expected output fragments.


74-95: LGTM!

The test properly validates the addition of a local site (without repo/branch) and verifies the output contains the "Local" type indicator.


161-165: LGTM!

Good use of data providers to cover multiple invalid domain scenarios with a single test case.


188-191: LGTM!

Appropriate data provider coverage for invalid branch scenarios.

tests/TestHelpers.php (3)

10-10: LGTM!

Import properly added for the new GitService mock.


261-276: LGTM!

The mockGitService() helper follows the established pattern of other service mocks. It correctly wraps a ProcessService to enable git command execution testing without real processes.


365-407: LGTM!

GitService is properly integrated into mockCommandContainer():

  • Added as an optional parameter (line 368)
  • Initialized with default if not provided (line 388)
  • Bound to the container (line 398)

The parameter order and binding sequence match BaseCommand's constructor order, as required by the coding guidelines.

Based on learnings

tests/Integration/Console/Site/SiteListCommandTest.php (2)

48-82: LGTM!

Good use of data providers to test multiple listing scenarios. The assertions correctly verify both the exit code and expected output fragments.


88-102: LGTM!

Properly validates the empty inventory case with appropriate warning message and guidance to add sites.

app/Repositories/SiteRepository.php (3)

105-124: LGTM!

The findByServer method correctly filters sites by server name using in_array with strict comparison. This enables features like preventing server deletion when sites depend on it.


172-186: LGTM!

The conditional inclusion of repo and branch only for git-based sites (using !$site->isLocal()) properly aligns with the SiteDTO contract where these fields are nullable for local sites.


194-207: LGTM!

The hydration logic correctly treats repo and branch as nullable strings, properly supporting local sites where these fields are absent from inventory data.

app/Console/Site/SiteListCommand.php (2)

1-21: LGTM!

The command class is properly structured:

  • Extends BaseCommand
  • Uses SiteHelpersTrait for display logic
  • Correctly annotated with #[AsCommand] attribute

26-54: LGTM!

The execute method follows the correct pattern:

  • Calls parent::execute to initialize base services
  • Properly handles empty inventory with a warning and guidance
  • Uses displaySiteDeets from SiteHelpersTrait for consistent formatting
  • Returns appropriate exit codes
tests/Integration/Console/Site/SiteDeleteCommandTest.php (3)

16-34: LGTM!

The test helper correctly prepares inventory data with raw site arrays, matching the pattern used in SiteAddCommandTest and ServerDeleteCommandTest.


45-66: LGTM!

The test properly validates non-interactive deletion with appropriate assertions on exit code and output fragments.


72-90: LGTM!

Correctly tests the error path for attempting to delete a non-existent site.

tests/Unit/Traits/SiteValidationTraitTest.php (5)

13-64: LGTM!

The TestSiteValidator fixture properly exposes protected validation methods for testing while maintaining the trait's original access levels in production code.


81-98: LGTM!

Comprehensive coverage of valid domain formats using a data provider.


149-165: LGTM!

Good coverage of valid branch name patterns, including feature branches, bugfixes, and release branches.


195-213: LGTM!

Thorough validation of repository URL formats across different Git hosting providers and protocols.


265-299: LGTM!

Proper coverage of server validation edge cases, including empty arrays, non-existent servers, and validation success scenarios.

tests/Integration/Console/Server/ServerDeleteCommandTest.php (2)

16-36: LGTM!

The test helper is correctly updated to accept and populate site inventory data. The explicit structure with both servers and sites keys (even when empty) makes the inventory format clearer.


109-134: LGTM!

The new test properly validates that server deletion is prevented when sites depend on it. The assertions correctly verify:

  • Failure exit code
  • Error indicator and message
  • Display of associated site domains

This aligns with the findByServer functionality added to SiteRepository.

app/DTOs/SiteDTO.php (1)

19-21: LGTM: Nullable repo/branch + isLocal()

API change is consistent and small helper isLocal() is clear.

Also applies to: 25-31

tests/Unit/Repositories/SiteRepositoryTest.php (3)

36-41: CRUD path coverage looks solid

Good end-to-end checks for create/find/all/delete, covering both git and local sites and isLocal().

Also applies to: 42-50, 51-59, 63-68, 69-77


97-121: Server filtering tests are clear and preserve input order

findByServer() behavior and expected ordering are well asserted.


159-223: Hydration robustness scenarios are thorough

Good handling of missing/wrong types for domain/repo/branch/servers. This matches the repository’s defensive hydration.

app/Console/Site/SiteDeleteCommand.php (1)

27-34: Options follow project conventions

--site VALUE_REQUIRED and --yes with -y are aligned with our CLI rules.

app/Contracts/BaseCommand.php (1)

11-12: LGTM: GitService DI added cleanly

Constructor and imports updated; tests/helpers already wire GitService accordingly.

Also applies to: 36-51

tests/Integration/Console/Server/ServerAddCommandTest.php (1)

15-20: LGTM: helper simplified; tests updated accordingly

createServerAddCommandTester() no longer requires SSH service; all call sites updated. Assertions remain meaningful.

Also applies to: 31-35, 57-60, 86-89, 112-115, 139-142, 173-176

tests/Unit/Traits/SiteHelpersTraitTest.php (1)

23-33: LGTM: covers display and server selection paths well

Good coverage for git/local display formatting and server validation via CLI options.

Also applies to: 54-63, 87-103, 105-119

tests/Integration/Console/Server/ServerListCommandTest.php (5)

17-49: LGTM!

The helper function correctly extends the test setup to support sites alongside servers. The inventory data structure properly maps both ServerDTO and SiteDTO fields to the expected format.


60-85: LGTM!

The test case appropriately validates server listing with full details. The descriptive test name and comprehensive assertions ensure the command displays all required server information.


107-128: LGTM!

Excellent use of dataset-driven testing to verify SSH key path display. The test covers both default and custom key scenarios concisely.


134-168: LGTM!

The test effectively verifies site display under servers with ordering checks. The position-based assertions ensure sites appear after their respective servers in the output.


170-185: LGTM!

The test correctly validates that no "Sites:" section is displayed when a server has no associated sites.

app/Console/Site/SiteAddCommand.php (6)

32-42: LGTM!

The option configuration follows guidelines by using OPTIONS only and VALUE_REQUIRED for all data inputs, enabling the getOptionOrPrompt pattern.


59-70: LGTM!

The server availability check provides clear user guidance and prevents proceeding without servers, ensuring data integrity.


75-89: LGTM!

Domain input properly uses validated option/prompt pattern with early return on validation failure. The flow correctly handles both CLI and interactive modes.


94-150: LGTM!

Site type selection and git-specific details collection are well-structured. The use of GitService for default detection enhances user experience, and validation ensures data integrity.


155-172: LGTM!

Server selection and validation properly handle errors with user-friendly messages and early returns on failure.


177-219: LGTM!

The final section correctly creates the DTO, persists the site with error handling, and displays the command hint as per guidelines. The conditional hint options for local vs git sites are appropriate.

app/Traits/SiteValidationTrait.php (4)

19-38: LGTM!

Domain validation correctly checks type, format, and uniqueness. The use of FILTER_VALIDATE_DOMAIN ensures proper domain name validation.


45-56: LGTM!

Branch name validation appropriately checks for non-empty strings without over-complicating the logic.


97-123: LGTM!

Git repository validation correctly uses exception-throwing for the I/O-heavy remote check, following the guideline for heavy validations. The timeout and error handling are appropriate.


131-143: LGTM!

Server validation correctly ensures at least one server is selected and validates each server's existence in inventory.

tests/Fixtures/TestConsoleCommand.php (4)

12-12: LGTM!

Import and trait additions correctly support the new GitService dependency and SiteHelpersTrait functionality in the test fixture.

Also applies to: 18-18, 32-32


42-42: LGTM!

Constructor updates correctly integrate GitService, following the pattern for updating test fixtures when BaseCommand adds a new service. Based on learnings.

Also applies to: 50-61


79-79: LGTM!

The 'servers' option addition supports testing server selection functionality from SiteHelpersTrait.


96-98: LGTM!

New match cases enable testing of SiteHelpersTrait methods, following the established pattern for test method dispatch.

app/Traits/SiteHelpersTrait.php (3)

29-71: LGTM!

The selectSite method provides excellent UX with helpful guidance when no sites exist. The return structure with both site and exit_code enables flexible handling by callers.


127-143: LGTM!

The displaySiteDeets method correctly differentiates between local and git sites, displaying appropriate details for each type with consistent formatting.


81-122: selectServers array transformation is safe: upstream validation and findByName prevent empty names, so array_filter won’t drop any valid entries.

Comment thread app/Services/GitService.php Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
app/Services/GitService.php (1)

12-16: Fix Rector failure: declare class as readonly.

The class is immutable and the pipeline fails (ReadOnlyClassRector). Declare it as readonly to satisfy Rector and PHP 8.2+ guidelines.

Apply this diff:

-final class GitService
+final readonly class GitService
 {
     public function __construct(private ProcessService $proc)
     {
     }

Then re-run Rector and Pint. Based on pipeline failures and coding guidelines.

🧹 Nitpick comments (1)
app/Services/GitService.php (1)

28-80: Consider extracting a helper to reduce duplication.

Both detectRemoteUrl and detectCurrentBranch duplicate the getcwd() handling, try-catch structure, and trim/return pattern. Extracting a private helper (e.g., runGitCommand(array $cmd, ?string $workingDir): ?string) would reduce repetition and improve maintainability.

Example refactor:

/**
 * Run a git command and return trimmed output or null on failure.
 *
 * @param list<string> $cmd Git command and arguments
 * @param string|null $workingDir Working directory (defaults to current)
 * @return string|null Command output or null on failure
 */
private function runGitCommand(array $cmd, ?string $workingDir): ?string
{
    try {
        $cwd = $workingDir ?? getcwd();
        if ($cwd === false) {
            return null;
        }

        $process = $this->proc->run($cmd, $cwd, 2.0);

        if ($process->isSuccessful()) {
            return trim($process->getOutput());
        }

        return null;
    } catch (\Exception) {
        return null;
    }
}

public function detectRemoteUrl(?string $workingDir = null): ?string
{
    return $this->runGitCommand(
        ['git', 'config', '--get', 'remote.origin.url'],
        $workingDir
    );
}

public function detectCurrentBranch(?string $workingDir = null): ?string
{
    return $this->runGitCommand(
        ['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
        $workingDir
    );
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b09384 and 11ed8ea.

📒 Files selected for processing (1)
  • app/Services/GitService.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.php

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

**/*.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

**/*.php: Adhere to PSR-12 coding style in all PHP files
Declare strict_types=1 at the top of every PHP file
Prefer PHP 8.x features (union types, match, attributes, readonly) where appropriate
Always import classes with use statements; avoid fully qualified class names in code bodies
All methods must declare explicit return types; use proper generics in types/docblocks (e.g., Collection<int, User>)
Use dependency injection instead of manually resolving or instantiating classes
Prefer Symfony component classes (e.g., Filesystem, Process) over native PHP functions for testability
All object creation must use $container->build(ClassName::class) instead of new, except for value objects/DTOs/pure data structures
In production code, access the Container via constructor injection, not via static/global access
Add minimal DocBlock comments with descriptions, parameters, and return types for classes and functions
Use comments as visual separators for sections/subsections with a single newline between header, subheader, and paragraph; avoid obvious or stale comments
Run rector on changed PHP files before completing a task
Run pint on changed PHP files to fix code style before completing a task

Files:

  • app/Services/GitService.php
**/*Service.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

**/*Service.php: Services provide atomic, reusable functionality with no console I/O
Services accept and return plain PHP data types (no console I/O)
Services must be dependency-injected via constructor; declare dependencies in constructor signatures
Extract complex orchestration shared by multiple Commands into dedicated Services
Stateful services should use lazy loading when initialization is expensive or path-dependent
State must be initialized explicitly via public methods (e.g., load(), initialize()) before use
Services should document their stateful nature and initialization requirements

Files:

  • app/Services/GitService.php
app/Services/**/*.php

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

Services must return plain data and never perform console IO (no SymfonyStyle, no Laravel Prompts)

Files:

  • app/Services/GitService.php
🧬 Code graph analysis (1)
app/Services/GitService.php (1)
app/Services/ProcessService.php (2)
  • ProcessService (12-49)
  • run (33-48)

Extract common git command execution pattern from detectRemoteUrl() and
detectCurrentBranch() into a private runGitCommand() helper method.

This reduces code duplication and improves maintainability by centralizing
git command execution logic in a single place.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
app/Services/GitService.php (1)

61-79: Consider extracting timeout constant.

The implementation correctly handles getcwd() returning false and catches exceptions appropriately for a non-fatal detection service. However, the hardcoded 2.0 timeout could be a private class constant for easier maintenance.

Apply this diff to extract the timeout:

 final readonly class GitService
 {
+    private const float GIT_COMMAND_TIMEOUT = 2.0;
+
     public function __construct(private ProcessService $proc)

Then update line 69:

-            $process = $this->proc->run($cmd, $cwd, 2.0);
+            $process = $this->proc->run($cmd, $cwd, self::GIT_COMMAND_TIMEOUT);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 11ed8ea and 6e81082.

📒 Files selected for processing (1)
  • app/Services/GitService.php (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.php

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

**/*.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

**/*.php: Adhere to PSR-12 coding style in all PHP files
Declare strict_types=1 at the top of every PHP file
Prefer PHP 8.x features (union types, match, attributes, readonly) where appropriate
Always import classes with use statements; avoid fully qualified class names in code bodies
All methods must declare explicit return types; use proper generics in types/docblocks (e.g., Collection<int, User>)
Use dependency injection instead of manually resolving or instantiating classes
Prefer Symfony component classes (e.g., Filesystem, Process) over native PHP functions for testability
All object creation must use $container->build(ClassName::class) instead of new, except for value objects/DTOs/pure data structures
In production code, access the Container via constructor injection, not via static/global access
Add minimal DocBlock comments with descriptions, parameters, and return types for classes and functions
Use comments as visual separators for sections/subsections with a single newline between header, subheader, and paragraph; avoid obvious or stale comments
Run rector on changed PHP files before completing a task
Run pint on changed PHP files to fix code style before completing a task

Files:

  • app/Services/GitService.php
**/*Service.php

📄 CodeRabbit inference engine (.cursor/rules/01-architecture.mdc)

**/*Service.php: Services provide atomic, reusable functionality with no console I/O
Services accept and return plain PHP data types (no console I/O)
Services must be dependency-injected via constructor; declare dependencies in constructor signatures
Extract complex orchestration shared by multiple Commands into dedicated Services
Stateful services should use lazy loading when initialization is expensive or path-dependent
State must be initialized explicitly via public methods (e.g., load(), initialize()) before use
Services should document their stateful nature and initialization requirements

Files:

  • app/Services/GitService.php
app/Services/**/*.php

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

Services must return plain data and never perform console IO (no SymfonyStyle, no Laravel Prompts)

Files:

  • app/Services/GitService.php
🧬 Code graph analysis (1)
app/Services/GitService.php (1)
app/Services/ProcessService.php (2)
  • ProcessService (12-49)
  • run (33-48)
🔇 Additional comments (3)
app/Services/GitService.php (3)

1-16: LGTM! Clean service setup.

The class properly declares strict_types, uses final readonly for PHP 8.2+ immutability, and injects ProcessService via constructor. The structure follows coding guidelines for services.


18-34: LGTM! Clean delegation pattern.

The method properly delegates to the private helper, has complete DocBlock documentation, and uses the visual separator as required by coding guidelines.


36-48: LGTM! Consistent with detectRemoteUrl.

The method follows the same clean delegation pattern and has complete documentation.

@loadinglucian
loadinglucian merged commit 9644d64 into main Oct 16, 2025
5 checks passed
@loadinglucian
loadinglucian deleted the feat/add-site-commands branch October 16, 2025 18:30
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