1
0
mirror of https://github.com/flarum/core.git synced 2025-08-15 04:44:08 +02:00

feat: misc additions

- Detect extensions that didn't update between updates
- Add composer why not command where approriate (when extension didn't update, when major update failed)
- Detect incompatible extensions in major update failure and show the extensions in the frontend
- Create last update run setting value which holds the state of the latest update runs
- Other fixes
This commit is contained in:
SychO9
2021-11-23 23:02:56 +01:00
parent c8b8dacb67
commit f4bb8158ef
31 changed files with 1877 additions and 1007 deletions

View File

@@ -0,0 +1,47 @@
<?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\Bus\Dispatcher;
use Flarum\Http\RequestUtil;
use Flarum\PackageManager\Command\WhyNot;
use Illuminate\Support\Arr;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Message\ServerRequestInterface;
class WhyNotController implements RequestHandlerInterface
{
/**
* @var Dispatcher
*/
protected $bus;
public function __construct(Dispatcher $bus)
{
$this->bus = $bus;
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
$actor = RequestUtil::getActor($request);
$package = Arr::get($request->getParsedBody(), 'data.package', '');
$version = Arr::get($request->getParsedBody(), 'data.version', '*');
$whyNot = $this->bus->dispatch(
new WhyNot($actor, $package, $version)
);
return new JsonResponse([
'data' => compact('whyNot')
]);
}
}

View File

@@ -11,7 +11,7 @@ namespace Flarum\PackageManager\Command;
use Flarum\PackageManager\Composer\ComposerAdapter;
use Flarum\PackageManager\Exception\ComposerCommandFailedException;
use Flarum\PackageManager\LastUpdateCheck;
use Flarum\PackageManager\Settings\LastUpdateCheck;
use Symfony\Component\Console\Input\ArrayInput;
class CheckForUpdatesHandler
@@ -22,7 +22,7 @@ class CheckForUpdatesHandler
protected $composer;
/**
* @var LastUpdateCheck
* @var \Flarum\PackageManager\Settings\LastUpdateCheck
*/
protected $lastUpdateCheck;
@@ -95,7 +95,9 @@ class CheckForUpdatesHandler
}
}
return $this->lastUpdateCheck->save($firstOutput);
return $this->lastUpdateCheck
->with('installed', $firstOutput['installed'])
->save();
}
/**

View File

@@ -15,7 +15,7 @@ use Flarum\PackageManager\Exception\MajorUpdateFailedException;
use Flarum\PackageManager\Exception\NoNewMajorVersionException;
use Illuminate\Contracts\Events\Dispatcher;
use Flarum\PackageManager\Event\FlarumUpdated;
use Flarum\PackageManager\LastUpdateCheck;
use Flarum\PackageManager\Settings\LastUpdateCheck;
use Symfony\Component\Console\Input\ArrayInput;
class MajorUpdateHandler
@@ -68,6 +68,28 @@ class MajorUpdateHandler
{
$command->actor->assertAdmin();
// @todo remove testing code
throw new MajorUpdateFailedException(
'*',
'Loading composer repositories with package information
Updating dependencies
Your requirements could not be resolved to an installable set of packages.
Problem 1
- Root composer.json requires flarum/tags * -> satisfiable by flarum/tags[1.0.0].
- flarum/tags 1.0.0 requires flarum/core >=0.1.0-beta.15 <0.1.0-beta.16 -> found flarum/core[v0.1.0-beta.15] but it conflicts with your root composer.json require (^1.1.1).
Problem 2
- Root composer.json requires sycho/flarum-profile-cover * -> satisfiable by sycho/flarum-profile-cover[1.0.0].
- sycho/flarum-profile-cover 1.0.0 requires flarum/core >=0.1.0-beta.15 <=0.1.0-beta.16 -> found flarum/core[v0.1.0-beta.15, v0.1.0-beta.16] but it conflicts with your root composer.json require (^1.1.1).
Problem 3
- Root composer.json requires askvortsov/flarum-auto-moderator * -> satisfiable by askvortsov/flarum-auto-moderator[1.0.0].
- askvortsov/flarum-auto-moderator 1.0.0 requires flarum/core 0.1.0-beta.15 -> found flarum/core[v0.1.0-beta.15] but it conflicts with your root composer.json require (^1.1.1).
<warning>Running update with --no-dev does not mean require-dev is ignored, it just means the packages will not be installed. If dev requirements are blocking the update you have to resolve those problems.</warning>
',
'2.0',
);
$majorVersion = $this->lastUpdateCheck->getNewMajorVersion();
if (! $majorVersion) {
@@ -91,6 +113,9 @@ class MajorUpdateHandler
return true;
}
/**
* @todo change minimum stability to 'stable' and any other similar params
*/
protected function updateComposerJson(string $majorVersion): void
{
$versionNumber = str_replace('v', '', $majorVersion);

View File

@@ -14,7 +14,7 @@ use Flarum\PackageManager\Composer\ComposerJson;
use Illuminate\Contracts\Events\Dispatcher;
use Flarum\PackageManager\Event\FlarumUpdated;
use Flarum\PackageManager\Exception\ComposerUpdateFailedException;
use Flarum\PackageManager\LastUpdateCheck;
use Flarum\PackageManager\Settings\LastUpdateCheck;
use Symfony\Component\Console\Input\StringInput;
class MinorUpdateHandler
@@ -25,7 +25,7 @@ class MinorUpdateHandler
protected $composer;
/**
* @var LastUpdateCheck
* @var \Flarum\PackageManager\Settings\LastUpdateCheck
*/
protected $lastUpdateCheck;

View File

@@ -16,7 +16,7 @@ use Flarum\PackageManager\Exception\ComposerUpdateFailedException;
use Flarum\PackageManager\Exception\ExtensionNotInstalledException;
use Flarum\PackageManager\Extension\Event\Updated;
use Flarum\PackageManager\UpdateExtensionValidator;
use Flarum\PackageManager\LastUpdateCheck;
use Flarum\PackageManager\Settings\LastUpdateCheck;
use Symfony\Component\Console\Input\StringInput;
class UpdateExtensionHandler
@@ -84,8 +84,6 @@ class UpdateExtensionHandler
throw new ComposerUpdateFailedException($extension->name, $output->getContents());
}
$this->lastUpdateCheck->forget($extension->name);
$this->events->dispatch(
new Updated($extension)
);

