Upgrade.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. <?php
  2. namespace Knuckles\Scribe\Commands;
  3. use Illuminate\Console\Command;
  4. use Knuckles\Camel\Camel;
  5. use Knuckles\Scribe\GroupedEndpoints\GroupedEndpointsFactory;
  6. use Knuckles\Scribe\Scribe;
  7. use Knuckles\Scribe\Tools\PathConfig;
  8. use Shalvah\Upgrader\Upgrader;
  9. use Symfony\Component\VarExporter\VarExporter;
  10. class Upgrade extends Command
  11. {
  12. protected $signature = "scribe:upgrade {--dry-run : Print the changes that will be made, without actually making them}
  13. {--config=scribe : choose which config file to use}
  14. ";
  15. protected $description = '';
  16. protected bool $applyChanges;
  17. protected string $configName;
  18. public function handle(): void
  19. {
  20. $this->applyChanges = !$this->option('dry-run');
  21. $this->configName = $this->option('config');
  22. if (!($oldConfig = config($this->configName))) {
  23. $this->error("The specified config (config/{$this->configName}.php) doesn't exist.");
  24. return;
  25. }
  26. if (array_key_exists("interactive", $oldConfig)) {
  27. $this->error("This upgrade tool is for upgrading from Scribe v3 to v4, but it looks like you're coming from v2.");
  28. $this->error("Please install v3 and follow its upgrade guide first.");
  29. return;
  30. }
  31. $isMajorUpgrade = array_key_exists("default_group", $oldConfig) || array_key_exists("faker_seed", $oldConfig);
  32. if ($isMajorUpgrade) $this->info("Welcome to the Scribe v3 to v4 upgrader.");
  33. $this->line("Checking for config file changes...");
  34. $upgrader = Upgrader::ofConfigFile("config/$this->configName.php", __DIR__ . '/../../config/scribe.php')
  35. ->dontTouch('routes', 'laravel.middleware', 'postman.overrides', 'openapi.overrides',
  36. 'example_languages', 'database_connections_to_transact', 'strategies', 'examples.models_source')
  37. ->move('default_group', 'groups.default')
  38. ->move('faker_seed', 'examples.faker_seed');
  39. if (!$isMajorUpgrade)
  40. $upgrader->dontTouch('groups');
  41. $changes = $upgrader->dryRun();
  42. if (empty($changes)) {
  43. $this->info("✔ No config file changes needed.");
  44. } else {
  45. $this->info('The following changes will be made to your config file:');
  46. $this->newLine();
  47. foreach ($changes as $change) {
  48. $this->line($change["description"]);
  49. }
  50. if ($this->applyChanges) {
  51. $upgrader->upgrade();
  52. $this->info("✔ Upgraded your config file. Your old config is backed up at config/$this->configName.php.bak.");
  53. }
  54. }
  55. $this->newLine();
  56. if (!$isMajorUpgrade) {
  57. $this->info("✔ Done.");
  58. $this->info(sprintf("See the full changelog at https://github.com/knuckleswtf/scribe/blob/%s/CHANGELOG.md", Scribe::VERSION));
  59. return;
  60. }
  61. $this->finishV4Upgrade();
  62. }
  63. protected function finishV4Upgrade(): void
  64. {
  65. $this->migrateToConfigFileSort();
  66. if ($this->applyChanges) {
  67. if ($this->confirm("Do you have any custom strategies?")) {
  68. $this->line('1. Add a new property <info>public ?ExtractedEndpointData $endpointData;</info>.');
  69. $this->line('2. Replace the <info>array $routeRules</info> parameter in __invoke() with <info>array $routeRules = []</info> .');
  70. }
  71. $this->newLine();
  72. $this->info("✔ Done.");
  73. }
  74. $this->line("See the release announcement at <href=https://scribe.knuckles.wtf/blog/laravel-v4>http://scribe.knuckles.wtf/blog/laravel-v4</> for the full upgrade guide!");
  75. }
  76. /**
  77. * In v3, you sorted endpoints by reordering them in the group file, and groups by renaming the group files alphabetically
  78. * (or by using `beforeGroup`/`afterGroup` for custom endpoints).
  79. * v4 replaces them with the config item `groups.order`.
  80. */
  81. protected function migrateToConfigFileSort()
  82. {
  83. $this->info("In v3, you sorted endpoints/groups by editing/renaming the generated YAML files (or `beforeGroup`/`afterGroup` for custom endpoints).");
  84. $this->info("We'll automatically import your current sorting into the config item `groups.order`.");
  85. $defaultGroup = config($this->configName.".default_group");
  86. $pathConfig = new PathConfig($this->configName);
  87. $extractedEndpoints = GroupedEndpointsFactory::fromCamelDir($pathConfig)->get();
  88. $order = array_map(function (array $group) {
  89. return array_map(function (array $endpoint) {
  90. return $endpoint['metadata']['title'] ?: ($endpoint['httpMethods'][0] . ' /'. $endpoint['uri']);
  91. }, $group['endpoints']);
  92. }, $extractedEndpoints);
  93. $groupsOrder = array_keys($order);
  94. $keyIndices = array_flip($groupsOrder);
  95. $userDefinedEndpoints = Camel::loadUserDefinedEndpoints(Camel::camelDir($pathConfig));
  96. if ($userDefinedEndpoints) {
  97. foreach ($userDefinedEndpoints as $endpoint) {
  98. $groupName = $endpoint['metadata']['groupName'] ?? $defaultGroup;
  99. $endpointTitle = $endpoint['metadata']['title'] ?? ($endpoint['httpMethods'][0] . ' /' . $endpoint['uri']);
  100. if (!isset($order[$groupName])) {
  101. // This is a new group; place it at the right spot.
  102. if (($nextGroup = $endpoint['metadata']['beforeGroup'] ?? null)) {
  103. $index = $keyIndices[$nextGroup];
  104. array_splice($groupsOrder, $index, 0, [$groupName]);
  105. } else if (($previousGroup = $endpoint['metadata']['afterGroup'] ?? null)) {
  106. $index = $keyIndices[$previousGroup];
  107. array_splice($groupsOrder, $index + 1, 0, [$groupName]);
  108. } else {
  109. $groupsOrder[] = $groupName;
  110. }
  111. $order[$groupName] = [$endpointTitle];
  112. } else {
  113. // Existing group, add endpoint
  114. $order[$groupName] = [...$order[$groupName], $endpointTitle];
  115. }
  116. }
  117. }
  118. // Re-add them, so it's sorted in the right order
  119. $newOrder = [];
  120. foreach ($groupsOrder as $groupName) {
  121. $newOrder[$groupName] = $order[$groupName];
  122. }
  123. $output = VarExporter::export($newOrder);
  124. if ($this->applyChanges) {
  125. $configFile = "config/{$this->configName}.php";
  126. $output = str_replace("\n", "\n ", $output);
  127. $newContents = str_replace(
  128. "'order' => [],",
  129. "'order' => $output,",
  130. file_get_contents($configFile)
  131. );
  132. file_put_contents($configFile, $newContents);
  133. $this->info("✔ Updated `groups.order`.");
  134. } else {
  135. $this->line("- `groups.order` will be set to:");
  136. $this->info($output);
  137. }
  138. }
  139. }