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
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
/.github export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.php_cs.dist export-ignore
.php-cs-fixer.dist.php export-ignore
phpunit.xml.dist export-ignore
infection.json.dist export-ignore
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@ composer diff -p # include platform dependencies
composer diff -f json # Output as JSON instead of table
```

### Strict mode

To help you control your dependencies, you may pass `--strict` option when running in CI. If there are any changes detected, a non-zero exit code will be returned.

Exit code of the command is built using following bit flags:

* `0` - OK.
* `1` - General error.
* `2` - There were changes in prod packages.
* `4` - There were changes is dev packages.
* `8` - There were downgrades in prod packages.
* `16` - There were downgrades in dev packages.

You may check for individual flags or simply check if the status is greater or equal 8 if you don't want to downgrade any package.

# Similar packages

While there are several existing packages offering similar functionality:
Expand Down
99 changes: 83 additions & 16 deletions src/Command/DiffCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace IonBazan\ComposerDiff\Command;

use Composer\Command\BaseCommand;
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Operation\UpdateOperation;
use IonBazan\ComposerDiff\Formatter\Formatter;
use IonBazan\ComposerDiff\Formatter\JsonFormatter;
use IonBazan\ComposerDiff\Formatter\MarkdownListFormatter;
Expand All @@ -16,6 +18,10 @@

class DiffCommand extends BaseCommand
{
const CHANGES_PROD = 2;
const CHANGES_DEV = 4;
const DOWNGRADES_PROD = 8;
const DOWNGRADES_DEV = 16;
/**
* @var PackageDiff
*/
Expand Down Expand Up @@ -54,43 +60,61 @@ protected function configure()
->addOption('with-links', 'l', InputOption::VALUE_NONE, 'Include compare/release URLs')
->addOption('format', 'f', InputOption::VALUE_REQUIRED, 'Output format (mdtable, mdlist, json)', 'mdtable')
->addOption('gitlab-domains', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Extra Gitlab domains (inherited from Composer config by default)', array())
->addOption('strict', 's', InputOption::VALUE_NONE, 'Return non-zero exit code if there are any changes')
->setHelp(<<<'EOF'
The <comment>%command.name%</comment> command displays all dependency changes between two <info>composer.lock</info> files.
By default, it will compare current filesystem changes with git <info>HEAD</info>:
The <info>%command.name%</info> command displays all dependency changes between two <comment>composer.lock</comment> files.

<comment>%command.full_name%</comment>
By default, it will compare current filesystem changes with git <comment>HEAD</comment>:

<info>%command.full_name%</info>

To compare with specific branch, pass its name as argument:

<comment>%command.full_name% master</comment>
<info>%command.full_name% master</info>

You can specify any valid git refs to compare with:

<comment>%command.full_name% HEAD~3 be4aabc</comment>
<info>%command.full_name% HEAD~3 be4aabc</info>

You can also use more verbose syntax for <info>base</info> and <info>target</info> options:

<comment>%command.full_name% --base master --target composer.lock</comment>
<info>%command.full_name% --base master --target composer.lock</info>

To compare files in specific path, use following syntax:

<comment>%command.full_name% master:subdirectory/composer.lock /path/to/another/composer.lock</comment>
<info>%command.full_name% master:subdirectory/composer.lock /path/to/another/composer.lock</info>

By default, <info>platform</info> dependencies are hidden. Add <comment>--with-platform</comment> option to include them in the report:
By default, <info>platform</info> dependencies are hidden. Add <info>--with-platform</info> option to include them in the report:

<comment>%command.full_name% --with-platform</comment>
<info>%command.full_name% --with-platform</info>

Use <comment>--with-links</comment> to include release and compare URLs in the report:
Use <info>--with-links</info> to include release and compare URLs in the report:

<comment>%command.full_name% --with-links</comment>
<info>%command.full_name% --with-links</info>

You can customize output format by specifying it with <comment>--format</comment> option. Choose between <info>mdtable</info>, <info>mdlist</info> and <info>json</info>:
You can customize output format by specifying it with <info>--format</info> option. Choose between <comment>mdtable</comment>, <comment>mdlist</comment> and <comment>json</comment>:

<info>%command.full_name% --format=json</info>

Hide <info>dev</info> dependencies using <info>--no-dev</info> option:

<info>%command.full_name% --no-dev</info>

Passing <info>--strict</info> option may help you to disallow changes or downgrades by returning non-zero exit code:

<info>%command.full_name% --strict</info>

<comment>%command.full_name% --format=json</comment>
Exit code
---------

Hide <info>dev</info> dependencies using <comment>--no-dev</comment> option:
Exit code of the command is built using following bit flags:

<comment>%command.full_name% --no-dev</comment>
* 0 - OK.
* 1 - General error.
* 2 - There were changes in prod packages.
* 4 - There were changes is dev packages.
* 8 - There were downgrades in prod packages.
* 16 - There were downgrades in dev packages.
EOF
)
;
Expand Down Expand Up @@ -122,7 +146,50 @@ protected function execute(InputInterface $input, OutputInterface $output)

$formatter->render($prodOperations, $devOperations, $withUrls);

return 0;
return $input->getOption('strict') ? $this->getExitCode($prodOperations, $devOperations) : 0;
}

/**
* @param OperationInterface[] $prodOperations
* @param OperationInterface[] $devOperations
*
* @return int Exit code
*/
private function getExitCode(array $prodOperations, array $devOperations)
{
$exitCode = 0;

if (!empty($prodOperations)) {
$exitCode = self::CHANGES_PROD;

if ($this->hasDowngrades($prodOperations)) {
$exitCode |= self::DOWNGRADES_PROD;
}
}

if (!empty($devOperations)) {
$exitCode |= self::CHANGES_DEV;

if ($this->hasDowngrades($devOperations)) {
$exitCode |= self::DOWNGRADES_DEV;
}
}

return $exitCode;
}

/**
* @param OperationInterface[] $operations
*
* @return bool
*/
private function hasDowngrades(array $operations)
{
$downgrades = array_filter($operations, function (OperationInterface $operation) {
return $operation instanceof UpdateOperation && !PackageDiff::isUpgrade($operation);
});

return !empty($downgrades);
}

/**
Expand Down
20 changes: 0 additions & 20 deletions src/Formatter/AbstractFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,8 @@
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use Composer\Package\PackageInterface;
use Composer\Semver\Semver;
use Composer\Semver\VersionParser;
use IonBazan\ComposerDiff\Url\GeneratorContainer;
use Symfony\Component\Console\Output\OutputInterface;
use UnexpectedValueException;

abstract class AbstractFormatter implements Formatter
{
Expand Down Expand Up @@ -74,21 +71,4 @@ private function getReleaseUrl(PackageInterface $package)

return $generator->getReleaseUrl($package);
}

/**
* @return bool
*/
protected static function isUpgrade(UpdateOperation $operation)
{
$versionParser = new VersionParser();
try {
$normalizedFrom = $versionParser->normalize($operation->getInitialPackage()->getVersion());
$normalizedTo = $versionParser->normalize($operation->getTargetPackage()->getVersion());
} catch (UnexpectedValueException $e) {
return true; // Consider as upgrade if versions are not parsable
}
$sorted = Semver::sort(array($normalizedTo, $normalizedFrom));

return $sorted[0] === $normalizedFrom;
}
}
3 changes: 2 additions & 1 deletion src/Formatter/JsonFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use IonBazan\ComposerDiff\PackageDiff;

class JsonFormatter extends AbstractFormatter
{
Expand Down Expand Up @@ -78,7 +79,7 @@ private function transformOperation(OperationInterface $operation)
if ($operation instanceof UpdateOperation) {
return array(
'name' => $operation->getInitialPackage()->getName(),
'operation' => self::isUpgrade($operation) ? 'upgrade' : 'downgrade',
'operation' => PackageDiff::isUpgrade($operation) ? 'upgrade' : 'downgrade',
'version_base' => $operation->getInitialPackage()->getFullPrettyVersion(),
'version_target' => $operation->getTargetPackage()->getFullPrettyVersion(),
);
Expand Down
3 changes: 2 additions & 1 deletion src/Formatter/MarkdownListFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Composer\DependencyResolver\Operation\OperationInterface;
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use IonBazan\ComposerDiff\PackageDiff;

class MarkdownListFormatter extends MarkdownFormatter
{
Expand Down Expand Up @@ -58,7 +59,7 @@ private function getRow(OperationInterface $operation, $withUrls)
}

if ($operation instanceof UpdateOperation) {
$isUpgrade = self::isUpgrade($operation);
$isUpgrade = PackageDiff::isUpgrade($operation);

return sprintf(
' - %s <fg=green>%s</> (<fg=yellow>%s</> => <fg=yellow>%s</>)%s',
Expand Down
3 changes: 2 additions & 1 deletion src/Formatter/MarkdownTableFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Composer\DependencyResolver\Operation\UninstallOperation;
use Composer\DependencyResolver\Operation\UpdateOperation;
use IonBazan\ComposerDiff\Formatter\Helper\Table;
use IonBazan\ComposerDiff\PackageDiff;

class MarkdownTableFormatter extends MarkdownFormatter
{
Expand Down Expand Up @@ -68,7 +69,7 @@ private function getTableRow(OperationInterface $operation)
if ($operation instanceof UpdateOperation) {
return array(
$operation->getInitialPackage()->getName(),
self::isUpgrade($operation) ? '<fg=cyan>Upgraded</>' : '<fg=yellow>Downgraded</>',
PackageDiff::isUpgrade($operation) ? '<fg=cyan>Upgraded</>' : '<fg=yellow>Downgraded</>',
$operation->getInitialPackage()->getFullPrettyVersion(),
$operation->getTargetPackage()->getFullPrettyVersion(),
);
Expand Down
20 changes: 20 additions & 0 deletions src/PackageDiff.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
use Composer\Package\CompletePackage;
use Composer\Package\Loader\ArrayLoader;
use Composer\Repository\ArrayRepository;
use Composer\Semver\Semver;
use Composer\Semver\VersionParser;
use UnexpectedValueException;

class PackageDiff
{
Expand Down Expand Up @@ -50,6 +53,23 @@ public function getPackageDiff($from, $to, $dev, $withPlatform)
return $operations;
}

/**
* @return bool
*/
public static function isUpgrade(UpdateOperation $operation)
{
$versionParser = new VersionParser();
try {
$normalizedFrom = $versionParser->normalize($operation->getInitialPackage()->getVersion());
$normalizedTo = $versionParser->normalize($operation->getTargetPackage()->getVersion());
} catch (UnexpectedValueException $e) {
return true; // Consider as upgrade if versions are not parsable
}
$sorted = Semver::sort(array($normalizedTo, $normalizedFrom));

return $sorted[0] === $normalizedFrom;
}

/**
* @param string $path
* @param bool $dev
Expand Down
Loading