View File

@@ -0,0 +1,37 @@
<?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;
use Flarum\User\User;
class WhyNot
{
/**
* @var User
*/
public $actor;
/**
* @var string
*/
public $package;
/**
* @var string
*/
public $version;
public function __construct(User $actor, string $package, string $version)
{
$this->actor = $actor;
$this->package = $package;
$this->version = $version;
}
}

View 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\Command;
use Flarum\PackageManager\Composer\ComposerAdapter;
use Illuminate\Contracts\Events\Dispatcher;
use Flarum\PackageManager\Exception\ComposerRequireFailedException;
use Flarum\PackageManager\WhyNotValidator;
use Symfony\Component\Console\Input\StringInput;
class WhyNotHandler
{
/**
* @var ComposerAdapter
*/
protected $composer;
/**
* @var WhyNotValidator
*/
protected $validator;
/**
* @var Dispatcher
*/
protected $events;
public function __construct(ComposerAdapter $composer, WhyNotValidator $validator, Dispatcher $events)
{
$this->composer = $composer;
$this->validator = $validator;
$this->events = $events;
}
/**
* @throws \Flarum\User\Exception\PermissionDeniedException
* @throws \Exception
*/
public function handle(WhyNot $command)
{
$command->actor->assertAdmin();
$this->validator->assertValid([
'package' => $command->package,
'version' => $command->version
]);
$output = $this->composer->run(
new StringInput("why-not $command->package $command->version")
);
if ($output->getExitCode() !== 0) {
throw new ComposerRequireFailedException($command->package, $output->getContents());
}
return $output->getContents();
}
}

