Skip to content

Refactor InstallCommand for improved clarity and consistency #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 12, 2025
Merged
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
92 changes: 21 additions & 71 deletions src/Console/InstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

namespace Laravel\Boost\Console;

use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Str;
use Laravel\Boost\Contracts\Agent;
use Laravel\Boost\Contracts\Ide;
Expand All @@ -19,6 +19,7 @@
use Laravel\Boost\Install\Herd;
use Laravel\Prompts\Concerns\Colors;
use Laravel\Prompts\Terminal;
use ReflectionClass;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Finder\Finder;

Expand Down Expand Up @@ -162,9 +163,6 @@ private function outro(): void
{
$label = 'https://boost.laravel.com/installed';

// Build install data - CSV format with type prefixes
$data = [];

$ideNames = $this->selectedTargetIdes->map(fn ($ide) => 'i:'.class_basename($ide))->toArray();
$agentNames = $this->selectedTargetAgents->map(fn ($agent) => 'a:'.class_basename($agent))->toArray();
$boostFeatures = $this->selectedBoostFeatures->map(fn ($feature) => 'b:'.$feature)->toArray();
Expand All @@ -182,7 +180,7 @@ private function outro(): void
// Combine all data
$allData = array_merge($ideNames, $agentNames, $boostFeatures, $guidelines);

// Create compact CSV string and base64 encode
// Create a compact CSV string and base64 encode
$installData = base64_encode(implode(',', $allData));

$link = $this->hyperlink($label, 'https://boost.laravel.com/installed/?d='.$installData);
Expand All @@ -191,12 +189,12 @@ private function outro(): void
$paddingLength = (int) (floor(($this->terminal->cols() - mb_strlen($text.$label)) / 2)) - 2;

echo "\033[42m\033[2K".str_repeat(' ', $paddingLength); // Make the entire line have a green background
echo $this->black($this->bold($text.$link)).$this->reset().PHP_EOL;
echo $this->black($this->bold($text.$link)).$this->reset(PHP_EOL);
}

private function hyperlink(string $label, string $url): string
{
return "\033]8;;{$url}\007{$label}\033]8;;\033\\";
return "\033]8;;$url\007$label\033]8;;\033\\";
}

