├── InstallLaravel.php ├── LICENSE └── README.md /InstallLaravel.php: -------------------------------------------------------------------------------- 1 | "sets([ 112 | LaravelSetList::LARAVEL_90 113 | ]); 114 | };\n\n", 115 | 116 | 'phpstan.neon' => "includes: 117 | - ./vendor/nunomaduro/larastan/extension.neon 118 | 119 | parameters: 120 | paths: 121 | - app/ 122 | 123 | # Level 9 is the highest level 124 | level: 5 125 | 126 | # ignoreErrors: 127 | # - '#PHPDoc tag @var#' 128 | # 129 | # excludePaths: 130 | # - ./*/*/FileToBeExcluded.php 131 | # 132 | # checkMissingIterableValueType: false\n\n", 133 | ]; 134 | 135 | $manualSteps = [ 136 | 'Create a database for your new Laravel instance', 137 | 'Edit .env so that the database connection info is correct/complete', 138 | "run {$color}php artisan migrate{$noColor}", 139 | "run {$color}php artisan db:seed{$noColor}", 140 | ]; 141 | 142 | $manualStepsFilament = [ 143 | "run {$color}php artisan filament:upgrade{$noColor}", 144 | "run {$color}php artisan make:filament-user{$noColor}", 145 | "run {$color}php artisan shield:install{$noColor}", 146 | ]; 147 | 148 | $manualStepsJetstreamInertia = [ 149 | "npm install", 150 | "npm run build", 151 | ]; 152 | 153 | $instructions = [ 154 | "You can run self-contained web server for your instance by typing {$color}php artisan serve{$noColor}", 155 | "Laravel docs are available from https://laravel.com/docs/9.x/", 156 | ]; 157 | $instructionsFilament = [ 158 | "Filament docs are available from https://filamentphp.com/docs/2.x/", 159 | "You can find the filament login screen under /admin", 160 | "Filament docs are available from https://filamentphp.com/docs/2.x/", 161 | "Information on how to use the installed filament plugins, go to https://filamentphp.com/plugins", 162 | ]; 163 | $instructionsJetstream = [ 164 | "Jetstream docs are available from https://jetstream.laravel.com/2.x/introduction.html", 165 | ]; 166 | 167 | ////////////////////////////////////////////////////////////////////////// 168 | /////////////////////////////// Install Code ///////////////////////////// 169 | ////////////////////////////////////////////////////////////////////////// 170 | 171 | 172 | // Check for args 173 | $targetDir = $argv[1] ?? null; 174 | if (!$targetDir) { 175 | die("Usage: InstallLaravel [--with-filament|--with-jetstream-livewire|--with-jetstream-inertia|--with-strict-mode]\n"); 176 | } 177 | if (file_exists($targetDir)) { 178 | die("Error: {$color}$targetDir{$noColor} already exists\n"); 179 | } 180 | if (strpos($targetDir, '--') === 0) { 181 | die("Usage: InstallLaravel [--with-filament|--with-jetstream-livewire|--with-jetstream-inertia|--with-strict-mode]\n"); 182 | } 183 | 184 | 185 | /////////////////////////////// Parse Args ///////////////////////////// 186 | $validArgs = [ 187 | '--with-filament', 188 | '--with-jetstream', 189 | '--with-jetstream-livewire', 190 | '--with-jetstream-inertia', 191 | '--with-strict-mode' 192 | ]; 193 | $withFilament = false; 194 | $withJetstreamLivewire = false; 195 | $withJetstreamInertia = false; 196 | $withStrictMode = false; 197 | for($i=2; $i<$argc; $i++) { 198 | switch($argv[$i]) { 199 | case '--with-filament': 200 | $withFilament = true; 201 | break; 202 | case '--with-jetstream': 203 | case '--with-jetstream-livewire': 204 | $withJetstreamLivewire = true; 205 | break; 206 | case '--with-jetstream-inertia': 207 | $withJetstreamInertia = true; 208 | break; 209 | case '--with-strict-mode': 210 | $withStrictMode = true; 211 | break; 212 | default: 213 | die("Usage: InstallLaravel [--with-filament|--with-jetstream-livewire|--with-jetstream-inertia|--with-strict-mode]\n"); 214 | } 215 | } 216 | 217 | if ($withFilament && ($withJetstreamLivewire || $withJetstreamInertia)) { 218 | die("Please choose either Filament or Jetstream support but not both\n"); 219 | } 220 | if ($withJetstreamLivewire && $withJetstreamInertia) { 221 | die("Please choose either Jetstream-Livewire or Jetstream-Inertia support but not both\n"); 222 | } 223 | 224 | 225 | /////////////////////////////// Try to find Laravel Installer ///////////////////////////// 226 | $laravelInstaller = findLaravelInstaller(); 227 | 228 | 229 | /////////////////////////////// Install Laravel ///////////////////////////// 230 | $cmd = "$laravelInstaller --no-interaction new $targetDir"; 231 | if (!$laravelInstaller) { // No installer found, use composer 232 | $cmd = "composer create-project --prefer-dist laravel/laravel --no-interaction $targetDir"; 233 | } 234 | print "{$color}Installing Laravel into [$targetDir]{$noColor}\n"; 235 | $rc = system($cmd); 236 | if ($rc === false) { 237 | die("System command [$cmd] failed ... exiting\n"); 238 | } 239 | 240 | 241 | /////////////////////////////// Build final package/steps/etc Lists ///////////////////////////// 242 | if ($withFilament) { 243 | $packages = array_merge($packages, $packagesFilament); 244 | $postProcessSteps = array_merge($postProcessSteps, $postProcessStepsFilament); 245 | $manualSteps = array_merge($manualSteps, $manualStepsFilament); 246 | $instructions = array_merge($instructions, $instructionsFilament); 247 | } elseif ($withJetstreamLivewire) { 248 | $packages = array_merge($packages, $packagesJetstream); 249 | $postProcessSteps = array_merge($postProcessSteps, $postProcessStepsJetstreamInertia); 250 | $instructions = array_merge($instructions, $instructionsJetstream); 251 | } elseif ($withJetstreamInertia) { 252 | $packages = array_merge($packages, $packagesJetstream); 253 | $postProcessSteps = array_merge($postProcessSteps, $postProcessStepsJetstreamInertia); 254 | $instructions = array_merge($instructions, $instructionsJetstream); 255 | } 256 | 257 | 258 | /////////////////////////////// Change into install dir ///////////////////////////// 259 | chdir($targetDir); 260 | 261 | 262 | /////////////////////////////// Install main packages ///////////////////////////// 263 | $cmd = "composer --no-interaction require " . implode(' ', $packages); 264 | execSteps($cmd, $color, $noColor); 265 | 266 | /////////////////////////////// Install dev packages ///////////////////////////// 267 | $cmd = "composer --no-interaction --dev require " . implode(' ', $packagesDev); 268 | execSteps($cmd, $color, $noColor); 269 | 270 | /////////////////////////////// Execute PostProcess Steps ///////////////////////////// 271 | execSteps($postProcessSteps, $color, $noColor); 272 | 273 | /////////////////////////////// Create PostProcess Files ///////////////////////////// 274 | foreach ($postProcessFiles as $k => $v) { 275 | print "Creating {$color}[$k]{$noColor}\n"; 276 | file_put_contents($k, $v); 277 | } 278 | 279 | /////////////////////////////// Patch user model for filament-shield ///////////////////////////// 280 | if (in_array("bezhansalleh/filament-shield", $packages)) { 281 | patchFilamentConfig($color, $noColor); 282 | patchFilamentTablesConfigFile($color, $noColor); 283 | patchFilamentComposerJson($color, $noColor); 284 | patchUserModelForFilamentShield($color, $noColor); 285 | } 286 | 287 | /////////////////////////////// Patch AppServiceProvider for StrictMode ///////////////////////////// 288 | if ($withStrictMode) { 289 | patchAppServiceProviderForStrictMode($color, $noColor); 290 | } 291 | 292 | 293 | print "\n"; 294 | print "{$color}"; 295 | print "============================================\n"; 296 | print "All done ... now perform these manual steps:\n"; 297 | print "============================================\n"; 298 | print "{$noColor}"; 299 | foreach ($manualSteps as $k => $v) { 300 | $kk = $k + 1; 301 | print "$kk) $v\n"; 302 | } 303 | print "\nSome hints to get you started:\n\n"; 304 | foreach ($instructions as $k => $v) { 305 | $kk = $k + 1; 306 | print "$kk) $v\n"; 307 | } 308 | 309 | print "\nNow go build something {$color}awesome!{$noColor}\n\n"; 310 | 311 | 312 | ////////////////////////////////////////////////////////////////////////// 313 | ////////////////////////////// Util Functions //////////////////////////// 314 | ////////////////////////////////////////////////////////////////////////// 315 | 316 | 317 | function commandExists($command): bool 318 | { 319 | $whereIsCommand = (PHP_OS == 'WINNT') ? 'where' : 'which'; 320 | 321 | $process = proc_open( 322 | "$whereIsCommand $command", 323 | array( 324 | 0 => array("pipe", "r"), //STDIN 325 | 1 => array("pipe", "w"), //STDOUT 326 | 2 => array("pipe", "w"), //STDERR 327 | ), 328 | $pipes 329 | ); 330 | if ($process !== false) { 331 | $stdout = stream_get_contents($pipes[1]); 332 | $stderr = stream_get_contents($pipes[2]); 333 | fclose($pipes[1]); 334 | fclose($pipes[2]); 335 | proc_close($process); 336 | 337 | return $stdout != ''; 338 | } 339 | 340 | return false; 341 | } 342 | 343 | 344 | function execSteps(mixed $steps, string $color, string $noColor): void 345 | { 346 | $steps = (array)$steps; 347 | foreach ($steps as $step) { 348 | print "Executing {$color}$step{$noColor}\n"; 349 | $rc = system($step); 350 | if ($rc === false) { 351 | throw new \Exception("Command [$step] returned an error"); 352 | } 353 | } 354 | } 355 | 356 | 357 | function findLaravelInstaller(): string|false 358 | { 359 | $homeDir = getHomeDirectory(); 360 | $installerCommand = null; 361 | $installerCommands = [ 362 | 'laravel', 363 | "$homeDir/bin/laravel", 364 | "$homeDir/.composer/vendor/bin/laravel", 365 | "$homeDir/.config/composer/vendor/bin/laravel", 366 | ]; 367 | 368 | foreach ($installerCommands as $installerCommand) { 369 | print "Checking for laravel installer: [$installerCommand] -> "; 370 | $haveInstaller = commandExists($installerCommand); 371 | if ($haveInstaller) { 372 | print "OK\n"; 373 | return $installerCommand; 374 | } 375 | print "not found\n"; 376 | } 377 | 378 | print "Reverting to composer\n"; 379 | 380 | return false; 381 | 382 | } 383 | 384 | 385 | function getHomeDirectory(): string 386 | { 387 | return posix_getpwuid(getmyuid())['dir']; 388 | } 389 | 390 | 391 | function patchFilamentConfig(string $color, string $noColor): void 392 | { 393 | $configFile = "config/filament.php"; 394 | print "Patching {$color}$configFile{$noColor} to enable darkMode & collapsibleOnDesktop\n"; 395 | 396 | $fileData = file_get_contents($configFile); 397 | if (!$fileData) { 398 | throw new \Exception("Unable to read [$configFile]"); 399 | } 400 | $fileLines = explode("\n", $fileData); 401 | $output = []; 402 | 403 | foreach ($fileLines as $line) { 404 | if (strpos($line, "'dark_mode' => false")) { 405 | $line = str_replace('false', 'true', $line); 406 | } 407 | if (strpos($line, "'is_collapsible_on_desktop' => false")) { 408 | $line = str_replace('false', 'true', $line); 409 | } 410 | $output[] = $line; 411 | } 412 | 413 | $rc = file_put_contents($configFile, implode("\n", $output)); 414 | if ($rc === false) { 415 | throw new \Exception("Unable to write [$configFile]"); 416 | } 417 | } 418 | 419 | 420 | function patchFilamentComposerJson(string $color, string $noColor): void 421 | { 422 | $composerFile = "composer.json"; 423 | print "Patching {$color}$composerFile{$noColor} to enable automatic upgrade\n"; 424 | 425 | $fileData = file_get_contents($composerFile); 426 | if (!$fileData) { 427 | throw new \Exception("Unable to read [$composerFile]"); 428 | } 429 | $fileLines = explode("\n", $fileData); 430 | $output = []; 431 | $insertNewLineNow = false; 432 | foreach ($fileLines as $line) { 433 | if (strpos($line, "@php artisan vendor:publish --tag=laravel-assets --ansi --force")) { 434 | $insertNewLineNow = true; 435 | $line .= ","; 436 | } 437 | $output[] = $line; 438 | if ($insertNewLineNow) { 439 | $output[] = '"@php artisan filament:upgrade"'; 440 | $insertNewLineNow = false; 441 | } 442 | } 443 | 444 | $rc = file_put_contents($composerFile, implode("\n", $output)); 445 | if ($rc === false) { 446 | throw new \Exception("Unable to write [$composerFile]"); 447 | } 448 | } 449 | 450 | 451 | function patchFilamentTablesConfigFile(string $color, string $noColor): void 452 | { 453 | $configFile = "config/tables.php"; 454 | print "Patching {$color}$configFile{$noColor} to increase table pagination size\n"; 455 | 456 | $fileData = file_get_contents($configFile); 457 | if (!$fileData) { 458 | throw new \Exception("Unable to read [$configFile]"); 459 | } 460 | $fileLines = explode("\n", $fileData); 461 | $output = []; 462 | 463 | foreach ($fileLines as $line) { 464 | if (strpos($line, "'default_records_per_page' => 10")) { 465 | $line = str_replace('10', '25', $line); 466 | } 467 | $output[] = $line; 468 | } 469 | 470 | $rc = file_put_contents($configFile, implode("\n", $output)); 471 | if ($rc === false) { 472 | throw new \Exception("Unable to write [$configFile]"); 473 | } 474 | } 475 | 476 | 477 | function patchUserModelForFilamentShield(string $color, string $noColor): void 478 | { 479 | $userModel = "app/Models/User.php"; 480 | print "Patching {$color}$userModel{$noColor} for use with FilamentShield\n"; 481 | 482 | $fileData = file_get_contents($userModel); 483 | if (!$fileData) { 484 | throw new \Exception("Unable to read [$userModel]"); 485 | } 486 | $fileLines = explode("\n", $fileData); 487 | $output = []; 488 | $firstUse = false; 489 | $firstBrace = false; 490 | foreach ($fileLines as $line) { 491 | $output[] = $line; 492 | if (!$firstUse && strpos($line, 'use ') === 0) { 493 | $output[] = 'use Spatie\\Permission\\Traits\\HasRoles;'; 494 | $firstUse = true; 495 | } 496 | if (!$firstBrace && strpos($line, '{') === 0) { 497 | $output[] = " use HasRoles;"; 498 | $firstBrace = true; 499 | } 500 | } 501 | 502 | $rc = file_put_contents($userModel, implode("\n", $output)); 503 | if ($rc === false) { 504 | throw new \Exception("Unable to write [$userModel]"); 505 | } 506 | } 507 | 508 | 509 | function patchAppServiceProviderForStrictMode(string $color, string $noColor): void 510 | { 511 | $serviceProvider = "app/Providers/AppServiceProvider.php"; 512 | print "Patching {$color}$serviceProvider{$noColor} to enable StrictMode protections & logging\n"; 513 | 514 | $fileData = file_get_contents($serviceProvider); 515 | if (!$fileData) { 516 | throw new \Exception("Unable to read [$serviceProvider]"); 517 | } 518 | $fileLines = explode("\n", $fileData); 519 | $output = []; 520 | $firstUse = false; 521 | 522 | for ($i=0; $igetName()}\"); 557 | }); 558 | 559 | // Log slow Commands (5 seconds) or Requests (1 second) 560 | if (\$this->app->runningInConsole()) { 561 | \$this->app[ConsoleKernel::class]->whenCommandLifecycleIsLongerThan( 562 | 5000, 563 | function (\$startedAt, \$input, \$status) { 564 | Log::warning(\"A command took longer than 5 seconds.\", [ 565 | 'command' => (string)\$input, 566 | 'duration' => \$startedAt->floatDiffInSeconds(), 567 | ]); 568 | } 569 | ); 570 | } else { 571 | \$this->app[HttpKernel::class]->whenRequestLifecycleIsLongerThan( 572 | 1000, 573 | function (\$startedAt, \$request, \$response) { 574 | Log::warning(\"A request took longer than 1 seconds.\", [ 575 | 'user' => \$request->user()?->id, 576 | 'url' => \$request->fullUrl(), 577 | 'duration' => \$startedAt->floatDiffInSeconds(), 578 | ]); 579 | } 580 | ); 581 | }\n"; 582 | } 583 | } 584 | 585 | $rc = file_put_contents($serviceProvider, implode("\n", $output)); 586 | if ($rc === false) { 587 | throw new \Exception("Unable to write [$serviceProvider]"); 588 | } 589 | } 590 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Robert Gasch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # laravel-installer-script 2 | An easily configurable PHP script to install laravel with extra modules and post-install actions. 3 | 4 | 5 | ## What it does 6 | If you do a lot of experimentation with Laravel, this script can save you some time. It allows you 7 | (in the top section of the script), to configure packages you want to install (both prod and dev) 8 | as well as some post-install actions and files to create. It also performs some additional actions 9 | such as publishing package config files, migrations, resources, etc. and patches some files so that 10 | everything is set up correctly. 11 | 12 | 13 | ## Who is it for 14 | It's for Laravel developers who quickly want to install a new instance of Laravel, have some manual 15 | steps taken care of automatically and have a baseline for further experimentation and/or development. 16 | If you know PHP, the install script should be self explanatory, if you don't know PHP, this is probably 17 | not for you. I wrote it for my own purposes during a weekend of experimentation (it's a bit hacky but 18 | does the job). 19 | 20 | 21 | ## Usage 22 | 23 | InstallLaravel.php [--with-filament|--with-jetstream-livewire|--with-jetstream-inertia] [--with-strict-mode] 24 | 25 | 26 | ### Default Install 27 | 28 | Out of the box, it installs the following packages: 29 | 30 | - spatie/laravel-backup 31 | - spatie/laravel-ray, 32 | - spatie/laravel-responsecache 33 | - spatie/laravel-settings 34 | - spatie/laravel-tags 35 | 36 | 37 | ### Strict Option Install 38 | 39 | Regardless of which extra distribution you choose to install (see below), you can supply 40 | the '--with-strict-mode' option which will patch AppServiceProvider to activate the following 41 | safeguards: 42 | 43 | - Model::preventAccessingMissingAttributes() 44 | - Model::preventSilentlyDiscardingAttributes() 45 | - Model::preventLazyLoading() - in production mode this will only log violations 46 | - Logging of queries running longer than 1 second 47 | - Logging of requests running longer than 1 second 48 | - Logging of commands running longer than 5 seconds 49 | 50 | For a more in-detail discussion of these options see 51 | [this](https://planetscale.com/blog/laravels-safety-mechanisms) excellent blog post 52 | by [Aaron Francis](https://twitter.com/aarondfrancis). 53 | 54 | **Warning**: while strict mode is a sensible default for a development environment, enabling 55 | this option may result in errors in some of the addons installed or in any additional 56 | modules you install. If so, it would probably be helpful to log tickets against any 57 | such packages and (temporarily?) disable the strict mode option which causes the 58 | issue but editing app/Providers/AppServiceProvider accordingly. 59 | 60 | 61 | ### Filament Install (--with-filament) 62 | 63 | If you select to install Filament, the following packages will be installed 64 | 65 | - filament/filament 66 | - filament/forms 67 | - filament/notifications 68 | - filament/tables 69 | - filament/spatie-laravel-settings-plugin 70 | - filament/spatie-laravel-tags-plugin 71 | - 3x1io/filament-user 72 | - 3x1io/filament-menus 73 | - bezhansalleh/filament-shield 74 | - filipfonal/filament-log-manager 75 | - ryangjchandler/filament-profile 76 | - ryangjchandler/filament-feature-flags 77 | - spatie/laravel-backup 78 | - spatie/laravel-model-flags 79 | - spatie/laravel-ray 80 | - spatie/laravel-responsecache 81 | - spatie/laravel-settings 82 | - spatie/laravel-tags 83 | 84 | In addition to this it will apply patches to enable the following filament features: 85 | 86 | - Patch the User model to enable usage of filament-shield 87 | - Enable Dark Mode 88 | - Enable Collapsible menu on desktop 89 | - Increase default pagination size to 25 90 | 91 | 92 | ### Jetstream Livewire Install (--with-jetstream|--with-jetstream-livewire) 93 | 94 | If you select to install Jetstream Livewire, the following package will be installed 95 | 96 | - laravel/jetstream 97 | 98 | and the livewire config will be published. 99 | 100 | 101 | ### Jetstream Inertia Install (--with-jetstream-inertia) 102 | 103 | If you select to install Jetstream Livewire, the following package will be installed 104 | 105 | - laravel/jetstream 106 | 107 | and the inertia config will be published. 108 | 109 | 110 | ### Dev Packages 111 | 112 | Regardless of which install option you choose, the following dev packages will be installed 113 | 114 | - laravel/pint 115 | - nunomaduro/larastan, 116 | - pestphp/pest-plugin-laravel 117 | - rector/rector 118 | 119 | Along with these dev packages, the following files will be created: 120 | 121 | - rector.php 122 | - phpstan.neon 123 | 124 | 125 | ## Config files and assets 126 | 127 | If any of the installed packages provide publishable 128 | 129 | ## Requirements 130 | - Written against a PHP8.1 installation, should also work on PHP8.0. 131 | - Requires either the Laravel installer or composer to be installed. 132 | 133 | --------------------------------------------------------------------------------