Skip to content

Commit

Permalink
Allow specifying direct dependencies (#40)
Browse files Browse the repository at this point in the history
  • Loading branch information
IonBazan authored Feb 6, 2025
1 parent b337dc8 commit 623a9a0
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 37 deletions.
7 changes: 4 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
- '8.2'
- '8.3'
- '8.4'
- '8.5'
include:
- php-versions: '5.3'
dependencies: 'lowest'
Expand Down Expand Up @@ -52,7 +53,7 @@ jobs:
with:
dependency-versions: ${{ matrix.dependencies }}
- name: Check PSR-4 mapping
if: ${{ matrix.php-versions == 8.3 && matrix.composer == 'v2' }}
if: ${{ matrix.php-versions == 8.4 && matrix.composer == 'v2' }}
run: composer dump-autoload --dev --optimize --strict-psr
- name: Set default branch for tests
run: git config --global init.defaultBranch main
Expand All @@ -63,15 +64,15 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Run mutation tests
if: ${{ matrix.php-versions == 8.3 && matrix.operating-system == 'ubuntu-latest' }}
if: ${{ matrix.php-versions == 8.4 && matrix.operating-system == 'ubuntu-latest' }}
env:
STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }}
run: |
composer config --no-plugins allow-plugins.infection/extension-installer true
composer require infection/infection --update-with-all-dependencies
vendor/bin/infection --ignore-msi-with-no-mutations --min-covered-msi=100 --min-msi=100 -s -j4 --only-covered
- name: Run phpstan
if: ${{ matrix.php-versions == 8.3 && matrix.operating-system == 'ubuntu-latest' }}
if: ${{ matrix.php-versions == 8.4 && matrix.operating-system == 'ubuntu-latest' }}
run: |
composer require phpstan/phpstan --dev
vendor/bin/phpstan
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ composer diff --help # Display detailed usage instructions
- `--target` (`-t`) - path, URL or git ref to modified `composer.lock` file
- `--no-dev` - ignore dev dependencies (`require-dev`)
- `--no-prod` - ignore prod dependencies (`require`)
- `--direct` (`-D`) - only show direct dependencies
- `--with-platform` (`-p`) - include platform dependencies (PHP, extensions, etc.)
- `--with-links` (`-l`) - include compare/release URLs
- `--with-licenses` (`-c`) - include license information
Expand Down
10 changes: 8 additions & 2 deletions src/Command/DiffCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ protected function configure()
->addOption('target', 't', InputOption::VALUE_REQUIRED, 'Target (modified) composer.lock file path or git ref', 'composer.lock')
->addOption('no-dev', null, InputOption::VALUE_NONE, 'Ignore dev dependencies')
->addOption('no-prod', null, InputOption::VALUE_NONE, 'Ignore prod dependencies')
->addOption('direct', 'D', InputOption::VALUE_NONE, 'Restricts the list of packages to your direct dependencies')
->addOption('with-platform', 'p', InputOption::VALUE_NONE, 'Include platform dependencies (PHP version, extensions, etc.)')
->addOption('with-links', 'l', InputOption::VALUE_NONE, 'Include compare/release URLs')
->addOption('with-licenses', 'c', InputOption::VALUE_NONE, 'Include licenses')
Expand Down Expand Up @@ -95,6 +96,10 @@ protected function configure()
By default, <info>platform</info> dependencies are hidden. Add <info>--with-platform</info> option to include them in the report:
<info>%command.full_name% --with-platform</info>
By default, <info>transient</info> dependencies are displayed. Add <info>--direct</info> option to only show direct dependencies:
<info>%command.full_name% --direct</info>
Use <info>--with-links</info> to include release and compare URLs in the report:
Expand Down Expand Up @@ -135,6 +140,7 @@ protected function handle(InputInterface $input, OutputInterface $output)
{
$base = null !== $input->getArgument('base') ? $input->getArgument('base') : $input->getOption('base');
$target = null !== $input->getArgument('target') ? $input->getArgument('target') : $input->getOption('target');
$onlyDirect = $input->getOption('direct');
$withPlatform = $input->getOption('with-platform');
$withUrls = $input->getOption('with-links');
$withLicenses = $input->getOption('with-licenses');
Expand All @@ -148,11 +154,11 @@ protected function handle(InputInterface $input, OutputInterface $output)
$devOperations = new DiffEntries(array());

if (!$input->getOption('no-prod')) {
$prodOperations = $this->packageDiff->getPackageDiff($base, $target, false, $withPlatform);
$prodOperations = $this->packageDiff->getPackageDiff($base, $target, false, $withPlatform, $onlyDirect);
}

if (!$input->getOption('no-dev')) {
$devOperations = $this->packageDiff->getPackageDiff($base, $target, true, $withPlatform);
$devOperations = $this->packageDiff->getPackageDiff($base, $target, true, $withPlatform, $onlyDirect);
}

$formatter->render($prodOperations, $devOperations, $withUrls, $withLicenses);
Expand Down
17 changes: 16 additions & 1 deletion src/Diff/DiffEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,19 @@ class DiffEntry
/** @var OperationInterface */
private $operation;

/** @var bool */
private $direct;

/** @var string */
private $type;

public function __construct(OperationInterface $operation)
/**
* @param bool $direct
*/
public function __construct(OperationInterface $operation, $direct = false)
{
$this->operation = $operation;
$this->direct = $direct;
$this->type = $this->determineType();
}

Expand All @@ -44,6 +51,14 @@ public function getType()
return $this->type;
}

