Verified Commit 0e3aa504 authored by Elias Häußler's avatar Elias Häußler 🐛
Browse files

[!!!][FEATURE] Require eliashaeussler/composer-update-check ^1.0.0

parent 0044f8e6
Pipeline #771 passed with stages
in 1 minute and 59 seconds
......@@ -14,7 +14,7 @@ TEMP_PATH="${TEMP_DIR}/update-reporter-test"
# Define cleanup function for several signals
function cleanup() {
exitCode=$?
local exitCode=$?
rm -rf "${TEMP_PATH}"
exit $exitCode
}
......
......@@ -22,7 +22,7 @@
"php": "^7.1",
"ext-json": "*",
"composer-plugin-api": "^1.0 || ^2.0",
"eliashaeussler/composer-update-check": "~0.4",
"eliashaeussler/composer-update-check": "^1.0@dev",
"nyholm/psr7": "^1.0",
"psr/http-client": "^1.0",
"spatie/emoji": "^2.0",
......
......@@ -24,8 +24,6 @@ namespace EliasHaeussler\ComposerUpdateReporter;
use Composer\Composer;
use Composer\EventDispatcher\EventSubscriberInterface;
use Composer\IO\IOInterface;
use Composer\Plugin\CommandEvent;
use Composer\Plugin\PluginEvents;
use Composer\Plugin\PluginInterface;
use EliasHaeussler\ComposerUpdateCheck\Event\PostUpdateCheckEvent;
......@@ -45,7 +43,7 @@ class Plugin implements PluginInterface, EventSubscriberInterface
public function activate(Composer $composer, IOInterface $io): void
{
$this->reporter = new Reporter($composer, $io);
$this->reporter = new Reporter($composer);
}
public function deactivate(Composer $composer, IOInterface $io)
......@@ -61,19 +59,16 @@ class Plugin implements PluginInterface, EventSubscriberInterface
public static function getSubscribedEvents(): array
{
return [
PluginEvents::COMMAND => [
PostUpdateCheckEvent::NAME => [
['onPostUpdateCheck']
],
];
}
public function onPostUpdateCheck(CommandEvent $event): void
public function onPostUpdateCheck(PostUpdateCheckEvent $event): void
{
if ($event instanceof PostUpdateCheckEvent) {
if ($event->getInput()->hasOption('json')) {
$this->reporter->setJson($event->getInput()->getOption('json'));
}
$this->reporter->report($event->getUpdateCheckResult());
}
$this->reporter->setBehavior($event->getBehavior());
$this->reporter->setOptions($event->getOptions());
$this->reporter->report($event->getUpdateCheckResult());
}
}
......@@ -22,7 +22,11 @@ namespace EliasHaeussler\ComposerUpdateReporter;
*/
use Composer\Composer;
use Composer\IO\IOInterface;
use Composer\IO\NullIO;
use EliasHaeussler\ComposerUpdateCheck\IO\OutputBehavior;
use EliasHaeussler\ComposerUpdateCheck\IO\Style;
use EliasHaeussler\ComposerUpdateCheck\IO\Verbosity;
use EliasHaeussler\ComposerUpdateCheck\Options;
use EliasHaeussler\ComposerUpdateCheck\Package\UpdateCheckResult;
use EliasHaeussler\ComposerUpdateReporter\Service\Email;
use EliasHaeussler\ComposerUpdateReporter\Service\GitLab;
......@@ -45,14 +49,14 @@ class Reporter
private $composer;
/**
* @var IOInterface
* @var OutputBehavior
*/
private $io;
private $behavior;
/**
* @var bool
* @var Options
*/
private $json;
private $options;
/**
* @var string[]
......@@ -64,11 +68,11 @@ class Reporter
*/
private $configuration;
public function __construct(Composer $composer, IOInterface $io, bool $json = false)
public function __construct(Composer $composer)
{
$this->composer = $composer;
$this->io = $io;
$this->json = $json;
$this->behavior = $this->getDefaultBehavior();
$this->options = new Options();
$this->registeredServices = $this->getDefaultServices();
$this->configuration = $this->resolveConfiguration();
}
......@@ -77,8 +81,7 @@ class Reporter
{
$services = $this->buildServicesFromConfiguration();
foreach ($services as $service) {
$service->setJson($this->json);
$service->report($result, $this->io);
$service->report($result);
}
}
......@@ -98,17 +101,22 @@ class Reporter
}
if ($registeredService::isEnabled($this->configuration)) {
$service = $registeredService::fromConfiguration($this->configuration);
$service->setJson($this->json);
$service->setBehavior($this->behavior);
$service->setOptions($this->options);
$services[] = $service;
}
}
return $services;
}
public function setJson(bool $json): self
public function setBehavior(OutputBehavior $behavior): void
{
$this->json = $json;
return $this;
$this->behavior = $behavior;
}
public function setOptions(Options $options): void
{
$this->options = $options;
}
public function setRegisteredServices(array $registeredServices): self
......@@ -128,6 +136,15 @@ class Reporter
];
}
private function getDefaultBehavior(): OutputBehavior
{
return new OutputBehavior(
new Style(Style::NORMAL),
new Verbosity(Verbosity::NORMAL),
new NullIO()
);
}
private function resolveConfiguration(): array
{
return $this->composer->getPackage()->getExtra()['update-check'] ?? [];
......
<?php
declare(strict_types=1);
namespace EliasHaeussler\ComposerUpdateReporter\Service;
/*
* This file is part of the Composer plugin "eliashaeussler/composer-update-check".
*
* Copyright (C) 2021 Elias Häußler <elias@haeussler.dev>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use Composer\IO\NullIO;
use EliasHaeussler\ComposerUpdateCheck\IO\OutputBehavior;
use EliasHaeussler\ComposerUpdateCheck\IO\Style;
use EliasHaeussler\ComposerUpdateCheck\IO\Verbosity;
use EliasHaeussler\ComposerUpdateCheck\Options;
use EliasHaeussler\ComposerUpdateCheck\Package\UpdateCheckResult;
use Spatie\Emoji\Emoji;
use Spatie\Emoji\Exceptions\UnknownCharacter;
/**
* AbstractService
*
* @author Elias Häußler <elias@haeussler.dev>
* @license GPL-3.0-or-later
*/
abstract class AbstractService implements ServiceInterface
{
/**
* @var OutputBehavior
*/
protected $behavior;
/**
* @var Options
*/
protected $options;
public static function isEnabled(array $configuration): bool
{
$identifier = static::getIdentifier();
$envVariable = strtoupper($identifier . '_enable');
$extra = $configuration[strtolower($identifier)] ?? null;
if (getenv($envVariable) !== false) {
return (bool)getenv($envVariable);
}
return is_array($extra) && (bool)($extra['enable'] ?? false);
}
abstract protected static function getIdentifier(): string;
abstract protected static function getName(): string;
public function report(UpdateCheckResult $result): bool
{
// Fall back to default output behavior if no custom behavior is defined
if ($this->behavior === null) {
$this->behavior = $this->getDefaultBehavior();
}
$outdatedPackages = $result->getOutdatedPackages();
// Do not send report if packages are up to date
if ($outdatedPackages === []) {
if (!$this->behavior->style->isJson()) {
$this->behavior->io->write(
sprintf('%s Skipped %s report', Emoji::prohibited(), static::getName())
);
}
return true;
}
$successful = $this->sendReport($result);
// Print report state
if (!$successful) {
$this->behavior->io->writeError(
sprintf('%s <error>Error during %s report</error>', Emoji::crossMark(), static::getName())
);
return false;
}
if (!$this->behavior->style->isJson()) {
try {
$checkMark = Emoji::getCharacter('checkMark');
} catch (UnknownCharacter $e) {
/** @noinspection PhpUnhandledExceptionInspection */
$checkMark = Emoji::getCharacter('heavyCheckMark');
}
$this->behavior->io->write(
sprintf('%s <info>%s report was successful</info>', $checkMark, static::getName())
);
}
return true;
}
/**
* @param UpdateCheckResult $result
* @return bool
*/
abstract protected function sendReport(UpdateCheckResult $result): bool;
public function setBehavior(OutputBehavior $behavior): ServiceInterface
{
$this->behavior = $behavior;
return $this;
}
public function setOptions(Options $options): ServiceInterface
{
$this->options = $options;
return $this;
}
private function getDefaultBehavior(): OutputBehavior
{
return new OutputBehavior(
new Style(Style::NORMAL),
new Verbosity(Verbosity::NORMAL),
new NullIO()
);
}
}
......@@ -21,12 +21,9 @@ namespace EliasHaeussler\ComposerUpdateReporter\Service;
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use Composer\IO\IOInterface;
use EliasHaeussler\ComposerUpdateCheck\Package\OutdatedPackage;
use EliasHaeussler\ComposerUpdateCheck\Package\UpdateCheckResult;
use EliasHaeussler\ComposerUpdateReporter\Traits\PackageProviderLinkTrait;
use Spatie\Emoji\Emoji;
use Spatie\Emoji\Exceptions\UnknownCharacter;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\Transport;
use Symfony\Component\Mailer\Transport\TransportInterface;
......@@ -38,10 +35,8 @@ use Symfony\Component\Mime\Email as SymfonyEmail;
* @author Elias Häußler <elias@haeussler.dev>
* @license GPL-3.0-or-later
*/
class Email implements ServiceInterface
class Email extends AbstractService
{
use PackageProviderLinkTrait;
/**
* @var TransportInterface
*/
......@@ -57,11 +52,6 @@ class Email implements ServiceInterface
*/
private $sender;
/**
* @var bool
*/
private $json = false;
public function __construct(string $dsn, array $receivers, string $sender)
{
$this->transport = Transport::fromDsn($dsn);
......@@ -115,31 +105,24 @@ class Email implements ServiceInterface
return new self($dsn, array_map('trim', array_filter($receivers)), $sender);
}
public static function isEnabled(array $configuration): bool
protected static function getIdentifier(): string
{
if (getenv('EMAIL_ENABLE') !== false && (bool)getenv('EMAIL_ENABLE')) {
return true;
}
$extra = $configuration['email'] ?? null;
return is_array($extra) && (bool)($extra['enable'] ?? false);
return 'email';
}
protected static function getName(): string
{
return 'E-mail';
}
/**
* @inheritDoc
* @throws TransportExceptionInterface
*/
public function report(UpdateCheckResult $result, IOInterface $io): bool
protected function sendReport(UpdateCheckResult $result): bool
{
$outdatedPackages = $result->getOutdatedPackages();
// Do not send report if packages are up to date
if ($outdatedPackages === []) {
if (!$this->json) {
$io->write(Emoji::crossMark() . ' Skipped Email report.');
}
return true;
}
// Set subject
$count = count($outdatedPackages);
$subject = sprintf('%d outdated package%s', $count, $count !== 1 ? 's' : '');
......@@ -149,6 +132,9 @@ class Email implements ServiceInterface
$html = $this->parseHtmlBody($outdatedPackages);
// Send email
if (!$this->behavior->style->isJson()) {
$this->behavior->io->write(Emoji::rocket() . ' Sending report via Email...');
}
$email = (new SymfonyEmail())
->from($this->sender)
->to(...$this->receivers)
......@@ -156,22 +142,8 @@ class Email implements ServiceInterface
->text($body)
->html($html);
$sentMessage = $this->transport->send($email);
$successful = $sentMessage !== null;
// Print report state
if (!$successful) {
$io->writeError(Emoji::crossMark() . ' Error during Email report.');
} elseif (!$this->json) {
try {
$checkMark = Emoji::checkMark();
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (UnknownCharacter $e) {
/** @noinspection PhpUndefinedMethodInspection */
$checkMark = Emoji::heavyCheckMark();
}
$io->write($checkMark . ' Email report was successful.');
}
return $successful;
return $sentMessage !== null;
}
/**
......@@ -219,7 +191,7 @@ class Email implements ServiceInterface
/** @noinspection HtmlUnknownTarget */
$html[] = sprintf(
'<td><a href="%s">%s</a></td>',
$this->getProviderLink($outdatedPackage),
$outdatedPackage->getProviderLink(),
$outdatedPackage->getName()
);
$html[] = '<td>' . $outdatedPackage->getOutdatedVersion() . $insecure . '</td>';
......@@ -245,12 +217,6 @@ class Email implements ServiceInterface
return $this->sender;
}
public function setJson(bool $json): ServiceInterface
{
$this->json = $json;
return $this;
}
private function validateReceivers(): void
{
if ($this->receivers === []) {
......
......@@ -21,7 +21,6 @@ namespace EliasHaeussler\ComposerUpdateReporter\Service;
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use Composer\IO\IOInterface;
use EliasHaeussler\ComposerUpdateCheck\Package\OutdatedPackage;
use EliasHaeussler\ComposerUpdateCheck\Package\UpdateCheckResult;
use EliasHaeussler\ComposerUpdateReporter\Traits\RemoteServiceTrait;
......@@ -30,7 +29,6 @@ use Nyholm\Psr7\Uri;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Message\UriInterface;
use Spatie\Emoji\Emoji;
use Spatie\Emoji\Exceptions\UnknownCharacter;
use Symfony\Component\HttpClient\Psr18Client;
/**
......@@ -39,7 +37,7 @@ use Symfony\Component\HttpClient\Psr18Client;
* @author Elias Häußler <elias@haeussler.dev>
* @license GPL-3.0-or-later
*/
class GitLab implements ServiceInterface
class GitLab extends AbstractService
{
use RemoteServiceTrait;
......@@ -53,11 +51,6 @@ class GitLab implements ServiceInterface
*/
private $authorizationKey;
/**
* @var bool
*/
private $json = false;
public function __construct(UriInterface $uri, string $authorizationKey)
{
$this->uri = $uri;
......@@ -100,31 +93,24 @@ class GitLab implements ServiceInterface
return new self($uri, $authKey);
}
public static function isEnabled(array $configuration): bool
protected static function getIdentifier(): string
{
if (getenv('GITLAB_ENABLE') !== false && (bool)getenv('GITLAB_ENABLE')) {
return true;
}
$extra = $configuration['gitlab'] ?? null;
return is_array($extra) && (bool)($extra['enable'] ?? false);
return 'gitlab';
}
protected static function getName(): string
{
return 'GitLab';
}
/**
* @inheritDoc
* @throws ClientExceptionInterface
*/
public function report(UpdateCheckResult $result, IOInterface $io): bool
protected function sendReport(UpdateCheckResult $result): bool
{
$outdatedPackages = $result->getOutdatedPackages();
// Do not send report if packages are up to date
if ($outdatedPackages === []) {
if (!$this->json) {
$io->write(Emoji::crossMark() . ' Skipped GitLab report.');
}
return true;
}
// Build JSON payload
$count = count($outdatedPackages);
$payload = array_merge([
......@@ -132,26 +118,12 @@ class GitLab implements ServiceInterface
], $this->getPackagesPayload($outdatedPackages));
// Send report
if (!$this->json) {
$io->write(Emoji::rocket() . ' Sending report to GitLab...');
if (!$this->behavior->style->isJson()) {
$this->behavior->io->write(Emoji::rocket() . ' Sending report to GitLab...');
}
$response = $this->sendRequest($payload, ['Authorization' => 'Bearer ' . $this->authorizationKey,]);
$successful = $response->getStatusCode() < 400;
// Print report state
if (!$successful) {
$io->writeError(Emoji::crossMark() . ' Error during GitLab report.');
} elseif (!$this->json) {
try {
$checkMark = Emoji::checkMark();
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (UnknownCharacter $e) {
/** @noinspection PhpUndefinedMethodInspection */
$checkMark = Emoji::heavyCheckMark();
}
$io->write($checkMark . ' GitLab report was successful.');
}
return $successful;
return $response->getStatusCode() < 400;
}
/**
......@@ -186,12 +158,6 @@ class GitLab implements ServiceInterface
return $this->authorizationKey;
}
public function setJson(bool $json): ServiceInterface
{
$this->json = $json;
return $this;
}
private function validateUri(): void
{
$uri = (string) $this->uri;
......
......@@ -21,17 +21,14 @@ namespace EliasHaeussler\ComposerUpdateReporter\Service;
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use Composer\IO\IOInterface;
use EliasHaeussler\ComposerUpdateCheck\Package\OutdatedPackage;
use EliasHaeussler\ComposerUpdateCheck\Package\UpdateCheckResult;
use EliasHaeussler\ComposerUpdateReporter\Traits\PackageProviderLinkTrait;
use EliasHaeussler\ComposerUpdateReporter\Traits\RemoteServiceTrait;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7\Uri;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Message\UriInterface;
use Spatie\Emoji\Emoji;
use Spatie\Emoji\Exceptions\UnknownCharacter;
use Symfony\Component\HttpClient\Psr18Client;
/**
......@@ -40,9 +37,8 @@ use Symfony\Component\HttpClient\Psr18Client;
* @author Elias Häußler <elias@haeussler.dev>
* @license GPL-3.0-or-later
*/
class Mattermost implements ServiceInterface
class Mattermost extends AbstractService
{
use PackageProviderLinkTrait;
use RemoteServiceTrait;
/**
......@@ -60,11 +56,6 @@ class Mattermost implements ServiceInterface
*/
private $username;
/**
* @var bool
*/
private $json = false;
public function __construct(UriInterface $uri, string $channelName, string $username = null)
{
$this->uri = $uri;
......@@ -116,31 +107,24 @@ class Mattermost implements ServiceInterface
return new self($uri, $channelName, $username);
}
public static function isEnabled(array $configuration): bool
protected static function getIdentifier(): string
{
if (getenv('MATTERMOST_ENABLE') !== false && (bool)getenv('MATTERMOST_ENABLE')) {
return true;
}
$extra = $configuration['mattermost'] ?? null;
return is_array($extra) && (bool)($extra['enable'] ?? false);
return 'mattermost';
}
protected static function getName(): string
{
return 'Mattermost';
}
/**
* @inheritDoc
* @throws ClientExceptionInterface