/**
Expand Down Expand Up @@ -282,7 +280,6 @@ private function discoverProjectAgents(): array
}
}

// Also check installed IDEs that might not have project files yet
foreach ($this->systemInstalledCodeEnvironments as $ide) {
if (isset($ideToAgentMap[$ide]) && ! in_array($ideToAgentMap[$ide], $agents)) {
$agents[] = $ideToAgentMap[$ide];
Expand All @@ -298,7 +295,7 @@ private function discoverProjectAgents(): array
private function selectTargetIdes(): Collection
{
$ides = [];
if (! $this->installingMcp() && ! $this->installingHerdMcp()) {
if (! $this->shouldInstallMcp() && ! $this->shouldInstallHerdMcp()) {
return collect();
}

Expand All @@ -313,7 +310,7 @@ private function selectTargetIdes(): Collection
$className = 'Laravel\\Boost\\Install\\Agents\\'.$ideFile->getBasename('.php');

if (class_exists($className)) {
$reflection = new \ReflectionClass($className);
$reflection = new ReflectionClass($className);

if ($reflection->implementsInterface(Ide::class) && ! $reflection->isAbstract()) {
$ides[$className] = Str::headline($ideFile->getBasename('.php'));
Expand All @@ -323,7 +320,6 @@ private function selectTargetIdes(): Collection

ksort($ides);

// Map detected IDE keys to class names
$detectedClasses = [];
foreach ($this->projectInstalledCodeEnvironments as $ideKey) {
foreach ($ides as $className => $displayName) {
Expand Down Expand Up @@ -352,7 +348,7 @@ private function selectTargetIdes(): Collection
private function selectTargetAgents(): Collection
{
$agents = [];
if (! $this->installingGuidelines()) {
if (! $this->shouldInstallAiGuidelines()) {
return collect();
}

Expand All @@ -367,7 +363,7 @@ private function selectTargetAgents(): Collection
$className = 'Laravel\\Boost\\Install\\Agents\\'.$agentFile->getBasename('.php');

if (class_exists($className)) {
$reflection = new \ReflectionClass($className);
$reflection = new ReflectionClass($className);

if ($reflection->implementsInterface(Agent::class)) {
$agents[$className] = Str::headline($agentFile->getBasename('.php'));
Expand Down Expand Up @@ -412,7 +408,7 @@ protected function enactGuidelines(): void

$guidelineConfig = new GuidelineConfig;
$guidelineConfig->enforceTests = $this->enforceTests;
$guidelineConfig->laravelStyle = $this->installingStyleGuidelines();
$guidelineConfig->laravelStyle = $this->shouldInstallStyleGuidelines();
$guidelineConfig->caresAboutLocalization = $this->detectLocalization();
$guidelineConfig->hasAnApi = false;

Expand All @@ -431,15 +427,15 @@ protected function enactGuidelines(): void
$longestAgentName = max(1, ...$this->selectedTargetAgents->map(fn ($agent) => Str::length(class_basename($agent)))->toArray());
foreach ($this->selectedTargetAgents as $agent) {
$agentName = class_basename($agent);
$displayAgentName = str_pad($agentName, $longestAgentName, ' ', STR_PAD_RIGHT);
$this->output->write(" {$displayAgentName}... ");
$displayAgentName = str_pad($agentName, $longestAgentName);
$this->output->write(" $displayAgentName... ");

try {
(new GuidelineWriter($agent))
->write($composedAiGuidelines);

$this->line($this->greenTick);
} catch (\Exception $e) {
} catch (Exception $e) {
$failed[$agentName] = $e->getMessage();
$this->line($this->redCross);
}
Expand All @@ -453,7 +449,7 @@ protected function enactGuidelines(): void
count($failed) === 1 ? '' : 's'
));
foreach ($failed as $agentName => $error) {
$this->line(" - {$agentName}: {$error}");
$this->line(" - $agentName: $error");
}
}
}
Expand All @@ -478,53 +474,7 @@ private function shouldInstallHerdMcp(): bool
return $this->selectedBoostFeatures->contains('herd_mcp');
}

protected function publishAndUpdateConfig(): void
{
$configPath = config_path('boost.php');

// Publish config if it doesn't exist
if (! file_exists($configPath)) {
$this->newLine();
$this->info(' Publishing Boost configuration file...');

Artisan::call('vendor:publish', [
'--provider' => 'Laravel\\Boost\\BoostServiceProvider',
'--tag' => 'boost-config',
'--force' => false,
]);

$this->line(' Configuration published '.$this->greenTick);
$this->newLine();
}

// $updated = $this->updateProjectPurposeInConfig($configPath, $this->projectPurpose);
}

protected function updateProjectPurposeInConfig(string $configPath, ?string $purpose): bool
{
if (empty($purpose) || $purpose === config('boost.project_purpose', '')) {
return false;
}

$content = file_get_contents($configPath);
if ($content === false) {
return false;
}

$purposeExists = preg_match('/\'project_purpose\'\s+\=\>\s+(.+),/', $content, $matches);

if (! $purposeExists) { // This shouldn't be possible
return false;
}

$newPurpose = addcslashes($purpose, "'");
$newPurposeLine = "'project_purpose' => '{$newPurpose}',";
$content = str_replace($matches[0], $newPurposeLine, $content);

return file_put_contents($configPath, $content) !== false;
}

protected function enactMcpServers(): void
private function enactMcpServers(): void
{
$this->newLine();
$this->info(' Installing MCP servers to your selected IDEs');
Expand All @@ -537,8 +487,8 @@ protected function enactMcpServers(): void

foreach ($this->selectedTargetIdes as $ide) {
$ideName = class_basename($ide);
$ideDisplay = str_pad($ideName, $longestIdeName, ' ', STR_PAD_RIGHT);
$this->output->write(" {$ideDisplay}... ");
$ideDisplay = str_pad($ideName, $longestIdeName);
$this->output->write(" $ideDisplay... ");
$results = [];

// Install Laravel Boost MCP if enabled
Expand All @@ -552,7 +502,7 @@ protected function enactMcpServers(): void
$results[] = $this->redCross.' Boost';
$failed[$ideName]['boost'] = 'Failed to write configuration';
}
} catch (\Exception $e) {
} catch (Exception $e) {
$results[] = $this->redCross.' Boost';
$failed[$ideName]['boost'] = $e->getMessage();
}
Expand All @@ -574,7 +524,7 @@ protected function enactMcpServers(): void
$results[] = $this->redCross.' Herd';
$failed[$ideName]['herd'] = 'Failed to write configuration';
}
} catch (\Exception $e) {
} catch (Exception $e) {
$results[] = $this->redCross.' Herd';
$failed[$ideName]['herd'] = $e->getMessage();
}
Expand All @@ -589,7 +539,7 @@ protected function enactMcpServers(): void
$this->error(sprintf('%s Some MCP servers failed to install:', $this->redCross));
foreach ($failed as $ideName => $errors) {
foreach ($errors as $server => $error) {
$this->line(" - {$ideName} ({$server}): {$error}");
$this->line(" - $ideName ($server): $error");
}
}
}
Expand All @@ -598,7 +548,7 @@ protected function enactMcpServers(): void
/**
* Is the project actually using localization for their new features?
*/
protected function detectLocalization(): bool
private function detectLocalization(): bool
{
$actuallyUsing = false;

Expand Down
Loading