1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | /* (c) Anton Medvedev <[email protected]> |
||
6 | * |
||
7 | * For the full copyright and license information, please view the LICENSE |
||
8 | * file that was distributed with this source code. |
||
9 | */ |
||
10 | |||
11 | namespace Deployer\Command; |
||
12 | |||
13 | use Symfony\Component\Console\Command\Command; |
||
14 | use Symfony\Component\Console\Input\InputInterface; |
||
15 | use Symfony\Component\Console\Input\InputOption; |
||
16 | use Symfony\Component\Console\Output\OutputInterface; |
||
17 | use Symfony\Component\Console\Style\SymfonyStyle; |
||
18 | use Symfony\Component\Process\Exception\RuntimeException; |
||
19 | use Symfony\Component\Process\PhpProcess; |
||
20 | use Symfony\Component\Process\Process; |
||
21 | |||
22 | class InitCommand extends Command |
||
23 | { |
||
24 | use CommandCommon; |
||
25 | |||
26 | protected function configure() |
||
27 | { |
||
28 | $this |
||
29 | ->setName('init') |
||
30 | ->setDescription('Initialize deployer in your project') |
||
31 | ->addOption('path', 'p', InputOption::VALUE_OPTIONAL, 'Recipe path'); |
||
32 | } |
||
33 | |||
34 | protected function execute(InputInterface $input, OutputInterface $output): int |
||
35 | { |
||
36 | if (getenv('COLORTERM') === 'truecolor') { |
||
37 | $output->write( |
||
38 | <<<EOF |
||
39 | ╭───────────────────────────────────────╮ |
||
40 | │ │ |
||
41 | │ │ |
||
42 | │ \e[38;2;94;231;223m_\e[39m\e[38;2;95;231;226m_\e[39m\e[38;2;96;230;228m_\e[39m\e[38;2;96;229;230m_\e[39m \e[38;2;97;226;230m_\e[39m │ |
||
43 | │ \e[38;2;98;223;229m|\e[39m \e[38;2;98;220;229m\\\e[39m \e[38;2;99;216;228m_\e[39m\e[38;2;100;213;228m_\e[39m\e[38;2;101;210;228m_\e[39m \e[38;2;101;208;227m_\e[39m\e[38;2;102;205;227m_\e[39m\e[38;2;103;202;227m_\e[39m\e[38;2;104;199;226m|\e[39m \e[38;2;104;196;226m|\e[39m\e[38;2;105;194;225m_\e[39m\e[38;2;106;191;225m_\e[39m\e[38;2;106;188;225m_\e[39m \e[38;2;107;186;224m_\e[39m \e[38;2;108;183;224m_\e[39m \e[38;2;109;181;224m_\e[39m\e[38;2;109;178;223m_\e[39m\e[38;2;110;176;223m_\e[39m \e[38;2;111;174;222m_\e[39m\e[38;2;111;171;222m_\e[39m\e[38;2;112;169;222m_\e[39m │ |
||
44 | │ \e[38;2;113;167;221m|\e[39m \e[38;2;113;165;221m|\e[39m \e[38;2;114;163;221m|\e[39m \e[38;2;115;160;220m-\e[39m\e[38;2;115;158;220m_\e[39m\e[38;2;116;156;219m|\e[39m \e[38;2;117;155;219m.\e[39m \e[38;2;117;153;219m|\e[39m \e[38;2;118;151;218m|\e[39m \e[38;2;119;149;218m.\e[39m \e[38;2;119;147;218m|\e[39m \e[38;2;120;145;217m|\e[39m \e[38;2;121;144;217m|\e[39m \e[38;2;121;142;216m-\e[39m\e[38;2;122;140;216m_\e[39m\e[38;2;123;139;216m|\e[39m \e[38;2;123;137;215m_\e[39m\e[38;2;124;136;215m|\e[39m │ |
||
45 | │ \e[38;2;124;134;215m|\e[39m\e[38;2;125;133;214m_\e[39m\e[38;2;126;132;214m_\e[39m\e[38;2;126;130;214m_\e[39m\e[38;2;127;129;213m_\e[39m\e[38;2;127;128;213m/\e[39m\e[38;2;130;128;212m|\e[39m\e[38;2;132;129;212m_\e[39m\e[38;2;134;129;212m_\e[39m\e[38;2;137;130;211m_\e[39m\e[38;2;139;131;211m|\e[39m \e[38;2;141;131;211m_\e[39m\e[38;2;143;132;210m|\e[39m\e[38;2;145;132;210m_\e[39m\e[38;2;147;133;209m|\e[39m\e[38;2;149;133;209m_\e[39m\e[38;2;151;134;209m_\e[39m\e[38;2;153;135;208m_\e[39m\e[38;2;155;135;208m|\e[39m\e[38;2;157;136;208m_\e[39m \e[38;2;159;136;207m|\e[39m\e[38;2;161;137;207m_\e[39m\e[38;2;162;137;206m_\e[39m\e[38;2;164;138;206m_\e[39m\e[38;2;166;139;206m|\e[39m\e[38;2;167;139;205m_\e[39m\e[38;2;169;140;205m|\e[39m │ |
||
46 | │ \e[38;2;170;140;205m|\e[39m\e[38;2;172;141;204m_\e[39m\e[38;2;173;141;204m|\e[39m \e[38;2;175;142;203m|\e[39m\e[38;2;176;142;203m_\e[39m\e[38;2;177;143;203m_\e[39m\e[38;2;179;143;202m_\e[39m\e[38;2;180;144;202m|\e[39m │ |
||
47 | │ │ |
||
48 | │ │ |
||
49 | ╰───────────────────────────────────────╯ |
||
50 | |||
51 | EOF, |
||
52 | ); |
||
53 | } else { |
||
54 | $output->write( |
||
55 | <<<EOF |
||
56 | ╭───────────────────────────────────────╮ |
||
57 | │ │ |
||
58 | │ │ |
||
59 | │ ____ _ │ |
||
60 | │ | \ ___ ___| |___ _ _ ___ ___ │ |
||
61 | │ | | | -_| . | | . | | | -_| _| │ |
||
62 | │ |____/|___| _|_|___|_ |___|_| │ |
||
63 | │ |_| |___| │ |
||
64 | │ │ |
||
65 | │ │ |
||
66 | ╰───────────────────────────────────────╯ |
||
67 | |||
68 | EOF, |
||
69 | ); |
||
70 | } |
||
71 | |||
72 | $io = new SymfonyStyle($input, $output); |
||
73 | $recipePath = $input->getOption('path'); |
||
74 | |||
75 | $language = $io->choice('Select recipe language', ['php', 'yaml'], 'php'); |
||
76 | if (empty($recipePath)) { |
||
77 | $recipePath = "deploy.$language"; |
||
78 | } |
||
79 | |||
80 | // Avoid accidentally override of existing file. |
||
81 | if (file_exists($recipePath)) { |
||
82 | $io->warning("$recipePath already exists"); |
||
83 | if (!$io->confirm("Do you want to override the existing file?", false)) { |
||
84 | $io->block('👍🏻'); |
||
85 | exit(1); |
||
0 ignored issues
–
show
|
|||
86 | } |
||
87 | } |
||
88 | |||
89 | // Template |
||
90 | $template = $io->choice('Select project template', $this->recipes(), 'common'); |
||
91 | |||
92 | // Repo |
||
93 | $default = ''; |
||
94 | try { |
||
95 | $process = Process::fromShellCommandline('git remote get-url origin'); |
||
96 | $default = $process->mustRun()->getOutput(); |
||
97 | $default = trim($default); |
||
98 | } catch (RuntimeException $e) { |
||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
|
|||
99 | } |
||
100 | $repository = $io->ask('Repository', $default); |
||
101 | |||
102 | // Guess host |
||
103 | if (preg_match('/github.com:(?<org>[A-Za-z0-9_.\-]+)\//', $repository, $m)) { |
||
104 | $org = $m['org']; |
||
105 | $tempHostFile = tempnam(sys_get_temp_dir(), 'temp-host-file'); |
||
106 | $php = new PhpProcess( |
||
107 | <<<EOF |
||
108 | <?php |
||
109 | \$ch = curl_init('https://api.github.com/orgs/$org'); |
||
110 | curl_setopt(\$ch, CURLOPT_USERAGENT, 'Deployer'); |
||
111 | curl_setopt(\$ch, CURLOPT_CUSTOMREQUEST, 'GET'); |
||
112 | curl_setopt(\$ch, CURLOPT_RETURNTRANSFER, true); |
||
113 | curl_setopt(\$ch, CURLOPT_FOLLOWLOCATION, true); |
||
114 | curl_setopt(\$ch, CURLOPT_MAXREDIRS, 10); |
||
115 | curl_setopt(\$ch, CURLOPT_CONNECTTIMEOUT, 5); |
||
116 | curl_setopt(\$ch, CURLOPT_TIMEOUT, 5); |
||
117 | \$result = curl_exec(\$ch); |
||
118 | curl_close(\$ch); |
||
119 | \$json = json_decode(\$result); |
||
120 | \$host = parse_url(\$json->blog, PHP_URL_HOST); |
||
121 | file_put_contents('$tempHostFile', \$host); |
||
122 | EOF, |
||
123 | ); |
||
124 | $php->start(); |
||
125 | } |
||
126 | |||
127 | // Project |
||
128 | $default = ''; |
||
129 | try { |
||
130 | $process = Process::fromShellCommandline('basename "$PWD"'); |
||
131 | $default = $process->mustRun()->getOutput(); |
||
132 | $default = trim($default); |
||
133 | } catch (RuntimeException $e) { |
||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
|
|||
134 | } |
||
135 | $project = $io->ask('Project name', $default); |
||
136 | |||
137 | // Hosts |
||
138 | $host = null; |
||
139 | if (isset($tempHostFile)) { |
||
140 | $host = file_get_contents($tempHostFile); |
||
141 | } |
||
142 | $hostsString = $io->ask('Hosts (comma separated)', $host); |
||
143 | if ($hostsString !== null) { |
||
144 | $hosts = explode(',', $hostsString); |
||
145 | } else { |
||
146 | $hosts = []; |
||
147 | } |
||
148 | |||
149 | file_put_contents($recipePath, $this->$language($template, $project, $repository, $hosts)); |
||
150 | |||
151 | $this->telemetry(); |
||
152 | $output->writeln(sprintf( |
||
153 | '<info>Successfully created</info> <comment>%s</comment>', |
||
154 | $recipePath, |
||
155 | )); |
||
156 | return 0; |
||
157 | } |
||
158 | |||
159 | private function php(string $template, string $project, string $repository, array $hosts): string |
||
160 | { |
||
161 | $h = ""; |
||
162 | foreach ($hosts as $host) { |
||
163 | $h .= "host('{$host}')\n" . |
||
164 | " ->set('remote_user', 'deployer')\n" . |
||
165 | " ->set('deploy_path', '~/{$project}');\n"; |
||
166 | } |
||
167 | |||
168 | return <<<PHP |
||
169 | <?php |
||
170 | namespace Deployer; |
||
171 | |||
172 | require 'recipe/$template.php'; |
||
173 | |||
174 | // Config |
||
175 | |||
176 | set('repository', '{$repository}'); |
||
177 | |||
178 | add('shared_files', []); |
||
179 | add('shared_dirs', []); |
||
180 | add('writable_dirs', []); |
||
181 | |||
182 | // Hosts |
||
183 | |||
184 | {$h} |
||
185 | // Hooks |
||
186 | |||
187 | after('deploy:failed', 'deploy:unlock'); |
||
188 | |||
189 | PHP; |
||
190 | } |
||
191 | |||
192 | private function yaml(string $template, string $project, string $repository, array $hosts): string |
||
193 | { |
||
194 | $h = ""; |
||
195 | foreach ($hosts as $host) { |
||
196 | $h .= " $host:\n" . |
||
197 | " remote_user: deployer\n" . |
||
198 | " deploy_path: '~/{$project}'\n"; |
||
199 | } |
||
200 | |||
201 | $additionalConfigs = $this->getAdditionalConfigs($template); |
||
202 | |||
203 | return <<<YAML |
||
204 | import: |
||
205 | - recipe/$template.php |
||
206 | |||
207 | config: |
||
208 | repository: '$repository' |
||
209 | $additionalConfigs |
||
210 | hosts: |
||
211 | $h |
||
212 | tasks: |
||
213 | build: |
||
214 | - run: uptime |
||
215 | |||
216 | after: |
||
217 | deploy:failed: deploy:unlock |
||
218 | |||
219 | YAML; |
||
220 | } |
||
221 | |||
222 | private function getAdditionalConfigs(string $template): string |
||
223 | { |
||
224 | if ($template !== 'common') { |
||
225 | return ''; |
||
226 | } |
||
227 | |||
228 | return <<<YAML |
||
229 | shared_files: |
||
230 | - .env |
||
231 | shared_dirs: |
||
232 | - uploads |
||
233 | writable_dirs: |
||
234 | - uploads |
||
235 | |||
236 | YAML; |
||
237 | } |
||
238 | |||
239 | private function recipes(): array |
||
240 | { |
||
241 | $recipes = []; |
||
242 | $dir = new \DirectoryIterator(__DIR__ . '/../../recipe'); |
||
243 | foreach ($dir as $fileinfo) { |
||
244 | if ($fileinfo->isDot()) { |
||
245 | continue; |
||
246 | } |
||
247 | if ($fileinfo->isDir()) { |
||
248 | continue; |
||
249 | } |
||
250 | |||
251 | $recipe = pathinfo($fileinfo->getFilename(), PATHINFO_FILENAME); |
||
252 | |||
253 | if ($recipe === 'README') { |
||
254 | continue; |
||
255 | } |
||
256 | |||
257 | $recipes[] = $recipe; |
||
258 | } |
||
259 | |||
260 | sort($recipes); |
||
261 | return $recipes; |
||
262 | } |
||
263 | } |
||
264 |
For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example: