ComposerCheckCommands.php 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. <?php
  2. declare(strict_types=1);
  3. namespace Drush\Commands\composer_check;
  4. use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
  5. use Drush\Commands\DrushCommands;
  6. use Symfony\Component\Yaml\Yaml;
  7. /**
  8. * MagpjmCommands provides Drush 9 commands for the MAGPJ run time module.
  9. */
  10. class ComposerCheckCommands extends DrushCommands {
  11. /**
  12. * The serialization.yaml service.
  13. *
  14. * @var \Symfony\Component\Yaml
  15. */
  16. private $yaml;
  17. public function __construct() {
  18. $this->yaml = new Yaml();
  19. }
  20. /**
  21. * Command callback for composer-check.
  22. *
  23. * @param null|string $lockPath
  24. * Optional. The path to a composer.lock file.
  25. */
  26. /**
  27. * Lists the packages requested in composer.json and the matching locked
  28. * version.
  29. *
  30. * @param ?string $lockPath
  31. *
  32. * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
  33. * The list of package versions, unless YAML encoding is used.
  34. *
  35. * @field-labels
  36. * name: Name
  37. * kind: Kind
  38. * req: Requirement
  39. * ver: Version
  40. *
  41. * @command composer:check
  42. * @aliases cch
  43. *
  44. * @option all List all locked packages, even those not requested
  45. * @option yaml Produce YAML output instead of a table
  46. *
  47. * @throws \Exception
  48. */
  49. public function composerCheck(string $lockPath = NULL) {
  50. $lockPath = $this->validateLockPath($lockPath);
  51. $jsonPath = $this->validateJsonPath($lockPath);
  52. ['run' => $jrPack, 'dev' => $jdPack] = $this->decodeJsonPackages($jsonPath);
  53. ['run' => $lrPack, 'dev' => $ldPack] = $this->decodeLockPackages($lockPath);
  54. $all = !!$this->input()->getOption('all');
  55. $yaml = !!$this->input()->getOption('yaml');
  56. $packages = ['run' => [], 'dev' => []];
  57. foreach ($jrPack as $package => $requirement) {
  58. if ($all || !empty($requirement)) {
  59. $package = mb_strtolower($package);
  60. $packages['run'][$package]['requirement'] = $requirement;
  61. }
  62. }
  63. foreach ($jdPack as $package => $requirement) {
  64. if ($all || !empty($requirement)) {
  65. $package = mb_strtolower($package);
  66. $packages['dev'][$package]['requirement'] = $requirement;
  67. }
  68. }
  69. foreach ($lrPack as $packageInfo) {
  70. $package = mb_strtolower($packageInfo['name']);
  71. if ($all || !empty($packages['run'][$package])) {
  72. $version = $packageInfo['version'];
  73. $packages['run'][$package]['version'] = $version;
  74. }
  75. }
  76. foreach ($ldPack as $packageInfo) {
  77. $package = mb_strtolower($packageInfo['name']);
  78. if ($all || !empty($packages['dev'][$package])) {
  79. $version = $packageInfo['version'];
  80. $packages['dev'][$package]['version'] = $version;
  81. }
  82. }
  83. ksort($packages['dev']);
  84. ksort($packages['run']);
  85. if ($yaml) {
  86. $this->output()->writeln($this->yaml->dump($packages, 3, 2));
  87. return NULL;
  88. }
  89. return $this->humanOutput($packages);
  90. }
  91. /**
  92. * Display a package comparison as a text table.
  93. *
  94. * @param array $packages
  95. * A package comparison array.
  96. *
  97. * @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
  98. *
  99. */
  100. protected function humanOutput($packages): RowsOfFields {
  101. //$header = ['Name', 'Kind', 'Requirement', 'Version'];
  102. $rows = [];
  103. foreach ($packages as $kind => $kindPackages) {
  104. foreach ($kindPackages as $package => $info) {
  105. $rows["$package/$kind"] = [
  106. 'name' => $package,
  107. 'kind' => $kind,
  108. 'req' => $info['requirement'] ?? '',
  109. 'ver' => $info['version'] ?? '',
  110. ];
  111. }
  112. }
  113. ksort($rows);
  114. return new RowsOfFields($rows);
  115. }
  116. /**
  117. * @param string $jsonPath
  118. *
  119. * @return array
  120. */
  121. protected function decodeJsonPackages(string $jsonPath): array {
  122. $json = json_decode(file_get_contents($jsonPath), TRUE);
  123. $jsonPackages = $json['require'] ?? [];
  124. $jsonDevPackages = $json['require-dev'] ?? [];
  125. return ['run' => $jsonPackages, 'dev' => $jsonDevPackages];
  126. }
  127. protected function decodeLockPackages(string $lockPath): array {
  128. $file = json_decode(file_get_contents($lockPath), TRUE);
  129. $run = $file['packages'];
  130. $dev = $file['packages-dev'];
  131. $platform = $file['platform'];
  132. array_walk($platform, function (&$requirement, $component) {
  133. $requirement = [
  134. 'name' => $component,
  135. 'version' => $requirement,
  136. ];
  137. });
  138. $run = array_merge($run, $platform);
  139. $dev = array_merge($dev, $platform);
  140. return ['run' => $run, 'dev' => $dev];
  141. }
  142. /**
  143. * @param string $lockPath The lockPath command argument.
  144. *
  145. * @return string
  146. *
  147. * @throws \Exception
  148. */
  149. protected function validateJsonPath(string $lockPath): string {
  150. $jsonPath = dirname($lockPath) . '/composer.json';
  151. if (!is_file($jsonPath) && is_readable($jsonPath)) {
  152. throw new \Exception("Cannot read composer.json file");
  153. }
  154. return $jsonPath;
  155. }
  156. /**
  157. * @param string|null $lockPath The lockPath optional command argument.
  158. *
  159. * @return string|null The defaulted lock path.
  160. *
  161. * @throws \Exception If the defaulted lock path cannot be read.
  162. */
  163. protected function validateLockPath(?string $lockPath): ?string {
  164. if (empty($lockPath)) {
  165. $lockPath = dirname(DRUPAL_ROOT) . '/composer.lock';
  166. }
  167. if (!is_file($lockPath) && is_readable($lockPath)) {
  168. throw new \Exception("Cannot read composer.lock file");
  169. }
  170. return $lockPath;
  171. }
  172. }