Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .cursor/rules/00-main.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ Build Deployer PHP: Composer package and CLI tool simplifying server provisionin
- Review surrounding code for reusable patterns
- Code should appear written by single person: naming, parameter precedence, logic flow, organization

### File Operations

Use terminal commands for file management—never read+write entire contents:

```bash
mv old.php new.php # Rename/move
cp source.php dest.php # Copy
mkdir -p path/to/dir # Create directories
```

### Execution Protocol

1. ULTRATHINK - analyze problem deeply
Expand Down
32 changes: 0 additions & 32 deletions app/Console/HelloCommand.php

This file was deleted.

7 changes: 6 additions & 1 deletion app/Console/Server/ServerAddCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

#[AsCommand(
name: 'server:add',
description: 'Add a new server to the inventory'
description: 'Add a new server to inventory'
)]
class ServerAddCommand extends BaseCommand
{
Expand Down Expand Up @@ -96,6 +96,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$this->yay('Server added to inventory');

$this->ul([
'Run <|cyan>server:info</> to view server information',
'Or run <|cyan>server:install</> to install your new server',
]);

//
// Show command replay
// ----
Expand Down
2 changes: 1 addition & 1 deletion app/Console/Server/ServerDeleteCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$this->servers->delete($server->name);

$this->yay("Server '{$server->name}' deleted from inventory");
$this->yay("Server '{$server->name}' removed from inventory");

//
// Delete associated sites
Expand Down
2 changes: 1 addition & 1 deletion app/Console/Server/ServerInstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$this->yay('Server installation completed successfully');

$this->ul([
'Run <|cyan>site:add</> to add a new site',
'Run <|cyan>site:create</> to create a new site',
'Add the following <|yellow>public key</> to your Git provider (GitHub, GitLab, etc.) to enable deployments:',
]);

Expand Down
2 changes: 1 addition & 1 deletion app/Console/Server/ServerListCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

#[AsCommand(
name: 'server:list',
description: 'List servers in the inventory'
description: 'List servers in inventory'
)]
class ServerListCommand extends BaseCommand
{
Expand Down
5 changes: 5 additions & 0 deletions app/Console/Server/ServerProvisionDigitalOceanCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$this->yay('Server added to inventory');

$this->ul([
'Run <|cyan>server:info</> to view server information',
'Or run <|cyan>server:install</> to install your new server',
]);

$shouldKeepDroplet = true;
} catch (\RuntimeException $e) {
$this->nay($e->getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
use Symfony\Component\Console\Output\OutputInterface;

#[AsCommand(
name: 'site:add',
description: 'Set up a new site on the server and add it to the inventory'
name: 'site:create',
description: 'Create a new site on a server and add it to inventory'
)]
class SiteAddCommand extends BaseCommand
class SiteCreateCommand extends BaseCommand
{
use PlaybooksTrait;
use ServersTrait;
Expand All @@ -35,8 +35,6 @@ protected function configure(): void

$this
->addOption('domain', null, InputOption::VALUE_REQUIRED, 'Domain name')
->addOption('repo', null, InputOption::VALUE_REQUIRED, 'Git repository URL')
->addOption('branch', null, InputOption::VALUE_REQUIRED, 'Git branch name')
->addOption('server', null, InputOption::VALUE_REQUIRED, 'Server name')
->addOption('php-version', null, InputOption::VALUE_REQUIRED, 'PHP version to use')
->addOption('www-mode', null, InputOption::VALUE_REQUIRED, 'WWW handling mode (redirect-to-root, redirect-to-www)');
Expand All @@ -50,15 +48,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
{
parent::execute($input, $output);

$this->h1('Add New Site');
$this->h1('Create New Site');

//
// Select server
// ----

$server = $this->selectServer();

if (is_int($server) || $server->info === null) {
if (is_int($server) || null === $server->info) {
return Command::FAILURE;
}

Expand All @@ -71,7 +69,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
/** @var string $permissions */

//
// Validate server is ready to add site
// Validate server is ready to create site
// ----

$validationResult = $this->validateServerReady($server->info);
Expand All @@ -86,14 +84,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$siteInfo = $this->gatherSiteInfo($server->info);

if ($siteInfo === null) {
if (null === $siteInfo) {
return Command::FAILURE;
}

[
'domain' => $domain,
'repo' => $repo,
'branch' => $branch,
'phpVersion' => $phpVersion,
'wwwMode' => $wwwMode,
] = $siteInfo;
Expand All @@ -104,21 +100,21 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$site = new SiteDTO(
domain: $domain,
repo: $repo,
branch: $branch,
repo: null,
branch: null,
server: $server->name
);

$this->displaySiteDeets($site);

//
// Add site on server
// Create site on server
// ----

$result = $this->executePlaybookSilently(
$server,
'site-add',
'Adding site...',
'site-create',
'Creating site on server...',
[
Comment thread
coderabbitai[bot] marked this conversation as resolved.
'DEPLOYER_DISTRO' => $distro,
'DEPLOYER_PERMS' => $permissions,
Expand All @@ -132,8 +128,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return $result;
}

$this->yay('Site added successfully');

//
// Save to inventory
// ----
Expand All @@ -146,35 +140,27 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return Command::FAILURE;
}

$this->yay('Site added to inventory');
$this->yay("Site '{$domain}' added to inventory");

//
// Display next steps
// ----

$displayUrl = ($wwwMode === 'redirect-to-www')
? 'http://www.' . $domain
: 'http://' . $domain;

$this->out([
'Next steps:',
' • Site is accessible at <fg=cyan>' . $displayUrl . '</>',
' • Update <fg=cyan>DNS records</>:',
' - Point <fg=cyan>@</> (root) to <fg=cyan>' . $server->host . '</>',
' - Point <fg=cyan>www</> to <fg=cyan>' . $server->host . '</>',
' • Run <fg=cyan>site:https</> to enable HTTPS once you have your DNS records set up',
' • Deploy your application with <fg=cyan>site:deploy</>',
'',
$this->info('Please update your DNS records:');

$this->ul([
'Point <fg=cyan>@</> (root) to <fg=cyan>' . $server->host . '</>',
'Point <fg=cyan>www</> to <fg=cyan>' . $server->host . '</>',
'Run <fg=cyan>site:https</> to enable HTTPS once you have your DNS records set up',
'Deploy your new site with <fg=cyan>site:deploy</>'
]);

//
// Show command replay
// ----

$this->commandReplay('site:add', [
$this->commandReplay('site:create', [
'domain' => $domain,
'repo' => $repo,
'branch' => $branch,
'server' => $server->name,
'php-version' => $phpVersion,
'www-mode' => $wwwMode,
Expand All @@ -200,12 +186,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int
private function validateServerReady(array $info): ?int
{
// Check if Caddy is installed
$caddyInstalled = isset($info['caddy']) && is_array($info['caddy']) && ($info['caddy']['available'] ?? false) === true;
$caddyInstalled = isset($info['caddy']) && is_array($info['caddy']) && true === ($info['caddy']['available'] ?? false);

// Check if PHP is installed
$phpInstalled = isset($info['php']) && is_array($info['php']) && isset($info['php']['versions']) && is_array($info['php']['versions']) && count($info['php']['versions']) > 0;

if (!$caddyInstalled || !$phpInstalled) {
if (! $caddyInstalled || ! $phpInstalled) {
$this->nay('Looks like the server was not installed as expected');
$this->out([
'Run <fg=cyan>server:install</> to install required software.',
Expand Down Expand Up @@ -251,7 +237,7 @@ private function selectPhpVersion(array $info): string|int
}

// If only one version, use it automatically
if (count($installedPhpVersions) === 1) {
if (1 === count($installedPhpVersions)) {
return $installedPhpVersions[0];
}

Expand All @@ -261,7 +247,7 @@ private function selectPhpVersion(array $info): string|int
/** @var array{default?: string|int|float}|null $phpInfo */
$phpInfo = $info['php'] ?? null;
$defaultVersion = is_array($phpInfo) ? ($phpInfo['default'] ?? null) : null;
$defaultVersionStr = $defaultVersion !== null ? (string) $defaultVersion : $installedPhpVersions[0];
$defaultVersionStr = null !== $defaultVersion ? (string) $defaultVersion : $installedPhpVersions[0];

$phpVersion = (string) $this->io->getOptionOrPrompt(
'php-version',
Expand All @@ -273,7 +259,7 @@ private function selectPhpVersion(array $info): string|int
);

// Validate CLI-provided version exists in available versions
if (!in_array($phpVersion, $installedPhpVersions, true)) {
if (! in_array($phpVersion, $installedPhpVersions, true)) {
$this->nay(
"PHP version {$phpVersion} is not installed on this server. Available: " . implode(', ', $installedPhpVersions)
);
Expand All @@ -292,7 +278,7 @@ private function selectPhpVersion(array $info): string|int
* Gather site details from user input or CLI options.
*
* @param array<string, mixed> $info Server information from serverInfo()
* @return array{domain: string, repo: string, branch: string, phpVersion: string, wwwMode: string}|null
* @return array{domain: string, phpVersion: string, wwwMode: string}|null
*/
protected function gatherSiteInfo(array $info): ?array
{
Expand All @@ -308,7 +294,7 @@ protected function gatherSiteInfo(array $info): ?array
fn ($value) => $this->validateSiteDomain($value)
);

if ($domain === null) {
if (null === $domain) {
return null;
}

Expand Down Expand Up @@ -338,49 +324,7 @@ protected function gatherSiteInfo(array $info): ?array
: sprintf("Invalid WWW mode '%s'. Allowed: %s", is_scalar($value) ? $value : gettype($value), implode(', ', array_keys($wwwModes)))
);

if ($wwwMode === null) {
return null;
}

//
// Gather git details
// ----

$defaultRepo = $this->git->detectRemoteUrl() ?? '';

/** @var string|null $repo */
$repo = $this->io->getValidatedOptionOrPrompt(
'repo',
fn ($validate) => $this->io->promptText(
label: 'Git repository URL:',
placeholder: 'git@github.com:user/repo.git',
default: $defaultRepo,
required: true,
validate: $validate
),
fn ($value) => $this->validateSiteRepo($value)
);

if ($repo === null) {
return null;
}

$defaultBranch = $this->git->detectCurrentBranch() ?? 'main';

/** @var string|null $branch */
$branch = $this->io->getValidatedOptionOrPrompt(
'branch',
fn ($validate) => $this->io->promptText(
label: 'Git branch:',
placeholder: $defaultBranch,
default: $defaultBranch,
required: true,
validate: $validate
),
fn ($value) => $this->validateSiteBranch($value)
);

if ($branch === null) {
if (null === $wwwMode) {
return null;
}

Expand All @@ -396,8 +340,6 @@ protected function gatherSiteInfo(array $info): ?array

return [
'domain' => $domain,
'repo' => $repo,
'branch' => $branch,
'phpVersion' => $phpVersion,
'wwwMode' => $wwwMode,
];
Expand Down
Loading