View File

@@ -43,6 +43,8 @@ class ComposerAdapter
public function run(InputInterface $input): ComposerOutput
{
$this->application->resetComposer();
$exitCode = $this->application->run($input, $this->output);
$outputContents = $this->output->fetch();

View File

@@ -14,8 +14,8 @@ use Flarum\User\User;
class FlarumUpdated
{
public const GLOBAL = 'global';
public const MAJOR = 'major';
public const MINOR = 'minor';
public const MAJOR = 'major';
/**
* @var User

View File

@@ -1,8 +1,17 @@
<?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\Exception;
use Composer\Semver\Semver;
use Flarum\PackageManager\Event\FlarumUpdated;
use Flarum\PackageManager\Settings\LastUpdateRun;
class MajorUpdateFailedException extends ComposerCommandFailedException
{
@@ -31,6 +40,12 @@ class MajorUpdateFailedException extends ComposerCommandFailedException
}
}
resolve(LastUpdateRun::class)
->for(FlarumUpdated::MAJOR)
->with('status', LastUpdateRun::FAILURE)
->with('incompatibleExtensions', $this->details['incompatible_extensions'])
->save();
return 'extensions_incompatible_with_new_major';
}

View File

@@ -1,78 +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;
use Carbon\Carbon;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class LastUpdateCheck
{
public const KEY = 'flarum-package-manager.last_update_check';
/**
* @var SettingsRepositoryInterface
*/
protected $settings;
public function __construct(SettingsRepositoryInterface $settings)
{
$this->settings = $settings;
}
public function save(array $updates): array
{
$lastUpdateCheck = [
'checkedAt' => Carbon::now(),
'updates' => $updates,
];
$this->settings->set(self::KEY, json_encode($lastUpdateCheck));
return $lastUpdateCheck;
}
public function get(): array
{
return json_decode($this->settings->get(self::KEY), true);
}
public function getNewMajorVersion(): ?string
{
$core = Arr::first(Arr::get($this->get(), 'updates.installed', []), function ($package) {
return $package['name'] === 'flarum/core';
});
return $core ? $core['latest-major'] : null;
}
public function forget(string $name): void
{
$lastUpdateCheck = $this->get();
if (isset($lastUpdateCheck['updates']) && ! empty($lastUpdateCheck['updates']['installed'])) {
$updatesListChanged = false;
foreach ($lastUpdateCheck['updates']['installed'] as $k => $package) {
if ($package['name'] === $name) {
unset($lastUpdateCheck['updates']['installed'][$k]);
$updatesListChanged = true;
break;
}
}
if ($updatesListChanged) {
$lastUpdateCheck['updates']['installed'] = array_values($lastUpdateCheck['updates']['installed']);
$this->settings->set(self::KEY, json_encode($lastUpdateCheck));
}
}
}
}

View File

@@ -10,15 +10,13 @@
namespace Flarum\PackageManager\Listener;
use Composer\Command\ClearCacheCommand;
use Flarum\Bus\Dispatcher;
use Flarum\Database\Console\MigrateCommand;
use Flarum\Foundation\Console\AssetsPublishCommand;
use Flarum\PackageManager\Command\CheckForUpdates;
use Flarum\PackageManager\Event\FlarumUpdated;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
class FlarumUpdateListener
class ClearCacheAfterUpdate
{
/**
* @var ClearCacheCommand
@@ -35,36 +33,20 @@ class FlarumUpdateListener
*/
private $migrate;
/**
* @var Dispatcher
*/
private $bus;
/**
* @param ClearCacheCommand $clearCache
* @param AssetsPublishCommand $publishAssets
* @param MigrateCommand $migrate
* @param Dispatcher $bus
*/
public function __construct(ClearCacheCommand $clearCache, AssetsPublishCommand $publishAssets, MigrateCommand $migrate, Dispatcher $bus)
public function __construct(ClearCacheCommand $clearCache, AssetsPublishCommand $publishAssets, MigrateCommand $migrate)
{
$this->clearCache = $clearCache;
$this->publishAssets = $publishAssets;
$this->migrate = $migrate;
$this->bus = $bus;
}
/**
* @throws \Exception
*/
public function handle(FlarumUpdated $event)
public function handle(FlarumUpdated $event): void
{
$this->clearCache->run(new ArrayInput([]), new NullOutput());
$this->migrate->run(new ArrayInput([]), new NullOutput());
$this->publishAssets->run(new ArrayInput([]), new NullOutput());
$this->bus->dispatch(
new CheckForUpdates($event->actor)
);
}
}

View File

@@ -0,0 +1,68 @@
<?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\Listener;
use Flarum\Bus\Dispatcher;
use Flarum\PackageManager\Command\CheckForUpdates;
use Flarum\PackageManager\Event\FlarumUpdated;
use Flarum\PackageManager\Extension\Event\Updated;
use Flarum\PackageManager\Settings\LastUpdateCheck;
use Flarum\PackageManager\Settings\LastUpdateRun;
class ReCheckForUpdates
{
/**
* @var LastUpdateRun
*/
private $lastUpdateRun;
/**
* @var LastUpdateCheck
*/
private $lastUpdateCheck;
/**
* @var Dispatcher
*/
private $bus;
public function __construct(LastUpdateRun $lastUpdateRun, LastUpdateCheck $lastUpdateCheck, Dispatcher $bus)
{
$this->lastUpdateRun = $lastUpdateRun;
$this->lastUpdateCheck = $lastUpdateCheck;
$this->bus = $bus;
}
/**
* @param FlarumUpdated|Updated $event
*/
public function handle($event): void
{
$previousUpdateCheck = $this->lastUpdateCheck->get();
$lastUpdateCheck = $this->bus->dispatch(
new CheckForUpdates($event->actor)
);
if ($event instanceof FlarumUpdated) {
$mapPackageName = function (array $package) {
return $package['name'];
};
$previousPackages = array_map($mapPackageName, $previousUpdateCheck['updates']['installed']);
$lastPackages = array_map($mapPackageName, $lastUpdateCheck['updates']['installed']);
$this->lastUpdateRun
->for($event->type)
->with('status', LastUpdateRun::SUCCESS)
->with('limitedPackages', array_intersect($previousPackages, $lastPackages))
->save();
}
}
}

View File

@@ -17,6 +17,7 @@ use Flarum\Foundation\Paths;
use Flarum\Frontend\RecompileFrontendAssets;
use Flarum\Locale\LocaleManager;
use Flarum\PackageManager\Composer\ComposerAdapter;
use Flarum\PackageManager\Listener\ReCheckForUpdates;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher;
use Monolog\Formatter\LineFormatter;
@@ -24,7 +25,7 @@ use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
use Flarum\PackageManager\Event\FlarumUpdated;
use Flarum\PackageManager\Extension\Event\Updated;
use Flarum\PackageManager\Listener\FlarumUpdateListener;
use Flarum\PackageManager\Listener\ClearCacheAfterUpdate;
class PackageManagerServiceProvider extends AbstractServiceProvider
{
@@ -89,6 +90,7 @@ class PackageManagerServiceProvider extends AbstractServiceProvider
}
);
$events->listen(FlarumUpdated::class, FlarumUpdateListener::class);
$events->listen(FlarumUpdated::class, ClearCacheAfterUpdate::class);
$events->listen([FlarumUpdated::class, Updated::class], ReCheckForUpdates::class);
}
}

