OwlCyberSecurity - MANAGER
Edit File: InstallCommand.php
<?php namespace Laravel\Breeze\Console; use Illuminate\Console\Command; use Illuminate\Contracts\Console\PromptsForMissingInput; use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Str; use RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Finder\Finder; use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\Process; use function Laravel\Prompts\confirm; use function Laravel\Prompts\multiselect; use function Laravel\Prompts\select; class InstallCommand extends Command implements PromptsForMissingInput { use InstallsApiStack, InstallsBladeStack, InstallsInertiaStacks, InstallsLivewireStack; /** * The name and signature of the console command. * * @var string */ protected $signature = 'breeze:install {stack : The development stack that should be installed (blade,livewire,livewire-functional,react,vue,api)} {--dark : Indicate that dark mode support should be installed} {--pest : Indicate that Pest should be installed} {--ssr : Indicates if Inertia SSR support should be installed} {--typescript : Indicates if TypeScript is preferred for the Inertia stack (Experimental)} {--composer=global : Absolute path to the Composer binary which should be used to install packages}'; /** * The console command description. * * @var string */ protected $description = 'Install the Breeze controllers and resources'; /** * Execute the console command. * * @return int|null */ public function handle() { if ($this->argument('stack') === 'vue') { return $this->installInertiaVueStack(); } elseif ($this->argument('stack') === 'react') { return $this->installInertiaReactStack(); } elseif ($this->argument('stack') === 'api') { return $this->installApiStack(); } elseif ($this->argument('stack') === 'blade') { return $this->installBladeStack(); } elseif ($this->argument('stack') === 'livewire') { return $this->installLivewireStack(); } elseif ($this->argument('stack') === 'livewire-functional') { return $this->installLivewireStack(true); } $this->components->error('Invalid stack. Supported stacks are [blade], [livewire], [livewire-functional], [react], [vue], and [api].'); return 1; } /** * Install Breeze's tests. * * @return bool */ protected function installTests() { (new Filesystem)->ensureDirectoryExists(base_path('tests/Feature')); $stubStack = match ($this->argument('stack')) { 'api' => 'api', 'livewire' => 'livewire-common', 'livewire-functional' => 'livewire-common', default => 'default', }; if ($this->option('pest') || $this->isUsingPest()) { if ($this->hasComposerPackage('phpunit/phpunit')) { $this->removeComposerPackages(['phpunit/phpunit'], true); } if (! $this->requireComposerPackages(['pestphp/pest:^2.0', 'pestphp/pest-plugin-laravel:^2.0'], true)) { return false; } (new Filesystem)->copyDirectory(__DIR__.'/../../stubs/'.$stubStack.'/pest-tests/Feature', base_path('tests/Feature')); (new Filesystem)->copyDirectory(__DIR__.'/../../stubs/'.$stubStack.'/pest-tests/Unit', base_path('tests/Unit')); (new Filesystem)->copy(__DIR__.'/../../stubs/'.$stubStack.'/pest-tests/Pest.php', base_path('tests/Pest.php')); } else { (new Filesystem)->copyDirectory(__DIR__.'/../../stubs/'.$stubStack.'/tests/Feature', base_path('tests/Feature')); } return true; } /** * Install the middleware to a group in the application Http Kernel. * * @param string $after * @param string $name * @param string $group * @return void */ protected function installMiddlewareAfter($after, $name, $group = 'web') { $httpKernel = file_get_contents(app_path('Http/Kernel.php')); $middlewareGroups = Str::before(Str::after($httpKernel, '$middlewareGroups = ['), '];'); $middlewareGroup = Str::before(Str::after($middlewareGroups, "'$group' => ["), '],'); if (! Str::contains($middlewareGroup, $name)) { $modifiedMiddlewareGroup = str_replace( $after.',', $after.','.PHP_EOL.' '.$name.',', $middlewareGroup, ); file_put_contents(app_path('Http/Kernel.php'), str_replace( $middlewareGroups, str_replace($middlewareGroup, $modifiedMiddlewareGroup, $middlewareGroups), $httpKernel )); } } /** * Determine if the given Composer package is installed. * * @param string $package * @return bool */ protected function hasComposerPackage($package) { $packages = json_decode(file_get_contents(base_path('composer.json')), true); return array_key_exists($package, $packages['require'] ?? []) || array_key_exists($package, $packages['require-dev'] ?? []); } /** * Installs the given Composer Packages into the application. * * @param array $packages * @param bool $asDev * @return bool */ protected function requireComposerPackages(array $packages, $asDev = false) { $composer = $this->option('composer'); if ($composer !== 'global') { $command = ['php', $composer, 'require']; } $command = array_merge( $command ?? ['composer', 'require'], $packages, $asDev ? ['--dev'] : [], ); return (new Process($command, base_path(), ['COMPOSER_MEMORY_LIMIT' => '-1'])) ->setTimeout(null) ->run(function ($type, $output) { $this->output->write($output); }) === 0; } /** * Removes the given Composer Packages from the application. * * @param array $packages * @param bool $asDev * @return bool */ protected function removeComposerPackages(array $packages, $asDev = false) { $composer = $this->option('composer'); if ($composer !== 'global') { $command = ['php', $composer, 'remove']; } $command = array_merge( $command ?? ['composer', 'remove'], $packages, $asDev ? ['--dev'] : [], ); return (new Process($command, base_path(), ['COMPOSER_MEMORY_LIMIT' => '-1'])) ->setTimeout(null) ->run(function ($type, $output) { $this->output->write($output); }) === 0; } /** * Update the "package.json" file. * * @param callable $callback * @param bool $dev * @return void */ protected static function updateNodePackages(callable $callback, $dev = true) { if (! file_exists(base_path('package.json'))) { return; } $configurationKey = $dev ? 'devDependencies' : 'dependencies'; $packages = json_decode(file_get_contents(base_path('package.json')), true); $packages[$configurationKey] = $callback( array_key_exists($configurationKey, $packages) ? $packages[$configurationKey] : [], $configurationKey ); ksort($packages[$configurationKey]); file_put_contents( base_path('package.json'), json_encode($packages, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT).PHP_EOL ); } /** * Delete the "node_modules" directory and remove the associated lock files. * * @return void */ protected static function flushNodeModules() { tap(new Filesystem, function ($files) { $files->deleteDirectory(base_path('node_modules')); $files->delete(base_path('yarn.lock')); $files->delete(base_path('package-lock.json')); }); } /** * Replace a given string within a given file. * * @param string $search * @param string $replace * @param string $path * @return void */ protected function replaceInFile($search, $replace, $path) { file_put_contents($path, str_replace($search, $replace, file_get_contents($path))); } /** * Get the path to the appropriate PHP binary. * * @return string */ protected function phpBinary() { return (new PhpExecutableFinder())->find(false) ?: 'php'; } /** * Run the given commands. * * @param array $commands * @return void */ protected function runCommands($commands) { $process = Process::fromShellCommandline(implode(' && ', $commands), null, null, null, null); if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) { try { $process->setTty(true); } catch (RuntimeException $e) { $this->output->writeln(' <bg=yellow;fg=black> WARN </> '.$e->getMessage().PHP_EOL); } } $process->run(function ($type, $line) { $this->output->write(' '.$line); }); } /** * Remove Tailwind dark classes from the given files. * * @param \Symfony\Component\Finder\Finder $finder * @return void */ protected function removeDarkClasses(Finder $finder) { foreach ($finder as $file) { file_put_contents($file->getPathname(), preg_replace('/\sdark:[^\s"\']+/', '', $file->getContents())); } } /** * Prompt for missing input arguments using the returned questions. * * @return array */ protected function promptForMissingArgumentsUsing() { return [ 'stack' => fn () => select( label: 'Which Breeze stack would you like to install?', options: [ 'blade' => 'Blade with Alpine', 'livewire' => 'Livewire (Volt Class API) with Alpine', 'livewire-functional' => 'Livewire (Volt Functional API) with Alpine', 'react' => 'React with Inertia', 'vue' => 'Vue with Inertia', 'api' => 'API only', ], scroll: 6, ), ]; } /** * Interact further with the user if they were prompted for missing arguments. * * @param \Symfony\Component\Console\Input\InputInterface $input * @param \Symfony\Component\Console\Output\OutputInterface $output * @return void */ protected function afterPromptingForMissingArguments(InputInterface $input, OutputInterface $output) { $stack = $input->getArgument('stack'); if (in_array($stack, ['react', 'vue'])) { collect(multiselect( label: 'Would you like any optional features?', options: [ 'dark' => 'Dark mode', 'ssr' => 'Inertia SSR', 'typescript' => 'TypeScript (experimental)', ] ))->each(fn ($option) => $input->setOption($option, true)); } elseif (in_array($stack, ['blade', 'livewire', 'livewire-functional'])) { $input->setOption('dark', confirm( label: 'Would you like dark mode support?', default: false )); } $input->setOption('pest', select( label: 'Which testing framework do you prefer?', options: ['PHPUnit', 'Pest'], default: $this->isUsingPest() ? 'Pest' : 'PHPUnit', ) === 'Pest'); } /** * Determine whether the project is already using Pest. * * @return bool */ protected function isUsingPest() { return class_exists(\Pest\TestSuite::class); } }