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
8 changes: 8 additions & 0 deletions .github/workflows/code_analysis.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ jobs:
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 "Matrix"'
run: php bin/monitor matrix --ansi

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

-
name: 'Composer dependency Analyser'
run: vendor/bin/composer-dependency-analyser
Expand Down
52 changes: 41 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,41 +20,71 @@ composer require rector/monitor --dev

<br>

## Usage

<br>

### 1. Quality control for `composer.json` in multiple repositories
## 1. Single place to Quality control Multiple repositories

```bash
vendor/bin/monitor analyze
```

This command will load `monitor.php` configuration file from project root. There you define repositories and requirements to meet.

```php
use Rector\Monitor\Config\MonitorConfig;

return MonitorConfig::configure()
->addRepositories([
'https://github.com/rectorphp/rector-symfony',
'https://github.com/rectorphp/rector-doctrine',
'https://github.com/rectorphp/rector-phpunit',
'https://github.com/rectorphp/rector-downgrade-php',
])
->addRepositoryBranch('https://github.com/rectorphp/rector-src', 'main')
// ->addRepositoryBranch('...', 'stage')

// composer rules
// ->disallowPackages(['symfony/phpunit-bridge'])
->requirePackages([
'rector/rector',
'phpecs/phpecs',
'phpstan/phpstan',
'phpstan/extension-installer',
'symplify/phpstan-rules',
])

// version requirements
->minPackageVersion('phpstan/phpstan', '2.1')
->minPackageVersion('rector/rector', '2.2')

// other rules
->noPhpstanBaseline();
```

<br>

The repositories are cached to get fast performance. To refresh them:

```bash
vendor/bin/monitor analyze --clear-cache
```

<br>

### 2. See full Matrix of repositories's dependencies
## 2. See full Matrix of Repositories' Dependencies

```bash
vendor/bin/monitor matrix
```


---

## Merge many projects to one Monorepo
<br>

Automate micro-services merge to one macro project, to make coding saint again.
## 3. See Difference between Two Projects

Automate micro-services merge to one macro project, to make coding saint again.

<br>

### Use

See how the monorepo project and the merge one are different. Use this knowledge to fill gaps in monorepo project. Only then create a final merge pull-request.