View File

@@ -13,10 +13,12 @@ use Flarum\Foundation\AbstractValidator;
class RequirePackageValidator extends AbstractValidator
{
public const PACKAGE_NAME_REGEX = '/^[A-z0-9-_]+\/[A-z-0-9]+(?::[A-z-0-9.->=<_]+){0,1}$/i';
/**
* {@inheritdoc}
*/
protected $rules = [
'package' => ['required', 'string', 'regex:/^[A-z0-9-_]+\/[A-z-0-9]+(?::[A-z-0-9.->=<_]+){0,1}$/i']
'package' => ['required', 'string', 'regex:'.self::PACKAGE_NAME_REGEX]
];
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Flarum\PackageManager\Settings;
interface JsonSetting
{
public function with(string $key, $value): self;
public function save(): array;
public function get(): array;
public static function key(): string;
public static function default(): array;
}

View File

@@ -0,0 +1,78 @@
<?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\Settings;
use Carbon\Carbon;
use Flarum\Settings\SettingsRepositoryInterface;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class LastUpdateCheck implements JsonSetting
{
/**
* @var SettingsRepositoryInterface
*/
protected $settings;
protected $data = [];
public function __construct(SettingsRepositoryInterface $settings)
{
$this->settings = $settings;
}
public function with(string $key, $value): JsonSetting
{
$this->data[$key] = $value;
return $this;
}
public function save(): array
{
$lastUpdateCheck = [
'checkedAt' => Carbon::now(),
'updates' => $this->data,
];
$this->settings->set($this->key(), json_encode($lastUpdateCheck));
return $lastUpdateCheck;
}
public function get(): array
{
return json_decode($this->settings->get($this->key()), true);
}
public static function key(): string
{
return 'flarum-package-manager.last_update_check';
}
public static function default(): array
{
return [
'checkedAt' => null,
'updates' => [
'installed' => [],
],
];
}
public function getNewMajorVersion(): ?string
{
$core = Arr::first(Arr::get($this->get(), 'updates.installed', []), function ($package) {
return $package['name'] === 'flarum/core';
});
return $core ? $core['latest-major'] : null;
}
}

View File

@@ -0,0 +1,91 @@
<?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\Settings;
use Carbon\Carbon;
use Flarum\PackageManager\Event\FlarumUpdated;
use Flarum\Settings\SettingsRepositoryInterface;
class LastUpdateRun implements JsonSetting
{
public const SUCCESS = 'success';
public const FAILURE = 'failure';
/**
* @var SettingsRepositoryInterface
*/
protected $settings;
protected $data;
/**
* @var {'major'|'minor'|'global'}
*/
protected $activeUpdate;
public function __construct(SettingsRepositoryInterface $settings)
{
$this->settings = $settings;
$this->data = self::default();
}
public function for(string $update): self
{
if (! in_array($update, [FlarumUpdated::MAJOR, FlarumUpdated::MINOR, FlarumUpdated::GLOBAL])) {
throw new \InvalidArgumentException("Last update runs can only be for one of: minor, major, global");
}
$this->activeUpdate = $update;
return $this;
}
public function with(string $key, $value): JsonSetting
{
$this->data[$this->activeUpdate][$key] = $value;
return $this;
}
public function save(): array
{
$this->data[$this->activeUpdate]['ranAt'] = Carbon::now();
$this->settings->set(self::key(), json_encode($this->data));
return $this->data;
}
public function get(): array
{
return json_decode($this->settings->get(self::key()), true);
}
public static function key(): string
{
return 'flarum-package-manager.last_update_run';
}
public static function default(): array
{
$defaultState = [
'ranAt' => null,
'status' => null,
'limitedPackages' => [],
'incompatibleExtensions' => [],
];
return [
FlarumUpdated::GLOBAL => $defaultState,
FlarumUpdated::MINOR => $defaultState,
FlarumUpdated::MAJOR => $defaultState,
];
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Flarum\PackageManager;
use Flarum\Foundation\AbstractValidator;
class WhyNotValidator extends AbstractValidator
{
/**
* {@inheritdoc}
*/
protected $rules = [
'package' => ['required', 'string', 'regex:'.RequirePackageValidator::PACKAGE_NAME_REGEX],
'version' => ['sometimes', 'string', 'regex:/(?:\*|[A-z0-9.-]+)/i']
];
}