Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add release date to show command, and also release date for latest package in --latest/outdated mode #11762

Merged
merged 3 commits into from
Dec 20, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/03-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@ php composer.phar show monolog/monolog 1.0.2
* **--major-only (-M):** Use with --latest or --outdated. Only shows packages that have major SemVer-compatible updates.
* **--minor-only (-m):** Use with --latest or --outdated. Only shows packages that have minor SemVer-compatible updates.
* **--patch-only:** Use with --latest or --outdated. Only shows packages that have patch-level SemVer-compatible updates.
* **--sort-by-age (-A):** Sort by and display current release age. Use with the --latest or --outdated option.
Seldaek marked this conversation as resolved.
Show resolved Hide resolved
* **--direct (-D):** Restricts the list of packages to your direct dependencies.
* **--strict:** Return a non-zero exit code when there are outdated packages.
* **--format (-f):** Lets you pick between text (default) or json output format.
Expand Down Expand Up @@ -589,6 +590,7 @@ The color coding is as such:
* **--major-only (-M):** Only shows packages that have major SemVer-compatible updates.
* **--minor-only (-m):** Only shows packages that have minor SemVer-compatible updates.
* **--patch-only (-p):** Only shows packages that have patch-level SemVer-compatible updates.
* **--sort-by-age (-A):** Sort by and display current release age.
Seldaek marked this conversation as resolved.
Show resolved Hide resolved
* **--format (-f):** Lets you pick between text (default) or json output format.
* **--no-dev:** Do not show outdated dev dependencies.
* **--locked:** Shows updates for packages from the lock file, regardless of what is currently in vendor dir.
Expand Down
4 changes: 4 additions & 0 deletions src/Composer/Command/OutdatedCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ protected function configure(): void
new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates.'),
new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates.'),
new InputOption('patch-only', 'p', InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates.'),
new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Sort by and display current release age.'),
Seldaek marked this conversation as resolved.
Show resolved Hide resolved
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
new InputOption('ignore', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Ignore specified package(s). Use it if you don\'t want to be informed about new versions of some packages.', null, $this->suggestInstalledPackage(false)),
new InputOption('no-dev', null, InputOption::VALUE_NONE, 'Disables search in require-dev packages.'),
Expand Down Expand Up @@ -97,6 +98,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
if ($input->getOption('no-dev')) {
$args['--no-dev'] = true;
}
if ($input->getOption('sort-by-age')) {
$args['--sort-by-age'] = true;
}
$args['--ignore-platform-req'] = $input->getOption('ignore-platform-req');
if ($input->getOption('ignore-platform-reqs')) {
$args['--ignore-platform-reqs'] = true;
Expand Down
92 changes: 80 additions & 12 deletions src/Composer/Command/ShowCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ protected function configure()
new InputOption('major-only', 'M', InputOption::VALUE_NONE, 'Show only packages that have major SemVer-compatible updates. Use with the --latest or --outdated option.'),
new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --latest or --outdated option.'),
new InputOption('patch-only', null, InputOption::VALUE_NONE, 'Show only packages that have patch SemVer-compatible updates. Use with the --latest or --outdated option.'),
new InputOption('sort-by-age', 'A', InputOption::VALUE_NONE, 'Sort by and display current release age. Use with the --latest or --outdated option.'),
Seldaek marked this conversation as resolved.
Show resolved Hide resolved
new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text', ['json', 'text']),
Expand Down Expand Up @@ -450,7 +451,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
if (isset($packages[$type])) {
ksort($packages[$type]);

$nameLength = $versionLength = $latestLength = 0;
$nameLength = $versionLength = $latestLength = $releaseDateLength = 0;

if ($showLatest && $showVersion) {
foreach ($packages[$type] as $package) {
Expand All @@ -469,9 +470,20 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion;
$writeLatest = $writeVersion && $showLatest;
$writeDescription = !$input->getOption('name-only') && !$input->getOption('path');
$writeReleaseDate = $writeLatest && $input->getOption('sort-by-age');

$hasOutdatedPackages = false;

if ($input->getOption('sort-by-age')) {
usort($packages[$type], function ($a, $b) {
if (is_object($a) && is_object($b)) {
return $a->getReleaseDate() <=> $b->getReleaseDate();
}

return 0;
naderman marked this conversation as resolved.
Show resolved Hide resolved
});
}

$viewData[$type] = [];
foreach ($packages[$type] as $package) {
$packageViewData = [];
Expand Down Expand Up @@ -505,6 +517,17 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$packageViewData['version'] = $package->getFullPrettyVersion();
$versionLength = max($versionLength, strlen($package->getFullPrettyVersion()));
}
if ($writeReleaseDate) {
if ($package->getReleaseDate() !== null) {
$packageViewData['release-age'] = str_replace(' ago', ' old', $this->getRelativeTime($package->getReleaseDate()));
if (!str_contains($packageViewData['release-age'], ' old')) {
$packageViewData['release-age'] = 'from '.$packageViewData['release-age'];
}
$releaseDateLength = max($releaseDateLength, strlen($packageViewData['release-age']));
} else {
$packageViewData['release-age'] = '';
}
}
if ($writeLatest && $latestPackage) {
$packageViewData['latest'] = $latestPackage->getFullPrettyVersion();
$packageViewData['latest-status'] = $this->getUpdateStatus($latestPackage, $package);
Expand Down Expand Up @@ -552,7 +575,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int
'nameLength' => $nameLength,
'versionLength' => $versionLength,
'latestLength' => $latestLength,
'releaseDateLength' => $releaseDateLength,
'writeLatest' => $writeLatest,
'writeReleaseDate' => $writeReleaseDate,
];
if ($input->getOption('strict') && $hasOutdatedPackages) {
$exitCode = 1;
Expand Down Expand Up @@ -588,11 +613,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$nameLength = $viewMetaData[$type]['nameLength'];
$versionLength = $viewMetaData[$type]['versionLength'];
$latestLength = $viewMetaData[$type]['latestLength'];
$releaseDateLength = $viewMetaData[$type]['releaseDateLength'];
$writeLatest = $viewMetaData[$type]['writeLatest'];
$writeReleaseDate = $viewMetaData[$type]['writeReleaseDate'];

$versionFits = $nameLength + $versionLength + 3 <= $width;
$latestFits = $nameLength + $versionLength + $latestLength + 3 <= $width;
$descriptionFits = $nameLength + $versionLength + $latestLength + 24 <= $width;
$releaseDateFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 3 <= $width;
$descriptionFits = $nameLength + $versionLength + $latestLength + $releaseDateLength + 24 <= $width;

if ($latestFits && !$io->isDecorated()) {
$latestLength += 2;
Expand Down Expand Up @@ -620,22 +648,22 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$io->writeError('');
$io->writeError('<info>Direct dependencies required in composer.json:</>');
if (\count($directDeps) > 0) {
$this->printPackages($io, $directDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
$this->printPackages($io, $directDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength);
} else {
$io->writeError('Everything up to date');
}
$io->writeError('');
$io->writeError('<info>Transitive dependencies not required in composer.json:</>');
if (\count($transitiveDeps) > 0) {
$this->printPackages($io, $transitiveDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
$this->printPackages($io, $transitiveDeps, $indent, $writeVersion && $versionFits, $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength);
} else {
$io->writeError('Everything up to date');
}
} else {
if ($writeLatest && \count($packages) === 0) {
$io->writeError('All your direct dependencies are up to date');
} else {
$this->printPackages($io, $packages, $indent, $writeVersion && $versionFits, $writeLatest && $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength);
$this->printPackages($io, $packages, $indent, $writeVersion && $versionFits, $writeLatest && $latestFits, $writeDescription && $descriptionFits, $width, $versionLength, $nameLength, $latestLength, $writeReleaseDate && $releaseDateFits, $releaseDateLength);
}
}

Expand All @@ -651,11 +679,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int
/**
* @param array<array{name: string, direct-dependency?: bool, version?: string, latest?: string, latest-status?: string, description?: string|null, path?: string|null, source?: string|null, homepage?: string|null, warning?: string, abandoned?: bool|string}> $packages
*/
private function printPackages(IOInterface $io, array $packages, string $indent, bool $writeVersion, bool $writeLatest, bool $writeDescription, int $width, int $versionLength, int $nameLength, int $latestLength): void
private function printPackages(IOInterface $io, array $packages, string $indent, bool $writeVersion, bool $writeLatest, bool $writeDescription, int $width, int $versionLength, int $nameLength, int $latestLength, bool $writeReleaseDate, int $releaseDateLength): void
{
$padName = $writeVersion || $writeLatest || $writeDescription;
$padVersion = $writeLatest || $writeDescription;
$padLatest = $writeDescription;
$padName = $writeVersion || $writeLatest || $writeReleaseDate || $writeDescription;
$padVersion = $writeLatest || $writeReleaseDate || $writeDescription;
$padLatest = $writeDescription || $writeReleaseDate;
$padReleaseDate = $writeDescription;
foreach ($packages as $package) {
$link = $package['source'] ?? $package['homepage'] ?? '';
if ($link !== '') {
Expand All @@ -674,10 +703,13 @@ private function printPackages(IOInterface $io, array $packages, string $indent,
$latestVersion = str_replace(['up-to-date', 'semver-safe-update', 'update-possible'], ['=', '!', '~'], $updateStatus) . ' ' . $latestVersion;
}
$io->write(' <' . $style . '>' . str_pad($latestVersion, ($padLatest ? $latestLength : 0), ' ') . '</' . $style . '>', false);
if ($writeReleaseDate && isset($package['release-age'])) {
$io->write(' '.str_pad($package['release-age'], ($padReleaseDate ? $releaseDateLength : 0), ' '), false);
}
}
if (isset($package['description']) && $writeDescription) {
$description = strtok($package['description'], "\r\n");
$remaining = $width - $nameLength - $versionLength - 4;
$remaining = $width - $nameLength - $versionLength - $releaseDateLength - 4;
if ($writeLatest) {
$remaining -= $latestLength;
}
Expand Down Expand Up @@ -806,14 +838,20 @@ protected function printPackageInfo(CompletePackageInterface $package, array $ve
*/
protected function printMeta(CompletePackageInterface $package, array $versions, InstalledRepository $installedRepo, ?PackageInterface $latestPackage = null): void
{
$isInstalledPackage = !PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package);

$io = $this->getIO();
$io->write('<info>name</info> : ' . $package->getPrettyName());
$io->write('<info>descrip.</info> : ' . $package->getDescription());
$io->write('<info>keywords</info> : ' . implode(', ', $package->getKeywords() ?: []));
$this->printVersions($package, $versions, $installedRepo);
if ($isInstalledPackage && $package->getReleaseDate() !== null) {
$io->write('<info>released</info> : ' . $package->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($package->getReleaseDate()));
}
if ($latestPackage) {
$style = $this->getVersionStyle($latestPackage, $package);
$io->write('<info>latest</info> : <'.$style.'>' . $latestPackage->getPrettyVersion() . '</'.$style.'>');
$releasedTime = $latestPackage->getReleaseDate() === null ? '' : ' released ' . $latestPackage->getReleaseDate()->format('Y-m-d') . ', ' . $this->getRelativeTime($latestPackage->getReleaseDate());
$io->write('<info>latest</info> : <'.$style.'>' . $latestPackage->getPrettyVersion() . '</'.$style.'>' . $releasedTime);
} else {
$latestPackage = $package;
}
Expand All @@ -822,7 +860,7 @@ protected function printMeta(CompletePackageInterface $package, array $versions,
$io->write('<info>homepage</info> : ' . $package->getHomepage());
$io->write('<info>source</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getSourceType(), $package->getSourceUrl(), $package->getSourceReference()));
$io->write('<info>dist</info> : ' . sprintf('[%s] <comment>%s</comment> %s', $package->getDistType(), $package->getDistUrl(), $package->getDistReference()));
if (!PlatformRepository::isPlatformPackage($package->getName()) && $installedRepo->hasPackage($package)) {
if ($isInstalledPackage) {
$path = $this->requireComposer()->getInstallationManager()->getInstallPath($package);
if (is_string($path)) {
$io->write('<info>path</info> : ' . realpath($path));
Expand Down Expand Up @@ -993,6 +1031,10 @@ protected function printPackageInfoAsJson(CompletePackageInterface $package, arr
} else {
$json['path'] = null;
}

if ($package->getReleaseDate() !== null) {
$json['released'] = $package->getReleaseDate()->format(DATE_ATOM);
}
}

if ($latestPackage instanceof CompletePackageInterface && $latestPackage->isAbandoned()) {
Expand Down Expand Up @@ -1447,4 +1489,30 @@ private function getRepositorySet(Composer $composer): RepositorySet

return $this->repositorySet;
}

private function getRelativeTime(\DateTimeInterface $releaseDate): string
{
if ($releaseDate->format('Ymd') === date('Ymd')) {
return 'today';
}

$diff = $releaseDate->diff(new \DateTimeImmutable());
if ($diff->days < 7) {
return 'this week';
}

if ($diff->days < 14) {
return 'last week';
}

if ($diff->m < 1 && $diff->days < 31) {
return floor($diff->days / 7) . ' weeks ago';
Seldaek marked this conversation as resolved.
Show resolved Hide resolved
}

if ($diff->y < 1) {
return $diff->m . ' month' . ($diff->m > 1 ? 's' : '') . ' ago';
}

return $diff->y . ' year' . ($diff->y > 1 ? 's' : '') . ' ago';
}
}