mirror of
https://github.com/flarum/core.git
synced 2025-08-16 13:24:11 +02:00
feat: Queue package manager commands (#3418)
* feat: Queue package manager commands * adjust tests * fix: force run whynot command synchronously * chore: maximize command output box's height * chore: more user instructions on background queue * feat: track command peak memory usage * feat: exit of CLI php version doesn't match web php version * chore: install deps * chore: format and typing workflow fix Signed-off-by: Sami Mazouz <ilyasmazouz@gmail.com>
This commit is contained in:
@@ -11,7 +11,7 @@ namespace Flarum\PackageManager\Api\Controller;
|
||||
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\CheckForUpdates;
|
||||
use Illuminate\Contracts\Bus\Dispatcher;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
@@ -29,14 +29,25 @@ class CheckForUpdatesController implements RequestHandlerInterface
|
||||
$this->bus = $bus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||
*/
|
||||
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
$lastUpdateCheck = $this->bus->dispatch(
|
||||
$actor->assertAdmin();
|
||||
|
||||
/**
|
||||
* @TODO somewhere, if we're queuing, check that a similar composer command isn't already running,
|
||||
* to avoid duplicate jobs.
|
||||
*/
|
||||
$response = $this->bus->dispatch(
|
||||
new CheckForUpdates($actor)
|
||||
);
|
||||
|
||||
return new JsonResponse($lastUpdateCheck);
|
||||
return $response->queueJobs
|
||||
? new JsonResponse(['processing' => true], 202)
|
||||
: new JsonResponse($response->data);
|
||||
}
|
||||
}
|
||||
|
@@ -9,10 +9,11 @@
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
|
||||
use Flarum\Bus\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\GlobalUpdate;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
@@ -36,10 +37,12 @@ class GlobalUpdateController implements RequestHandlerInterface
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
$this->bus->dispatch(
|
||||
$response = $this->bus->dispatch(
|
||||
new GlobalUpdate($actor)
|
||||
);
|
||||
|
||||
return new EmptyResponse(200);
|
||||
return $response->queueJobs
|
||||
? new JsonResponse(['processing' => true], 202)
|
||||
: new EmptyResponse(201);
|
||||
}
|
||||
}
|
||||
|
@@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
|
||||
use Flarum\Api\Controller\AbstractListController;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Api\Serializer\TaskSerializer;
|
||||
use Flarum\PackageManager\Task;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class ListTaskController extends AbstractListController
|
||||
{
|
||||
public $serializer = TaskSerializer::class;
|
||||
|
||||
/**
|
||||
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
RequestUtil::getActor($request)->assertAdmin();
|
||||
|
||||
return Task::query()->orderBy('created_at', 'desc')->get();
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
|
||||
use Flarum\Api\Controller\AbstractListController;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Http\UrlGenerator;
|
||||
use Flarum\PackageManager\Api\Serializer\TaskSerializer;
|
||||
use Flarum\PackageManager\Task\TaskRepository;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
|
||||
class ListTasksController extends AbstractListController
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $serializer = TaskSerializer::class;
|
||||
|
||||
/**
|
||||
* @var UrlGenerator
|
||||
*/
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* @var TaskRepository
|
||||
*/
|
||||
protected $repository;
|
||||
|
||||
public function __construct(UrlGenerator $url, TaskRepository $repository)
|
||||
{
|
||||
$this->url = $url;
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
$actor->assertAdmin();
|
||||
|
||||
$limit = $this->extractLimit($request);
|
||||
$offset = $this->extractOffset($request);
|
||||
|
||||
$results = $this->repository
|
||||
->query()
|
||||
->latest()
|
||||
->offset($offset)
|
||||
->limit($limit)
|
||||
->get();
|
||||
|
||||
$total = $this->repository->query()->count();
|
||||
|
||||
$document->addMeta('total', $total);
|
||||
|
||||
$document->addPaginationLinks(
|
||||
$this->url->to('api')->route('package-manager.tasks.index'),
|
||||
$request->getQueryParams(),
|
||||
$offset,
|
||||
$limit,
|
||||
$total
|
||||
);
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
@@ -9,11 +9,12 @@
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
|
||||
use Flarum\Bus\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\MajorUpdate;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
@@ -35,10 +36,12 @@ class MajorUpdateController implements RequestHandlerInterface
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$dryRun = (bool) (int) Arr::get($request->getParsedBody(), 'data.dryRun', 0);
|
||||
|
||||
$this->bus->dispatch(
|
||||
$response = $this->bus->dispatch(
|
||||
new MajorUpdate($actor, $dryRun)
|
||||
);
|
||||
|
||||
return new EmptyResponse(200);
|
||||
return $response->queueJobs
|
||||
? new JsonResponse(['processing' => true], 202)
|
||||
: new EmptyResponse(201);
|
||||
}
|
||||
}
|
||||
|
@@ -9,10 +9,11 @@
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
|
||||
use Flarum\Bus\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\MinorUpdate;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
@@ -36,10 +37,12 @@ class MinorUpdateController implements RequestHandlerInterface
|
||||
{
|
||||
$actor = RequestUtil::getActor($request);
|
||||
|
||||
$this->bus->dispatch(
|
||||
$response = $this->bus->dispatch(
|
||||
new MinorUpdate($actor)
|
||||
);
|
||||
|
||||
return new EmptyResponse(200);
|
||||
return $response->queueJobs
|
||||
? new JsonResponse(['processing' => true], 202)
|
||||
: new EmptyResponse(201);
|
||||
}
|
||||
}
|
||||
|
@@ -9,11 +9,12 @@
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
|
||||
use Flarum\Bus\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\RemoveExtension;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
@@ -35,10 +36,12 @@ class RemoveExtensionController implements RequestHandlerInterface
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$extensionId = Arr::get($request->getQueryParams(), 'id');
|
||||
|
||||
$this->bus->dispatch(
|
||||
$response = $this->bus->dispatch(
|
||||
new RemoveExtension($actor, $extensionId)
|
||||
);
|
||||
|
||||
return new EmptyResponse(200);
|
||||
return $response->queueJobs
|
||||
? new JsonResponse(['processing' => true], 202)
|
||||
: new EmptyResponse(201);
|
||||
}
|
||||
}
|
||||
|
@@ -9,9 +9,9 @@
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
|
||||
use Flarum\Bus\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\RequireExtension;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
@@ -35,10 +35,12 @@ class RequireExtensionController implements RequestHandlerInterface
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$package = Arr::get($request->getParsedBody(), 'data.package');
|
||||
|
||||
$data = $this->bus->dispatch(
|
||||
$response = $this->bus->dispatch(
|
||||
new RequireExtension($actor, $package)
|
||||
);
|
||||
|
||||
return new JsonResponse($data);
|
||||
return $response->queueJobs
|
||||
? new JsonResponse(['processing' => true], 202)
|
||||
: new JsonResponse($response->data);
|
||||
}
|
||||
}
|
||||
|
@@ -9,11 +9,12 @@
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
|
||||
use Flarum\Bus\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\UpdateExtension;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laminas\Diactoros\Response\EmptyResponse;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
@@ -35,10 +36,12 @@ class UpdateExtensionController implements RequestHandlerInterface
|
||||
$actor = RequestUtil::getActor($request);
|
||||
$extensionId = Arr::get($request->getQueryParams(), 'id');
|
||||
|
||||
$this->bus->dispatch(
|
||||
$response = $this->bus->dispatch(
|
||||
new UpdateExtension($actor, $extensionId)
|
||||
);
|
||||
|
||||
return new EmptyResponse(200);
|
||||
return $response->queueJobs
|
||||
? new JsonResponse(['processing' => true], 202)
|
||||
: new EmptyResponse(201);
|
||||
}
|
||||
}
|
||||
|
@@ -9,9 +9,9 @@
|
||||
|
||||
namespace Flarum\PackageManager\Api\Controller;
|
||||
|
||||
use Flarum\Bus\Dispatcher;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\PackageManager\Command\WhyNot;
|
||||
use Flarum\PackageManager\Job\Dispatcher;
|
||||
use Illuminate\Support\Arr;
|
||||
use Laminas\Diactoros\Response\JsonResponse;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
@@ -36,12 +36,10 @@ class WhyNotController implements RequestHandlerInterface
|
||||
$package = Arr::get($request->getParsedBody(), 'data.package', '');
|
||||
$version = Arr::get($request->getParsedBody(), 'data.version', '*');
|
||||
|
||||
$whyNot = $this->bus->dispatch(
|
||||
$whyNot = $this->bus->sync()->dispatch(
|
||||
new WhyNot($actor, $package, $version)
|
||||
);
|
||||
|
||||
return new JsonResponse([
|
||||
'data' => compact('whyNot')
|
||||
]);
|
||||
return new JsonResponse(['data' => ['reason' => $whyNot->data['reason']]]);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Api\Serializer;
|
||||
|
||||
use Flarum\Api\Serializer\AbstractSerializer;
|
||||
use Flarum\PackageManager\Task\Task;
|
||||
use InvalidArgumentException;
|
||||
|
||||
class TaskSerializer extends AbstractSerializer
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $type = 'package-manager-tasks';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param Task $model
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function getDefaultAttributes($model)
|
||||
{
|
||||
if (! ($model instanceof Task)) {
|
||||
throw new InvalidArgumentException(
|
||||
get_class($this).' can only serialize instances of '.Task::class
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'status' => $model->status,
|
||||
'operation' => $model->operation,
|
||||
'command' => $model->command,
|
||||
'package' => $model->package,
|
||||
'output' => $model->output,
|
||||
'createdAt' => $model->created_at,
|
||||
'startedAt' => $model->started_at,
|
||||
'finishedAt' => $model->finished_at,
|
||||
'peakMemoryUsed' => $model->peak_memory_used,
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Command;
|
||||
|
||||
interface BusinessCommandInterface
|
||||
{
|
||||
public function getOperationName(): string;
|
||||
}
|
@@ -9,10 +9,16 @@
|
||||
|
||||
namespace Flarum\PackageManager\Command;
|
||||
|
||||
use Flarum\PackageManager\Task\Task;
|
||||
use Flarum\User\User;
|
||||
|
||||
class CheckForUpdates
|
||||
class CheckForUpdates implements BusinessCommandInterface
|
||||
{
|
||||
/**
|
||||
* @var Task
|
||||
*/
|
||||
public $task = null;
|
||||
|
||||
/**
|
||||
* @var \Flarum\User\User
|
||||
*/
|
||||
@@ -22,4 +28,9 @@ class CheckForUpdates
|
||||
{
|
||||
$this->actor = $actor;
|
||||
}
|
||||
|
||||
public function getOperationName(): string
|
||||
{
|
||||
return Task::UPDATE_CHECK;
|
||||
}
|
||||
}
|
||||
|
@@ -48,29 +48,24 @@ class CheckForUpdatesHandler
|
||||
*
|
||||
* The results from both commands are properly processed and merged to have new key values `latest-minor` and `latest-major`.
|
||||
*
|
||||
* @throws \Flarum\User\Exception\PermissionDeniedException|ComposerCommandFailedException
|
||||
* @todo integration test
|
||||
* @throws ComposerCommandFailedException
|
||||
*/
|
||||
public function handle(CheckForUpdates $command)
|
||||
{
|
||||
$actor = $command->actor;
|
||||
|
||||
$actor->assertAdmin();
|
||||
|
||||
$firstOutput = $this->runComposerCommand(false);
|
||||
$firstOutput = $this->runComposerCommand(false, $command);
|
||||
$firstOutput = json_decode($this->cleanJson($firstOutput), true);
|
||||
|
||||
$majorUpdates = false;
|
||||
|
||||
foreach ($firstOutput['installed'] as $package) {
|
||||
if ($package['latest-status'] === 'update-possible') {
|
||||
if (isset($package['latest-status']) && $package['latest-status'] === 'update-possible') {
|
||||
$majorUpdates = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($majorUpdates) {
|
||||
$secondOutput = $this->runComposerCommand(true);
|
||||
$secondOutput = $this->runComposerCommand(true, $command);
|
||||
$secondOutput = json_decode($this->cleanJson($secondOutput), true);
|
||||
}
|
||||
|
||||
@@ -81,7 +76,7 @@ class CheckForUpdatesHandler
|
||||
foreach ($firstOutput['installed'] as &$mainPackageUpdate) {
|
||||
$mainPackageUpdate['latest-minor'] = $mainPackageUpdate['latest-major'] = null;
|
||||
|
||||
if ($mainPackageUpdate['latest-status'] === 'update-possible') {
|
||||
if (isset($mainPackageUpdate['latest-status']) && $mainPackageUpdate['latest-status'] === 'update-possible') {
|
||||
$mainPackageUpdate['latest-major'] = $mainPackageUpdate['latest'];
|
||||
|
||||
$minorPackageUpdate = array_filter($secondOutput['installed'], function ($package) use ($mainPackageUpdate) {
|
||||
@@ -92,7 +87,7 @@ class CheckForUpdatesHandler
|
||||
$mainPackageUpdate['latest-minor'] = $minorPackageUpdate['latest'];
|
||||
}
|
||||
} else {
|
||||
$mainPackageUpdate['latest-minor'] = $mainPackageUpdate['latest'];
|
||||
$mainPackageUpdate['latest-minor'] = $mainPackageUpdate['latest'] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +108,7 @@ class CheckForUpdatesHandler
|
||||
/**
|
||||
* @throws ComposerCommandFailedException
|
||||
*/
|
||||
protected function runComposerCommand(bool $minorOnly): string
|
||||
protected function runComposerCommand(bool $minorOnly, CheckForUpdates $command): string
|
||||
{
|
||||
$input = [
|
||||
'command' => 'outdated',
|
||||
@@ -125,7 +120,7 @@ class CheckForUpdatesHandler
|
||||
$input['--minor-only'] = true;
|
||||
}
|
||||
|
||||
$output = $this->composer->run(new ArrayInput($input));
|
||||
$output = $this->composer->run(new ArrayInput($input), $command->task ?? null);
|
||||
|
||||
if ($output->getExitCode() !== 0) {
|
||||
throw new ComposerCommandFailedException('', $output->getContents());
|
||||
|
@@ -9,10 +9,16 @@
|
||||
|
||||
namespace Flarum\PackageManager\Command;
|
||||
|
||||
use Flarum\PackageManager\Task\Task;
|
||||
use Flarum\User\User;
|
||||
|
||||
class GlobalUpdate
|
||||
class GlobalUpdate implements BusinessCommandInterface
|
||||
{
|
||||
/**
|
||||
* @var Task
|
||||
*/
|
||||
public $task = null;
|
||||
|
||||
/**
|
||||
* @var \Flarum\User\User
|
||||
*/
|
||||
@@ -22,4 +28,9 @@ class GlobalUpdate
|
||||
{
|
||||
$this->actor = $actor;
|
||||
}
|
||||
|
||||
public function getOperationName(): string
|
||||
{
|
||||
return Task::UPDATE_GLOBAL;
|
||||
}
|
||||
}
|
||||
|
@@ -48,7 +48,8 @@ class GlobalUpdateHandler
|
||||
$command->actor->assertAdmin();
|
||||
|
||||
$output = $this->composer->run(
|
||||
new StringInput('update --prefer-dist --no-dev -a --with-all-dependencies')
|
||||
new StringInput('update --prefer-dist --no-dev -a --with-all-dependencies'),
|
||||
$command->task ?? null
|
||||
);
|
||||
|
||||
if ($output->getExitCode() !== 0) {
|
||||
@@ -58,7 +59,5 @@ class GlobalUpdateHandler
|
||||
$this->events->dispatch(
|
||||
new FlarumUpdated($command->actor, FlarumUpdated::GLOBAL)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -9,10 +9,16 @@
|
||||
|
||||
namespace Flarum\PackageManager\Command;
|
||||
|
||||
use Flarum\PackageManager\Task\Task;
|
||||
use Flarum\User\User;
|
||||
|
||||
class MajorUpdate
|
||||
class MajorUpdate implements BusinessCommandInterface
|
||||
{
|
||||
/**
|
||||
* @var Task
|
||||
*/
|
||||
public $task = null;
|
||||
|
||||
/**
|
||||
* @var \Flarum\User\User
|
||||
*/
|
||||
@@ -28,4 +34,9 @@ class MajorUpdate
|
||||
$this->actor = $actor;
|
||||
$this->dryRun = $dryRun;
|
||||
}
|
||||
|
||||
public function getOperationName(): string
|
||||
{
|
||||
return Task::UPDATE_MAJOR;
|
||||
}
|
||||
}
|
||||
|
@@ -76,19 +76,17 @@ class MajorUpdateHandler
|
||||
|
||||
$this->updateComposerJson($majorVersion);
|
||||
|
||||
$this->runCommand($command->dryRun, $majorVersion);
|
||||
$this->runCommand($command, $majorVersion);
|
||||
|
||||
if ($command->dryRun) {
|
||||
$this->composerJson->revert();
|
||||
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->events->dispatch(
|
||||
new FlarumUpdated($command->actor, FlarumUpdated::MAJOR)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,7 +103,7 @@ class MajorUpdateHandler
|
||||
/**
|
||||
* @throws MajorUpdateFailedException
|
||||
*/
|
||||
protected function runCommand(bool $dryRun, string $majorVersion): void
|
||||
protected function runCommand(MajorUpdate $command, string $majorVersion): void
|
||||
{
|
||||
$input = [
|
||||
'command' => 'update',
|
||||
@@ -116,11 +114,11 @@ class MajorUpdateHandler
|
||||
'--with-all-dependencies' => true,
|
||||
];
|
||||
|
||||
if ($dryRun) {
|
||||
if ($command->dryRun) {
|
||||
$input['--dry-run'] = true;
|
||||
}
|
||||
|
||||
$output = $this->composer->run(new ArrayInput($input));
|
||||
$output = $this->composer->run(new ArrayInput($input), $command->task ?? null);
|
||||
|
||||
if ($output->getExitCode() !== 0) {
|
||||
throw new MajorUpdateFailedException('*', $output->getContents(), $majorVersion);
|
||||
|
@@ -9,10 +9,16 @@
|
||||
|
||||
namespace Flarum\PackageManager\Command;
|
||||
|
||||
use Flarum\PackageManager\Task\Task;
|
||||
use Flarum\User\User;
|
||||
|
||||
class MinorUpdate
|
||||
class MinorUpdate implements BusinessCommandInterface
|
||||
{
|
||||
/**
|
||||
* @var Task
|
||||
*/
|
||||
public $task = null;
|
||||
|
||||
/**
|
||||
* @var \Flarum\User\User
|
||||
*/
|
||||
@@ -22,4 +28,9 @@ class MinorUpdate
|
||||
{
|
||||
$this->actor = $actor;
|
||||
}
|
||||
|
||||
public function getOperationName(): string
|
||||
{
|
||||
return Task::UPDATE_MINOR;
|
||||
}
|
||||
}
|
||||
|
@@ -61,7 +61,8 @@ class MinorUpdateHandler
|
||||
$this->composerJson->require('flarum/core', $coreRequirement);
|
||||
|
||||
$output = $this->composer->run(
|
||||
new StringInput('update --prefer-dist --no-dev -a --with-all-dependencies')
|
||||
new StringInput('update --prefer-dist --no-dev -a --with-all-dependencies'),
|
||||
$command->task ?? null
|
||||
);
|
||||
|
||||
if ($output->getExitCode() !== 0) {
|
||||
@@ -71,7 +72,5 @@ class MinorUpdateHandler
|
||||
$this->events->dispatch(
|
||||
new FlarumUpdated($command->actor, FlarumUpdated::MINOR)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -9,10 +9,16 @@
|
||||
|
||||
namespace Flarum\PackageManager\Command;
|
||||
|
||||
use Flarum\PackageManager\Task\Task;
|
||||
use Flarum\User\User;
|
||||
|
||||
class RemoveExtension
|
||||
class RemoveExtension implements BusinessCommandInterface
|
||||
{
|
||||
/**
|
||||
* @var Task
|
||||
*/
|
||||
public $task = null;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
@@ -28,4 +34,9 @@ class RemoveExtension
|
||||
$this->actor = $actor;
|
||||
$this->extensionId = $extensionId;
|
||||
}
|
||||
|
||||
public function getOperationName(): string
|
||||
{
|
||||
return Task::EXTENSION_REMOVE;
|
||||
}
|
||||
}
|
||||
|
@@ -55,8 +55,13 @@ class RemoveExtensionHandler
|
||||
throw new ExtensionNotInstalledException($command->extensionId);
|
||||
}
|
||||
|
||||
if (isset($command->task)) {
|
||||
$command->task->package = $extension->name;
|
||||
}
|
||||
|
||||
$output = $this->composer->run(
|
||||
new StringInput("remove $extension->name")
|
||||
new StringInput("remove $extension->name"),
|
||||
$command->task ?? null
|
||||
);
|
||||
|
||||
if ($output->getExitCode() !== 0) {
|
||||
|
@@ -9,10 +9,16 @@
|
||||
|
||||
namespace Flarum\PackageManager\Command;
|
||||
|
||||
use Flarum\PackageManager\Task\Task;
|
||||
use Flarum\User\User;
|
||||
|
||||
class RequireExtension
|
||||
class RequireExtension implements BusinessCommandInterface
|
||||
{
|
||||
/**
|
||||
* @var Task
|
||||
*/
|
||||
public $task = null;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
@@ -28,4 +34,9 @@ class RequireExtension
|
||||
$this->actor = $actor;
|
||||
$this->package = $package;
|
||||
}
|
||||
|
||||
public function getOperationName(): string
|
||||
{
|
||||
return Task::EXTENSION_INSTALL;
|
||||
}
|
||||
}
|
||||
|
@@ -74,7 +74,8 @@ class RequireExtensionHandler
|
||||
}
|
||||
|
||||
$output = $this->composer->run(
|
||||
new StringInput("require $packageName")
|
||||
new StringInput("require $packageName"),
|
||||
$command->task ?? null
|
||||
);
|
||||
|
||||
if ($output->getExitCode() !== 0) {
|
||||
|
@@ -9,10 +9,16 @@
|
||||
|
||||
namespace Flarum\PackageManager\Command;
|
||||
|
||||
use Flarum\PackageManager\Task\Task;
|
||||
use Flarum\User\User;
|
||||
|
||||
class UpdateExtension
|
||||
class UpdateExtension implements BusinessCommandInterface
|
||||
{
|
||||
/**
|
||||
* @var Task
|
||||
*/
|
||||
public $task = null;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
@@ -28,4 +34,9 @@ class UpdateExtension
|
||||
$this->actor = $actor;
|
||||
$this->extensionId = $extensionId;
|
||||
}
|
||||
|
||||
public function getOperationName(): string
|
||||
{
|
||||
return Task::EXTENSION_UPDATE;
|
||||
}
|
||||
}
|
||||
|
@@ -77,7 +77,8 @@ class UpdateExtensionHandler
|
||||
}
|
||||
|
||||
$output = $this->composer->run(
|
||||
new StringInput("require $extension->name:*")
|
||||
new StringInput("require $extension->name:*"),
|
||||
$command->task ?? null
|
||||
);
|
||||
|
||||
if ($output->getExitCode() !== 0) {
|
||||
@@ -87,7 +88,5 @@ class UpdateExtensionHandler
|
||||
$this->events->dispatch(
|
||||
new Updated($command->actor, $extension)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -9,10 +9,16 @@
|
||||
|
||||
namespace Flarum\PackageManager\Command;
|
||||
|
||||
use Flarum\PackageManager\Task\Task;
|
||||
use Flarum\User\User;
|
||||
|
||||
class WhyNot
|
||||
class WhyNot implements BusinessCommandInterface
|
||||
{
|
||||
/**
|
||||
* @var Task
|
||||
*/
|
||||
public $task = null;
|
||||
|
||||
/**
|
||||
* @var User
|
||||
*/
|
||||
@@ -34,4 +40,9 @@ class WhyNot
|
||||
$this->package = $package;
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
public function getOperationName(): string
|
||||
{
|
||||
return Task::WHY_NOT;
|
||||
}
|
||||
}
|
||||
|
@@ -53,13 +53,14 @@ class WhyNotHandler
|
||||
]);
|
||||
|
||||
$output = $this->composer->run(
|
||||
new StringInput("why-not $command->package $command->version")
|
||||
new StringInput("why-not $command->package $command->version"),
|
||||
$command->task ?? null
|
||||
);
|
||||
|
||||
if ($output->getExitCode() !== 0) {
|
||||
throw new ComposerRequireFailedException($command->package, $output->getContents());
|
||||
}
|
||||
|
||||
return $output->getContents();
|
||||
return ['reason' => $output->getContents()];
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ namespace Flarum\PackageManager\Composer;
|
||||
use Composer\Console\Application;
|
||||
use Flarum\Foundation\Paths;
|
||||
use Flarum\PackageManager\OutputLogger;
|
||||
use Flarum\PackageManager\Task\Task;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
|
||||
@@ -48,7 +49,7 @@ class ComposerAdapter
|
||||
$this->output = new BufferedOutput();
|
||||
}
|
||||
|
||||
public function run(InputInterface $input): ComposerOutput
|
||||
public function run(InputInterface $input, ?Task $task = null): ComposerOutput
|
||||
{
|
||||
$this->application->resetComposer();
|
||||
|
||||
@@ -58,10 +59,15 @@ class ComposerAdapter
|
||||
$exitCode = $this->application->run($input, $this->output);
|
||||
chdir($currDir);
|
||||
|
||||
$outputContents = $this->output->fetch();
|
||||
$command = $input->__toString();
|
||||
$output = $this->output->fetch();
|
||||
|
||||
$this->logger->log($input->__toString(), $outputContents, $exitCode);
|
||||
if ($task) {
|
||||
$task->update(compact('command', 'output'));
|
||||
} else {
|
||||
$this->logger->log($command, $output, $exitCode);
|
||||
}
|
||||
|
||||
return new ComposerOutput($exitCode, $outputContents);
|
||||
return new ComposerOutput($exitCode, $output);
|
||||
}
|
||||
}
|
||||
|
65
extensions/package-manager/src/Job/ComposerCommandJob.php
Normal file
65
extensions/package-manager/src/Job/ComposerCommandJob.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Job;
|
||||
|
||||
use Flarum\Bus\Dispatcher;
|
||||
use Flarum\PackageManager\Command\BusinessCommandInterface;
|
||||
use Flarum\Queue\AbstractJob;
|
||||
use Throwable;
|
||||
|
||||
class ComposerCommandJob extends AbstractJob
|
||||
{
|
||||
/**
|
||||
* @var BusinessCommandInterface
|
||||
*/
|
||||
protected $command;
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
protected $phpVersion;
|
||||
|
||||
public function __construct(BusinessCommandInterface $command, array $phpVersion)
|
||||
{
|
||||
$this->command = $command;
|
||||
$this->phpVersion = $phpVersion;
|
||||
}
|
||||
|
||||
public function handle(Dispatcher $bus)
|
||||
{
|
||||
try {
|
||||
if ([PHP_MAJOR_VERSION, PHP_MINOR_VERSION] !== [$this->phpVersion[0], $this->phpVersion[1]]) {
|
||||
$webPhpVersion = implode('.', $this->phpVersion);
|
||||
$sshPhpVersion = implode('.', [PHP_MAJOR_VERSION, PHP_MINOR_VERSION]);
|
||||
|
||||
throw new \Exception("PHP version mismatch. SSH PHP version must match web server PHP version. Found SSH (PHP $sshPhpVersion) and Web Server (PHP $webPhpVersion).");
|
||||
}
|
||||
|
||||
$this->command->task->start();
|
||||
|
||||
$bus->dispatch($this->command);
|
||||
|
||||
$this->command->task->end(true);
|
||||
} catch (Throwable $exception) {
|
||||
$this->abort($exception);
|
||||
}
|
||||
}
|
||||
|
||||
public function abort(Throwable $exception)
|
||||
{
|
||||
if (! $this->command->task->output) {
|
||||
$this->command->task->output = $exception->getMessage();
|
||||
}
|
||||
|
||||
$this->command->task->end(false);
|
||||
|
||||
$this->fail($exception);
|
||||
}
|
||||
}
|
84
extensions/package-manager/src/Job/Dispatcher.php
Normal file
84
extensions/package-manager/src/Job/Dispatcher.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Job;
|
||||
|
||||
use Flarum\Bus\Dispatcher as Bus;
|
||||
use Flarum\PackageManager\Command\BusinessCommandInterface;
|
||||
use Flarum\PackageManager\Task\Task;
|
||||
use Flarum\Settings\SettingsRepositoryInterface;
|
||||
use Illuminate\Contracts\Queue\Queue;
|
||||
use Illuminate\Queue\SyncQueue;
|
||||
|
||||
class Dispatcher
|
||||
{
|
||||
/**
|
||||
* @var Bus
|
||||
*/
|
||||
protected $bus;
|
||||
|
||||
/**
|
||||
* @var Queue
|
||||
*/
|
||||
protected $queue;
|
||||
|
||||
/**
|
||||
* @var SettingsRepositoryInterface
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* Overrides the user setting for execution mode if set.
|
||||
* Runs synchronously regardless of user setting if set true.
|
||||
* Asynchronously if set false.
|
||||
*
|
||||
* @var bool|null
|
||||
*/
|
||||
protected $runSyncOverride;
|
||||
|
||||
public function __construct(Bus $bus, Queue $queue, SettingsRepositoryInterface $settings)
|
||||
{
|
||||
$this->bus = $bus;
|
||||
$this->queue = $queue;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function sync(): self
|
||||
{
|
||||
$this->runSyncOverride = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function async(): self
|
||||
{
|
||||
$this->runSyncOverride = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function dispatch(BusinessCommandInterface $command): DispatcherResponse
|
||||
{
|
||||
$queueJobs = ($this->runSyncOverride === false) || ($this->runSyncOverride !== true && $this->settings->get('flarum-package-manager.queue_jobs'));
|
||||
|
||||
if ($queueJobs && (! $this->queue instanceof SyncQueue)) {
|
||||
$task = Task::build($command->getOperationName(), $command->package ?? null);
|
||||
|
||||
$command->task = $task;
|
||||
|
||||
$this->queue->push(
|
||||
new ComposerCommandJob($command, [PHP_MAJOR_VERSION, PHP_MINOR_VERSION])
|
||||
);
|
||||
} else {
|
||||
$data = $this->bus->dispatch($command);
|
||||
}
|
||||
|
||||
return new DispatcherResponse($queueJobs, $data ?? null);
|
||||
}
|
||||
}
|
23
extensions/package-manager/src/Job/DispatcherResponse.php
Normal file
23
extensions/package-manager/src/Job/DispatcherResponse.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Job;
|
||||
|
||||
class DispatcherResponse
|
||||
{
|
||||
public $queueJobs;
|
||||
|
||||
public $data;
|
||||
|
||||
public function __construct(bool $queueJobs, ?array $data)
|
||||
{
|
||||
$this->queueJobs = $queueJobs;
|
||||
$this->data = $data;
|
||||
}
|
||||
}
|
93
extensions/package-manager/src/Task/Task.php
Normal file
93
extensions/package-manager/src/Task/Task.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Task;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Flarum\Database\AbstractModel;
|
||||
|
||||
/**
|
||||
* @property int id
|
||||
* @property int status
|
||||
* @property string operation
|
||||
* @property string command
|
||||
* @property string package
|
||||
* @property string output
|
||||
* @property Carbon created_at
|
||||
* @property Carbon started_at
|
||||
* @property Carbon finished_at
|
||||
* @property int peak_memory_used
|
||||
*/
|
||||
class Task extends AbstractModel
|
||||
{
|
||||
/**
|
||||
* Statuses (@todo use an enum with php8.1).
|
||||
*/
|
||||
public const PENDING = 'pending';
|
||||
public const RUNNING = 'running';
|
||||
public const FAILURE = 'failure';
|
||||
public const SUCCESS = 'success';
|
||||
|
||||
/**
|
||||
* Operations (@todo use an enum with php8.1).
|
||||
*/
|
||||
public const EXTENSION_INSTALL = 'extension_install';
|
||||
public const EXTENSION_REMOVE = 'extension_remove';
|
||||
public const EXTENSION_UPDATE = 'extension_update';
|
||||
public const UPDATE_GLOBAL = 'update_global';
|
||||
public const UPDATE_MINOR = 'update_minor';
|
||||
public const UPDATE_MAJOR = 'update_major';
|
||||
public const UPDATE_CHECK = 'update_check';
|
||||
public const WHY_NOT = 'why_not';
|
||||
|
||||
public const UPDATED_AT = null;
|
||||
|
||||
protected $table = 'package_manager_tasks';
|
||||
|
||||
protected $fillable = ['command', 'output'];
|
||||
|
||||
public $timestamps = true;
|
||||
|
||||
protected $casts = [
|
||||
self::CREATED_AT => 'datetime',
|
||||
'started_at' => 'datetime',
|
||||
'finished_at' => 'datetime',
|
||||
];
|
||||
|
||||
public static function build(string $operation, ?string $package): self
|
||||
{
|
||||
$task = new static;
|
||||
|
||||
$task->operation = $operation;
|
||||
$task->package = $package;
|
||||
$task->status = static::PENDING;
|
||||
$task->created_at = Carbon::now();
|
||||
|
||||
$task->save();
|
||||
|
||||
return $task;
|
||||
}
|
||||
|
||||
public function start(): bool
|
||||
{
|
||||
$this->status = static::RUNNING;
|
||||
$this->started_at = Carbon::now();
|
||||
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
public function end(bool $success): bool
|
||||
{
|
||||
$this->status = $success ? static::SUCCESS : static::FAILURE;
|
||||
$this->finished_at = Carbon::now();
|
||||
$this->peak_memory_used = round(memory_get_peak_usage() / 1024);
|
||||
|
||||
return $this->save();
|
||||
}
|
||||
}
|
34
extensions/package-manager/src/Task/TaskRepository.php
Normal file
34
extensions/package-manager/src/Task/TaskRepository.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\PackageManager\Task;
|
||||
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class TaskRepository
|
||||
{
|
||||
/**
|
||||
* @return Builder
|
||||
*/
|
||||
public function query()
|
||||
{
|
||||
return Task::query();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param User $actor
|
||||
* @return Task
|
||||
*/
|
||||
public function findOrFail($id, User $actor = null): Task
|
||||
{
|
||||
return Task::findOrFail($id);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user