Skip to content
Open
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
1 change: 1 addition & 0 deletions assets/modules/store/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,7 @@ input#store_search:focus {
display: block;
min-width: 0;
overflow-wrap: anywhere;
white-space: pre-wrap;
color: #d8dee6;
font-size: 13px;
line-height: 1.35;
Expand Down
10 changes: 9 additions & 1 deletion core/src/Console/Packages/InstallPackageRequireCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,15 @@ public function putComposer()
public function runComposer()
{
putenv('COMPOSER_HOME=' . EVO_CORE_PATH . 'composer');
$arguments = ['command' => 'update'];
// A partial update: only the requested package and what it pulls in may
// move. A bare `update` re-resolves the whole lock for the running PHP,
// which on a container whose vendor tree was built on 8.3 and runs on
// 8.4 rewrote 62 core packages before the extra was even touched.
$arguments = [
'command' => 'update',
'packages' => [(string) $this->argument('key')],
'--with-all-dependencies' => true,
];
if ($this->hasCommandOption('no-dev') && $this->option('no-dev')) {
$arguments['--no-dev'] = true;
}
Expand Down
28 changes: 25 additions & 3 deletions core/src/Services/SystemTasks/ConsoleInstallFlowService.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

class ConsoleInstallFlowService implements SystemTaskHandlerInterface
{
use ReportsProcessFailure;

protected CatalogService $catalogService;

public function __construct(?CatalogService $catalogService = null)
Expand All @@ -33,6 +35,7 @@ public function execute(SystemCliTask $task, ?callable $report = null)
'key' => $composerName,
'value' => $composerVersion,
'composer_run' => 1,
'--no-dev' => !$this->vendorHasDevPackages(),
],
'install_require',
30,
Expand Down Expand Up @@ -107,7 +110,9 @@ protected function runArtisanCommand($command, array $arguments, $step, $progres
}

if ((int) $exitCode !== 0) {
$reason = $this->summarizeOutput($output);
$this->reportProcessFailure($report, $step, $progress, $command, $exitCode, $output);

$reason = $this->summarizeOutput($output, true);
if ($reason !== '') {
throw new \RuntimeException($command . ' failed with exit code ' . (int) $exitCode . '. ' . $reason);
}
Expand All @@ -116,6 +121,23 @@ protected function runArtisanCommand($command, array $arguments, $step, $progres
}
}

/**
* Does vendor/ carry require-dev packages? Mirrors that state into the
* composer run so an install neither strips a dev checkout nor pulls the
* dev tree into a --no-dev production build.
*/
protected function vendorHasDevPackages(): bool
{
$installed = EVO_CORE_PATH . 'vendor/composer/installed.json';
if (!file_exists($installed)) {
return true;
}

$data = json_decode((string) file_get_contents($installed), true);

return !is_array($data) || !array_key_exists('dev', $data) || (bool) $data['dev'];
}

protected function buildArtisanProcessArguments($command, array $arguments)
{
$parts = [PHP_BINARY, EVO_CORE_PATH . 'artisan', $command];
Expand Down Expand Up @@ -276,7 +298,7 @@ protected function isComposerDependencyName(string $name): bool
return true;
}

protected function summarizeOutput($output)
protected function summarizeOutput($output, bool $fromEnd = false)
{
$lines = preg_split('/\r\n|\r|\n/', trim((string) $output));
$lines = array_values(array_filter(array_map(function ($line) {
Expand All @@ -303,7 +325,7 @@ protected function summarizeOutput($output)
return true;
}));

$output = implode(' ', array_slice($lines, 0, 3));
$output = implode(' ', $this->pickSummaryLines($lines, $fromEnd, $fromEnd ? 5 : 3));
if ($output === '') {
return '';
}
Expand Down
10 changes: 7 additions & 3 deletions core/src/Services/SystemTasks/ConsoleUninstallFlowService.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

class ConsoleUninstallFlowService implements SystemTaskHandlerInterface
{
use ReportsProcessFailure;

protected string $corePath;
protected string $providersDir;
protected string $aliasesDir;
Expand Down Expand Up @@ -100,7 +102,9 @@ protected function runArtisanCommand($command, array $arguments, $step, $progres
}

if ((int) $exitCode !== 0) {
$reason = $this->summarizeOutput($output);
$this->reportProcessFailure($report, $step, $progress, $command, $exitCode, $output);

$reason = $this->summarizeOutput($output, true);
if ($reason !== '') {
throw new \RuntimeException($command . ' failed with exit code ' . (int) $exitCode . '. ' . $reason);
}
Expand Down Expand Up @@ -134,7 +138,7 @@ protected function buildArtisanProcessArguments($command, array $arguments)
return $parts;
}

protected function summarizeOutput($output)
protected function summarizeOutput($output, bool $fromEnd = false)
{
$lines = preg_split('/\r\n|\r|\n/', trim((string) $output));
$lines = array_values(array_filter(array_map(function ($line) {
Expand Down Expand Up @@ -165,7 +169,7 @@ protected function summarizeOutput($output)
return '';
}

return implode(' ', array_slice($lines, 0, 3));
return implode(' ', $this->pickSummaryLines($lines, $fromEnd, $fromEnd ? 5 : 3));
}

protected function purgeInvalidDiscoveryArtifacts(?callable $report = null)
Expand Down
36 changes: 36 additions & 0 deletions core/src/Services/SystemTasks/ReportsProcessFailure.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php namespace EvolutionCMS\Services\SystemTasks;

/**
* Puts the tail of a failed child process into the task log.
*
* Composer and artisan print their banner first and the error last, so the
* head of the output names the step and the tail names the failure. The
* manager renders log messages only, so the tail goes in there whole.
*
* @since 3.5.8
*/
trait ReportsProcessFailure
{
protected function reportProcessFailure(?callable $report, $step, $progress, $label, $exitCode, $output, array $context = [])
{
$lines = preg_split('/\r\n|\r|\n/', trim((string) $output));
$tail = trim(implode("\n", array_slice($lines, -40)));
if ($tail === '') {
$tail = $label . ' produced no output.';
}

$this->report($report, $step, $progress, $tail, 'error', $context + [
'command' => $label,
'exit_code' => (int) $exitCode,
'output' => mb_substr((string) $output, -16000),
]);
}

/**
* The one-line reason for the exception: last lines on failure, first lines otherwise.
*/
protected function pickSummaryLines(array $lines, bool $fromEnd, int $count)
{
return $fromEnd ? array_slice($lines, -$count) : array_slice($lines, 0, $count);
}
}
10 changes: 7 additions & 3 deletions core/src/Services/SystemTasks/SiteUpdateFlowService.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

class SiteUpdateFlowService implements SystemTaskHandlerInterface
{
use ReportsProcessFailure;

protected string $corePath;

public function __construct(?string $corePath = null)
Expand Down Expand Up @@ -79,7 +81,9 @@ public function execute(SystemCliTask $task, ?callable $report = null)
}

if ((int) $exitCode !== 0) {
$reason = $this->summarizeOutput($output);
$this->reportProcessFailure($report, 'site_update', 80, 'make:site', $exitCode, $output, $reportContext);

$reason = $this->summarizeOutput($output, true);
if ($reason !== '') {
throw new \RuntimeException('Site update failed with exit code ' . (int) $exitCode . '. ' . $reason);
}
Expand Down Expand Up @@ -158,7 +162,7 @@ protected function buildArtisanProcessArguments($command, array $arguments)
return $parts;
}

protected function summarizeOutput($output)
protected function summarizeOutput($output, bool $fromEnd = false)
{
$lines = preg_split('/\r\n|\r|\n/', trim((string) $output));
$lines = array_values(array_filter(array_map(function ($line) {
Expand All @@ -175,7 +179,7 @@ protected function summarizeOutput($output)
return '';
}

return implode(' ', array_slice($lines, 0, 5));
return implode(' ', $this->pickSummaryLines($lines, $fromEnd, 5));
}

protected function report(?callable $report, $step, $progress, $message, $level = 'info', array $context = [])
Expand Down
17 changes: 17 additions & 0 deletions core/tests/Unit/SystemTasks/ConsoleInstallFlowServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,20 @@ function invokeConsoleInstallFlowMethod(ConsoleInstallFlowService $service, stri
'Vendor\\Package\\SecondaryServiceProvider',
]);
});

test('summarizeOutput reads the failure from the tail when asked', function () {
$service = new ConsoleInstallFlowService();

$output = implode("\n", [
'Evolution CMS 3.5.8',
'Lock file operations: 1 install, 62 updates, 0 removals',
' - Upgrading composer/ca-bundle (1.5.13 => 1.5.14)',
'Your requirements could not be resolved to an installable set of packages.',
' - elcreator/aimage 1.0.0 requires ext-imagick * -> it is missing from your system.',
]);

expect(invokeConsoleInstallFlowMethod($service, 'summarizeOutput', [$output]))
->toStartWith('Lock file operations')
->and(invokeConsoleInstallFlowMethod($service, 'summarizeOutput', [$output, true]))
->toContain('ext-imagick');
});