Skip to content
Merged
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
14 changes: 7 additions & 7 deletions .github/workflows/code_analysis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,19 @@ jobs:

-
name: 'Check Active Classes'
run: vendor/bin/class-leak check bin src tests --ansi --skip-type="Rector\Monitor\Compare\Contract\ComparatorInterface"
run: vendor/bin/class-leak check bin src tests --ansi --skip-type="Rector\Monitor\Compare\Contract\ComparatorInterface" --skip-type="Entropy\Console\Contract\CommandInterface" --skip-type="\Rector\Monitor\Analyze\Contract\RuleProcessorInterface"

-
name: 'Run "Compare Projects" command'
run: php bin/monitor compare-projects tests/project-fixture/first-project --merge-project tests/project-fixture/second-project --ansi
name: 'Run "Analyze"'
run: php bin/monitor analyze

-
name: 'Run "Matrix"'
run: php bin/monitor matrix --ansi
name: 'Run "Compare"'
run: php bin/monitor compare-projects tests/project-fixture/first-project --merge-project tests/project-fixture/second-project

-
name: 'Run "Analyze"'
run: php bin/monitor analyze --ansi
name: 'Run "Matrix"'
run: php bin/monitor matrix

-
name: 'Composer dependency Analyser'
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# [PRIVATE BETA] Monitor quality of many Packages and Projects in one place