/**
* @return bool
*/
public function isDirect()
{
return $this->direct;
}

/**
* @return bool
*/
Expand Down
7 changes: 5 additions & 2 deletions src/Formatter/JsonFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ private function format(array $data)
* @param bool $withUrls
* @param bool $withLicenses
*
* @return array<array<string, string|null>>
* @return array<array<string, mixed>>
*/
private function transformEntries(DiffEntries $entries, $withUrls, $withLicenses)
{
Expand All @@ -69,7 +69,7 @@ private function transformEntries(DiffEntries $entries, $withUrls, $withLicenses
}

/**
* @return array<string, string|null>
* @return array<string, string|bool|null>
*/
private function transformEntry(DiffEntry $entry)
{
Expand All @@ -78,6 +78,7 @@ private function transformEntry(DiffEntry $entry)
if ($operation instanceof InstallOperation) {
return array(
'name' => $operation->getPackage()->getName(),
'direct' => $entry->isDirect(),
'operation' => $entry->getType(),
'version_base' => null,
'version_target' => $operation->getPackage()->getFullPrettyVersion(),
Expand All @@ -87,6 +88,7 @@ private function transformEntry(DiffEntry $entry)
if ($operation instanceof UpdateOperation) {
return array(
'name' => $operation->getInitialPackage()->getName(),
'direct' => $entry->isDirect(),
'operation' => $entry->getType(),
'version_base' => $operation->getInitialPackage()->getFullPrettyVersion(),
'version_target' => $operation->getTargetPackage()->getFullPrettyVersion(),
Expand All @@ -96,6 +98,7 @@ private function transformEntry(DiffEntry $entry)
if ($operation instanceof UninstallOperation) {
return array(
'name' => $operation->getPackage()->getName(),
'direct' => $entry->isDirect(),
'operation' => $entry->getType(),
'version_base' => $operation->getPackage()->getFullPrettyVersion(),
'version_target' => null,
Expand Down
96 changes: 82 additions & 14 deletions src/PackageDiff.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,18 @@

class PackageDiff
{
const LOCKFILE = 'composer.lock';
const COMPOSER = 'composer';
const EXTENSION_LOCK = '.lock';
const EXTENSION_JSON = '.json';
const GIT_SEPARATOR = ':';

/**
* @param string[] $directPackages
* @param bool $onlyDirect
*
* @return DiffEntries
*/
public function getDiff(RepositoryInterface $oldPackages, RepositoryInterface $targetPackages)
public function getDiff(RepositoryInterface $oldPackages, RepositoryInterface $targetPackages, array $directPackages = array(), $onlyDirect = false)
{
$operations = array();

Expand Down Expand Up @@ -59,24 +65,37 @@ public function getDiff(RepositoryInterface $oldPackages, RepositoryInterface $t
}
}

return new DiffEntries(array_map(function (OperationInterface $operation) {
return new DiffEntry($operation);
}, $operations));
$entries = array_map(function (OperationInterface $operation) use ($directPackages) {
$package = $operation instanceof UpdateOperation ? $operation->getTargetPackage() : $operation->getPackage();

return new DiffEntry($operation, in_array($package->getName(), $directPackages, true));
}, $operations);

if ($onlyDirect) {
$entries = array_values(array_filter($entries, function (DiffEntry $entry) {
return $entry->isDirect();
}));
}

return new DiffEntries($entries);
}

/**
* @param string $from
* @param string $to
* @param bool $dev
* @param bool $withPlatform
* @param bool $onlyDirect
*
* @return DiffEntries
*/
public function getPackageDiff($from, $to, $dev, $withPlatform)
public function getPackageDiff($from, $to, $dev, $withPlatform, $onlyDirect = false)
{
return $this->getDiff(
$this->loadPackages($from, $dev, $withPlatform),
$this->loadPackages($to, $dev, $withPlatform)
$this->loadPackages($to, $dev, $withPlatform),
array_merge($this->getDirectPackages($from), $this->getDirectPackages($to)),
$onlyDirect
);
}

Expand Down Expand Up @@ -110,33 +129,82 @@ private function loadPackages($path, $dev, $withPlatform)
/**
* @param string $path
*
* @return string[]
*/
private function getDirectPackages($path)
{
$data = \json_decode($this->getFileContents($path, false), true);

$packages = array();

foreach (array('require', 'require-dev') as $key) {
if (isset($data[$key])) {
$packages = array_merge($packages, array_keys($data[$key]));
}
}

return $packages; // @phpstan-ignore return.type
}

/**
* @param string $path
* @param bool $lockFile
*
* @return string
*/
private function getFileContents($path)
private function getFileContents($path, $lockFile = true)
{
$originalPath = $path;

if (empty($path)) {
$path = self::LOCKFILE;
$path = self::COMPOSER.($lockFile ? self::EXTENSION_LOCK : self::EXTENSION_JSON);
}

if (filter_var($path, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED) || file_exists($path)) {
$localPath = $path;

if (!$lockFile) {
$localPath = $this->getJsonPath($localPath);
}

if (filter_var($localPath, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED) || file_exists($localPath)) {
// @phpstan-ignore return.type
return file_get_contents($path);
return file_get_contents($localPath);
}

if (false === strpos($originalPath, self::GIT_SEPARATOR)) {
$path .= self::GIT_SEPARATOR.self::COMPOSER.($lockFile ? self::EXTENSION_LOCK : self::EXTENSION_JSON);
}

if (false === strpos($originalPath, ':')) {
$path .= ':'.self::LOCKFILE;
if (!$lockFile) {
$path = $this->getJsonPath($path);
}

$output = array();
@exec(sprintf('git show %s 2>&1', escapeshellarg($path)), $output, $exit);
$outputString = implode("\n", $output);

if (0 !== $exit) {
throw new \RuntimeException(sprintf('Could not open file %s or find it in git as %s: %s', $originalPath, $path, $outputString));
if ($lockFile) {
throw new \RuntimeException(sprintf('Could not open file %s or find it in git as %s: %s', $originalPath, $path, $outputString));
}

return '{}'; // Do not throw exception for composer.json as it might not exist and that's fine
}

return $outputString;
}

/**
* @param string $path
*
* @return string
*/
private function getJsonPath($path)
{
if (self::EXTENSION_LOCK === substr($path, -strlen(self::EXTENSION_LOCK))) {
return substr($path, 0, -strlen(self::EXTENSION_LOCK)).self::EXTENSION_JSON;
}

return $path;
}
}
7 changes: 7 additions & 0 deletions tests/Command/DiffCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -207,42 +207,49 @@ public function outputDataProvider()
'packages' => array(
'a/package-1' => array(
'name' => 'a/package-1',
'direct' => false,
'operation' => 'install',
'version_base' => null,
'version_target' => '1.0.0',
),
'a/package-2' => array(
'name' => 'a/package-2',
'direct' => false,
'operation' => 'upgrade',
'version_base' => '1.0.0',
'version_target' => '1.2.0',
),
'a/package-3' => array(
'name' => 'a/package-3',
'direct' => false,
'operation' => 'remove',
'version_base' => '0.1.1',
'version_target' => null,
),
'a/package-4' => array(
'name' => 'a/package-4',
'direct' => false,
'operation' => 'remove',
'version_base' => '0.1.1',
'version_target' => null,
),
'a/package-5' => array(
'name' => 'a/package-5',
'direct' => false,
'operation' => 'remove',
'version_base' => '0.1.1',
'version_target' => null,
),
'a/package-6' => array(
'name' => 'a/package-6',
'direct' => false,
'operation' => 'remove',
'version_base' => '0.1.1',
'version_target' => null,
),
'a/package-7' => array(
'name' => 'a/package-7',
'direct' => false,
'operation' => 'downgrade',
'version_base' => '1.2.0',
'version_target' => '1.0.0',
Expand Down
Loading

0 comments on commit 623a9a0

Please sign in to comment.