laravel-zero /
framework
| 1 | <?php |
||||
| 2 | |||||
| 3 | declare(strict_types=1); |
||||
| 4 | |||||
| 5 | /** |
||||
| 6 | * This file is part of Laravel Zero. |
||||
| 7 | * |
||||
| 8 | * (c) Nuno Maduro <[email protected]> |
||||
| 9 | * |
||||
| 10 | * For the full copyright and license information, please view the LICENSE |
||||
| 11 | * file that was distributed with this source code. |
||||
| 12 | */ |
||||
| 13 | |||||
| 14 | namespace LaravelZero\Framework\Commands; |
||||
| 15 | |||||
| 16 | use Illuminate\Console\Application as Artisan; |
||||
| 17 | use Illuminate\Support\Facades\File; |
||||
| 18 | use Symfony\Component\Console\Helper\ProgressBar; |
||||
| 19 | use Symfony\Component\Console\Input\InputInterface; |
||||
| 20 | use Symfony\Component\Console\Output\NullOutput; |
||||
| 21 | use Symfony\Component\Console\Output\OutputInterface; |
||||
| 22 | use Symfony\Component\Process\Process; |
||||
| 23 | |||||
| 24 | final class BuildCommand extends Command |
||||
| 25 | { |
||||
| 26 | /** |
||||
| 27 | * {@inheritdoc} |
||||
| 28 | */ |
||||
| 29 | protected $signature = 'app:build |
||||
| 30 | {name? : The build name} |
||||
| 31 | {--build-version= : The build version, if not provided it will be asked} |
||||
| 32 | {--timeout=300 : The timeout in seconds or 0 to disable}'; |
||||
| 33 | |||||
| 34 | /** |
||||
| 35 | * {@inheritdoc} |
||||
| 36 | */ |
||||
| 37 | protected $description = 'Build a single file executable'; |
||||
| 38 | |||||
| 39 | /** |
||||
| 40 | * Holds the configuration on is original state. |
||||
| 41 | * |
||||
| 42 | * @var string|null |
||||
| 43 | */ |
||||
| 44 | private static $config; |
||||
| 45 | |||||
| 46 | /** |
||||
| 47 | * Holds the box.json on is original state. |
||||
| 48 | * |
||||
| 49 | * @var string|null |
||||
| 50 | */ |
||||
| 51 | private static $box; |
||||
| 52 | |||||
| 53 | /** |
||||
| 54 | * Holds the command original output. |
||||
| 55 | * |
||||
| 56 | * @var \Symfony\Component\Console\Output\OutputInterface |
||||
| 57 | */ |
||||
| 58 | private $originalOutput; |
||||
| 59 | |||||
| 60 | /** |
||||
| 61 | * {@inheritdoc} |
||||
| 62 | */ |
||||
| 63 | 2 | public function handle() |
|||
| 64 | { |
||||
| 65 | 2 | if ($this->supportsAsyncSignals()) { |
|||
| 66 | 2 | $this->listenForSignals(); |
|||
| 67 | } |
||||
| 68 | |||||
| 69 | 2 | $this->title('Building process'); |
|||
| 70 | |||||
| 71 | 2 | $this->build($this->input->getArgument('name') ?? $this->getBinary()); |
|||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
| 72 | 1 | } |
|||
| 73 | |||||
| 74 | /** |
||||
| 75 | * {@inheritdoc} |
||||
| 76 | */ |
||||
| 77 | 2 | public function run(InputInterface $input, OutputInterface $output) |
|||
| 78 | { |
||||
| 79 | 2 | parent::run($input, $this->originalOutput = $output); |
|||
| 80 | 1 | } |
|||
| 81 | |||||
| 82 | /** |
||||
| 83 | * Builds the application into a single file. |
||||
| 84 | */ |
||||
| 85 | 2 | private function build(string $name): BuildCommand |
|||
| 86 | { |
||||
| 87 | /* |
||||
| 88 | * We prepare the application for a build, moving it to production. Then, |
||||
| 89 | * after compile all the code to a single file, we move the built file |
||||
| 90 | * to the builds folder with the correct permissions. |
||||
| 91 | */ |
||||
| 92 | 2 | $this->prepare() |
|||
| 93 | 2 | ->compile($name) |
|||
| 94 | 1 | ->clear(); |
|||
| 95 | |||||
| 96 | 1 | $this->output->writeln( |
|||
| 97 | 1 | sprintf(' Compiled successfully: <fg=green>%s</>', $this->app->buildsPath($name)) |
|||
| 98 | ); |
||||
| 99 | |||||
| 100 | 1 | return $this; |
|||
| 101 | } |
||||
| 102 | |||||
| 103 | 2 | private function compile(string $name): BuildCommand |
|||
| 104 | { |
||||
| 105 | 2 | if (! File::exists($this->app->buildsPath())) { |
|||
| 106 | 2 | File::makeDirectory($this->app->buildsPath()); |
|||
| 107 | } |
||||
| 108 | |||||
| 109 | 2 | $process = new Process( |
|||
| 110 | 2 | './box compile --working-dir="'.base_path().'" --config="'.base_path('box.json').'"', |
|||
|
0 ignored issues
–
show
'./box compile --working..._path('box.json') . '"' of type string is incompatible with the type array expected by parameter $command of Symfony\Component\Process\Process::__construct().
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 111 | 2 | dirname(dirname(__DIR__)).'/bin', |
|||
| 112 | 2 | null, |
|||
| 113 | 2 | null, |
|||
| 114 | 2 | $this->getTimeout() |
|||
| 115 | ); |
||||
| 116 | |||||
| 117 | 2 | $section = tap($this->originalOutput->section())->write(''); |
|||
|
0 ignored issues
–
show
The method
section() does not exist on Symfony\Component\Console\Output\OutputInterface. It seems like you code against a sub-type of Symfony\Component\Console\Output\OutputInterface such as Symfony\Component\Console\Style\OutputStyle or Symfony\Component\Console\Output\ConsoleOutput or anonymous//tests/BuildCommandTest.php$1 or anonymous//tests/BuildCommandTest.php$3 or Symfony\Component\Console\Output\ConsoleOutput.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 118 | |||||
| 119 | 2 | $progressBar = tap( |
|||
| 120 | 2 | new ProgressBar( |
|||
| 121 | 2 | $this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL ? new NullOutput() : $section, 25 |
|||
| 122 | ) |
||||
| 123 | 2 | )->setProgressCharacter("\xF0\x9F\x8D\xBA"); |
|||
| 124 | |||||
| 125 | 2 | foreach (tap($process)->start() as $type => $data) { |
|||
| 126 | 2 | $progressBar->advance(); |
|||
| 127 | |||||
| 128 | 2 | if ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) { |
|||
| 129 | 2 | $process::OUT === $type ? $this->info("$data") : $this->error("$data"); |
|||
| 130 | } |
||||
| 131 | } |
||||
| 132 | |||||
| 133 | 2 | $progressBar->finish(); |
|||
| 134 | |||||
| 135 | 2 | $section->clear(); |
|||
| 136 | |||||
| 137 | 1 | $this->task(' 2. <fg=yellow>Compile</> into a single file'); |
|||
| 138 | |||||
| 139 | 1 | $this->output->newLine(); |
|||
| 140 | |||||
| 141 | 1 | File::move($this->app->basePath($this->getBinary()).'.phar', $this->app->buildsPath($name)); |
|||
| 142 | |||||
| 143 | 1 | return $this; |
|||
| 144 | } |
||||
| 145 | |||||
| 146 | 2 | private function prepare(): BuildCommand |
|||
| 147 | { |
||||
| 148 | 2 | $configFile = $this->app->configPath('app.php'); |
|||
| 149 | 2 | static::$config = File::get($configFile); |
|||
|
0 ignored issues
–
show
|
|||||
| 150 | |||||
| 151 | 2 | $config = include $configFile; |
|||
| 152 | |||||
| 153 | 2 | $config['production'] = true; |
|||
| 154 | 2 | $version = $this->option('build-version') ?: $this->ask('Build version?', $config['version']); |
|||
| 155 | 2 | $config['version'] = $version; |
|||
| 156 | |||||
| 157 | 2 | $boxFile = $this->app->basePath('box.json'); |
|||
| 158 | 2 | static::$box = File::get($boxFile); |
|||
|
0 ignored issues
–
show
|
|||||
| 159 | |||||
| 160 | 2 | $this->task( |
|||
| 161 | 2 | ' 1. Moving application to <fg=yellow>production mode</>', |
|||
| 162 | function () use ($configFile, $config) { |
||||
| 163 | 2 | File::put($configFile, '<?php return '.var_export($config, true).';'.PHP_EOL); |
|||
| 164 | 2 | } |
|||
| 165 | ); |
||||
| 166 | |||||
| 167 | 2 | $boxContents = json_decode(static::$box, true); |
|||
| 168 | 2 | $boxContents['main'] = $this->getBinary(); |
|||
| 169 | 2 | File::put($boxFile, json_encode($boxContents)); |
|||
| 170 | |||||
| 171 | 2 | File::put($configFile, '<?php return '.var_export($config, true).';'.PHP_EOL); |
|||
| 172 | |||||
| 173 | 2 | return $this; |
|||
| 174 | } |
||||
| 175 | |||||
| 176 | 2 | private function clear(): BuildCommand |
|||
| 177 | { |
||||
| 178 | 2 | File::put($this->app->configPath('app.php'), static::$config); |
|||
|
0 ignored issues
–
show
|
|||||
| 179 | |||||
| 180 | 2 | File::put($this->app->basePath('box.json'), static::$box); |
|||
|
0 ignored issues
–
show
|
|||||
| 181 | |||||
| 182 | 2 | static::$config = null; |
|||
| 183 | |||||
| 184 | 2 | static::$box = null; |
|||
| 185 | |||||
| 186 | 2 | return $this; |
|||
| 187 | } |
||||
| 188 | |||||
| 189 | /** |
||||
| 190 | * Returns the artisan binary. |
||||
| 191 | */ |
||||
| 192 | 2 | private function getBinary(): string |
|||
| 193 | { |
||||
| 194 | 2 | return str_replace(["'", '"'], '', Artisan::artisanBinary()); |
|||
| 195 | } |
||||
| 196 | |||||
| 197 | /** |
||||
| 198 | * Returns a valid timeout value. Non positive values are converted to null, |
||||
| 199 | * meaning no timeout. |
||||
| 200 | * |
||||
| 201 | * @return float|null |
||||
| 202 | * @throws \InvalidArgumentException |
||||
| 203 | */ |
||||
| 204 | 2 | private function getTimeout(): ?float |
|||
| 205 | { |
||||
| 206 | 2 | if (! is_numeric($this->option('timeout'))) { |
|||
|
0 ignored issues
–
show
|
|||||
| 207 | throw new \InvalidArgumentException('The timeout value must be a number.'); |
||||
| 208 | } |
||||
| 209 | |||||
| 210 | 2 | $timeout = (float) $this->option('timeout'); |
|||
| 211 | |||||
| 212 | 2 | return $timeout > 0 ? $timeout : null; |
|||
| 213 | } |
||||
| 214 | |||||
| 215 | /** |
||||
| 216 | * Enable and listen to async signals for the process. |
||||
| 217 | */ |
||||
| 218 | 2 | private function listenForSignals(): void |
|||
| 219 | { |
||||
| 220 | 2 | pcntl_async_signals(true); |
|||
| 221 | |||||
| 222 | pcntl_signal(SIGINT, function () { |
||||
| 223 | if (static::$config !== null) { |
||||
|
0 ignored issues
–
show
|
|||||
| 224 | $this->clear(); |
||||
| 225 | } |
||||
| 226 | |||||
| 227 | exit; |
||||
|
0 ignored issues
–
show
|
|||||
| 228 | 2 | }); |
|||
| 229 | 2 | } |
|||
| 230 | |||||
| 231 | /** |
||||
| 232 | * Determine if "async" signals are supported. |
||||
| 233 | */ |
||||
| 234 | 2 | private function supportsAsyncSignals(): bool |
|||
| 235 | { |
||||
| 236 | 2 | return extension_loaded('pcntl'); |
|||
| 237 | } |
||||
| 238 | |||||
| 239 | /** |
||||
| 240 | * Makes sure that the `clear` is performed even |
||||
| 241 | * if the command fails. |
||||
| 242 | * |
||||
| 243 | * @return void |
||||
| 244 | */ |
||||
| 245 | 39 | public function __destruct() |
|||
| 246 | { |
||||
| 247 | 39 | if (static::$config !== null) { |
|||
|
0 ignored issues
–
show
|
|||||
| 248 | 1 | $this->clear(); |
|||
| 249 | } |
||||
| 250 | 39 | } |
|||
| 251 | } |
||||
| 252 |