This commit is contained in:
Phan Hữu Tài 2024-08-19 15:12:27 +07:00
commit 22e8386d31
21 changed files with 1972 additions and 0 deletions

15
.editorconfig Normal file
View File

@ -0,0 +1,15 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[*.{yml,yaml}]
indent_size = 2

19
.gitattributes vendored Normal file
View File

@ -0,0 +1,19 @@
* text=auto
*.blade.php diff=html
*.css diff=css
*.html diff=html
*.md diff=markdown
*.php diff=php
/.github export-ignore
/tests export-ignore
/tests-output export-ignore
.editorconfig export-ignore
.gitattributes export-ignore
.gitignore export-ignore
.styleci.yml export-ignore
CHANGELOG.md export-ignore
phpstan.neon.dist export-ignore
phpunit.xml.dist export-ignore
RELEASE.md export-ignore

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/vendor
composer.lock
phpunit.xml
.phpunit.result.cache

4
.styleci.yml Normal file
View File

@ -0,0 +1,4 @@
php:
preset: laravel
js: true
css: true

424
CHANGELOG.md Normal file
View File

@ -0,0 +1,424 @@
# Release Notes
## [Unreleased](https://github.com/laravel/installer/compare/v5.8.3...master)
## [v5.8.3](https://github.com/laravel/installer/compare/v5.8.2...v5.8.3) - 2024-06-18
* Validate Laravel can be installed on selected directory by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/installer/pull/344
* Automatically run migration when `--database` is given. by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/installer/pull/346
## [v5.8.2](https://github.com/laravel/installer/compare/v5.8.1...v5.8.2) - 2024-06-02
* [5.x] Fixes issue if there is no `herd` or `valet` installed by [@xiCO2k](https://github.com/xiCO2k) in https://github.com/laravel/installer/pull/343
## [v5.8.1](https://github.com/laravel/installer/compare/v5.8.0...v5.8.1) - 2024-05-21
* Only run migration after selecting the database by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/installer/pull/337
* [1.x] Enforce output when running with `--quiet` by [@xiCO2k](https://github.com/xiCO2k) in https://github.com/laravel/installer/pull/339
## [v5.8.0](https://github.com/laravel/installer/compare/v5.7.3...v5.8.0) - 2024-05-07
* Improves output if Herd or Valet are installed. by [@xiCO2k](https://github.com/xiCO2k) in https://github.com/laravel/installer/pull/335
* Gets the default "tld" on install. by [@xiCO2k](https://github.com/xiCO2k) in https://github.com/laravel/installer/pull/336
## [v5.7.3](https://github.com/laravel/installer/compare/v5.7.2...v5.7.3) - 2024-04-30
* Prevent using unavailable databases by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/installer/pull/334
## [v5.7.2](https://github.com/laravel/installer/compare/v5.7.1...v5.7.2) - 2024-04-16
* Install Breeze with dev flag by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/installer/pull/332
## [v5.7.1](https://github.com/laravel/installer/compare/v5.7.0...v5.7.1) - 2024-04-02
* [5.x] Removes "(experimental)" label by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/installer/pull/330
## [v5.7.0](https://github.com/laravel/installer/compare/v5.6.0...v5.7.0) - 2024-03-26
* Remove unused `migrate` parameter by [@nshiro](https://github.com/nshiro) in https://github.com/laravel/installer/pull/323
* Add `--database` option by [@CasEbb](https://github.com/CasEbb) in https://github.com/laravel/installer/pull/320
## [v5.6.0](https://github.com/laravel/installer/compare/v5.5.3...v5.6.0) - 2024-03-12
* Remove Laravel 11 Checks by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/installer/pull/319
## [v5.5.3](https://github.com/laravel/installer/compare/v5.5.2...v5.5.3) - 2024-03-05
* [11.x] Removes `publish config` files by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/installer/pull/317
## [v5.5.2](https://github.com/laravel/installer/compare/v5.5.1...v5.5.2) - 2024-02-20
- Prompt for config publish by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/installer/commit/c3b3c661ffc1323951537b5ff470ec706357fa1f
## [v5.5.1](https://github.com/laravel/installer/compare/v5.5.0...v5.5.1) - 2024-02-12
* [5.x] Fixes Pest installation on Windows by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/installer/pull/314
## [v5.5.0](https://github.com/laravel/installer/compare/v5.4.0...v5.5.0) - 2024-01-30
* [5.x] Improves installation of Pest by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/installer/pull/311
## [v5.4.0](https://github.com/laravel/installer/compare/v5.3.0...v5.4.0) - 2024-01-23
* SQLite by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/installer/pull/304
* [5.x] Implies only the new migrations behaviour on L11 by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/installer/pull/305
* [5.x] Improves ending message by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/installer/pull/307
## [v5.3.0](https://github.com/laravel/installer/compare/v5.2.1...v5.3.0) - 2024-01-16
* [5.x] Laravel v11 support by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/installer/pull/302
## [v5.2.1](https://github.com/laravel/installer/compare/v5.2.0...v5.2.1) - 2024-01-09
* Adjust link to docs by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/installer/pull/299
* [5.x] Added default value on selection question while creating new application by [@bigship-prashant](https://github.com/bigship-prashant) in https://github.com/laravel/installer/pull/300
## [v5.2.0](https://github.com/laravel/installer/compare/v5.1.3...v5.2.0) - 2023-12-05
* Apply using the str_starts_with function by [@peter279k](https://github.com/peter279k) in https://github.com/laravel/installer/pull/289
* Add mariadb installation option by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/installer/pull/292
* [5.x] Removes alias by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/installer/pull/293
## [v5.1.3](https://github.com/laravel/installer/compare/v5.1.2...v5.1.3) - 2023-10-10
- Remove extra DB_DATABASE by [@ConnySjoblom](https://github.com/ConnySjoblom) in https://github.com/laravel/installer/pull/287
- Adjusts new command with new Breeze options by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/installer/pull/288
## [v5.1.2](https://github.com/laravel/installer/compare/v5.1.1...v5.1.2) - 2023-09-26
- Add the Livewire stack by [@mpociot](https://github.com/mpociot) in https://github.com/laravel/installer/pull/285
## [v5.1.1](https://github.com/laravel/installer/compare/v5.1.0...v5.1.1) - 2023-09-12
- Fix Jetstream SSR option by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/installer/pull/281
## [v5.1.0](https://github.com/laravel/installer/compare/v5.0.4...v5.1.0) - 2023-08-29
- Fixes breeze installation with `--ssr` options by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/installer/pull/278
- Adds missing jetstream options by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/installer/pull/279
- Add ability to select default database connection by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/installer/pull/275
- Updated to use the Process constructor to install Pest and use `Composer` helper class by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/installer/pull/274
## [v5.0.4](https://github.com/laravel/installer/compare/v5.0.3...v5.0.4) - 2023-08-22
No major changes.
## [v5.0.3](https://github.com/laravel/installer/compare/v5.0.2...v5.0.3) - 2023-08-15
- Add option to indicate whether Breeze should be scaffolded with TypeScript support by [@weavdale](https://github.com/weavdale) in https://github.com/laravel/installer/pull/271
## [v5.0.2](https://github.com/laravel/installer/compare/v5.0.1...v5.0.2) - 2023-08-08
- Show the directory name at the end of the installer by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/installer/pull/270
## [v5.0.1](https://github.com/laravel/installer/compare/v5.0.0...v5.0.1) - 2023-08-03
- Don't prompt for Git if Github Option is found by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/installer/pull/269
## [v5.0.0](https://github.com/laravel/installer/compare/v4.5.1...v5.0.0) - 2023-08-01
- Update prompts by [@jessarcher](https://github.com/jessarcher) in https://github.com/laravel/installer/pull/267
## [v4.5.1](https://github.com/laravel/installer/compare/v4.5.0...v4.5.1) - 2023-07-25
- Wrap PHP_BINARY in quotes by [@mpociot](https://github.com/mpociot) in https://github.com/laravel/installer/pull/268
## [v4.5.0](https://github.com/laravel/installer/compare/v4.4.3...v4.5.0) - 2023-03-20
- Use Pest v2 by @nunomaduro in https://github.com/laravel/installer/pull/262
## [v4.4.3](https://github.com/laravel/installer/compare/v4.4.2...v4.4.3) - 2023-03-07
- Prevent unknown option with git commands by @zepfietje in https://github.com/laravel/installer/pull/260
## [v4.4.2](https://github.com/laravel/installer/compare/v4.4.1...v4.4.2) - 2023-03-02
- Adds option "dark" to jetstream installation by @MarioPerini in https://github.com/laravel/installer/pull/259
## [v4.4.1](https://github.com/laravel/installer/compare/v4.4.0...v4.4.1) - 2023-02-21
- Add an option to install PHPUnit by @likeadeckofcards in https://github.com/laravel/installer/pull/256
## [v4.4.0](https://github.com/laravel/installer/compare/v4.3.0...v4.4.0) - 2023-02-14
### Added
- Adds Laravel Breeze scaffolding by @nunomaduro in https://github.com/laravel/installer/pull/253
### Fixed
- Fixes installation of Pest in Laravel 10 by @nunomaduro in https://github.com/laravel/installer/pull/254
## [v4.3.0](https://github.com/laravel/installer/compare/v4.2.17...v4.3.0) - 2023-02-07
### Added
- Adds `--pest` flag to use Pest by @nunomaduro in https://github.com/laravel/installer/pull/251
## [v4.2.17](https://github.com/laravel/installer/compare/v4.2.16...v4.2.17) - 2022-09-13
### Changed
- Remove `storage:link` command by @jessarcher in https://github.com/laravel/installer/pull/245
## [v4.2.16](https://github.com/laravel/installer/compare/v4.2.15...v4.2.16) - 2022-08-16
### Changed
- Remove duplicate `npm install` and `npm run build` steps by @jessarcher in https://github.com/laravel/installer/pull/242
## [v4.2.15](https://github.com/laravel/installer/compare/v4.2.14...v4.2.15) - 2022-08-09
### Fixed
- Changes APP_URL replacement with name to lower by @RhysLees in https://github.com/laravel/installer/pull/239
- Prevent unresolvable `APP_URL` by @jessarcher in https://github.com/laravel/installer/pull/240
## [v4.2.14](https://github.com/laravel/installer/compare/v4.2.13...v4.2.14) - 2022-08-02
### Changed
- Simplify push to GitHub by @driesvints in https://github.com/laravel/installer/pull/238
## [v4.2.13](https://github.com/laravel/installer/compare/v4.2.12...v4.2.13) - 2022-07-26
### Changed
- Improved console output by @nunomaduro in https://github.com/laravel/installer/pull/235
## [v4.2.12](https://github.com/laravel/installer/compare/v4.2.11...v4.2.12) - 2022-07-13
### Fixed
- Check directory before deleting in Windows OS by @azizramdan in https://github.com/laravel/installer/pull/233
## [v4.2.11](https://github.com/laravel/installer/compare/v4.2.10...v4.2.11) - 2022-06-28
### Fixed
- Use build command instead of dev command for Vite support by @driesvints in https://github.com/laravel/installer/pull/232
## [v4.2.10 (2022-01-18)](https://github.com/laravel/installer/compare/v4.2.9...v4.2.10)
### Changed
- Symfony v6 support ([#217](https://github.com/laravel/installer/pull/217))
### Fixed
- Maintain current functionality with gh repo create rewrite ([#219](https://github.com/laravel/installer/pull/219))
## [v4.2.9 (2021-10-26)](https://github.com/laravel/installer/compare/v4.2.8...v4.2.9)
### Changed
- Always run NPM install ([#214](https://github.com/laravel/installer/pull/214))
## [v4.2.8 (2021-08-17)](https://github.com/laravel/installer/compare/v4.2.7...v4.2.8)
### Changed
- Respect git global config for a default branch ([#207](https://github.com/laravel/installer/pull/207))
## [v4.2.7 (2021-06-08)](https://github.com/laravel/installer/compare/v4.2.6...v4.2.7)
### Fixed
- Use `isDecorated` for no-ansi detection ([#203](https://github.com/laravel/installer/pull/203))
## [v4.2.6 (2021-06-01)](https://github.com/laravel/installer/compare/v4.2.5...v4.2.6)
### Fixed
- Fix new command failing without `no-ansi` option ([#202](https://github.com/laravel/installer/pull/202))
## [v4.2.5 (2021-04-27)](https://github.com/laravel/installer/compare/v4.2.4...v4.2.5)
### Changed
- Add support for older versions of Git ([#199](https://github.com/laravel/installer/pull/199))
## [v4.2.4 (2021-03-23)](https://github.com/laravel/installer/compare/v4.2.3...v4.2.4)
### Added
- Add branch flag ([#197](https://github.com/laravel/installer/pull/197), [a8d5c2d](https://github.com/laravel/installer/commit/a8d5c2d2ff7df892b567ffea19527b0c4451b750))
### Changed
- Update pushing branch ([#196](https://github.com/laravel/installer/pull/196))
## [v4.2.3 (2021-03-18)](https://github.com/laravel/installer/compare/v4.2.2...v4.2.3)
### Added
- Add organization flag ([#194](https://github.com/laravel/installer/pull/194))
## [v4.2.2 (2021-03-16)](https://github.com/laravel/installer/compare/v4.2.1...v4.2.2)
### Changed
- Revert "Improve Git push" ([#192](https://github.com/laravel/installer/pull/192))
## [v4.2.1 (2021-03-16)](https://github.com/laravel/installer/compare/v4.2.0...v4.2.1)
### Changed
- Improve Git push ([#191](https://github.com/laravel/installer/pull/191))
## [v4.2.0 (2021-03-09)](https://github.com/laravel/installer/compare/v4.1.1...v4.2.0)
### Added
- Git Support ([#185](https://github.com/laravel/installer/pull/185))
## [v4.1.1 (2020-11-17)](https://github.com/laravel/installer/compare/v4.1.0...v4.1.1)
### Changed
- Require name argument ([#178](https://github.com/laravel/installer/pull/178))
## [v4.1.0 (2020-11-03)](https://github.com/laravel/installer/compare/v4.0.7...v4.1.0)
### Added
- PHP 8 Support ([#168](https://github.com/laravel/installer/pull/168))
### Changed
- Use `dev-master` for `dev` version ([9ce64f82](https://github.com/laravel/installer/commit/9ce64f82dcc6d700d91e34b7bcfc32f0b16e2839))
## [v4.0.7 (2020-10-30)](https://github.com/laravel/installer/compare/v4.0.6...v4.0.7)
### Fixed
- Fixed some jetstream prompt issues
## [v4.0.6 (2020-10-30)](https://github.com/laravel/installer/compare/v4.0.5...v4.0.6)
### Added
- Add prompt-jetstream switch ([95c3a00](https://github.com/laravel/installer/commit/95c3a00ee7fc188121ae3e90292f712eae19b26b))
### Changed
- Update `DB_DATABASE` in `.env.example` ([#167](https://github.com/laravel/installer/pull/167))
## [v4.0.5 (2020-09-22)](https://github.com/laravel/installer/compare/v4.0.4...v4.0.5)
### Fixed
- Ensure artisan command is executable ([#153](https://github.com/laravel/installer/pull/153))
- Fix quiet and no-ansi flags ([#156](https://github.com/laravel/installer/pull/156))
## [v4.0.4 (2020-09-15)](https://github.com/laravel/installer/compare/v4.0.3...v4.0.4)
### Fixed
- Close `<fg>` tag ([#149](https://github.com/laravel/installer/pull/149))
- Add warning about `--force` and installing in current directory ([#152](https://github.com/laravel/installer/pull/152))
## [v4.0.3 (2020-09-08)](https://github.com/laravel/installer/compare/v4.0.2...v4.0.3)
### Fixed
- Fix for directories with spaces in current working directory path ([#147](https://github.com/laravel/installer/pull/147))
## [v4.0.2 (2020-09-08)](https://github.com/laravel/installer/compare/v4.0.1...v4.0.2)
### Added
- Add stack and teams options ([#143](https://github.com/laravel/installer/pull/143))
## [v4.0.1 (2020-09-07)](https://github.com/laravel/installer/compare/v4.0.0...v4.0.1)
### Changed
- Require PHP 7.3 ([#132](https://github.com/laravel/installer/pull/132))
### Fixed
- Fix multiple issues when running on Windows ([#133](https://github.com/laravel/installer/pull/133), [#137](https://github.com/laravel/installer/pull/137))
- Only change `.env` file when project name exists ([#140](https://github.com/laravel/installer/pull/140))
## [v4.0.0 (2020-09-03)](https://github.com/laravel/installer/compare/v3.2.0...v4.0.0)
### Changed
- Switch to `composer create-project` ([#124](https://github.com/laravel/installer/pull/124), [562650d](https://github.com/laravel/installer/commit/562650de8b637253b7ae47c3383bdd20e8419d1c), [8ab3502](https://github.com/laravel/installer/commit/8ab3502f1d5561d10cf1767213ec0c008baa145b))
## [v3.2.0 (2020-06-30)](https://github.com/laravel/installer/compare/v3.1.0...v3.2.0)
### Added
- Guzzle 7 support ([144a695](https://github.com/laravel/installer/commit/144a69576bfb0df2bbd5c7ae3f40dd87db64d0ba))
## [v3.1.0 (2020-05-21)](https://github.com/laravel/installer/compare/v3.0.1...v3.1.0)
### Removed
- Drop support for PHP 7.2 ([#118](https://github.com/laravel/installer/pull/118))
## [v3.0.1 (2019-11-26)](https://github.com/laravel/installer/compare/v3.0.0...v3.0.1)
### Fixed
- Fix composer autoloader path ([f3db3f3](https://github.com/laravel/installer/commit/f3db3f306c3c2dbbf4ecce4a5dbefe6c1fd178be))
## [v3.0.0 (2019-11-26)](https://github.com/laravel/installer/compare/v2.3.0...v3.0.0)
### Changed
- Move `laravel` binary to new directory ([c581a78](https://github.com/laravel/installer/commit/c581a784643911b97c3b8a2ec25ac809eadbf9c5))
- Require PHP 7.2 as the new minimum version ([3ab97f2](https://github.com/laravel/installer/commit/3ab97f2e454d9c95833ccdd141d2fdbcdc8e0066))
- Allow Symfony 5 ([513a060](https://github.com/laravel/installer/commit/513a060e9877bc8ab222d7ff4a60bc97131a0a0c))
### Removed
- Remove Symfony 3.x support ([a09d8fe](https://github.com/laravel/installer/commit/a09d8fe2ced9579d4fce445aa1336b0993e3e9d0))
- Remove `zipper.sh` ([78ef1db](https://github.com/laravel/installer/commit/78ef1dbe9ad2fbe5f16a85917748f89bb372599f))
## [v2.3.0 (2019-11-19)](https://github.com/laravel/installer/compare/v2.2.1...v2.3.0)
### Added
- Add `--auth` flag ([f5ebbff](https://github.com/laravel/installer/commit/f5ebbff32f9ff9c40fdf4c200cb2f396050e3cf3))
## [v2.2.1 (2019-10-29)](https://github.com/laravel/installer/compare/v2.2.0...v2.2.1)
### Fixed
- Make sure zip file is valid before extracting ([#100](https://github.com/laravel/installer/pull/100))
## [v2.2.0 (2019-10-15)](https://github.com/laravel/installer/compare/v2.1.0...v2.2.0)
### Added
- Create a new project in the current directory using "laravel new ." ([#99](https://github.com/laravel/installer/pull/99))
## [v2.1.0 (2019-04-30)](https://github.com/laravel/installer/compare/v2.0.1...v2.1.0)
### Added
- Added an alias to the `--force` option ([#79](https://github.com/laravel/installer/pull/79))
### Changed
- Use the `extension_loaded` method to check if the 'zip' extension is loaded ([#81](https://github.com/laravel/installer/pull/81))
### Fixed
- Respect `--quiet` option ([#77](https://github.com/laravel/installer/pull/77))
- Update composer path on `findComposer` ([#86](https://github.com/laravel/installer/pull/86))
## [v2.0.1 (2018-02-01)](https://github.com/laravel/installer/compare/v2.0.0...v2.0.1)
### Changed
- Update dependencies ([6e34188](https://github.com/laravel/installer/commit/6e341883b9ba45be6a06f40c8e2c1b5033029d99))
## [v2.0.0 (2018-02-01)](https://github.com/laravel/installer/compare/v1.5.0...v2.0.0)
### Changed
- Bump guzzle requirement ([f909b98](https://github.com/laravel/installer/commit/f909b983e1b57f13b5b102f4c0c0fc1883fcbe22))

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Taylor Otwell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

26
README.md Normal file
View File

@ -0,0 +1,26 @@
# Laravel Installer
<a href="https://github.com/laravel/installer/actions"><img src="https://github.com/laravel/installer/workflows/tests/badge.svg" alt="Build Status"></a>
<a href="https://packagist.org/packages/laravel/installer"><img src="https://img.shields.io/packagist/dt/laravel/installer" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/laravel/installer"><img src="https://img.shields.io/packagist/v/laravel/installer" alt="Latest Stable Version"></a>
<a href="https://packagist.org/packages/laravel/installer"><img src="https://img.shields.io/packagist/l/laravel/installer" alt="License"></a>
## Official Documentation
Documentation for installing Laravel can be found on the [Laravel website](https://laravel.com/docs#creating-a-laravel-project).
## Contributing
Thank you for considering contributing to the Installer! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
Please review [our security policy](https://github.com/laravel/installer/security/policy) on how to report security vulnerabilities.
## License
Laravel Installer is open-sourced software licensed under the [MIT license](LICENSE.md).

4
RELEASE.md Normal file
View File

@ -0,0 +1,4 @@
# Release Instructions
1. Update the version in [`bin/laravel`](./bin/laravel) and commit it
2. [Create a new GitHub release](https://github.com/laravel/installer/releases/new) for this version with the release notes

13
bin/laravel Normal file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env php
<?php
if (file_exists(__DIR__.'/../../../autoload.php')) {
require __DIR__.'/../../../autoload.php';
} else {
require __DIR__.'/../vendor/autoload.php';
}
$app = new Symfony\Component\Console\Application('Laravel Installer', '5.8.3');
$app->add(new Laravel\Installer\Console\NewCommand);
$app->run();

42
composer.json Normal file
View File

@ -0,0 +1,42 @@
{
"name": "laravel/installer",
"description": "Laravel application installer.",
"keywords": ["laravel"],
"license": "MIT",
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"require": {
"php": "^8.2",
"illuminate/filesystem": "^10.20|^11.0",
"illuminate/support": "^10.20|^11.0",
"laravel/prompts": "^0.1",
"symfony/console": "^6.2|^7.0",
"symfony/process": "^6.2|^7.0"
},
"require-dev": {
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.4"
},
"bin": [
"bin/laravel"
],
"autoload": {
"psr-4": {
"Laravel\\Installer\\Console\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Laravel\\Installer\\Console\\Tests\\": "tests/"
}
},
"config": {
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true
}

6
phpstan.neon.dist Normal file
View File

@ -0,0 +1,6 @@
parameters:
paths:
- bin
- src
level: 0

9
phpunit.xml.dist Normal file
View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true">
<testsuites>
<testsuite name="Laravel Installer Test Suite">
<directory suffix="Test.php">./tests</directory>
<exclude>./tests/scaffolds</exclude>
</testsuite>
</testsuites>
</phpunit>

View File

@ -0,0 +1,132 @@
<?php
namespace Laravel\Installer\Console\Concerns;
use Laravel\Prompts\ConfirmPrompt;
use Laravel\Prompts\MultiSelectPrompt;
use Laravel\Prompts\PasswordPrompt;
use Laravel\Prompts\Prompt;
use Laravel\Prompts\SelectPrompt;
use Laravel\Prompts\SuggestPrompt;
use Laravel\Prompts\TextPrompt;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
trait ConfiguresPrompts
{
/**
* Configure the prompt fallbacks.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return void
*/
protected function configurePrompts(InputInterface $input, OutputInterface $output)
{
Prompt::fallbackWhen(! $input->isInteractive() || PHP_OS_FAMILY === 'Windows');
TextPrompt::fallbackUsing(fn (TextPrompt $prompt) => $this->promptUntilValid(
fn () => (new SymfonyStyle($input, $output))->ask($prompt->label, $prompt->default ?: null) ?? '',
$prompt->required,
$prompt->validate,
$output
));
PasswordPrompt::fallbackUsing(fn (PasswordPrompt $prompt) => $this->promptUntilValid(
fn () => (new SymfonyStyle($input, $output))->askHidden($prompt->label) ?? '',
$prompt->required,
$prompt->validate,
$output
));
ConfirmPrompt::fallbackUsing(fn (ConfirmPrompt $prompt) => $this->promptUntilValid(
fn () => (new SymfonyStyle($input, $output))->confirm($prompt->label, $prompt->default),
$prompt->required,
$prompt->validate,
$output
));
SelectPrompt::fallbackUsing(fn (SelectPrompt $prompt) => $this->promptUntilValid(
fn () => (new SymfonyStyle($input, $output))->choice($prompt->label, $prompt->options, $prompt->default),
false,
$prompt->validate,
$output
));
MultiSelectPrompt::fallbackUsing(function (MultiSelectPrompt $prompt) use ($input, $output) {
if ($prompt->default !== []) {
return $this->promptUntilValid(
fn () => (new SymfonyStyle($input, $output))->choice($prompt->label, $prompt->options, implode(',', $prompt->default), true),
$prompt->required,
$prompt->validate,
$output
);
}
return $this->promptUntilValid(
fn () => collect((new SymfonyStyle($input, $output))->choice(
$prompt->label,
array_is_list($prompt->options)
? ['None', ...$prompt->options]
: ['none' => 'None', ...$prompt->options],
'None',
true)
)->reject(array_is_list($prompt->options) ? 'None' : 'none')->all(),
$prompt->required,
$prompt->validate,
$output
);
});
SuggestPrompt::fallbackUsing(fn (SuggestPrompt $prompt) => $this->promptUntilValid(
function () use ($prompt, $input, $output) {
$question = new Question($prompt->label, $prompt->default);
is_callable($prompt->options)
? $question->setAutocompleterCallback($prompt->options)
: $question->setAutocompleterValues($prompt->options);
return (new SymfonyStyle($input, $output))->askQuestion($question);
},
$prompt->required,
$prompt->validate,
$output
));
}
/**
* Prompt the user until the given validation callback passes.
*
* @param \Closure $prompt
* @param bool|string $required
* @param \Closure|null $validate
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return mixed
*/
protected function promptUntilValid($prompt, $required, $validate, $output)
{
while (true) {
$result = $prompt();
if ($required && ($result === '' || $result === [] || $result === false)) {
$output->writeln('<error>'.(is_string($required) ? $required : 'Required.').'</error>');
continue;
}
if ($validate) {
$error = $validate($result);
if (is_string($error) && strlen($error) > 0) {
$output->writeln("<error>{$error}</error>");
continue;
}
}
return $result;
}
}
}

986
src/NewCommand.php Normal file
View File

@ -0,0 +1,986 @@
<?php
namespace Laravel\Installer\Console;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\Composer;
use Illuminate\Support\ProcessUtils;
use RuntimeException;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessStartFailedException;
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;
use function Laravel\Prompts\text;
class NewCommand extends Command
{
use Concerns\ConfiguresPrompts;
/**
* The Composer instance.
*
* @var \Illuminate\Support\Composer
*/
protected $composer;
/**
* Configure the command options.
*
* @return void
*/
protected function configure()
{
$this
->setName('new')
->setDescription('Create a new Laravel application')
->addArgument('name', InputArgument::REQUIRED)
->addOption('dev', null, InputOption::VALUE_NONE, 'Installs the latest "development" release')
->addOption('git', null, InputOption::VALUE_NONE, 'Initialize a Git repository')
->addOption('branch', null, InputOption::VALUE_REQUIRED, 'The branch that should be created for a new repository', $this->defaultBranch())
->addOption('github', null, InputOption::VALUE_OPTIONAL, 'Create a new repository on GitHub', false)
->addOption('organization', null, InputOption::VALUE_REQUIRED, 'The GitHub organization to create the new repository for')
->addOption('database', null, InputOption::VALUE_REQUIRED, 'The database driver your application will use')
->addOption('stack', null, InputOption::VALUE_OPTIONAL, 'The Breeze / Jetstream stack that should be installed')
->addOption('breeze', null, InputOption::VALUE_NONE, 'Installs the Laravel Breeze scaffolding')
->addOption('jet', null, InputOption::VALUE_NONE, 'Installs the Laravel Jetstream scaffolding')
->addOption('dark', null, InputOption::VALUE_NONE, 'Indicate whether Breeze or Jetstream should be scaffolded with dark mode support')
->addOption('typescript', null, InputOption::VALUE_NONE, 'Indicate whether Breeze should be scaffolded with TypeScript support')
->addOption('ssr', null, InputOption::VALUE_NONE, 'Indicate whether Breeze or Jetstream should be scaffolded with Inertia SSR support')
->addOption('api', null, InputOption::VALUE_NONE, 'Indicates whether Jetstream should be scaffolded with API support')
->addOption('teams', null, InputOption::VALUE_NONE, 'Indicates whether Jetstream should be scaffolded with team support')
->addOption('verification', null, InputOption::VALUE_NONE, 'Indicates whether Jetstream should be scaffolded with email verification support')
->addOption('pest', null, InputOption::VALUE_NONE, 'Installs the Pest testing framework')
->addOption('phpunit', null, InputOption::VALUE_NONE, 'Installs the PHPUnit testing framework')
->addOption('prompt-breeze', null, InputOption::VALUE_NONE, 'Issues a prompt to determine if Breeze should be installed (Deprecated)')
->addOption('prompt-jetstream', null, InputOption::VALUE_NONE, 'Issues a prompt to determine if Jetstream should be installed (Deprecated)')
->addOption('force', 'f', InputOption::VALUE_NONE, 'Forces install even if the directory already exists');
}
/**
* Interact with the user before validating the input.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return void
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
parent::interact($input, $output);
$this->configurePrompts($input, $output);
$output->write(PHP_EOL.' <fg=red> _ _
| | | |
| | __ _ _ __ __ ___ _____| |
| | / _` | \'__/ _` \ \ / / _ \ |
| |___| (_| | | | (_| |\ V / __/ |
|______\__,_|_| \__,_| \_/ \___|_|</>'.PHP_EOL.PHP_EOL);
if (! $input->getArgument('name')) {
$input->setArgument('name', text(
label: 'What is the name of your project?',
placeholder: 'E.g. example-app',
required: 'The project name is required.',
validate: function ($value) use ($input) {
if (preg_match('/[^\pL\pN\-_.]/', $value) !== 0) {
return 'The name may only contain letters, numbers, dashes, underscores, and periods.';
}
if ($input->getOption('force') !== true) {
try {
$this->verifyApplicationDoesntExist($this->getInstallationDirectory($value));
} catch (RuntimeException $e) {
return 'Application already exists.';
}
}
},
));
}
if ($input->getOption('force') !== true) {
$this->verifyApplicationDoesntExist(
$this->getInstallationDirectory($input->getArgument('name'))
);
}
if (! $input->getOption('breeze') && ! $input->getOption('jet')) {
match (select(
label: 'Would you like to install a starter kit?',
options: [
'none' => 'No starter kit',
'breeze' => 'Laravel Breeze',
'jetstream' => 'Laravel Jetstream',
],
default: 'none',
)) {
'breeze' => $input->setOption('breeze', true),
'jetstream' => $input->setOption('jet', true),
default => null,
};
}
if ($input->getOption('breeze')) {
$this->promptForBreezeOptions($input);
} elseif ($input->getOption('jet')) {
$this->promptForJetstreamOptions($input);
}
if (! $input->getOption('phpunit') && ! $input->getOption('pest')) {
$input->setOption('pest', select(
label: 'Which testing framework do you prefer?',
options: ['Pest', 'PHPUnit'],
default: 'Pest',
) === 'Pest');
}
if (! $input->getOption('git') && $input->getOption('github') === false && Process::fromShellCommandline('git --version')->run() === 0) {
$input->setOption('git', confirm(label: 'Would you like to initialize a Git repository?', default: false));
}
}
/**
* Execute the command.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$this->validateDatabaseOption($input);
$this->validateStackOption($input);
$name = $input->getArgument('name');
$directory = $this->getInstallationDirectory($name);
$this->composer = new Composer(new Filesystem(), $directory);
$version = $this->getVersion($input);
if (! $input->getOption('force')) {
$this->verifyApplicationDoesntExist($directory);
}
if ($input->getOption('force') && $directory === '.') {
throw new RuntimeException('Cannot use --force option when using current directory for installation!');
}
$composer = $this->findComposer();
$phpBinary = $this->phpBinary();
$commands = [
$composer." create-project laravel/laravel \"$directory\" $version --remove-vcs --prefer-dist --no-scripts",
$composer." run post-root-package-install -d \"$directory\"",
$phpBinary." \"$directory/artisan\" key:generate --ansi",
];
if ($directory != '.' && $input->getOption('force')) {
if (PHP_OS_FAMILY == 'Windows') {
array_unshift($commands, "(if exist \"$directory\" rd /s /q \"$directory\")");
} else {
array_unshift($commands, "rm -rf \"$directory\"");
}
}
if (PHP_OS_FAMILY != 'Windows') {
$commands[] = "chmod 755 \"$directory/artisan\"";
}
if (($process = $this->runCommands($commands, $input, $output))->isSuccessful()) {
if ($name !== '.') {
$this->replaceInFile(
'APP_URL=http://localhost',
'APP_URL='.$this->generateAppUrl($name),
$directory.'/.env'
);
[$database, $migrate] = $this->promptForDatabaseOptions($directory, $input);
$this->configureDefaultDatabaseConnection($directory, $database, $name);
if ($migrate) {
$this->runCommands([
$this->phpBinary().' artisan migrate',
], $input, $output, workingPath: $directory);
}
}
if ($input->getOption('git') || $input->getOption('github') !== false) {
$this->createRepository($directory, $input, $output);
}
if ($input->getOption('breeze')) {
$this->installBreeze($directory, $input, $output);
} elseif ($input->getOption('jet')) {
$this->installJetstream($directory, $input, $output);
} elseif ($input->getOption('pest')) {
$this->installPest($directory, $input, $output);
}
if ($input->getOption('github') !== false) {
$this->pushToGitHub($name, $directory, $input, $output);
$output->writeln('');
}
$output->writeln(" <bg=blue;fg=white> INFO </> Application ready in <options=bold>[{$name}]</>. You can start your local development using:".PHP_EOL);
$output->writeln('<fg=gray>➜</> <options=bold>cd '.$name.'</>');
if ($this->isParked($directory)) {
$url = $this->generateAppUrl($name);
$output->writeln('<fg=gray>➜</> Open: <options=bold;href='.$url.'>'.$url.'</>');
} else {
$output->writeln('<fg=gray>➜</> <options=bold>php artisan serve</>');
}
$output->writeln('');
$output->writeln(' New to Laravel? Check out our <href=https://bootcamp.laravel.com>bootcamp</> and <href=https://laravel.com/docs/installation#next-steps>documentation</>. <options=bold>Build something amazing!</>');
$output->writeln('');
}
return $process->getExitCode();
}
/**
* Return the local machine's default Git branch if set or default to `main`.
*
* @return string
*/
protected function defaultBranch()
{
$process = new Process(['git', 'config', '--global', 'init.defaultBranch']);
$process->run();
$output = trim($process->getOutput());
return $process->isSuccessful() && $output ? $output : 'main';
}
/**
* Configure the default database connection.
*
* @param string $directory
* @param string $database
* @param string $name
* @return void
*/
protected function configureDefaultDatabaseConnection(string $directory, string $database, string $name)
{
$this->pregReplaceInFile(
'/DB_CONNECTION=.*/',
'DB_CONNECTION='.$database,
$directory.'/.env'
);
$this->pregReplaceInFile(
'/DB_CONNECTION=.*/',
'DB_CONNECTION='.$database,
$directory.'/.env.example'
);
if ($database === 'sqlite') {
$environment = file_get_contents($directory.'/.env');
// If database options aren't commented, comment them for SQLite...
if (! str_contains($environment, '# DB_HOST=127.0.0.1')) {
$this->commentDatabaseConfigurationForSqlite($directory);
return;
}
return;
}
// Any commented database configuration options should be uncommented when not on SQLite...
$this->uncommentDatabaseConfiguration($directory);
$defaultPorts = [
'pgsql' => '5432',
'sqlsrv' => '1433',
];
if (isset($defaultPorts[$database])) {
$this->replaceInFile(
'DB_PORT=3306',
'DB_PORT='.$defaultPorts[$database],
$directory.'/.env'
);
$this->replaceInFile(
'DB_PORT=3306',
'DB_PORT='.$defaultPorts[$database],
$directory.'/.env.example'
);
}
$this->replaceInFile(
'DB_DATABASE=laravel',
'DB_DATABASE='.str_replace('-', '_', strtolower($name)),
$directory.'/.env'
);
$this->replaceInFile(
'DB_DATABASE=laravel',
'DB_DATABASE='.str_replace('-', '_', strtolower($name)),
$directory.'/.env.example'
);
}
/**
* Determine if the application is using Laravel 11 or newer.
*
* @param string $directory
* @return bool
*/
public function usingLaravelVersionOrNewer(int $usingVersion, string $directory): bool
{
$version = json_decode(file_get_contents($directory.'/composer.json'), true)['require']['laravel/framework'];
$version = str_replace('^', '', $version);
$version = explode('.', $version)[0];
return $version >= $usingVersion;
}
/**
* Comment the irrelevant database configuration entries for SQLite applications.
*
* @param string $directory
* @return void
*/
protected function commentDatabaseConfigurationForSqlite(string $directory): void
{
$defaults = [
'DB_HOST=127.0.0.1',
'DB_PORT=3306',
'DB_DATABASE=laravel',
'DB_USERNAME=root',
'DB_PASSWORD=',
];
$this->replaceInFile(
$defaults,
collect($defaults)->map(fn ($default) => "# {$default}")->all(),
$directory.'/.env'
);
$this->replaceInFile(
$defaults,
collect($defaults)->map(fn ($default) => "# {$default}")->all(),
$directory.'/.env.example'
);
}
/**
* Uncomment the relevant database configuration entries for non SQLite applications.
*
* @param string $directory
* @return void
*/
protected function uncommentDatabaseConfiguration(string $directory)
{
$defaults = [
'# DB_HOST=127.0.0.1',
'# DB_PORT=3306',
'# DB_DATABASE=laravel',
'# DB_USERNAME=root',
'# DB_PASSWORD=',
];
$this->replaceInFile(
$defaults,
collect($defaults)->map(fn ($default) => substr($default, 2))->all(),
$directory.'/.env'
);
$this->replaceInFile(
$defaults,
collect($defaults)->map(fn ($default) => substr($default, 2))->all(),
$directory.'/.env.example'
);
}
/**
* Install Laravel Breeze into the application.
*
* @param string $directory
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return void
*/
protected function installBreeze(string $directory, InputInterface $input, OutputInterface $output)
{
$commands = array_filter([
$this->findComposer().' require laravel/breeze --dev',
trim(sprintf(
$this->phpBinary().' artisan breeze:install %s %s %s %s %s',
$input->getOption('stack'),
$input->getOption('typescript') ? '--typescript' : '',
$input->getOption('pest') ? '--pest' : '',
$input->getOption('dark') ? '--dark' : '',
$input->getOption('ssr') ? '--ssr' : '',
)),
]);
$this->runCommands($commands, $input, $output, workingPath: $directory);
$this->commitChanges('Install Breeze', $directory, $input, $output);
}
/**
* Install Laravel Jetstream into the application.
*
* @param string $directory
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return void
*/
protected function installJetstream(string $directory, InputInterface $input, OutputInterface $output)
{
$commands = array_filter([
$this->findComposer().' require laravel/jetstream',
trim(sprintf(
$this->phpBinary().' artisan jetstream:install %s %s %s %s %s %s %s',
$input->getOption('stack'),
$input->getOption('api') ? '--api' : '',
$input->getOption('dark') ? '--dark' : '',
$input->getOption('teams') ? '--teams' : '',
$input->getOption('pest') ? '--pest' : '',
$input->getOption('verification') ? '--verification' : '',
$input->getOption('ssr') ? '--ssr' : '',
)),
]);
$this->runCommands($commands, $input, $output, workingPath: $directory);
$this->commitChanges('Install Jetstream', $directory, $input, $output);
}
/**
* Determine the default database connection.
*
* @param string $directory
* @param \Symfony\Component\Console\Input\InputInterface $input
* @return array
*/
protected function promptForDatabaseOptions(string $directory, InputInterface $input)
{
$defaultDatabase = collect(
$databaseOptions = $this->databaseOptions()
)->keys()->first();
if (! $input->getOption('database') && $input->isInteractive()) {
$input->setOption('database', select(
label: 'Which database will your application use?',
options: $databaseOptions,
default: $defaultDatabase,
));
$migrate = confirm(
label: $input->getOption('database') !== 'sqlite'
? 'Default database updated. Would you like to run the default database migrations?'
: 'Would you like to run the default database migrations?',
default: true
);
}
return [$input->getOption('database') ?? $defaultDatabase, $migrate ?? $input->hasOption('database')];
}
/**
* Get the available database options.
*
* @return array
*/
protected function databaseOptions(): array
{
return collect([
'sqlite' => ['SQLite', extension_loaded('pdo_sqlite')],
'mysql' => ['MySQL', extension_loaded('pdo_mysql')],
'mariadb' => ['MariaDB', extension_loaded('pdo_mysql')],
'pgsql' => ['PostgreSQL', extension_loaded('pdo_pgsql')],
'sqlsrv' => ['SQL Server', extension_loaded('pdo_sqlsrv')],
])
->sortBy(fn ($database) => $database[1] ? 0 : 1)
->map(fn ($database) => $database[0].($database[1] ? '' : ' (Missing PDO extension)'))
->all();
}
/**
* Determine the stack for Breeze.
*
* @return void
*/
protected function promptForBreezeOptions(InputInterface $input)
{
if (! $input->getOption('stack')) {
$input->setOption('stack', 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',
],
default: 'blade',
));
}
if (in_array($input->getOption('stack'), ['react', 'vue']) && (! $input->getOption('dark') || ! $input->getOption('ssr'))) {
collect(multiselect(
label: 'Would you like any optional features?',
options: [
'dark' => 'Dark mode',
'ssr' => 'Inertia SSR',
'typescript' => 'TypeScript',
],
default: array_filter([
$input->getOption('dark') ? 'dark' : null,
$input->getOption('ssr') ? 'ssr' : null,
$input->getOption('typescript') ? 'typescript' : null,
]),
))->each(fn ($option) => $input->setOption($option, true));
} elseif (in_array($input->getOption('stack'), ['blade', 'livewire', 'livewire-functional']) && ! $input->getOption('dark')) {
$input->setOption('dark', confirm(
label: 'Would you like dark mode support?',
default: false,
));
}
}
/**
* Determine the stack for Jetstream.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @return void
*/
protected function promptForJetstreamOptions(InputInterface $input)
{
if (! $input->getOption('stack')) {
$input->setOption('stack', select(
label: 'Which Jetstream stack would you like to install?',
options: [
'livewire' => 'Livewire',
'inertia' => 'Vue with Inertia',
],
default: 'livewire',
));
}
collect(multiselect(
label: 'Would you like any optional features?',
options: collect([
'api' => 'API support',
'dark' => 'Dark mode',
'verification' => 'Email verification',
'teams' => 'Team support',
])->when(
$input->getOption('stack') === 'inertia',
fn ($options) => $options->put('ssr', 'Inertia SSR')
)->all(),
default: array_filter([
$input->getOption('api') ? 'api' : null,
$input->getOption('dark') ? 'dark' : null,
$input->getOption('teams') ? 'teams' : null,
$input->getOption('verification') ? 'verification' : null,
$input->getOption('stack') === 'inertia' && $input->getOption('ssr') ? 'ssr' : null,
]),
))->each(fn ($option) => $input->setOption($option, true));
}
/**
* Validate the database driver input.
*
* @param \Symfony\Components\Console\Input\InputInterface
*/
protected function validateDatabaseOption(InputInterface $input)
{
if ($input->getOption('database') && ! in_array($input->getOption('database'), $drivers = ['mysql', 'mariadb', 'pgsql', 'sqlite', 'sqlsrv'])) {
throw new \InvalidArgumentException("Invalid database driver [{$input->getOption('database')}]. Valid options are: ".implode(', ', $drivers).'.');
}
}
/**
* Validate the starter kit stack input.
*
* @param \Symfony\Components\Console\Input\InputInterface
*/
protected function validateStackOption(InputInterface $input)
{
if ($input->getOption('breeze')) {
if (! in_array($input->getOption('stack'), $stacks = ['blade', 'livewire', 'livewire-functional', 'react', 'vue', 'api'])) {
throw new \InvalidArgumentException("Invalid Breeze stack [{$input->getOption('stack')}]. Valid options are: ".implode(', ', $stacks).'.');
}
return;
}
if ($input->getOption('jet')) {
if (! in_array($input->getOption('stack'), $stacks = ['inertia', 'livewire'])) {
throw new \InvalidArgumentException("Invalid Jetstream stack [{$input->getOption('stack')}]. Valid options are: ".implode(', ', $stacks).'.');
}
return;
}
}
/**
* Install Pest into the application.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return void
*/
protected function installPest(string $directory, InputInterface $input, OutputInterface $output)
{
$composerBinary = $this->findComposer();
$commands = [
$composerBinary.' remove phpunit/phpunit --dev --no-update',
$composerBinary.' require pestphp/pest pestphp/pest-plugin-laravel --no-update --dev',
$composerBinary.' update',
$this->phpBinary().' ./vendor/bin/pest --init',
];
$this->runCommands($commands, $input, $output, workingPath: $directory, env: [
'PEST_NO_SUPPORT' => 'true',
]);
$this->replaceFile(
'pest/Feature.php',
$directory.'/tests/Feature/ExampleTest.php',
);
$this->replaceFile(
'pest/Unit.php',
$directory.'/tests/Unit/ExampleTest.php',
);
$this->commitChanges('Install Pest', $directory, $input, $output);
}
/**
* Create a Git repository and commit the base Laravel skeleton.
*
* @param string $directory
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return void
*/
protected function createRepository(string $directory, InputInterface $input, OutputInterface $output)
{
$branch = $input->getOption('branch') ?: $this->defaultBranch();
$commands = [
'git init -q',
'git add .',
'git commit -q -m "Set up a fresh Laravel app"',
"git branch -M {$branch}",
];
$this->runCommands($commands, $input, $output, workingPath: $directory);
}
/**
* Commit any changes in the current working directory.
*
* @param string $message
* @param string $directory
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return void
*/
protected function commitChanges(string $message, string $directory, InputInterface $input, OutputInterface $output)
{
if (! $input->getOption('git') && $input->getOption('github') === false) {
return;
}
$commands = [
'git add .',
"git commit -q -m \"$message\"",
];
$this->runCommands($commands, $input, $output, workingPath: $directory);
}
/**
* Create a GitHub repository and push the git log to it.
*
* @param string $name
* @param string $directory
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @return void
*/
protected function pushToGitHub(string $name, string $directory, InputInterface $input, OutputInterface $output)
{
$process = new Process(['gh', 'auth', 'status']);
$process->run();
if (! $process->isSuccessful()) {
$output->writeln(' <bg=yellow;fg=black> WARN </> Make sure the "gh" CLI tool is installed and that you\'re authenticated to GitHub. Skipping...'.PHP_EOL);
return;
}
$name = $input->getOption('organization') ? $input->getOption('organization')."/$name" : $name;
$flags = $input->getOption('github') ?: '--private';
$commands = [
"gh repo create {$name} --source=. --push {$flags}",
];
$this->runCommands($commands, $input, $output, workingPath: $directory, env: ['GIT_TERMINAL_PROMPT' => 0]);
}
/**
* Verify that the application does not already exist.
*
* @param string $directory
* @return void
*/
protected function verifyApplicationDoesntExist($directory)
{
if ((is_dir($directory) || is_file($directory)) && $directory != getcwd()) {
throw new RuntimeException('Application already exists!');
}
}
/**
* Generate a valid APP_URL for the given application name.
*
* @param string $name
* @return string
*/
protected function generateAppUrl($name)
{
$hostname = mb_strtolower($name).'.'.$this->getTld();
return $this->canResolveHostname($hostname) ? 'http://'.$hostname : 'http://localhost';
}
/**
* Get the TLD for the application.
*
* @return string
*/
protected function getTld()
{
return $this->runOnValetOrHerd('tld') ?? 'test';
}
/**
* Determine whether the given hostname is resolvable.
*
* @param string $hostname
* @return bool
*/
protected function canResolveHostname($hostname)
{
return gethostbyname($hostname.'.') !== $hostname.'.';
}
/**
* Determine if the given directory is parked using Herd or Valet.
*
* @param string $directory
* @return bool
*/
protected function isParked(string $directory)
{
$output = $this->runOnValetOrHerd('paths');
return $output !== false ? in_array(dirname($directory), json_decode($output)) : false;
}
/**
* Get the installation directory.
*
* @param string $name
* @return string
*/
protected function getInstallationDirectory(string $name)
{
return $name !== '.' ? getcwd().'/'.$name : '.';
}
/**
* Get the version that should be downloaded.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* @return string
*/
protected function getVersion(InputInterface $input)
{
if ($input->getOption('dev')) {
return 'dev-master';
}
return '';
}
/**
* Get the composer command for the environment.
*
* @return string
*/
protected function findComposer()
{
return implode(' ', $this->composer->findComposer());
}
/**
* Get the path to the appropriate PHP binary.
*
* @return string
*/
protected function phpBinary()
{
$phpBinary = (new PhpExecutableFinder)->find(false);
return $phpBinary !== false
? ProcessUtils::escapeArgument($phpBinary)
: 'php';
}
/**
* Runs the given command on the "herd" or "valet" CLI.
*
* @param string $command
* @return string|bool
*/
protected function runOnValetOrHerd(string $command)
{
foreach (['herd', 'valet'] as $tool) {
$process = new Process([$tool, $command, '-v']);
try {
$process->run();
if ($process->isSuccessful()) {
return trim($process->getOutput());
}
} catch (ProcessStartFailedException) {
}
}
return false;
}
/**
* Run the given commands.
*
* @param array $commands
* @param \Symfony\Component\Console\Input\InputInterface $input
* @param \Symfony\Component\Console\Output\OutputInterface $output
* @param string|null $workingPath
* @param array $env
* @return \Symfony\Component\Process\Process
*/
protected function runCommands($commands, InputInterface $input, OutputInterface $output, string $workingPath = null, array $env = [])
{
if (! $output->isDecorated()) {
$commands = array_map(function ($value) {
if (str_starts_with($value, 'chmod')) {
return $value;
}
if (str_starts_with($value, 'git')) {
return $value;
}
return $value.' --no-ansi';
}, $commands);
}
if ($input->getOption('quiet')) {
$commands = array_map(function ($value) {
if (str_starts_with($value, 'chmod')) {
return $value;
}
if (str_starts_with($value, 'git')) {
return $value;
}
return $value.' --quiet';
}, $commands);
}
$process = Process::fromShellCommandline(implode(' && ', $commands), $workingPath, $env, null, null);
if ('\\' !== DIRECTORY_SEPARATOR && file_exists('/dev/tty') && is_readable('/dev/tty')) {
try {
$process->setTty(true);
} catch (RuntimeException $e) {
$output->writeln(' <bg=yellow;fg=black> WARN </> '.$e->getMessage().PHP_EOL);
}
}
$process->run(function ($type, $line) use ($output) {
$output->write(' '.$line);
});
return $process;
}
/**
* Replace the given file.
*
* @param string $replace
* @param string $file
* @return void
*/
protected function replaceFile(string $replace, string $file)
{
$stubs = dirname(__DIR__).'/stubs';
file_put_contents(
$file,
file_get_contents("$stubs/$replace"),
);
}
/**
* Replace the given string in the given file.
*
* @param string|array $search
* @param string|array $replace
* @param string $file
* @return void
*/
protected function replaceInFile(string|array $search, string|array $replace, string $file)
{
file_put_contents(
$file,
str_replace($search, $replace, file_get_contents($file))
);
}
/**
* Replace the given string in the given file using regular expressions.
*
* @param string|array $search
* @param string|array $replace
* @param string $file
* @return void
*/
protected function pregReplaceInFile(string $pattern, string $replace, string $file)
{
file_put_contents(
$file,
preg_replace($pattern, $replace, file_get_contents($file))
);
}
}

7
stubs/pest/Feature.php Normal file
View File

@ -0,0 +1,7 @@
<?php
it('returns a successful response', function () {
$response = $this->get('/');
$response->assertStatus(200);
});

5
stubs/pest/Unit.php Normal file
View File

@ -0,0 +1,5 @@
<?php
test('that true is true', function () {
expect(true)->toBeTrue();
});

2
tests-output/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

49
tests/NewCommandTest.php Normal file
View File

@ -0,0 +1,49 @@
<?php
namespace Laravel\Installer\Console\Tests;
use Laravel\Installer\Console\NewCommand;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
class NewCommandTest extends TestCase
{
public function test_it_can_scaffold_a_new_laravel_app()
{
$scaffoldDirectoryName = 'tests-output/my-app';
$scaffoldDirectory = __DIR__.'/../'.$scaffoldDirectoryName;
if (file_exists($scaffoldDirectory)) {
if (PHP_OS_FAMILY == 'Windows') {
exec("rd /s /q \"$scaffoldDirectory\"");
} else {
exec("rm -rf \"$scaffoldDirectory\"");
}
}
$app = new Application('Laravel Installer');
$app->add(new NewCommand);
$tester = new CommandTester($app->find('new'));
$statusCode = $tester->execute(['name' => $scaffoldDirectoryName], ['interactive' => false]);
$this->assertSame(0, $statusCode);
$this->assertDirectoryExists($scaffoldDirectory.'/vendor');
$this->assertFileExists($scaffoldDirectory.'/.env');
}
public function test_on_at_least_laravel_11()
{
$command = new NewCommand;
$onLaravel10 = $command->usingLaravelVersionOrNewer(11, __DIR__.'/fixtures/laravel10');
$onLaravel11 = $command->usingLaravelVersionOrNewer(11, __DIR__.'/fixtures/laravel11');
$onLaravel12 = $command->usingLaravelVersionOrNewer(11, __DIR__.'/fixtures/laravel12');
$this->assertFalse($onLaravel10);
$this->assertTrue($onLaravel11);
$this->assertTrue($onLaravel12);
}
}

66
tests/fixtures/laravel10/composer.json vendored Normal file
View File

@ -0,0 +1,66 @@
{
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.1",
"guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^10.10",
"laravel/sanctum": "^3.3",
"laravel/tinker": "^2.8"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",
"laravel/pint": "^1.0",
"laravel/sail": "^1.18",
"mockery/mockery": "^1.4.4",
"nunomaduro/collision": "^7.0",
"phpunit/phpunit": "^10.1",
"spatie/laravel-ignition": "^2.0"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi"
]
},
"extra": {
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}

69
tests/fixtures/laravel11/composer.json vendored Normal file
View File

@ -0,0 +1,69 @@
{
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.2",
"laravel/framework": "^11.0",
"laravel/tinker": "^2.9"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/pint": "^1.13",
"laravel/sail": "^1.26",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.0",
"phpunit/phpunit": "^10.5",
"spatie/laravel-ignition": "^2.4"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --ansi"
]
},
"extra": {
"branch-alias": {
"dev-master": "11.x-dev"
},
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "dev",
"prefer-stable": true
}

69
tests/fixtures/laravel12/composer.json vendored Normal file
View File

@ -0,0 +1,69 @@
{
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.2",
"laravel/framework": "^12.0.1",
"laravel/tinker": "^2.9"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/pint": "^1.13",
"laravel/sail": "^1.26",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.0",
"phpunit/phpunit": "^10.5",
"spatie/laravel-ignition": "^2.4"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --ansi"
]
},
"extra": {
"branch-alias": {
"dev-master": "11.x-dev"
},
"laravel": {
"dont-discover": []
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "dev",
"prefer-stable": true
}