From 6e58afa80ad9e8d0bde6b224e6e8417ee2b6724b Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Mon, 29 Sep 2025 10:26:38 -0400 Subject: [PATCH 01/12] Update NewCommand.php --- src/NewCommand.php | 120 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/src/NewCommand.php b/src/NewCommand.php index 9d9339e..9f35bfa 100644 --- a/src/NewCommand.php +++ b/src/NewCommand.php @@ -16,7 +16,9 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\Process; +use Throwable; +use function Illuminate\Filesystem\join_paths; use function Laravel\Prompts\confirm; use function Laravel\Prompts\select; use function Laravel\Prompts\text; @@ -87,6 +89,8 @@ protected function interact(InputInterface $input, OutputInterface $output) $this->ensureExtensionsAreAvailable($input, $output); + $this->checkForUpdate($input, $output); + if (! $input->getArgument('name')) { $input->setArgument('name', text( label: 'What is the name of your project?', @@ -199,6 +203,122 @@ protected function ensureExtensionsAreAvailable(InputInterface $input, OutputInt ); } + /** + * Check for newer version of the installer package. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return void + */ + protected function checkForUpdate(InputInterface $input, OutputInterface $output) + { + $package = 'laravel/installer'; + $version = $this->getApplication()->getVersion(); + $versionData = $this->getLatestVersionData($package); + + if ($versionData === false) { + return; + } + + $data = json_decode($versionData, true); + $latestVersion = ltrim($data['packages'][$package][0]['version'], 'v'); + + if (version_compare($version, $latestVersion) !== -1) { + return; + } + + $output->writeln(" WARN A new version of the Laravel Installer is available. You have version {$version} installed, the latest version is {$latestVersion}."); + + $shouldUpdate = confirm( + label: 'Would you like to update now?' + ); + + if ($shouldUpdate) { + $this->runCommands(['composer global update laravel/installer'], $input, $output); + $output->writeln(''); + } + } + + /** + * Get the latest version of the installer package from Packagist. + * + * @param string $package + * @return string|false + */ + protected function getLatestVersionData(string $package): string|false + { + $packagePrefix = str_replace('/', '-', $package); + $cachedPath = join_paths(sys_get_temp_dir(), $packagePrefix.'-version-check.json'); + $lastModifiedPath = join_paths(sys_get_temp_dir(), $packagePrefix.'-last-modified'); + + $cacheExists = file_exists($cachedPath); + $lastModifiedExists = file_exists($lastModifiedPath); + + $cacheLastWrittenAt = $cacheExists ? filemtime($cachedPath) : 0; + $lastModifiedResponse = $lastModifiedExists ? file_get_contents($lastModifiedPath) : null; + + if ($cacheLastWrittenAt > time() - 86400) { + // Cache is less than 24 hours old, use it + return file_get_contents($cachedPath); + } + + $curl = curl_init(); + + $headers = ['User-Agent: Laravel Installer']; + + if ($lastModifiedResponse) { + $headers[] = "If-Modified-Since: {$lastModifiedResponse}"; + } + + curl_setopt_array($curl, [ + CURLOPT_URL => "https://repo.packagist.org/p2/{$package}.json", + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HEADER => true, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_TIMEOUT => 10, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_SSL_VERIFYPEER => true, + ]); + + try { + $response = curl_exec($curl); + $httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE); + $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE); + $error = curl_error($curl); + curl_close($curl); + } catch (Throwable $e) { + return false; + } + + if ($error) { + return false; + } + + $responseHeaders = substr($response, 0, $headerSize); + $result = substr($response, $headerSize); + + $lastModifiedFromResponse = null; + + if (preg_match('/^Last-Modified:\s*(.+)$/mi', $responseHeaders, $matches)) { + $lastModifiedFromResponse = trim($matches[1]); + } + + file_put_contents($lastModifiedPath, $lastModifiedFromResponse); + + if ($httpCode === 304 && $cacheExists) { + touch($cachedPath); + return file_get_contents($cachedPath); + } + + if ($httpCode === 200 && $result !== false) { + file_put_contents($cachedPath, $result); + + return $result; + } + + return ($cacheExists) ? file_get_contents($cachedPath) : false; + } + /** * Execute the command. * From f43672849d68b24f3f9eee6ce215b7d5818d877a Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Mon, 29 Sep 2025 10:31:40 -0400 Subject: [PATCH 02/12] Update NewCommand.php --- src/NewCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/NewCommand.php b/src/NewCommand.php index 9f35bfa..d757e9d 100644 --- a/src/NewCommand.php +++ b/src/NewCommand.php @@ -307,6 +307,7 @@ protected function getLatestVersionData(string $package): string|false if ($httpCode === 304 && $cacheExists) { touch($cachedPath); + return file_get_contents($cachedPath); } From 0469550d9d64b9035d63d67a9db9267c1caff69a Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Mon, 29 Sep 2025 10:32:43 -0400 Subject: [PATCH 03/12] Update NewCommand.php --- src/NewCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NewCommand.php b/src/NewCommand.php index d757e9d..c0a9460 100644 --- a/src/NewCommand.php +++ b/src/NewCommand.php @@ -307,7 +307,7 @@ protected function getLatestVersionData(string $package): string|false if ($httpCode === 304 && $cacheExists) { touch($cachedPath); - + return file_get_contents($cachedPath); } From 5bf6ba326acdd2cfc233316c3cff0b83e0204349 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 29 Sep 2025 17:03:13 +0100 Subject: [PATCH 04/12] Update NewCommand.php --- src/NewCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NewCommand.php b/src/NewCommand.php index c0a9460..8c665da 100644 --- a/src/NewCommand.php +++ b/src/NewCommand.php @@ -227,7 +227,7 @@ protected function checkForUpdate(InputInterface $input, OutputInterface $output return; } - $output->writeln(" WARN A new version of the Laravel Installer is available. You have version {$version} installed, the latest version is {$latestVersion}."); + $output->writeln(" WARN A new version of the Laravel installer is available. You have version {$version} installed, the latest version is {$latestVersion}."); $shouldUpdate = confirm( label: 'Would you like to update now?' From 7587d1dc766269b2d5e98eeb23e2e56279814a4c Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Mon, 29 Sep 2025 13:37:34 -0400 Subject: [PATCH 05/12] Update NewCommand.php --- src/NewCommand.php | 76 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/src/NewCommand.php b/src/NewCommand.php index c0a9460..96c796e 100644 --- a/src/NewCommand.php +++ b/src/NewCommand.php @@ -14,6 +14,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Process\ExecutableFinder; use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\Process; use Throwable; @@ -229,16 +230,79 @@ protected function checkForUpdate(InputInterface $input, OutputInterface $output $output->writeln(" WARN A new version of the Laravel Installer is available. You have version {$version} installed, the latest version is {$latestVersion}."); - $shouldUpdate = confirm( - label: 'Would you like to update now?' - ); + $laravelInstallerPath = (new ExecutableFinder())->find('laravel') ?? ''; + $isHerd = str_contains($laravelInstallerPath, DIRECTORY_SEPARATOR.'Herd'.DIRECTORY_SEPARATOR); + // Intalled via php.new + $isHerdLite = str_contains($laravelInstallerPath, DIRECTORY_SEPARATOR.'herd-lite'.DIRECTORY_SEPARATOR); + + if ($isHerd) { + return $this->confirmUpdateAndContinue( + "To update, open Herd > Settings > PHP > Laravel Installer " + ."and click the \"Update\" button.", + $input, + $output + ); + } + + if ($isHerdLite) { + $message = match(PHP_OS_FAMILY) { + 'Windows' => "Set-ExecutionPolicy Bypass -Scope Process -Force; " + ."[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; " + ."iex ((New-Object System.Net.WebClient).DownloadString('https://php.new/install/windows'))", + 'Darwin' => '/bin/bash -c "$(curl -fsSL https://php.new/install/mac)"', + default => '/bin/bash -c "$(curl -fsSL https://php.new/install/linux)"', + }; - if ($shouldUpdate) { + return $this->confirmUpdateAndContinue($message, $input, $output); + } + + if (confirm(label: 'Would you like to update now?')) { $this->runCommands(['composer global update laravel/installer'], $input, $output); - $output->writeln(''); + $this->proxyLaravelNew($input, $output); } } + /** + * Allow the user to update the Laravel installer and continue. + * + * @param string $message + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return void + */ + protected function confirmUpdateAndContinue(string $message, InputInterface $input, OutputInterface $output): void + { + $output->writeln(""); + $output->writeln(" {$message}"); + + $updated = confirm( + label: 'Would you like to update now?', + yes: 'I have updated', + no: 'Not now', + ); + + if (!$updated) { + return; + } + + $this->proxyLaravelNew($input, $output); + } + + + /** + * Proxy the command to the globally installed Laravel installer. + * + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @return void + */ + protected function proxyLaravelNew(InputInterface $input, OutputInterface $output) + { + $output->writeln(''); + $this->runCommands(['laravel '.$input], $input, $output, workingPath: getcwd()); + exit; + } + /** * Get the latest version of the installer package from Packagist. * @@ -275,7 +339,7 @@ protected function getLatestVersionData(string $package): string|false CURLOPT_RETURNTRANSFER => true, CURLOPT_HEADER => true, CURLOPT_HTTPHEADER => $headers, - CURLOPT_TIMEOUT => 10, + CURLOPT_TIMEOUT => 3, CURLOPT_FOLLOWLOCATION => true, CURLOPT_SSL_VERIFYPEER => true, ]); From ab65d1ef6cfab132db1b1957b1299bb492cc1594 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Mon, 29 Sep 2025 13:38:22 -0400 Subject: [PATCH 06/12] Update NewCommand.php --- src/NewCommand.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NewCommand.php b/src/NewCommand.php index d14cffe..8da1cc2 100644 --- a/src/NewCommand.php +++ b/src/NewCommand.php @@ -228,7 +228,7 @@ protected function checkForUpdate(InputInterface $input, OutputInterface $output return; } - $output->writeln(" WARN A new version of the Laravel installer is available. You have version {$version} installed, the latest version is {$latestVersion}."); + $output->writeln(" WARN A new version of the Laravel Installer is available. You have version {$version} installed, the latest version is {$latestVersion}."); $laravelInstallerPath = (new ExecutableFinder())->find('laravel') ?? ''; $isHerd = str_contains($laravelInstallerPath, DIRECTORY_SEPARATOR.'Herd'.DIRECTORY_SEPARATOR); @@ -263,7 +263,7 @@ protected function checkForUpdate(InputInterface $input, OutputInterface $output } /** - * Allow the user to update the Laravel installer and continue. + * Allow the user to update the Laravel Installer and continue. * * @param string $message * @param \Symfony\Component\Console\Input\InputInterface $input @@ -290,7 +290,7 @@ protected function confirmUpdateAndContinue(string $message, InputInterface $inp /** - * Proxy the command to the globally installed Laravel installer. + * Proxy the command to the globally installed Laravel Installer. * * @param \Symfony\Component\Console\Input\InputInterface $input * @param \Symfony\Component\Console\Output\OutputInterface $output From 713cbc5ee0ff6ed19c5e311dda382fa39cc2aa0d Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Mon, 29 Sep 2025 13:40:22 -0400 Subject: [PATCH 07/12] Update NewCommand.php --- src/NewCommand.php | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/NewCommand.php b/src/NewCommand.php index 8da1cc2..139dede 100644 --- a/src/NewCommand.php +++ b/src/NewCommand.php @@ -237,8 +237,8 @@ protected function checkForUpdate(InputInterface $input, OutputInterface $output if ($isHerd) { return $this->confirmUpdateAndContinue( - "To update, open Herd > Settings > PHP > Laravel Installer " - ."and click the \"Update\" button.", + 'To update, open Herd > Settings > PHP > Laravel Installer ' + .'and click the "Update" button.', $input, $output ); @@ -246,14 +246,14 @@ protected function checkForUpdate(InputInterface $input, OutputInterface $output if ($isHerdLite) { $message = match(PHP_OS_FAMILY) { - 'Windows' => "Set-ExecutionPolicy Bypass -Scope Process -Force; " - ."[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; " + 'Windows' => 'Set-ExecutionPolicy Bypass -Scope Process -Force; ' + .'[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; ' ."iex ((New-Object System.Net.WebClient).DownloadString('https://php.new/install/windows'))", 'Darwin' => '/bin/bash -c "$(curl -fsSL https://php.new/install/mac)"', default => '/bin/bash -c "$(curl -fsSL https://php.new/install/linux)"', }; - return $this->confirmUpdateAndContinue($message, $input, $output); + return $this->confirmUpdateAndContinue($message, $input, $output); } if (confirm(label: 'Would you like to update now?')) { @@ -272,20 +272,20 @@ protected function checkForUpdate(InputInterface $input, OutputInterface $output */ protected function confirmUpdateAndContinue(string $message, InputInterface $input, OutputInterface $output): void { - $output->writeln(""); - $output->writeln(" {$message}"); + $output->writeln(""); + $output->writeln(" {$message}"); - $updated = confirm( - label: 'Would you like to update now?', - yes: 'I have updated', - no: 'Not now', - ); + $updated = confirm( + label: 'Would you like to update now?', + yes: 'I have updated', + no: 'Not now', + ); - if (!$updated) { - return; - } + if (!$updated) { + return; + } - $this->proxyLaravelNew($input, $output); + $this->proxyLaravelNew($input, $output); } From aa82d0f9710034b09969ba82ed735d5f008b5e9c Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Mon, 29 Sep 2025 13:42:36 -0400 Subject: [PATCH 08/12] Update NewCommand.php --- src/NewCommand.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NewCommand.php b/src/NewCommand.php index 139dede..9206d5c 100644 --- a/src/NewCommand.php +++ b/src/NewCommand.php @@ -245,7 +245,7 @@ protected function checkForUpdate(InputInterface $input, OutputInterface $output } if ($isHerdLite) { - $message = match(PHP_OS_FAMILY) { + $message = match (PHP_OS_FAMILY) { 'Windows' => 'Set-ExecutionPolicy Bypass -Scope Process -Force; ' .'[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; ' ."iex ((New-Object System.Net.WebClient).DownloadString('https://php.new/install/windows'))", @@ -272,7 +272,7 @@ protected function checkForUpdate(InputInterface $input, OutputInterface $output */ protected function confirmUpdateAndContinue(string $message, InputInterface $input, OutputInterface $output): void { - $output->writeln(""); + $output->writeln(''); $output->writeln(" {$message}"); $updated = confirm( @@ -281,7 +281,7 @@ protected function confirmUpdateAndContinue(string $message, InputInterface $inp no: 'Not now', ); - if (!$updated) { + if (! $updated) { return; } From 095e33ea521db87f6aaff946a66b8cd6dee10e33 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Mon, 29 Sep 2025 13:43:28 -0400 Subject: [PATCH 09/12] Update NewCommand.php --- src/NewCommand.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/NewCommand.php b/src/NewCommand.php index 9206d5c..ce2df09 100644 --- a/src/NewCommand.php +++ b/src/NewCommand.php @@ -288,7 +288,6 @@ protected function confirmUpdateAndContinue(string $message, InputInterface $inp $this->proxyLaravelNew($input, $output); } - /** * Proxy the command to the globally installed Laravel Installer. * From 26507584d8249bba04eb87d713c805d391228411 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Mon, 29 Sep 2025 13:45:48 -0400 Subject: [PATCH 10/12] Update NewCommand.php --- src/NewCommand.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/NewCommand.php b/src/NewCommand.php index ce2df09..1fccc02 100644 --- a/src/NewCommand.php +++ b/src/NewCommand.php @@ -236,12 +236,14 @@ protected function checkForUpdate(InputInterface $input, OutputInterface $output $isHerdLite = str_contains($laravelInstallerPath, DIRECTORY_SEPARATOR.'herd-lite'.DIRECTORY_SEPARATOR); if ($isHerd) { - return $this->confirmUpdateAndContinue( + $this->confirmUpdateAndContinue( 'To update, open Herd > Settings > PHP > Laravel Installer ' .'and click the "Update" button.', $input, $output ); + + return; } if ($isHerdLite) { @@ -253,7 +255,9 @@ protected function checkForUpdate(InputInterface $input, OutputInterface $output default => '/bin/bash -c "$(curl -fsSL https://php.new/install/linux)"', }; - return $this->confirmUpdateAndContinue($message, $input, $output); + $this->confirmUpdateAndContinue($message, $input, $output); + + return; } if (confirm(label: 'Would you like to update now?')) { @@ -295,7 +299,7 @@ protected function confirmUpdateAndContinue(string $message, InputInterface $inp * @param \Symfony\Component\Console\Output\OutputInterface $output * @return void */ - protected function proxyLaravelNew(InputInterface $input, OutputInterface $output) + protected function proxyLaravelNew(InputInterface $input, OutputInterface $output): void { $output->writeln(''); $this->runCommands(['laravel '.$input], $input, $output, workingPath: getcwd()); From 1adb6964014a49b1e1043fb148d85eb6c4b2a695 Mon Sep 17 00:00:00 2001 From: Joe Tannenbaum Date: Mon, 29 Sep 2025 13:52:48 -0400 Subject: [PATCH 11/12] Update NewCommand.php --- src/NewCommand.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/NewCommand.php b/src/NewCommand.php index 1fccc02..587e431 100644 --- a/src/NewCommand.php +++ b/src/NewCommand.php @@ -255,6 +255,9 @@ protected function checkForUpdate(InputInterface $input, OutputInterface $output default => '/bin/bash -c "$(curl -fsSL https://php.new/install/linux)"', }; + $output->writeln(''); + $output->writeln(' To update, run the following command in your terminal:'); + $this->confirmUpdateAndContinue($message, $input, $output); return; From e1d5bfb03459bbafa7d343de5c3b5f9164423502 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 30 Sep 2025 11:27:02 +0100 Subject: [PATCH 12/12] formatting --- src/NewCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NewCommand.php b/src/NewCommand.php index 587e431..04dea0f 100644 --- a/src/NewCommand.php +++ b/src/NewCommand.php @@ -228,7 +228,7 @@ protected function checkForUpdate(InputInterface $input, OutputInterface $output return; } - $output->writeln(" WARN A new version of the Laravel Installer is available. You have version {$version} installed, the latest version is {$latestVersion}."); + $output->writeln(" WARN A new version of the Laravel installer is available. You have version {$version} installed, the latest version is {$latestVersion}."); $laravelInstallerPath = (new ExecutableFinder())->find('laravel') ?? ''; $isHerd = str_contains($laravelInstallerPath, DIRECTORY_SEPARATOR.'Herd'.DIRECTORY_SEPARATOR);