Repository navigation
Fix false passes and stale results in Doctor diagnostics #722
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
base: 0.4
Are you sure you want to change the base?
Changes from all commits
4388e3d
2923ad8
5ce8544
5156e47
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,12 +4,14 @@ | |
|
|
||
| namespace Hypervel\Doctor\Diagnostics; | ||
|
|
||
| use Hypervel\Contracts\Process\ProcessResult; | ||
| use Hypervel\Doctor\Contracts\Fixable; | ||
| use Hypervel\Doctor\Diagnostic; | ||
| use Hypervel\Doctor\Results\DiagnosticResult; | ||
| use Hypervel\Doctor\Results\FixResult; | ||
| use Hypervel\Doctor\Results\Message; | ||
| use Hypervel\Doctor\Support\Composer; | ||
| use Hypervel\Doctor\Support\Details; | ||
| use Hypervel\Foundation\PackageManifest; | ||
| use Hypervel\Support\Facades\Process; | ||
|
|
||
|
|
@@ -70,7 +72,7 @@ public function check(): DiagnosticResult | |
|
|
||
| return $this->fail('invalid') | ||
| ->fixable() | ||
| ->withDetails(trim($process->errorOutput() !== '' ? $process->errorOutput() : $process->output())); | ||
| ->withDetails($this->failureDetails($process)); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -89,9 +91,21 @@ public function fix(DiagnosticResult $result, ?string $option = null): FixResult | |
|
|
||
| if (! $process->successful()) { | ||
| return $this->fixFailed('regeneration-failed') | ||
| ->withDetails(trim($process->errorOutput() !== '' ? $process->errorOutput() : $process->output())); | ||
| ->withDetails($this->failureDetails($process)); | ||
| } | ||
|
|
||
| return $this->fixed('regenerated'); | ||
| } | ||
|
|
||
| /** | ||
| * Get the failure details for a Composer process. | ||
| */ | ||
| private function failureDetails(ProcessResult $process): string | ||
| { | ||
| return Details::processOutput( | ||
| $process->output(), | ||
| $process->errorOutput(), | ||
| "Composer exited with code {$process->exitCode()} without output.", | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1. A missing exit code prints a blank number failureDetails() interpolates the nullable result of exitCode() directly into its fallback message. If a failed Composer process has no output and no exit code, both the check and fix display Composer exited with code without output. Agent Prompt
|
||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,12 +60,37 @@ private function missingValues(): array | |
| $required = $this->requiredValues(); | ||
|
|
||
| return [ | ||
| ...$this->missingDefaultValues(), | ||
| ...array_intersect_key($required, array_flip(Configured::missing(array_keys($required)))), | ||
| ...$this->missingMercureValues(), | ||
| ...$this->missingMailValues(), | ||
| ]; | ||
| } | ||
|
|
||
| /** | ||
| * Get default driver keys without a usable value, mapped to the feature that needs them. | ||
| * | ||
| * The managers read these keys with typed getters, so a blank or null | ||
| * value fails at runtime instead of falling back to a default driver. | ||
| * | ||
| * @return array<string, string> | ||
| */ | ||
| private function missingDefaultValues(): array | ||
| { | ||
| $defaults = [ | ||
| 'broadcasting.default' => 'default broadcaster', | ||
| 'filesystems.default' => 'default filesystem disk', | ||
| 'mail.default' => 'default mailer', | ||
| 'queue.default' => 'default queue connection', | ||
| ]; | ||
|
|
||
| return array_filter( | ||
| $defaults, | ||
| static fn (string $key): bool => Configured::string($key) === null, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reject whitespace-only defaults.
Treat whitespace-only defaults as missing, and use the same rule to skip their driver-specific checks.
|
||
| ARRAY_FILTER_USE_KEY, | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Get configuration keys required by the active drivers, mapped to the feature that needs them. | ||
| * | ||
|
|
@@ -88,7 +113,12 @@ private function requiredValues(): array | |
| */ | ||
| private function broadcastingValues(): array | ||
| { | ||
| $connection = Configured::string('broadcasting.default', 'null'); | ||
| $connection = Configured::string('broadcasting.default'); | ||
|
|
||
| if ($connection === null) { | ||
| return []; | ||
| } | ||
|
|
||
| $driver = Configured::string("broadcasting.connections.{$connection}.driver", $connection); | ||
|
|
||
| $keys = match ($driver) { | ||
|
|
@@ -107,7 +137,12 @@ private function broadcastingValues(): array | |
| */ | ||
| private function missingMercureValues(): array | ||
| { | ||
| $connection = Configured::string('broadcasting.default', 'null'); | ||
| $connection = Configured::string('broadcasting.default'); | ||
|
|
||
| if ($connection === null) { | ||
| return []; | ||
| } | ||
|
|
||
| $prefix = "broadcasting.connections.{$connection}"; | ||
|
|
||
| if (Configured::string("{$prefix}.driver", $connection) !== 'mercure') { | ||
|
|
@@ -140,9 +175,9 @@ private function missingMercureValues(): array | |
| */ | ||
| private function filesystemValues(): array | ||
| { | ||
| $disk = Configured::string('filesystems.default', 'local'); | ||
| $disk = Configured::string('filesystems.default'); | ||
|
|
||
| if (Configured::string("filesystems.disks.{$disk}.driver", $disk) !== 's3') { | ||
| if ($disk === null || Configured::string("filesystems.disks.{$disk}.driver", $disk) !== 's3') { | ||
| return []; | ||
| } | ||
|
|
||
|
|
@@ -156,9 +191,16 @@ private function filesystemValues(): array | |
| */ | ||
| private function loggingValues(): array | ||
| { | ||
| $default = Configured::string('logging.default'); | ||
|
|
||
| // The default log channel is optional; without one, LogManager writes to its emergency logger. | ||
| if ($default === null) { | ||
| return []; | ||
| } | ||
|
|
||
| $values = []; | ||
|
|
||
| foreach (ActiveDrivers::logChannels(Configured::string('logging.default', 'stack')) as $channel) { | ||
| foreach (ActiveDrivers::logChannels($default) as $channel) { | ||
| $values = [...$values, ...$this->logChannelValues($channel)]; | ||
| } | ||
|
|
||
|
|
@@ -204,9 +246,15 @@ private function logChannelValues(string $channel): array | |
| */ | ||
| private function missingMailValues(): array | ||
| { | ||
| $default = Configured::string('mail.default'); | ||
|
|
||
| if ($default === null) { | ||
| return []; | ||
| } | ||
|
|
||
| $values = []; | ||
|
|
||
| foreach (ActiveDrivers::mailers(Configured::string('mail.default', 'log')) as $mailer) { | ||
| foreach (ActiveDrivers::mailers($default) as $mailer) { | ||
| $values = [...$values, ...$this->missingMailerValues($mailer)]; | ||
| } | ||
|
|
||
|
|
@@ -263,7 +311,12 @@ private function missingMailerValues(string $mailer): array | |
| */ | ||
| private function queueValues(): array | ||
| { | ||
| $connection = Configured::string('queue.default', 'database'); | ||
| $connection = Configured::string('queue.default'); | ||
|
|
||
| if ($connection === null) { | ||
| return []; | ||
| } | ||
|
|
||
| $values = []; | ||
|
|
||
| foreach (ActiveDrivers::queueConnections($connection) as $name) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve stdout when stderr contains only whitespace.
If Composer or a script writes an error to stdout and only whitespace to stderr,
Details::processOutput()selects stderr before trimming it. The new fallback then incorrectly says Composer produced no output.Trim stderr before choosing between streams, and use the fallback only when both trimmed streams are empty.