[![License](https://img.shields.io/packagist/l/rector/monitor.svg?style=flat-square)](https://packagist.org/packages/rector/monitor)
[![Downloads total](https://img.shields.io/packagist/dt/rector/monitor.svg?style=flat-square)](https://packagist.org/packages/rector/monitor/stats)

Monitor code quality for all your projects and packages in one place, ensuring consistency and eliminating conflicts across PHP versions.
Expand Down Expand Up @@ -42,7 +41,7 @@ return MonitorConfig::configure()
// ->addRepositoryBranch('...', 'stage')

// composer rules
// ->disallowPackages(['symfony/phpunit-bridge'])
->disallowPackages(['symfony/phpunit-bridge'])
->requirePackages([
'rector/rector',
'phpecs/phpecs',
Expand Down
8 changes: 5 additions & 3 deletions bin/monitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

use Rector\Monitor\Console\MonitorConsoleApplication;
use Entropy\Console\ConsoleApplication;
use Rector\Monitor\DependencyInjection\ContainerFactory;

$scoperAutoloadFilepath = __DIR__ . '/../vendor/scoper-autoload.php';
Expand Down Expand Up @@ -30,5 +30,7 @@
$containerFactory = new ContainerFactory();
$container = $containerFactory->create();

$application = $container->make(MonitorConsoleApplication::class);
exit($application->run());
$application = $container->make(ConsoleApplication::class);
$exitCode = $application->run($argv);

exit($exitCode);
4 changes: 3 additions & 1 deletion composer-dependency-analyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@

use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;

return new Configuration();
return (new Configuration())
// conditional use
->ignoreErrorsOnExtension('ext-mbstring', [\ShipMonk\ComposerDependencyAnalyser\Config\ErrorType::SHADOW_DEPENDENCY]);
18 changes: 8 additions & 10 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,19 @@
"composer/semver": "^3.4",
"entropy/entropy": "dev-main",
"nette/neon": "^3.4",
"nette/utils": "^4.1",
"symfony/filesystem": "^7.4",
"symfony/console": "^6.4",
"symfony/finder": "^7.4",
"symfony/process": "^7.4",
"webmozart/assert": "^1.12"
"symfony/filesystem": "^7.4|8.0.*",
"symfony/finder": "^7.4|8.0.*",
"symfony/process": "^7.4|8.0.*",
"webmozart/assert": "^1.12|^2.0"
},
"require-dev": {
"phpecs/phpecs": "^2.2",
"phpecs/phpecs": "^2.3",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "^2.1",
"phpstan/phpstan-deprecation-rules": "^2.0",
"phpunit/phpunit": "^11.5",
"rector/jack": "^0.4.0",
"rector/rector": "^2.2",
"phpunit/phpunit": "^12.5",
"rector/jack": "^0.5",
"rector/rector": "^2.3",
"shipmonk/composer-dependency-analyser": "^1.8",
"symplify/phpstan-extensions": "^12.0",
"symplify/phpstan-rules": "^14.9",
Expand Down
8 changes: 8 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,11 @@ parameters:
treatPhpDocTypesAsCertain: false

errorFormat: symplify

ignoreErrors:
# part of entropy magic contract
- '#Public method "Rector\\(.*?)Command\:\:run\(\)" is never used#'

# too detailed
- '#Parameter (.*?) expects list<int>, (.*?)<int<0, max>, int<0, max>> given#'

112 changes: 48 additions & 64 deletions src/Analyze/Command/AnalyzeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,79 +4,60 @@

namespace Rector\Monitor\Analyze\Command;

use Entropy\Console\Contract\CommandInterface;
use Entropy\Console\Enum\ExitCode;
use Entropy\Console\Output\OutputPrinter;
use Rector\Monitor\Analyze\Contract\RuleProcessorInterface;
use Rector\Monitor\Analyze\Reporting\ErrorCollector;
use Rector\Monitor\Analyze\RuleProcessor\DisallowedPackagesRuleProcessor;
use Rector\Monitor\Analyze\RuleProcessor\MetafileProcessor\NoPHPStanBaselineMetafileProcessor;
use Rector\Monitor\Analyze\RuleProcessor\MissingPackagesRuleProcessor;
use Rector\Monitor\Analyze\RuleProcessor\TooLowPackagesRulesProcessor;
use Rector\Monitor\Config\ConfigInitializer;
use Rector\Monitor\Config\MonitorConfigProvider;
use Rector\Monitor\Git\RepositoryMetafilesResolver;
use Rector\Monitor\ValueObject\RepositoryCollection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

final class AnalyzeCommand extends Command
use Webmozart\Assert\Assert;

final readonly class AnalyzeCommand implements CommandInterface
{
/**
* @var RuleProcessorInterface[]
* @param RuleProcessorInterface[] $ruleProcessors
*/
private array $ruleProcessors;

public function __construct(
private readonly SymfonyStyle $symfonyStyle,
private readonly MonitorConfigProvider $monitorConfigProvider,
private readonly RepositoryMetafilesResolver $repositoryMetafilesResolver,
private readonly ConfigInitializer $configInitializer,
DisallowedPackagesRuleProcessor $disallowedPackagesRuleProcessor,
MissingPackagesRuleProcessor $missingPackagesRuleProcessor,
TooLowPackagesRulesProcessor $tooLowPackagesRulesProcessor,
NoPHPStanBaselineMetafileProcessor $noPHPStanBaselineMetafileProcessor
private OutputPrinter $outputPrinter,
private MonitorConfigProvider $monitorConfigProvider,
private RepositoryMetafilesResolver $repositoryMetafilesResolver,
private ConfigInitializer $configInitializer,
private array $ruleProcessors,
) {
parent::__construct();

$this->ruleProcessors[] = $disallowedPackagesRuleProcessor;
$this->ruleProcessors[] = $missingPackagesRuleProcessor;
$this->ruleProcessors[] = $tooLowPackagesRulesProcessor;
$this->ruleProcessors[] = $noPHPStanBaselineMetafileProcessor;
Assert::notEmpty($ruleProcessors);
}

protected function configure(): void
public function getName(): string
{
$this->setName('analyze');
$this->setAliases(['analyse']);

$this->setDescription(
'Check repositories composer.json files, assess "require" section, root files, tooling setup and min PHP version with defined quality control rules'
);
return 'analyze';
}

$this->addOption(
'clear-cache',
null,
InputOption::VALUE_NONE,
'Remove cached repositoryCollection composer.json files'
);
public function getDescription(): string
{
return 'Analyze multiple repositories for required packages, root files, min PHP version etc. against defined standard';
}

protected function execute(InputInterface $input, OutputInterface $output): int
/**
* @param bool $clearCache Remove cached repositoryCollection composer.json files
* @return ExitCode::*
*/
public function run(bool $clearCache = false, bool $debug = false): int
{
if ($this->configInitializer->createConfigIfMissing(getcwd())) {
return self::SUCCESS;
return ExitCode::SUCCESS;
}

$monitorConfig = $this->monitorConfigProvider->provide();
$shouldClearCache = (bool) $input->getOption('clear-cache');

$this->repositoryMetafilesResolver->decorateRepositories(
$monitorConfig->getRepositoryCollection(),
$shouldClearCache
$clearCache,
$debug
);

$this->symfonyStyle->title('Repository analysis report');
$this->outputPrinter->title('Repository analysis report');

$erroredRepositoryCount = 0;

Expand All @@ -85,42 +66,45 @@ protected function execute(InputInterface $input, OutputInterface $output): int

$composerJson = $repository->getComposerJson();

$this->symfonyStyle->writeln(
sprintf('<fg=yellow;options=bold>%d) %s</>', $key + 1, $composerJson->getRepositoryName())
$this->outputPrinter->writeln(
sprintf('<fg=yellow>%d) %s</>', $key + 1, $composerJson->getRepositoryName())
);
foreach ($this->ruleProcessors as $ruleProcessor) {
$ruleProcessor->process($monitorConfig, $composerJson, $errorCollector);
}

foreach ($errorCollector->getErrorMessages() as $errorMessage) {
$this->symfonyStyle->writeln($errorMessage);
$this->outputPrinter->writeln($errorMessage);
}

if ($errorCollector->hasErrors() === false) {
$this->symfonyStyle->writeln(' * <fg=green>Package perfect ✔</>');
$this->outputPrinter->greenBackground('Package perfect ✔');
} else {
++$erroredRepositoryCount;
}

$this->symfonyStyle->newLine();
$this->outputPrinter->newLine();
}

$this->printAnalysisSummary($monitorConfig->getRepositoryCollection(), $erroredRepositoryCount);
$this->printAnalysisSummary($erroredRepositoryCount);

return self::SUCCESS;
return ExitCode::SUCCESS;
}

private function printAnalysisSummary(RepositoryCollection $repositoryCollection, int $erroredRepositoryCount): void
private function printAnalysisSummary(int $erroredRepositoryCount): void
{
$this->symfonyStyle->newLine();

$this->symfonyStyle->writeln(sprintf(
' Summary: %d %s analyzed, %d with issues',
$repositoryCollection->count(),
$repositoryCollection->count() === 1 ? 'repository' : 'repositories',
$erroredRepositoryCount
));
$this->outputPrinter->newLine();

if ($erroredRepositoryCount > 0) {
$this->outputPrinter->redBackground(sprintf(
'Found %d %s',
$erroredRepositoryCount,
$erroredRepositoryCount === 1 ? 'issue' : 'issues'
));
} else {
$this->outputPrinter->greenBackground('All repositories are perfect ✔');
}

$this->symfonyStyle->newLine();
$this->outputPrinter->newLine();
}
}
48 changes: 22 additions & 26 deletions src/Compare/Command/CompareProjectsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,58 +4,54 @@

namespace Rector\Monitor\Compare\Command;

use Entropy\Console\Contract\CommandInterface;
use Entropy\Console\Output\OutputPrinter;
use Rector\Console\ExitCode;
use Rector\Monitor\Compare\Contract\ComparatorInterface;
use Rector\Monitor\Compare\ValueObject\ProjectMetadata;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Webmozart\Assert\Assert;

final class CompareProjectsCommand extends Command
final readonly class CompareProjectsCommand implements CommandInterface
{
/**
* @param ComparatorInterface[] $comparators
*/
public function __construct(
private readonly SymfonyStyle $symfonyStyle,
private readonly array $comparators
private OutputPrinter $outputPrinter,
private array $comparators
) {
parent::__construct();

// make sure not empty to verify DI works
Assert::notEmpty($comparators);
Assert::allIsInstanceOf($comparators, ComparatorInterface::class);
}

protected function configure(): void
public function getName(): string
{
$this->setName('compare-projects');

$this->setDescription(
'Compare two projects and show the differences. First is the macro, monorepo project we want to merge into. Other is the project to merge merge and dismantle later.'
);
return 'compare-projects';
}

$this->addArgument('monorepo-directory', InputArgument::REQUIRED, 'Path to the monorepo directory');
$this->addOption('merge-project', null, InputOption::VALUE_REQUIRED);
public function getDescription(): string
{
return 'Compare 2 projects and show their differences - configs, required packages, PHPStan extensions, levels etc.';
}

protected function execute(InputInterface $input, OutputInterface $output): int
/**
* @param string $monorepoDirectory Path to the monorepo directory
* @param string $mergeProject Path to the project to merge directory
*
* @return ExitCode::*
*/
public function run(string $monorepoDirectory, string $mergeProject): int
{
$monorepoDirectory = $input->getArgument('monorepo-directory');
$monorepoProjectMetadata = new ProjectMetadata($monorepoDirectory);
$mergeProjectMetadata = new ProjectMetadata($mergeProject);

$mergeDirectory = $input->getOption('merge-project');
$mergeProjectMetadata = new ProjectMetadata($mergeDirectory);

$this->symfonyStyle->writeln('<fg=green>Both directories found. Comparing 2 projects</>');
$this->outputPrinter->green('Both directories found. Comparing 2 projects');

foreach ($this->comparators as $comparator) {
$comparator->compare($monorepoProjectMetadata, $mergeProjectMetadata);
}

return self::SUCCESS;
return \Entropy\Console\Enum\ExitCode::SUCCESS;
}
}
Loading