Passed
Push — stable ( 151844...8dec2a )
by Nuno
01:48
created

BuildCommand::build()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 16
ccs 7
cts 7
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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\Support\Facades\File;
17
use Symfony\Component\Process\Process;
18
use Illuminate\Console\Application as Artisan;
19
use Symfony\Component\Console\Output\NullOutput;
20
use Symfony\Component\Console\Helper\ProgressBar;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Output\OutputInterface;
23
24
final class BuildCommand extends Command
25
{
26
    /**
27
     * {@inheritdoc}
28
     */
29
    protected $signature = 'app:build {name? : The build name} {--timeout=300 : The timeout in seconds or 0 to disable}';
30
31
    /**
32
     * {@inheritdoc}
33
     */
34
    protected $description = 'Build a single file executable';
35
36
    /**
37
     * Holds the configuration on is original state.
38
     *
39
     * @var string|null
40
     */
41
    private static $config;
42
43
    /**
44
     * Holds the box.json on is original state.
45
     *
46
     * @var string|null
47
     */
48
    private static $box;
49
50
    /**
51
     * Holds the command original output.
52
     *
53
     * @var \Symfony\Component\Console\Output\OutputInterface
54
     */
55
    private $originalOutput;
56
57
    /**
58
     * {@inheritdoc}
59
     */
60 2
    public function handle()
61
    {
62 2
        $this->title('Building process');
63
64 2
        $this->build($this->input->getArgument('name') ?? $this->getBinary());
0 ignored issues
show
Bug introduced by
It seems like $this->input->getArgumen...) ?? $this->getBinary() can also be of type string[]; however, parameter $name of LaravelZero\Framework\Co...s\BuildCommand::build() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

64
        $this->build(/** @scrutinizer ignore-type */ $this->input->getArgument('name') ?? $this->getBinary());
Loading history...
65 1
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70 2
    public function run(InputInterface $input, OutputInterface $output)
71
    {
72 2
        parent::run($input, $this->originalOutput = $output);
73 1
    }
74
75
    /**
76
     * Builds the application into a single file.
77
     */
78 2
    private function build(string $name): BuildCommand
79
    {
80
        /*
81
         * We prepare the application for a build, moving it to production. Then,
82
         * after compile all the code to a single file, we move the built file
83
         * to the builds folder with the correct permissions.
84
         */
85 2
        $this->prepare()
86 2
            ->compile($name)
87 1
            ->clear();
88
89 1
        $this->output->writeln(
90 1
            sprintf('    Compiled successfully: <fg=green>%s</>', $this->app->buildsPath($name))
91
        );
92
93 1
        return $this;
94
    }
95
96 2
    private function compile(string $name): BuildCommand
97
    {
98 2
        if (! File::exists($this->app->buildsPath())) {
99 2
            File::makeDirectory($this->app->buildsPath());
100
        }
101
102 2
        $process = new Process(
103 2
            './box compile --working-dir="'.base_path().'" --config="'.base_path('box.json').'"',
0 ignored issues
show
Bug introduced by
'./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 ignore-type  annotation

103
            /** @scrutinizer ignore-type */ './box compile --working-dir="'.base_path().'" --config="'.base_path('box.json').'"',
Loading history...
104 2
            dirname(dirname(__DIR__)).'/bin',
105 2
            null,
106 2
            null,
107 2
            $this->getTimeout()
108
        );
109
110 2
        $section = tap($this->originalOutput->section())->write('');
0 ignored issues
show
Bug introduced by
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 ignore-call  annotation

110
        $section = tap($this->originalOutput->/** @scrutinizer ignore-call */ section())->write('');
Loading history...
111
112 2
        $progressBar = tap(
113 2
            new ProgressBar(
114 2
                $this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL ? new NullOutput() : $section, 25
115
            )
116 2
        )->setProgressCharacter("\xF0\x9F\x8D\xBA");
117
118 2
        foreach (tap($process)->start() as $type => $data) {
119 2
            $progressBar->advance();
120
121 2
            if ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) {
122 2
                $process::OUT === $type ? $this->info("$data") : $this->error("$data");
123
            }
124
        }
125
126 2
        $progressBar->finish();
127
128 2
        $section->clear();
129
130 1
        $this->task('   2. <fg=yellow>Compile</> into a single file');
131
132 1
        $this->output->newLine();
133
134 1
        File::move($this->app->basePath($this->getBinary()).'.phar', $this->app->buildsPath($name));
135
136 1
        return $this;
137
    }
138
139 2
    private function prepare(): BuildCommand
140
    {
141 2
        $configFile = $this->app->configPath('app.php');
142 2
        static::$config = File::get($configFile);
0 ignored issues
show
Bug introduced by
Since $config is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $config to at least protected.
Loading history...
143
144 2
        $config = include $configFile;
145
146 2
        $config['production'] = true;
147 2
        $version = $this->ask('Build version?', $config['version']);
148 2
        $config['version'] = $version;
149
150 2
        $boxFile = $this->app->basePath('box.json');
151 2
        static::$box = File::get($boxFile);
0 ignored issues
show
Bug introduced by
Since $box is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $box to at least protected.
Loading history...
152
153 2
        $this->task(
154 2
            '   1. Moving application to <fg=yellow>production mode</>',
155
            function () use ($configFile, $config) {
156 2
                File::put($configFile, '<?php return '.var_export($config, true).';'.PHP_EOL);
157 2
            }
158
        );
159
160 2
        $boxContents = json_decode(static::$box, true);
161 2
        $boxContents['main'] = $this->getBinary();
162 2
        File::put($boxFile, json_encode($boxContents));
163
164 2
        File::put($configFile, '<?php return '.var_export($config, true).';'.PHP_EOL);
165
166 2
        return $this;
167
    }
168
169 2
    private function clear(): BuildCommand
170
    {
171 2
        File::put($this->app->configPath('app.php'), static::$config);
0 ignored issues
show
Bug introduced by
Since $config is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $config to at least protected.
Loading history...
172
173 2
        File::put($this->app->basePath('box.json'), static::$box);
0 ignored issues
show
Bug introduced by
Since $box is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $box to at least protected.
Loading history...
174
175 2
        static::$config = null;
176
177 2
        static::$box = null;
178
179 2
        return $this;
180
    }
181
182
    /**
183
     * Returns the artisan binary.
184
     */
185 2
    private function getBinary(): string
186
    {
187 2
        return str_replace(["'", '"'], '', Artisan::artisanBinary());
188
    }
189
190
    /**
191
     * Returns a valid timeout value. Non positive values are converted to null,
192
     * meaning no timeout.
193
     *
194
     * @return float|null
195
     * @throws \InvalidArgumentException
196
     */
197 2
    private function getTimeout(): ?float
198
    {
199 2
        if (! is_numeric($this->option('timeout'))) {
0 ignored issues
show
introduced by
The condition is_numeric($this->option('timeout')) is always true.
Loading history...
200
            throw new \InvalidArgumentException('The timeout value must be a number.');
201
        }
202
203 2
        $timeout = (float) $this->option('timeout');
204
205 2
        return $timeout > 0 ? $timeout : null;
206
    }
207
208
    /**
209
     * Makes sure that the `clear` is performed even
210
     * if the command fails.
211
     *
212
     * @return void
213
     */
214 36
    public function __destruct()
215
    {
216 36
        if (static::$config !== null) {
0 ignored issues
show
Bug introduced by
Since $config is declared private, accessing it with static will lead to errors in possible sub-classes; you can either use self, or increase the visibility of $config to at least protected.
Loading history...
217 1
            $this->clear();
218
        }
219 36
    }
220
}
221