├── LICENSE ├── README.md ├── bin └── rr ├── composer.json └── src ├── Archive ├── Archive.php ├── ArchiveInterface.php ├── Factory.php ├── FactoryInterface.php ├── PharArchive.php ├── PharAwareArchive.php ├── TarPharArchive.php └── ZipPharArchive.php ├── Command.php ├── Command ├── ArchitectureOption.php ├── InstallationLocationOption.php ├── OperatingSystemOption.php ├── Option.php ├── OptionInterface.php ├── StabilityOption.php └── VersionFilterOption.php ├── Configuration ├── Generator.php ├── Plugins.php ├── Presets.php └── Section │ ├── AbstractSection.php │ ├── Amqp.php │ ├── Beanstalk.php │ ├── Boltdb.php │ ├── Broadcast.php │ ├── Endure.php │ ├── Fileserver.php │ ├── Grpc.php │ ├── Http.php │ ├── Jobs.php │ ├── Kv.php │ ├── Logs.php │ ├── Metrics.php │ ├── Nats.php │ ├── Otel.php │ ├── Redis.php │ ├── Reload.php │ ├── Rpc.php │ ├── SectionInterface.php │ ├── Server.php │ ├── Service.php │ ├── Sqs.php │ ├── Status.php │ ├── Tcp.php │ ├── Temporal.php │ ├── Version.php │ └── Websockets.php ├── DownloadProtocBinaryCommand.php ├── Environment ├── Architecture.php ├── Architecture │ └── Factory.php ├── Enum.php ├── Environment.php ├── OperatingSystem.php ├── OperatingSystem │ └── Factory.php └── Stability.php ├── GetBinaryCommand.php ├── MakeConfigCommand.php ├── Repository ├── Asset.php ├── AssetInterface.php ├── AssetsCollection.php ├── Collection.php ├── GitHub │ ├── GitHubAsset.php │ ├── GitHubRelease.php │ └── GitHubRepository.php ├── Release.php ├── ReleaseInterface.php ├── ReleasesCollection.php ├── RepositoriesCollection.php ├── RepositoryInterface.php └── Version1 │ └── StaticRepository.php └── VersionsCommand.php /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Spiral Scout 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |

17 | 18 | RoadRunner is an open-source (MIT licensed) high-performance PHP application server, load balancer, and process manager. 19 | It supports running as a service with the ability to extend its functionality on a per-project basis. 20 | 21 | RoadRunner includes PSR-7/PSR-17 compatible HTTP and HTTP/2 server and can be used to replace classic Nginx+FPM setup with much greater performance and flexibility. 22 | 23 |

24 | Official Website | 25 | Documentation 26 |

27 | 28 | ## RoadRunner CLI 29 | 30 | This repository contains commands to help you work with the RoadRunner, such as: 31 | 32 | - `get-binary` (or `get`) - allows to install the latest version of the RoadRunner compatible with 33 | your environment (operating system, processor architecture, runtime, etc...). 34 | Also, this command creates an example `.rr.yaml` configuration file. If don't use the command without additional options 35 | `plugin` and `preset`, an example with a complete configuration file will be created. 36 | Using the `plugin` option (shortcut `p`) can create an example configuration file with only plugins needed. 37 | For example, with http plugin only: `get-binary -p http`, http and jobs: `get-binary -p http -p jobs`. 38 | Available plugins: `amqp`, `beanstalk`, `boltdb`, `broadcast`, `endure`, `fileserver`, `grpc`, `http`, `jobs`, `kv`, 39 | `logs`, `metrics`, `nats`, `redis`, `reload`, `rpc`, `server`, `service`, `sqs`, `status`, `tcp`, `temporal`, `websockets`. 40 | Using the `preset` option can create an example configuration file with popular plugins for different typical tasks. 41 | For example, with web preset: `get-binary --preset web`. 42 | Available presets: `web` (contains plugins `http`, `jobs`). 43 | - `download-protoc-binary` - allows to install the latest version of the `protoc-gen-php-grpc` file compatible with 44 | your environment (operating system, processor architecture, runtime, etc...). 45 | - `versions` - displays a list of available RoadRunner binary versions. 46 | 47 | Testing: 48 | -------- 49 | 50 | This codebase is automatically tested via host repository - [roadrunner-server/roadrunner](https://github.com/roadrunner-server/roadrunner). 51 | 52 | License: 53 | -------- 54 | 55 | The MIT License (MIT). Please see [`LICENSE`](./LICENSE) for more information. 56 | Maintained by [Spiral Scout](https://spiralscout.com). 57 | -------------------------------------------------------------------------------- /bin/rr: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add(new GetBinaryCommand()); 72 | $app->add(new VersionsCommand()); 73 | $app->add(new DownloadProtocBinaryCommand()); 74 | $app->add(new MakeConfigCommand()); 75 | 76 | $app->run(); 77 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spiral/roadrunner-cli", 3 | "type": "library", 4 | "description": "RoadRunner: Command Line Interface", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Anton Titov (wolfy-j)", 9 | "email": "wolfy-j@spiralscout.com" 10 | }, 11 | { 12 | "name": "RoadRunner Community", 13 | "homepage": "https://github.com/spiral/roadrunner/graphs/contributors" 14 | } 15 | ], 16 | "bin": [ 17 | "bin/rr" 18 | ], 19 | "homepage": "https://roadrunner.dev", 20 | "support": { 21 | "docs": "https://docs.roadrunner.dev", 22 | "issues": "https://github.com/roadrunner-server/roadrunner/issues", 23 | "chat": "https://discord.gg/V6EK4he" 24 | }, 25 | "funding": [ 26 | { 27 | "type": "github", 28 | "url": "https://github.com/sponsors/roadrunner-server" 29 | } 30 | ], 31 | "require": { 32 | "php": ">=8.1", 33 | "ext-json": "*", 34 | "composer/semver": "^3.4", 35 | "spiral/roadrunner-worker": "^2 || ^3", 36 | "spiral/tokenizer": "^2.13 || ^3.15", 37 | "symfony/console": "^5.3 || ^6.0 || ^7.0", 38 | "symfony/http-client": "^4.4.51 || ^5.4.49 || ^6.4.17 || ^7.2", 39 | "symfony/yaml": "^5.4.49 || ^6.4.17 || ^7.2" 40 | }, 41 | "require-dev": { 42 | "jetbrains/phpstorm-attributes": "^1.2", 43 | "spiral/code-style": "^2.2.2", 44 | "spiral/dumper": "^3.3", 45 | "vimeo/psalm": "^6.0" 46 | }, 47 | "autoload": { 48 | "psr-4": { 49 | "Spiral\\RoadRunner\\Console\\": "src" 50 | } 51 | }, 52 | "scripts": { 53 | "cs:diff": "php-cs-fixer fix --dry-run -v --diff --show-progress dots", 54 | "cs:fix": "php-cs-fixer fix -v", 55 | "psalm": "psalm", 56 | "psalm:baseline": "psalm --set-baseline=psalm-baseline.xml" 57 | }, 58 | "config": { 59 | "sort-packages": true 60 | }, 61 | "minimum-stability": "dev", 62 | "prefer-stable": true 63 | } 64 | -------------------------------------------------------------------------------- /src/Archive/Archive.php: -------------------------------------------------------------------------------- 1 | assertArchiveValid($archive); 19 | } 20 | 21 | private function assertArchiveValid(\SplFileInfo $archive): void 22 | { 23 | if (! $archive->isFile()) { 24 | throw new \InvalidArgumentException( 25 | \sprintf('Archive "%s" is not a file', $archive->getFilename()), 26 | ); 27 | } 28 | 29 | if (! $archive->isReadable()) { 30 | throw new \InvalidArgumentException( 31 | \sprintf('Archive file "%s" is not readable', $archive->getFilename()), 32 | ); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Archive/ArchiveInterface.php: -------------------------------------------------------------------------------- 1 | $mappings 18 | * @return \Generator 19 | */ 20 | public function extract(iterable $mappings): \Generator; 21 | } 22 | -------------------------------------------------------------------------------- /src/Archive/Factory.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | private array $matchers = []; 25 | 26 | /** 27 | * FactoryTrait constructor. 28 | */ 29 | public function __construct() 30 | { 31 | $this->bootDefaultMatchers(); 32 | } 33 | 34 | public function extend(\Closure $matcher): self 35 | { 36 | \array_unshift($this->matchers, $matcher); 37 | 38 | return $this; 39 | } 40 | 41 | public function create(\SplFileInfo $file): ArchiveInterface 42 | { 43 | $errors = []; 44 | 45 | foreach ($this->matchers as $matcher) { 46 | try { 47 | if ($archive = $matcher($file)) { 48 | return $archive; 49 | } 50 | } catch (\Throwable $e) { 51 | $errors[] = ' - ' . $e->getMessage(); 52 | continue; 53 | } 54 | } 55 | 56 | $error = \sprintf('Can not open the archive "%s":%s', $file->getFilename(), \PHP_EOL) . 57 | \implode(\PHP_EOL, $errors) 58 | ; 59 | 60 | throw new \InvalidArgumentException($error); 61 | } 62 | 63 | public function fromAsset(AssetInterface $asset, ?\Closure $progress = null, ?string $temp = null): ArchiveInterface 64 | { 65 | $temp = $this->getTempDirectory($temp) . '/' . $asset->getName(); 66 | 67 | $file = new \SplFileObject($temp, 'wb+'); 68 | 69 | try { 70 | foreach ($asset->download($progress) as $chunk) { 71 | $file->fwrite($chunk); 72 | } 73 | } catch (\Throwable $e) { 74 | @\unlink($temp); 75 | 76 | throw $e; 77 | } 78 | 79 | return $this->create($file); 80 | } 81 | 82 | private function bootDefaultMatchers(): void 83 | { 84 | $this->extend($this->matcher( 85 | 'zip', 86 | static fn(\SplFileInfo $info): ArchiveInterface => new ZipPharArchive($info), 87 | )); 88 | 89 | $this->extend($this->matcher( 90 | 'tar.gz', 91 | static fn(\SplFileInfo $info): ArchiveInterface => new TarPharArchive($info), 92 | )); 93 | 94 | $this->extend($this->matcher( 95 | 'phar', 96 | static fn(\SplFileInfo $info): ArchiveInterface => new PharArchive($info), 97 | )); 98 | } 99 | 100 | /** 101 | * @param ArchiveMatcher $then 102 | * @return ArchiveMatcher 103 | */ 104 | private function matcher(string $extension, \Closure $then): \Closure 105 | { 106 | return static fn(\SplFileInfo $info): ?ArchiveInterface => 107 | \str_ends_with(\strtolower($info->getFilename()), '.' . $extension) ? $then($info) : null 108 | ; 109 | } 110 | 111 | private function getTempDirectory(?string $temp): string 112 | { 113 | if ($temp) { 114 | if (! \is_dir($temp) || ! \is_writable($temp)) { 115 | throw new \LogicException(\sprintf('Directory "%s" is not writeable', $temp)); 116 | } 117 | 118 | return $temp; 119 | } 120 | 121 | return \sys_get_temp_dir(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Archive/FactoryInterface.php: -------------------------------------------------------------------------------- 1 | getPathname()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Archive/PharAwareArchive.php: -------------------------------------------------------------------------------- 1 | archive = $this->open($archive); 23 | } 24 | 25 | /** 26 | * @param iterable $mappings 27 | * @return \Generator 28 | */ 29 | public function extract(iterable $mappings): \Generator 30 | { 31 | $phar = $this->open($this->archive); 32 | 33 | if (! $phar->isReadable()) { 34 | throw new \LogicException(\sprintf('Could not open "%s" for reading', $this->archive->getPathname())); 35 | } 36 | 37 | /** @var \PharFileInfo $file */ 38 | foreach (new \RecursiveIteratorIterator($phar) as $file) { 39 | foreach ($mappings as $from => $to) { 40 | if ($file->getFilename() === $from && (yield $file => new \SplFileInfo($to)) !== false) { 41 | \copy($file->getPathname(), $to); 42 | } 43 | } 44 | } 45 | } 46 | 47 | abstract protected function open(\SplFileInfo $file): \PharData; 48 | } 49 | -------------------------------------------------------------------------------- /src/Archive/TarPharArchive.php: -------------------------------------------------------------------------------- 1 | getPathname()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Archive/ZipPharArchive.php: -------------------------------------------------------------------------------- 1 | getPathname(), 0, null, $format); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Command.php: -------------------------------------------------------------------------------- 1 | \array_filter([ 40 | 'authorization' => $token ? 'token ' . $token : null, 41 | ]), 42 | ]); 43 | 44 | return new RepositoriesCollection([ 45 | GitHubRepository::create('roadrunner-server', 'roadrunner', $client), 46 | ]); 47 | } 48 | 49 | protected function io(InputInterface $input, OutputInterface $output): StyleInterface 50 | { 51 | return new SymfonyStyle($input, $output); 52 | } 53 | 54 | protected function confirm(InputInterface $input, OutputInterface $out, string $message): bool 55 | { 56 | $question = new ConfirmationQuestion($message); 57 | 58 | /** @var QuestionHelper $helper */ 59 | $helper = $this->getHelper('question'); 60 | 61 | return (bool) $helper->ask($input, $out, $question); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Command/ArchitectureOption.php: -------------------------------------------------------------------------------- 1 | warning(\sprintf($message, $this->name, $architecture, $this->choices())); 33 | } 34 | 35 | return $architecture; 36 | } 37 | 38 | protected function getDescription(): string 39 | { 40 | return 'Required processor architecture (one of: ' . $this->choices() . ')'; 41 | } 42 | 43 | protected function default(): string 44 | { 45 | return Architecture::createFromGlobals(); 46 | } 47 | 48 | private function choices(): string 49 | { 50 | return \implode(', ', Architecture::all()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Command/InstallationLocationOption.php: -------------------------------------------------------------------------------- 1 | name, $location); 32 | 33 | $io->warning($message); 34 | 35 | throw new \InvalidArgumentException('Installation directory not found or not writable'); 36 | } 37 | 38 | return $location; 39 | } 40 | 41 | protected function getDescription(): string 42 | { 43 | return 'Installation directory'; 44 | } 45 | 46 | protected function default(): string 47 | { 48 | return \getcwd() ?: '.'; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Command/OperatingSystemOption.php: -------------------------------------------------------------------------------- 1 | warning(\sprintf($message, $this->name, $os, $this->choices())); 33 | } 34 | 35 | return $os; 36 | } 37 | 38 | protected function getDescription(): string 39 | { 40 | return 'Required operating system (one of: ' . $this->choices() . ')'; 41 | } 42 | 43 | protected function default(): string 44 | { 45 | return OperatingSystem::createFromGlobals(); 46 | } 47 | 48 | private function choices(): string 49 | { 50 | return \implode(', ', OperatingSystem::all()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Command/Option.php: -------------------------------------------------------------------------------- 1 | name = $name; 29 | 30 | $this->register($command, $name, $short ?? $name); 31 | } 32 | 33 | public function getName(): string 34 | { 35 | return $this->name; 36 | } 37 | 38 | public function get(InputInterface $input, StyleInterface $io): string 39 | { 40 | $result = $input->getOption($this->name) ?: $this->default(); 41 | 42 | return \is_string($result) ? $result : ''; 43 | } 44 | 45 | /** 46 | * @return InputOptionType 47 | */ 48 | protected function getMode(): int 49 | { 50 | return InputOption::VALUE_OPTIONAL; 51 | } 52 | 53 | abstract protected function getDescription(): string; 54 | 55 | abstract protected function default(): ?string; 56 | 57 | private function register(Command $command, string $name, string $short): void 58 | { 59 | $command->addOption($name, $short, $this->getMode(), $this->getDescription(), $this->default()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Command/OptionInterface.php: -------------------------------------------------------------------------------- 1 | warning(\sprintf($message, $this->name, $stability, $this->choices())); 40 | } 41 | 42 | return $stability; 43 | } 44 | 45 | protected function getDescription(): string 46 | { 47 | return 'Release minimum stability flag'; 48 | } 49 | 50 | protected function default(): string 51 | { 52 | return Stability::STABILITY_STABLE; 53 | } 54 | 55 | private function choices(): string 56 | { 57 | return \implode(', ', Stability::all()); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Command/VersionFilterOption.php: -------------------------------------------------------------------------------- 1 | map(static fn(ReleaseInterface $release): string => $release->getVersion()) 33 | ->toArray() 34 | ; 35 | 36 | return \implode(', ', \array_unique($versions)); 37 | } 38 | 39 | public function find(InputInterface $input, StyleInterface $io, RepositoryInterface $repo): ReleasesCollection 40 | { 41 | $constraint = $this->get($input, $io); 42 | 43 | // All available releases 44 | $available = $repo->getReleases() 45 | ->sortByVersion() 46 | ; 47 | 48 | // With constraints 49 | $filtered = $available->satisfies($constraint); 50 | 51 | $this->validateNotEmpty($filtered, $available, $constraint); 52 | 53 | return $filtered; 54 | } 55 | 56 | protected function getDescription(): string 57 | { 58 | return 'Required version of RoadRunner binaries'; 59 | } 60 | 61 | protected function default(): string 62 | { 63 | return RoadRunnerVersion::constraint(); 64 | } 65 | 66 | private function validateNotEmpty(ReleasesCollection $filtered, ReleasesCollection $all, string $constraint): void 67 | { 68 | if ($filtered->empty()) { 69 | $header = 'Could not find any available RoadRunner binary version which meets version criterion (--%s=%s)'; 70 | $header = \sprintf($header, $this->name, $constraint); 71 | 72 | $footer = 'Available: ' . $this->choices($all); 73 | 74 | throw new \UnexpectedValueException(\implode("\n", [$header, $footer])); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Configuration/Generator.php: -------------------------------------------------------------------------------- 1 | > */ 15 | protected const REQUIRED_SECTIONS = [ 16 | Version::class, 17 | Rpc::class, 18 | ]; 19 | 20 | /** @var SectionInterface[] */ 21 | protected array $sections = []; 22 | 23 | public function generate(Plugins $plugins): string 24 | { 25 | $this->collectSections($plugins->getPlugins()); 26 | 27 | return Yaml::dump($this->getContent(), 10); 28 | } 29 | 30 | protected function getContent(): array 31 | { 32 | $content = []; 33 | foreach ($this->sections as $section) { 34 | $content += $section->render(); 35 | } 36 | 37 | return $content; 38 | } 39 | 40 | protected function collectSections(array $plugins): void 41 | { 42 | $sections = \array_merge(self::REQUIRED_SECTIONS, $plugins); 43 | 44 | foreach ($sections as $section) { 45 | $this->fromSection(new $section()); 46 | } 47 | } 48 | 49 | /** 50 | * @psalm-return non-empty-array 51 | */ 52 | protected function fromSection(SectionInterface $section): void 53 | { 54 | if (!isset($this->sections[\get_class($section)])) { 55 | $this->sections[\get_class($section)] = $section; 56 | } 57 | 58 | foreach ($section->getRequired() as $required) { 59 | $this->fromSection(new $required()); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Configuration/Plugins.php: -------------------------------------------------------------------------------- 1 | 22 | * 23 | * Default plugins in a class-string format. 24 | */ 25 | private array $defaultPlugins = [ 26 | Version::class, 27 | Rpc::class, 28 | Server::class, 29 | Http::class, 30 | Jobs::class, 31 | Kv::class, 32 | Metrics::class, 33 | ]; 34 | 35 | /** 36 | * @var string[] 37 | * 38 | * Requested plugins in a shortname format. 39 | */ 40 | private array $requestedPlugins; 41 | 42 | /** 43 | * @psalm-var non-empty-array> 44 | * 45 | * All plugins. 46 | */ 47 | private array $available; 48 | 49 | private function __construct(array $plugins) 50 | { 51 | $this->available = $this->getAvailable(); 52 | $this->requestedPlugins = $plugins; 53 | } 54 | 55 | public static function fromPlugins(array $plugins): self 56 | { 57 | return new self($plugins); 58 | } 59 | 60 | public static function fromPreset(string $preset): self 61 | { 62 | $plugins = []; 63 | switch ($preset) { 64 | case Presets::WEB_PRESET_NANE: 65 | $plugins = Presets::WEB_PLUGINS; 66 | } 67 | 68 | return new self(\array_map(static function (string $plugin) { 69 | return $plugin::getShortName(); 70 | }, $plugins)); 71 | } 72 | 73 | public function getPlugins(): array 74 | { 75 | if ($this->requestedPlugins === []) { 76 | return $this->defaultPlugins; 77 | } 78 | 79 | $plugins = []; 80 | foreach ($this->available as $plugin) { 81 | if (\in_array($plugin::getShortName(), $this->requestedPlugins, true)) { 82 | $plugins[] = $plugin; 83 | } 84 | } 85 | 86 | return $plugins; 87 | } 88 | 89 | private function getAvailable(): array 90 | { 91 | $finder = new Finder(); 92 | $finder->files()->in(__DIR__ . '/Section')->name('*.php'); 93 | 94 | $locator = new ClassLocator($finder); 95 | 96 | /** @var SectionInterface[] $available */ 97 | $available = []; 98 | foreach ($locator->getClasses() as $class) { 99 | if ($this->isPlugin($class)) { 100 | $available[] = $class->getName(); 101 | } 102 | } 103 | 104 | return $available; 105 | } 106 | 107 | private function isPlugin(\ReflectionClass $class): bool 108 | { 109 | return $class->implementsInterface(SectionInterface::class) && !$class->isAbstract() && !$class->isInterface(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Configuration/Presets.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'addr' => 'amqp://guest:guest@127.0.0.1:5672/', 21 | ], 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Configuration/Section/Beanstalk.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'addr' => 'tcp://127.0.0.1:11300', 21 | 'timeout' => '10s', 22 | ], 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Configuration/Section/Boltdb.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'permissions' => 0777, 21 | ], 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Configuration/Section/Broadcast.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'default' => [ 21 | 'driver' => 'memory', 22 | 'config' => [], 23 | ], 24 | // 'default-redis' => [ 25 | // 'driver' => 'redis', 26 | // 'config' => [ 27 | // 'addrs' => [ 28 | // 'localhost:6379' 29 | // ], 30 | // 'master_name' => '', 31 | // 'username' => '', 32 | // 'password' => '', 33 | // 'db' => 0, 34 | // 'sentinel_password' => '', 35 | // 'route_by_latency' => false, 36 | // 'route_randomly' => false, 37 | // 'dial_timeout' => 0, 38 | // 'max_retries' => 1, 39 | // 'min_retry_backoff' => 0, 40 | // 'max_retry_backoff' => 0, 41 | // 'pool_size' => 0, 42 | // 'min_idle_conns' => 0, 43 | // 'max_conn_age' => 0, 44 | // 'read_timeout' => 0, 45 | // 'write_timeout' => 0, 46 | // 'pool_timeout' => 0, 47 | // 'idle_timeout' => 0, 48 | // 'idle_check_freq' => 0, 49 | // 'read_only' => false 50 | // ] 51 | // ] 52 | ], 53 | ]; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Configuration/Section/Endure.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'grace_period' => '30s', 21 | 'print_graph' => false, 22 | 'log_level' => 'error', 23 | ], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Configuration/Section/Fileserver.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'address' => '127.0.0.1:10101', 21 | 'calculate_etag' => true, 22 | 'weak' => false, 23 | 'stream_request_body' => true, 24 | // 'serve' => [ 25 | // [ 26 | // 'prefix' => '/foo', 27 | // 'root' => '../../../tests', 28 | // 'compress' => false, 29 | // 'cache_duration' => 10, 30 | // 'max_age' => 10, 31 | // 'bytes_range' => true 32 | // ], 33 | // [ 34 | // 'prefix' => '/foo/bar', 35 | // 'root' => '../../../tests', 36 | // 'compress' => false, 37 | // 'cache_duration' => 10, 38 | // 'max_age' => 10, 39 | // 'bytes_range' => true 40 | // ] 41 | // ] 42 | ], 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Configuration/Section/Grpc.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'listen' => 'tcp://127.0.0.1:9001', 21 | 'proto' => [ 22 | 'first.proto', 23 | 'second.proto', 24 | ], 25 | // 'tls' => [ 26 | // 'key' => '', 27 | // 'cert' => '', 28 | // 'root_ca' => '', 29 | // 'client_auth_type' => 'no_client_certs' 30 | // ], 31 | // 'max_send_msg_size' => 50, 32 | // 'max_recv_msg_size' => 50, 33 | // 'max_connection_idle' => '0s', 34 | // 'max_connection_age' => '0s', 35 | // 'max_connection_age_grace' => '0s8h', 36 | // 'max_concurrent_streams' => 10, 37 | // 'ping_time' => '1s', 38 | // 'timeout' => '200s', 39 | // 'pool' => [ 40 | // 'num_workers' => 2, 41 | // 'max_jobs' => 0, 42 | // 'allocate_timeout' => '60s', 43 | // 'destroy_timeout' => 60 44 | // ] 45 | ], 46 | ]; 47 | } 48 | 49 | public function getRequired(): array 50 | { 51 | return [ 52 | Server::class, 53 | ]; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Configuration/Section/Http.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'address' => '0.0.0.0:8080', 21 | 'middleware' => [ 22 | 'gzip', 23 | 'static', 24 | ], 25 | 'static' => [ 26 | 'dir' => 'public', 27 | 'forbid' => ['.php', '.htaccess'], 28 | ], 29 | 'pool' => [ 30 | 'num_workers' => 1, 31 | 'supervisor' => [ 32 | 'max_worker_memory' => 100, 33 | ], 34 | ], 35 | ], 36 | ]; 37 | } 38 | 39 | public function getRequired(): array 40 | { 41 | return [ 42 | Server::class, 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Configuration/Section/Jobs.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'pool' => [ 21 | 'num_workers' => 2, 22 | 'max_worker_memory' => 100, 23 | ], 24 | 'consume' => [], 25 | ], 26 | ]; 27 | } 28 | 29 | public function getRequired(): array 30 | { 31 | return [ 32 | Server::class, 33 | ]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Configuration/Section/Kv.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'local' => [ 21 | 'driver' => 'memory', 22 | 'config' => [ 23 | 'interval' => 60, 24 | ], 25 | ], 26 | // 'redis' => [ 27 | // 'driver' => 'redis', 28 | // 'config' => [ 29 | // 'addrs' => [ 30 | // 'localhost:6379' 31 | // ] 32 | // ] 33 | // ] 34 | ], 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Configuration/Section/Logs.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'mode' => 'development', 21 | 'level' => 'debug', 22 | // 'encoding' => 'console', 23 | // 'line_ending' => '\n', 24 | // 'output' => 'stderr', 25 | // 'err_output' => 'stderr', 26 | // 'file_logger_options' => [ 27 | // 'log_output' => '/tmp/my.log', 28 | // 'max_size' => 100, 29 | // 'max_age' => 1, 30 | // 'max_backups' => 5, 31 | // 'compress' => false 32 | // ], 33 | // 'channels' => [ 34 | // 'http' => [ 35 | // 'mode' => 'development', 36 | // 'level' => 'panic', 37 | // 'encoding' => 'console', 38 | // 'output' => 'stdout', 39 | // 'err_output' => 'stderr' 40 | // ], 41 | // 'server' => [ 42 | // 'mode' => 'production', 43 | // 'level' => 'info', 44 | // 'encoding' => 'json', 45 | // 'output' => 'stdout', 46 | // 'err_output' => 'stdout' 47 | // ], 48 | // 'rpc' => [ 49 | // 'mode' => 'raw', 50 | // 'level' => 'debug', 51 | // 'encoding' => 'console', 52 | // 'output' => 'stderr', 53 | // 'err_output' => 'stdout' 54 | // ] 55 | // ] 56 | ], 57 | ]; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Configuration/Section/Metrics.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'address' => '127.0.0.1:2112', 21 | ], 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Configuration/Section/Nats.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'addr' => 'demo.nats.io', 21 | ], 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Configuration/Section/Otel.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'insecure' => false, 21 | 'compress' => false, 22 | 'client' => 'http', 23 | 'exporter' => 'otlp', 24 | 'custom_url' => '', 25 | 'service_name' => 'RoadRunner', 26 | 'service_version' => '1.0.0', 27 | 'endpoint' => '127.0.0.1:4318', 28 | ], 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Configuration/Section/Redis.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'addrs' => [ 21 | 'localhost:6379', 22 | ], 23 | // 'master_name' => '', 24 | // 'username' => '', 25 | // 'password' => '', 26 | // 'db' => 0, 27 | // 'sentinel_password' => '', 28 | // 'route_by_latency' => false, 29 | // 'route_randomly' => false, 30 | // 'dial_timeout' => 0, 31 | // 'max_retries' => 1, 32 | // 'min_retry_backoff' => 0, 33 | // 'max_retry_backoff' => 0, 34 | // 'pool_size' => 0, 35 | // 'min_idle_conns' => 0, 36 | // 'max_conn_age' => 0, 37 | // 'read_timeout' => 0, 38 | // 'write_timeout' => 0, 39 | // 'pool_timeout' => 0, 40 | // 'idle_timeout' => 0, 41 | // 'idle_check_freq' => 0, 42 | // 'read_only' => false 43 | ], 44 | ]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Configuration/Section/Reload.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'interval' => '1s', 21 | 'patterns' => [ 22 | '.php', 23 | ], 24 | 'services' => [ 25 | 'http' => [ 26 | 'dirs' => [ 27 | '.', 28 | ], 29 | 'recursive' => true, 30 | 'ignore' => [ 31 | 'vendor', 32 | ], 33 | 'patterns' => [ 34 | '.php', 35 | '.go', 36 | '.md', 37 | ], 38 | ], 39 | ], 40 | ], 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Configuration/Section/Rpc.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'listen' => 'tcp://127.0.0.1:6001', 21 | ], 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Configuration/Section/SectionInterface.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'command' => 'php app.php', 21 | 'relay' => 'pipes', 22 | ], 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Configuration/Section/Service.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'some_service_1' => [ 21 | 'command' => 'php tests/plugins/service/test_files/loop.php', 22 | 'env' => [ 23 | 'foo' => 'BAR', 24 | 'foo2' => 'BAR2', 25 | ], 26 | 'process_num' => 1, 27 | 'exec_timeout' => 0, 28 | 'remain_after_exit' => true, 29 | 'restart_sec' => 1, 30 | ], 31 | 'some_service_2' => [ 32 | 'command' => 'binary', 33 | 'env' => [ 34 | 'foo' => 'BAR', 35 | 'foo2' => 'BAR2', 36 | ], 37 | 'process_num' => 1, 38 | 'exec_timeout' => 0, 39 | 'remain_after_exit' => true, 40 | 'restart_sec' => 1, 41 | ], 42 | ], 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Configuration/Section/Sqs.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'key' => 'api-key', 21 | 'secret' => 'api-secret', 22 | 'region' => 'us-west-1', 23 | 'session_token' => 'test', 24 | 'endpoint' => 'http://127.0.0.1:9324', 25 | ], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Configuration/Section/Status.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'address' => '127.0.0.1:2114', 21 | 'unavailable_status_code' => 503, 22 | ], 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Configuration/Section/Tcp.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'servers' => [ 21 | 'server1' => [ 22 | 'addr' => '127.0.0.1:7778', 23 | // 'delimiter' => '\r\n', 24 | // 'read_buf_size' => 1 25 | ], 26 | // 'server2' => [ 27 | // 'addr' => '127.0.0.1:8811', 28 | // 'read_buf_size' => 10 29 | // ], 30 | // 'server3' => [ 31 | // 'addr' => '127.0.0.1:8812', 32 | // 'delimiter' => '\r\n', 33 | // 'read_buf_size' => 1 34 | // ] 35 | ], 36 | 'pool' => [ 37 | 'command' => '', 38 | 'num_workers' => 5, 39 | 'max_jobs' => 0, 40 | 'allocate_timeout' => '60s', 41 | 'destroy_timeout' => '60s', 42 | ], 43 | ], 44 | ]; 45 | } 46 | 47 | public function getRequired(): array 48 | { 49 | return [ 50 | Server::class, 51 | ]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Configuration/Section/Temporal.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'address' => '127.0.0.1:7233', 21 | // 'cache_size' => 10000, 22 | // 'namespace' => 'default', 23 | // 'codec' => 'proto', 24 | // 'debug_level' => 2, 25 | // 'metrics' => [ 26 | // 'address' => '127.0.0.1:9091', 27 | // 'type' => 'summary', 28 | // 'prefix' => 'foobar' 29 | // ], 30 | // 'activities' => [ 31 | // 'debug' => false, 32 | // 'command' => 'php my-super-app.php', 33 | // 'num_workers' => 0, 34 | // 'max_jobs' => 64, 35 | // 'allocate_timeout' => '60s', 36 | // 'destroy_timeout' => '60s', 37 | // 'supervisor' => [ 38 | // 'watch_tick' => '1s', 39 | // 'ttl' => '0s', 40 | // 'idle_ttl' => '10s', 41 | // 'max_worker_memory' => 128, 42 | // 'exec_ttl' => '60s' 43 | // ] 44 | // ] 45 | ], 46 | ]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Configuration/Section/Version.php: -------------------------------------------------------------------------------- 1 | self::CONFIG_VERSION, 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Configuration/Section/Websockets.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'broker' => 'default-redis', 21 | 'allowed_origin' => '*', 22 | 'path' => '/ws', 23 | ], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/DownloadProtocBinaryCommand.php: -------------------------------------------------------------------------------- 1 | os = new OperatingSystemOption($this); 41 | $this->arch = new ArchitectureOption($this); 42 | $this->version = new VersionFilterOption($this); 43 | $this->location = new InstallationLocationOption($this); 44 | $this->stability = new StabilityOption($this); 45 | } 46 | 47 | public function getDescription(): string 48 | { 49 | return 'Install or update protoc-gen-php-grpc binary'; 50 | } 51 | 52 | public function execute(InputInterface $input, OutputInterface $output): int 53 | { 54 | $io = $this->io($input, $output); 55 | 56 | $target = $this->location->get($input, $io); 57 | $repository = $this->getRepository(); 58 | 59 | $output->writeln(''); 60 | $output->writeln(' Environment:'); 61 | $output->writeln(\sprintf(' - Version: %s', $this->version->get($input, $io))); 62 | $output->writeln(\sprintf(' - Stability: %s', $this->stability->get($input, $io))); 63 | $output->writeln(\sprintf(' - Operating System: %s', $this->os->get($input, $io))); 64 | $output->writeln(\sprintf(' - Architecture: %s', $this->arch->get($input, $io))); 65 | $output->writeln(''); 66 | 67 | // List of all available releases 68 | $releases = $this->version->find($input, $io, $repository); 69 | 70 | /** 71 | * @var AssetInterface $asset 72 | * @var ReleaseInterface $release 73 | */ 74 | [$asset, $release] = $this->findAsset($repository, $releases, $input, $io); 75 | 76 | // Installation 77 | $output->writeln( 78 | \sprintf(" - %s", $release->getRepositoryName()) . 79 | \sprintf(' (%s):', $release->getVersion()) . 80 | ' Downloading...', 81 | ); 82 | 83 | if ($output->isVerbose()) { 84 | $output->writeln(\sprintf(" -- %s", $asset->getName())); 85 | } 86 | 87 | // Install rr binary 88 | $file = $this->installBinary($target, $release, $asset, $io, $output); 89 | 90 | // Success 91 | if ($file === null) { 92 | $io->warning('protoc-gen-php-grpc has not been installed'); 93 | 94 | return 1; 95 | } 96 | 97 | return 0; 98 | } 99 | 100 | private function installBinary( 101 | string $target, 102 | ReleaseInterface $release, 103 | AssetInterface $asset, 104 | StyleInterface $io, 105 | OutputInterface $out, 106 | ): ?\SplFileInfo { 107 | $extractor = $this->assetToArchive($asset, $out) 108 | ->extract([ 109 | 'protoc-gen-php-grpc.exe' => $target . '/protoc-gen-php-grpc.exe', 110 | 'protoc-gen-php-grpc' => $target . '/protoc-gen-php-grpc', 111 | ]); 112 | 113 | $file = null; 114 | while ($extractor->valid()) { 115 | $file = $extractor->current(); 116 | 117 | if (!$this->checkExisting($file, $io)) { 118 | $extractor->send(false); 119 | continue; 120 | } 121 | 122 | // Success 123 | $path = $file->getRealPath() ?: $file->getPathname(); 124 | $message = 'protoc-gen-php-grpc (%s) has been installed into %s'; 125 | $message = \sprintf($message, $release->getVersion(), $path); 126 | $out->writeln($message); 127 | 128 | $extractor->next(); 129 | 130 | if (!$file->isExecutable()) { 131 | @\chmod($file->getRealPath(), 0755); 132 | } 133 | } 134 | 135 | return $file; 136 | } 137 | 138 | private function checkExisting(\SplFileInfo $bin, StyleInterface $io): bool 139 | { 140 | if (\is_file($bin->getPathname())) { 141 | $io->warning('protoc-gen-php-grpc binary file already exists!'); 142 | 143 | if (!$io->confirm('Do you want overwrite it?', false)) { 144 | $io->note('Skipping protoc-gen-php-grpc installation...'); 145 | 146 | return false; 147 | } 148 | } 149 | 150 | return true; 151 | } 152 | 153 | private function findAsset( 154 | RepositoryInterface $repo, 155 | ReleasesCollection $releases, 156 | InputInterface $in, 157 | StyleInterface $io, 158 | ): array { 159 | $osOption = $this->os->get($in, $io); 160 | $archOption = $this->arch->get($in, $io); 161 | $stabilityOption = $this->stability->get($in, $io); 162 | 163 | /** @var ReleaseInterface[] $filtered */ 164 | $filtered = $releases 165 | ->minimumStability($stabilityOption) 166 | ->withAssets(); 167 | 168 | foreach ($filtered as $release) { 169 | $asset = $release->getAssets() 170 | ->filter( 171 | static fn(AssetInterface $asset): bool => 172 | \str_starts_with($asset->getName(), 'protoc-gen-php-grpc'), 173 | ) 174 | ->whereArchitecture($archOption) 175 | ->whereOperatingSystem($osOption) 176 | ->first(); 177 | 178 | if ($asset === null) { 179 | $io->warning( 180 | \vsprintf('%s %s does not contain available assembly (further search in progress)', [ 181 | $repo->getName(), 182 | $release->getVersion(), 183 | ]), 184 | ); 185 | 186 | continue; 187 | } 188 | 189 | return [$asset, $release]; 190 | } 191 | 192 | $message = \vsprintf(self::ERROR_ENVIRONMENT, [ 193 | $this->os->getName(), 194 | $osOption, 195 | $this->arch->getName(), 196 | $archOption, 197 | $this->stability->getName(), 198 | $stabilityOption, 199 | $this->version->choices($releases), 200 | ]); 201 | 202 | throw new \UnexpectedValueException($message); 203 | } 204 | 205 | private function assetToArchive(AssetInterface $asset, OutputInterface $out, ?string $temp = null): ArchiveInterface 206 | { 207 | $factory = new Factory(); 208 | 209 | $progress = new ProgressBar($out); 210 | $progress->setFormat(' [%bar%] %percent:3s%% (%size%Kb/%total%Kb)'); 211 | $progress->setMessage('0.00', 'size'); 212 | $progress->setMessage('?.??', 'total'); 213 | $progress->display(); 214 | 215 | try { 216 | return $factory->fromAsset($asset, static function (int $size, int $total) use ($progress): void { 217 | if ($progress->getMaxSteps() !== $total) { 218 | $progress->setMaxSteps($total); 219 | } 220 | 221 | if ($progress->getStartTime() === 0) { 222 | $progress->start(); 223 | } 224 | 225 | $progress->setMessage(\number_format($size / 1000, 2), 'size'); 226 | $progress->setMessage(\number_format($total / 1000, 2), 'total'); 227 | 228 | $progress->setProgress($size); 229 | }, $temp); 230 | } finally { 231 | $progress->clear(); 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/Environment/Architecture.php: -------------------------------------------------------------------------------- 1 | createFromGlobals(); 39 | } 40 | 41 | /** 42 | * @return array 43 | */ 44 | public static function all(): array 45 | { 46 | static $values; 47 | 48 | if ($values === null) { 49 | $values = Enum::values(self::class, 'ARCH_'); 50 | } 51 | 52 | /** @psalm-var array $values */ 53 | return $values; 54 | } 55 | 56 | public static function isValid(string $value): bool 57 | { 58 | return \in_array($value, self::all(), true); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Environment/Architecture/Factory.php: -------------------------------------------------------------------------------- 1 | > 32 | */ 33 | private const UNAME_MAPPINGS = [ 34 | Architecture::ARCH_X86_64 => [ 35 | 'AMD64', 36 | 'amd64', 37 | 'x86', 38 | 'x64', 39 | 'x86_64', 40 | ], 41 | Architecture::ARCH_ARM_64 => [ 42 | 'arm64', 43 | 'aarch64', 44 | ], 45 | ]; 46 | 47 | /** 48 | * @return ArchitectureType 49 | */ 50 | #[ExpectedValues(valuesFromClass: Architecture::class)] 51 | public function createFromGlobals(): string 52 | { 53 | $uname = \php_uname('m'); 54 | 55 | foreach (self::UNAME_MAPPINGS as $result => $available) { 56 | if (\in_array($uname, $available, true)) { 57 | return $result; 58 | } 59 | } 60 | 61 | throw new \OutOfRangeException(\sprintf(self::ERROR_UNKNOWN_ARCH, $uname)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Environment/Enum.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | public static function values(string $class, string $prefix): array 25 | { 26 | $result = []; 27 | 28 | try { 29 | $reflection = new \ReflectionClass($class); 30 | } catch (\ReflectionException $e) { 31 | return []; 32 | } 33 | 34 | /** @psalm-var int|string $value */ 35 | foreach ($reflection->getConstants() as $name => $value) { 36 | if (\str_starts_with($name, $prefix)) { 37 | $result[$name] = $value; 38 | } 39 | } 40 | 41 | return $result; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Environment/Environment.php: -------------------------------------------------------------------------------- 1 | createFromGlobals($variables); 54 | } 55 | 56 | public static function isValid(string $value): bool 57 | { 58 | return \in_array($value, self::all(), true); 59 | } 60 | 61 | /** 62 | * @return array 63 | */ 64 | public static function all(): array 65 | { 66 | static $values; 67 | 68 | if ($values === null) { 69 | $values = Enum::values(self::class, 'OS_'); 70 | } 71 | 72 | /** @psalm-var array $values */ 73 | return $values; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Environment/OperatingSystem/Factory.php: -------------------------------------------------------------------------------- 1 | 4, 49 | self::STABILITY_RC => 3, 50 | self::STABILITY_BETA => 2, 51 | self::STABILITY_ALPHA => 1, 52 | self::STABILITY_DEV => 0, 53 | ]; 54 | 55 | /** 56 | * @param StabilityType $type 57 | * @return positive-int|0 58 | */ 59 | public static function toInt(string $type): int 60 | { 61 | return self::WEIGHT[$type] ?? 0; 62 | } 63 | 64 | /** 65 | * @return array 66 | */ 67 | public static function all(): array 68 | { 69 | static $values; 70 | 71 | if ($values === null) { 72 | $values = Enum::values(self::class, 'STABILITY_'); 73 | } 74 | 75 | /** @psalm-var array $values */ 76 | return $values; 77 | } 78 | 79 | public static function isValid(string $value): bool 80 | { 81 | return \in_array($value, self::all(), true); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/GetBinaryCommand.php: -------------------------------------------------------------------------------- 1 | os = new OperatingSystemOption($this); 53 | $this->arch = new ArchitectureOption($this); 54 | $this->version = new VersionFilterOption($this); 55 | $this->location = new InstallationLocationOption($this); 56 | $this->stability = new StabilityOption($this); 57 | } 58 | 59 | public function getDescription(): string 60 | { 61 | return 'Install or update RoadRunner binary'; 62 | } 63 | 64 | /** 65 | * 66 | * @throws \Throwable 67 | */ 68 | public function execute(InputInterface $input, OutputInterface $output): int 69 | { 70 | $io = $this->io($input, $output); 71 | 72 | $target = $this->location->get($input, $io); 73 | $repository = $this->getRepository(); 74 | 75 | $output->writeln(''); 76 | $output->writeln(' Environment:'); 77 | $output->writeln(\sprintf(' - Version: %s', $this->version->get($input, $io))); 78 | $output->writeln(\sprintf(' - Stability: %s', $this->stability->get($input, $io))); 79 | $output->writeln(\sprintf(' - Operating System: %s', $this->os->get($input, $io))); 80 | $output->writeln(\sprintf(' - Architecture: %s', $this->arch->get($input, $io))); 81 | $output->writeln(''); 82 | 83 | 84 | // List of all available releases 85 | $releases = $this->version->find($input, $io, $repository); 86 | 87 | /** 88 | * @var AssetInterface $asset 89 | * @var ReleaseInterface $release 90 | */ 91 | [$asset, $release] = $this->findAsset($repository, $releases, $input, $io); 92 | 93 | // Installation 94 | $output->writeln( 95 | \sprintf(" - %s", $release->getRepositoryName()) . 96 | \sprintf(' (%s):', $release->getVersion()) . 97 | ' Downloading...', 98 | ); 99 | 100 | if ($output->isVerbose()) { 101 | $output->writeln(\sprintf(" -- %s", $asset->getName())); 102 | } 103 | 104 | // Install rr binary 105 | $file = $this->installBinary($target, $release, $asset, $io, $output); 106 | 107 | $this->installConfig($target, $input, $io); 108 | 109 | // Success 110 | if ($file === null) { 111 | $io->warning('RoadRunner has not been installed'); 112 | 113 | return 1; 114 | } 115 | 116 | $io->success('Your project is now ready in ' . $file->getPath()); 117 | 118 | $io->title('Whats Next?'); 119 | $io->listing([ 120 | // 1) 121 | 'For more detailed documentation, see the ' . 122 | 'https://roadrunner.dev', 123 | 124 | // 2) 125 | 'To run the application, use the following command: ' . 126 | '$ ' . $file->getFilename() . ' serve', 127 | ]); 128 | 129 | return 0; 130 | } 131 | 132 | protected function configure(): void 133 | { 134 | $this->addOption( 135 | 'plugin', 136 | 'p', 137 | InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 138 | 'Generate configuration with selected plugins.', 139 | ); 140 | 141 | $this->addOption( 142 | 'preset', 143 | null, 144 | InputOption::VALUE_OPTIONAL, 145 | 'Generate configuration with plugins in a selected preset.', 146 | ); 147 | 148 | $this->addOption( 149 | 'no-config', 150 | null, 151 | InputOption::VALUE_NONE, 152 | 'Do not generate configuration at all.', 153 | ); 154 | } 155 | 156 | /** 157 | * @throws \Throwable 158 | */ 159 | private function installBinary( 160 | string $target, 161 | ReleaseInterface $release, 162 | AssetInterface $asset, 163 | StyleInterface $io, 164 | OutputInterface $out, 165 | ): ?\SplFileInfo { 166 | $extractor = $this->assetToArchive($asset, $out) 167 | ->extract([ 168 | 'rr.exe' => $target . '/rr.exe', 169 | 'rr' => $target . '/rr', 170 | ]) 171 | ; 172 | 173 | $file = null; 174 | while ($extractor->valid()) { 175 | $file = $extractor->current(); 176 | 177 | if (! $this->checkExisting($file, $io)) { 178 | $extractor->send(false); 179 | continue; 180 | } 181 | 182 | // Success 183 | $path = $file->getRealPath() ?: $file->getPathname(); 184 | $message = 'RoadRunner (%s) has been installed into %s'; 185 | $message = \sprintf($message, $release->getVersion(), $path); 186 | $out->writeln($message); 187 | 188 | $extractor->next(); 189 | 190 | if (! $file->isExecutable()) { 191 | @\chmod($file->getRealPath(), 0755); 192 | } 193 | } 194 | 195 | return $file; 196 | } 197 | 198 | private function installConfig(string $to, InputInterface $in, StyleInterface $io): bool 199 | { 200 | $to .= '/.rr.yaml'; 201 | 202 | if (\is_file($to) || \is_file(\getcwd() . '/.rr.yaml')) { 203 | return false; 204 | } 205 | 206 | if ($in->getOption('no-config') || ! $io->confirm('Do you want create default ".rr.yaml" configuration file?', true)) { 207 | return false; 208 | } 209 | 210 | $generator = new Generator(); 211 | $plugins = $in->getOption('preset') ? 212 | Plugins::fromPreset($in->getOption('preset')) : 213 | Plugins::fromPlugins($in->getOption('plugin')); 214 | 215 | try { 216 | $config = $generator->generate($plugins); 217 | } catch (\Throwable $e) { 218 | $io->error($e->getMessage()); 219 | } 220 | 221 | \file_put_contents($to, $config); 222 | 223 | return true; 224 | } 225 | 226 | private function checkExisting(\SplFileInfo $bin, StyleInterface $io): bool 227 | { 228 | if (\is_file($bin->getPathname())) { 229 | $io->warning('RoadRunner binary file already exists!'); 230 | 231 | if (! $io->confirm('Do you want overwrite it?', false)) { 232 | $io->note('Skipping RoadRunner installation...'); 233 | 234 | return false; 235 | } 236 | } 237 | 238 | return true; 239 | } 240 | 241 | /** 242 | * @return array{0: AssetInterface, 1: ReleaseInterface} 243 | */ 244 | private function findAsset( 245 | RepositoryInterface $repo, 246 | ReleasesCollection $releases, 247 | InputInterface $in, 248 | StyleInterface $io, 249 | ): array { 250 | $osOption = $this->os->get($in, $io); 251 | $archOption = $this->arch->get($in, $io); 252 | $stabilityOption = $this->stability->get($in, $io); 253 | 254 | /** @var ReleaseInterface[] $filtered */ 255 | $filtered = $releases 256 | ->minimumStability($stabilityOption) 257 | ->withAssets() 258 | ; 259 | 260 | foreach ($filtered as $release) { 261 | $asset = $release->getAssets() 262 | ->onlyRoadrunner() 263 | ->exceptDebPackages() 264 | ->whereArchitecture($archOption) 265 | ->whereOperatingSystem($osOption) 266 | ->first() 267 | ; 268 | 269 | if ($asset === null) { 270 | $io->warning(\vsprintf('%s %s does not contain available assembly (further search in progress)', [ 271 | $repo->getName(), 272 | $release->getVersion(), 273 | ])); 274 | 275 | continue; 276 | } 277 | 278 | return [$asset, $release]; 279 | } 280 | 281 | $message = \vsprintf(self::ERROR_ENVIRONMENT, [ 282 | $this->os->getName(), 283 | $osOption, 284 | $this->arch->getName(), 285 | $archOption, 286 | $this->stability->getName(), 287 | $stabilityOption, 288 | $this->version->choices($releases), 289 | ]); 290 | 291 | throw new \UnexpectedValueException($message); 292 | } 293 | 294 | /** 295 | * @throws \Throwable 296 | */ 297 | private function assetToArchive(AssetInterface $asset, OutputInterface $out, ?string $temp = null): ArchiveInterface 298 | { 299 | $factory = new Factory(); 300 | 301 | $progress = new ProgressBar($out); 302 | $progress->setFormat(' [%bar%] %percent:3s%% (%size%Kb/%total%Kb)'); 303 | $progress->setMessage('0.00', 'size'); 304 | $progress->setMessage('?.??', 'total'); 305 | $progress->display(); 306 | 307 | try { 308 | return $factory->fromAsset($asset, static function (int $size, int $total) use ($progress): void { 309 | if ($progress->getMaxSteps() !== $total) { 310 | $progress->setMaxSteps($total); 311 | } 312 | 313 | if ($progress->getStartTime() === 0) { 314 | $progress->start(); 315 | } 316 | 317 | $progress->setMessage(\number_format($size / 1000, 2), 'size'); 318 | $progress->setMessage(\number_format($total / 1000, 2), 'total'); 319 | 320 | $progress->setProgress($size); 321 | }, $temp); 322 | } finally { 323 | $progress->clear(); 324 | } 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /src/MakeConfigCommand.php: -------------------------------------------------------------------------------- 1 | location = new InstallationLocationOption($this); 22 | } 23 | 24 | /** 25 | * 26 | * @throws \Throwable 27 | */ 28 | public function execute(InputInterface $input, OutputInterface $output): int 29 | { 30 | $io = $this->io($input, $output); 31 | 32 | 33 | $target = $this->location->get($input, $io) . '/.rr.yaml'; 34 | 35 | if (\is_file($target) || \is_file(\getcwd() . '/.rr.yaml')) { 36 | return self::FAILURE; 37 | } 38 | 39 | $generator = new Generator(); 40 | $plugins = $input->getOption('preset') ? 41 | Plugins::fromPreset($input->getOption('preset')) : 42 | Plugins::fromPlugins($input->getOption('plugin')); 43 | 44 | try { 45 | $config = $generator->generate($plugins); 46 | \file_put_contents($target, $config); 47 | } catch (\Throwable $e) { 48 | $io->error($e->getMessage()); 49 | return self::FAILURE; 50 | } 51 | 52 | return self::SUCCESS; 53 | } 54 | 55 | protected function configure(): void 56 | { 57 | $this->addOption( 58 | 'plugin', 59 | 'p', 60 | InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 61 | 'Generate configuration with selected plugins.', 62 | ); 63 | 64 | $this->addOption( 65 | 'preset', 66 | null, 67 | InputOption::VALUE_OPTIONAL, 68 | 'Generate configuration with plugins in a selected preset.', 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Repository/Asset.php: -------------------------------------------------------------------------------- 1 | name = $name; 22 | $this->uri = $uri; 23 | } 24 | 25 | public function getName(): string 26 | { 27 | return $this->name; 28 | } 29 | 30 | public function getUri(): string 31 | { 32 | return $this->uri; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Repository/AssetInterface.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | public function download(?\Closure $progress = null): iterable; 24 | } 25 | -------------------------------------------------------------------------------- /src/Repository/AssetsCollection.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | final class AssetsCollection extends Collection 18 | { 19 | /** 20 | * @return $this 21 | */ 22 | public function onlyRoadrunner(): self 23 | { 24 | return $this->filter( 25 | static fn(AssetInterface $asset): bool => 26 | \str_starts_with($asset->getName(), 'roadrunner'), 27 | ); 28 | } 29 | 30 | /** 31 | * @return $this 32 | */ 33 | public function exceptDebPackages(): self 34 | { 35 | return $this->except( 36 | static fn(AssetInterface $asset): bool => 37 | \str_ends_with(\strtolower($asset->getName()), '.deb'), 38 | ); 39 | } 40 | 41 | /** 42 | * @return $this 43 | */ 44 | public function whereArchitecture(string $arch): self 45 | { 46 | return $this->filter( 47 | static fn(AssetInterface $asset): bool => 48 | \str_contains($asset->getName(), '-' . \strtolower($arch) . '.'), 49 | ); 50 | } 51 | 52 | /** 53 | * @return $this 54 | */ 55 | public function whereOperatingSystem(string $os): self 56 | { 57 | return $this->filter( 58 | static fn(AssetInterface $asset): bool => 59 | \str_contains($asset->getName(), '-' . \strtolower($os) . '-'), 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Repository/Collection.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | abstract class Collection implements \IteratorAggregate, \Countable 23 | { 24 | /** 25 | * @var array 26 | */ 27 | protected array $items; 28 | 29 | /** 30 | * @param array $items 31 | */ 32 | final public function __construct(array $items) 33 | { 34 | $this->items = $items; 35 | } 36 | 37 | /** 38 | * @param mixed|iterable|\Closure $items 39 | * @return static 40 | */ 41 | public static function create($items): self 42 | { 43 | switch (true) { 44 | case $items instanceof static: 45 | return $items; 46 | 47 | case $items instanceof \Traversable: 48 | $items = \iterator_to_array($items); 49 | 50 | // no break 51 | case \is_array($items): 52 | return new static($items); 53 | 54 | case $items instanceof \Closure: 55 | return static::from($items); 56 | 57 | default: 58 | throw new \InvalidArgumentException( 59 | \sprintf('Unsupported iterable type %s', \get_debug_type($items)), 60 | ); 61 | } 62 | } 63 | 64 | /** 65 | * @return static 66 | */ 67 | public static function from(\Closure $generator): self 68 | { 69 | return static::create($generator()); 70 | } 71 | 72 | /** 73 | * @param callable(T): bool $filter 74 | * @return $this 75 | */ 76 | public function filter(callable $filter): self 77 | { 78 | return new static(\array_filter($this->items, $filter)); 79 | } 80 | 81 | /** 82 | * @param callable(T): mixed $map 83 | * @return $this 84 | */ 85 | public function map(callable $map): self 86 | { 87 | return new static(\array_map($map, $this->items)); 88 | } 89 | 90 | /** 91 | * @param callable(T): bool $filter 92 | * @return $this 93 | * 94 | * @psalm-suppress MissingClosureParamType 95 | * @psalm-suppress MixedArgument 96 | */ 97 | public function except(callable $filter): self 98 | { 99 | $callback = static fn(...$args): bool => ! $filter(...$args); 100 | 101 | return new static(\array_filter($this->items, $callback)); 102 | } 103 | 104 | /** 105 | * @param null|callable(T): bool $filter 106 | * @return T|null 107 | */ 108 | public function first(?callable $filter = null): ?object 109 | { 110 | $self = $filter === null ? $this : $this->filter($filter); 111 | 112 | return $self->items === [] ? null : \reset($self->items); 113 | } 114 | 115 | /** 116 | * @param callable(): T $otherwise 117 | * @param null|callable(T): bool $filter 118 | * @return T 119 | */ 120 | public function firstOr(callable $otherwise, ?callable $filter = null): object 121 | { 122 | return $this->first($filter) ?? $otherwise(); 123 | } 124 | 125 | public function getIterator(): \Traversable 126 | { 127 | return new \ArrayIterator($this->items); 128 | } 129 | 130 | public function count(): int 131 | { 132 | return \count($this->items); 133 | } 134 | 135 | /** 136 | * @return $this 137 | */ 138 | public function whenEmpty(callable $then): self 139 | { 140 | if ($this->empty()) { 141 | $then(); 142 | } 143 | 144 | return $this; 145 | } 146 | 147 | public function empty(): bool 148 | { 149 | return $this->items === []; 150 | } 151 | 152 | /** 153 | * @return array 154 | */ 155 | public function toArray(): array 156 | { 157 | return \array_values($this->items); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/Repository/GitHub/GitHubAsset.php: -------------------------------------------------------------------------------- 1 | client = $client; 31 | 32 | parent::__construct($name, $uri); 33 | } 34 | 35 | /** 36 | * @param GitHubAssetApiResponse $asset 37 | * @return static 38 | * 39 | * @psalm-suppress DocblockTypeContradiction 40 | */ 41 | public static function fromApiResponse(HttpClientInterface $client, array $asset): self 42 | { 43 | // Validate name 44 | if (! isset($asset['name']) || ! \is_string($asset['name'])) { 45 | throw new \InvalidArgumentException( 46 | 'Passed array must contain "name" value of type string', 47 | ); 48 | } 49 | 50 | // Validate uri 51 | if (! isset($asset['browser_download_url']) || ! \is_string($asset['browser_download_url'])) { 52 | throw new \InvalidArgumentException( 53 | 'Passed array must contain "browser_download_url" key of type string', 54 | ); 55 | } 56 | 57 | return new self($client, $asset['name'], $asset['browser_download_url']); 58 | } 59 | 60 | /** 61 | * 62 | * @throws ExceptionInterface 63 | */ 64 | public function download(?\Closure $progress = null): \Traversable 65 | { 66 | $response = $this->client->request('GET', $this->getUri(), [ 67 | 'on_progress' => $progress, 68 | ]); 69 | 70 | foreach ($this->client->stream($response) as $chunk) { 71 | yield $chunk->getContent(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Repository/GitHub/GitHubRelease.php: -------------------------------------------------------------------------------- 1 | 25 | * } 26 | */ 27 | final class GitHubRelease extends Release 28 | { 29 | private HttpClientInterface $client; 30 | 31 | /** 32 | * @param iterable|array $assets 33 | */ 34 | public function __construct( 35 | HttpClientInterface $client, 36 | string $name, 37 | string $version, 38 | string $repository, 39 | iterable $assets = [], 40 | ) { 41 | $this->client = $client; 42 | 43 | parent::__construct($name, $version, $repository, $assets); 44 | } 45 | 46 | /** 47 | * @param GitHubReleaseApiResponse $release 48 | * @return static 49 | */ 50 | public static function fromApiResponse(GitHubRepository $repository, HttpClientInterface $client, array $release): self 51 | { 52 | if (! isset($release['name'])) { 53 | throw new \InvalidArgumentException( 54 | 'Passed array must contain "name" value of type string', 55 | ); 56 | } 57 | 58 | $instantiator = static function () use ($client, $release): \Generator { 59 | foreach ($release['assets'] ?? [] as $item) { 60 | yield GitHubAsset::fromApiResponse($client, $item); 61 | } 62 | }; 63 | 64 | $name = self::getTagName($release); 65 | $version = $release['tag_name'] ?? $release['name']; 66 | 67 | return new self($client, $name, $version, $repository->getName(), AssetsCollection::from($instantiator)); 68 | } 69 | 70 | public function getConfig(): string 71 | { 72 | $config = \vsprintf('https://raw.githubusercontent.com/%s/%s/.rr.yaml', [ 73 | $this->getRepositoryName(), 74 | $this->getVersion(), 75 | ]); 76 | 77 | $response = $this->client->request('GET', $config); 78 | 79 | return $response->getContent(); 80 | } 81 | 82 | /** 83 | * Returns pretty-formatted tag (release) name. 84 | * 85 | * Note: The return value is "pretty", but that does not mean that the 86 | * tag physically exists. 87 | * 88 | * @param array { tag_name: string, name: string } $release 89 | */ 90 | private static function getTagName(array $release): string 91 | { 92 | $parser = new VersionParser(); 93 | 94 | try { 95 | return $parser->normalize($release['tag_name']); 96 | } catch (\Throwable $e) { 97 | try { 98 | return $parser->normalize($release['name']); 99 | } catch (\Throwable $e) { 100 | return 'dev-' . $release['tag_name']; 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Repository/GitHub/GitHubRepository.php: -------------------------------------------------------------------------------- 1 | 'application/vnd.github.v3+json', 40 | ]; 41 | 42 | public function __construct(string $owner, string $repository, ?HttpClientInterface $client = null) 43 | { 44 | $this->name = $owner . '/' . $repository; 45 | $this->client = $client ?? HttpClient::create(); 46 | } 47 | 48 | public static function create(string $owner, string $name, ?HttpClientInterface $client = null): GitHubRepository 49 | { 50 | return new GitHubRepository($owner, $name, $client); 51 | } 52 | 53 | /** 54 | * 55 | * @throws ExceptionInterface 56 | */ 57 | public function getReleases(): ReleasesCollection 58 | { 59 | return ReleasesCollection::from(function () { 60 | $page = 0; 61 | 62 | do { 63 | $response = $this->releasesRequest(++$page); 64 | 65 | /** @psalm-var GitHubReleaseApiResponse $data */ 66 | foreach ($response->toArray() as $data) { 67 | yield GitHubRelease::fromApiResponse($this, $this->client, $data); 68 | } 69 | } while ($this->hasNextPage($response)); 70 | }); 71 | } 72 | 73 | public function getName(): string 74 | { 75 | return $this->name; 76 | } 77 | 78 | /** 79 | * @throws TransportExceptionInterface 80 | * @see HttpClientInterface::request() 81 | */ 82 | protected function request(string $method, string $uri, array $options = []): ResponseInterface 83 | { 84 | // Merge headers with defaults 85 | $options['headers'] = \array_merge($this->headers, (array) ($options['headers'] ?? [])); 86 | 87 | return $this->client->request($method, $uri, $options); 88 | } 89 | 90 | /** 91 | * @param positive-int $page 92 | * @throws TransportExceptionInterface 93 | */ 94 | private function releasesRequest(int $page): ResponseInterface 95 | { 96 | return $this->request('GET', $this->uri(self::URL_RELEASES), [ 97 | 'query' => [ 98 | 'page' => $page, 99 | ], 100 | ]); 101 | } 102 | 103 | private function uri(string $pattern): string 104 | { 105 | return \sprintf($pattern, $this->getName()); 106 | } 107 | 108 | /** 109 | * @throws ExceptionInterface 110 | */ 111 | private function hasNextPage(ResponseInterface $response): bool 112 | { 113 | $headers = $response->getHeaders(); 114 | $link = $headers['link'] ?? []; 115 | 116 | if (! isset($link[0])) { 117 | return false; 118 | } 119 | 120 | return \str_contains($link[0], 'rel="next"'); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/Repository/Release.php: -------------------------------------------------------------------------------- 1 | version = $version; 33 | $this->repository = $repository; 34 | 35 | $this->name = $this->simplifyReleaseName($name); 36 | $this->assets = AssetsCollection::create($assets); 37 | 38 | $this->stability = $this->parseStability($version); 39 | } 40 | 41 | public function getName(): string 42 | { 43 | return $this->name; 44 | } 45 | 46 | public function getVersion(): string 47 | { 48 | return $this->version; 49 | } 50 | 51 | public function getRepositoryName(): string 52 | { 53 | return $this->repository; 54 | } 55 | 56 | #[ExpectedValues(valuesFromClass: Stability::class)] 57 | public function getStability(): string 58 | { 59 | return $this->stability; 60 | } 61 | 62 | public function getAssets(): AssetsCollection 63 | { 64 | return $this->assets; 65 | } 66 | 67 | public function satisfies(string $constraint): bool 68 | { 69 | return Semver::satisfies($this->getName(), $constraint); 70 | } 71 | 72 | private function parseStability(string $version): string 73 | { 74 | return VersionParser::parseStability($version); 75 | } 76 | 77 | private function simplifyReleaseName(string $name): string 78 | { 79 | $version = (new VersionParser())->normalize($name); 80 | 81 | $parts = \explode('-', $version); 82 | $number = \substr($parts[0], 0, -2); 83 | 84 | return isset($parts[1]) 85 | ? $number . '-' . $parts[1] 86 | : $number 87 | ; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Repository/ReleaseInterface.php: -------------------------------------------------------------------------------- 1 | 46 | */ 47 | public function getAssets(): AssetsCollection; 48 | 49 | public function satisfies(string $constraint): bool; 50 | 51 | public function getConfig(): string; 52 | } 53 | -------------------------------------------------------------------------------- /src/Repository/ReleasesCollection.php: -------------------------------------------------------------------------------- 1 | 18 | * @psalm-import-type StabilityType from Stability 19 | */ 20 | final class ReleasesCollection extends Collection 21 | { 22 | /** 23 | * @return $this 24 | */ 25 | public function satisfies(string ...$constraints): self 26 | { 27 | $result = $this; 28 | 29 | foreach ($this->constraints($constraints) as $constraint) { 30 | $result = $result->filter(static fn(ReleaseInterface $r): bool => $r->satisfies($constraint)); 31 | } 32 | 33 | return $result; 34 | } 35 | 36 | /** 37 | * @return $this 38 | */ 39 | public function notSatisfies(string ...$constraints): self 40 | { 41 | $result = $this; 42 | 43 | foreach ($this->constraints($constraints) as $constraint) { 44 | $result = $result->except(static fn(ReleaseInterface $r): bool => $r->satisfies($constraint)); 45 | } 46 | 47 | return $result; 48 | } 49 | 50 | /** 51 | * @return $this 52 | */ 53 | public function withAssets(): self 54 | { 55 | return $this->filter( 56 | static fn(ReleaseInterface $r): bool => ! $r->getAssets() 57 | ->empty(), 58 | ); 59 | } 60 | 61 | /** 62 | * @return $this 63 | */ 64 | public function sortByVersion(): self 65 | { 66 | $result = $this->items; 67 | 68 | $sort = function (ReleaseInterface $a, ReleaseInterface $b): int { 69 | return \version_compare($this->comparisonVersionString($b), $this->comparisonVersionString($a)); 70 | }; 71 | 72 | \uasort($result, $sort); 73 | 74 | return new self($result); 75 | } 76 | 77 | /** 78 | * @return $this 79 | */ 80 | public function stable(): self 81 | { 82 | return $this->stability(Stability::STABILITY_STABLE); 83 | } 84 | 85 | /** 86 | * @param StabilityType $stability 87 | * @return $this 88 | */ 89 | public function stability(string $stability): self 90 | { 91 | $filter = static fn(ReleaseInterface $rel): bool => $rel->getStability() === $stability; 92 | 93 | return $this->filter($filter); 94 | } 95 | 96 | /** 97 | * @param StabilityType $stability 98 | * @return $this 99 | */ 100 | public function minimumStability(string $stability): self 101 | { 102 | $weight = Stability::toInt($stability); 103 | 104 | return $this->filter(static function (ReleaseInterface $release) use ($weight): bool { 105 | return Stability::toInt($release->getStability()) >= $weight; 106 | }); 107 | } 108 | 109 | /** 110 | * @param array $constraints 111 | * @return array 112 | */ 113 | private function constraints(array $constraints): array 114 | { 115 | $result = []; 116 | 117 | foreach ($constraints as $constraint) { 118 | foreach (\explode('|', $constraint) as $expression) { 119 | $result[] = $expression; 120 | } 121 | } 122 | 123 | return \array_unique( 124 | \array_filter( 125 | \array_map('\\trim', $result), 126 | ), 127 | ); 128 | } 129 | 130 | private function comparisonVersionString(ReleaseInterface $release): string 131 | { 132 | $stability = $release->getStability(); 133 | $weight = Stability::toInt($stability); 134 | 135 | return \str_replace('-' . $stability, '.' . $weight . '.', $release->getVersion()); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Repository/RepositoriesCollection.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | private array $repositories; 20 | 21 | /** 22 | * @param array $repositories 23 | */ 24 | public function __construct(array $repositories) 25 | { 26 | $this->repositories = $repositories; 27 | } 28 | 29 | public function getName(): string 30 | { 31 | return 'unknown/unknown'; 32 | } 33 | 34 | public function getReleases(): ReleasesCollection 35 | { 36 | return ReleasesCollection::from(function () { 37 | foreach ($this->repositories as $repository) { 38 | yield from $repository->getReleases(); 39 | } 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Repository/RepositoryInterface.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | public function getReleases(): ReleasesCollection; 22 | } 23 | -------------------------------------------------------------------------------- /src/Repository/Version1/StaticRepository.php: -------------------------------------------------------------------------------- 1 | }> 27 | */ 28 | private const RELEASES_DATA = [ 29 | [ 30 | 'name' => '1.9.1', 31 | 'version' => 'v1.9.1', 32 | 'assets' => [ 33 | [ 34 | 'name' => 'roadrunner-1.9.1-darwin-amd64.zip', 35 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.9.1/roadrunner-1.9.1-darwin-amd64.zip', 36 | ], 37 | [ 38 | 'name' => 'roadrunner-1.9.1-freebsd-amd64.zip', 39 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.9.1/roadrunner-1.9.1-freebsd-amd64.zip', 40 | ], 41 | [ 42 | 'name' => 'roadrunner-1.9.1-linux-amd64.tar.gz', 43 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.9.1/roadrunner-1.9.1-linux-amd64.tar.gz', 44 | ], 45 | [ 46 | 'name' => 'roadrunner-1.9.1-unknown-musl-amd64.zip', 47 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.9.1/roadrunner-1.9.1-unknown-musl-amd64.zip', 48 | ], 49 | [ 50 | 'name' => 'roadrunner-1.9.1-windows-amd64.zip', 51 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.9.1/roadrunner-1.9.1-windows-amd64.zip', 52 | ], 53 | ], 54 | ], 55 | [ 56 | 'name' => '1.9.0', 57 | 'version' => 'v1.9.0', 58 | 'assets' => [ 59 | [ 60 | 'name' => 'roadrunner-1.9.0-darwin-amd64.zip', 61 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.9.0/roadrunner-1.9.0-darwin-amd64.zip', 62 | ], 63 | [ 64 | 'name' => 'roadrunner-1.9.0-freebsd-amd64.zip', 65 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.9.0/roadrunner-1.9.0-freebsd-amd64.zip', 66 | ], 67 | [ 68 | 'name' => 'roadrunner-1.9.0-linux-amd64.tar.gz', 69 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.9.0/roadrunner-1.9.0-linux-amd64.tar.gz', 70 | ], 71 | [ 72 | 'name' => 'roadrunner-1.9.0-unknown-musl-amd64.zip', 73 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.9.0/roadrunner-1.9.0-unknown-musl-amd64.zip', 74 | ], 75 | [ 76 | 'name' => 'roadrunner-1.9.0-windows-amd64.zip', 77 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.9.0/roadrunner-1.9.0-windows-amd64.zip', 78 | ], 79 | ], 80 | ], 81 | [ 82 | 'name' => '1.8.4', 83 | 'version' => 'v1.8.4', 84 | 'assets' => [ 85 | [ 86 | 'name' => 'roadrunner-1.8.4-darwin-amd64.zip', 87 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.4/roadrunner-1.8.4-darwin-amd64.zip', 88 | ], 89 | [ 90 | 'name' => 'roadrunner-1.8.4-freebsd-amd64.zip', 91 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.4/roadrunner-1.8.4-freebsd-amd64.zip', 92 | ], 93 | [ 94 | 'name' => 'roadrunner-1.8.4-linux-amd64.tar.gz', 95 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.4/roadrunner-1.8.4-linux-amd64.tar.gz', 96 | ], 97 | [ 98 | 'name' => 'roadrunner-1.8.4-unknown-musl-amd64.zip', 99 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.4/roadrunner-1.8.4-unknown-musl-amd64.zip', 100 | ], 101 | [ 102 | 'name' => 'roadrunner-1.8.4-windows-amd64.zip', 103 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.4/roadrunner-1.8.4-windows-amd64.zip', 104 | ], 105 | ], 106 | ], 107 | [ 108 | 'name' => '1.8.3', 109 | 'version' => 'v1.8.3', 110 | 'assets' => [ 111 | [ 112 | 'name' => 'roadrunner-1.8.3-darwin-amd64.zip', 113 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.3/roadrunner-1.8.3-darwin-amd64.zip', 114 | ], 115 | [ 116 | 'name' => 'roadrunner-1.8.3-freebsd-amd64.zip', 117 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.3/roadrunner-1.8.3-freebsd-amd64.zip', 118 | ], 119 | [ 120 | 'name' => 'roadrunner-1.8.3-linux-amd64.tar.gz', 121 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.3/roadrunner-1.8.3-linux-amd64.tar.gz', 122 | ], 123 | [ 124 | 'name' => 'roadrunner-1.8.3-unknown-musl-amd64.zip', 125 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.3/roadrunner-1.8.3-unknown-musl-amd64.zip', 126 | ], 127 | [ 128 | 'name' => 'roadrunner-1.8.3-windows-amd64.zip', 129 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.3/roadrunner-1.8.3-windows-amd64.zip', 130 | ], 131 | ], 132 | ], 133 | [ 134 | 'name' => '1.8.2', 135 | 'version' => 'v1.8.2', 136 | 'assets' => [ 137 | [ 138 | 'name' => 'roadrunner-1.8.2-darwin-amd64.zip', 139 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.2/roadrunner-1.8.2-darwin-amd64.zip', 140 | ], 141 | [ 142 | 'name' => 'roadrunner-1.8.2-freebsd-amd64.zip', 143 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.2/roadrunner-1.8.2-freebsd-amd64.zip', 144 | ], 145 | [ 146 | 'name' => 'roadrunner-1.8.2-linux-amd64.tar.gz', 147 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.2/roadrunner-1.8.2-linux-amd64.tar.gz', 148 | ], 149 | [ 150 | 'name' => 'roadrunner-1.8.2-unknown-musl-amd64.zip', 151 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.2/roadrunner-1.8.2-unknown-musl-amd64.zip', 152 | ], 153 | [ 154 | 'name' => 'roadrunner-1.8.2-windows-amd64.zip', 155 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.2/roadrunner-1.8.2-windows-amd64.zip', 156 | ], 157 | ], 158 | ], 159 | [ 160 | 'name' => '1.8.1', 161 | 'version' => 'v1.8.1', 162 | 'assets' => [ 163 | [ 164 | 'name' => 'roadrunner-1.8.1-darwin-amd64.zip', 165 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.1/roadrunner-1.8.1-darwin-amd64.zip', 166 | ], 167 | [ 168 | 'name' => 'roadrunner-1.8.1-freebsd-amd64.zip', 169 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.1/roadrunner-1.8.1-freebsd-amd64.zip', 170 | ], 171 | [ 172 | 'name' => 'roadrunner-1.8.1-linux-amd64.tar.gz', 173 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.1/roadrunner-1.8.1-linux-amd64.tar.gz', 174 | ], 175 | [ 176 | 'name' => 'roadrunner-1.8.1-unknown-musl-amd64.zip', 177 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.1/roadrunner-1.8.1-unknown-musl-amd64.zip', 178 | ], 179 | [ 180 | 'name' => 'roadrunner-1.8.1-windows-amd64.zip', 181 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.1/roadrunner-1.8.1-windows-amd64.zip', 182 | ], 183 | ], 184 | ], 185 | [ 186 | 'name' => '1.8.0', 187 | 'version' => 'v1.8.0', 188 | 'assets' => [ 189 | [ 190 | 'name' => 'roadrunner-1.8.0-darwin-amd64.zip', 191 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.0/roadrunner-1.8.0-darwin-amd64.zip', 192 | ], 193 | [ 194 | 'name' => 'roadrunner-1.8.0-freebsd-amd64.zip', 195 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.0/roadrunner-1.8.0-freebsd-amd64.zip', 196 | ], 197 | [ 198 | 'name' => 'roadrunner-1.8.0-linux-amd64.tar.gz', 199 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.0/roadrunner-1.8.0-linux-amd64.tar.gz', 200 | ], 201 | [ 202 | 'name' => 'roadrunner-1.8.0-unknown-musl-amd64.zip', 203 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.0/roadrunner-1.8.0-unknown-musl-amd64.zip', 204 | ], 205 | [ 206 | 'name' => 'roadrunner-1.8.0-windows-amd64.zip', 207 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.8.0/roadrunner-1.8.0-windows-amd64.zip', 208 | ], 209 | ], 210 | ], 211 | [ 212 | 'name' => '1.7.1', 213 | 'version' => 'v1.7.1', 214 | 'assets' => [ 215 | [ 216 | 'name' => 'roadrunner-1.7.1-darwin-amd64.zip', 217 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.7.1/roadrunner-1.7.1-darwin-amd64.zip', 218 | ], 219 | [ 220 | 'name' => 'roadrunner-1.7.1-freebsd-amd64.zip', 221 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.7.1/roadrunner-1.7.1-freebsd-amd64.zip', 222 | ], 223 | [ 224 | 'name' => 'roadrunner-1.7.1-linux-amd64.tar.gz', 225 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.7.1/roadrunner-1.7.1-linux-amd64.tar.gz', 226 | ], 227 | [ 228 | 'name' => 'roadrunner-1.7.1-unknown-musl-amd64.zip', 229 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.7.1/roadrunner-1.7.1-unknown-musl-amd64.zip', 230 | ], 231 | [ 232 | 'name' => 'roadrunner-1.7.1-windows-amd64.zip', 233 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.7.1/roadrunner-1.7.1-windows-amd64.zip', 234 | ], 235 | ], 236 | ], 237 | [ 238 | 'name' => '1.7.0', 239 | 'version' => 'v1.7.0', 240 | 'assets' => [ 241 | [ 242 | 'name' => 'roadrunner-1.7.0-darwin-amd64.zip', 243 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.7.0/roadrunner-1.7.0-darwin-amd64.zip', 244 | ], 245 | [ 246 | 'name' => 'roadrunner-1.7.0-freebsd-amd64.zip', 247 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.7.0/roadrunner-1.7.0-freebsd-amd64.zip', 248 | ], 249 | [ 250 | 'name' => 'roadrunner-1.7.0-linux-amd64.tar.gz', 251 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.7.0/roadrunner-1.7.0-linux-amd64.tar.gz', 252 | ], 253 | [ 254 | 'name' => 'roadrunner-1.7.0-unknown-musl-amd64.zip', 255 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.7.0/roadrunner-1.7.0-unknown-musl-amd64.zip', 256 | ], 257 | [ 258 | 'name' => 'roadrunner-1.7.0-windows-amd64.zip', 259 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.7.0/roadrunner-1.7.0-windows-amd64.zip', 260 | ], 261 | ], 262 | ], 263 | [ 264 | 'name' => '1.6.4', 265 | 'version' => 'v1.6.4', 266 | 'assets' => [ 267 | [ 268 | 'name' => 'roadrunner-1.6.4-darwin-amd64.zip', 269 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.4/roadrunner-1.6.4-darwin-amd64.zip', 270 | ], 271 | [ 272 | 'name' => 'roadrunner-1.6.4-freebsd-amd64.zip', 273 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.4/roadrunner-1.6.4-freebsd-amd64.zip', 274 | ], 275 | [ 276 | 'name' => 'roadrunner-1.6.4-linux-amd64.tar.gz', 277 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.4/roadrunner-1.6.4-linux-amd64.tar.gz', 278 | ], 279 | [ 280 | 'name' => 'roadrunner-1.6.4-unknown-musl-amd64.zip', 281 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.4/roadrunner-1.6.4-unknown-musl-amd64.zip', 282 | ], 283 | [ 284 | 'name' => 'roadrunner-1.6.4-windows-amd64.zip', 285 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.4/roadrunner-1.6.4-windows-amd64.zip', 286 | ], 287 | ], 288 | ], 289 | [ 290 | 'name' => '1.6.3', 291 | 'version' => 'v1.6.3', 292 | 'assets' => [ 293 | [ 294 | 'name' => 'roadrunner-1.6.3-darwin-amd64.zip', 295 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.3/roadrunner-1.6.3-darwin-amd64.zip', 296 | ], 297 | [ 298 | 'name' => 'roadrunner-1.6.3-freebsd-amd64.zip', 299 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.3/roadrunner-1.6.3-freebsd-amd64.zip', 300 | ], 301 | [ 302 | 'name' => 'roadrunner-1.6.3-linux-amd64.tar.gz', 303 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.3/roadrunner-1.6.3-linux-amd64.tar.gz', 304 | ], 305 | [ 306 | 'name' => 'roadrunner-1.6.3-unknown-musl-amd64.zip', 307 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.3/roadrunner-1.6.3-unknown-musl-amd64.zip', 308 | ], 309 | [ 310 | 'name' => 'roadrunner-1.6.3-windows-amd64.zip', 311 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.3/roadrunner-1.6.3-windows-amd64.zip', 312 | ], 313 | ], 314 | ], 315 | [ 316 | 'name' => '1.6.2', 317 | 'version' => 'v1.6.2', 318 | 'assets' => [ 319 | [ 320 | 'name' => 'roadrunner-1.6.2-darwin-amd64.zip', 321 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.2/roadrunner-1.6.2-darwin-amd64.zip', 322 | ], 323 | [ 324 | 'name' => 'roadrunner-1.6.2-freebsd-amd64.zip', 325 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.2/roadrunner-1.6.2-freebsd-amd64.zip', 326 | ], 327 | [ 328 | 'name' => 'roadrunner-1.6.2-linux-amd64.tar.gz', 329 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.2/roadrunner-1.6.2-linux-amd64.tar.gz', 330 | ], 331 | [ 332 | 'name' => 'roadrunner-1.6.2-unknown-musl-amd64.zip', 333 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.2/roadrunner-1.6.2-unknown-musl-amd64.zip', 334 | ], 335 | [ 336 | 'name' => 'roadrunner-1.6.2-windows-amd64.zip', 337 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.2/roadrunner-1.6.2-windows-amd64.zip', 338 | ], 339 | ], 340 | ], 341 | [ 342 | 'name' => '1.6.1', 343 | 'version' => 'v1.6.1', 344 | 'assets' => [ 345 | [ 346 | 'name' => 'roadrunner-1.6.1-darwin-amd64.zip', 347 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.1/roadrunner-1.6.1-darwin-amd64.zip', 348 | ], 349 | [ 350 | 'name' => 'roadrunner-1.6.1-freebsd-amd64.zip', 351 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.1/roadrunner-1.6.1-freebsd-amd64.zip', 352 | ], 353 | [ 354 | 'name' => 'roadrunner-1.6.1-linux-amd64.tar.gz', 355 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.1/roadrunner-1.6.1-linux-amd64.tar.gz', 356 | ], 357 | [ 358 | 'name' => 'roadrunner-1.6.1-windows-amd64.zip', 359 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.1/roadrunner-1.6.1-windows-amd64.zip', 360 | ], 361 | ], 362 | ], 363 | [ 364 | 'name' => '1.6.0', 365 | 'version' => 'v1.6.0', 366 | 'assets' => [ 367 | [ 368 | 'name' => 'roadrunner-1.6.0-darwin-amd64.zip', 369 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.0/roadrunner-1.6.0-darwin-amd64.zip', 370 | ], 371 | [ 372 | 'name' => 'roadrunner-1.6.0-freebsd-amd64.zip', 373 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.0/roadrunner-1.6.0-freebsd-amd64.zip', 374 | ], 375 | [ 376 | 'name' => 'roadrunner-1.6.0-linux-amd64.tar.gz', 377 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.0/roadrunner-1.6.0-linux-amd64.tar.gz', 378 | ], 379 | [ 380 | 'name' => 'roadrunner-1.6.0-windows-amd64.zip', 381 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.6.0/roadrunner-1.6.0-windows-amd64.zip', 382 | ], 383 | ], 384 | ], 385 | [ 386 | 'name' => '1.5.3', 387 | 'version' => 'v1.5.3', 388 | 'assets' => [ 389 | [ 390 | 'name' => 'roadrunner-1.5.3-darwin-amd64.zip', 391 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.3/roadrunner-1.5.3-darwin-amd64.zip', 392 | ], 393 | [ 394 | 'name' => 'roadrunner-1.5.3-freebsd-amd64.zip', 395 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.3/roadrunner-1.5.3-freebsd-amd64.zip', 396 | ], 397 | [ 398 | 'name' => 'roadrunner-1.5.3-linux-amd64.tar.gz', 399 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.3/roadrunner-1.5.3-linux-amd64.tar.gz', 400 | ], 401 | [ 402 | 'name' => 'roadrunner-1.5.3-windows-amd64.zip', 403 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.3/roadrunner-1.5.3-windows-amd64.zip', 404 | ], 405 | ], 406 | ], 407 | [ 408 | 'name' => '1.5.2', 409 | 'version' => 'v1.5.2', 410 | 'assets' => [ 411 | [ 412 | 'name' => 'roadrunner-1.5.2-darwin-amd64.zip', 413 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.2/roadrunner-1.5.2-darwin-amd64.zip', 414 | ], 415 | [ 416 | 'name' => 'roadrunner-1.5.2-freebsd-amd64.zip', 417 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.2/roadrunner-1.5.2-freebsd-amd64.zip', 418 | ], 419 | [ 420 | 'name' => 'roadrunner-1.5.2-linux-amd64.tar.gz', 421 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.2/roadrunner-1.5.2-linux-amd64.tar.gz', 422 | ], 423 | [ 424 | 'name' => 'roadrunner-1.5.2-windows-amd64.zip', 425 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.2/roadrunner-1.5.2-windows-amd64.zip', 426 | ], 427 | ], 428 | ], 429 | [ 430 | 'name' => '1.5.1', 431 | 'version' => 'v1.5.1', 432 | 'assets' => [ 433 | [ 434 | 'name' => 'roadrunner-1.5.1-darwin-amd64.zip', 435 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.1/roadrunner-1.5.1-darwin-amd64.zip', 436 | ], 437 | [ 438 | 'name' => 'roadrunner-1.5.1-freebsd-amd64.zip', 439 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.1/roadrunner-1.5.1-freebsd-amd64.zip', 440 | ], 441 | [ 442 | 'name' => 'roadrunner-1.5.1-linux-amd64.tar.gz', 443 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.1/roadrunner-1.5.1-linux-amd64.tar.gz', 444 | ], 445 | [ 446 | 'name' => 'roadrunner-1.5.1-windows-amd64.zip', 447 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.1/roadrunner-1.5.1-windows-amd64.zip', 448 | ], 449 | ], 450 | ], 451 | [ 452 | 'name' => '1.5.0', 453 | 'version' => 'v1.5.0', 454 | 'assets' => [ 455 | [ 456 | 'name' => 'roadrunner-1.5.0-darwin-amd64.zip', 457 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.0/roadrunner-1.5.0-darwin-amd64.zip', 458 | ], 459 | [ 460 | 'name' => 'roadrunner-1.5.0-freebsd-amd64.zip', 461 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.0/roadrunner-1.5.0-freebsd-amd64.zip', 462 | ], 463 | [ 464 | 'name' => 'roadrunner-1.5.0-linux-amd64.tar.gz', 465 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.0/roadrunner-1.5.0-linux-amd64.tar.gz', 466 | ], 467 | [ 468 | 'name' => 'roadrunner-1.5.0-windows-amd64.zip', 469 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.5.0/roadrunner-1.5.0-windows-amd64.zip', 470 | ], 471 | ], 472 | ], 473 | [ 474 | 'name' => '1.4.8', 475 | 'version' => 'v1.4.8', 476 | 'assets' => [ 477 | [ 478 | 'name' => 'roadrunner-1.4.8-darwin-amd64.zip', 479 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.8/roadrunner-1.4.8-darwin-amd64.zip', 480 | ], 481 | [ 482 | 'name' => 'roadrunner-1.4.8-freebsd-amd64.zip', 483 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.8/roadrunner-1.4.8-freebsd-amd64.zip', 484 | ], 485 | [ 486 | 'name' => 'roadrunner-1.4.8-linux-amd64.tar.gz', 487 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.8/roadrunner-1.4.8-linux-amd64.tar.gz', 488 | ], 489 | [ 490 | 'name' => 'roadrunner-1.4.8-windows-amd64.zip', 491 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.8/roadrunner-1.4.8-windows-amd64.zip', 492 | ], 493 | ], 494 | ], 495 | [ 496 | 'name' => '1.4.7', 497 | 'version' => 'v1.4.7', 498 | 'assets' => [ 499 | [ 500 | 'name' => 'roadrunner-1.4.7-darwin-amd64.zip', 501 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.7/roadrunner-1.4.7-darwin-amd64.zip', 502 | ], 503 | [ 504 | 'name' => 'roadrunner-1.4.7-freebsd-amd64.zip', 505 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.7/roadrunner-1.4.7-freebsd-amd64.zip', 506 | ], 507 | [ 508 | 'name' => 'roadrunner-1.4.7-linux-amd64.tar.gz', 509 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.7/roadrunner-1.4.7-linux-amd64.tar.gz', 510 | ], 511 | [ 512 | 'name' => 'roadrunner-1.4.7-windows-amd64.zip', 513 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.7/roadrunner-1.4.7-windows-amd64.zip', 514 | ], 515 | ], 516 | ], 517 | [ 518 | 'name' => '1.4.6', 519 | 'version' => 'v1.4.6', 520 | 'assets' => [ 521 | [ 522 | 'name' => 'roadrunner-1.4.6-darwin-amd64.zip', 523 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.6/roadrunner-1.4.6-darwin-amd64.zip', 524 | ], 525 | [ 526 | 'name' => 'roadrunner-1.4.6-freebsd-amd64.zip', 527 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.6/roadrunner-1.4.6-freebsd-amd64.zip', 528 | ], 529 | [ 530 | 'name' => 'roadrunner-1.4.6-linux-amd64.tar.gz', 531 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.6/roadrunner-1.4.6-linux-amd64.tar.gz', 532 | ], 533 | [ 534 | 'name' => 'roadrunner-1.4.6-windows-amd64.zip', 535 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.6/roadrunner-1.4.6-windows-amd64.zip', 536 | ], 537 | ], 538 | ], 539 | [ 540 | 'name' => '1.4.5', 541 | 'version' => 'v1.4.5', 542 | 'assets' => [ 543 | [ 544 | 'name' => 'roadrunner-1.4.5-darwin-amd64.zip', 545 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.5/roadrunner-1.4.5-darwin-amd64.zip', 546 | ], 547 | [ 548 | 'name' => 'roadrunner-1.4.5-freebsd-amd64.zip', 549 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.5/roadrunner-1.4.5-freebsd-amd64.zip', 550 | ], 551 | [ 552 | 'name' => 'roadrunner-1.4.5-linux-amd64.tar.gz', 553 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.5/roadrunner-1.4.5-linux-amd64.tar.gz', 554 | ], 555 | [ 556 | 'name' => 'roadrunner-1.4.5-windows-amd64.zip', 557 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.5/roadrunner-1.4.5-windows-amd64.zip', 558 | ], 559 | ], 560 | ], 561 | [ 562 | 'name' => '1.4.4', 563 | 'version' => 'v1.4.4', 564 | 'assets' => [ 565 | [ 566 | 'name' => 'roadrunner-1.4.4-darwin-amd64.zip', 567 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.4/roadrunner-1.4.4-darwin-amd64.zip', 568 | ], 569 | [ 570 | 'name' => 'roadrunner-1.4.4-freebsd-amd64.zip', 571 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.4/roadrunner-1.4.4-freebsd-amd64.zip', 572 | ], 573 | [ 574 | 'name' => 'roadrunner-1.4.4-linux-amd64.tar.gz', 575 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.4/roadrunner-1.4.4-linux-amd64.tar.gz', 576 | ], 577 | [ 578 | 'name' => 'roadrunner-1.4.4-windows-amd64.zip', 579 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.4/roadrunner-1.4.4-windows-amd64.zip', 580 | ], 581 | ], 582 | ], 583 | [ 584 | 'name' => '1.4.3', 585 | 'version' => 'v1.4.3', 586 | 'assets' => [ 587 | [ 588 | 'name' => 'roadrunner-1.4.3-darwin-amd64.zip', 589 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.3/roadrunner-1.4.3-darwin-amd64.zip', 590 | ], 591 | [ 592 | 'name' => 'roadrunner-1.4.3-freebsd-amd64.zip', 593 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.3/roadrunner-1.4.3-freebsd-amd64.zip', 594 | ], 595 | [ 596 | 'name' => 'roadrunner-1.4.3-linux-amd64.tar.gz', 597 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.3/roadrunner-1.4.3-linux-amd64.tar.gz', 598 | ], 599 | [ 600 | 'name' => 'roadrunner-1.4.3-windows-amd64.zip', 601 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.3/roadrunner-1.4.3-windows-amd64.zip', 602 | ], 603 | ], 604 | ], 605 | [ 606 | 'name' => '1.4.2', 607 | 'version' => 'v1.4.2', 608 | 'assets' => [ 609 | [ 610 | 'name' => 'roadrunner-1.4.2-darwin-amd64.zip', 611 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.2/roadrunner-1.4.2-darwin-amd64.zip', 612 | ], 613 | [ 614 | 'name' => 'roadrunner-1.4.2-freebsd-amd64.zip', 615 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.2/roadrunner-1.4.2-freebsd-amd64.zip', 616 | ], 617 | [ 618 | 'name' => 'roadrunner-1.4.2-linux-amd64.tar.gz', 619 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.2/roadrunner-1.4.2-linux-amd64.tar.gz', 620 | ], 621 | [ 622 | 'name' => 'roadrunner-1.4.2-windows-amd64.zip', 623 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.2/roadrunner-1.4.2-windows-amd64.zip', 624 | ], 625 | ], 626 | ], 627 | [ 628 | 'name' => '1.4.1', 629 | 'version' => 'v1.4.1', 630 | 'assets' => [ 631 | [ 632 | 'name' => 'roadrunner-1.4.1-darwin-amd64.zip', 633 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.1/roadrunner-1.4.1-darwin-amd64.zip', 634 | ], 635 | [ 636 | 'name' => 'roadrunner-1.4.1-freebsd-amd64.zip', 637 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.1/roadrunner-1.4.1-freebsd-amd64.zip', 638 | ], 639 | [ 640 | 'name' => 'roadrunner-1.4.1-linux-amd64.tar.gz', 641 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.1/roadrunner-1.4.1-linux-amd64.tar.gz', 642 | ], 643 | [ 644 | 'name' => 'roadrunner-1.4.1-windows-amd64.zip', 645 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.1/roadrunner-1.4.1-windows-amd64.zip', 646 | ], 647 | ], 648 | ], 649 | [ 650 | 'name' => '1.4.0', 651 | 'version' => 'v1.4.0', 652 | 'assets' => [ 653 | [ 654 | 'name' => 'roadrunner-1.4.0-darwin-amd64.zip', 655 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.0/roadrunner-1.4.0-darwin-amd64.zip', 656 | ], 657 | [ 658 | 'name' => 'roadrunner-1.4.0-freebsd-amd64.zip', 659 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.0/roadrunner-1.4.0-freebsd-amd64.zip', 660 | ], 661 | [ 662 | 'name' => 'roadrunner-1.4.0-linux-amd64.tar.gz', 663 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.0/roadrunner-1.4.0-linux-amd64.tar.gz', 664 | ], 665 | [ 666 | 'name' => 'roadrunner-1.4.0-windows-amd64.zip', 667 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.4.0/roadrunner-1.4.0-windows-amd64.zip', 668 | ], 669 | ], 670 | ], 671 | [ 672 | 'name' => '1.3.7', 673 | 'version' => 'v1.3.7', 674 | 'assets' => [ 675 | [ 676 | 'name' => 'roadrunner-1.3.7-darwin-amd64.zip', 677 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.7/roadrunner-1.3.7-darwin-amd64.zip', 678 | ], 679 | [ 680 | 'name' => 'roadrunner-1.3.7-freebsd-amd64.zip', 681 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.7/roadrunner-1.3.7-freebsd-amd64.zip', 682 | ], 683 | [ 684 | 'name' => 'roadrunner-1.3.7-linux-amd64.tar.gz', 685 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.7/roadrunner-1.3.7-linux-amd64.tar.gz', 686 | ], 687 | [ 688 | 'name' => 'roadrunner-1.3.7-windows-amd64.zip', 689 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.7/roadrunner-1.3.7-windows-amd64.zip', 690 | ], 691 | ], 692 | ], 693 | [ 694 | 'name' => '1.3.6', 695 | 'version' => 'v1.3.6', 696 | 'assets' => [ 697 | [ 698 | 'name' => 'roadrunner-1.3.6-darwin-amd64.zip', 699 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.6/roadrunner-1.3.6-darwin-amd64.zip', 700 | ], 701 | [ 702 | 'name' => 'roadrunner-1.3.6-freebsd-amd64.zip', 703 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.6/roadrunner-1.3.6-freebsd-amd64.zip', 704 | ], 705 | [ 706 | 'name' => 'roadrunner-1.3.6-linux-amd64.tar.gz', 707 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.6/roadrunner-1.3.6-linux-amd64.tar.gz', 708 | ], 709 | [ 710 | 'name' => 'roadrunner-1.3.6-windows-amd64.zip', 711 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.6/roadrunner-1.3.6-windows-amd64.zip', 712 | ], 713 | ], 714 | ], 715 | [ 716 | 'name' => '1.3.5', 717 | 'version' => 'v1.3.5', 718 | 'assets' => [ 719 | [ 720 | 'name' => 'roadrunner-1.3.5-darwin-amd64.zip', 721 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.5/roadrunner-1.3.5-darwin-amd64.zip', 722 | ], 723 | [ 724 | 'name' => 'roadrunner-1.3.5-freebsd-amd64.zip', 725 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.5/roadrunner-1.3.5-freebsd-amd64.zip', 726 | ], 727 | [ 728 | 'name' => 'roadrunner-1.3.5-linux-amd64.tar.gz', 729 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.5/roadrunner-1.3.5-linux-amd64.tar.gz', 730 | ], 731 | [ 732 | 'name' => 'roadrunner-1.3.5-windows-amd64.zip', 733 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.5/roadrunner-1.3.5-windows-amd64.zip', 734 | ], 735 | ], 736 | ], 737 | [ 738 | 'name' => '1.3.4', 739 | 'version' => 'v1.3.4', 740 | 'assets' => [ 741 | [ 742 | 'name' => 'roadrunner-1.3.4-darwin-amd64.zip', 743 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.4/roadrunner-1.3.4-darwin-amd64.zip', 744 | ], 745 | [ 746 | 'name' => 'roadrunner-1.3.4-freebsd-amd64.zip', 747 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.4/roadrunner-1.3.4-freebsd-amd64.zip', 748 | ], 749 | [ 750 | 'name' => 'roadrunner-1.3.4-linux-amd64.tar.gz', 751 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.4/roadrunner-1.3.4-linux-amd64.tar.gz', 752 | ], 753 | [ 754 | 'name' => 'roadrunner-1.3.4-windows-amd64.zip', 755 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.4/roadrunner-1.3.4-windows-amd64.zip', 756 | ], 757 | ], 758 | ], 759 | [ 760 | 'name' => '1.3.3', 761 | 'version' => 'v1.3.3', 762 | 'assets' => [ 763 | [ 764 | 'name' => 'roadrunner-1.3.3-darwin-amd64.zip', 765 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.3/roadrunner-1.3.3-darwin-amd64.zip', 766 | ], 767 | [ 768 | 'name' => 'roadrunner-1.3.3-freebsd-amd64.zip', 769 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.3/roadrunner-1.3.3-freebsd-amd64.zip', 770 | ], 771 | [ 772 | 'name' => 'roadrunner-1.3.3-linux-amd64.tar.gz', 773 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.3/roadrunner-1.3.3-linux-amd64.tar.gz', 774 | ], 775 | [ 776 | 'name' => 'roadrunner-1.3.3-windows-amd64.zip', 777 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.3/roadrunner-1.3.3-windows-amd64.zip', 778 | ], 779 | ], 780 | ], 781 | [ 782 | 'name' => '1.3.2', 783 | 'version' => 'v1.3.2', 784 | 'assets' => [ 785 | [ 786 | 'name' => 'roadrunner-1.3.2-darwin-amd64.zip', 787 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.2/roadrunner-1.3.2-darwin-amd64.zip', 788 | ], 789 | [ 790 | 'name' => 'roadrunner-1.3.2-freebsd-amd64.zip', 791 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.2/roadrunner-1.3.2-freebsd-amd64.zip', 792 | ], 793 | [ 794 | 'name' => 'roadrunner-1.3.2-linux-amd64.tar.gz', 795 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.2/roadrunner-1.3.2-linux-amd64.tar.gz', 796 | ], 797 | [ 798 | 'name' => 'roadrunner-1.3.2-windows-amd64.zip', 799 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.2/roadrunner-1.3.2-windows-amd64.zip', 800 | ], 801 | ], 802 | ], 803 | [ 804 | 'name' => '1.3.1', 805 | 'version' => 'v1.3.1', 806 | 'assets' => [ 807 | [ 808 | 'name' => 'roadrunner-1.3.1-darwin-amd64.zip', 809 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.1/roadrunner-1.3.1-darwin-amd64.zip', 810 | ], 811 | [ 812 | 'name' => 'roadrunner-1.3.1-freebsd-amd64.zip', 813 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.1/roadrunner-1.3.1-freebsd-amd64.zip', 814 | ], 815 | [ 816 | 'name' => 'roadrunner-1.3.1-linux-amd64.tar.gz', 817 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.1/roadrunner-1.3.1-linux-amd64.tar.gz', 818 | ], 819 | [ 820 | 'name' => 'roadrunner-1.3.1-windows-amd64.zip', 821 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.1/roadrunner-1.3.1-windows-amd64.zip', 822 | ], 823 | ], 824 | ], 825 | [ 826 | 'name' => '1.3.0', 827 | 'version' => 'v1.3.0', 828 | 'assets' => [ 829 | [ 830 | 'name' => 'roadrunner-1.3.0-darwin-amd64.zip', 831 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.0/roadrunner-1.3.0-darwin-amd64.zip', 832 | ], 833 | [ 834 | 'name' => 'roadrunner-1.3.0-freebsd-amd64.zip', 835 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.0/roadrunner-1.3.0-freebsd-amd64.zip', 836 | ], 837 | [ 838 | 'name' => 'roadrunner-1.3.0-linux-amd64.tar.gz', 839 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.0/roadrunner-1.3.0-linux-amd64.tar.gz', 840 | ], 841 | [ 842 | 'name' => 'roadrunner-1.3.0-windows-amd64.zip', 843 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.3.0/roadrunner-1.3.0-windows-amd64.zip', 844 | ], 845 | ], 846 | ], 847 | [ 848 | 'name' => '1.2.8', 849 | 'version' => 'v1.2.8', 850 | 'assets' => [ 851 | [ 852 | 'name' => 'roadrunner-1.2.8-darwin-amd64.zip', 853 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.8/roadrunner-1.2.8-darwin-amd64.zip', 854 | ], 855 | [ 856 | 'name' => 'roadrunner-1.2.8-freebsd-amd64.zip', 857 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.8/roadrunner-1.2.8-freebsd-amd64.zip', 858 | ], 859 | [ 860 | 'name' => 'roadrunner-1.2.8-linux-amd64.tar.gz', 861 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.8/roadrunner-1.2.8-linux-amd64.tar.gz', 862 | ], 863 | [ 864 | 'name' => 'roadrunner-1.2.8-windows-amd64.zip', 865 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.8/roadrunner-1.2.8-windows-amd64.zip', 866 | ], 867 | ], 868 | ], 869 | [ 870 | 'name' => '1.2.7', 871 | 'version' => 'v1.2.7', 872 | 'assets' => [ 873 | [ 874 | 'name' => 'roadrunner-1.2.7-darwin-amd64.zip', 875 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.7/roadrunner-1.2.7-darwin-amd64.zip', 876 | ], 877 | [ 878 | 'name' => 'roadrunner-1.2.7-freebsd-amd64.zip', 879 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.7/roadrunner-1.2.7-freebsd-amd64.zip', 880 | ], 881 | [ 882 | 'name' => 'roadrunner-1.2.7-linux-amd64.tar.gz', 883 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.7/roadrunner-1.2.7-linux-amd64.tar.gz', 884 | ], 885 | [ 886 | 'name' => 'roadrunner-1.2.7-windows-amd64.zip', 887 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.7/roadrunner-1.2.7-windows-amd64.zip', 888 | ], 889 | ], 890 | ], 891 | [ 892 | 'name' => '1.2.6', 893 | 'version' => 'v1.2.6', 894 | 'assets' => [ 895 | [ 896 | 'name' => 'roadrunner-1.2.6-darwin-amd64.zip', 897 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.6/roadrunner-1.2.6-darwin-amd64.zip', 898 | ], 899 | [ 900 | 'name' => 'roadrunner-1.2.6-freebsd-amd64.zip', 901 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.6/roadrunner-1.2.6-freebsd-amd64.zip', 902 | ], 903 | [ 904 | 'name' => 'roadrunner-1.2.6-linux-amd64.tar.gz', 905 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.6/roadrunner-1.2.6-linux-amd64.tar.gz', 906 | ], 907 | [ 908 | 'name' => 'roadrunner-1.2.6-windows-amd64.zip', 909 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.6/roadrunner-1.2.6-windows-amd64.zip', 910 | ], 911 | ], 912 | ], 913 | [ 914 | 'name' => '1.2.5', 915 | 'version' => 'v1.2.5', 916 | 'assets' => [ 917 | [ 918 | 'name' => 'roadrunner-1.2.5-darwin-amd64.zip', 919 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.5/roadrunner-1.2.5-darwin-amd64.zip', 920 | ], 921 | [ 922 | 'name' => 'roadrunner-1.2.5-freebsd-amd64.zip', 923 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.5/roadrunner-1.2.5-freebsd-amd64.zip', 924 | ], 925 | [ 926 | 'name' => 'roadrunner-1.2.5-linux-amd64.tar.gz', 927 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.5/roadrunner-1.2.5-linux-amd64.tar.gz', 928 | ], 929 | [ 930 | 'name' => 'roadrunner-1.2.5-windows-amd64.zip', 931 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.5/roadrunner-1.2.5-windows-amd64.zip', 932 | ], 933 | ], 934 | ], 935 | [ 936 | 'name' => '1.2.4', 937 | 'version' => 'v1.2.4', 938 | 'assets' => [ 939 | [ 940 | 'name' => 'roadrunner-1.2.4-darwin-amd64.zip', 941 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.4/roadrunner-1.2.4-darwin-amd64.zip', 942 | ], 943 | [ 944 | 'name' => 'roadrunner-1.2.4-freebsd-amd64.zip', 945 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.4/roadrunner-1.2.4-freebsd-amd64.zip', 946 | ], 947 | [ 948 | 'name' => 'roadrunner-1.2.4-linux-amd64.tar.gz', 949 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.4/roadrunner-1.2.4-linux-amd64.tar.gz', 950 | ], 951 | [ 952 | 'name' => 'roadrunner-1.2.4-windows-amd64.zip', 953 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.4/roadrunner-1.2.4-windows-amd64.zip', 954 | ], 955 | ], 956 | ], 957 | [ 958 | 'name' => '1.2.3', 959 | 'version' => 'v1.2.3', 960 | 'assets' => [ 961 | [ 962 | 'name' => 'roadrunner-1.2.3-darwin-amd64.zip', 963 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.3/roadrunner-1.2.3-darwin-amd64.zip', 964 | ], 965 | [ 966 | 'name' => 'roadrunner-1.2.3-freebsd-amd64.zip', 967 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.3/roadrunner-1.2.3-freebsd-amd64.zip', 968 | ], 969 | [ 970 | 'name' => 'roadrunner-1.2.3-linux-amd64.tar.gz', 971 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.3/roadrunner-1.2.3-linux-amd64.tar.gz', 972 | ], 973 | [ 974 | 'name' => 'roadrunner-1.2.3-windows-amd64.zip', 975 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.3/roadrunner-1.2.3-windows-amd64.zip', 976 | ], 977 | ], 978 | ], 979 | [ 980 | 'name' => '1.2.2', 981 | 'version' => 'v1.2.2', 982 | 'assets' => [ 983 | [ 984 | 'name' => 'roadrunner-1.2.2-darwin-amd64.zip', 985 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.2/roadrunner-1.2.2-darwin-amd64.zip', 986 | ], 987 | [ 988 | 'name' => 'roadrunner-1.2.2-freebsd-amd64.zip', 989 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.2/roadrunner-1.2.2-freebsd-amd64.zip', 990 | ], 991 | [ 992 | 'name' => 'roadrunner-1.2.2-linux-amd64.tar.gz', 993 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.2/roadrunner-1.2.2-linux-amd64.tar.gz', 994 | ], 995 | [ 996 | 'name' => 'roadrunner-1.2.2-windows-amd64.zip', 997 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.2/roadrunner-1.2.2-windows-amd64.zip', 998 | ], 999 | ], 1000 | ], 1001 | [ 1002 | 'name' => '1.2.1', 1003 | 'version' => 'v1.2.1', 1004 | 'assets' => [ 1005 | [ 1006 | 'name' => 'roadrunner-1.2.1-darwin-amd64.zip', 1007 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.1/roadrunner-1.2.1-darwin-amd64.zip', 1008 | ], 1009 | [ 1010 | 'name' => 'roadrunner-1.2.1-freebsd-amd64.zip', 1011 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.1/roadrunner-1.2.1-freebsd-amd64.zip', 1012 | ], 1013 | [ 1014 | 'name' => 'roadrunner-1.2.1-linux-amd64.tar.gz', 1015 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.1/roadrunner-1.2.1-linux-amd64.tar.gz', 1016 | ], 1017 | [ 1018 | 'name' => 'roadrunner-1.2.1-windows-amd64.zip', 1019 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.1/roadrunner-1.2.1-windows-amd64.zip', 1020 | ], 1021 | ], 1022 | ], 1023 | [ 1024 | 'name' => '1.2.0', 1025 | 'version' => 'v1.2.0', 1026 | 'assets' => [ 1027 | [ 1028 | 'name' => 'roadrunner-1.2.0-darwin-amd64.zip', 1029 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.0/roadrunner-1.2.0-darwin-amd64.zip', 1030 | ], 1031 | [ 1032 | 'name' => 'roadrunner-1.2.0-freebsd-amd64.zip', 1033 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.0/roadrunner-1.2.0-freebsd-amd64.zip', 1034 | ], 1035 | [ 1036 | 'name' => 'roadrunner-1.2.0-linux-amd64.tar.gz', 1037 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.0/roadrunner-1.2.0-linux-amd64.tar.gz', 1038 | ], 1039 | [ 1040 | 'name' => 'roadrunner-1.2.0-windows-amd64.zip', 1041 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.2.0/roadrunner-1.2.0-windows-amd64.zip', 1042 | ], 1043 | ], 1044 | ], 1045 | [ 1046 | 'name' => '1.1.1', 1047 | 'version' => 'v1.1.1', 1048 | 'assets' => [ 1049 | [ 1050 | 'name' => 'roadrunner-1.1.1-darwin-amd64.zip', 1051 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.1.1/roadrunner-1.1.1-darwin-amd64.zip', 1052 | ], 1053 | [ 1054 | 'name' => 'roadrunner-1.1.1-freebsd-amd64.zip', 1055 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.1.1/roadrunner-1.1.1-freebsd-amd64.zip', 1056 | ], 1057 | [ 1058 | 'name' => 'roadrunner-1.1.1-linux-amd64.tar.gz', 1059 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.1.1/roadrunner-1.1.1-linux-amd64.tar.gz', 1060 | ], 1061 | [ 1062 | 'name' => 'roadrunner-1.1.1-windows-amd64.zip', 1063 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.1.1/roadrunner-1.1.1-windows-amd64.zip', 1064 | ], 1065 | ], 1066 | ], 1067 | [ 1068 | 'name' => '1.1.0', 1069 | 'version' => 'v1.1.0', 1070 | 'assets' => [ 1071 | [ 1072 | 'name' => 'roadrunner-1.1.0-darwin-amd64.zip', 1073 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.1.0/roadrunner-1.1.0-darwin-amd64.zip', 1074 | ], 1075 | [ 1076 | 'name' => 'roadrunner-1.1.0-freebsd-amd64.zip', 1077 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.1.0/roadrunner-1.1.0-freebsd-amd64.zip', 1078 | ], 1079 | [ 1080 | 'name' => 'roadrunner-1.1.0-linux-amd64.tar.gz', 1081 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.1.0/roadrunner-1.1.0-linux-amd64.tar.gz', 1082 | ], 1083 | [ 1084 | 'name' => 'roadrunner-1.1.0-windows-amd64.zip', 1085 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.1.0/roadrunner-1.1.0-windows-amd64.zip', 1086 | ], 1087 | ], 1088 | ], 1089 | [ 1090 | 'name' => '1.0.5', 1091 | 'version' => 'v1.0.5', 1092 | 'assets' => [ 1093 | [ 1094 | 'name' => 'roadrunner-1.0.5-darwin-amd64.zip', 1095 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.5/roadrunner-1.0.5-darwin-amd64.zip', 1096 | ], 1097 | [ 1098 | 'name' => 'roadrunner-1.0.5-freebsd-amd64.zip', 1099 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.5/roadrunner-1.0.5-freebsd-amd64.zip', 1100 | ], 1101 | [ 1102 | 'name' => 'roadrunner-1.0.5-linux-amd64.tar.gz', 1103 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.5/roadrunner-1.0.5-linux-amd64.tar.gz', 1104 | ], 1105 | [ 1106 | 'name' => 'roadrunner-1.0.5-windows-amd64.zip', 1107 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.5/roadrunner-1.0.5-windows-amd64.zip', 1108 | ], 1109 | ], 1110 | ], 1111 | [ 1112 | 'name' => '1.0.4', 1113 | 'version' => 'untagged-f7dbae972208eff54361', 1114 | 'assets' => [ 1115 | [ 1116 | 'name' => 'roadrunner-1.0.4-darwin-amd64.zip', 1117 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/untagged-f7dbae972208eff54361/roadrunner-1.0.4-darwin-amd64.zip', 1118 | ], 1119 | [ 1120 | 'name' => 'roadrunner-1.0.4-freebsd-amd64.zip', 1121 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/untagged-f7dbae972208eff54361/roadrunner-1.0.4-freebsd-amd64.zip', 1122 | ], 1123 | [ 1124 | 'name' => 'roadrunner-1.0.4-linux-amd64.tar.gz', 1125 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/untagged-f7dbae972208eff54361/roadrunner-1.0.4-linux-amd64.tar.gz', 1126 | ], 1127 | [ 1128 | 'name' => 'roadrunner-1.0.4-windows-amd64.zip', 1129 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/untagged-f7dbae972208eff54361/roadrunner-1.0.4-windows-amd64.zip', 1130 | ], 1131 | ], 1132 | ], 1133 | [ 1134 | 'name' => '1.0.3', 1135 | 'version' => 'v1.0.3', 1136 | 'assets' => [ 1137 | [ 1138 | 'name' => 'roadrunner-1.0.3-darwin-amd64.zip', 1139 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.3/roadrunner-1.0.3-darwin-amd64.zip', 1140 | ], 1141 | [ 1142 | 'name' => 'roadrunner-1.0.3-freebsd-amd64.zip', 1143 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.3/roadrunner-1.0.3-freebsd-amd64.zip', 1144 | ], 1145 | [ 1146 | 'name' => 'roadrunner-1.0.3-linux-amd64.tar.gz', 1147 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.3/roadrunner-1.0.3-linux-amd64.tar.gz', 1148 | ], 1149 | [ 1150 | 'name' => 'roadrunner-1.0.3-windows-amd64.zip', 1151 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.3/roadrunner-1.0.3-windows-amd64.zip', 1152 | ], 1153 | ], 1154 | ], 1155 | [ 1156 | 'name' => '1.0.2', 1157 | 'version' => 'v1.0.2', 1158 | 'assets' => [ 1159 | [ 1160 | 'name' => 'roadrunner-1.0.2-darwin-amd64.zip', 1161 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.2/roadrunner-1.0.2-darwin-amd64.zip', 1162 | ], 1163 | [ 1164 | 'name' => 'roadrunner-1.0.2-freebsd-amd64.zip', 1165 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.2/roadrunner-1.0.2-freebsd-amd64.zip', 1166 | ], 1167 | [ 1168 | 'name' => 'roadrunner-1.0.2-linux-amd64.tar.gz', 1169 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.2/roadrunner-1.0.2-linux-amd64.tar.gz', 1170 | ], 1171 | [ 1172 | 'name' => 'roadrunner-1.0.2-windows-amd64.zip', 1173 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.2/roadrunner-1.0.2-windows-amd64.zip', 1174 | ], 1175 | ], 1176 | ], 1177 | [ 1178 | 'name' => '1.0.1', 1179 | 'version' => 'v1.0.1', 1180 | 'assets' => [ 1181 | [ 1182 | 'name' => 'roadrunner-1.0.1-darwin-amd64.zip', 1183 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.1/roadrunner-1.0.1-darwin-amd64.zip', 1184 | ], 1185 | [ 1186 | 'name' => 'roadrunner-1.0.1-freebsd-amd64.zip', 1187 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.1/roadrunner-1.0.1-freebsd-amd64.zip', 1188 | ], 1189 | [ 1190 | 'name' => 'roadrunner-1.0.1-linux-amd64.tar.gz', 1191 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.1/roadrunner-1.0.1-linux-amd64.tar.gz', 1192 | ], 1193 | [ 1194 | 'name' => 'roadrunner-1.0.1-windows-amd64.zip', 1195 | 'uri' => 'https://github.com/spiral/roadrunner/releases/download/v1.0.1/roadrunner-1.0.1-windows-amd64.zip', 1196 | ], 1197 | ], 1198 | ], 1199 | [ 1200 | 'name' => '1.0.0', 1201 | 'version' => 'v1.0.0', 1202 | 'assets' => [], 1203 | ], 1204 | [ 1205 | 'name' => '0.9.0', 1206 | 'version' => 'v0.9.0', 1207 | 'assets' => [], 1208 | ], 1209 | ]; 1210 | 1211 | /** 1212 | * @var array 1213 | */ 1214 | private array $releases = []; 1215 | 1216 | private HttpClientInterface $client; 1217 | 1218 | public function __construct(?HttpClientInterface $client = null) 1219 | { 1220 | $this->client = $client ?? HttpClient::create(); 1221 | 1222 | foreach (self::RELEASES_DATA as $release) { 1223 | $this->releases[] = $this->createRelease($release); 1224 | } 1225 | } 1226 | 1227 | public function getName(): string 1228 | { 1229 | return 'spiral/roadrunner'; 1230 | } 1231 | 1232 | public function getReleases(): ReleasesCollection 1233 | { 1234 | return new ReleasesCollection($this->releases); 1235 | } 1236 | 1237 | /** 1238 | * @param array{name: string, version: string, assets: array} $release 1239 | */ 1240 | private function createRelease(array $release): ReleaseInterface 1241 | { 1242 | $assets = []; 1243 | 1244 | foreach ($release['assets'] as $asset) { 1245 | $assets[] = $this->createAsset($asset); 1246 | } 1247 | 1248 | return new GitHubRelease($this->client, $release['name'], $release['version'], $this->getName(), $assets); 1249 | } 1250 | 1251 | /** 1252 | * @param array{name: string, uri: string} $asset 1253 | */ 1254 | private function createAsset(array $asset): AssetInterface 1255 | { 1256 | return new GitHubAsset($this->client, $asset['name'], $asset['uri']); 1257 | } 1258 | } 1259 | -------------------------------------------------------------------------------- /src/VersionsCommand.php: -------------------------------------------------------------------------------- 1 | os = new OperatingSystemOption($this); 37 | $this->arch = new ArchitectureOption($this); 38 | $this->stability = new StabilityOption($this); 39 | 40 | $this->version = new class($this) extends VersionFilterOption { 41 | protected function default(): string 42 | { 43 | return '*'; 44 | } 45 | }; 46 | } 47 | 48 | public function getDescription(): string 49 | { 50 | return 'Returns a list of all available RoadRunner versions'; 51 | } 52 | 53 | public function execute(InputInterface $input, OutputInterface $output): int 54 | { 55 | $io = $this->io($input, $output); 56 | 57 | $output->writeln(''); 58 | $output->writeln(' Environment:'); 59 | $output->writeln(\sprintf(' - Version: %s', $this->version->get($input, $io))); 60 | $output->writeln(\sprintf(' - Stability: %s', $this->stability->get($input, $io))); 61 | $output->writeln(''); 62 | 63 | $rows = []; 64 | 65 | $versions = $this->getRepository() 66 | ->getReleases() 67 | ->sortByVersion() 68 | ->minimumStability($this->stability->get($input, $io)) 69 | ->filter(static fn(ReleaseInterface $release): bool => \count($release->getAssets()) > 0) 70 | ->satisfies($this->version->get($input, $io)) 71 | ; 72 | 73 | foreach ($versions as $version) { 74 | $rows[] = [ 75 | $this->versionToString($version), 76 | $this->stabilityToString($version), 77 | $this->assetsToString($version), 78 | $this->compatibilityToString($version, $input, $io), 79 | ]; 80 | } 81 | 82 | $io->table(['Release', 'Stability', 'Binaries', 'Compatibility'], $rows); 83 | 84 | return 0; 85 | } 86 | 87 | private function compatibilityToString(ReleaseInterface $release, InputInterface $input, StyleInterface $io): string 88 | { 89 | $template = ' ✖ (reason: %s)'; 90 | 91 | // Validate version 92 | if (! $release->satisfies(Version::constraint())) { 93 | return \sprintf($template, 'incompatible version'); 94 | } 95 | 96 | // Validate assets 97 | $assets = $release->getAssets(); 98 | 99 | if ($assets->empty()) { 100 | return \sprintf($template, 'no binaries'); 101 | } 102 | 103 | // Validate OS 104 | $assets = $assets->whereOperatingSystem( 105 | $os = $this->os->get($input, $io), 106 | ); 107 | 108 | if ($assets->empty()) { 109 | return \sprintf($template, 'no assembly for ' . $os); 110 | } 111 | 112 | // Validate architecture 113 | $assets = $assets->whereArchitecture( 114 | $arch = $this->arch->get($input, $io), 115 | ); 116 | 117 | if ($assets->empty()) { 118 | return \sprintf($template, 'no assembly for ' . $arch); 119 | } 120 | 121 | return ' ✓ '; 122 | } 123 | 124 | private function versionToString(ReleaseInterface $release): string 125 | { 126 | return $release->getName(); 127 | } 128 | 129 | private function stabilityToString(ReleaseInterface $release): string 130 | { 131 | $stability = $release->getStability(); 132 | 133 | switch ($stability) { 134 | case Stability::STABILITY_STABLE: 135 | return " $stability "; 136 | 137 | case Stability::STABILITY_RC: 138 | return " $stability "; 139 | 140 | case Stability::STABILITY_BETA: 141 | return " $stability "; 142 | 143 | case Stability::STABILITY_ALPHA: 144 | return " $stability "; 145 | 146 | default: 147 | return " $stability "; 148 | } 149 | } 150 | 151 | private function assetsToString(ReleaseInterface $release): string 152 | { 153 | $count = $release->getAssets() 154 | ->count() 155 | ; 156 | 157 | if ($count > 0) { 158 | return \sprintf(' ✓ (%d)', $count); 159 | } 160 | 161 | return ' ✖ '; 162 | } 163 | } 164 | --------------------------------------------------------------------------------