Skip to content

Commit b0a963e

Browse files
authored
Merge pull request #15 from laravel/cleanup_query_methos
Refactor InstallCommand for clarity and maintainability
2 parents 87bd1ab + ce2d8e4 commit b0a963e

File tree

2 files changed

+47
-61
lines changed

2 files changed

+47
-61
lines changed

src/Console/InstallCommand.php

Lines changed: 46 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
use Illuminate\Support\Collection;
1010
use Illuminate\Support\Facades\Artisan;
1111
use Illuminate\Support\Str;
12+
use Laravel\Boost\Contracts\Agent;
13+
use Laravel\Boost\Contracts\Ide;
1214
use Laravel\Boost\Install\Cli\DisplayHelper;
1315
use Laravel\Boost\Install\CodeEnvironmentsDetector;
1416
use Laravel\Boost\Install\GuidelineComposer;
@@ -25,7 +27,6 @@
2527
use function Laravel\Prompts\multiselect;
2628
use function Laravel\Prompts\note;
2729
use function Laravel\Prompts\select;
28-
use function Laravel\Prompts\text;
2930

3031
#[AsCommand('boost:install', 'Install Laravel Boost')]
3132
class InstallCommand extends Command
@@ -40,27 +41,24 @@ class InstallCommand extends Command
4041

4142
private Terminal $terminal;
4243

43-
/** @var Collection<int, \Laravel\Boost\Contracts\Agent> */
44-
private Collection $agentsToInstallTo;
44+
/** @var Collection<int, Agent> */
45+
private Collection $selectedTargetAgents;
4546

46-
/** @var Collection<int, \Laravel\Boost\Contracts\Ide> */
47-
private Collection $idesToInstallTo;
47+
/** @var Collection<int, Ide> */
48+
private Collection $selectedTargetIdes;
4849

49-
private Collection $boostToInstall;
50+
/** @var Collection<int, string> */
51+
private Collection $selectedBoostFeatures;
5052

5153
private string $projectName;
5254

53-
private string $projectPurpose = '';
54-
5555
/** @var array<non-empty-string> */
5656
private array $systemInstalledCodeEnvironments = [];
5757

5858
private array $projectInstalledCodeEnvironments = [];
5959

6060
private bool $enforceTests = true;
6161

62-
private array $boostToolsToDisable = [];
63-
6462
private array $projectInstalledAgents = [];
6563

6664
private string $greenTick;
@@ -73,7 +71,7 @@ public function handle(CodeEnvironmentsDetector $codeEnvironmentsDetector, Herd
7371

7472
$this->displayBoostHeader();
7573
$this->discoverEnvironment();
76-
$this->query();
74+
$this->collectInstallationPreference();
7775
$this->enact();
7876
$this->outro();
7977
}
@@ -89,8 +87,8 @@ private function bootstrapBoost(CodeEnvironmentsDetector $codeEnvironmentsDetect
8987
$this->greenTick = $this->green('');
9088
$this->redCross = $this->red('');
9189

92-
$this->agentsToInstallTo = collect();
93-
$this->idesToInstallTo = collect();
90+
$this->selectedTargetAgents = collect();
91+
$this->selectedTargetIdes = collect();
9492

9593
$this->projectName = basename(base_path());
9694
}
@@ -122,28 +120,23 @@ private function discoverEnvironment(): void
122120
$this->projectInstalledAgents = $this->discoverProjectAgents();
123121
}
124122

125-
private function query()
123+
private function collectInstallationPreference(): void
126124
{
127-
// Which parts of boost should we install
128-
$this->boostToInstall = $this->boostToInstall();
129-
// $this->boostToolsToDisable = $this->boostToolsToDisable(); // Not useful to start
130-
131-
// $this->projectPurpose = $this->projectPurpose();
132-
$this->enforceTests = $this->shouldEnforceTests(ask: false);
133-
134-
$this->idesToInstallTo = $this->idesToInstallTo(); // To add boost:mcp to the correct file
135-
$this->agentsToInstallTo = $this->agentsToInstallTo(); // AI Guidelines, which file do they go, are they separated, or all in one file?
125+
$this->selectedBoostFeatures = $this->selectBoostFeatures();
126+
$this->enforceTests = $this->determineTestEnforcement(ask: false);
127+
$this->selectedTargetIdes = $this->selectTargetIdes();
128+
$this->selectedTargetAgents = $this->selectTargetAgents();
136129
}
137130

