Skip to content

Commit

Permalink
Move VersionChecker from helfi_api_base to helfi_drupal_tools
Browse files Browse the repository at this point in the history
  • Loading branch information
hyrsky committed Dec 17, 2024
1 parent f671eb0 commit 56686f8
Show file tree
Hide file tree
Showing 8 changed files with 298 additions and 32 deletions.
30 changes: 14 additions & 16 deletions src/Drush/Commands/PackageScannerDrushCommands.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@

use Consolidation\AnnotatedCommand\CommandResult;
use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
use Drupal\Component\DependencyInjection\ContainerInterface;
use Drupal\helfi_api_base\Package\VersionChecker;
use DrupalTools\OutputFormatters\MarkdownTableFormatter;
use DrupalTools\Package\ComposerOutdatedProcess;
use DrupalTools\Package\VersionChecker;
use Drush\Attributes\Argument;
use Drush\Attributes\Bootstrap;
use Drush\Attributes\Command;
use Drush\Attributes\FieldLabels;
use Drush\Boot\DrupalBootLevels;
use Drush\Commands\DrushCommands;
use Drush\Drush;
use Psr\Container\ContainerInterface as DrushContainer;

/**
Expand All @@ -24,7 +25,7 @@ final class PackageScannerDrushCommands extends DrushCommands {
/**
* Constructs a new instance.
*
* @param \Drupal\helfi_api_base\Package\VersionChecker $versionChecker
* @param \DrupalTools\Package\VersionChecker $versionChecker
* The version checker service.
*/
public function __construct(
Expand All @@ -36,20 +37,19 @@ public function __construct(
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, ?DrushContainer $drush = NULL): self {
if (!$drush && Drush::hasContainer()) {
$drush = Drush::getContainer();
}
public static function create(DrushContainer $drush): self {
/** @var \Drush\Formatters\DrushFormatterManager $formatterManager */
$formatterManager = $drush->get('formatterManager');

// @todo Figure out if there's a better way to inject this service.
if (!$formatterManager->hasFormatter('markdown_table')) {
$formatterManager->addFormatter('markdown_table', new MarkdownTableFormatter());
}
return new self(
$container->get('helfi_api_base.package_version_checker'),
);

$process = new ComposerOutdatedProcess();
$versionChecker = new VersionChecker($process);

return new self($versionChecker);
}

/**
Expand All @@ -66,6 +66,7 @@ public static function create(ContainerInterface $container, ?DrushContainer $dr
* The result.
*/
#[Command(name: 'helfi:tools:check-composer-versions')]
#[Bootstrap(level: DrupalBootLevels::NONE)]
#[Argument(name: 'file', description: 'Path to composer.lock file')]
#[FieldLabels(labels: [
'name' => 'Name',
Expand All @@ -75,11 +76,8 @@ public static function create(ContainerInterface $container, ?DrushContainer $dr
public function checkVersions(?string $file = NULL, array $options = ['format' => 'table']) : CommandResult {
$rows = [];
foreach ($this->versionChecker->getOutdated($file) as $version) {
/** @var \Drupal\helfi_api_base\Package\Version $version */

// Skip dev versions since we can't easily verify the latest
// version.
if ($version->isLatest || str_starts_with($version->version, 'dev-')) {
// Skip dev versions since we can't easily verify the latest version.
if (str_starts_with($version->version, 'dev-')) {
continue;
}

Expand Down
30 changes: 30 additions & 0 deletions src/Package/ComposerOutdatedProcess.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace DrupalTools\Package;

use Symfony\Component\Process\Process;

/**
* Process for `composer outdated` command.
*/
class ComposerOutdatedProcess {

/**
* Runs `composer outdated`.
*
* @return array
* Decoded JSON from `composer outdated`.
*
* @throws \Symfony\Component\Process\Exception\ProcessFailedException
*/
public function run($workingDir): array {

Check failure on line 22 in src/Package/ComposerOutdatedProcess.php

View workflow job for this annotation

GitHub Actions / tests (8.1)

Method DrupalTools\Package\ComposerOutdatedProcess::run() has parameter $workingDir with no type specified.

Check failure on line 22 in src/Package/ComposerOutdatedProcess.php

View workflow job for this annotation

GitHub Actions / tests (8.2)

Method DrupalTools\Package\ComposerOutdatedProcess::run() has parameter $workingDir with no type specified.
$process = new Process([
'composer', 'outdated', '--direct', '--format=json', '--working-dir=' . $workingDir,
]);
$process->mustRun();
return json_decode($process->getOutput(), TRUE);
}

}
11 changes: 11 additions & 0 deletions src/Package/Exception/VersionCheckException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace DrupalTools\Package\Exception;

/**
* Indicated failure to check versions.
*/
class VersionCheckException extends \RuntimeException {
}
29 changes: 29 additions & 0 deletions src/Package/Version.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace DrupalTools\Package;

/**
* A value object to store package version data.
*/
final class Version {

/**
* Constructs a new instance.
*
* @param string $name
* The package name.
* @param string $latestVersion
* The latest version.
* @param string $version
* The current version.
*/
public function __construct(
public string $name,
public string $latestVersion,
public string $version,
) {
}

}
74 changes: 74 additions & 0 deletions src/Package/VersionChecker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace DrupalTools\Package;

use DrupalTools\Package\Exception\VersionCheckException;
use Symfony\Component\Process\Exception\ProcessFailedException;

/**
* Provides a package version checker.
*/
class VersionChecker {

/**
* Constructs a new instance.
*/
public function __construct(
private readonly ComposerOutdatedProcess $process,
) {
}

/**
* Gets outdated package versions.
*
* @param string $composerLockFile
* Path to composer lock file.
*
* @return \DrupalTools\Package\Version[]
* Outdated packages.
*
* @throws \DrupalTools\Package\Exception\VersionCheckException
*/
public function getOutdated(string $composerLockFile) : array {
$packages = $this->getPackages($composerLockFile);
$versions = [];

foreach ($packages as $packageName => $package) {
$versions[] = new Version($packageName, $package['latest'], $package['version']);
}

return $versions;
}

/**
* Get outdated packages.
*
* @throws \DrupalTools\Package\Exception\VersionCheckException
*/
private function getPackages(string $composerLockFile): array {
if (!$composerLockFile = realpath($composerLockFile)) {
throw new VersionCheckException('Composer lock file not found');
}

$workingDir = dirname($composerLockFile);
try {
$packages = $this->process->run($workingDir);
$packages = $packages['installed'] ?? [];
}
catch (ProcessFailedException) {
throw new VersionCheckException("Composer process failed");
}

$result = [];

// Key with package name.
foreach ($packages as $package) {
$result[$package['name']] = $package;
}

return $result;
}

}
50 changes: 50 additions & 0 deletions tests/fixtures/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 7 additions & 16 deletions tests/src/Kernel/CheckPackageVersionsCommandsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
namespace Drupal\Tests\helfi_drupal_tools\Kernel;

use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
use Drupal\helfi_api_base\Package\ComposerOutdatedProcess;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\helfi_api_base\Traits\ApiTestTrait;
use DrupalTools\Drush\Commands\PackageScannerDrushCommands;
use DrupalTools\OutputFormatters\MarkdownTableFormatter;
use DrupalTools\Package\ComposerOutdatedProcess;
use DrupalTools\Package\VersionChecker;
use Drush\Commands\DrushCommands;
use Drush\Formatters\DrushFormatterManager;
use League\Container\Container;
Expand Down Expand Up @@ -49,19 +50,10 @@ private function getDrushContainer() : ContainerInterface {
*/
public function testMarkdownTableFormatter() : void {
$container = $this->getDrushContainer();
PackageScannerDrushCommands::create($this->container, $container);
PackageScannerDrushCommands::create($container);
$this->assertInstanceOf(MarkdownTableFormatter::class, $container->get('formatterManager')->getFormatter('markdown_table'));
}

/**
* Tests version check with invalid composer.json file.
*/
public function testInvalidComposerFileException() : void {
$sut = PackageScannerDrushCommands::create($this->container, $this->getDrushContainer());
$this->expectException(\RuntimeException::class);
$sut->checkVersions('nonexistent.lock');
}

/**
* Tests version check.
*/
Expand All @@ -79,16 +71,15 @@ public function testVersionCheck() : void {
],
],
]);
$this->container->set(ComposerOutdatedProcess::class, $process->reveal());

$sut = PackageScannerDrushCommands::create($this->container, $this->getDrushContainer());
$versionChecker = new VersionChecker($process->reveal());
$sut = new PackageScannerDrushCommands($versionChecker);

// Test with up-to-date version and make sure we exit with success.
$return = $sut->checkVersions();
$return = $sut->checkVersions(__DIR__ . '/../../fixtures/composer.lock');
$this->assertEquals(DrushCommands::EXIT_SUCCESS, $return->getExitCode());

// Test with old version and make sure we exit with failure.
$return = $sut->checkVersions();
$return = $sut->checkVersions(__DIR__ . '/../../fixtures/composer.lock');
$this->assertEquals(DrushCommands::EXIT_FAILURE_WITH_CLARITY, $return->getExitCode());
$rows = $return->getOutputData();
$this->assertInstanceOf(RowsOfFields::class, $rows);
Expand Down
Loading

0 comments on commit 56686f8

Please sign in to comment.