Skip to content

Commit 8fa21f7

Browse files
Check for new version of installer on new (#438)
* Update NewCommand.php * Update NewCommand.php * Update NewCommand.php * Update NewCommand.php * Update NewCommand.php * Update NewCommand.php * Update NewCommand.php * Update NewCommand.php * Update NewCommand.php * Update NewCommand.php * Update NewCommand.php * formatting --------- Co-authored-by: Taylor Otwell <[email protected]> Co-authored-by: Taylor Otwell <[email protected]>
1 parent ea31c85 commit 8fa21f7

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed

src/NewCommand.php

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@
1414
use Symfony\Component\Console\Input\InputInterface;
1515
use Symfony\Component\Console\Input\InputOption;
1616
use Symfony\Component\Console\Output\OutputInterface;
17+
use Symfony\Component\Process\ExecutableFinder;
1718
use Symfony\Component\Process\PhpExecutableFinder;
1819
use Symfony\Component\Process\Process;
20+
use Throwable;
1921

22+
use function Illuminate\Filesystem\join_paths;
2023
use function Laravel\Prompts\confirm;
2124
use function Laravel\Prompts\select;
2225
use function Laravel\Prompts\text;
@@ -87,6 +90,8 @@ protected function interact(InputInterface $input, OutputInterface $output)
8790

8891
$this->ensureExtensionsAreAvailable($input, $output);
8992

93+
$this->checkForUpdate($input, $output);
94+
9095
if (! $input->getArgument('name')) {
9196
$input->setArgument('name', text(
9297
label: 'What is the name of your project?',
@@ -199,6 +204,192 @@ protected function ensureExtensionsAreAvailable(InputInterface $input, OutputInt
199204
);
200205
}
201206

207+
/**
208+
* Check for newer version of the installer package.
209+
*
210+
* @param \Symfony\Component\Console\Input\InputInterface $input
211+
* @param \Symfony\Component\Console\Output\OutputInterface $output
212+
* @return void
213+
*/
214+
protected function checkForUpdate(InputInterface $input, OutputInterface $output)
215+
{
216+
$package = 'laravel/installer';
217+
$version = $this->getApplication()->getVersion();
218+
$versionData = $this->getLatestVersionData($package);
219+
220+
if ($versionData === false) {
221+
return;
222+
}
223+
224+
$data = json_decode($versionData, true);
225+
$latestVersion = ltrim($data['packages'][$package][0]['version'], 'v');
226+
227+
if (version_compare($version, $latestVersion) !== -1) {
228+
return;
229+
}
230+
231+
$output->writeln(" <bg=yellow;fg=black> WARN </> A new version of the Laravel installer is available. You have version {$version} installed, the latest version is {$latestVersion}.");
232+
233+
$laravelInstallerPath = (new ExecutableFinder())->find('laravel') ?? '';
234+
$isHerd = str_contains($laravelInstallerPath, DIRECTORY_SEPARATOR.'Herd'.DIRECTORY_SEPARATOR);
235+
// Intalled via php.new
236+
$isHerdLite = str_contains($laravelInstallerPath, DIRECTORY_SEPARATOR.'herd-lite'.DIRECTORY_SEPARATOR);
237+
238+
if ($isHerd) {
239+
$this->confirmUpdateAndContinue(
240+
'To update, open <options=bold>Herd</> > <options=bold>Settings</> > <options=bold>PHP</> > <options=bold>Laravel Installer</> '
241+
.'and click the <options=bold>"Update"</> button.',
242+
$input,
243+
$output
244+
);
245+
246+
return;
247+
}
248+
249+
if ($isHerdLite) {
250+
$message = match (PHP_OS_FAMILY) {
251+
'Windows' => 'Set-ExecutionPolicy Bypass -Scope Process -Force; '
252+
.'[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; '
253+
."iex ((New-Object System.Net.WebClient).DownloadString('https://php.new/install/windows'))",
254+
'Darwin' => '/bin/bash -c "$(curl -fsSL https://php.new/install/mac)"',
255+
default => '/bin/bash -c "$(curl -fsSL https://php.new/install/linux)"',
256+
};
257+
258+
$output->writeln('');
259+
$output->writeln(' To update, run the following command in your terminal:');
260+
261+
$this->confirmUpdateAndContinue($message, $input, $output);
262+
263+
return;
264+
}
265+
266+
if (confirm(label: 'Would you like to update now?')) {
267+
$this->runCommands(['composer global update laravel/installer'], $input, $output);
268+
$this->proxyLaravelNew($input, $output);
269+
}
270+
}
271+
272+
/**
273+
* Allow the user to update the Laravel Installer and continue.
274+
*
275+
* @param string $message
276+
* @param \Symfony\Component\Console\Input\InputInterface $input
277+
* @param \Symfony\Component\Console\Output\OutputInterface $output
278+
* @return void
279+
*/
280+
protected function confirmUpdateAndContinue(string $message, InputInterface $input, OutputInterface $output): void
281+
{
282+
$output->writeln('');
283+
$output->writeln(" {$message}");
284+
285+
$updated = confirm(
286+
label: 'Would you like to update now?',
287+
yes: 'I have updated',
288+
no: 'Not now',
289+
);
290+
291+
if (! $updated) {
292+
return;
293+
}
294+
295+
$this->proxyLaravelNew($input, $output);
296+
}
297+
298+
/**
299+
* Proxy the command to the globally installed Laravel Installer.
300+
*
301+
* @param \Symfony\Component\Console\Input\InputInterface $input
302+
* @param \Symfony\Component\Console\Output\OutputInterface $output
303+
* @return void
304+
*/
305+
protected function proxyLaravelNew(InputInterface $input, OutputInterface $output): void
306+
{
307+
$output->writeln('');
308+
$this->runCommands(['laravel '.$input], $input, $output, workingPath: getcwd());
309+
exit;
310+
}
311+
312+
/**
313+
* Get the latest version of the installer package from Packagist.
314+
*
315+
* @param string $package
316+
* @return string|false
317+
*/
318+
protected function getLatestVersionData(string $package): string|false
319+
{
320+
$packagePrefix = str_replace('/', '-', $package);
321+
$cachedPath = join_paths(sys_get_temp_dir(), $packagePrefix.'-version-check.json');
322+
$lastModifiedPath = join_paths(sys_get_temp_dir(), $packagePrefix.'-last-modified');
323+
324+
$cacheExists = file_exists($cachedPath);
325+
$lastModifiedExists = file_exists($lastModifiedPath);
326+
327+
$cacheLastWrittenAt = $cacheExists ? filemtime($cachedPath) : 0;
328+
$lastModifiedResponse = $lastModifiedExists ? file_get_contents($lastModifiedPath) : null;
329+
330+
if ($cacheLastWrittenAt > time() - 86400) {
331+
// Cache is less than 24 hours old, use it
332+
return file_get_contents($cachedPath);
333+
}
334+
335+
$curl = curl_init();
336+
337+
$headers = ['User-Agent: Laravel Installer'];
338+
339+
if ($lastModifiedResponse) {
340+
$headers[] = "If-Modified-Since: {$lastModifiedResponse}";
341+
}
342+
343+
curl_setopt_array($curl, [
344+
CURLOPT_URL => "https://repo.packagist.org/p2/{$package}.json",
345+
CURLOPT_RETURNTRANSFER => true,
346+
CURLOPT_HEADER => true,
347+
CURLOPT_HTTPHEADER => $headers,
348+
CURLOPT_TIMEOUT => 3,
349+
CURLOPT_FOLLOWLOCATION => true,
350+
CURLOPT_SSL_VERIFYPEER => true,
351+
]);
352+
353+
try {
354+
$response = curl_exec($curl);
355+
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
356+
$headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
357+
$error = curl_error($curl);
358+
curl_close($curl);
359+
} catch (Throwable $e) {
360+
return false;
361+
}
362+
363+
if ($error) {
364+
return false;
365+
}
366+
367+
$responseHeaders = substr($response, 0, $headerSize);
368+
$result = substr($response, $headerSize);
369+
370+
$lastModifiedFromResponse = null;
371+
372+
if (preg_match('/^Last-Modified:\s*(.+)$/mi', $responseHeaders, $matches)) {
373+
$lastModifiedFromResponse = trim($matches[1]);
374+
}
375+
376+
file_put_contents($lastModifiedPath, $lastModifiedFromResponse);
377+
378+
if ($httpCode === 304 && $cacheExists) {
379+
touch($cachedPath);
380+
381+
return file_get_contents($cachedPath);
382+
}
383+
384+
if ($httpCode === 200 && $result !== false) {
385+
file_put_contents($cachedPath, $result);
386+
387+
return $result;
388+
}
389+
390+
return ($cacheExists) ? file_get_contents($cachedPath) : false;
391+
}
392+
202393
/**
203394
* Execute the command.
204395
*

0 commit comments

Comments
 (0)