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
15 changes: 9 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"mockery/mockery": "^1.6.12",
"orchestra/testbench": "^8.36.0|^9.15.0|^10.6",
"pestphp/pest": "^2.36.0|^3.8.4",
"phpstan/phpstan": "^2.1.27"
"phpstan/phpstan": "^2.1.27",
"rector/rector": "^2.1"
},
"autoload": {
"psr-4": {
Expand All @@ -52,13 +53,15 @@
},
"scripts": {
"lint": [
"vendor/bin/pint",
"vendor/bin/phpstan --memory-limit=-1"
"pint",
"phpstan --memory-limit=-1",
"rector"
],
"test": [
"vendor/bin/pest"
"test": "pest",
"test:lint": [
"pint --test",
"rector --dry-run"
],
"test:lint": "pint --test",
"test:types": "phpstan",
"check": [
"@composer lint",
Expand Down
6 changes: 3 additions & 3 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
<phpunit colors="true">
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
<directory suffix="Test.php">tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
<directory suffix="Test.php">tests/Feature</directory>
</testsuite>
<testsuite name="Arch">
<file>./tests/ArchTest.php</file>
<file>tests/ArchTest.php</file>
</testsuite>
</testsuites>
<php>
Expand Down
29 changes: 29 additions & 0 deletions rector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

use Rector\CodingStyle\Rector\Encapsed\EncapsedStringsToSprintfRector;
use Rector\Config\RectorConfig;
use Rector\Php81\Rector\Property\ReadOnlyPropertyRector;
use Rector\Strict\Rector\BooleanNot\BooleanInBooleanNotRuleFixerRector;
use Rector\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector;

return RectorConfig::configure()
->withPaths([
__DIR__.'/src',
__DIR__.'/tests',
])
->withSkip([
ReadOnlyPropertyRector::class,
EncapsedStringsToSprintfRector::class,
DisallowedEmptyRuleFixerRector::class,
BooleanInBooleanNotRuleFixerRector::class,
])
->withPreparedSets(
deadCode: true,
codeQuality: true,
codingStyle: true,
typeDeclarations: true,
earlyReturn: true,
strictBooleans: true,
)->withPhpSets(php81: true);
23 changes: 9 additions & 14 deletions src/BoostServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function register(): void
];

$cacheKey = 'boost.roster.scan';
$lastModified = max(array_map(fn ($path) => file_exists($path) ? filemtime($path) : 0, $lockFiles));
$lastModified = max(array_map(fn (string $path): int|false => file_exists($path) ? filemtime($path) : 0, $lockFiles));

$cached = cache()->get($cacheKey);
if ($cached && isset($cached['timestamp']) && $cached['timestamp'] >= $lastModified) {
Expand Down Expand Up @@ -113,7 +113,12 @@ private function registerRoutes(): void
* } $log */
foreach ($logs as $log) {
$logger->write(
level: self::mapJsTypeToPsr3Level($log['type']),
level: match ($log['type']) {
'warn' => 'warning',
'log', 'table' => 'debug',
'window_error', 'uncaught_error', 'unhandled_rejection' => 'error',
default => $log['type']
},
message: self::buildLogMessageFromData($log['data']),
context: [
'url' => $log['url'],
Expand Down Expand Up @@ -165,22 +170,12 @@ private function registerBrowserLogger(): void

private function registerBladeDirectives(BladeCompiler $bladeCompiler): void
{
$bladeCompiler->directive('boostJs', fn () => '<?php echo \\Laravel\\Boost\\Services\\BrowserLogger::getScript(); ?>');
}

private static function mapJsTypeToPsr3Level(string $type): string
{
return match ($type) {
'warn' => 'warning',
'log', 'table' => 'debug',
'window_error', 'uncaught_error', 'unhandled_rejection' => 'error',
default => $type
};
$bladeCompiler->directive('boostJs', fn (): string => '<?php echo '.\Laravel\Boost\Services\BrowserLogger::class.'::getScript(); ?>');
}

private function hookIntoResponses(Router $router): void
{
$this->app->booted(function () use ($router) {
$this->app->booted(function () use ($router): void {
$router->pushMiddlewareToGroup('web', InjectBoost::class);
});
}
Expand Down
4 changes: 2 additions & 2 deletions src/Concerns/MakesHttpRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ public function client(): PendingRequest
]);

// Disable SSL verification for local development URLs and testing
if (app()->environment(['local', 'testing']) || str_contains(config('boost.hosted.api_url', ''), '.test')) {
$client = $client->withoutVerifying();
if (app()->environment(['local', 'testing']) || str_contains((string) config('boost.hosted.api_url', ''), '.test')) {
return $client->withoutVerifying();
}

return $client;
Expand Down
4 changes: 2 additions & 2 deletions src/Concerns/ReadsLogs.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private function getChunkSizeStart(): int

private function getChunkSizeMax(): int
{
return 1 * 1024 * 1024; // 1 MB
return 1024 * 1024; // 1 MB
}

/**
Expand Down Expand Up @@ -96,7 +96,7 @@ protected function readLastErrorEntry(string $logFile): ?string

for ($i = count($entries) - 1; $i >= 0; $i--) {
if ($this->isErrorEntry($entries[$i])) {
return trim($entries[$i]);
return trim((string) $entries[$i]);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/Console/ExecuteToolCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ public function handle(): int
try {
/** @var Response $response */
$response = $tool->handle($request); // @phpstan-ignore-line
} catch (Throwable $e) {
$errorResult = Response::error("Tool execution failed (E_THROWABLE): {$e->getMessage()}");
} catch (Throwable $throwable) {
$errorResult = Response::error("Tool execution failed (E_THROWABLE): {$throwable->getMessage()}");

$this->error(json_encode([
'isError' => true,
Expand Down
40 changes: 20 additions & 20 deletions src/Console/InstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ private function bootstrap(CodeEnvironmentsDetector $codeEnvironmentsDetector, H
$this->terminal = $terminal;

$this->terminal->initDimensions();

$this->greenTick = $this->green('✓');
$this->redCross = $this->red('✗');

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

$ideNames = $this->selectedTargetMcpClient->map(fn (McpClient $mcpClient) => 'i:'.$mcpClient->mcpClientName())
$ideNames = $this->selectedTargetMcpClient->map(fn (McpClient $mcpClient): string => 'i:'.$mcpClient->mcpClientName())
->toArray();
$agentNames = $this->selectedTargetAgents->map(fn (Agent $agent) => 'a:'.$agent->agentName())->toArray();
$boostFeatures = $this->selectedBoostFeatures->map(fn ($feature) => 'b:'.$feature)->toArray();
$agentNames = $this->selectedTargetAgents->map(fn (Agent $agent): string => 'a:'.$agent->agentName())->toArray();
$boostFeatures = $this->selectedBoostFeatures->map(fn ($feature): string => 'b:'.$feature)->toArray();

$guidelines = [];
if ($this->shouldInstallAiGuidelines()) {
Expand Down Expand Up @@ -211,12 +212,12 @@ protected function determineTestEnforcement(bool $ask = true): bool
$hasMinimumTests = Str::of($process->getOutput())
->trim()
->explode("\n")
->filter(fn ($line) => str_contains($line, '::'))
->filter(fn ($line): bool => str_contains($line, '::'))
->count() >= self::MIN_TEST_COUNT;
}

if (! $hasMinimumTests && $ask) {
$hasMinimumTests = select(
return select(
label: 'Should AI always create tests?',
options: ['Yes', 'No'],
default: 'Yes'
Expand Down Expand Up @@ -320,19 +321,17 @@ private function selectCodeEnvironments(string $contractClass, string $label): C
$allEnvironments = $this->codeEnvironmentsDetector->getCodeEnvironments();
$config = $this->getSelectionConfig($contractClass);

$availableEnvironments = $allEnvironments->filter(function (CodeEnvironment $environment) use ($contractClass) {
return $environment instanceof $contractClass;
});
$availableEnvironments = $allEnvironments->filter(fn (CodeEnvironment $environment): bool => $environment instanceof $contractClass);

if ($availableEnvironments->isEmpty()) {
return collect();
}

$options = $availableEnvironments->mapWithKeys(function (CodeEnvironment $environment) use ($config) {
$options = $availableEnvironments->mapWithKeys(function (CodeEnvironment $environment) use ($config): array {
$displayMethod = $config['displayMethod'];
$displayText = $environment->{$displayMethod}();

return [get_class($environment) => $displayText];
return [$environment::class => $displayText];
})->sort();

$detectedClasses = [];
Expand All @@ -342,9 +341,9 @@ private function selectCodeEnvironments(string $contractClass, string $label): C
));

foreach ($installedEnvNames as $envKey) {
$matchingEnv = $availableEnvironments->first(fn (CodeEnvironment $env) => strtolower($envKey) === strtolower($env->name()));
$matchingEnv = $availableEnvironments->first(fn (CodeEnvironment $env): bool => strtolower((string) $envKey) === strtolower($env->name()));
if ($matchingEnv) {
$detectedClasses[] = get_class($matchingEnv);
$detectedClasses[] = $matchingEnv::class;
}
}

Expand All @@ -354,17 +353,17 @@ private function selectCodeEnvironments(string $contractClass, string $label): C
default: array_unique($detectedClasses),
scroll: $config['scroll'],
required: $config['required'],
hint: empty($detectedClasses) ? '' : sprintf('Auto-detected %s for you',
hint: $detectedClasses === [] ? '' : sprintf('Auto-detected %s for you',
Arr::join(array_map(function ($className) use ($availableEnvironments, $config) {
$env = $availableEnvironments->first(fn ($env) => get_class($env) === $className);
$env = $availableEnvironments->first(fn ($env): bool => $env::class === $className);
$displayMethod = $config['displayMethod'];

return $env->{$displayMethod}();
}, $detectedClasses), ', ', ' & ')
)
))->sort();

return $selectedClasses->map(fn ($className) => $availableEnvironments->first(fn ($env) => get_class($env) === $className));
return $selectedClasses->map(fn ($className) => $availableEnvironments->first(fn ($env): bool => $env::class === $className));
}

private function installGuidelines(): void
Expand Down Expand Up @@ -392,7 +391,7 @@ private function installGuidelines(): void
$this->info(sprintf(' Adding %d guidelines to your selected agents', $guidelines->count()));
DisplayHelper::grid(
$guidelines
->map(fn ($guideline, string $key) => $key.($guideline['custom'] ? '*' : ''))
->map(fn ($guideline, string $key): string => $key.($guideline['custom'] ? '*' : ''))
->values()
->sort()
->toArray(),
Expand All @@ -408,7 +407,7 @@ private function installGuidelines(): void
/** @var CodeEnvironment $agent */
foreach ($this->selectedTargetAgents as $agent) {
$agentName = $agent->agentName();
$displayAgentName = str_pad($agentName, $longestAgentName);
$displayAgentName = str_pad((string) $agentName, $longestAgentName);
$this->output->write(" {$displayAgentName}... ");
/** @var Agent $agent */
try {
Expand All @@ -424,7 +423,7 @@ private function installGuidelines(): void

$this->newLine();

if (count($failed) > 0) {
if ($failed !== []) {
$this->error(sprintf('✗ Failed to install guidelines to %d agent%s:',
count($failed),
count($failed) === 1 ? '' : 's'
Expand Down Expand Up @@ -466,6 +465,7 @@ private function installMcpServerConfig(): void

return;
}

$this->newLine();
$this->info(' Installing MCP servers to your selected IDEs');
$this->newLine();
Expand All @@ -483,7 +483,7 @@ private function installMcpServerConfig(): void
/** @var McpClient $mcpClient */
foreach ($this->selectedTargetMcpClient as $mcpClient) {
$ideName = $mcpClient->mcpClientName();
$ideDisplay = str_pad($ideName, $longestIdeName);
$ideDisplay = str_pad((string) $ideName, $longestIdeName);
$this->output->write(" {$ideDisplay}... ");
$results = [];

Expand Down Expand Up @@ -532,7 +532,7 @@ private function installMcpServerConfig(): void

$this->newLine();

if (count($failed) > 0) {
if ($failed !== []) {
$this->error(sprintf('%s Some MCP servers failed to install:', $this->redCross));
foreach ($failed as $ideName => $errors) {
foreach ($errors as $server => $error) {
Expand Down
18 changes: 13 additions & 5 deletions src/Install/Assists/Inertia.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,19 @@ public function __construct(private Roster $roster) {}

public function gte(string $version): bool
{
return
$this->roster->usesVersion(Packages::INERTIA_LARAVEL, $version, '>=') ||
$this->roster->usesVersion(Packages::INERTIA_REACT, $version, '>=') ||
$this->roster->usesVersion(Packages::INERTIA_SVELTE, $version, '>=') ||
$this->roster->usesVersion(Packages::INERTIA_VUE, $version, '>=');
if ($this->roster->usesVersion(Packages::INERTIA_LARAVEL, $version, '>=')) {
return true;
}

if ($this->roster->usesVersion(Packages::INERTIA_REACT, $version, '>=')) {
return true;
}

if ($this->roster->usesVersion(Packages::INERTIA_SVELTE, $version, '>=')) {
return true;
}

return $this->roster->usesVersion(Packages::INERTIA_VUE, $version, '>=');
}

public function hasFormComponent(): bool
Expand Down
Loading
Loading