```bash
Expand Down
5 changes: 2 additions & 3 deletions monitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,19 @@
return MonitorConfig::configure()
->addRepositories([
// rector repositories
'https://github.com/rectorphp/rector-src',
'https://github.com/rectorphp/rector-symfony',
'https://github.com/rectorphp/rector-doctrine',
'https://github.com/rectorphp/rector-phpunit',
'https://github.com/rectorphp/rector-downgrade-php',
])
// ->addRepositoryBranch('...', 'stage')
->addRepositoryBranch('https://github.com/rectorphp/rector-src', 'main')

// composer rules
// ->disallowPackages(['symfony/phpunit-bridge'])
->requirePackages([
'phpstan/phpstan',
'phpecs/phpecs',
'rector/rector',
'rector/rector-src',
'phpstan/phpstan-deprecation-rules',
'phpstan/extension-installer',
'symplify/phpstan-rules',
Expand Down
2 changes: 2 additions & 0 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\TypeDeclaration\Rector\StmtsAwareInterface\DeclareStrictTypesRector;

return RectorConfig::configure()
->withPaths([__DIR__ . '/bin', __DIR__ . '/src', __DIR__ . '/tests'])
Expand All @@ -23,5 +24,6 @@
\Rector\Naming\Rector\ClassMethod\RenameParamToMatchTypeRector::class => [
__DIR__ . '/src/Composer/ComposerJsonResolver.php',
],
DeclareStrictTypesRector::class,
])
->withImportNames(removeUnusedImports: true);
4 changes: 4 additions & 0 deletions src/Config/MonitorConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ public function getMinPackagesVersions(): array

public function addRepositoryBranch(string $repository, string $branch): self
{
if (! str_ends_with($repository, '.git')) {
$repository .= '.git';
}

MonitorAssert::assertRepositoryUrl($repository);

$this->repositoriesWithBranches[$repository] = $branch;
Expand Down
88 changes: 48 additions & 40 deletions src/Git/RepositoryMetafilesResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,42 +22,36 @@ public function __construct(
) {
}

public function decorateRepositories(RepositoryCollection $repositoryCollection, bool $clearCache = false): void
{
public function decorateRepositories(
RepositoryCollection $repositoryCollection,
bool $clearCache = false,
bool $isDebug = false
): void {
foreach ($repositoryCollection->all() as $repository) {
$repositoryCacheDirectory = sys_get_temp_dir() . '/rector-monitor-cache/repository-' . md5(
$repository->getRepositoryUrl()
);

if (file_exists($repositoryCacheDirectory) && $this->hasDirectorySomeFiles($repositoryCacheDirectory)) {
if ($clearCache) {
// clear old files
FileSystem::delete($repositoryCacheDirectory);
} else {
$rootFiles = $this->findRootFilesInDirectory($repositoryCacheDirectory);
$repository->decorateRootFiles($rootFiles);

$composerJsonFile = $this->matchComposerJsonFile($rootFiles, $repository);

$composerJson = $this->composerJsonFactory->create($composerJsonFile, $repository);
$repository->decorateComposerJson($composerJson);

$this->symfonyStyle->writeln(sprintf(
' * loading "<fg=green>%s</>" repository from cache',
$repository->getComposerJson()
->getRepositoryName()
));

continue;
}
if ($clearCache) {
// clear old files
FileSystem::delete($repositoryCacheDirectory);
}

$this->symfonyStyle->writeln(sprintf(
' * loading files from "<fg=green>%s</>" remote repository',
$repository->getRepositoryUrl()
));
if ($this->hasDirectorySomeFiles($repositoryCacheDirectory)) {
$this->symfonyStyle->writeln(sprintf(
' * loading "<fg=green>%s</>" repository from cache',
$repository->getRepositoryUrl()
));
} else {
$this->symfonyStyle->writeln(sprintf(
' * loading files from "<fg=green>%s</>" remote repository',
$repository->getRepositoryUrl()
));

$this->downloadRepository($repository, $repositoryCacheDirectory, $isDebug);
}

$rootFiles = $this->loadRootFilesFromRepository($repository, $repositoryCacheDirectory);
$rootFiles = $this->findRootFilesInDirectory($repositoryCacheDirectory);
$repository->decorateRootFiles($rootFiles);

$composerJsonFile = $this->matchComposerJsonFile($rootFiles, $repository);
Expand All @@ -77,32 +71,43 @@ private function findRootFilesInDirectory(string $repositoryCacheDirectory): arr
return iterator_to_array($repositoryRootFilesFinder->getIterator());
}

/**
* @return SplFileInfo[]
*/
private function loadRootFilesFromRepository(Repository $repository, string $repositoryCacheDirectory): array
private function downloadRepository(Repository $repository, string $repositoryCacheDirectory, bool $isDebug): void
{
FileSystem::createDir($repositoryCacheDirectory);

Process::fromShellCommandline('git init', $repositoryCacheDirectory)
->mustRun();
$process = Process::fromShellCommandline('git init', $repositoryCacheDirectory);
if ($isDebug) {
$this->symfonyStyle->writeln('Running command: ' . $process->getCommandLine());
}

Process::fromShellCommandline(
$process->mustRun();
if ($isDebug) {
$this->symfonyStyle->writeln('Cloning repository:' . PHP_EOL . $process->getOutput());
}

$process = Process::fromShellCommandline(
'git remote add origin ' . $repository->getClonableRepositoryUrl(),
$repositoryCacheDirectory
)->mustRun();
);

if ($isDebug) {
$this->symfonyStyle->writeln('Running command: ' . $process->getCommandLine());
}

$process->mustRun();
if ($isDebug) {
$this->symfonyStyle->writeln('Cloning repository:' . PHP_EOL . $process->getOutput());
}

// might be a bit longer for large repositories
Process::fromShellCommandline('git fetch origin --depth 1', $repositoryCacheDirectory, timeout: 120)
->mustRun();

// Fetch the latest changes from the remote repository
Process::fromShellCommandline(
sprintf('git checkout %s', $repository->getBranch() ?: 'FETCH_HEAD'),
sprintf('git checkout %s', $repository->getBranch() ?: 'main'),
$repositoryCacheDirectory
)->mustRun();

return $this->findRootFilesInDirectory($repositoryCacheDirectory);
}

/**
Expand All @@ -126,8 +131,11 @@ private function matchComposerJsonFile(array $rootFiles, Repository $repository)

private function hasDirectorySomeFiles(string $repositoryCacheDirectory): bool
{
$fileCountInDirectory = Finder::create()->in($repositoryCacheDirectory)->files()->count();
if (! is_dir($repositoryCacheDirectory)) {
return false;
}

$fileCountInDirectory = Finder::create()->in($repositoryCacheDirectory)->files()->count();
return $fileCountInDirectory > 0;
}
}
18 changes: 11 additions & 7 deletions src/Matrix/Command/MatrixCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ protected function configure(): void
InputOption::VALUE_NONE,
'Remove cached repositories composer.json files'
);

// add --debug option
$this->addOption('debug', null, InputOption::VALUE_NONE, 'Enable debug mode for more verbose output');
}

protected function execute(InputInterface $input, OutputInterface $output): int
Expand All @@ -54,24 +57,26 @@ protected function execute(InputInterface $input, OutputInterface $output): int

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

$this->symfonyStyle->title('Showing Dependency Matrix');
$this->symfonyStyle->title('Composer Dependency Matrix');

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

$this->symfonyStyle->newLine(2);

$repositoryCollection = $monitorConfig->getRepositoryCollection();
$requiredPackageNames = $monitorConfig->getComposerRequiredPackageNames();

$tableHeadlines = array_merge(['dependency'], $repositoryCollection->getRepositoryNames());
$tableHeadlines = array_merge(['dependency'], $repositoryCollection->getRepositoryNamesByPackageCount());
$tableRows = $this->createTableRows($requiredPackageNames, $repositoryCollection);

$this->renderTable($tableHeadlines, $tableRows);

$this->symfonyStyle->newLine();

return self::SUCCESS;
}

Expand All @@ -93,7 +98,6 @@ private function createTableRows(array $requiredPackageNames, RepositoryCollecti

$dataRow = [];
foreach ($repositoryCollection->allSorterByPackageCount() as $composerJson) {

$packageVersion = $composerJson->getPackageVersion($requiredPackageName);
if ($packageVersion !== null) {
++$knownValuesCount;
Expand All @@ -112,7 +116,7 @@ private function createTableRows(array $requiredPackageNames, RepositoryCollecti
}

$dataRow = SymfonyColumnStyler::styleHighsAndLows($dataRow);
$shortRequiredPackageName = Strings::truncate($requiredPackageName, 25);
$shortRequiredPackageName = Strings::truncate($requiredPackageName, 35);

$tableRow = array_merge([$shortRequiredPackageName], $dataRow);
$tableRows[] = $tableRow;
Expand Down
14 changes: 13 additions & 1 deletion src/ValueObject/RepositoryCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ public function getComposerRequiredPackageNames(): array
$requiredPackageNames = [];
foreach ($this->repositories as $repository) {
$composerJson = $repository->getComposerJson();

$requiredPackageNames = array_merge($requiredPackageNames, $composerJson->getRequiredPackageNames());
}

Expand All @@ -49,6 +48,19 @@ public function getRepositoryNames(): array
return $repositoryNames;
}

/**
* @return string[]
*/
public function getRepositoryNamesByPackageCount(): array
{
$repositoryNames = [];
foreach ($this->allSorterByPackageCount() as $composerJson) {
$repositoryNames[] = $composerJson->getRepositoryName();
}

return $repositoryNames;
}

public function count(): int
{
return count($this->repositories);
Expand Down