138131
private function enact(): void
139132
{
140-
if ($this->installingGuidelines() && ! empty($this->agentsToInstallTo)) {
133+
if ($this->installingGuidelines() && ! empty($this->selectedTargetAgents)) {
141134
$this->enactGuidelines();
142135
}
143136

144137
usleep(750000);
145138

146-
if (($this->installingMcp() || $this->installingHerdMcp()) && $this->idesToInstallTo->isNotEmpty()) {
139+
if (($this->installingMcp() || $this->installingHerdMcp()) && $this->selectedTargetIdes->isNotEmpty()) {
147140
$this->enactMcpServers();
148141
}
149142
}
@@ -176,9 +169,9 @@ private function outro(): void
176169
// Build install data - CSV format with type prefixes
177170
$data = [];
178171

179-
$ideNames = $this->idesToInstallTo->map(fn ($ide) => 'i:'.class_basename($ide))->toArray();
180-
$agentNames = $this->agentsToInstallTo->map(fn ($agent) => 'a:'.class_basename($agent))->toArray();
181-
$boostFeatures = $this->boostToInstall->map(fn ($feature) => 'b:'.$feature)->toArray();
172+
$ideNames = $this->selectedTargetIdes->map(fn ($ide) => 'i:'.class_basename($ide))->toArray();
173+
$agentNames = $this->selectedTargetAgents->map(fn ($agent) => 'a:'.class_basename($agent))->toArray();
174+
$boostFeatures = $this->selectedBoostFeatures->map(fn ($feature) => 'b:'.$feature)->toArray();
182175

183176
// Guidelines installed (prefix: g)
184177
$guidelines = [];
@@ -210,45 +203,38 @@ private function hyperlink(string $label, string $url): string
210203
return "\033]8;;{$url}\007{$label}\033]8;;\033\\";
211204
}
212205

213-
protected function projectPurpose(): string
214-
{
215-
return text(
216-
label: sprintf('What does the %s project do? (optional)', $this->projectName),
217-
placeholder: 'i.e. SaaS platform selling concert tickets, integrates with Stripe and Twilio, lots of CS using Nova backend',
218-
default: config('boost.project_purpose') ?? '',
219-
hint: 'This helps guides AI. How would you explain it to a new developer?'
220-
);
221-
}
222-
223206
/**
224207
* We shouldn't add an AI guideline enforcing tests if they don't have a basic test setup.
225-
* This would likely just create headaches for them, or be a waste of time as they
208+
* This would likely just create headaches for them or be a waste of time as they
226209
* won't have the CI setup to make use of them anyway, so we're just wasting their
227210
* tokens/money by enforcing them.
211+
*
212+
* @param bool $ask
213+
* @return bool
228214
*/
229-
protected function shouldEnforceTests(bool $ask = true): bool
215+
protected function determineTestEnforcement(bool $ask = true): bool
230216
{
231-
$enforce = Finder::create()
217+
$hasMinimumTests = Finder::create()
232218
->in(base_path('tests'))
233219
->files()
234220
->name('*.php')
235221
->count() > 6;
236222

237-
if ($enforce === false && $ask === true) {
238-
$enforce = select(
223+
if (! $hasMinimumTests && $ask) {
224+
$hasMinimumTests = select(
239225
label: 'Should AI always create tests?',
240226
options: ['Yes', 'No'],
241227
default: 'Yes'
242228
) === 'Yes';
243229
}
244230

245-
return $enforce;
231+
return $hasMinimumTests;
246232
}
247233

248234
/**
249235
* @return Collection<int, string>
250236
*/
251-
protected function boostToInstall(): Collection
237+
private function selectBoostFeatures(): Collection
252238
{
253239
$defaultToInstallOptions = ['mcp_server', 'ai_guidelines'];
254240
$toInstallOptions = [
@@ -316,9 +302,9 @@ private function discoverProjectAgents(): array
316302
}
317303

318304
/**
319-
* @return Collection<int, \Laravel\Boost\Contracts\Ide>
305+
* @return Collection<int, Ide>
320306
*/
321-
private function idesToInstallTo(): Collection
307+
private function selectTargetIdes(): Collection
322308
{
323309
$ides = [];
324310
if (! $this->installingMcp() && ! $this->installingHerdMcp()) {
@@ -338,7 +324,7 @@ private function idesToInstallTo(): Collection
338324
if (class_exists($className)) {
339325
$reflection = new \ReflectionClass($className);
340326

341-
if ($reflection->implementsInterface(\Laravel\Boost\Contracts\Ide::class) && ! $reflection->isAbstract()) {
327+
if ($reflection->implementsInterface(Ide::class) && ! $reflection->isAbstract()) {
342328
$ides[$className] = Str::headline($ideFile->getBasename('.php'));
343329
}
344330
}
@@ -370,9 +356,9 @@ private function idesToInstallTo(): Collection
370356
}
371357

372358
/**
373-
* @return Collection<int, \Laravel\Boost\Contracts\Agent>
359+
* @return Collection<int, Agent>
374360
*/
375-
private function agentsToInstallTo(): Collection
361+
private function selectTargetAgents(): Collection
376362
{
377363
$agents = [];
378364
if (! $this->installingGuidelines()) {
@@ -392,7 +378,7 @@ private function agentsToInstallTo(): Collection
392378
if (class_exists($className)) {
393379
$reflection = new \ReflectionClass($className);
394380

395-
if ($reflection->implementsInterface(\Laravel\Boost\Contracts\Agent::class)) {
381+
if ($reflection->implementsInterface(Agent::class)) {
396382
$agents[$className] = Str::headline($agentFile->getBasename('.php'));
397383
}
398384
}
@@ -427,7 +413,7 @@ protected function enactGuidelines(): void
427413
return;
428414
}
429415

430-
if ($this->agentsToInstallTo->isEmpty()) {
416+
if ($this->selectedTargetAgents->isEmpty()) {
431417
$this->info('No agents selected for guideline installation.');
432418

433419
return;
@@ -451,8 +437,8 @@ protected function enactGuidelines(): void
451437
$failed = [];
452438
$composedAiGuidelines = $composer->compose();
453439

454-
$longestAgentName = max(1, ...$this->agentsToInstallTo->map(fn ($agent) => Str::length(class_basename($agent)))->toArray());
455-
foreach ($this->agentsToInstallTo as $agent) {
440+
$longestAgentName = max(1, ...$this->selectedTargetAgents->map(fn ($agent) => Str::length(class_basename($agent)))->toArray());
441+
foreach ($this->selectedTargetAgents as $agent) {
456442
$agentName = class_basename($agent);
457443
$displayAgentName = str_pad($agentName, $longestAgentName, ' ', STR_PAD_RIGHT);
458444
$this->output->write(" {$displayAgentName}... ");
@@ -483,22 +469,22 @@ protected function enactGuidelines(): void
483469

484470
protected function installingGuidelines(): bool
485471
{
486-
return $this->boostToInstall->contains('ai_guidelines');
472+
return $this->selectedBoostFeatures->contains('ai_guidelines');
487473
}
488474

489475
protected function installingStyleGuidelines(): bool
490476
{
491-
return $this->boostToInstall->contains('style_guidelines');
477+
return $this->selectedBoostFeatures->contains('style_guidelines');
492478
}
493479

494480
protected function installingMcp(): bool
495481
{
496-
return $this->boostToInstall->contains('mcp_server');
482+
return $this->selectedBoostFeatures->contains('mcp_server');
497483
}
498484

499485
protected function installingHerdMcp(): bool
500486
{
501-
return $this->boostToInstall->contains('herd_mcp');
487+
return $this->selectedBoostFeatures->contains('herd_mcp');
502488
}
503489

504490
protected function publishAndUpdateConfig(): void
@@ -556,9 +542,9 @@ protected function enactMcpServers(): void
556542
usleep(750000);
557543

558544
$failed = [];
559-
$longestIdeName = max(1, ...$this->idesToInstallTo->map(fn ($ide) => Str::length(class_basename($ide)))->toArray());
545+
$longestIdeName = max(1, ...$this->selectedTargetIdes->map(fn ($ide) => Str::length(class_basename($ide)))->toArray());
560546

561-
foreach ($this->idesToInstallTo as $ide) {
547+
foreach ($this->selectedTargetIdes as $ide) {
562548
$ideName = class_basename($ide);
563549
$ideDisplay = str_pad($ideName, $longestIdeName, ' ', STR_PAD_RIGHT);
564550
$this->output->write(" {$ideDisplay}... ");

tests/Feature/Console/InstallCommandMultiselectTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public function test_multiselect_returns_values_for_indexed_array(): void
7272
*/
7373
public function test_multiselect_behavior_matches_install_command_expectations(): void
7474
{
75-
// Test the exact same structure used in InstallCommand::boostToInstall()
75+
// Test the exact same structure used in InstallCommand::selectBoostFeatures()
7676
// Note: mcp_server and ai_guidelines are already selected by default
7777
Prompt::fake([
7878
Key::DOWN, // Move to ai_guidelines (already selected)

0 commit comments

Comments
 (0)